Stride

OPEN / CLOSE
  • Features
  • Blog
  • Documentation
  • Community
(icon) Download

  • Discord
  • Facebook
  • Twitter
  • YouTube

LANGUAGE

OPEN / CLOSE
  • English
  • 日本語
    Show / Hide Table of Contents

    Automatic shader stage input/output

    Advanced Programmer

    When you write a HLSL shader, you have to precisely define your vertex attributes and carefully pass them across the stage of your final shader.

    Here's an example of a simple HLSL shader that uses the color from the vertex.

    struct VS_IN
    {
        float4 pos : POSITION;
        float4 col : COLOR;
    };
    
    struct PS_IN
    {
        float4 pos : SV_POSITION;
        float4 col : COLOR;
    };
    
    PS_IN VS( VS_IN input )
    {
        PS_IN output = (PS_IN)0;
    
        output.pos = input.pos;
        output.col = input.col;
    
        return output;
    }
    
    float4 PS( PS_IN input ) : SV_Target
    {
        return input.col;
    }
    
    technique10 Render
    {
        pass P0
        {
            SetGeometryShader( 0 );
            SetVertexShader( CompileShader( vs_4_0, VS() ) );
            SetPixelShader( CompileShader( ps_4_0, PS() ) );
        }
    }
    

    Imagine you want to add a normal to your model and modify the resulting color accordingly. You have to modify the code that computes the color and adjust the intermediate structures to pass the attribute from the vertex to the pixel shader. You also have to be careful of the semantics you use.

    Code: Modified HLSL shader

    struct VS_IN
    {
        float4 pos : POSITION;
        float4 col : COLOR;
        float3 normal : NORMAL;
    };
    
    struct PS_IN
    {
        float4 pos : SV_POSITION;
        float4 col : COLOR;
        float3 normal : TEXCOORD0;
    };
    
    PS_IN VS( VS_IN input )
    {
        PS_IN output = (PS_IN)0;
    
        output.pos = input.pos;
        output.col = input.col;
        output.normal = input.normal;
    
        return output;
    }
    
    float4 PS( PS_IN input ) : SV_Target
    {
        return input.col * max(input.normal.z, 0.0);
    }
    
    technique10 Render
    {
        pass P0
        {
            SetGeometryShader( 0 );
            SetVertexShader( CompileShader( vs_4_0, VS() ) );
            SetPixelShader( CompileShader( ps_4_0, PS() ) );
        }
    }
    

    This example is simple. Real projects have many more shaders, so a single change might mean rewriting lots of shaders, structures, and so on.

    Schematically, adding a new attribute requires you to update all the stages and intermediate structures from the vertex input to the last stage you want to use the attribute in.

    media/hlsl_add_normal.png

    SDSL

    SDSL has a convenient way to pass parameters across the different stages of your shader. The stream variables are:

    • variables
    • defined like any shader member, with the stream keyword
    • used with the stream prefix (omitting it results in a compilation error). When the stream is ambiguous (same name), you should provide the shader name in front of the variable (ie streams.<my_shader>.<my_variable>)

    Streams regroup the concepts of attributes, varyings and outputs in a single concept.

    • An attribute is a stream read in a vertex shader before being written to.
    • A varying is a stream present across shader stages.
    • An output is a stream assigned before being read.

    Think of streams as global objects that you can access everywhere without specifying as a parameter of your functions.

    Note

    You don't have to create a semantic for these variables; the compiler creates them automatically. However, keep in mind that the variables sharing the same semantic will be merged in the final shader. This behavior can be useful when you want to use a stream variable locally without inheriting from the shader where it was declared.

    After you declare a stream, you can access it at any stage of your shader. The shader compiler takes care of everything. The variables just have to be visible from the calling code (ie in the inheritance hierarchy) like any other variable.

    Code: Stream definition and use:

    shader BaseShader
    {
        stream float3 myVar;
    
        float3 Compute()
        {
            return streams.myVar;
        }
    };
    

    Code: Stream specification

    shader StreamShader
    {
        stream float3 myVar;
    };
    
    shader ShaderA : BaseShader, StreamShader
    {
        float3 Test()
        {
            return streams.BaseShader.myVar + streams.StreamShader.myVar;
        }
    }
    

    Example of SDSL shader

    Let's look at the same HLSL shader as the first example but in SDSL.

    Code: Same shader in SDSL

    shader MyShader : ShaderBase
    {
        stream float4 pos : POSITION;
        stream float4 col : COLOR;
    
        override void VSMain()
        {
            streams.ShadingPosition = streams.pos;
        }
    
        override void PSMain()
        {
            streams.ColorTarget = streams.col;
        }
    };
    

    Now let's add the normal computation.

    Code: Modified shader in SDSL

    shader MyShader : ShaderBase
    {
        stream float4 pos : POSITION;
        stream float4 col : COLOR;
        stream float3 normal : NORMAL;
    
        override void VSMain()
        {
            streams.ShadingPosition = streams.pos;
        }
    
        override void PSMain()
        {
            streams.ColorTarget = streams.col * max(streams.normal.z, 0.0);
        }
    };
    

    In SDSL, adding a new attribute is as simple as adding it to the pool of streams and using it where you want.

    media/sdsl_add_normal.png

    See also

    • Effect language
    • Shading language index
      • Shader classes, mixins and inheritance
      • Composition
      • Templates
      • Shader stages
    • Improve this Doc
    In This Article

    Back to top

    Copyright © 2019-2021 .NET Foundation and Contributors
    Supported by the .NET Foundation