Troubleshooting a Black Canvas in ShaderGif

If your preview is just a black rectangle, it usually means the shader didn’t run, it rendered off-screen, or it rendered fully transparent. This guide walks through the most common causes in ShaderGif (GLSL 1.00, GLSL 3.00, and JS/p5.js modes) and shows quick, reliable ways to isolate the problem.

Start with the fast checks (2 minutes)

A “black canvas” is rarely a single issue. Treat it as a symptom: either nothing is being drawn, or something is being drawn but you can’t see it. Run through these checks first before digging into details.

1) Confirm the code is actually executing

  • Look for compile/link errors: A single shader compile failure can result in no fragments being drawn.
  • Switch to a known-good output: Replace your fragment shader body with a constant colour (examples below).
  • Check the editor mode: GLSL 1.00 vs GLSL 3.00 syntax mismatches are a top cause (for example, gl_FragColor vs an out variable).

2) Ensure the canvas is sized and the viewport is correct

If the canvas has a size but the WebGL viewport doesn’t match it, you can “render” perfectly and still see nothing useful. Symptoms include a black canvas, a tiny output in one corner, or a stretched/blurred image.

  • Make sure the canvas element has non-zero width/height (not only CSS size).
  • Ensure the renderer updates the WebGL viewport when the canvas size changes.

3) Verify uniforms are valid (not NaN)

Uniforms like time, resolution, and ratio are commonly used for coordinate mapping. If any of these become NaN (or are missing), calculations can collapse to black or fully transparent output.

  • Temporarily remove dependence on time and resolution to see if the shader draws at all.
  • Avoid divisions that can hit zero (e.g. 1.0 / resolution.y when height is 0).

Use a “minimal shader” to prove your pipeline works

The fastest way to debug is to simplify until something appears, then add complexity back step-by-step. Start by forcing a visible output. If you still get black, the problem is likely outside your math (mode mismatch, compile error, viewport/canvas size, context issues).

GLSL 1.00 minimal fragment output

precision mediump float;

void main() {
  gl_FragColor = vec4(1.0, 0.0, 0.6, 1.0); // solid pink
}

GLSL 3.00 minimal fragment output

#version 300 es
precision mediump float;

out vec4 outColor;

void main() {
  outColor = vec4(0.2, 0.8, 1.0, 1.0); // solid cyan
}

UV/coordinate sanity check (shows a gradient)

If your environment provides a UV coordinate or a resolution uniform, output a simple gradient. A gradient confirms you’re receiving coordinates and drawing across the whole canvas.

// GLSL 1.00 style (adjust variable names to match ShaderGif's provided inputs)
precision mediump float;
uniform vec2 resolution;

void main() {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  gl_FragColor = vec4(uv, 0.0, 1.0);
}

Common GLSL version mismatches (very frequent)

ShaderGif supports multiple coding modes. A black canvas often happens when you paste code written for a different GLSL version or context.

GLSL 1.00 vs 3.00 differences that cause black output

  • Fragment output: GLSL 1.00 uses gl_FragColor. GLSL 3.00 requires out vec4 ....
  • Varyings: GLSL 1.00 uses varying. GLSL 3.00 uses in/out.
  • Texture sampling: GLSL 1.00 typically uses texture2D. GLSL 3.00 uses texture.
  • Precision + version line: GLSL 3.00 needs #version 300 es as the first line (before precision qualifiers).

Symptoms of a version mismatch

  • “Unexpected token” or compile errors in the console/log.
  • Nothing renders, even with simple code.
  • Works in one mode (e.g. GLSL 1.00) but black in another (e.g. GLSL 3.00).

If you’re new to WebGL shader setup, MDN’s guide is a solid refresher on how contexts, shaders, and buffers fit together: Getting started with WebGL (MDN WebGL tutorial).

Precision, ranges, and “invisible” colours

Sometimes the shader runs, but your values end up outside visible ranges (negative colours, very small values, or alpha = 0). That can look identical to a broken shader.

1) Precision qualifiers matter on mobile

On some devices, missing or overly-low precision can cause banding, instability, or unexpected zeros. If you do heavy math, try highp in the fragment shader where supported.

// Try upgrading precision if your math is sensitive
precision highp float;

2) Clamp early while debugging

If you’re doing palette curves, exponentials, or distance fields, clamp your final colour while you debug so you can see whether anything is happening.

// After computing col.rgb
col.rgb = clamp(col.rgb, 0.0, 1.0);
col.a = 1.0;

3) Watch alpha (transparency can look like black)

If your output alpha becomes 0 (or blending is enabled unexpectedly), your canvas may show the page background instead of your shader. While debugging, force alpha to 1.0.

Coordinate-space mistakes (rendering “off screen”)

A common pattern is to assume coordinates are in one space (0–1 UVs) while they’re actually in pixels (gl_FragCoord). If you compute shapes in the wrong space, everything can end up off-canvas.

1) Decide: are you using UV or pixel coordinates?

  • UV space: 0–1 across the canvas. Great for gradients and normalised math.
  • Pixel space: gl_FragCoord.xy in pixels. Great for crisp shapes and screen-space effects.

2) Correct for aspect ratio

Circles becoming ovals is a clue that aspect ratio correction is missing. More importantly, some distance-field math can “disappear” if the scaling is wrong. A reliable approach:

uniform vec2 resolution;

vec2 toCenteredUV() {
  vec2 uv = (gl_FragCoord.xy / resolution.xy) * 2.0 - 1.0;
  uv.x *= resolution.x / resolution.y; // aspect-correct
  return uv;
}

3) Debug by drawing axes

When you’re unsure where (0,0) is, draw simple reference lines:

// Pseudocode: draw crosshair at centre
vec2 uv = (gl_FragCoord.xy / resolution.xy);
float cx = smoothstep(0.49, 0.50, uv.x) - smoothstep(0.50, 0.51, uv.x);
float cy = smoothstep(0.49, 0.50, uv.y) - smoothstep(0.50, 0.51, uv.y);
float line = max(cx, cy);
gl_FragColor = vec4(vec3(line), 1.0);

Read the shader compile log (don’t guess)

If the editor UI doesn’t surface detailed errors, the browser console usually will. The fastest route to a fix is reading the actual compile/link message rather than tweaking random lines.

Typical compile errors that lead to black output

  • Syntax errors: missing semicolons, mismatched braces, wrong keywords for the GLSL version.
  • Undeclared identifiers: using UV, time, or resolution when the mode provides different names.
  • Type mismatches: assigning a vec3 to a vec4, or mixing int and float incorrectly.
  • Varying/interface mismatch: vertex shader outputs don’t match fragment shader inputs (GLSL 3.00 especially).

A practical workflow

  1. Replace your shader with a minimal solid-colour output. Confirm it renders.
  2. Add your uniforms back one at a time (time, resolution, ratio, textures).
  3. Add your coordinate mapping next (UV or gl_FragCoord).
  4. Reintroduce your effect logic in small chunks, verifying output each time.

WebGL context issues and browser gotchas

Sometimes your shader is fine, but the WebGL context isn’t. This can happen due to GPU/driver issues, power-saving modes, or the browser blocking WebGL features.

1) Context loss

WebGL contexts can be lost (especially on memory pressure). If you see intermittent black output after working earlier, try reloading the page and reducing complexity.

2) WebGL 2 required features

If you use GLSL 3.00 features, you need a WebGL 2 context. If the environment falls back to WebGL 1, your shader may fail to compile or link.

3) Extensions and precision support

Some effects rely on extensions (e.g. derivatives, float textures). If the extension isn’t available, you may get black output or broken sampling. While debugging, remove extension dependencies and confirm your base pipeline works.

Textures, images, and external assets (CORS + sampling pitfalls)

If your shader samples a texture and gets all zeros, the result may be black. This is common when an image hasn’t loaded yet, cross-origin rules block it, or you’re sampling outside valid UV ranges.

1) Confirm the texture is loaded

  • Use a fallback colour when a texture isn’t available.
  • Temporarily disable texture sampling to see if your non-texture math works.

2) Clamp UVs while debugging

If UVs go outside 0–1, sampling behaviour depends on wrapping modes. While debugging:

vec2 uv = clamp(computedUV, 0.0, 1.0);

3) Match texture sampling to GLSL version

  • GLSL 1.00: texture2D(sampler, uv)
  • GLSL 3.00: texture(sampler, uv)

On slower GPUs (or very heavy shaders), the preview can appear frozen or blank because frames take too long, the browser throttles, or the tab becomes unresponsive. The fix isn’t “more tweaks” – it’s reducing the cost until you can see output again.

Practical performance triage

  • Reduce loops: Cut iteration counts to 8–16 while debugging, then increase gradually.
  • Remove expensive functions: Heavy noise, multiple texture lookups, or nested FBM can stall on mobile.
  • Lower resolution: If your environment allows it, test at half resolution first.
  • Short-circuit early: Return a debug colour before the expensive parts to confirm flow.

If you want more guidance on keeping shaders responsive, see the ShaderGif docs on optimisation: Performance optimisation tips.