If you have multiple shaders, could you instead of switching between shaders all the time just have one shader with a switch and each case will send you along to another shader function which you require for that specific object? ( And you would send an enumerated value through your constant buffer for that specific object)

I guess the real question is. What would take up the most performance, the switch or the switching between shaders?


      case 0 :
        { return transPSShader();}
      case 1:
        { return nonTransPSShader();}
  • 4
    \$\begingroup\$ Well, think about this: the switch() has to be evaluated every time the shader runs, i.e. for every pixel drawn. If you keep your shaders separate, there is no extra work per pixel. But don't take my word for it...why not code up both versions and measure the performance? \$\endgroup\$ Jun 12 '13 at 16:36
  • \$\begingroup\$ hmm you got a point there, didnt think about that it has to do it every pixel. But lets forget about the pixel shader then, how about the vertex shader (and i belive there is a geometry shader? srry im kinda new). Are they called on a per pixel or a per object basis? \$\endgroup\$ Jun 12 '13 at 16:40
  • 2
    \$\begingroup\$ Vertex shader is run per-vertex. Take an object with 1,000 vertexes - that means your switch needs to be called 1,000 times - versus just one shader change. \$\endgroup\$ Jun 12 '13 at 16:49
  • \$\begingroup\$ That's not exactly true. GPU is not CPU. GPU is working in parallel. A lot of threads (I think 16-32) are working with the same instruction in the same time. And if it evaluates the same value, it also continues the same way (in you switch). But if you have a lot of "case" branches, it will still be slow - a lot of conditions have to be evaluated. \$\endgroup\$
    – zacharmarz
    Jun 12 '13 at 17:03
  • \$\begingroup\$ The technique of making one shader with many behaviors is commonly referred to as an "uber shader," which might help you find additional information via Google. \$\endgroup\$ Jun 12 '13 at 17:11

Typically, branching of any kind (switches, if-statements, loops with non-constant iterations) are best avoided. This is mildly true on the PC (not enough to be worth worrying about at all outside of very tight inner loops), especially true on some general-purpose CPUs like the 360's Xenon (common hardware that makes indirect references on branches tolerable on modern super-scalar out-of-order deep-pipeline CPUs was left off the Xenon to save on cost), and especially true on GPUs.

GPUs are very special beasts. They do not work like a general-purpose CPU. They run potentially thousands of copies of a shader at a time, and there are limitations imposed on the hardware to make this possible. One of these limitations is that a number of execution cores share resources. For sake of example, let's say 4 cores are tied together like this on our hypothetical GPU.

So at any time, four "core" are running a shader in lock-step. They share an instruction pointer. They sort-of share a register file. The SIMD behavior of your shaders is not like SIMD behavior on the CPU which is typically used in games; each shader isn't doing four-way vector operations at once, but rather, all four cores work on a single component from the four different data streams at once. These four cores are tightly tied together.

The shared instruction pointer is key. If two of your shaders in this group execute switch case 1 and the other two run switch case 2, all four cores must run both switch cases! Predicated instructions are used to ensure that the results of the instructions in the "off" case are ignored for a particular core, but it still takes the time to execute the instruction and do any memory/register/texture accesses (which is why you should only do texture lookups in uniform code paths), etc.

Hence, branches are "slow" in the fact that your hardware ends up really under-utilized. A potentially very large portion of it ends up spending time evaluating instructions that have no effect. This is different than the CPU case where branches hurt because of pipeline stalls and mispredictions; the GPU often has very restricted branching capabilities anyway.

Is this "slower" than swapping out shaders? That depends. If you batch up your draw commands such that you do all draw operations using a particular shader in a row (so you don't switch from shader A to shader B and then back to shader A, but rather do all drawing with shader A and only then do drawing needing shader B)... it still depends, but it will probably be faster with this batching. As will all things performance-related, you need to test and find out specifically for your application and target hardware. If your switch statements are simple enough you might find that it is indeed quicker to use them.

It is often better anyway to batch up object with identical material properties (same shaders, textures, material constant buffers, etc.) just to avoid changing active resources even when using an uber shader. Breaking up your shaders to play well with this is often not hard at the low-end of the graphics scale. It can get to be a bit more difficult with multiple material types in a deferred shading context, and here a semi-uber shader approach is typically taken (often just for BRDF calculations and the like).

Note that engines like CryTek have taken the uber shader approach (unsure if the most recent incarnations still do), so it's certainly usable for very high-end games in the real world.

  • \$\begingroup\$ awsome anwser :) With uber shader you are refering to what i call Big shader? \$\endgroup\$ Jun 12 '13 at 18:41
  • \$\begingroup\$ Current GPUs have more like 32-wide or 64-wide SIMD, not just 4-wide. :) \$\endgroup\$ Jun 12 '13 at 18:45
  • 1
    \$\begingroup\$ @FredrikBostonWestman: yes. You'll be better off using the more common terminology, if for no reason other than that Google searches will be more fruitful. \$\endgroup\$ Jun 12 '13 at 19:06
  • \$\begingroup\$ What about a switch on a uniform that is always the same value? Like a fog mode parameter that would only change very rarely. \$\endgroup\$
    – jjxtra
    May 9 '18 at 5:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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