Quick answer: Uniforms only update while a shader is bound. Call shader_set first, then shader_set_uniform_f, then the draw call, then shader_reset. Cache the uniform handle via shader_get_uniform in Create to avoid per-frame string lookups.

Here is how to fix GameMaker GMS2 shader uniforms that stay frozen at their initial value despite per-frame updates. You set u_time in the Step event, but the shader still uses zero (or some stale value). The shader system has a strict order: bind, set uniforms, draw, unbind. Setting uniforms outside the bound window does nothing.

The Symptom

A shader effect (water shimmer, scrolling texture, animated noise) renders correctly the first frame and then stops animating. Uniforms set per-frame have no visible effect. Disabling and re-enabling the shader reveals the same frozen output.

What Causes This

Uniform set outside shader_set window. shader_set_uniform_f only writes to the currently bound shader. Calling it in Step (before any shader is bound) is a no-op.

Handle invalidated. shader_get_uniform returns -1 if the uniform name is not declared in the shader. Setting -1 is silently ignored.

Shader not declared in YYP. A custom shader that is not part of the project resource list compiles in the editor preview but is missing at runtime.

Multiple shaders sharing a name. If two shaders both declare u_time and you cache the handle from one, setting it while the other is bound writes nothing.

The Fix

Step 1: Cache uniform handles in Create.

// Create event of oWaterEffect
shader = sh_water;
u_time = shader_get_uniform(shader, "u_time");
u_amp = shader_get_uniform(shader, "u_amplitude");
amp = 0.05;

Step 2: Set uniforms inside the shader_set/draw/reset block.

// Draw event
shader_set(shader);
shader_set_uniform_f(u_time, current_time / 1000);
shader_set_uniform_f(u_amp, amp);

draw_sprite(spr_water, 0, x, y);

shader_reset();

The shader is bound during the draw_sprite call. Uniforms set just before are visible to the shader for that draw.

Step 3: Validate the handle.

if (u_time == -1) {
    show_debug_message("Shader missing u_time uniform");
}

Add this once during Create. A -1 means the shader does not declare the uniform with that exact name (or the project does not include the shader).

Step 4: For multiple draws of the same shader, set once.

shader_set(shader);
shader_set_uniform_f(u_time, current_time / 1000);

with oWaterTile {
    draw_sprite(spr_water, 0, x, y);
}

shader_reset();

One uniform set per shader binding, then many draws. Avoids redundant uniform writes.

Step 5: Use shader_set_uniform_array_f for multi-value uniforms.

var _color_arr = [1.0, 0.5, 0.2, 1.0];
shader_set_uniform_f_array(u_color, _color_arr);

Common Pitfalls

Calling shader_get_uniform every frame with a string is slow due to lookup. Cache once in Create.

Forgetting shader_reset. The shader stays bound for subsequent draws elsewhere in the frame, applying the wrong effect.

Mixing per-instance and global uniforms. If two instances of the same effect want different per-instance values, each must shader_set + set uniform + draw + reset within their own draw call.

“Uniforms exist only while bound. Bind, set, draw, reset. The cycle never breaks.”

Related Issues

For surface lifecycle issues, see Surface Lost After Resize. For draw event firing, see Draw Event Not Firing.

Cache the handle in Create. Set inside the bind/draw/reset block. The shader animates.