API reference

Every public class, method, and event in @codexo/exojs. Generated from source.

C

classShaderFilter

@codexo/exojs / rendering / stable

A Filter that renders its input through a user-supplied shader, in whichever language the active backend speaks. One filter carries both sources - GLSL for WebGL2, WGSL for WebGPU - on the same Shader contract materials use, and picks between them internally. Supply both and the filter runs unchanged under `backend: 'auto'`, where the engine decides which backend it gets. ## Usage ```ts const filter = new ShaderFilter({ glsl: { fragment: `#version 300 es precision mediump float; uniform sampler2D uTexture; uniform float uTime; in vec2 vUv; out vec4 fragColor; void main() { fragColor = texture(uTexture, vUv); } `, }, wgsl: ` struct Uniforms { uTime: f32 }; @group(0) @binding(1) var uTexture: texture_2d<f32>; @group(0) @binding(2) var uSampler: sampler; @group(1) @binding(0) var<uniform> uniforms: Uniforms; @fragment fn fragmentMain(@location(0) vUv: vec2<f32>) -> @location(0) vec4<f32> { return textureSample(uTexture, uSampler, vUv); } `, uniforms: { uTime: 0 }, }); filter.setUniform('uTime', performance.now() / 1000); sprite.filters = [filter]; ``` ## Auto-bound entries Both languages receive the filter's input texture, the output dimensions and the v-axis orientation, and both see a `vUv` varying running 0..1 across the quad. Declare only the ones the source reads. ### GLSL ```glsl uniform sampler2D uTexture; // the filter's input, texture slot 0 uniform vec2 uResolution; // output dimensions in texels uniform float uOrientation; // sign of the v axis against the effect domain in vec2 vUv; ``` ### WGSL ```wgsl @group(0) @binding(0) var<uniform> uResolution: vec2<f32>; @group(0) @binding(1) var uTexture: texture_2d<f32>; @group(0) @binding(2) var uSampler: sampler; @group(0) @binding(3) var<uniform> uOrientation: f32; ``` ## Sampling and the v axis `vUv` addresses the input in TEXEL space: sampling `uTexture` at `vUv` reproduces the input unchanged, whatever the effect domain looks like. The two backends store that domain the other way up, though - a WebGL2 render texture bottom-up, a WebGPU one top-down - so `v` runs downwards through the effect on one and upwards on the other. `uOrientation` is the sign that relates the two: `+1` where `v` grows ALONG the effect domain's y axis (downwards) and `-1` where it grows against it. Multiply the v component of any DIRECTIONAL offset by it and one source behaves identically on both backends: ```glsl // Read the texel `dy` below this one, on either backend. vec4 below = texture(uTexture, vUv + vec2(0.0, dy * uOrientation)); ``` Offsets that are not directional - a radial blur kernel, a symmetric neighbourhood, anything that only recolours its own texel - need nothing. The same sign also maps `vUv` onto a texture sampled ALONGSIDE the input (a displacement or mask map, whose own row 0 is its top on both backends): `0.5 + (vUv.y - 0.5) * uOrientation` is that texture's v. ## User uniforms A source built with createFilterShader can declare its uniforms, in which case the engine generates both languages' declarations from one layout and uniforms becomes a namespace of typed accessors: ```ts const shader = createFilterShader({ glsl: { fragment }, wgsl, uniforms: { uTime: UniformType.Float }, }); const filter = ShaderFilter.from(shader); filter.uniforms.uTime.set(elapsed); ``` Both bodies then read through the instance name `uniforms`, textures are declared in `textures`, and setUniform is gone from the type. Without a declaration the source keeps today's contract: anything in uniforms is bound after the auto-binds, GLSL resolves them by name with texture uniforms claiming slots 1..N, and WGSL packs every non-texture uniform into one buffer at `@group(1) @binding(0)`, each in a 16-byte slot **in declaration order**, binding texture uniforms from `@group(1) @binding(1)` onwards, each followed by its sampler. ## Missing sources A filter that carries only one language throws ShaderFilterBackendError when it attaches to a backend speaking the other one - before it compiles or allocates anything.

4
props
8
methods
0
events
Import
import { ShaderFilter } from '@codexo/exojs'

A Filter that renders its input through a user-supplied shader, in whichever language the active backend speaks.

One filter carries both sources - GLSL for WebGL2, WGSL for WebGPU - on the same Shader contract materials use, and picks between them internally. Supply both and the filter runs unchanged under `backend: 'auto'`, where the engine decides which backend it gets.

## Usage

```ts const filter = new ShaderFilter({ glsl: { fragment: `#version 300 es precision mediump float; uniform sampler2D uTexture; uniform float uTime; in vec2 vUv; out vec4 fragColor; void main() { fragColor = texture(uTexture, vUv); } `, }, wgsl: ` struct Uniforms { uTime: f32 };

@group(0) @binding(1) var uTexture: texture_2d<f32>; @group(0) @binding(2) var uSampler: sampler; @group(1) @binding(0) var<uniform> uniforms: Uniforms;

@fragment fn fragmentMain(@location(0) vUv: vec2<f32>) -> @location(0) vec4<f32> { return textureSample(uTexture, uSampler, vUv); } `, uniforms: { uTime: 0 }, });

filter.setUniform('uTime', performance.now() / 1000); sprite.filters = [filter]; ```

## Auto-bound entries

Both languages receive the filter's input texture, the output dimensions and the v-axis orientation, and both see a `vUv` varying running 0..1 across the quad. Declare only the ones the source reads.

### GLSL

```glsl uniform sampler2D uTexture; // the filter's input, texture slot 0 uniform vec2 uResolution; // output dimensions in texels uniform float uOrientation; // sign of the v axis against the effect domain in vec2 vUv; ```

### WGSL

```wgsl @group(0) @binding(0) var<uniform> uResolution: vec2<f32>; @group(0) @binding(1) var uTexture: texture_2d<f32>; @group(0) @binding(2) var uSampler: sampler; @group(0) @binding(3) var<uniform> uOrientation: f32; ```

## Sampling and the v axis

`vUv` addresses the input in TEXEL space: sampling `uTexture` at `vUv` reproduces the input unchanged, whatever the effect domain looks like. The two backends store that domain the other way up, though - a WebGL2 render texture bottom-up, a WebGPU one top-down - so `v` runs downwards through the effect on one and upwards on the other.

`uOrientation` is the sign that relates the two: `+1` where `v` grows ALONG the effect domain's y axis (downwards) and `-1` where it grows against it. Multiply the v component of any DIRECTIONAL offset by it and one source behaves identically on both backends:

```glsl // Read the texel `dy` below this one, on either backend. vec4 below = texture(uTexture, vUv + vec2(0.0, dy * uOrientation)); ```

Offsets that are not directional - a radial blur kernel, a symmetric neighbourhood, anything that only recolours its own texel - need nothing. The same sign also maps `vUv` onto a texture sampled ALONGSIDE the input (a displacement or mask map, whose own row 0 is its top on both backends): `0.5 + (vUv.y - 0.5) * uOrientation` is that texture's v.

## User uniforms

A source built with createFilterShader can declare its uniforms, in which case the engine generates both languages' declarations from one layout and uniforms becomes a namespace of typed accessors:

```ts const shader = createFilterShader({ glsl: { fragment }, wgsl, uniforms: { uTime: UniformType.Float }, });

const filter = ShaderFilter.from(shader);

filter.uniforms.uTime.set(elapsed); ```

Both bodies then read through the instance name `uniforms`, textures are declared in `textures`, and setUniform is gone from the type.

Without a declaration the source keeps today's contract: anything in uniforms is bound after the auto-binds, GLSL resolves them by name with texture uniforms claiming slots 1..N, and WGSL packs every non-texture uniform into one buffer at `@group(1) @binding(0)`, each in a 16-byte slot **in declaration order**, binding texture uniforms from `@group(1) @binding(1)` onwards, each followed by its sampler.

## Missing sources

A filter that carries only one language throws ShaderFilterBackendError when it attaches to a backend speaking the other one - before it compiles or allocates anything.

Constructors1
new(options: ShaderFilterOptionsConstruction options for a ShaderFilter.<F, B>): ShaderFilter<F, B>
Methods8
Execute one filter pass: sample from input, write the result to output. Both textures are bounds × resolution texels - NOT the drawable's logical bounding box. Any parameter a subclass expresses in pixels (a blur radius, a displacement amount) is in LOGICAL units by convention and must be multiplied by resolution before it is used as a texel offset; otherwise the effect shrinks by 1/resolution on a HiDPI display. Parameters expressed as a fraction of the target (or as pure colour maths) need no adjustment. The engine always passes resolution. It is optional for the hand-rolled case - a post-processing chain that creates its own RenderTextures and calls apply directly - where the textures are whatever size the caller made them and 1 is the honest answer.
destroy(): void
Release any GPU-side resources held by this filter (uniform buffers, pipelines, intermediate textures). The base drops the attachment list; subclasses with GPU state (BlurFilter, ColorMatrixFilter) override and call super.destroy().
The logical bounds this effect can produce from the logical bounds it is given - the contract that lets an effect change a drawable's visual extent instead of being clipped by the geometry it was captured from. Both rectangles are in the capture domain's LOGICAL units, the same ones RenderNode.getBounds reports. They are not device pixels: the target a pass runs against is separately allocated at bounds × resolution texels, so an expansion of 8 stays 8 logical units at every pixel ratio. The default is the identity - an effect that only recolours what it is given (a colour matrix, a LUT) needs no override. An effect that reaches outside its input (a blur, a glow) must declare that reach, and one that reaches asymmetrically (a drop shadow) may move the edges independently: ts public override getOutputBounds(input: ReadonlyRectangle, output: Rectangle): void { output.set(input.x - this.radius, input.y - this.radius, input.width + this.radius * 2, input.height + this.radius * 2); } In a CHAIN each filter is asked in turn, with the previous filter's output as its input, and the barrier's capture domain is the union of the source bounds and every stage's answer. A bounds-REDUCING effect is therefore represented - the domain simply keeps the room its predecessors needed, so no pass is ever clipped by a target smaller than what it declared. input and output are never the same object, so an implementation may read input freely while writing output. Called once per frame for every filtered node, so it must not allocate.
invalidate(): void
Tell every node this filter is attached to that its rendered output is out of date. Call it after mutating anything that changes what the filter draws or how far it reaches - the stock filters do this from their own setters. Without it a cached or retained representation of the owning node keeps replaying the result the filter produced before the change.
Set one uniform and notify every node rendering this filter. Only available on a source that declares no uniform schema; a typed filter writes through uniforms instead.
Build a filter from an existing Shader, so one source can back several filters. The source must already carry complete sources per language - no default vertex stage is filled in.
Properties4
resolution: TargetResolution
Resolution this filter's render targets are rasterized at, in device pixels per logical unit. 'inherit' (the default) matches the surface the result is composited into, so a filtered subtree stays as sharp as its surroundings on a HiDPI display. Lower it for a filter whose output is low-frequency anyway - a heavy blur at 0.5 costs a quarter of the fragments and is hard to tell apart. A filter CHAIN shares one target size, so the whole chain runs at the lowest resolution any of its filters asks for. ts const blur = new BlurFilter({ strength: 4 }); blur.resolution = 0.5; // half-resolution blur, quarter the fill cost
The declared named uniform blocks, each owning its own values.
The typed accessors of the declared uniform block, or - on a source without a schema - the current uniform values, for reading. The untyped record is deliberately not writable: a value written straight into it would reach the GPU on the next draw but tell nobody, so a cached or retained representation of the owning node would keep replaying the frame the old value produced. Write through setUniform / setUniforms.
Source