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

Different shader kernels depending on active shader features?

Discussion in 'Shaders' started by thebarryman, Dec 6, 2019.

  1. thebarryman

    thebarryman

    Joined:
    Nov 22, 2012
    Posts:
    130
    Is it possible for a shader to conditionally use different stage kernels depending on which keywords are defined, by wrapping the #pragma directives within an #if statement?

    For example, could you do something like this and expect Unity's shader compiler to handle it?

    Code (CSharp):
    1. #pragma shader_feature MY_TESSELLATION
    2. #pragma vertex vert
    3. #pragma fragment frag
    4. #if (MY_TESSELLATION)
    5.     #pragma hull my_hull
    6.     #pragma domain my_domain
    7. #endif
    8. #if (MY_TESSELLATION)
    9.     hullInput vert() ...
    10. #else
    11.     fragInput vert() ...
    12. #endif
    I've also tried this way, figuring it's a little more explicit for the compiler, but still no dice.

    Code (CSharp):
    1. #pragma shader_feature MY_TESSELLATION
    2.  
    3. #if (MY_TESSELLATION)
    4.     #pragma vertex tess_vert
    5.     #pragma hull my_hull
    6.     #pragma domain my_domain
    7.     #pragma fragment frag
    8. #else
    9.     #pragma vertex vert
    10.     #pragma fragment frag
    11. #endif
    12.  
    13. fragInput vert() ...
    14. float4 frag() ...
    15.  
    16. #if (MY_TESSELLATION)
    17.     hullInput tess_vert() ...
    18.     hullInput my_hull() ...
    19.     fragInput my_domain() ...
    20. #endif
    Is it straight up just not possible to toggle kernels with shader features like this? The use case here is that we want the game to use different shader configurations depending on the desired platform/feature set.
     
    Last edited: Dec 7, 2019
  2. thebarryman

    thebarryman

    Joined:
    Nov 22, 2012
    Posts:
    130
    Tried making a minimal repro test case. Getting the following compiler complaints that I don't really get, given the shader code.

    Error: Did not find shader kernel 'my_domain' to compile [my confusion - it can't find my_domain since it's defined within an #if (USE_MY_TESSELLATION) block, but the my_domain kernel is only requested within an #if (USE_MY_TESSELLATION) block as well...]
    Error: Did not find shader kernel 'my_hull' to compile
    Error: Did not find shader kernel 'my_vert_tess' to compile
    Warning: Duplicate #pragma vertex found, ignoring: #pragma vertex my_vert (line 24) [my confusion - how can there be a duplicate #pragma vertex when they're declared within mutually exclusive #if blocks?]
    Warning: Duplicate #pragma fragment found, ignoring: #pragma fragment my_frag (line 25)

    Code (CSharp):
    1. Shader "Custom/ConditionalTessFail" {
    2.  
    3.     Properties
    4.     {
    5.         _TessFactor("Tessellation Factor", Float) = 5
    6.         [Toggle(USE_MY_TESSELLATION)]_UseTess("Use Tessellation", Float) = 0
    7.     }
    8.  
    9.     SubShader
    10.     {
    11.         Pass
    12.         {
    13.             CGPROGRAM
    14.  
    15.             #pragma target 4.6
    16.             #pragma shader_feature USE_MY_TESSELLATION
    17.  
    18.             #if (USE_MY_TESSELLATION)
    19.                 #pragma vertex my_vert_tess
    20.                 #pragma hull my_hull
    21.                 #pragma domain my_domain
    22.                 #pragma fragment my_frag
    23.             #else
    24.                 #pragma vertex my_vert
    25.                 #pragma fragment my_frag
    26.             #endif
    27.  
    28.             struct vert_input
    29.             {
    30.                 float4 vertex : POSITION;
    31.             };
    32.  
    33.             struct frag_input
    34.             {
    35.                 float4 vertex : SV_POSITION;
    36.             };
    37.  
    38.             frag_input my_vert(vert_input i)
    39.             {
    40.                 frag_input o = (frag_input)0;
    41.                 o.vertex = UnityObjectToClipPos(i.vertex);
    42.  
    43.                 return o;
    44.             }
    45.  
    46.             float4 my_frag(frag_input i) : SV_Target0
    47.             {
    48.                 return 1;
    49.             }
    50.  
    51.             #if (USE_MY_TESSELLATION)
    52.                 float _TessFactor;
    53.                 struct hull_input
    54.                 {
    55.                     float4 vertex : INTERNALTESSPOS;
    56.                 };
    57.  
    58.                 hull_input my_vert_tess(vert_input v)
    59.                 {
    60.                     hull_input o;
    61.                     o.vertex = v.vertex;
    62.                     return o;
    63.                 }
    64.  
    65.                 struct TessellationFactors
    66.                 {
    67.                     float edge[3] : SV_TessFactor;
    68.                     float inside : SV_InsideTessFactor;
    69.                 };
    70.  
    71.                 TessellationFactors GetTessellationFactors(InputPatch<hull_input, 3> patch)
    72.                 {
    73.                     TessellationFactors f;
    74.                     f.edge[0] = _TessFactor;
    75.                     f.edge[1] = _TessFactor;
    76.                     f.edge[2] = _TessFactor;
    77.                     f.inside = _TessFactor;
    78.                     return f;
    79.                 }
    80.  
    81.                 [UNITY_domain("tri")]
    82.                 [UNITY_outputcontrolpoints(3)]
    83.                 [UNITY_outputtopology("triangle_cw")]
    84.                 [UNITY_partitioning("fractional_odd")]
    85.                 [UNITY_patchconstantfunc("GetTessellationFactors")]
    86.                 hull_input my_hull(InputPatch<hull_input, 3> patch, uint id : SV_OutputControlPointID)
    87.                 {
    88.                     return patch[id];
    89.                 }
    90.  
    91.                 [UNITY_domain("tri")]
    92.                 frag_input my_domain(TessellationFactors factors, OutputPatch<hull_input, 3> patch, float3 domLoc : SV_DomainLocation)
    93.                 {
    94.                     vert_input o;
    95.                     o.vertex = patch[0].vertex * domLoc.x + patch[1].vertex * domLoc.y + patch[2].vertex * domLoc.z;
    96.                     return my_vert(o);
    97.                 }
    98.             #endif
    99.             ENDCG
    100.         }
    101.     }
    102. }
     
    Last edited: Dec 7, 2019
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    In Unity’s shader compiling system,
    #pragma
    lines are processed before
    #if
    lines, so you can’t create variants with different shader stages. This makes sense a bit since they use some
    #pragma
    lines to determine what variants to compile.
     
  4. thebarryman

    thebarryman

    Joined:
    Nov 22, 2012
    Posts:
    130
    Thanks for the response. So is it just not possible to conditionally enable tessellation on a shader? It seems like the compiler should be able to support something along those lines, since some platforms support the feature and some don't.
     
    Last edited: Dec 9, 2019
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Correct.

    This is possible. You need a shader with multiple SubShaders, each with different
    #pragma target
    and/or
    #pragma requires
    , with the highest level or most requirements first.
    https://docs.unity3d.com/Manual/SL-ShaderCompileTargets.html

    Code (csharp):
    1. Shader "SubShaderExample"
    2. {
    3.   Properties {
    4.     // stuff
    5.   }
    6.  
    7.   // SubShader with all passes requiring Shader Level 3.0
    8.   SubShader {
    9.     Pass {
    10.       CGPROGRAM
    11.       #pragma target 3.0
    12.       // stuff
    13.       ENDCG
    14.     }
    15.   }
    16.  
    17.   // SubShader with all passes requiring Shader Level 2.0, used only if the first SubShader can't be
    18.   SubShader {
    19.     Pass {
    20.       CGPROGRAM
    21.       #pragma target 2.0
    22.       // stuff
    23.       ENDCG
    24.     }
    25.   }
    26. }
     
  6. thebarryman

    thebarryman

    Joined:
    Nov 22, 2012
    Posts:
    130
    I see. What about the use case where a platform supports the feature but our app doesn't have available performance cycles to enable the feature for that platform (ie, we manually want to disable/enable the feature and not rely on hardware support level).
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    https://docs.unity3d.com/Manual/SL-ShaderLOD.html
    The LOD levels are arbitrary, and mostly unused by Unity at this point, so the documentation only represents how it was used for legacy shaders. The Standard shader has two SubShaders, one using LOD 300 w/ #pragma target 3.0, the other using LOD 150 w/ #pragma target 2.0. That doesn't mesh with that documentation at all, but again it doesn't really matter. Just set your LOD to >300 for tesellation and set the max LOD to less than that when you want to skip that subshader.
     
    thebarryman likes this.
  8. thebarryman

    thebarryman

    Joined:
    Nov 22, 2012
    Posts:
    130
    Awesome, I knew there had to be some use for the shader LOD system - I'll look into it! Thanks so much for your help bgolus.
     
  9. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,924
    This sounds like a way to effectively workaround the wanted-for-many-years feature of conditionally compiling shader Pass's - create two subshaders, one with the pass, one without, move all their code into a separate cginc file to avoid maintaining two copies, and use Shader.maximumLOD to choose between variants at runtime?
     
  10. forestrf

    forestrf

    Joined:
    Aug 28, 2010
    Posts:
    229