2
\$\begingroup\$

Currently I have only a really basic shader and a shader class.

My question is that if I want to make different shaders (with different uniforms, inputs, etc) how should the architecture look like?


Edit: I want it in a way that I can add/remove/modify shaders without recompiling.

(#1, #2, #3 were written before this edit, so I'm sorry if they are irrelevant now)

(I wrote #4 just now, and currently I am very happy with it, but I would like to hear your opinion about it.)


I have a few ideas:

(1.) Should I make an abstract shader class, and a derived class for every invidual shader program?

  • (So the abstract class handles the general things, and the derived class handles the specific things, like uniforms, inputs, etc)

But in my opinion this isn't a good practice, because for every shader I create, I have to create a new class too, which means that if I want to add a new shader, or modify one's source, I have to recompile the engine.

(2.) Should I register the shader specific things (location of inputs, uniforms, etc.), and pass EVERY possible required data to the shader, which will use only the ones which were registered during the shader's compile time?

  • Like Shader A needs position and normal, Shader B needs position and UV.

  • position, normal and UV will be passed to the active shader object, and it will buffer only those which it needs (based on the registered inputs, uniforms, etc) and won't care about the others.

(3.) Having every possible input, uniform etc in all the shaders. The active shader will receive every required input, uniform, etc. So a shader is only unique in its main(). Like Shader A uses input 1,2,3, shader B uses input 1,2,5. (But both of them will receive all inputs)

These are the way I thought of, but these aren't very good in my opinion, there must be a good way for this.


Edit:

(4.) What I most recently thought of:

  • Only one shader class. has a field: unordered_map < name, location at shader >

  • During shader initialization, I store the input, uniforms, etc in the previously mentioned map.

  • When I want to render the Object, I call the active shader's SetInput(...) or SetUniform(...) method

  • These methods receive a string and a data (like vec3, vec4, mat4, etc)

  • In these methods, I check if the string is in the map, and if it is, I buffer the data to the location.

I think this is good, because there will be a few method calls for every object, and then finding a key in an unordered_map is really fast.

What do you say?

\$\endgroup\$
  • \$\begingroup\$ I would say answer to this will marginally differ depending on how many shaders you have, whether new shaders can be added and if so whether it is acceptable to re-compile when new shader is added/changed. \$\endgroup\$ – wondra Jan 2 '17 at 17:10
  • \$\begingroup\$ @wondra I would like to avoid recompiling. I edited the post at the start and at the end of it. \$\endgroup\$ – Tudvari Jan 2 '17 at 17:26
  • 1
    \$\begingroup\$ Regarding your edit(4.), isnt it exactly what the driver does for you (except maybe some added type safety)? I tried answer the question myself(following similar thought process) few years ago, and I came to the conclusion there is no good way. Closest I could get was writing own shader parser that will attempt to resolve semantically input/output and map them onto well-known locations with fallback to the string interface - complexity of this solution brought me back to the first question, whether I even need dynamically loaded shaders if the input I can provide to shader are limited anyway. \$\endgroup\$ – wondra Jan 2 '17 at 17:43
  • \$\begingroup\$ Is there a "better than the other" way? Or I shouldn't even bother with dynamic shader linking, because it will be rarely make profit but always slow down my engine? \$\endgroup\$ – Tudvari Jan 2 '17 at 18:44
  • 1
    \$\begingroup\$ There should be no additional cost, any unused variables should be optimized out by drivers however, setting such uniform should result in OpenGL error. \$\endgroup\$ – wondra Jan 2 '17 at 19:49
2
\$\begingroup\$

If your hardware supports OpenGL 4.1+, you can use separate shader objects and pipeline objects to create new pipelines on the fly:

https://www.khronos.org/opengl/wiki/GLSL_Object

In this way, you can load your shader stages seperately, and combine them as necessary.

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

Your options are:

  1. Determine all the shaders you will need at the start of your game (using the materials of your meshes for example) and compile and link them to shader programs there. Later when rendering you will change the current program with glUseProgram(ID) based on the mesh's material.
  2. If you need to assemble shader programs dynamically, you will have to at least link the different modules together before you need them. You could compile a collection of vertex and fragment modules at startup and then link them together based on a more modular material component from your mesh.
\$\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.