7
votes

I'm developping a little 3D Engine using OpenGL and GLSL. I currently use Texture Buffer Objects (TBOs) to store all my matrices (Proj, View, Model and Shadow Matrices). But I did some researches on what is the best way to handle matrices (I mean the most efficient way) within a graphic engine, without any success. The goal is to store a maximum of matrices into a minimum number of TBO and occur a minimum of state changes and a minimum of exchanges between the GPU and client code (glBufferSubData).

I propose 2 different methods (with their advantages and disadvantages):

Here's a scene example:

1 Camera (1 ProjMatrix, 1 ViewMatrix) 5 boxes (5 ModelMatrix)

Here's an example of a simple vertex shader I use:

#version 400

/*
** Vertex attributes.
*/
layout (location = 0) in vec4 VertexPosition;
layout (location = 1) in vec2 VertexTexture;

/*
** Uniform matrix buffer.
*/
uniform samplerBuffer matrixBuffer;

/*
** Matrix buffer offset.
*/
uniform int MatrixBufferOffset;

/*
** Output variables.
*/
out vec2 TexCoords;

/*
** Returns matrix4x4 from texture cache.
*/
mat4 Get_Matrix(int offset)
{
    return (mat4(texelFetch(
        matrixBuffer, offset), texelFetch(
            matrixBuffer, offset + 1), texelFetch(matrixBuffer, offset + 2),
                texelFetch(matrixBuffer, offset + 3)));
}

/*
** Vertex shader entry point.
*/
void main(void)
{
    TexCoords = VertexTexture;
    {
        mat4 ModelViewProjMatrix = Get_Matrix(
            MatrixBufferOffset);
        gl_Position = ModelViewProjMatrix  * VertexPosition;
    }
}

1) The method I currently use: in my vertex shader I use to use ModelViewProjMatrix (needed for rasterization(gl_Position)), ModelViewMatrix (for lighting calculations) and ModelMatrix. So to avoid useless calculation within the vertex shader I've decided to store the ModelViewProjMatrix, the ModelViewMatrix and the ModelMatrix for each mesh node inlined in the TBO as follow:

TBO = {[ModelViewProj_Box1][ModelView_Box1][Model_Box1]|[ModelViewProj_Box2]...}

Advantages: I don't need to compute the product Proj * View * Model (ModelViewProj for example) for each vertex shader (the matrices are pre-calculated).

Disadvantages: if I move the camera I need to update all the ModelViewProj and ModelView matrices. So, a lot of informations to update.

2) I thought about an other way, I think more efficient: store once the projection matrix, once the view matrix and finally each box scene node model matrix once again this way:

TBO = {[ProjMatrix][ViewMatrix][ModelMatrix_Box1][ModelMatrix_Box2]...}

So my vertex shader will look like this:

#version 400

/*
** Vertex attributes.
*/
layout (location = 0) in vec4 VertexPosition;
layout (location = 1) in vec2 VertexTexture;

/*
** Uniform matrix buffer.
*/
uniform samplerBuffer matrixBuffer;

/*
** Matrix buffer offset.
*/
uniform int MatrixBufferOffset;

/*
** Output variables.
*/
out vec2 TexCoords;

/*
** Returns matrix4x4 from texture cache.
*/
mat4 Get_Matrix(int offset)
{
    return (mat4(texelFetch(
        matrixBuffer, offset), texelFetch(
            matrixBuffer, offset + 1), texelFetch(matrixBuffer, offset + 2),
                texelFetch(matrixBuffer, offset + 3)));
}

/*
** Vertex shader entry point.
*/
void main(void)
{
    TexCoords = VertexTexture;
    {
        mat4 ProjMatrix = Get_Matrix(MatrixBufferOffset);
        mat4 ViewMatrix = Get_Matrix(MatrixBufferOffset + 4);
        mat4 ModelMatrix = Get_Matrix(MatrixBufferOffset + 8);

        gl_Position = ProjMatrix * ViewMatrix * ModelMatrix * VertexPosition;
    }
}

Advantages: The TBO contains the exact number of matrices used. The update is highly targeted (if I move the camera I only updates the view matrix, if I resize the window I only updates the projection matrix and finally if a object is moving only its model matrix will be updated).

Disadvantages: I need to compute fo each vertex within the vertex shader the ModelViewProjMatrix. Plus, if the scene is composed of a huge number of object with each of them owning a different model matrix, I probably need to create a new TBO. Consequently, I will loose the proj/view matrix information because I won't be connect to the right TBO, which bring us to my third method.

3) Store the Projection and View matrix in a TBO and all the other model matrices within another or others TBO(s) as follow:

TBO_0 = {[ProjMatrix][ViewMatrix]} TBO_1 = {[ModelMatrix_Box1][ModelMatrix_Box2]...}

What do you think of my 3 methods ? Which one is the best for you?

Thanks a lot in advance for your help!

1
Why do you use a TBO instead of a uniform buffer? Because of the size limit?Jerem

1 Answers

4
votes

The solution 3 is what most engines do, except they use uniform buffers (constant buffers) instead of texture buffers. Also they don't generally group all the model matrices together in the same buffer, they usually are grouped by object type (because same objects are drawn at once with instancing) and sometimes by frequency of update (objects that never move are in the same buffer so that it never needs to be updated).

Also glBufferSubData can be pretty slow; updating a buffer is often slower than just binding a different one, because of all the synchronization happening inside the driver. There is a very good book chapter about that, freely available on the Internet, called "OpenGL Insights: Asynchronous Buffer Transfers" (Google it to find it).

EDIT: The nvidia article you linked in the comments is very interesting. They recommend using glMultiDrawElements to make several draw calls at once (that's the main trick, everything else is there because of that decision). That can reduce the CPU work in the driver a lot, but that also mean that it's a lot more complicated to provide all the data required to draw the objects: you have to build/update bigger buffers for the matrices / material values and, you also need to use something like bindless textures to be able to have different textures for each object. So, interesting, but more complicated.

And glMultiDrawElements is only important if you want to draw a lot of different objects. Their examples have 68000-98000 different meshes, that's really a lot. In a game, for example, you usually have lots of instances of the same objects, but only a few hundred of different objects (maximum). In the end, it depends on what your 3D engine needs to render.