ShaderGif is designed to make “code → looping GIF” feel immediate, but real-time rendering is still limited by GPU/CPU budget, browser overhead, and the cost of exporting frames. This page explains the most common performance bottlenecks and practical ways to keep previews smooth and exports reliable on a wide range of devices.
How performance breaks down in ShaderGif
When you press Run (or when a preview is playing), the browser is doing a repeating loop of: update uniforms (time, resolution, user inputs), draw a frame via WebGL (GPU work), and present the result to the page. When you export, the same work happens many times in a tight loop, plus the CPU has to encode the output (for GIF, this often includes palette selection and dithering).
- GPU time: fragment shader cost (most common), texture sampling, overdraw, precision, and branching.
- CPU time: JavaScript overhead, UI updates, frame readback (
readPixels), and encoding. - Memory / bandwidth: high resolutions, many textures, large buffers, and frequent allocations.
- Thermal throttling: laptops and mobiles may slow down after sustained load.
Fast wins (do these first)
1) Reduce export resolution before changing code
The number of pixels you shade is the biggest multiplier. Dropping from 800×800 to 400×400 reduces fragment work by ~4×. If your shader is “just a bit slow”, reducing resolution is often enough to make exports finish without timeouts.
2) Export fewer frames (or lower FPS)
A 3-second loop at 60 fps is 180 full renders. At 30 fps it’s 90. If the animation still reads well, halving frames is a huge stability improvement during export.
3) Keep the preview lightweight
If you’re iterating, start with a “draft mode” approach: simpler shading, fewer raymarch steps, fewer texture reads, and lower preview resolution. Once the look is locked, raise quality in controlled steps.
4) Avoid per-frame allocations
In JavaScript sketches, repeatedly creating arrays, images, or large objects per frame can cause garbage collector stalls. Prefer reusing buffers and objects.
Shader-side optimisation (GLSL)
Know what actually costs the most
In ShaderGif workflows, performance issues are most often caused by fragment shaders doing “too much work per pixel”. Common culprits:
- Raymarching with too many steps (or expensive distance functions).
- Nested loops whose iteration count scales with resolution or time.
- Multiple texture lookups per pixel (especially with dependent reads).
- Heavy use of
pow,exp,log,atanin hot paths. - High-frequency noise (especially multi-octave) evaluated for every pixel.
Prefer fewer iterations over micro-optimisations
Reducing a raymarch loop from 128 steps to 64 steps almost always beats clever algebra. If quality drops, consider adaptive stepping or early exits rather than cranking the max iteration count.
Use early exits (but be careful with divergence)
Breaking out early can help, especially in raymarching and signed-distance-field scenes. However, branching can introduce “warp divergence” where neighbouring pixels take different paths and the GPU effectively runs both branches. As a rule:
- Early exit helps most when many pixels exit early (e.g., background regions).
- If almost every pixel takes a different number of iterations, it may not help as much as you expect.
Clamp and bound expensive domains
Unbounded inputs can force your shader into worst-case behaviour (e.g., huge values flowing into trig functions, noise, or distance functions). Where appropriate, clamp or normalise inputs, and keep values in a stable range.
Reduce precision when it’s safe
On some devices, using lower precision can improve throughput. If you’re writing WebGL shaders, consider when mediump is acceptable (for colour math, some UV work, and non-accumulating operations). For geometry-like calculations (ray directions, camera matrices, accumulated distances), highp may be necessary to prevent artefacts. The right balance depends on the effect and target devices.
Minimise texture bandwidth
Textures are powerful, but sampling can become the bottleneck quickly:
- Reduce the number of texture reads in your hottest loop.
- Prefer small textures where possible, and avoid sampling multiple large textures per pixel.
- If a value doesn’t need to change every pixel, consider computing it per-vertex (where applicable) or approximating it analytically.
Avoid resolution-dependent loops
If you scale iteration counts by resolution (directly or indirectly), exports at higher resolutions can become dramatically slower. Keep loop bounds constant (or only mildly scaled), and treat resolution as a multiplier you manage outside the shader (via export settings).
Replace expensive functions with approximations (selectively)
Sometimes you can trade tiny visual differences for large speed-ups:
- Use polynomial approximations for smooth curves instead of repeated
pow. - Use cheaper noise (value noise) instead of complex gradient noise in large areas.
- Precompute palette curves instead of calling multiple trig functions per pixel.
If you want practical GLSL patterns you can paste in and adapt, see GLSL tips and snippets.
Export-specific bottlenecks (GIF creation)
Frame readback is expensive
To encode a GIF, the browser must read rendered pixels back from the GPU into CPU memory (commonly via readPixels). This step can stall the pipeline because the CPU has to wait for the GPU to finish drawing. If exports are slow even when preview feels smooth, readback + encoding is often the reason.
Encoding cost scales with frames and pixels
GIF encoding is CPU-heavy because it needs to compress many frames and often performs palette selection and dithering. To make exports more reliable:
- Lower resolution first, then reduce FPS, then reduce duration.
- Prefer clean, flatter colour ramps (heavy gradients can make dithering slower and noisier).
- Keep backgrounds simple when possible; animated noise everywhere increases temporal complexity.
Use “quality ladders”
A simple workflow that avoids frustration:
- Draft: low resolution, low frames, quick checks for timing and composition.
- Preview: medium settings to validate the look and loop seam.
- Final: only after you’ve confirmed the animation, raise one setting at a time.
Profiling and debugging performance
Look for the “shape” of slowdown
- Constant low FPS: shader is too heavy per pixel (GPU-bound).
- Periodic stutters: allocations/GC or expensive UI updates (CPU-bound).
- Export freezes late: encoder bottleneck or memory pressure.
- Starts fast, then slows: thermal throttling or sustained GPU load.
Use browser tools
Chrome/Edge DevTools Performance panel can reveal long tasks, heavy scripting, and repeated layout/paint work. For WebGL-specific insight, the broader WebGL community maintains practical checklists and explanations of common pitfalls; the Khronos WebGL wiki is a useful reference: WebGL Debugging
Quick self-check: is it GPU or CPU?
Try reducing resolution by 50%. If FPS jumps significantly, you’re likely GPU-bound (per-pixel work). If FPS barely changes, you’re likely CPU-bound (JavaScript, UI, encoding, or driver overhead).
Common patterns that hurt performance (and safer alternatives)
Raymarching scenes
- Problem: high max step counts, multiple distance evaluations, and costly lighting per step.
- Alternatives: reduce steps, early-out on max distance, simplify distance functions, compute lighting only near hits, cache repeated values.
Noise everywhere
- Problem: multi-octave noise per pixel, sometimes per step in a loop.
- Alternatives: fewer octaves, lower frequency noise, domain-warp less often, or bake a noise texture and sample it.
Branch-heavy shaders
- Problem: many
if/elsepaths per pixel can cause divergence. - Alternatives: use smooth masks (
smoothstep), mix values (mix), or precompute mode flags so the shader executes one main path.
Practical “performance checklist” before you publish or share
- Does the preview run smoothly at your intended resolution on an average laptop?
- Have you tested export at a smaller size first to confirm settings and loop seam?
- Are your loop bounds stable and not accidentally scaling with resolution/time?
- Have you removed debug visualisations and extra passes you no longer need?
- Is your palette and colour work simple enough to encode cleanly without excessive dithering?
Related docs
- GLSL tips and snippets – practical code patterns for cleaner, faster shaders
- Colour palettes in shaders – palette functions that stay readable and efficient
- Licensing – reuse, attribution, and sharing guidelines for shader work
Last updated: 30 December 2025
