8
$\begingroup$

I'm creating a renderer using modern OpenGL (3.1 and up) and now I'm trying to create an efficient but flexible way of handling uniforms. I've been reading up on uniform buffer objects and on what a 'common' approach is to using these (the latter unfortunately didn't give me as many results as I had hoped).

In order to reduce OpenGL API calls and storing data in contiguous memory, I'm considering creating multiple large buffers for each data structure that should be uploaded to the GPU. Each buffer has a maximum size of 16kb (as from what I understand this much is guaranteed to be available for an UBO). When an object wants to be able to upload uniforms to the GPU it fetches the first buffer of the to-be-uploaded-type that isn't full yet and gets the next available index in that buffer. When the object is drawn it binds the UBO (if not bound yet) and uploads the element index of the UBO.

This results in something like this:

layout(std140) uniform ModelData { 
    mat4 model_matrix[kNumInstancesPerModelUbo]; 
}
uniform int u_ModelDataIndex;

layout(std140) uniform SkeletonData { 
    mat4 bone_transforms[kNumInstancesPerSkeletonUbo][kMaxBones]; 
}
uniform int u_SkeletonDataIndex;

However I'm also considering the following:

layout(std140) uniform MeshData {
    mat4 model_matrix[kNumInstancesPerMeshUbo];
    mat4 bone_transforms[kNumInstancesPerMeshUbo][kMaxBones];
}
uniform int u_MeshDataIndex;

In some ways this feels a lot cleaner in that it takes a single index to access all data related to the mesh to be uploaded. On the other hand this could get out of hand (buffer size larger than 16kb, handles to irrelevant data (e.g. a mesh with no skeleton) or even synchronization issues as you're not allowed access to say the bones while uploading the model matrices) and I'm not sure how this would affect the memory layout on the GPU either.

Frankly it feels like I'm stuck here and I can't find a good concrete example of how you'd go about handling UBO's fast and flexible.

Do you have any advice or resources for me that could help me out here?

$\endgroup$
2
$\begingroup$

Suballocating from a larger buffer is absolutely the way to go, with caveats. I'm coming more from a DirectX/Vulkan side of things, but this should apply equally to OpenGL (I just won't have direct API calls here in this answer). The things to consider are the following:

  • Do you need to index into the larger buffer, or are you OK with binding the resource to the offset each time?
  • Have you taken care of any/all alignment restrictions for your uniforms that are packed together (256 byte alignment is common)?

Newer graphics APIs have a "dynamic offset" you can specify with the draw command which is a pretty fast way to indirectly access a subregion of a buffer. However, assuming you are triple-buffering data that's mutable, there should be little to no contention in the driver for binding the data (just some fixed overhead).

In summary, yes, allocating larger regions of memory/buffers and subleasing those regions is considered best practice. This applies even to objects with different shaders (if your allocator can handle it).

$\endgroup$
0
$\begingroup$

Include a benchmarking phase for of both solutions in your application and then select the winning solution at runtime. This is simple, portable, and future proof. I mean, you do have test for this, right? ;-)

I know this is a pretty generic answer to "best practice" for high performance but if you think of it there are thousands of possible target configurations and many vendors to consider. If you need that little extra, go pay your vendor for a driver optimized for your application.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.