Jump to content

  • Log In with Google      Sign In   
  • Create Account

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


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
8 replies to this topic

#1 newObjekt   Members   -  Reputation: 208

Like
0Likes
Like

Posted 18 April 2013 - 12:56 AM

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.

Sponsor:

#2 C0lumbo   Crossbones+   -  Reputation: 2268

Like
1Likes
Like

Posted 18 April 2013 - 01:28 AM

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).



#3 Ashaman73   Crossbones+   -  Reputation: 7454

Like
2Likes
Like

Posted 18 April 2013 - 01:53 AM

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.


Edited by Ashaman73, 18 April 2013 - 01:53 AM.


#4 Kaptein   Prime Members   -  Reputation: 2148

Like
1Likes
Like

Posted 18 April 2013 - 06:02 AM

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

#5 Hodgman   Moderators   -  Reputation: 30384

Like
3Likes
Like

Posted 18 April 2013 - 08:22 AM

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.

#6 newObjekt   Members   -  Reputation: 208

Like
0Likes
Like

Posted 18 April 2013 - 11:05 AM

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.



#7 BGB   Crossbones+   -  Reputation: 1554

Like
1Likes
Like

Posted 18 April 2013 - 12:02 PM

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.



#8 newObjekt   Members   -  Reputation: 208

Like
0Likes
Like

Posted 18 April 2013 - 12:12 PM

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.


Edited by newObjekt, 18 April 2013 - 12:15 PM.


#9 Hodgman   Moderators   -  Reputation: 30384

Like
0Likes
Like

Posted 18 April 2013 - 06:31 PM

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)




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS