Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Conditionals in Shader Performance Issues

Discussion in 'Shaders' started by NoahTheProgrammer, Mar 21, 2020.

  1. NoahTheProgrammer

    NoahTheProgrammer

    Joined:
    Jun 9, 2014
    Posts:
    3
    Hi, I have a bit of a predicament in my shader which has been bothering me for quite a while. In C#, I set the float _fractalNumber in my shader with a variable FractalNumber I change in the inspector. For now, I created a bunch of separate shaders with basically the same code except with different return statements and chose the shader depending on the value of FractalNumber (thus, there are 35 different shaders), so I can evade all the if statements used in the switch function. These shaders are nearly identical, and I'm pretty sure there's a better way than to succumb to this.

    The method used in the code right now slows down my computer quite a bit, and I was wondering if there was anything I can do to improve the speed without having to create a bunch of different shaders.

    Hopefully you can understand what I'm trying to ask here, this is my first time asking a question. Here is the code in question:

    Code (CSharp):
    1. inline float DistanceFunction(float3 p)
    2. {
    3.     switch (_fractalNumber)
    4.     {
    5.         case 0:
    6.             return sdMandelbulb(p);
    7.             break;
    8.         case 1:
    9.             return sdDinamMandelbulb(p);
    10.             break;
    11.         case 2:
    12.             return sdJulia(p);
    13.             break;
    14.         case 3:
    15.             return sdJuliabulb(p);
    16.             break;
    17.         case 4:
    18.             return sierpinski(p);
    19.             break;
    20.         case 5:
    21.             return mandelbox(p);
    22.             break;
    23.         case 6:
    24.             return kaleidoscopic_IFS(p);
    25.             break;
    26.         case 7:
    27.             return tglad_formula(p);
    28.             break;
    29.         case 8:
    30.             return hartverdrahtet(p);
    31.             break;
    32.         case 9:
    33.             return pseudo_kleinian(p);
    34.             break;
    35.         case 10:
    36.             return pseudo_knightyan(p);
    37.             break;
    38.         case 11:
    39.             return mandelbulb2(p, _power);
    40.             break;      
    41.         case 12:
    42.             return MengerSponge(p);
    43.             break;    
    44.         case 13:
    45.             return apo(p, .0274, float3(1., 1., 1.3), float3(0., 0., 0.));
    46.             break;
    47.         case 14:
    48.             return sdPlane(p, float4(0,1,0,0));
    49.             break;  
    50.         case 15:
    51.             return FCT_BBSK(p, Params);    
    52.             break;  
    53.         case 16:
    54.             return trinoise(p);    
    55.             break;
    56.         /*case 17:
    57.             return RecursiveTetrahedron(p, 3);    
    58.             break;  */
    59.         case 18:
    60.             return TruchetTentacles(p);    
    61.             break;
    62.         case 19:
    63.             return FCT_PROTEIN(p, Params);    
    64.             break;
    65.         case 20:
    66.             return FCT_ORBIT(p);    
    67.             break;
    68.         case 21:
    69.             return FCT_MNMT(p);    
    70.             break;
    71.         case 22:
    72.             return FCT_CRAB(p);    
    73.             break;
    74.         case 23:
    75.             return FCT_HUB(p, Params);    
    76.             break;
    77.         case 24:
    78.             return FCT_HYPERAPO(p, Params);    
    79.             break;
    80.         case 25:
    81.             return FCT_DLBT(p, Params);    
    82.             break;
    83.         case 26:
    84.             return FCT_MZGN(p, Params);    
    85.             break;
    86.         case 27:
    87.             return FCT_PIPES(p);    
    88.             break;
    89.         case 28:
    90.             return FCT_APOP(p, Params);    
    91.             break;
    92.         case 29:
    93.             return FCT_APO(p);    
    94.             break;
    95.         case 30:
    96.             return FCT_HTVT(p, Params);    
    97.             break;
    98.         case 31:
    99.             return FCT_KNKL(p, Params);    
    100.             break;  
    101.         case 32:
    102.             return FCT_KIFS(p, Params);    
    103.             break;
    104.         case 33:
    105.             return FCT_TEXT(p);    
    106.             break;
    107.         case 34:
    108.             return FCT_TEST(p);    
    109.             break;                                                              
    110.         default:
    111.             return apo(p, .0274, float3(1., 1., 1.3), float3(0., 0., 0.));
    112.             break;        
    113.     }
    114.     return 1;
    115.    
    116.     //return apo(p, .0274, float3(1., 1., 1.3), float3(0., 0., 0.));
    117. }
     
  2. hjohnsen

    hjohnsen

    Joined:
    Feb 4, 2018
    Posts:
    67
    AFAIK there is no thing like function call in shaders and there is not even 'real' conditional code. I suspect that all the code of your shaders are executed whatever the value of _fractalNumber is.
    You can have a look at the disasembled code.
     
  3. aleksandrk

    aleksandrk

    Unity Technologies

    Joined:
    Jul 3, 2017
    Posts:
    2,983
    NoahTheProgrammer likes this.
  4. NoahTheProgrammer

    NoahTheProgrammer

    Joined:
    Jun 9, 2014
    Posts:
    3
    Hi @aleksandrk,
    Just to make sure, would I make multiple shader program variants using the code with 35 keywords:
    Code (HLSL):
    1. #pragma multi_compile apo, .....
    Then use a #ifdef _ and #elif _ cycle with the keywords instead of my current switch statement?
     
  5. aleksandrk

    aleksandrk

    Unity Technologies

    Joined:
    Jul 3, 2017
    Posts:
    2,983
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    To save you some sanity.
    #pragma multi_compile MY_KEYWORD_A MY_KEYWORD_B
    keywords should be in all caps, with no commas.
    You may want to use
    #if defined(MY_KEYWORD_A)
    instead of
    #ifdef
    for consistency since with
    #elif
    you need to do
    #elif defined(MY_KEYWORD_B)
    .
    Instead of having a function with a bunch of
    #if #elif
    lines, you can just define a function name as being another function name.
    Code (CSharp):
    1. #if defined(SD_MANDELBULB)
    2. #define DistanceFunction sdMandelbulb
    3. #elif defined(SD_DINAM_MANDELBULB)
    4. #define DistanceFunction sdDinamMandelbulb
    5. // etc
    6. #endif
     
    polyflow3d and NoahTheProgrammer like this.
  7. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    333
    If you want to go down the route of writing a single function with branching logic inside it, but without the performance implications of that, there's another trick you can do.

    Any branch that can be determined at compile time can be removed.

    Code (CSharp):
    1. float foo(float t, bool mode) {
    2.   if (mode == true) { return sin(t); }
    3.   return cos(t);
    4. }
    5.  
    6. // ...
    7.  
    8. // Slow, because it has to run the branch in foo
    9. float a = foo(time, time > 10);
    10.  
    11. // Fast, because the branch gets compiled away because mode is constant
    12. float b = foo(time, true);
    If you had only a single
    computeFractal
    function with branches inside it, you could branch as you are now by doing something like this.

    Code (CSharp):
    1. switch (_fractalNumber)
    2.     {
    3.         case 0:
    4.             return computeFractal(p, 0);
    5.             break;
    6.         case 1:
    7.             return computeFractal(p, 1)
    8.             break;
    9.         // ...
    10. }
    Then you would only have one function to maintain with readable logic, but the switch would be the only branch. Of course if you replace the switch with the variants technique, you have no branches which is even better.
     
    NoahTheProgrammer likes this.