Yes, that is happening to me now. And now that I'm trying to add compile-time light type/enable switching...yuck. I'm spoiled by C++ templates I guess.
This does seem cleaner, but either way, not so much fun to manage. This is begging for a nice C++ library wrapper.
100% agreed. However, I am not talking about using standard uniforms for this. uniforms have a defined purpose. They are constant within a batch but can change across multiple invocations of that same compiled/linked shader program. The GPU+driver should never recompile/reoptimize your shader code when you change a standard uniform value -- at least in this day-and-age. That was done ~7 GPU generations ago due to HW limitations, but I haven't seen/heard any clear indications that this has gone on since.
No, what I'm talking about is using constants for this. Constants by definition cannot change across all invocations of that specific compiled shader source. They are unchangeable. And thus they are ideal for setting "shader permutation" state which the compiler can automagically use to snip away code for shader permutations that aren't active.
For instance:
When any compiler that's worth 5 cents builds an expression tree and plugs in the constants, it effectively sees:Code :const bool FLAT = false; if ( FLAT ) { ... }
Code :if ( false ) { ... }
so all the code in the braces will never be executed for this compiled shader and can be thrown in the bit bucket, along with the "if" expression evaluation and branch as well. It knows this at compile-time.
So we get what we want, and all without any #if/#else/#elif/#endif mess.
Last edited by Dark Photon; 07-09-2012 at 04:58 PM.
Agreed. It's alot more cumbersome than it needs to be, particularly when you have 20-30 "shader permutation" variables parameterizing your code.
Using const shader permutation settings and standard GLSL language if/else/switch/etc. statements cleans up the GLSL code a ton (vs. nested #if/#else/#elif/#endif etc.)
But you still have the mess on the C++ side with sprintfing that big block that gets manually slapped onto the top of your shader programs. For this, what I'd prefer to see is something like this:
GLSL:
Code :const bool FLAT = false; // False is just the default; C++ GL API can override this if ( FLAT ) { ... }
C++:
Code :glConstant1i( "FLAT", flat ); glCompileShader( ... ); ...
The compiler just "plugs in" the provided value for FLAT into the shader and compiles/links with it (and of course throws away all the "if (FLAT) { ... }" code if FLAT was set to false. The above seems like it might be a pretty simple change.
There are of course a lot of possible variations here. For instance:
- We could declare these special "C++ API-settable" constants "const param" in the shader, and then only allow "const param" settings to be settable via the GL C++ API.
- We could have these const param settings be settable after compile but before link (like subroutines) and provide a uniform-like API to query for the active const param settings and set them (e.g. instead of glGetProgramiv( ... GL_ACTIVE_UNIFORMS ... ), glGetActiveUniforms(), and glUniform*(), use glGetProgramiv( ... GL_ACTIVE_CONST_PARAMS ... ), glGetActiveConstParams(), glConstant*().
- Or we could conceivably call these "const uniform" settings (instead of "const param") and then reuse some of the GL C++ uniform API to set/get them (e.g. glUniform*). The tradeoff is potential confusion with the original "uniform" semantics (...but I think shader subroutines may have already muddied that water).
Last edited by Dark Photon; 07-09-2012 at 04:51 PM.
Managing conditional re-compiling also seems a bit tiresome, regardless of how the consts get passed in. I'm not writing quite enough GLSL code to justify writing a library for it, but I almost feel like I should. Does your suggestion eliminate the need for re-compilation?
Yes, I understand. But you couldn't select e.g. between different uniform block definitions or interpolation methods using constant ifs. For me, preprocessor directives seem like a better alternative, though agree that drivers should be able to optimize out constant ifs too.
Disclaimer: This is my personal profile. Whatever I write here is my personal opinion and none of my statements or speculations are anyhow related to my employer and as such should not be treated as accurate or valid and in no case should those be considered to represent the opinions of my employer.
Technical Blog: http://www.rastergrid.com/blog/
For the large portion of cases (ordinary uniforms, interpolators, etc.) there's no need for any conditional compilation (uniforms become inactive, unused interpolators go away, etc.). But yes, I agree if you're trying to do something the language doesn't support in the same shader source, you might need the preprocessor to force it all into the same file. Before you do though, it's definitely worth considering whether you should just use a separate ubershader source file for that permutation -- could be the cleaner approach.
2 more questions:
- How is this idea different than branching on uniform bools? Couldn't the compiler optimize uniform branches in the same way?
- Previously I was under the impression that branches based on uniforms were relatively efficient, how much of a performance boost might I get using const branches set up by the preprocessor instead? For example, the branches required to handle light types and enable/disable states in the gl_LightSource array. Assuming gl_MaxLights is 8, there is quite a bit of branching required. I know the correct answer will be "test and see" but I would rather not spend the time if the expected impact is minimal.
Doesn't shader subroutines already give you the performance you'd expect from this hypothetical const re-compilation? I've found the cost of recompiling/linking a shader to outweigh dynamic strategies like this. Granted, shader subroutines are GL4-only, but at least they currently exist![]()