Tutorial 53: Shader Uniforms and EffectParameter
What you’ll learn
- What an
EffectParameteris and how to fetch one by name. - The
SetValue()overloads, including arrays. - Why the order of setting parameters relative to
Applymatters.
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 thirteen 2D-only renderers (SDL_RENDERER, DIRECT2D, CANVAS, HTML_DOM, SKIA, BLEND2D, FREEDIRECT, DIRECTX1, GDI, SVG_DOM, OPENVG, NANOVG, PIXIJS) throw on 3D calls.
ShaderEffect uniforms do not go through EffectParameter. That CNAEXT type exposes SetUniformXxx() instead. In contrast, alpha.1's renderer-qualified compiled XNA Effect Framework path does reflect parameters, techniques and passes into the ordinary Effect collections. Stock effects also expose typed properties and parameters. 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. Building with CNA_STRICT_XNA_API turns any use of it into a compile error, which is 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: SPIR-V on VULKAN and SDL_GPU, HLSL compiled at runtime on DIRECTX9/DIRECTX11/DIRECTX12. GDI, METAL, HTML_DOM and SVG_DOM throw on a custom shader, FNA3D cannot compile one at all, and BGFX accepts it and silently ignores 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 |
SetTexture(int unit, Texture2D&) | sampler2D on an extra unit |
SetTexture(int unit, TextureCube&) | samplerCube on an extra unit |
Note the mismatch between the two array methods: SetUniformFloatArray counts scalars, SetUniformVec2Array counts elements. There is no vec3, vec4 or mat4 array setter — a bone palette has to be flattened and pushed through SetUniformFloatArray, or set one matrix at a time with indexed names such as "u_bones[3]".
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 into
// whatever program is currently bound, so this order is mandatory.
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
Call order matters. Uniform setters write directly to the currently bound shader program. Call Apply() first, then set uniforms, then draw. Setting uniforms before Apply() writes them into whichever program happened to be bound.
The constructor does not throw when the shader fails to compile. IsEffectValid() is the only signal — check it once after construction and treat false as a hard error.
What EffectParameter actually is
EffectParameter is CNA's port of XNA's parameter-reflection surface, and it is populated by the stock effects that build a fixed parameter list at construction time — AlphaTestEffect, DualTextureEffect, EnvironmentMapEffect, SkinnedEffect and the PBR effects. It is not built from GLSL reflection, and nothing populates it for a ShaderEffect or a bare BasicEffect. Use it to inspect or drive a stock effect the XNA way; use SetUniformXxx() for anything you wrote yourself.
Getting parameters by name
The collection is reached through getParametersProperty(), and its string subscript returns a pointer, not 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 reference, by position.
EffectParameter& first = effect->getParametersProperty()[0];
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 | Uploaded column-major (the GLSL default). World, View, Projection, MVP. |
bool | bool | Feature toggles inside the shader (fog enabled, vertex colour enabled). |
Texture2D* | sampler2D | Binds the texture to the next available texture unit and sets the sampler uniform to that unit index. |
TextureCube* | samplerCube | Binds a cubemap to a texture unit and sets the sampler uniform. |
int | int | Integer uniforms; also used for explicit sampler unit binding. |
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 class registered at construction time. It is not built by reflecting the compiled shader, so it is empty on any effect that does not build one — including every ShaderEffect.
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
There is no matrix-array setter on ShaderEffect, so a bone palette must be flattened to column-major floats and pushed through SetUniformFloatArray, or written one element at a time using indexed uniform names.
The count must not exceed the array size declared in GLSL. Uploading fewer elements than the declared size is legal — 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 rules, and mixing them up is the most common way to get a shader that silently renders with stale values.
On a stock effect, SetValue() marks state 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 straight to the currently bound program, so Apply() must come first:
// 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.
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 "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;
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.
}
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_ = (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)
* camera_.View()
* camera_.Projection();
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_);
gd.Present();
}
};
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.