NoesisGUI

Shaders

RenderSystem does not directly expose an API for managing individual render states, vertex shaders, pixel shaders, geometry shaders, etc. Instead a portable concept of shader is exposed. In noesis the concept shader refers to a set of low-level shaders (vertex, pixel, etc) that offers a platform independent interface and that are implemented for each platform.

Shader Interface

Shader interfaces are stored in .shader files.

The shader interface expose the set of properties that can be set in the shader. For example,

[Type = Metal; ZSlot = 5; Desc = "This is a test shader"]
Shader
{
    Properties
    {
        shared Frame
        {
            bool b1 = true;
            [Min = 0; Max = 10;] int i1 = 10;
            int i2[2] = {10, 20};
            float f1 = 5.0;
            float f2[2] = {0.5, 0.75};
            vector2 v2 = {0.5, 0.75};
            vector3 v3 = {0.5, 0.75, 1.0};
            vector4 v4 = {0.5, 0.75, 1.0, 2.0};
            matrix3 m3 = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0};
            matrix4 m4 = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,
                          10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
            transform3 t3 = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0,
                             11.0, 12.0};
            rgba color = {0.1, 0.2, 0.3, 0.4};
            texture t0 = "Textures/Brick.texture.nsd";
         }

         Tweaks
         {
             [Desc = "StepSize"] float step = 2.5;
             [Desc = "DiffuseColor"] rgba diffuseColor = {1.0, 0.0, 0.0, 0.0};
         }
    }

    Macros
    {
        [Hidden;] Debug = { DEBUG, 0x3 };
        [Hidden;] Lod = { LOD, 0x4 };
    }
}

Public properties are exposed through groups. As we will see later, each group can be set independently to the RenderSystem. A group that can be shared between several shaders is marked with the keyword shared. The following properties are supported:

  • bool
  • int
  • float
  • vector2
  • vector3
  • vector4
  • matrix3
  • matrix4
  • transform3
  • rgb
  • rgba
  • texture

Array of properties and default values are supported with the trivial syntax shown in the example above. Before the definition of a property a block of metadata can be specified. That metadata is freely interpreted by the the user of each shader.

Macros

Macros are similar to properties but they are normally translated to #define preprocessor instructions inside the shader implementation. For each macro there is an associated mask that determine the valid range of values. In the example above the following macros are found:

  • Debug (mask = 0x011), 4 values: DEBUG = 0, DEBUG = 1, DEBUG = 2, DEBUG = 3
  • Lod (mask= 0x100), 2 values: LOD = 0, LOD = 1

Macros can be combined. For example, the instance 0x111 (we will see later how this is activated) will select the shader with (DEBUG=3 and LOD = 1)

Shader Implementations

DX9

DX9 driver implements the shader using HLSL and a syntax very similar to Microsoft FX. Implementation is stored in .nfx files. For example,

#ifdef VERTEX_SHADER
    float3   camPos: register(c18);
    float4   time: register(c19);
    float4x4 viewProj: register(c20);
#endif


float4x4 world;
float4x4 worldViewProj;

float3 color;

struct VS_OUTPUT
{
    float4 Position : POSITION;
    float3 Normal: NORMAL0;
    float2 Coords : TEXCOORD0;
};

VS_OUTPUT WireVS(in float4 vPosition : POSITION, in float3 vNormal: NORMAL0,
    in float2 vCoords : TEXCOORD0)
{
    VS_OUTPUT Output;
    Output.Position = mul( vPosition, worldViewProj);
    Output.Coords = vCoords;
    Output.Normal = vNormal;
    return Output;
};

struct PS_OUTPUT
{
    float4 RGBColor : COLOR0;
};

PS_OUTPUT WirePS( VS_OUTPUT In)
{
    PS_OUTPUT Output;
    float3 light = normalize(float3(1.0, 0.2, -1.0f));
    In.Normal = normalize(In.Normal);
    float prod = clamp((dot(In.Normal,light) / 2.0) + 0.5, 0.1, 1.0);
    Output.RGBColor = float4(color * prod, 1.0f);
    return Output;
};




// -------------------------------------------------------------------------------

technique Render
{
    pass p0
    {
        VertexShader = compile vs_3_0 WireVS();
        PixelShader = compile ps_3_0 WirePS();
    }
}

Interface properties and shader implementation properties are matched by name. Properties of a shared group must be bound to hardware registers (:register(...)) in this implementation.

Defines

For this implementation the following defines can be used:

  • D3D9
  • HLSL
  • VERTEX_SHADER (only for vertex shaders)
  • PIXEL_SHADER (only for pixel shaders)

RenderStates

RenderStates are declated inside each pass. The following render states are implemented (default states indicated):

  • ZEnable = true | false;
  • ZWriteEnable = true | false;
  • ZFunc = Never | LessEqual | Less | Equal | GreaterEqual | Greater | NotEqual | Always;
  • FillMode = Solid | Wireframe;
  • CullMode = None | Front | Back;
  • ScissorTestEnable = true | false;
  • PointSize = 1.0;
  • PointSpriteEnable = true | false;
  • AlphaToCoverageEnable = true | false;
  • AlphaBlendEnable = true | false;
  • SrcBlend = Zero | One | SrcColor | InvSrcColor | SrcAlpha | InvSrcAlpha | DestColor | InvDestColor | DestAlpha | InvDestAlpha;
  • DestBlend = Zero | One | SrcColor | InvSrcColor | SrcAlpha | InvSrcAlpha | DestColor | InvDestColor | DestAlpha | InvDestAlpha;
  • ColorWriteEnable = 0xf;
  • StencilEnable = true | false;
  • StencilFail = Keep | Zero | Replace | IncrSat | DecrSat | Invert | Incr | Decr;
  • StencilZFail = Keep | Zero | Replace | IncrSat | DecrSat | Invert | Incr | Decr;
  • StencilPass = Keep | Zero | Replace | IncrSat | DecrSat | Invert | Incr | Decr;
  • StencilFunc = Never | LessEqual | Less | Equal | GreaterEqual | Greater | NotEqual | Always;
  • StencilRef = 0 | <Constant>;
  • StencilMask = 0xffffffff;
  • StencilWriteMask = 0xffffffff;

Some RenderStates can get its value from interface properties. An example for the StencilRef value:

Mask
{
    int stencilRef;
}
technique Render
{
    pass p0
    {
        StencilRef = <stencilRef>;
    }
}

Sampler states

Sampler states are declared inside each sampler. The following sampler states are implemented:

  • AddressU | AddressU | AddressW = Wrap | Mirror | Clamp | Border | MirrorOnce;
  • MagFilter | MinFilter | MipFilter = None | Point | Linear | Anisotropic;
  • Texture = a texture property that must appear in the shader interface
  • MaxAnisotropy = 1;

Using shaders

Having a resource shader loaded the first step is instantiating each property group. The following code, create a constant buffer for the group Tweaks. When creating a constant buffer you must indicate if its content will change frequently or not. A dynamic constant buffer will copy its content to the command buffer when it is activated. A static constant buffer will only copy a reference to the data inside the command buffer. In this example the constant buffers is considered dynamic.

Ptr<Render::IShader> shader = resourceSystem->Load(NSS(Shader), NST("Solid.shader.nsd"));
Ptr<Render::IConstantBuffer> cb =
    shader ->CreateConstantBuffer(NSS(Tweaks), ConstantBufferUsage_Dynamic);

To change properties values, the IConstantBuffer interface is used

cb->GetProperties()->Set<Matrix4f>(NSS(WorldViewProj), Transpose(mView * mProj));

Before rendering a primitive we need to bind the shader and its corresponding constant buffers. Before doing it, we need to define a context. The context indicates the instance (the combination of macros that are active), the technique and the pass. In this example, there is only one instance, one technique and one pass:

IShader::Context context = shader->GetContext(0, 0, 0);
commands->SetShader(shader, context);
commands->SetConstantBuffer(cb, context);

commands->Draw(PrimitiveType_LineList, 0, 24);
© 2017 Noesis Technologies