Glsl - Which is faster: lots of if statements or lots of seperate shaders?

Started by
7 comments, last by Hodgman 11 years ago
Ive noticed that as I add more features to my glsl shader I have been getting really poor performance. I currently have one shader with multiple blocks of if statements that apply different effects such as normal mapping, specular, lighting and etc... I was wondering if it would be more efficent to just generate a new frag and vert shader for each material in my scene so that I would have no conditonal blocks in the glsl shader. I also would need to know if there is a limit on the number of shader programs you can have, and if constantly switching shader programs during drawing has a major impact on performance.
Advertisement

From experience on mobile platforms (GLSL ES) it is massively faster to use specialised shaders with no conditionals.

I don't have much experience on modern desktop hardware but I've heard they cope pretty well with conditionals, particularly if the conditionals can be evaluated from uniforms (as opposed to, say, vertex data or texture data).

There's no general rule. On one hand, a specialist shader is often faster than a more general uber-shader with a lot of branches. But if you need to subdivide a shader into mutliple passes, you could get in trouble with bandwidth or with state changes if you over-do the number of shaders (e.g. each object get its own shader, so that batching is impossible).

An other option is, to write an uber shader, but to use pre-processor statements instead of dynamic conditions to generate the shader you need on-the-fly.

if statements that cause branching will be slower in a functional (block-data) enviroment
if you use constants, the compiler will work the same way as if it was a #ifdef SOMETHING ... #endif

http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter34.html
outdated, but still useful
especially for mobile GPUs, which i personally pretend don't exist smile.png
A branch will have a cost, say a dozen clock cycles per pixel/vertex, just for existing. If nearby pixels each take different paths, then all those pixels will pay the cost of executing both paths.

When you switch shaders, there's the small CPU cost of making extra GL function calls, and there's the cost GPU command to change the current program, which is interesting:
The GPU is heavily pipelined, doing large work batches at once. As long as the previous shader has been used to process enough work (say, a few hundred pixels), then there will be pretty much no overhead in switching shaders - it's free. If the previous shader was not used for enough work, then you'll create a pipeline bubble, which acts as if the previous shader did perform a lot of work (i.e. if you use 100 shaders but each is only used to fill 1 pixel, it may take as long as if each shader was used to fill 500 pixels -- that's 100 pixels worth of shading costing as much as 50000 pixels worth).

So yes, you want to have several specialized shaders, but not so many that each is only used on a tiny part of the screen. You want to draw as much stuff as possible with each shader before moving on to the next one, so be sure to sort your objects by their material/shader type.

Alright, so you say as long as I fill a good amount of screen space it's good to use multiple shaders. The way I currently have my rendering work is this.

for(objects in the game)

for(models in the object)

for(materials in the model)

renderTriangles();

When the model is loaded it takes every triangle of each material and puts it into a floatBuffer and saves it. This essentially divides the model by material. I then just bind the textures and what not and send the floatbuffer to GL and it renders it on each frame.

For the most part that is going to usually be more than 500 pixels... but what if the object is offscreen? Should I check and see if an object is visible on the cpu and just skip uploading it if it isn't?

One of the map models I'm using in game is about 20k triangles with around 20 or so materials. Some of those materials will probably use the same glsl shader, but I will most likely end up switching shaders between them anyways because I'd be doing it per material.

Would it be worth the performance to also divide these groups of triangles by glsl shader.

So:

for(objects in the game)

for(models in the object)

for(glsl shaders in the model)

for(materials that use this GLSL shader)

renderTriangles();

Thanks for the advice so far.

IME:

"if()" seems fairly cheap if it is working with uniforms (such as to enable/disable certain features, at least as far as tested thus-far).

OTOH, if using the conditionals for things which tend to vary, such as per-pixel data, then they will cost a fair bit more.

I have actually been considering moving between several smaller shaders possibly to a bigger one.

the issue is mostly that there are at present several different lighting shaders, which need to be switched between for various combinations of light-source settings and materials, and it may be cheaper in this case to use a single bigger shader, which enables/disables various lighting features instead (say, we can enable or disable shadow-mapping, normal and specular mapping, the use of projection and falloff textures, ...).

granted, in this case, I would probably have to make a split between light-source and material attributes (as-is, all are being setup in a big ugly mass every time either the light-source or bound material changes).

but, either way, the idea is that it may make sense for the code to instead just change which attributes are relevant to the current material (say, enable/disable normal-mapping, ...).

however, if what the shaders do is unrelated, say, they operate at different stages of the rendering pipeline, then it probably makes sense to use different shaders.

I am not entirely sure what the actual cost of an if based on a uniform actually is.

IME:

"if()" seems fairly cheap if it is working with uniforms (such as to enable/disable certain features, at least as far as tested thus-far).

OTOH, if using the conditionals for things which tend to vary, such as per-pixel data, then they will cost a fair bit more.

I have actually been considering moving between several smaller shaders possibly to a bigger one.

the issue is mostly that there are at present several different lighting shaders, which need to be switched between for various combinations of light-source settings and materials, and it may be cheaper in this case to use a single bigger shader, which enables/disables various lighting features instead (say, we can enable or disable shadow-mapping, normal and specular mapping, the use of projection and falloff textures, ...).

granted, in this case, I would probably have to make a split between light-source and material attributes (as-is, all are being setup in a big ugly mass every time either the light-source or bound material changes).

but, either way, the idea is that it may make sense for the code to instead just change which attributes are relevant to the current material (say, enable/disable normal-mapping, ...).

however, if what the shaders do is unrelated, say, they operate at different stages of the rendering pipeline, then it probably makes sense to use different shaders.

I am not entirely sure what the actual cost of an if based on a uniform actually is.

Ah, that's interesting. For the most part I have uniforms that just enable and disable features.

It kind of looks like this... (I'm not on my linux HDD right now so I can't get the exact code)

uniform boolean unlit;

uniform integer normalSource;

uniform integer specularSource;

uniform integer illuminationSource;

uniform integer transparencySource;

if(!unlit) {

//lighting stuff

}

//do diffuse texture and detail mixing and crap

if(specularSource> -1) {

//do specular mapping

}

if(normalSource > -1) {

//do normal mapping

}

if(illuminationSource> -1) {

//do illum mapping

}

if(transparencySource> -1) {

//do transparency mapping

}

///finish up and set fragColor

Also, I'd like to point out that the reason I use integers for the differnt texture sources is I have a system where I use integers to specify which textures and color channels to use for operations. IE If i set specularSource to 14 it will use the diffuse.alpha for specular. Or if I set illuminationSource to 11 it would use the diffuse.red channel.

Should I check and see if an object is visible on the cpu and just skip uploading it if it isn't?

it's a standard optimization to perform 'frustum culling' - eg check if an object's bounding sphere is outside of the camera's FOV/frustum - which saves CPU time in GL calls, and saves GPU time processing useless vertices.

for(objects in the game)
for(models in the object)
for(glsl shaders in the model)
for(materials that use this GLSL shader)
renderTriangles();

I would replace that last line with pushToQueue(drawData), then outside of the loops, sortQueueByMaterial(), then
For(data in queue) draw(data)

This topic is closed to new replies.

Advertisement