Reducing switching shader programs

Hi! I am profiling my application (with codeXL) and apparently I am switching shaders too often.
I would like to receive some suggestions to reduce it.

I have investigated and here are my ideas (please, feel free to comment them :slight_smile: ):

  1. currently I do: each mesh renders itself using its associated shader.
    I would like to change it in: each shader renders all the meshes that use it.
  2. also reducing uniform settings: from the profiler I don’t understand if setting uniforms values (for example calling glUniform3f ) affects performance.
    I know that every glGet (for example glGetUniformLocation ) affects performance, so I am planning to manage it.

Any tips / suggestions are appreciated :slight_smile:

Generally speaking, shader changes are relatively expensive, but changing loose uniforms (e.g. glUniform3f()) is relatively cheap. Double-check this on your test platform(s) though. Assuming you find the same to be true there, bin your rendering by expensive state changes (e.g. framebuffer binds first, and then shader binds) to minimize the number of those expensive state changes.

That said, I wouldn’t blindly trust the profiler. Do some overall CPU and GPU timing and just look at where your frame time is going (first on the CPU, then on the GPU). Find the biggest fish and go after it (biggest being biggest consumer of your frame time). That’s where the most potential gain is. Adding some “knobs” to just turn major rendering layers and effects on and off can greatly speed up isolating the frame time hogs, and all without runs in a profiler.

For instance, are you wasting CPU and GPU time sending meshes down the pipe (along with associated shader binds and uniform changes) which are completely offscreen? That’s 100% wasted time.

1 Like

Currently I don’t have that problem, I test if my geometry is offscreen before rendering :slight_smile:

And talking about the geometry, I am batching elements such as lines and points and planning to do it also for other elements in my scene.

Thanks for this info. I thought that even calling functions such as glUniform3f() were extremely expensive and should be avoided.

At this point I think the best solution is implement and test my option 1.

Thanks again :smiley:

  1. Try to batch meshes, i.e. order draw calls so that those using the same shader are grouped together.

  2. Try to reduce the number of different shaders so that you have fewer, larger batches. Modern hardware has actual branch instructions so branches which aren’t taken due to uniform conditions (those which don’t change per vertex or per fragment) don’t have a performance cost. You don’t need to use distinct shaders just to enable/disable specific features.

Distinct shaders should only be needed where there are fundamental differences to the rendering process. E.g. use or not of a geometry shader, differences in the number of render targets, use or not of features such as discard or writing to gl_FragDepth which affect the shader simply by their presence in the code regardless of whether they are executed, etc. Try to restrict shader differences to different render passes or substantially different types of object (static vs dynamic meshes, translucent vs opaque, “ordinary” surfaces vs special effects, etc).

While largely true for the branch instructions themselves, you do need to be somewhat careful with the potential side-effects of this approach.

Taking an A shader and a B shader and turning them an if ( cond ) { A } else { B } shader often involves the shaders utilizing more registers, possibly doing more memory accesses, and taking more resources as inputs. More registers means fewer shader invocations running in parallel, which means less memory latency can be hidden by shader switching. This can reduce your throughput.

The take-away from this is, don’t feel like you have to carry this to the limit and render your entire scene with one shader. Balance it against the cost of switching shaders on your platform(s) and the max number of shader switches you need to do.