Search Unity

Question Differences in booleans and keywords

Discussion in 'Shaders' started by Gabriel-Oliveira-Almeida, Dec 16, 2022.

  1. Gabriel-Oliveira-Almeida

    Gabriel-Oliveira-Almeida

    Joined:
    Jan 13, 2016
    Posts:
    5
    Hi,
    I trying to understand the differences between bool and keywords for shader, let me show some examples
    Code (CSharp):
    1. //Subshader keyword
    2. #pragma shader_feature_local LIGHT_COLOR
    3. ...
    4. //fragment shader
    5. #ifdef LIGHT_COLOR
    6. color = color * lightColor
    7. #endif
    So, by creating a keyword i can enable or disable the keyword, so when build, the engine creates variations of the shaders based on the keywords, right?
    Why not just do this:
    Code (CSharp):
    1. //Parameter declaration
    2. [ToggleOff]_LightColor("Light Color", Float) = 0
    3. ...
    4. //fragment shader
    5. if(_LightColor > 0)
    6. color = color * lightColor
    I don't understand why creating variants is good, and when i should use a bool like or keywords in shaders.
    Someone can explain to me?
     
  2. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    620
    This isn't a shader thing and I don't know what that example does but you are describing something common to compiled languages. The first example is a "compile -time" option and the second one is a "run-time" option.

    If you were (for example) targeting Android and Windows you wouldn't (and probably couldn't) include all the code for both systems. So the compile-time options only compile the relevant parts of the code while giving you a way to not maintain two completely different sets of code.
     
  3. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
  4. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    620
  5. Gabriel-Oliveira-Almeida

    Gabriel-Oliveira-Almeida

    Joined:
    Jan 13, 2016
    Posts:
    5
    What do you mean by functional differences? For me they work the same way, like give the same result, the differences is that the keywords creates a variants version of the shader, and that variants is used instead of the full code, but my understand is that in run time the "ifs" is almost free anyway.
     
  6. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Yes, in this case they are the same. Sometimes you will need the
    #ifdef
    approach though. Off the top of my head some cases where you may need variants.

    1. Changing the inputs or outputs of various shader stages -- vertex shader inputs, vertex-to-fragment interpolators, multiple render targets or depth/coverage/whatever writes, etc, etc.
    2. Flexible pipeline configuration (tesselation and geometry shaders)
    3. Use of alpha testing/discard. If you use clip() in your shader, it might take a slower path through the GPU, so that should be ifdefed
    4. Using a feature not supported by all GPUs. If you use a feature in your code, then it will fail for all GPUs that don't have that feature, so to scale to lower-end GPUs, you'll need to create variants.
    5. Speaking of, sometimes different GPUs support a feature in different ways. This came up for me recently with wave intrinsics, since NVIDIA GPUs have 32 lanes per wave and AMD has 64. This is a fairly niche case.

    There are other cases, these are just some examples.

    Also, don't assume the if() is free. If there are texture fetches under it, the resources will still need to be bound on the CPU. The shader code will be longer, and may use additional registers, affecting I$ and occupancy.

    A lot of branches will just get flattened, meaning that it'll calculate both sides then throw away the results. Which definitely isn't free since you're calculating both sides of the branch, but is usually close to free or hidden by other latencies on the GPU.

    But if you consider a "big" feature like CSM, PCSS, POM, etc... these things can add a lot of registers to your shader meaning the GPU can't execute as many pixels in parallel.
     
  7. Gabriel-Oliveira-Almeida

    Gabriel-Oliveira-Almeida

    Joined:
    Jan 13, 2016
    Posts:
    5
    Really thx, that is the type of answer i was looking for. I didn't know it worked like that, for the if, like calculate the both sides, so yea putting something that have high processing can be a real problem in branch situations. But for small calcs can be helpful.
    What i understand from this is that:
    - Specific GPU/Shaders limitations and high processing feature should use variants.
    - branch should be use in low impact calcs. Like multiply A*B or A*C, if i already have this values, or if calc this values is not that demand.
    Thank you for your time!