3DO Optimization Types

Joined
Jun 28, 2022
Posts
103
Reaction score
37
Points
28
Website
gaming.tchapman500.com
When making an optimized 3DO, it's important to know what you are optimizing for. If your 3DO is very detailed, but there will only be a few instances (or even just one), then the optimization should be to reduce the 3DO's memory footprint. If your 3DO is very simple, but there will be many of them (such as with the caution light 3DOs), you should sacrifice memory footprint to minimize draw calls. Each mesh is one draw call.

Here's how a draw call works.
The game will first modify the GPU state such that the mesh appears correctly. This means setting the state registers to their correct values, setting the texture registers to point to the correct textures, copying material data to the GPU, and setting the correct vertex and index buffers. Finally, the game sends the draw command to the GPU and the object is drawn to the back buffer.

In NR2003, each mesh may have multiple index buffers. These are in the form of one or more triangle strip arrays (one buffer per array) and an optional triangle list array. If there are no triangle strip arrays, then there will be a single triangle list array. The difference between the two types of arrays aren't important here (though triangle strips are generally faster to draw). What is important is the fact that each of these index arrays results in its own draw call. Practically speaking, this means that (assuming NR2003 is properly optimized) for every mesh, the game has to set the state and texture registers, copy material data, and set the correct vertex buffer, then set each index buffer individually before sending the draw command for that index buffer. Make3do can be told to optimize the mesh using tri-stripping, which may or may not provide a performance benefit on modern systems (I haven't tested that, nor do I know of any tests). For simplicity however, we will consider each mesh to be just a single draw call for this post.

Anyways, a lot of the considerations for mesh optimization will actually come down to state switches. For something like the default caution lights, you will notice that there are 4, nearly identical copies of the mesh (one for each state that the caution light can be in). The only difference is in which of the lamps are lit at any given time. The state switch within the 3DO effectively gates the mesh rendering to just a single mesh at a time. So we can consider each instance of the caution light 3DO to be a single draw call. It makes sense to make the caution light 3DO in this manner, since the mesh is relatively simple and there are going to be a lot of caution lights around the track. The file is larger than it could be, with the 4 meshes making the memory footprint almost 4 times larger than what it could be, but each caution light only needs one draw call. If we were to optimize it to minimize memory footprint and file size, we would need 2 draw calls. One draw call for the part not dependent on the state of the light, and the second draw call for the part that does depend on the state of the light.

Interestingly enough, truck_tron.3do also appears to be made in this manner, even though there are only going to be a few of those at any track at most. The default Talladega track has just one. Though this can still be somewhat justified by the simplicity of the mesh, which itself is justified by the fact that it's never going to be viewed up close. But in this case, because there are so few of these at any given track, it would be perfectly reasonable to have everything except the animated part outside of the animation state switch. That would be two draw calls at most. Note that for this 3do, there's a weekend and population state switch that will hide the 3do under certain conditions, resulting in zero draw calls for that object. There's also a state switch that turns off the animations. So in this case, we could, in theory, have only one draw call for when the object isn't being animated, but two draw calls when it is. And of course, zero draw calls when the weekend or population state switches have turned it off. The population state switch is controlled by the user's graphics settings, under "Trackside Objects".

There is another point I want to touch on. And that's having multiple, identical copies of the mesh with the only real difference being the texture that's applied to them. This is a limitation of using make3do, as PSG scripts must make a copy of the mesh in order to apply a different texture to it. The 3DO file itself has no such limitations. This means that objects like truck_tron.3do has 8 copies of the mesh that has the animation. And the grandstands also have two copies of their meshes, one with crowds in them, and one without. But there is nothing in the 3DO file itself that requires us to have one copy of each mesh per texture that we want to apply to it. Remember, each mesh is one vertex buffer plus one or more index buffers. So making a 3DO file that has only one copy of a mesh where only the textures are swapped-out would be a tremendous memory footprint optimization with no downsides. All we need are the proper tools.

While much of what I said here uses track 3DOs as examples, some of this also applies equally to car 3DOs. Especially the point about not needing to make a copy of the mesh for the sole purpose of retexturing it. The main issue that one has to consider is memory footprint vs the number of draw calls against the number of instances one expects to be in the scene. Simple 3DOs (such as boxes textured to look more complex) should be optimized for the fewest draw calls. Detailed 3DOs should have a single copy of their most detailed meshes to reduce memory footprint, while the simpler meshes within detailed 3DOs should be optimized to reduce the number of draw calls. By far the most detailed meshes should be the vehicles that will be driving around the track, or objects that can be viewed up close during normal gameplay, but will only have a small handful of instances ever. NR2003 can only use a maximum of 4 GB of RAM. That might sound like a lot at first, but it will quickly be taken up by the many 3DOs and their textures that need to be loaded, and the track geometry and its textures. All of which must remain in memory and have room left over for the game to store stuff like scoring and replay information.

Edit: It should also be noted that if your model uses multiple textures, you will need to split your mesh up into multiple meshes. One mesh per texture. Where possible, you should combine multiple textures into a texture atlas to reduce the number of meshes that are generated. Make3do will make the necessary splits automatically. A couple other things to keep in mind is that faces that triangles that share a single point that have a common normal and texture coordinate can share the same vertex. But if the texture coords or normals of two triangles that share a single point are different, then you will need one vertex per triangle that does not share both a texture coord and normal with another triangle. Make3do will not always correctly generate these extra vertices, which will result in the model being incorrectly shaded in-game. So you will have to make the necessary splits in the modeling program in order for it to be shaded correctly in game.
 
Last edited:
Regarding mods and draw calls, say you have 2 mods, both have the same exact vert count

Mod 1 has these pieces:
chassis
windows
interior

Mod 2 is split up a bit more:
chassis
windows
engine
rollbars
underbody

Would mod 2 essentially be 80+ additional draw calls when a full field is on track since its 2 additional separate meshes? Or is the mod itself a single draw call and only has more when lods or damage state switches?

Mods get really complex and certain pieces can't be merged for example the window with translucent textures need a higher mip priority than the other meshes so the z-depth is correct even though the windows alone is usually light on vert count alone. All my mods have aggressive lod levels but even though I've not received reports of my mods being bad for performance I've found more and more ways to optimize them so the LOD 1 level will still be high detail but uses far less verts than is needed.
 
There will be one draw call for every mesh that is not gated by a state or lod switch per vehicle, and one draw call per mesh per vehicle that is enabled through a state or lod switch. So with a 40-car field and no gating certain parts of the mesh behind a state or lod switch, you would indeed get 80 more draw calls on the mod that has 5 meshes than on the mod that has only 3 meshes. And that's before taking into account the wheels, which are an additional draw call (minimum) per wheel per vehicle.

I do understand that mods are very complex in nature and therefore probably very hard to optimize (and that's just about it for my knowledge of mods). But the basic principal is still the same. Every mesh that the game renders (even if it is hidden by another mesh) is one draw call per instance. I believe what you are doing with LOD switches is good practice, saving draw calls for vehicles that are close to the camera over vehicles far from the camera.
 
If the meshes are grouped together so they nest to a single lod level does it still count as separate draws or just a single one?

For example in a mod I group all 5 together like this:

Code:
car group: GROUP (chassis, windows, engine, rollbars, underbody)

this group is then used as a single lod level like this:

Code:
car_LOD:   STATIC_LOD (              

                                            0         car group,
                                            7.0     separate group of meshes for lower lod level,
                                            17.0     separate group of meshes for lower lod level,
                                            30.5    separate group of meshes for lower lod level,,
                                            48.2    separate group of meshes for lower lod level,,
                                            70.0   separate group of meshes for lower lod level,


etc etc etc
 
This is great and very important background info on game-ready asset authoring, as it applies to Papy 3DO

"All we need are the proper tools."---might I add: due awareness on the side of content authors ("know your PSG and final use")
it's unfortunate a single 3DO can be so stuffed up with all sorts of data (11 MB size: orig cup make_a.3do) ...
⠀⠀... even a casual inspection of Papy's original 3DO authoring is time-consuming
The tried/tested procedure to import assets on a game's engine usually involves:
-- inspect the orig asset (of same characteristics ofc),⠀⠀[very important step]
-- apply found conventions to new asset,
-- convert for final import.
 
With the z modeler 2 you can merge. Objects together .so 1. Object. Has more then. More then 1 texture for it .
 
Last edited:
Here's what I meant about merging objects together .the 2 one has 10 objects the 1 pic has only 1 objects but use the same. Texture's
 

Attachments

  • clp1.jpg
    clp1.jpg
    90.1 KB · Views: 2
  • clp2.jpg
    clp2.jpg
    100.2 KB · Views: 2
Back
Top