0
\$\begingroup\$

I'm making some test with OpenGL and I've got a problem with my application design. Currently, I've got something like that in pseudo code:

// Ask to draw a given scene.
renderer->drawScene( myScene, myCamera );

// Here is what I do in the renderer
for( auto& mesh : scene->getElements() )
{
    this->draw(mesh);
}

// And here is what my draw method do.
// First geometry stuff.
context->setVertexFormat( mesh->geometry->vertexFormat );
context->setVertexBuffer( mesh->geometry->vertexBuffer ); // Use VAO is available.
context->setIndexBuffer( mesh->geometry->indexBuffer );

// Second, material stuff.
context->setProgram( mesh->material->program );
mesh->material->program->setParameter( "model", mesh->transform->position );
mesh->material->program->setParameter( "uCamera", camera.matrix );
context->setBlendMode(mesh->material->blendMode);

// Finally draw.
context->draw( mesh->material->drawingMode, mesh->geometry->indiceCount );

The problem is present with shader attributs, by exemple from main loop:

int main()
{
    while()
    {
        // I want change texture offset for the mesh N°7, that will change it for every mesh.
        myMesh7->material->program->setParameter("offset", 15.f);    


        renderer->DrawScene(myScene, myCamera);
    }  
}

How can I have a flexible design? A Shader/Program is shared with others meshes, so in the case where I would change "color" or "offset" attribute in my shader for only one mesh it's not possible. How can I handle that?

(I don't think it will be good to copy somewhere shader attributs per mesh)

Thanks for your help

\$\endgroup\$
1
\$\begingroup\$

The thing to realize when trying to design a rendering architecture is that OpenGL (and GPU programming in general) is all about registers and state. You have to bind buffers and textures and then issue draw calls, not issue draw calls that come with data.

Although this seems limiting, it's very efficient. Because GPUs often offload operations like texture filtering to dedicated silicon (like texture units), these operations are very fast. The downside is that there are a limited number of specialized units, and they are unable to access just anything in memory. Similar to CPUs, GPUs work best when their memory is in a quick register or cache, and binding operations allow the programmer to guarantee that a variable, buffer, or texture is loaded into a register or assigned to a piece of dedicated silicon.

Many operations that deal with binding are very expensive. They may need to flush the pipeline and DMA data from system memory to video memory. In particular, texture and shader binding are very expensive operations. To make sure that you aren't wasting too much time, you want to ensure that you are binding resources as little as possible.

The best way to do this is to define a high-level rendering system that transforms a list of renderables into a sequence of commands (that are implemented as OpenGL function calls). A renderable associates various pieces of rendering data to represent elements such as objects and lights, while a command maps to one or more related OpenGL function calls.

Internally, this system is essentially just a big sorting algorithm. Renderables are sorted according to their most "expensive" pieces of rendering data. Primarily, renderables would be sorted by texture first and shader next, while uniform changing is not a big priority for sorting. You can think of it as organizing the renderables into a tree, where the leaves are draw calls and the nodes are bindings. You can then walk the "tree" and produce the commands.

As an example, here's an unsorted list of renderables:

  • Small rock (rock texture, static shader)
  • Player (skin texture, animated shader)
  • Door (wood texture, static shader)
  • Big rock (rock texture, static shader)
  • Cart (wood texture, animated shader)

And here would be an optimized command sequence to draw them (assuming one texture unit):

  1. Bind static shader
  2. Bind rock texture
  3. Draw small rock
  4. Draw big rock
  5. Bind wood texture
  6. Draw door
  7. Bind animated shader
  8. Draw cart
  9. Bind skin texture
  10. Draw player
\$\endgroup\$
0
\$\begingroup\$

I assume you know in loop when it is time to draw mesh in a different way. What you can do is this

While drawing every object

if it is time for your special mesh
set uniform variable x = 1 (you should use the corresponding gluniform1i(...)
else
set uniform variable x = 0

and in your shader program, you will have a uniform integer x in the code

if ( x == 0 )
do it usual way
else if ( x == 1)
do it the specific way

This way, when it is time for your specific mesh, your C++ code will set x to 1 using gluniform1i and your shader code will behave differently for that code. You can add as many parameters as you like to control your shader code using uniforms.

Alternatively, you can create a different program with separate shader code that includes only the part that should be executed when it is time for the specific mesh, and you can enable it when the right time comes (but this is a very bad idea for a minor change)

\$\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.