Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

This question already has an answer here:

For any given object that you want to render, there may be a whole bunch of things that need to be considered for rendering (Material, Texture, Lighting, Blending etc). But, you may also have some objects which are very simple, and do not have a Texture or material or require any blending and ignore lights.

So, how do people approach this. Do you write one GLSL shader that has various booleans and checks that need to be set as vertices are pumped through, or do you write separate shaders for each general category of object, and switch between these as your render?

share|improve this question
    
Same question as gamedev.stackexchange.com/questions/49779/… except for using OpenGL instead of D3D. (But the answer is the same for either API.) –  Nathan Reed Mar 13 '13 at 22:08
    
This question seemed to be asking about shaders, not about pipelines, so I applied an edit to make that more obvious for readers and future searchers. If I misunderstood your intent, please revert. :) –  Trevor Powell Mar 13 '13 at 22:09
add comment

marked as duplicate by Nathan Reed, Sean Middleditch, Ali.S, Nicol Bolas, Patrick Hughes Mar 14 '13 at 6:06

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

2 Answers

To answer your specific question, and focussing on two recent OpenGL titles:

  • A frame of Doom 3 BFG Edition can use ~12 different GLSL programs.
  • A frame of Rage can use ~38 different GLSL programs.

(Captured using GL Intercept).

So the answer is a "yes" - OpenGL games (or at least these ones) will use multiple shaders.

As a general case answer it's more a case of "it depends". The choice to use multiple shaders or using branching can often be down to which is the more expensive option - making a shader change or having every vertex or fragment processed have to take a branch.

In the old days this was pretty clear cut - the first shading languages available didn't support branching at all, so you had no option - you used multiple shaders.

As branching support started coming in, things got interesting. First off, you might want to support older hardware, in which case you likewise didn't have a choice - no branching. Secondly, the original forms of shader branching were quite primitive; basically both branches would be executed and the internal hardware registers would be updated based on which one passed (i.e. branching was most likely just emulated using a mix/lerp/step/etc instruction - you can still see this today in HLSL code if you compile with branching disabled and view it's disassembly). So again the best option was to not branch.

Modern hardware is, of course, more flexible and you have 2 forms of branching available. Branching based on a shader uniform (GLSL) or constant (HLSL) should be very cheap indeed - not free, but the hardware can know that all executions of the shader for the current set of states will take the same branch, and optimize accordingly.

Branching based on some value determined at runtime (or input via a vertex attrib) is more interesting. Modern hardware will execute shaders in "groups" (I'm avoiding vendor-specific terminology here) of vertices or fragments, and if all executions of a shader for a given "group" take the same branch, it can also pull some optimizatios.

There's also the fact that GLSL complicates things a little by having current uniform values be part of program state. HLSL doesn't have this, and it's not the way hardware works, but that's the decision that was made for GLSL, so what it means is that a shader change could also involve saving out the existing contents of hardware registers, then loading in new values as part of the new program state. Using UBOs would also avoid that, of course, but if you're using standalone uniforms then you need to be aware that changing shaders can involve a good deal more than just swapping out some small executable images.

None of this is to say that having a single big shader with lots of branching is the preferred option, of course. Branching still has it's own cost, and it may well incur more overhead than a shader change, even on modern hardware. You also need to consider that your vertex shader input is quite tied to the shader itself, unless you use a common vertex format and layout for everything. On modern hardware with geometry and tesselation shader stages you may have to deal with cases where these (optional) shader stages may or may not be active in the current program. Beware especially of the geometry shader, because it can be sloooooow - even if just a passthrough shader - so you'll not want it active unless you're doing something that explicitly requires it (such as writing to a non-zero viewport index).

So all this lengthy preamble was a fairly roundabout way of coming to the usual conclusion to questions such as this: everybody's use case is different, so profile, benchmark, find which is the fastest for you, decide which performance vs flexibility/code-cleanliness tradeoffs you will or won't accept, and base your code around these real-world metrics.

share|improve this answer
    
Thanks for the thorough answer. At the moment I am just implementing ever more complex shader pipelines as I learn more. So at my current beginner knowledge level I believe I will aim for a good general purpose shader programs with some branching, and look at specialised swapping if I encounter performance issues. –  LGW Mar 14 '13 at 21:53
add comment

In general, I believe that what you want to do is minimize the number of times that you change OpenGL state. That means calls to glUniform, glUseProgram, etc should be avoided as much as possible. To do that, you'd want to group all of your objects such that those that require the same OpenGL state get rendered together (i.e. render objects without textures and materials all at once).

The way this usually works is that each object that will be rendered will use a specific "material"... not in the OpenGL sense, but rather in the "what state settings do I need to render this" sense. These include textures, shaders, and other attributes that are needed for rendering. What you want to do is group as many of these "materials" together in sequential draw calls to avoid changing parameters, or at least, change very little.

share|improve this answer
add comment

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