The 2024 Wheel Reinvention Jam just concluded. See the results.

Support complex meshes in current renderer

Hello I was wondering how complex meshes(with a lot of vertices) could be handled in a nice way in the current handmade hero's renderer. Would creating a new queue for submitting mesh data into a big vertex buffer be a good idea?


Edited by clivi on

I would look into how textures are handled in HH code - and do meshes similarly. Textures also have notion of "complex" data - a lot of pixels, that you don't want to transfer on every frame. So it gets loaded into GPU memory only once, and then referenced with handle every time you want to use them.

Let the pure graphics engine have basic models with one set of triangles per material (one per draw call), so that you have a clean, fast and reusable foundation that is bug free before doing anything game specific.

The game engine can then connect multiple models using instances in a compound model format where you can add recorded animations, collision shapes, physical constraints, particle emitters, sound effects, transmission torque graphs, damage behaviors, scripting, et cetera. If the game engine derails into something bad, you can just delete it and start fresh from the graphics engine.

A compound model can for example have a list of unique models, where each model has a list of relative locations and custom shader constants/uniforms. Either use it at runtime while rendering with recorded animations, or use it as a template when loading for more individual control over each instance when doing game logic and physics.

Ok that sound smart but how would you actually refer to the meshes? Would you use a index or some kind of id (chosen by the game-code since there is no way to ask to the opengl layer for an id)?


Edited by clivi on

Indices are the easiest to debug, but the problem with indices is that you have to update them if an element is removed from a list, which can lead to many bugs if done manually in complex data-structures. I made a database system where each attribute holding element indices can refer to a target by table index and automatically update its indices when elements in the target table are deleted or moved.

You can have a tuple of index and sparse 64-bit ID, so that the index is updated by reference using a search for the ID if the index no longer refers to the correct ID. Then you never reuse the ID number and just keep counting up, because you assign enough bits to not run out of them for thousands of years.

Another solution is to use reference counted pointers in C++. The disadvantage with this are that you are limited to C++, fragmented memory is slower than a densely packed array and you cannot save the pointers automatically to files. The advantage of pointers is that they will remain the same until deleted. Needs manual conversion when storing in files, which may become a source of bugs that are hard to find.

Merging vertex data with automatic grouping of low detailed objects reduces the number of draw calls sent to the GPU over PCI express and may improve rendering performance tenfold if you have a scene cluttered with low polygon meshes of the same material. Then you can afford hundreds of light sources casting dynamic shadows all over the scene by not being limited by the draw calls. Hardware instancing can also be useful to reduce draw calls depending on the situation.


Replying to clivi (#26249)

Sorry I may have expressed myself wrong.

In the current renderer textures are referred with an index since they are stored in a texture array. Take a look at the following struct:

union renderer_texture
{
    u64 Packed;
    struct
    {
        // NOTE(casey): You could pack an offset in here if you wanted!  Just use
        // a 16-bit index.
        u32 Index;
        u16 Width;
        u16 Height;
    };
};

I was asking how to store the models and how to refer to them. For example should i pretend i have a "model array" where i refer to them with indices(not graphics indices) or should i store them in one single vertex buffer and refer to them with buffer ranges(offset,count)?


Edited by clivi on

If you have only static meshes, then packing them into one VBO and then using offsets into it is perfectly fine.

For dynamic data it may be not so optimal because then driver will need to deal with extra synchronization when you're updating it while it is still being used.

In more modern GL versions you can use persistently mapped buffers which allows you to access memory on CPU without mapping/unmapping. Then you can deal with synchronization places more explicitly and update parts you want to update without significant performance implications.

But if you're only starting with your rendering with simple stuff, then I suggest keep things simple - use one VBO per one mesh/piece/geometry thing and don't worry about it. Optimize it later when it starts to become problem.


Replying to clivi (#26257)

Thanks for the good suggestions. Right now I already got the renderer working and i am already using a vertex buffer per mesh but I came here to learn about a "good"(in terms of performance and api design) way to do it.

After taking a look at the answers I was thinking about allocating a big vertex buffer and use a free-list allocator to store the meshes's vertices(the meshes are static however I would like to "stream" the into GPU memory just like textures). Do you think this is a good in terms of performance and api design?


Edited by clivi on

No, you should not stream static meshes and textures to GPU. Just upload them once, and reuse them for next draw calls. Stream only the data that is newly loaded or which changes all the time.


Edited by Mārtiņš Možeiko on
Replying to clivi (#26270)

With "streaming" i don't mean that i submit every texture/vertex mesh data to the GPU every frame but I mean keep render memory on the GPU memory that I only care about in a particular moment(for a particolar frame).

If I have 8GBs of textures I should stream them on the GPU since I can't have every asset loaded on the GPU because it just would be too much memory. In the case of the texture array, textures are streamed in based on the current scene. If the texture array is full the least used texture will be evicted. I would want to do something like that for meshes as well.


Edited by clivi on

That kind of streaming would be overkill unless you are making a game like Skyrim with thousands of artists working full time, in which case streaming could easily be added in the game's code when needed. Just load assets from disk when getting close enough to almost see them at the horizon and erase the furthest ones when the given memory limit is reached.

Very big levels can easily fit into 512 megabytes by reusing textures. Blend-mapping allow generating all the fades needed procedurally while rendering, and you can fade using sharp patterns for better realism, so that stones become smaller or fewer at lower blend weights. Color mixing can reuse a grayscale texture in multiple colors, rendered from the same memory. Multiple layers of textures in different scales can be used to get a fine gravel texture up close while having large patterns without visible repetition when seen from afar. There is also hardware support for texture compression on graphics cards, for textures that are large and not reused so often. For models, you can just use a more efficient vertex structure or detail tessellate.


Edited by Dawoodoz on
Replying to clivi (#26272)