Tutorial 53: Shader Uniforms and EffectParameter
What you’ll learn
- What an
EffectParameteris, which effects populate it, and how to fetch one by name. - The
SetValue()overloads, including arrays. - Why the order of setting parameters relative to
Applymatters — and how it differs between stock, compiled andShaderEffecteffects.
Before you start — Tutorial 52: Writing Custom Shaders (ShaderEffect) — parameters are how you feed the shader written there. Requires a 3D-capable renderer such as OPENGLES3 or VULKAN; the 2D-only renderer (SDL_RENDERER) throws on 3D calls.
ShaderEffect uniforms do not go through EffectParameter. That CNAEXT type exposes SetUniformXxx() instead. In contrast, the renderer-qualified compiled XNA Effect Framework path (11 of 14 renderer identities) does reflect parameters, techniques and passes into the ordinary Effect collections — that is where EffectParameter is the real interface. Stock effects expose typed properties plus a fixed set of parameters that mostly mirror those properties. Choose the model that matches the effect type; do not treat all custom shaders as one interchangeable format.
ShaderEffect is CNAEXT — a CNA extension, not part of the XNA 4.0 API. Its header carries the CNAEXT marker on the class and on nearly every method, and code written against it will not port back to XNA or MonoGame unchanged. Defining CNA_STRICT_XNA_API turns any use of it into a [[deprecated]] warning, which becomes a compile error under -Werror=deprecated-declarations — the intended way to prove an XNA-pure port.
The uniform names on this page are GLSL because GLSL is what the GL family expects, but the language is a property of the selected renderer, not of ShaderEffect: desktop GLSL on OPENGL33, GLSL ES on the ES/WebGL profiles, SPIR-V on VULKAN and SDL_GPU, WGSL on WEBGPU, HLSL compiled at runtime on DIRECTX9/DIRECTX11, MSL on METAL (through SpriteBatch only; a 3D draw with one throws), FNA3D hands back an invalid effect, and SOFTWARE and HEADLESS accept it and never execute it. The SetUniformXxx() calls below are the same everywhere; what they feed is not. See Tutorial 52.
Setting uniforms on a ShaderEffect
A ShaderEffect is constructed from vertex and fragment shader source text — three arguments, and neither string is a file path. Its uniforms are then set by name through direct methods, each taking a const char*:
| Method | GLSL uniform type |
|---|---|
SetUniformFloat(name, float) | float |
SetUniformInt(name, int) | int (also explicit sampler-unit binding) |
SetUniformVec2(name, x, y) | vec2 |
SetUniformVec3(name, x, y, z) | vec3 |
SetUniformVec4(name, x, y, z, w) | vec4 |
SetUniformMat4(name, const float*) | mat4, column-major |
SetUniformFloatArray(name, const float*, count) | float[] — count is the number of scalar elements |
SetUniformVec2Array(name, const float*, count) | vec2[] — count is the number of vec2s, so the buffer holds count * 2 floats |
SetUniformVec3Array(name, const float*, count) | vec3[] — count is the number of vec3s (count * 3 floats) |
SetUniformMat4Array(name, const float*, count) | mat4[], column-major — count matrices (count * 16 floats); the "name[0]" spelling also works |
SetTexture(int unit, Texture2D&) | sampler2D on an extra unit |
SetTexture(int unit, TextureCube&) | samplerCube on an extra unit |
SetTexture(int unit, Texture3D&) | sampler3D on an extra unit |
Note the mismatch between the array methods: SetUniformFloatArray counts scalars, while the vec2, vec3 and mat4 setters count elements. A bone palette goes through SetUniformMat4Array (flatten the matrices to column-major floats with Matrix::ToColumnMajor). There is still no vec4-array setter and no mat3 setter.
There is also no Vector2/Vector3/Matrix overload: these take loose floats and raw pointers, so unpack CNA's math types yourself.
// Apply() binds this effect's compiled program. SetUniformXxx() writes to that
// program; applying first is the portable order (the GL renderers also make the
// program current inside each setter).
fx_->Apply();
fx_->SetUniformFloat("u_time", elapsed);
fx_->SetUniformVec2 ("u_resolution", 800.0f, 600.0f);
fx_->SetUniformVec3 ("u_lightDir", dir.X, dir.Y, dir.Z);
float mvpCM[16];
mvp.ToColumnMajor(mvpCM); // SetUniformMat4 wants column-major
fx_->SetUniformMat4("u_mvp", mvpCM);
fx_->SetTexture(1, *normalMapTex_); // unit 0 is usually the caller's
float weights[16] = { /* ... */ };
fx_->SetUniformFloatArray("u_weights", weights, 16); // 16 scalars
std::vector<float> palette(72 * 16); // 72 column-major matrices
// ... boneMatrices[i].ToColumnMajor(&palette[i * 16]) for each bone ...
fx_->SetUniformMat4Array("u_bones", palette.data(), 72);
Call Apply() first. Uniform setters write to the effect's shader program. On the EasyGL identities each setter makes its own program current first (so the alpha.1-era failure, values landing in whichever program happened to be bound, no longer happens there), but other renderers were not checked: call Apply(), then set uniforms, then draw.
The string constructor does not throw when the shader fails to compile. Check IsEffectValid() once after construction, treat false as a hard error, and read GetCompileErrorEXT() (or GetShaderDiagnosticsEXT()) for the reason. The typed ShaderCodeEXT/ShaderPackageEXT constructors (CNA_CNAEXT=ON) throw instead.
What EffectParameter actually is
EffectParameter is CNA's port of XNA's parameter-reflection surface, and there are three different situations behind it:
- Stock effects build a fixed parameter list at construction time.
BasicEffectregistersTexture,DiffuseColor,EmissiveColor,SpecularColor,SpecularPower, the three directional lights,EyePosition, the fog parameters,World,WorldInverseTranspose,WorldViewProjandShaderIndex;AlphaTestEffect,DualTextureEffect,EnvironmentMapEffect,SkinnedEffectand the PBR effects register theirs. On these effects the parameters are mostly outputs: the effect writes them from its own property fields when it is applied, and draws read the fields, not the parameters — so a manualSetValueon, say,DiffuseColoris overwritten or unused. Prefer the typed properties (setDiffuseColorProperty()) and use the parameters to inspect. - Compiled XNA effects (
Effect(device, bytes)or an XNBEffect, on the 11 capable renderer identities) reflect a complete graph — parameters, array elements, structure members, annotations, techniques and passes — into the same collections, and their parameter values are genuine storage uploaded atEffectPass::Apply()when dirty. This is the realEffectParameteruse case. ShaderEffecthas no parameter collection: nothing populates it. UseSetUniformXxx().
Nothing is built from GLSL reflection.
Getting parameters by name
The collection is reached through getParametersProperty(), and both its string subscript and its integer subscript return a pointer, not a reference (this is XNA's null-when-absent behaviour, and a source-breaking change since alpha.1, where the integer subscript returned a reference):
// Returns EffectParameter*, or nullptr if no such parameter exists.
EffectParameter* p1 = effect->getParametersProperty()["DiffuseColor"];
if (p1 != nullptr) {
p1->SetValue(Vector3(1.0f, 0.5f, 0.25f));
}
// The int subscript returns a pointer too (nullptr when out of range).
EffectParameter* first = effect->getParametersProperty()[0];
if (first != nullptr) {
std::cout << first->getNameProperty() << "\n";
}
There is no GetByName method, and a missing name does not throw — you get nullptr, so a typo in the name is a silent no-op unless you check. The one lookup helper that does exist is GetParameterBySemantic(const std::string&), which also returns a pointer or nullptr.
Pointers into the collection stay valid across later Add() calls — the elements are held behind std::unique_ptr precisely so that caching a pointer is safe — but only for the lifetime of the parent effect. Do not hold them across an effect reload.
SetValue() Overloads
The EffectParameter::SetValue() method is overloaded for every GLSL-compatible type that CNA supports. The correct overload is selected by the C++ type of the argument you pass. The table below lists each overload, the corresponding GLSL uniform type, and notes on usage:
| C++ argument type | GLSL uniform type | Notes |
|---|---|---|
float | float | Most common scalar uniform (time, intensity, threshold). |
Vector2 | vec2 | Screen resolution, UV offset, 2D position. |
Vector3 | vec3 | World-space positions, RGB colour values, light direction. |
Vector4 | vec4 | RGBA colour, quaternion, homogeneous position. |
Matrix | mat4 | World, View, Projection, MVP. Stored as an XNA (row-major) Matrix; the renderer converts it on upload. |
bool | bool | Feature toggles inside the shader (fog enabled, vertex colour enabled). |
Texture2D* | sampler2D | Stores the texture on the parameter; the renderer binds it when the pass is applied. (On a non-compiled effect SetValue only stores the value — nothing is bound or uploaded until the effect is applied.) |
TextureCube* | samplerCube | Stores a cubemap on the parameter, bound when the pass is applied. |
int | int | Integer values (compiled effects keep them in their own storage). |
Quaternion | vec4 | Rotation as a quaternion. |
std::string | — | String-valued parameter; no GLSL equivalent, present for XNA parity. |
std::vector<T> | T[N] | Array upload. There is a std::vector overload for every scalar and vector type above, plus std::vector<Matrix>. |
Note that arrays are passed as std::vector, not as a pointer-plus-count pair — there is no SetValue(const float*, int) or SetValue(const Matrix*, int) overload. Matrices additionally have SetValueTranspose(const Matrix&) and SetValueTranspose(const std::vector<Matrix>&) when the shader expects the opposite storage order.
Texture note: the texture overloads take a pointer (Texture*, Texture2D*, Texture3D*, TextureCube*). Passing a dereferenced texture — SetValue(*myTexture) — does not compile against any overload.
EffectParameterCollection
getParametersProperty() returns an EffectParameterCollection, an iterable container of all EffectParameter objects belonging to the effect. You can iterate over it to inspect or dump all parameters at runtime — useful during debugging:
// Dump all parameter names for debugging
for (const auto& param : effect->getParametersProperty()) {
std::cout << " uniform: " << param.getNameProperty()
<< " type: " << (int)param.getParameterTypeProperty()
<< " rows: " << param.getRowCountProperty()
<< " cols: " << param.getColumnCountProperty()
<< "\n";
}
Each EffectParameter exposes the following metadata properties:
getNameProperty()— the string name of the uniform as declared in GLSL.getParameterTypeProperty()— anEffectParameterTypeenum value (Single,Vector2,Matrix,Texture, etc.).getRowCountProperty()— number of rows (1 for scalars and vectors, 4 for mat4).getColumnCountProperty()— number of columns (1 for scalars, 4 for vec4 and mat4).
The collection contains exactly the parameters the concrete effect registered at construction time: a stock effect's fixed list, or the full reflected graph of a compiled XNA effect. It is empty on any effect that does not build one — including every ShaderEffect.
Reflecting a compiled effect
A compiled effect gives you techniques, passes, parameters, array elements, structure members and annotations to walk. This is the part of EffectParameter that has no SetUniformXxx() equivalent (see Tutorial 128 for loading one):
for (auto& technique : effect->getTechniquesProperty()) {
std::cout << "technique " << technique.getNameProperty() << "\n";
for (auto& pass : technique.getPassesProperty()) {
std::cout << " pass " << pass.getNameProperty() << "\n";
for (auto& annotation : pass.getAnnotationsProperty())
std::cout << " annotation " << annotation.getNameProperty() << "\n";
}
}
for (auto& parameter : effect->getParametersProperty()) {
if (parameter.getElementsProperty().getCountProperty() > 0) {
// an array parameter: each element is itself an EffectParameter
}
for (auto& member : parameter.getStructureMembersProperty()) {
// members of a struct-typed parameter
}
}
Passing Arrays
The GLSL side declares a fixed-size array:
// In vertex shader
uniform float u_weights[16]; // e.g. blur kernel weights
uniform mat4 u_bones[72]; // bone matrix palette for skinning
On a stock effect's EffectParameter, arrays go in as a std::vector:
std::vector<float> weights(16);
// ... fill weights ...
if (auto* p = effect->getParametersProperty()["u_weights"]) {
p->SetValue(weights);
}
std::vector<Matrix> boneMatrices(72, Matrix::getIdentityProperty());
// ... populate matrices ...
if (auto* p = effect->getParametersProperty()["u_bones"]) {
p->SetValue(boneMatrices);
}
On a ShaderEffect the equivalent is SetUniformFloatArray, whose count is the number of scalar floats:
fx_->Apply();
fx_->SetUniformFloatArray("u_weights", weights.data(), 16); // 16 floats
For a bone palette, SetUniformMat4Array takes the flattened column-major floats directly:
std::vector<float> palette(boneMatrices.size() * 16);
for (std::size_t i = 0; i < boneMatrices.size(); ++i)
boneMatrices[i].ToColumnMajor(&palette[i * 16]);
fx_->SetUniformMat4Array("u_bones", palette.data(), static_cast<int>(boneMatrices.size()));
The count must not exceed the array size declared in the shader. Uploading fewer elements than the declared size is legal on GL — the remaining array slots keep their last-set values. This is commonly used in skeletal animation where the active bone count is less than the maximum palette size.
Apply ordering
The two effect families have opposite ordering habits, and mixing them up is the most common way to get a shader that silently renders with stale values on a renderer that does not paper over it.
On a stock effect (and on a compiled effect), state is marked dirty and the pass upload happens inside Apply(), so set values first:
// Stock effect: set all values, then apply once. obj.Tint is a Vector3.
for (auto& obj : scene_) {
basicFx_->setWorldProperty(obj.World);
basicFx_->setDiffuseColorProperty(obj.Tint);
basicFx_->Apply();
gd.DrawPrimitives(PrimitiveType::TriangleList, 0, obj.TriCount);
}
On a ShaderEffect, SetUniformXxx() writes to the effect's own program, so call Apply() first (this is the portable order; the GL renderers tolerate the other one):
// ShaderEffect: apply (binds the program), then set uniforms, then draw.
for (auto& obj : scene_) {
fx_->setWorldProperty(obj.World); // IEffectMatrices, forwarded by the device
fx_->Apply();
fx_->SetUniformVec4("u_color", obj.Tint.X, obj.Tint.Y, obj.Tint.Z, 1.0f);
gd.DrawPrimitives(PrimitiveType::TriangleList, 0, obj.TriCount);
}
ShaderEffect implements IEffectMatrices, so World, View and Projection are set as properties rather than as uniforms; CNA forwards them to the renderer, which binds them as uniforms of exactly those names on your program.
Complete Example: Wave Vertex Animation
The following example demonstrates a custom effect that animates vertex positions in the vertex shader using a sine-wave displacement driven by a time uniform. The fragment shader outputs a colour that shifts over time, demonstrating multiple uniform types in action.
The two shaders below are GLSL ES 3.00 (#version 300 es), the dialect of OPENGLES3 and WEBGL2; on OPENGL33 change the version line to #version 330 core (and drop the precision lines), and other renderers need their own dialect (Tutorial 52).
GLSL Vertex Shader (wave.vert)
#version 300 es
precision highp float;
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec2 a_texcoord;
uniform float u_time;
uniform vec2 u_resolution;
uniform mat4 u_mvp;
out vec2 v_texcoord;
void main() {
vec3 pos = a_position;
// Displace Y by a sine wave driven by X position and time
pos.y += sin(pos.x * 4.0 + u_time * 2.0) * 0.1;
gl_Position = u_mvp * vec4(pos, 1.0);
v_texcoord = a_texcoord;
}
GLSL Fragment Shader (wave.frag)
#version 300 es
precision highp float;
in vec2 v_texcoord;
uniform float u_time;
uniform sampler2D u_texture;
out vec4 fragColor;
void main() {
vec4 texColor = texture(u_texture, v_texcoord);
// Modulate green channel with a slow pulse
float pulse = 0.5 + 0.5 * sin(u_time * 1.5);
fragColor = vec4(texColor.r, texColor.g * pulse, texColor.b, texColor.a);
}
C++ Game Class
The shader source is read straight off disk and handed to the ShaderEffect constructor. There are no parameter handles to cache — uniform names are passed as string literals at the point of use.
#include <iostream>
#include "Microsoft/Xna/Framework/Graphics/ShaderEffect.hpp"
#include "System/IO/File.hpp"
class WaveGame final : public Game {
std::unique_ptr<ShaderEffect> waveEffect_;
Texture2D waveTex_;
std::unique_ptr<VertexBuffer> vb_;
int vertCount_ = 0;
float time_ = 0.0f;
Matrix view_, proj_;
void LoadContent() override {
auto& gd = getGraphicsDeviceProperty();
// Three arguments; the two strings are shader SOURCE, never a path.
waveEffect_ = std::make_unique<ShaderEffect>(
gd,
System::IO::File::ReadAllText("Content/shaders/wave.vert.glsl"),
System::IO::File::ReadAllText("Content/shaders/wave.frag.glsl"));
// The constructor does not throw on a compile failure.
if (!waveEffect_->IsEffectValid()) {
// The GLSL did not compile. Do not draw with it; say why.
std::cerr << waveEffect_->GetCompileErrorEXT() << "\n";
}
waveTex_ = getContentProperty().Load<Texture2D>("textures/grid");
// Build a subdivided grid mesh ...
BuildGrid(32, 32);
}
void Update(GameTime& gt) override {
// Cache the value; uniforms are pushed in Draw(), after Apply().
time_ = static_cast<float>(gt.getTotalGameTimeProperty().getTotalSecondsProperty());
}
void Draw(const GameTime&) override {
auto& gd = getGraphicsDeviceProperty();
gd.Clear(Color::Black);
Matrix mvp = Matrix::CreateRotationY(0.3f)
* Matrix::CreateTranslation(0, 0, -3.0f)
* view_
* proj_; // your own camera matrices (Tutorial 34)
float mvpCM[16];
mvp.ToColumnMajor(mvpCM);
// Apply() binds the program; every SetUniformXxx() below targets it.
waveEffect_->Apply();
waveEffect_->SetUniformFloat("u_time", time_);
waveEffect_->SetUniformVec2 ("u_resolution", 800.0f, 600.0f);
waveEffect_->SetUniformMat4 ("u_mvp", mvpCM);
waveEffect_->SetTexture(0, waveTex_);
gd.SetVertexBuffer(vb_.get());
gd.DrawPrimitives(PrimitiveType::TriangleList, 0, vertCount_);
// No gd.Present(): Game presents in EndDraw().
}
};
The vertex shader above declares u_mvp rather than the World/View/Projection trio that IEffectMatrices binds automatically, so the matrix is uploaded by hand. If you name your uniforms World, View and Projection instead, set them with setWorldProperty()/setViewProperty()/setProjectionProperty() and CNA forwards them for you — see Tutorial 52.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- Effect object model: techniques, passes, parameters and the draw packet — What a CNA Effect contains in its stock, compiled and ShaderEffect forms: collections, pass application, Clone and Dispose, stock parameter tables versus XNA, EffectParameter storage and GpuDrawParams.