Quick answer: Add precision highp float; at the top of your fragment shader, declare every uniform with explicit highp/mediump, and set uniforms each draw call rather than once at startup.
A pulsing-color shader works perfectly on the Windows export. The HTML5 build shows the sprite in solid magenta — the GameMaker error material, indicating a compile failure. Or worse, the shader runs but ignores your u_time uniform and just renders a static frame.
Why Web GLSL Is Stricter
HTML5 builds compile shaders to GLSL ES via WebGL. Three places where desktop and web diverge:
- Precision qualifiers — required on every float uniform and varying in GLSL ES.
- Trailing commas in argument lists — allowed in some desktop GLSL implementations, rejected in GLSL ES.
- State preservation — WebGL contexts can be lost (and restored) on tab backgrounding; uniform values may not survive.
Fix 1: Add Precision Qualifiers
// fragment shader header
precision highp float;
uniform highp float u_time;
uniform mediump vec4 u_tint;
varying mediump vec2 v_uv;
The precision highp float; declaration sets the default for any float type without an explicit qualifier. Explicit qualifiers on each uniform are more robust — if a future driver tightens defaults, the explicit ones survive.
Use highp for time, positions, world-space values. mediump for colors, UVs, normalized vectors. lowp for flags. Mismatch (passing a value into a different-precision input) can silently truncate to lower precision.
Fix 2: Watch Trailing Commas
// works on Windows, fails on HTML5
vec3 mix3(vec3 a, vec3 b, vec3 c, float t,) {
...
}
// works everywhere
vec3 mix3(vec3 a, vec3 b, vec3 c, float t) {
...
}
The trailing comma after t, is invalid GLSL ES. Search your shaders for ,) and remove the comma.
Fix 3: Set Uniforms Every Draw Call
/// obj_player Draw
shader_set(sh_glow);
var uni_time = shader_get_uniform(sh_glow, "u_time");
shader_set_uniform_f(uni_time, current_time / 1000);
var uni_tint = shader_get_uniform(sh_glow, "u_tint");
shader_set_uniform_f(uni_tint, 1.0, 0.8, 0.5, 1.0);
draw_self();
shader_reset();
Caching the uniform handle outside the draw event is fine; what you must do every frame is call shader_set_uniform_f. Desktop drivers preserve uniform values per-program-per-context; WebGL is not guaranteed to.
Fix 4: Cache Uniform Handles
/// obj_player Create
uni_time = shader_get_uniform(sh_glow, "u_time");
uni_tint = shader_get_uniform(sh_glow, "u_tint");
shader_get_uniform performs a string lookup and is more expensive than setting the value. Cache the integer handle in Create, reuse in Draw.
Diagnosing in the Browser
Open the browser’s DevTools console while running the HTML5 export. Shader compile errors appear as red “WebGL: INVALID_OPERATION: useProgram: program not valid” messages, usually accompanied by the actual compile log. Search for “ERROR:” or “Cannot compile” lines.
Verifying
Build the HTML5 export, open in Chrome or Firefox, and confirm the shader runs. The sprite should animate as on desktop. If the browser shows the GameMaker magenta error material, the compile is still failing — check the console for the exact error.
“Desktop GLSL is forgiving. WebGL GLSL is not. Add precision qualifiers, set uniforms every frame, and read the browser console.”
If you ship to web, develop and test on web from day one. Catching these issues at launch is far more painful than at start.