Custom shaders bind GLSL and WGSL programs to scene objects via Shader.from({ gl, gpu, resources }). Uniforms live in typed UniformGroups, textures are passed as separate resources, and the same shader can target both WebGL and WebGPU.
Quick Start
Related skills: pixijs-filters (built-in filters), pixijs-scene-mesh (custom geometry), pixijs-performance (batch optimization), pixijs-migration-v8 (shader API migration from v7).
Core Patterns
Dual-renderer shader (WebGL + WebGPU)
If only gl is provided, the shader works with WebGL only. If only gpu is provided, it works with WebGPU only. The compatibleRenderers bitmask is set automatically.
GlProgram does not auto-inject #version 300 es. If you write #version 300 es yourself, PixiJS preserves it and treats the shader as GLSL ES 3.0; otherwise it injects WebGL1 compat macros (#define in varying, #define texture texture2D) and runs the shader as WebGL1-style GLSL. GlProgram always injects a default precision (highp vertex, mediump fragment) and the program name. It also declares highp for sampler types GLSL ES 3.0 gives no default (usampler2D, isampler2D, sampler2DArray, sampler3D, and others), so you don't need a precision line for them; one you write yourself is kept. sampler2D and samplerCube keep their lowp default. For GLSL ES 3.0, use in/out instead of attribute/varying, texture() instead of texture2D(), and an out vec4 instead of gl_FragColor.
Textures as resources
Textures are resources, not uniforms. Pass the texture's source and style separately:
Resources are a flat key-value map. The key must match the uniform/binding name in the shader source.
Resources can also be plain objects (auto-wrapped into UniformGroup):
UBO mode (Uniform Buffer Objects)
UBO mode packs uniforms into a single GPU buffer. Required for WebGPU; optional (WebGL2+) for WebGL.
UBO rules:
- Only
f32andi32based types are supported (nou32). Matrices are float-only. - Samplers/textures cannot go in a UBO.
- The UniformGroup name in resources must exactly match the UBO block name in the shader.
- Structure and order must exactly match the shader layout.
- UBO sync uses
new Functionunder the hood. In strict-CSP environments (nounsafe-eval), importpixi.js/unsafe-evalonce at startup to swap in the fallback sync path; without it, UBO-backed shaders (and therefore WebGPU) will throw on first use.
Custom filter
Filter.from({ gl, resources }) is the shorthand. Pass only a fragment shader; PixiJS supplies a default vertex shader that handles output frame positioning.
For a custom vertex shader, use new Filter({ glProgram: new GlProgram({ vertex, fragment }), resources }).
Filter shader conventions (GLSL ES 3.0)
in vec2 vTextureCoord;instead ofvarying vec2 vTextureCoord;out vec4 finalColor;instead ofgl_FragColortexture(uTexture, uv)instead oftexture2D(uTexture, uv)- The default vertex shader exposes
uInputSize,uOutputFrame,uOutputTextureand helpersfilterVertexPosition()/filterTextureCoord()
Sampling the render target behind the filter
Set blendRequired: true and sample uBackTexture in the fragment shader. PixiJS copies the destination pixels into that uniform before running the filter:
Only enable blendRequired when you need it; it forces an extra GPU copy every frame.
Updating uniforms at runtime
Advanced GPU features
See references/advanced-gpu.md [blocked] for full samples of:
- Partial buffer and texture uploads:
buffer.update(sizeInBytes, offsetInBytes)uploads only the changed byte range;bufferImageSource.update(startTexel, endTexel)uploads only a texel range of a data texture. - Vertex and index counts, winding, culling:
geometry.vertexCount(replaces the deprecatedgetSize()),geometry.indexCountto draw a prefix of a shared index buffer,state.clockwiseFrontFaceandcullMode. - WGSL override constants (WebGPU):
Shader.from({ gpu, resources, overrides: { STEPS: 8 } }); each distinct set compiles its own pipeline. - Custom bind group layouts (WebGPU): generate the default with
generateGpuLayoutGroups(extractStructAndGroups(source)), edit it, pass it asgpuLayout. - Depth sampling with
TextureView(WebGPU): bindnew TextureView(depth, { aspect: "depth-only" })as a resource while the target's depth attachment isdepthReadOnly. - Integer textures:
BufferImageSourcewithrgba32uintetc.,usampler2D/texelFetchandtexture_2d<u32>/textureLoad,scaleMode: "nearest". - 3D and array textures:
depthorarrayLayerCounton aTextureSource/BufferImageSource, sampled withsampler3D/texture_3d<f32>orsampler2DArray/texture_2d_array<f32>; they upload whole, andrenderer.render({ container, target: source, layer })draws into one slice or layer. - Storage textures (WebGPU):
storage: trueon aTextureSource, thenrenderer.texture.getGpuSource(source).createView()to write it from your own compute pass. - Render bundles (WebGPU): record draws once with
encoder.beginBundle()/endBundle()and replay withexecuteBundle()whileisBundleValid()holds. - Pooled scratch textures:
TexturePool.getOptimalTexture({ width, height, resolution, antialias })andreturnTexture().
Uniform type reference
See references/uniform-types.md [blocked] for the complete table of supported types, their WGSL/GLSL equivalents, and value formats.
Custom Batcher (extension-based)
The Batcher abstract class enables custom batching for specialized rendering. Subclass it and register via extensions:
Elements reference the batcher by batcherName. The BatchableElement interface requires: batcherName, texture, blendMode, indexSize, attributeSize, topology, and packAsQuad.
A custom InstructionPipe that caches state per InstructionSet (the way BatcherPipe keeps its batchers) should implement destroyInstructionSet(instructionSet). The renderer calls it when the owning render group is destroyed, so the cached GPU objects are released with it instead of leaking.
Common Mistakes
[CRITICAL] Old Shader.from(vertex, fragment, uniforms) constructor
Wrong:
Correct:
v8 requires an options object with gl/gpu programs and resources. The positional API was removed.
[CRITICAL] UniformGroup without type annotation
Wrong:
Correct:
Every uniform requires an explicit { value, type } pair. Omitting the type causes a runtime error: "Uniform type undefined is not supported."
[HIGH] UBO with unsupported types or wrong structure
UBO mode supports f32 and i32 based types (scalars and vectors). u32 is not in the supported UniformGroup type list and will throw. Matrices are float-only (mat*<f32>). Samplers cannot be placed in UBOs.
The struct name and field order must exactly match the shader's UBO declaration. Mismatches produce garbled rendering with no error.
[HIGH] Putting textures in UniformGroup
Wrong:
Correct:
Textures are resources, not uniforms. Pass texture.source (TextureSource) and texture.source.style (TextureStyle) as top-level resource entries.
[HIGH] Replaying a render bundle against a different render target
Wrong:
Correct:
A bundle bakes the attachments and winding of the pass it was recorded in, and the device it was recorded on. Replaying it under a different target either fails WebGPU validation for the whole frame or silently renders inside out; replaying it after a device loss is rejected outright.
[MEDIUM] Reading Geometry.getSize()
geometry.getSize() is deprecated since 8.20.0 and logs a warning. Use geometry.vertexCount, which is cached and only recomputed when buffers or attributes change.


