Automatic shader stage input/output

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.

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

  1. {
  2. float4 pos : POSITION;
  3. float4 col : COLOR;
  4. float3 normal : NORMAL;
  5. };
  6. struct PS_IN
  7. {
  8. float4 pos : SV_POSITION;
  9. float4 col : COLOR;
  10. float3 normal : TEXCOORD0;
  11. };
  12. PS_IN VS( VS_IN input )
  13. {
  14. PS_IN output = (PS_IN)0;
  15. output.pos = input.pos;
  16. output.col = input.col;
  17. output.normal = input.normal;
  18. float4 PS( PS_IN input ) : SV_Target
  19. {
  20. return input.col * max(input.normal.z, 0.0);
  21. }
  22. technique10 Render
  23. {
  24. pass P0
  25. {
  26. SetGeometryShader( 0 );
  27. SetVertexShader( CompileShader( vs_4_0, VS() ) );
  28. SetPixelShader( CompileShader( ps_4_0, PS() ) );
  29. }
  30. }

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.

XKSL 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:

  1. shader StreamShader
  2. {
  3. stream float3 myVar;
  4. };
  5. shader ShaderA : BaseShader, StreamShader
  6. float3 Test()
  7. {
  8. return streams.BaseShader.myVar + streams.StreamShader.myVar;
  9. }
  10. }

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

Code: Same shader in XKSL

Now let's add the normal computation.

Code: Modified shader in XKSL

  1. shader MyShader : ShaderBase
  2. {
  3. stream float4 pos : POSITION;
  4. stream float4 col : COLOR;
  5. stream float3 normal : NORMAL;
  6. override void VSMain()
  7. {
  8. streams.ShadingPosition = streams.pos;
  9. }
  10. override void PSMain()
  11. {
  12. streams.ColorTarget = streams.col * max(streams.normal.z, 0.0);
  13. };

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

See also