Custom shaders
insomni draws everything through a fixed set of built-in “kinds” (rects, circles, strokes, sprites, glyphs…). Custom shaders are the escape hatch when you want a pixel program the built-ins don’t cover — an animated sprite effect, a generative background, an image filter, a refracting lens.
The public entry point is createTextureEffect: a textured quad, placed by
the renderer’s camera, whose fragment stage you supply as a string of WGSL. It
returns a CustomDrawable, so it
drops straight into the same render([...]) call as any layer — no extra
pipelines to manage, no core changes.
import { createTextureEffect, loadTexture } from "insomni";
const texture = await loadTexture(renderer, "/ship.png");const effect = createTextureEffect(renderer, { texture, rect: { x: 40, y: 40, width: 128, height: 128 }, fragment: /* wgsl */ ` let c = texAt(uv); if (c.a < 0.01) { discard; } return c + vec4f(vec3f(sin(u.time) * 0.3), 0.0) * c.a; `,});
renderer.render([effect]);Two shapes, one API
Section titled “Two shapes, one API”createTextureEffect always shades a textured quad — but whether the fragment
reads the texture is up to you. That single fact gives you two distinct uses:
| Shape | Texture | Rect | Used for |
|---|---|---|---|
| Per-element | a loaded image | the sprite’s placement box | sprite effects, image filters — outline, pixelate, cel-shade |
| Full-view | a 1×1 blank, ignored | the whole viewport | generative backgrounds, plasma/noise, glass lenses, water |
A per-element effect samples the texture with texAt(uv) and transforms it. A
full-view effect ignores the texture entirely and computes every pixel from uv
- uniforms — the classic full-screen shader. The guide walks through both.
How it fits into a frame
Section titled “How it fits into a frame”An effect is a CustomDrawable: the renderer hands it an active render pass and
it records one 4-vertex draw in an explicit render phase —
- Placement is
"underlay"(before all scene layers) or"overlay"(the existing late phase and default). Array position orders effects only within a phase. - Space is
"ui"(pixels) or"world"(through the live camera), same as a layer. Both are dpr- and resize-correct because the effect binds the renderer’s real camera bind group. - Blending is premultiplied alpha, matching sprites — your fragment must
returna premultipliedvec4f(ordiscard).
To land a custom shader between a layer’s own primitives instead of a fixed
phase, push it into the layer with
layer.pushCustom — it z-interleaves at its
submission point with the layer’s shapes, glyphs, and sprites.
What it can and can’t do
Section titled “What it can and can’t do”It shades a quad from a texture you hand it plus uniforms. It is not a
post-process over the already-rendered scene — there is no built-in “grab the
frame and filter it” pass yet, so effects like a custom full-screen anti-alias or
a scene-wide bloom aren’t expressible through createTextureEffect alone. Those
need render-to-texture; if you need them, reach for a raw
CustomDrawable with your own
offscreen target.
Performance model
Section titled “Performance model”The compiled pipeline and bind-group layouts are cached per device, keyed by
the WGSL source. An outline shader applied to 100 sprites is one shader compile
and one pipeline — each effect owns only a small uniform buffer and two bind
groups. A static effect (no set() calls per frame) does zero per-frame CPU
work, and damage-tracked rendering skips it entirely on idle frames. You pay for
what animates, nothing more.
Authoring in TypeGPU
Section titled “Authoring in TypeGPU”The fragment field is just WGSL text, and TypeGPU
is a WGSL generator — so you can author an effect in TGSL and pass the resolved
string. See Writing shader effects for the
bridge.
See also
Section titled “See also”- Writing shader effects — the full
createTextureEffectreference and patterns. - Custom shaders in a layer —
layer.pushCustomto interleave a custom shader at a submission point. - CustomDrawable — the raw render-pass hook underneath.
- Drawing primitives — sprites, textures, and the built-in kinds.