ShaderEffect

Microsoft::Xna::Framework::Graphics — user-supplied GLSL / SPIR-V shaders via .shader.json

Implementation status: ShaderEffect is CNA's substitute for XNA's compiled .fx effects, which CNA cannot load. GLSL source shaders are pixel-tested on EasyGL; SPIR-V bytecode shaders are pixel-tested on Vulkan. The bgfx and SDL_Renderer backends do not support custom ShaderEffect at all — bgfx expects its own precompiled shader binaries, and SDL_Renderer has no programmable pipeline.

Overview

ShaderEffect, in the Microsoft::Xna::Framework::Graphics namespace, is the base class that all CNA effects — including every stock effect such as BasicEffect and SkinnedEffect — extend. It binds a compiled shader program to the graphics pipeline and exposes its uniform inputs through the EffectParameter collection, exactly mirroring the XNA 4.0 design.

When the built-in stock effects do not cover your use case, you can author your own vertex and fragment shaders and load them through the ContentManager pipeline using a lightweight .shader.json descriptor file. The same CurrentTechnique.Passes[0].Apply() pattern used by stock effects applies equally to custom ShaderEffect instances.

Relationship to stock effects

ShaderEffect is the common base of the entire CNA effect hierarchy. Stock effects such as BasicEffect, AlphaTestEffect, DualTextureEffect, EnvironmentMapEffect, and SkinnedEffect are all specialised subclasses that bundle a hard-coded shader program together with typed C++ properties for convenience. A custom ShaderEffect follows exactly the same runtime pattern — the only difference is that the shader source is supplied by the application rather than compiled into the engine.

This means any code that works with Effect* pointers polymorphically will accept a custom ShaderEffect without modification.

The .shader.json descriptor

The content pipeline locates shader programs through a .shader.json file placed inside your content directory. The descriptor records the paths to the vertex and fragment shader source files (relative to the content root) and lists the uniform names that the engine should expose as EffectParameter objects.

{
  "vertex":   "shaders/myvert.glsl",
  "fragment": "shaders/myfrag.glsl",
  "uniforms": ["World", "View", "Projection", "Texture0"]
}

Fields:

  • "vertex" — path to the GLSL vertex shader source (.glsl) or compiled SPIR-V bytecode (.spv), relative to the content root.
  • "fragment" — path to the GLSL fragment shader source or SPIR-V bytecode.
  • "uniforms" — optional array of uniform name strings. Names listed here are pre-resolved at load time so that effect->Parameters["Name"] lookups do not require a string search at runtime. Uniforms not listed are still accessible but are resolved lazily on first access.

For the Vulkan backend, replace the GLSL .glsl paths with pre-compiled SPIR-V .spv files. Push constant blocks are limited to 128 bytes, and uniform buffer layout follows std140 rules.

Loading a custom shader

Pass the asset name (without the .shader.json extension) to Content.Load<ShaderEffect>, exactly as you would load any other content asset.

// In your Game::LoadContent():
auto effect = Content.Load<ShaderEffect>("shaders/custom");

The content manager resolves "shaders/custom" to Content/shaders/custom.shader.json, reads the descriptor, compiles or uploads the shader program, and returns a fully initialised ShaderEffect shared pointer.

Setting uniforms via EffectParameter

Every uniform declared in the shader (and optionally listed in the "uniforms" array) is accessible through the Effect::Parameters collection as an EffectParameter object. Use the typed SetValue() overload that matches the GLSL uniform type.

// Matrix uniforms
effect->Parameters["World"]->SetValue(worldMatrix);
effect->Parameters["View"]->SetValue(camera.View());
effect->Parameters["Projection"]->SetValue(camera.Projection());

// Scalar and vector uniforms
effect->Parameters["Opacity"]->SetValue(0.75f);
effect->Parameters["Tint"]->SetValue(Vector4(1.0f, 0.8f, 0.6f, 1.0f));

// Boolean flag
effect->Parameters["UseNormalMap"]->SetValue(true);

Passing a texture uniform

Bind a Texture2D to a sampler uniform by name. The engine maps the parameter to the correct texture unit automatically.

// Load a texture through the content pipeline
auto diffuse = Content.Load<Texture2D>("textures/wall");

// Bind it to the sampler declared in the fragment shader
effect->Parameters["Texture0"]->SetValue(diffuse);

The fragment shader declares the corresponding sampler as a standard GLSL uniform sampler2D Texture0;. No additional registration is needed beyond listing "Texture0" in the "uniforms" array of the descriptor.

Applying the effect and drawing

After all parameters are set, apply the effect's first (and typically only) pass before issuing draw calls. This uploads the current parameter values to the GPU and binds the shader program.

effect->CurrentTechnique->Passes[0]->Apply();
graphicsDevice->DrawIndexedPrimitives(
    PrimitiveType::TriangleList, 0, 0, indexCount / 3);

If your effect defines multiple techniques or passes, iterate over effect->Techniques and pass->Apply() inside the loop, re-issuing draw calls for each pass.

Full example: custom tinted mesh shader

The following example shows a complete .shader.json descriptor together with matching GLSL vertex and fragment shaders, and the C++ code that loads and drives the effect.

Content/shaders/tinted.shader.json

{
  "vertex":   "shaders/tinted.vert.glsl",
  "fragment": "shaders/tinted.frag.glsl",
  "uniforms": ["World", "View", "Projection", "Tint", "Texture0"]
}

Content/shaders/tinted.vert.glsl

#version 330 core

uniform mat4 World;
uniform mat4 View;
uniform mat4 Projection;

layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoord;

out vec2 v_TexCoord;

void main()
{
    mat4 wvp = Projection * View * World;
    gl_Position = wvp * vec4(a_Position, 1.0);
    v_TexCoord  = a_TexCoord;
}

Content/shaders/tinted.frag.glsl

#version 330 core

uniform sampler2D Texture0;
uniform vec4      Tint;

in  vec2 v_TexCoord;
out vec4 fragColor;

void main()
{
    vec4 texColor = texture(Texture0, v_TexCoord);
    fragColor = texColor * Tint;
}

C++ usage

// LoadContent
auto tintedEffect = Content.Load<ShaderEffect>("shaders/tinted");
auto wallTexture  = Content.Load<Texture2D>("textures/wall");

// Draw (called each frame)
tintedEffect->Parameters["World"]->SetValue(Matrix::Identity);
tintedEffect->Parameters["View"]->SetValue(camera.View());
tintedEffect->Parameters["Projection"]->SetValue(camera.Projection());
tintedEffect->Parameters["Tint"]->SetValue(Vector4(1.0f, 0.5f, 0.5f, 1.0f));
tintedEffect->Parameters["Texture0"]->SetValue(wallTexture);

tintedEffect->CurrentTechnique->Passes[0]->Apply();
graphicsDevice->DrawIndexedPrimitives(
    PrimitiveType::TriangleList, 0, 0, indexCount / 3);

Vertex shader inputs and VertexDeclaration

The attribute layout declared in the vertex shader (layout(location = N) in ...) must match the VertexDeclaration of the vertex buffer bound at draw time. CNA maps each VertexElement to the corresponding attribute location in the order they appear in the declaration. A mismatch in type or location will produce incorrect geometry or a GPU error, and in debug builds CNA logs a warning.

For the standard VertexPositionTexture layout used in the example above, position is always location 0 and texture coordinates are location 1. If you use a custom vertex struct, declare a matching VertexDeclaration and ensure the GLSL locations align.

Always verify that layout(location = N) assignments in your GLSL vertex shader match the element order in the VertexDeclaration of your vertex buffer. Mismatches are a common source of silent rendering errors.

EffectParameter supported types

The table below lists every type supported by EffectParameter::GetValue<T>() and EffectParameter::SetValue(), together with the corresponding GLSL uniform type.

C++ type GLSL uniform type Notes
float float Scalar floating-point value
Vector2 vec2 2-component float vector
Vector3 vec3 3-component float vector
Vector4 vec4 4-component float vector; also used for colours
Matrix mat4 4×4 column-major matrix
Matrix[] mat4[] Array of matrices; used for bone palettes in skinning
Texture2D sampler2D 2D texture sampler binding
bool bool Boolean flag
int int Signed 32-bit integer scalar

Backend availability

ShaderEffect with user-supplied shaders is available on the EasyGL and Vulkan backends only. The bgfx and SDL_Renderer backends do not implement the custom shader pipeline.

Backend Shader format Status
EasyGL GLSL source (.glsl) pixel-tested
Vulkan SPIR-V bytecode (.spv) pixel-tested
bgfx not implemented
SDL_Renderer not implemented