Performance

Learn what makes an effect expensive on the GPU and how to keep it inside the frame budget.

Staying inside the frame budget

At 60fps, each frame has 16.67ms. That window is the frame budget. Shaders draws once per frame, combining every visible layer, and caps drawing at 60fps.

A simple setup renders in under 1ms. Deep nesting with several effects can push toward the full 16.67ms.

Keeping effects fast offscreen

A <Shader> that starts offscreen doesn't initialize until it scrolls into view. When it scrolls out again, the framework components pause its animation. The renderer itself drops to about 1fps whenever the canvas is offscreen, which also covers createShader in the JS package. Offscreen effects cost almost nothing.

Knowing what each component costs

Components fall into two groups with different costs:

  • Generators (LinearGradient, Circle, Plasma, Swirl and others) compute each pixel from scratch with a formula. A full-screen gradient or noise pattern takes a fraction of a millisecond.
  • Filters that read neighbouring pixels (Blur, Glass, GlassTiles, Glow, Dither and others) need the finished image of the layers inside them first. That costs an extra render pass, explained below. Filters that change each pixel on its own, such as HueShift or Vignette, skip that pass.

Rough cost per component:

CategoryExamplesTypical cost
Very lightSolidColor, LinearGradient, RadialGradient~0–0.1ms
LightSwirl, Circle, Plasma, SimplexNoise, most generators~0.1–0.5ms
MediumBlur, Glow, Dither, Halftone, Pixelate, CursorTrail~0.5–2ms
HeavyGlass, GlassTiles, multiple nested render passes~1–2ms+

These are rough estimates on a modern GPU. Real cost varies with hardware, canvas size, and pixel density.

Limiting render passes

A render pass draws layers into a separate offscreen image instead of the canvas. Picture painting a layer on its own sheet, then laying the sheet over the rest. An effect that needs to read the finished pixels of other layers works from that sheet. Shaders' internals call this render-to-texture (RTT).

Three things add a render pass:

  • Filters that read neighbouring pixels: Blur, Glass, GlassTiles and similar, for the layers inside them.
  • The map driver: a dynamic prop mapped from another layer renders that source layer to its own pass.
  • Masks: a layer used as a maskSource is rendered to its own pass.

Nesting multiplies the cost. Each pass must finish before the one wrapping it can start, so three nested filters means three passes in a row.

Keep heavy filters flat instead of nested:

<!-- Avoid: three render passes in a row -->
<Shader>
  <LinearGradient />
  <GlassTiles>
    <Glass>
      <Blur :intensity="20">
        <Circle />
      </Blur>
    </Glass>
  </GlassTiles>
</Shader>

<!-- Better: one render pass -->
<Shader>
  <LinearGradient />
  <GlassTiles>
    <Circle />
  </GlassTiles>
</Shader>

Practical tips

Build the background from generators. LinearGradient, Plasma, SimplexNoise and similar generators cost almost nothing. Lay the base with these before adding filters on top.

Prefer flat filters to nested ones. Every filter that wraps children adds a render pass. Three filters side by side cost less than three nested.

Hide layers with visible, not opacity. A layer with visible={false} is left out of the composition entirely, so it costs nothing. A layer at opacity 0 still renders. Changing visible rebuilds the composition, so use it for layers that stay off, not for animation.

Expect larger canvases to cost more. Render passes match the canvas resolution. A full-screen Glass on a 4K display costs more than on a 1080p one.

Animate runtime props, not compile-time props. Most props update a uniform: a value the GPU reads every frame, like a CSS variable the shader can see. Changing one is instant. A few props are compile-time: changing them recompiles the effect. Shaders keeps drawing the old version until the new one is ready, but each recompile costs CPU and GPU time. Component pages flag compile-time props.

Next steps

  • Composing effects: stack and nest layers, and control which layers an effect touches.
  • Dynamic props: drive props from time, the cursor, or another layer.
  • Hooks and events: run code when an effect is ready, and handle browsers without WebGPU.