Shader classes, mixins and inheritance

Xenko Shading Language (XKSL) is an extension of HLSL, which makes it closer to C# syntax and concepts. The language is object-oriented:

  • shader classes are the foundation of the code
  • shader classes contain methods and members
  • shader classes can be inherited, methods can be overridden
  • member types can be shader classesXKSL uses an original way to handle multiple inheritance. Inheritance is performed through mixins, so the order of inheritance is crucial:

  • the order of inheritance defines the actual implementation of a method (the last override)

  • if a mixin appears several times in the inheritance, only the first occurrence is taken into account (as well as its members and methods)
  • to can call the previous implementation of a method, use base.<method name>(<arguments>)

Keywords

XKSL uses the keywords as HLSL, and adds new ones:

  • stage: method and member keyword. This keyword makes sure the method or member is only defined once and is the same in the compositions.
  • stream: member keyword. The member is accessible at every stage of the shader. For more information, see Automatic shader stage input/out.
  • streams: sort of global structure storing variables needed across several stages of the shader. For more information, see Automatic shader stage input/out.
  • override: method keyword. If this keyword is missing, the compilation returns an error.
  • abstract: used in front of a method declaration (without a body).
  • clone: method keyword. When a method appears several times in the inheritance tree of a shader class, this keyword forces the creation of multiple instances of the method at each level of the inheritance instead of one. For more information, see Composition.
  • Input: for geometry and tessellation shaders. For more information, see Shader stages.
  • Output: for geometry and tessellation shaders. For more information, see Shader stages.
  • Input2: for tessellation shaders. For more information, see Shader stages.
  • Constants: for tessellation shaders. For more information, see Shader stages.

Abstract methods

Abstract methods are available in XKSL. They should be prefixed with the abstract keyword. You can inherit from a shader class with abstract methods without having to implement them; the compiler will simply produce a harmless warning. However, you should implement it in your final shader to prevent a compilation error.

Annotations

Like HLSL, annotations are available in XKSL. Some of the most useful ones are:

  • [Color] for float4 variables. The ParameterKey will have the type Color4 instead of Vector4. It also specifies to Game Studio that this variable should be treated as a color, so you can edit it in Game Studio.
  • [Link(…)] specifies which ParameterKey to use to set this value. However, an independent default key is still created.
  • [Map(…)] specifies which ParameterKey to use to set this value. No new ParameterKey is created.
  • [RenameLink] prevents the creation of a ParameterKey. It should be used with [Link()].

Example code: annotations

  1. shader BaseShader
  2. {
  3. [Color] float4 myColor;
  4. [Link("ProjectKeys.MyTextureKey")]
  5. [RenameLink]
  6. Texture2D texture;
  7. [Map("Texturing.Texture0")] Texture2D defaultTexture;
  8. };

Example code: inheritance

  1. shader BaseInterface
  2. {
  3. abstract float Compute();
  4. };
  5. shader BaseShader : BaseInterface
  6. {
  7. float Compute()
  8. {
  9. return 1.0f;
  10. }
  11. };
  12. shader ShaderA : BaseShader
  13. {
  14. override void Compute()
  15. {
  16. return 2.0f;
  17. }
  18. };
  19. shader ShaderB : BaseShader
  20. {
  21. override void Compute()
  22. {
  23. float prevValue = base.Compute();
  24. return (5.0f + prevValue);
  25. }
  26. };

Example code: the importance of inheritance order

Notice what happens when we change the inheritance order between ShaderA and ShaderB.

  1. shader MixAB : ShaderA, ShaderB
  2. {
  3. };
  4. shader MixBA : ShaderB, ShaderA
  5. {
  6. };
  7. // Resulting code (representation)
  8. shader MixAB : BaseInterface, BaseShader, ShaderA, ShaderB
  9. {
  10. float Compute()
  11. {
  12. // code from BaseShader
  13. float v0 = 1.0f;
  14. // code from ShaderA
  15. float v1 = 2.0f;
  16. // code from ShaderB
  17. float prevValue = v1;
  18. float v2 = 5.0f + prevValue;
  19. return v2; // = 7.0f
  20. }
  21. };
  22. shader MixBA : BaseInterface, BaseShader, ShaderA, ShaderB
  23. {
  24. float Compute()
  25. {
  26. // code from BaseShader
  27. float v0 = 1.0f;
  28. // code from ShaderB
  29. float prevValue = v0;
  30. float v1 = 5.0f + prevValue;
  31. // code from ShaderA
  32. float v2 = 2.0f;
  33. return v2; // = 2.0f
  34. }
  35. };

Static calls

You can also use a variable or call a method from a shader without having to inherit from it. To do this, use <shader_name>.<variable or method_name>. It behaves the same way as a static call.

Note that if you statically call a method that uses shader class variables, the shader won't compile. This is a convenient way to only use a part of a shader, but this isn't an optimization. The shader compiler already automatically removes any unnecessary variables.

Code example: static calls

  1. shader StaticClass
  2. {
  3. float StaticValue;
  4. float StaticMethod(float a)
  5. {
  6. return 2.0f * a;
  7. }
  8. // this method uses a
  9. float NonStaticMethod()
  10. {
  11. return 2.0f * StaticValue;
  12. }
  13. };
  14. // this shader class is fine
  15. shader CorrectStaticCallClass
  16. {
  17. float Compute()
  18. {
  19. return StaticClass.StaticValue * StaticMethod(5.0f);
  20. }
  21. };
  22. // this shader class won't compile since the call is not static
  23. shader IncorrectStaticCallClass
  24. {
  25. float Compute()
  26. {
  27. return StaticClass.NonStaticMethod();
  28. }
  29. };
  30. // one way to fix this
  31. shader IncorrectStaticCallClassFixed : StaticClass
  32. {
  33. float Compute()
  34. {
  35. return NonStaticMethod();
  36. }
  37. };

See also