shaderpad 1.0.0-beta.78 → 1.0.0-beta.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +887 -0
  2. package/dist/chunk-BUZPU5IY.mjs +7 -0
  3. package/dist/chunk-BUZPU5IY.mjs.map +1 -0
  4. package/dist/dev/{chunk-ZVPQU2RM.mjs → chunk-W5VOJOKO.mjs} +13 -9
  5. package/dist/dev/chunk-W5VOJOKO.mjs.map +1 -0
  6. package/dist/dev/plugins/face.js +14 -11
  7. package/dist/dev/plugins/face.js.map +1 -1
  8. package/dist/dev/plugins/face.mjs +5 -4
  9. package/dist/dev/plugins/face.mjs.map +1 -1
  10. package/dist/dev/plugins/hands.js +14 -11
  11. package/dist/dev/plugins/hands.js.map +1 -1
  12. package/dist/dev/plugins/hands.mjs +5 -4
  13. package/dist/dev/plugins/hands.mjs.map +1 -1
  14. package/dist/dev/plugins/mediapipe-common.js +13 -8
  15. package/dist/dev/plugins/mediapipe-common.js.map +1 -1
  16. package/dist/dev/plugins/mediapipe-common.mjs +3 -1
  17. package/dist/dev/plugins/pose.js +14 -11
  18. package/dist/dev/plugins/pose.js.map +1 -1
  19. package/dist/dev/plugins/pose.mjs +5 -4
  20. package/dist/dev/plugins/pose.mjs.map +1 -1
  21. package/dist/dev/plugins/segmenter.js +14 -11
  22. package/dist/dev/plugins/segmenter.js.map +1 -1
  23. package/dist/dev/plugins/segmenter.mjs +5 -4
  24. package/dist/dev/plugins/segmenter.mjs.map +1 -1
  25. package/dist/dev/web-component.js +17 -0
  26. package/dist/dev/web-component.js.map +1 -1
  27. package/dist/dev/web-component.mjs +17 -0
  28. package/dist/dev/web-component.mjs.map +1 -1
  29. package/dist/plugins/face.d.mts +1 -0
  30. package/dist/plugins/face.d.ts +1 -0
  31. package/dist/plugins/face.js +34 -34
  32. package/dist/plugins/face.js.map +1 -1
  33. package/dist/plugins/face.mjs +33 -33
  34. package/dist/plugins/face.mjs.map +1 -1
  35. package/dist/plugins/hands.d.mts +1 -0
  36. package/dist/plugins/hands.d.ts +1 -0
  37. package/dist/plugins/hands.js +13 -13
  38. package/dist/plugins/hands.js.map +1 -1
  39. package/dist/plugins/hands.mjs +5 -5
  40. package/dist/plugins/hands.mjs.map +1 -1
  41. package/dist/plugins/mediapipe-common.d.mts +3 -2
  42. package/dist/plugins/mediapipe-common.d.ts +3 -2
  43. package/dist/plugins/mediapipe-common.js +5 -5
  44. package/dist/plugins/mediapipe-common.js.map +1 -1
  45. package/dist/plugins/mediapipe-common.mjs +1 -1
  46. package/dist/plugins/pose.d.mts +1 -0
  47. package/dist/plugins/pose.d.ts +1 -0
  48. package/dist/plugins/pose.js +35 -35
  49. package/dist/plugins/pose.js.map +1 -1
  50. package/dist/plugins/pose.mjs +12 -12
  51. package/dist/plugins/pose.mjs.map +1 -1
  52. package/dist/plugins/segmenter.d.mts +1 -0
  53. package/dist/plugins/segmenter.d.ts +1 -0
  54. package/dist/plugins/segmenter.js +13 -13
  55. package/dist/plugins/segmenter.js.map +1 -1
  56. package/dist/plugins/segmenter.mjs +5 -5
  57. package/dist/plugins/segmenter.mjs.map +1 -1
  58. package/dist/web-component.css +8 -0
  59. package/dist/web-component.js +4 -3
  60. package/dist/web-component.js.map +1 -1
  61. package/dist/web-component.mjs +2 -1
  62. package/dist/web-component.mjs.map +1 -1
  63. package/package.json +7 -2
  64. package/dist/chunk-RWGXFWIP.mjs +0 -7
  65. package/dist/chunk-RWGXFWIP.mjs.map +0 -1
  66. package/dist/dev/chunk-ZVPQU2RM.mjs.map +0 -1
package/README.md ADDED
@@ -0,0 +1,887 @@
1
+ ![ShaderPad logo](./ShaderPad.png)
2
+
3
+ **Minimal setup. Tiny footprint.**
4
+
5
+ ShaderPad is a lightweight, dependency-free library that reduces boilerplate when working with fragment shaders. It provides a simple interface for setup, texture and uniform management, and user interactions such as mouse movements and window resizing. It comes “batteries included” for common needs, with optional plugins — from PNG export to face/pose tracking — for more advanced use cases. Simple, performant, and portable, ShaderPad lets you focus on the fun part: writing GLSL.
6
+
7
+ ## Installation
8
+
9
+ To install the library into an existing project:
10
+
11
+ ```bash
12
+ npm install shaderpad
13
+ ```
14
+
15
+ ## Starters
16
+
17
+ To scaffold a local fullscreen starter app:
18
+
19
+ ```bash
20
+ npm create shaderpad@latest
21
+ ```
22
+
23
+ The CLI will walk you through the available starters interactively.
24
+
25
+ To try the same templates online on StackBlitz:
26
+
27
+ - [Open the basic starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-basic-ts?title=ShaderPad%20Basic%20shader%20%28TypeScript%29)
28
+ - [Open the LYGIA starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-lygia-ts?title=ShaderPad%20LYGIA%20%28TypeScript%29)
29
+ - [Open the face tracking starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-face-ts?title=ShaderPad%20with%20face%20tracking%20%28TypeScript%29)
30
+ - [Open the pose tracking starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-pose-ts?title=ShaderPad%20with%20pose%20tracking%20%28TypeScript%29)
31
+ - [Open the hand tracking starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-hands-ts?title=ShaderPad%20with%20hand%20tracking%20%28TypeScript%29)
32
+ - [Open the segmentation starter](https://stackblitz.com/fork/github/miseryco/shaderpad/tree/main/packages/create-shaderpad/template-segmenter-ts?title=ShaderPad%20with%20segmentation%20%28TypeScript%29)
33
+
34
+ ## Links
35
+
36
+ ### For humans:
37
+
38
+ - [Docs](https://misery.co/shaderpad/)
39
+ - [Quickstart](https://misery.co/shaderpad/docs/getting-started/quickstart/)
40
+ - [Browse interactive examples](https://misery.co/shaderpad/examples/)
41
+
42
+ ### For AIs and coding agents:
43
+
44
+ - [AI agent guide](https://misery.co/shaderpad/docs/getting-started/ai-agent-guide/)
45
+ - [llms.txt](https://misery.co/shaderpad/llms.txt)
46
+ - [Examples on GitHub](https://github.com/miseryco/shaderpad/tree/main/docs/src/examples/demos)
47
+
48
+ ## Example
49
+
50
+ ```typescript
51
+ import ShaderPad from 'shaderpad';
52
+
53
+ const fragmentShaderSrc = `#version 300 es
54
+ precision highp float;
55
+
56
+ // Built-in variables.
57
+ in vec2 v_uv;
58
+ uniform float u_time;
59
+ uniform vec2 u_resolution;
60
+ uniform vec2 u_cursor;
61
+
62
+ // Custom variables.
63
+ uniform vec3 u_cursorColor;
64
+
65
+ out vec4 outColor;
66
+
67
+ void main() {
68
+ vec2 uv = v_uv * u_resolution;
69
+ vec2 dotGrid = mod(uv, 50.) - 25.;
70
+ float dotDist = length(dotGrid);
71
+ float dot = step(dotDist, 5.);
72
+ float cursorDist = distance(uv, u_cursor * u_resolution);
73
+ float cursor = step(cursorDist, 25. + sin(u_time * 5.) * 5.);
74
+ vec3 color = mix(vec3(0., 0., 1.), vec3(1.), dot);
75
+ color = mix(color, u_cursorColor, cursor);
76
+ outColor = vec4(color, 1.);
77
+ }
78
+ `;
79
+
80
+ const shader = new ShaderPad(fragmentShaderSrc /* , options */);
81
+ const getColor = (time: number) =>
82
+ [time, time + (Math.PI * 2) / 3, time + (Math.PI * 4) / 3].map(x => 1 + Math.sin(x) / 2);
83
+ shader.initializeUniform('u_cursorColor', 'float', getColor(0));
84
+ shader.play(time => {
85
+ shader.updateUniforms({ u_cursorColor: getColor(time) });
86
+ });
87
+ ```
88
+
89
+ See the [interactive examples](https://misery.co/shaderpad/examples/) or browse the source in [`docs/src/examples/demos/`](./docs/src/examples/demos).
90
+
91
+ ## Usage
92
+
93
+ ### Uniforms
94
+
95
+ #### `initializeUniform(name, type, value, options?)`
96
+
97
+ Initialize a uniform, which must be declared in your shader.
98
+
99
+ ```typescript
100
+ shader.initializeUniform('u_speed', 'float', 1.5);
101
+ // Vectors are passed as arrays. This is a vec3:
102
+ shader.initializeUniform('u_color', 'float', [1.0, 0.5, 0.0]);
103
+ // …but you can also pass an array. This is an array of floats:
104
+ shader.initializeUniform('u_data', 'float', [1.0, 0.5, 0.0], { arrayLength: 3 });
105
+ ```
106
+
107
+ **Parameters:**
108
+
109
+ - `name` (string): Uniform name
110
+ - `type` ('float' | 'int' | 'uint'): Uniform type
111
+ - `value` (number | number[] | (number | number[])[]): Initial value(s)
112
+ - `options` (optional): `{ arrayLength?: number }` - Required for arrays
113
+
114
+ #### `updateUniforms(updates, options?)`
115
+
116
+ Update uniform values.
117
+
118
+ ```typescript
119
+ shader.updateUniforms({
120
+ u_speed: 2.0,
121
+ u_color: [1.0, 0.0, 0.0],
122
+ });
123
+ ```
124
+
125
+ **Parameters:**
126
+
127
+ - `updates`: Object mapping uniform names to their new values
128
+ - `options` (optional): `{ startIndex?: number }` - Starting index for partial array updates
129
+
130
+ #### Uniform arrays
131
+
132
+ ShaderPad supports uniform arrays. Initialize them by passing an `arrayLength` option:
133
+
134
+ ```typescript
135
+ // Initialize a vec2 array with 5 elements.
136
+ shader.initializeUniform(
137
+ 'u_positions',
138
+ 'float',
139
+ [
140
+ [0, 0],
141
+ [1, 1],
142
+ [2, 2],
143
+ ],
144
+ { arrayLength: 3 },
145
+ );
146
+
147
+ // Update all elements.
148
+ shader.updateUniforms({
149
+ u_positions: [
150
+ [0.1, 0.2],
151
+ [1.1, 1.2],
152
+ [2.1, 2.2],
153
+ ],
154
+ });
155
+
156
+ // Update a subset starting at index 2.
157
+ shader.updateUniforms(
158
+ {
159
+ u_positions: [
160
+ [2.5, 2.5],
161
+ [3.5, 3.5],
162
+ ],
163
+ },
164
+ { startIndex: 2 },
165
+ );
166
+ ```
167
+
168
+ #### Included uniforms
169
+
170
+ | Uniform | Type | Description |
171
+ | ---------------------- | -------------- | --------------------------------------------------------------------------------- |
172
+ | `u_time` | float | The current time in seconds. |
173
+ | `u_frame` | int | The current frame number. |
174
+ | `u_resolution` | float[2] | The canvas element’s dimensions. |
175
+ | `u_cursor` | float[2] | Cursor position (x, y), normalized to [0,1] over the `cursorTarget` bounding box. |
176
+ | `u_click` | float[3] | Click position (x, y) and left click state (z). |
177
+ | `u_history` | sampler2DArray | Buffer texture of prior frames. Available if `history` option is set. |
178
+ | `u_historyFrameOffset` | int | Frame offset for accessing history texture. Available if `history` option is set. |
179
+
180
+ #### Included varyings
181
+
182
+ | Varying | Type | Description |
183
+ | ------- | -------- | ----------------------------------- |
184
+ | `v_uv` | float[2] | The UV coordinates of the fragment. |
185
+
186
+ ### Textures
187
+
188
+ #### `initializeTexture(name, source, options?)`
189
+
190
+ Initialize a texture from an image, video, canvas, or typed array.
191
+
192
+ ```typescript
193
+ shader.initializeTexture('u_texture', img);
194
+ shader.initializeTexture(
195
+ 'u_custom',
196
+ { data: new Float32Array(width * height * 4), width, height },
197
+ {
198
+ internalFormat: 'RGBA32F',
199
+ type: 'FLOAT',
200
+ minFilter: 'NEAREST',
201
+ magFilter: 'NEAREST',
202
+ },
203
+ );
204
+ shader.initializeTexture('u_webcam', videoElement, { history: 30 });
205
+ shader.initializeTexture('u_canvas', canvasElement, { preserveY: true });
206
+ ```
207
+
208
+ **Parameters:**
209
+
210
+ - `name` (string): The name of the texture uniform as declared in your shader
211
+ - `options` (optional): Texture options (see below)
212
+ - `source`: Image, video, canvas, or `{ data, width, height }`
213
+
214
+ **Texture Options:**
215
+
216
+ - `history?: number` - Number of previous frames to store (creates a `sampler2DArray`)
217
+ - `preserveY?: boolean` - For DOM sources only: if `true`, don't flip vertically (default: `false`, flips to match WebGL’s bottom-up convention)
218
+ - `internalFormat?: string` - Storage format in GPU memory (e.g., `'RGBA8'`, `'RGBA32F'`, `'R8'`). Defaults to `'RGBA8'` for 8-bit, or `'RGBA32F'` if `type` is `'FLOAT'`
219
+ - `format?: string` - Source data layout (default: `'RGBA'`). Describes the channels in your input data (e.g., `'RGBA'`, `'RGB'`, `'RED'`)
220
+ - `type?: string` - Source data type (default: `'UNSIGNED_BYTE'` for DOM sources, must be specified for typed arrays). Examples: `'UNSIGNED_BYTE'`, `'FLOAT'`, `'HALF_FLOAT'`
221
+ - `minFilter?: string` - Minification filter (default: `'LINEAR'`). Examples: `'LINEAR'`, `'NEAREST'`
222
+ - `magFilter?: string` - Magnification filter (default: `'LINEAR'`). Examples: `'LINEAR'`, `'NEAREST'`
223
+ - `wrapS?: string` - Wrap mode for S coordinate (default: `'CLAMP_TO_EDGE'`). Examples: `'CLAMP_TO_EDGE'`, `'REPEAT'`, `'MIRRORED_REPEAT'`
224
+ - `wrapT?: string` - Wrap mode for T coordinate (default: `'CLAMP_TO_EDGE'`). Examples: `'CLAMP_TO_EDGE'`, `'REPEAT'`, `'MIRRORED_REPEAT'`
225
+
226
+ **Note:** For typed array sources (`CustomTexture`), you must provide data in bottom-up orientation (WebGL convention). The `preserveY` option is ignored for typed arrays.
227
+
228
+ #### `updateTextures(updates)`
229
+
230
+ ```typescript
231
+ shader.updateTextures({ u_webcam: videoElement, u_overlay: overlayCanvas });
232
+ shader.updateTextures({
233
+ u_custom: { data: partialData, width, height, isPartial: true, x: offsetX, y: offsetY },
234
+ });
235
+ ```
236
+
237
+ **Parameters:**
238
+
239
+ - `updates`: Object mapping texture names to updated sources
240
+
241
+ ### Lifecycle methods
242
+
243
+ #### `play(onBeforeStep?)`
244
+
245
+ Start the render loop.
246
+
247
+ ```typescript
248
+ shader.play();
249
+
250
+ // With per-frame callbacks.
251
+ shader.play((_time, frame) => {
252
+ shader.updateTextures({ u_webcam: videoElement });
253
+ // Only save every 10th frame to history.
254
+ return { skipHistory: frame % 10 === 0 };
255
+ });
256
+ ```
257
+
258
+ **Parameters:**
259
+
260
+ - `onBeforeStep?`: `(time: number, frame: number) => StepOptions | void` - Called before each frame
261
+
262
+ #### `step(options?)`
263
+
264
+ Manually advance one frame. Updates `u_time` and `u_frame`, renders, then writes to history if `skipHistory` is `false` (default).
265
+
266
+ **Parameters:**
267
+
268
+ - `options?` (optional): `StepOptions` - Options to control rendering behavior (see below)
269
+
270
+ #### `draw(options?)`
271
+
272
+ Render without updating uniforms, frame counter, or history.
273
+
274
+ ```typescript
275
+ shader.draw({ skipClear: true });
276
+ ```
277
+
278
+ **Parameters:**
279
+
280
+ - `options?` (optional): `StepOptions` - Options to control rendering behavior (see below)
281
+
282
+ #### `clear()`
283
+
284
+ Clear ShaderPad’s current render target without advancing time, frame, or history.
285
+
286
+ #### `StepOptions`
287
+
288
+ ```typescript
289
+ interface StepOptions {
290
+ skipClear?: boolean; // Skip clearing canvas before rendering
291
+ skipHistory?: boolean; // Skip writing to history buffers
292
+ }
293
+ ```
294
+
295
+ **Options:**
296
+
297
+ - `skipClear?: boolean` - If `true`, the canvas is not cleared before rendering. Useful for accumulating effects or multi-pass rendering.
298
+ - `skipHistory?: boolean` - If `true`, history buffers are not updated. Useful when you want to render without affecting the history state.
299
+
300
+ #### `pause()`, `reset()`, `destroy()`
301
+
302
+ ```typescript
303
+ shader.pause(); // Pause the render loop.
304
+ shader.reset(); // Reset frame counter, clock, and clear history buffers.
305
+ shader.resetFrame(); // Reset frame counter and clock only.
306
+ shader.destroy(); // Clean up resources.
307
+ ```
308
+
309
+ ### Properties
310
+
311
+ - `gl` (WebGL2RenderingContext): The WebGL2 context used for rendering
312
+ - `canvas` (HTMLCanvasElement | OffscreenCanvas): The canvas element used for rendering
313
+
314
+ ### Type exports
315
+
316
+ `shaderpad` exports `Options`, `StepOptions`, `TextureOptions`, `InitializeTextureOptions`, `TextureSource`,
317
+ `UpdateTextureSource`, `CustomTexture`, `PartialCustomTexture`, `Plugin`, `PluginContext`, `ShaderPadEventName`, and
318
+ the GL literal string types. `shaderpad/util` exports `ToBlobOptions` and `SaveOptions`.
319
+
320
+ ### Event Listeners
321
+
322
+ #### `on(event, callback)`
323
+
324
+ Register a callback for a lifecycle event.
325
+
326
+ ```typescript
327
+ shader.on('updateResolution', (width, height) => {
328
+ console.log(`New resolution: ${width}x${height}`);
329
+ });
330
+ ```
331
+
332
+ **Parameters:**
333
+
334
+ - `event` (string): The event name
335
+ - `callback` (Function): The callback function
336
+
337
+ #### `off(event, callback)`
338
+
339
+ Remove a previously registered callback.
340
+
341
+ **Parameters:**
342
+
343
+ - `event` (string): The event name
344
+ - `callback` (Function): The callback function to remove
345
+
346
+ #### Available Events
347
+
348
+ | Event | Callback Arguments | Description |
349
+ | ------------------- | ------------------------------------------------------ | ------------------------------------------------ |
350
+ | `updateResolution` | `(width: number, height: number)` | Fired when the drawing buffer resolution changes |
351
+ | `play` | none | Fired when the render loop starts |
352
+ | `pause` | none | Fired when the render loop is paused |
353
+ | `reset` | none | Fired when the shader is reset |
354
+ | `destroy` | none | Fired when the shader is destroyed |
355
+ | `beforeStep` | `(time: number, frame: number, options?: StepOptions)` | Fired before each render step |
356
+ | `afterStep` | `(time: number, frame: number, options?: StepOptions)` | Fired after each render step |
357
+ | `beforeDraw` | `(options?: StepOptions)` | Fired before each draw call |
358
+ | `afterDraw` | `(options?: StepOptions)` | Fired after each draw call |
359
+ | `initializeTexture` | `(name, source, options?)` | Fired after a texture is initialized |
360
+ | `initializeUniform` | `(name, type, value, options?)` | Fired after a uniform is initialized |
361
+ | `updateTextures` | `(updates)` | Fired after textures are updated |
362
+ | `updateUniforms` | `(updates, options?)` | Fired after uniforms are updated |
363
+
364
+ Plugins may emit additional namespaced events (e.g., `face:ready`, `pose:result`).
365
+
366
+ #### Plugin authoring
367
+
368
+ ShaderPad plugins are plain functions passed in `plugins: [...]`. Each plugin receives the `ShaderPad` instance and a
369
+ small `PluginContext` during construction, before the fragment shader is compiled. In practice, that means a plugin can
370
+ inject GLSL, listen to lifecycle events with `shader.on(...)`, emit its own namespaced events, and publish plugin-owned
371
+ textures with `updateTexture(...)`. If a plugin needs the backing canvas or raw WebGL access, use `shader.canvas` and
372
+ `shader.gl`.
373
+
374
+ The most useful lifecycle hooks are usually `_init` for setup, `beforeStep` / `beforeDraw` for per-frame work, and
375
+ `destroy` for cleanup. Plugin order is stable: plugins install in `plugins[]` order, handlers run in registration
376
+ order, and GLSL injections preserve plugin order. If a plugin touches shared GL state directly, it should restore that
377
+ state before returning.
378
+
379
+ Start with the
380
+ [autosize source](./packages/shaderpad/src/plugins/autosize.ts) if you
381
+ want a compact example that listens to lifecycle events, emits a custom event, and cleans up after itself. For more
382
+ examples, browse the
383
+ [plugins source folder](./packages/shaderpad/src/plugins) or read the
384
+ [plugin guide](https://misery.co/shaderpad/docs/api/plugins/).
385
+
386
+ ## Options
387
+
388
+ ShaderPad’s constructor accepts an optional `options` object.
389
+
390
+ In addition to the fields below, constructor options also accept texture storage/filter/wrap settings such as
391
+ `internalFormat`, `format`, `type`, `minFilter`, `magFilter`, `wrapS`, and `wrapT`. These configure ShaderPad’s
392
+ internal render targets and history buffers, which is mainly useful for float/integer pipelines and chained shaders.
393
+
394
+ ### canvas
395
+
396
+ The `canvas` option allows you to pass in an existing canvas element, dimensions object, or `null` for headless mode.
397
+
398
+ ```typescript
399
+ const canvas = document.createElement('canvas');
400
+ const shader = new ShaderPad(fragmentShaderSrc, { canvas });
401
+
402
+ // Use utilities for a fullscreen canvas.
403
+ import autosize from 'shaderpad/plugins/autosize';
404
+ import { createFullscreenCanvas } from 'shaderpad/util';
405
+ const shader = new ShaderPad(fragmentShaderSrc, { canvas: createFullscreenCanvas(), plugins: [autosize()] });
406
+
407
+ // Create a headless ShaderPad for intermediate processing with initial dimensions.
408
+ const shader = new ShaderPad(fragmentShaderSrc, { canvas: { width: 640, height: 480 } });
409
+ ```
410
+
411
+ ### cursorTarget
412
+
413
+ Element (or `window`) to listen for cursor/click events; coordinates are normalized to [0,1] over its bounding box. If omitted and the canvas is an HTML canvas, the canvas is used.
414
+
415
+ ```typescript
416
+ // Listen on the window (e.g. full viewport, coordinates 0–1).
417
+ const shader = new ShaderPad(fragmentShaderSrc, { cursorTarget: window });
418
+
419
+ // Listen on a specific container element.
420
+ const container = document.querySelector('.shader-container');
421
+ const shader = new ShaderPad(fragmentShaderSrc, { cursorTarget: container });
422
+ ```
423
+
424
+ ### history
425
+
426
+ Enable framebuffer history to access previous frames.
427
+
428
+ ```typescript
429
+ // Store the last 10 frames of shader output.
430
+ const shader = new ShaderPad(fragmentShaderSrc, { history: 10 });
431
+
432
+ // In your shader, access history using the u_history uniform:
433
+ // uniform highp sampler2DArray u_history;
434
+ // It’s stored as a ringbuffer, so you can access specific frames with the provided offset uniform:
435
+ // uniform int u_historyFrameOffset;
436
+ ```
437
+
438
+ **High-precision history:** By default, history textures use 8-bit precision (RGBA8). For high-precision rendering, specify `internalFormat` and `type` options. This enables FBO rendering and preserves precision in history textures.
439
+
440
+ ```typescript
441
+ // For 32-bit float precision (requires EXT_color_buffer_float extension):
442
+ const shader = new ShaderPad(fragmentShaderSrc, {
443
+ history: 60,
444
+ internalFormat: 'RGBA32F',
445
+ type: 'FLOAT',
446
+ });
447
+ ```
448
+
449
+ You can also enable history for individual textures:
450
+
451
+ ```typescript
452
+ // Store the last 30 webcam frames.
453
+ shader.initializeTexture('u_webcam', videoElement, { history: 30 });
454
+ // In your shader, access history using the u_webcam and u_webcamFrameOffset uniforms:
455
+ // uniform highp sampler2DArray u_webcam;
456
+ // uniform int u_webcamFrameOffset;
457
+ ```
458
+
459
+ It’s recommended to use the `helpers` plugin to simplify access to history textures:
460
+
461
+ ```glsl
462
+ int nFramesAgo = 2; // Get the color 2 frames ago.
463
+ float zIndex = historyZ(u_webcam, u_webcamFrameOffset, nFramesAgo);
464
+ vec4 historyColor = texture(u_webcam, vec3(v_uv, zIndex));
465
+ ```
466
+
467
+ ### plugins
468
+
469
+ ShaderPad adds additional functionality through plugins, which keeps bundle sizes small.
470
+
471
+ #### helpers
472
+
473
+ The `helpers` plugin provides convenience functions and constants. The individual GLSL snippets live in [`src/helpers`](./packages/shaderpad/src/helpers), and the plugin injects the combined set.
474
+
475
+ ```typescript
476
+ import helpers from 'shaderpad/plugins/helpers';
477
+ const shader = new ShaderPad(fragmentShaderSrc, { plugins: [helpers()] });
478
+ ```
479
+
480
+ **Note:** The `helpers` plugin automatically injects the `u_resolution` uniform into your shader. Do not declare it yourself.
481
+
482
+ If you already have a GLSL-aware bundler pipeline such as `vite-plugin-glsl`, you can also import the raw helper sources individually:
483
+
484
+ ```typescript
485
+ import fitCoverGLSL from 'shaderpad/helpers/fitCover.glsl';
486
+ import fitCoverInverseGLSL from 'shaderpad/helpers/fitCoverInverse.glsl';
487
+ ```
488
+
489
+ If you want the whole helper set injected for you, use the `helpers()` plugin. The raw GLSL exports are for pulling in specific helpers yourself.
490
+
491
+ If you import `fitContain.glsl`, `fitContainInverse.glsl`, `fitCover.glsl`, or `fitCoverInverse.glsl` directly, you must declare `u_resolution` in your shader:
492
+
493
+ ```glsl
494
+ uniform vec2 u_resolution;
495
+ #include "/shaderpad/helpers/fitContain.glsl"
496
+ ```
497
+
498
+ For `vite-plugin-glsl`, that leading `/` matches the common `root: '/node_modules/'` setup used in the ShaderPad starters.
499
+
500
+ #### save / toBlob
501
+
502
+ The `shaderpad/util` entrypoint exposes `toBlob(shader, options?)` for custom capture flows and
503
+ `save(shader, filename?, text?, options?)` for download/share behavior. Both render the current frame before
504
+ serializing it.
505
+
506
+ ```typescript
507
+ import { save, toBlob } from 'shaderpad/util';
508
+
509
+ await save(shader, 'filename', 'Optional mobile share text', { preventShare: true });
510
+ const blob = await toBlob(shader);
511
+ ```
512
+
513
+ **`save()` parameters:**
514
+
515
+ - `filename?` (string) - Output filename. Defaults to `export.png`
516
+ - `text?` (string) - Optional share text for platforms that support the Web Share API
517
+ - `options.preventShare?: boolean` - If `true`, the Web Share API is not used and the file is always downloaded. Default: `false`.
518
+
519
+ #### face
520
+
521
+ The `face` plugin uses [MediaPipe](https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker) to detect faces in video or image textures.
522
+
523
+ ```typescript
524
+ import face from 'shaderpad/plugins/face';
525
+ const shader = new ShaderPad(fragmentShaderSrc, {
526
+ plugins: [face({ textureName: 'u_webcam', options: { maxFaces: 3 } })],
527
+ });
528
+ ```
529
+
530
+ **Options:**
531
+
532
+ - `maxFaces?: number` - Maximum faces to detect (default: 1)
533
+ - `history?: number` - Frames of history to store for landmarks and mask textures
534
+
535
+ **Events:**
536
+
537
+ | Event | Callback Arguments | Description |
538
+ | ------------- | -------------------------------- | ---------------------------------------- |
539
+ | `face:ready` | none | Fired when the detection model is loaded |
540
+ | `face:result` | `(result: FaceLandmarkerResult)` | Fired with detection results each frame |
541
+
542
+ **Uniforms:**
543
+
544
+ | Uniform | Type | Description |
545
+ | -------------------- | ----------------------------- | ---------------------------------------------------------------------------- |
546
+ | `u_maxFaces` | int | Maximum number of faces to detect |
547
+ | `u_nFaces` | int | Current number of detected faces |
548
+ | `u_faceLandmarksTex` | sampler2D (or sampler2DArray) | Raw landmark data texture (use `faceLandmark()` to access) |
549
+ | `u_faceMask` | sampler2D (or sampler2DArray) | Face mask texture (R: region type, G: face region, B: normalized face index) |
550
+
551
+ **Helper functions:**
552
+
553
+ All region functions return `vec2(confidence, faceIndex)`. faceIndex is 0-indexed (-1 = no face). When `history` is enabled, all functions accept an optional `int framesAgo` parameter.
554
+
555
+ - `faceLandmark(int faceIndex, int landmarkIndex) -> vec4` - Returns landmark data as `vec4(x, y, z, visibility)`. Use `vec2(faceLandmark(...))` to get just the screen position.
556
+ - `leftEyebrowAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in left eyebrow, `vec2(0.0, -1.0)` otherwise.
557
+ - `rightEyebrowAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in right eyebrow, `vec2(0.0, -1.0)` otherwise.
558
+ - `leftEyeAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in left eye, `vec2(0.0, -1.0)` otherwise.
559
+ - `rightEyeAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in right eye, `vec2(0.0, -1.0)` otherwise.
560
+ - `lipsAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in lips, `vec2(0.0, -1.0)` otherwise.
561
+ - `mouthAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in full mouth area (lips + inner mouth), `vec2(0.0, -1.0)` otherwise.
562
+ - `innerMouthAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in inner mouth region, `vec2(0.0, -1.0)` otherwise.
563
+ - `faceOvalAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in face oval contour, `vec2(0.0, -1.0)` otherwise.
564
+ - `faceAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in the full face area, `vec2(0.0, -1.0)` otherwise.
565
+ - `eyeAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in either eye, `vec2(0.0, -1.0)` otherwise.
566
+ - `eyebrowAt(vec2 pos) -> vec2` - Returns `vec2(1.0, faceIndex)` if position is in either eyebrow, `vec2(0.0, -1.0)` otherwise.
567
+
568
+ **Convenience functions** (return confidence 0-1 if found, `0.0` otherwise):
569
+
570
+ - `inFace(vec2 pos) -> float` - Returns confidence (0-1) if position is in the full face area, `0.0` otherwise.
571
+ - `inEye(vec2 pos) -> float` - Returns confidence (0-1) if position is in either eye, `0.0` otherwise.
572
+ - `inEyebrow(vec2 pos) -> float` - Returns confidence (0-1) if position is in either eyebrow, `0.0` otherwise.
573
+ - `inMouth(vec2 pos) -> float` - Returns confidence (0-1) if position is in full mouth area (lips + inner mouth), `0.0` otherwise.
574
+ - `inInnerMouth(vec2 pos) -> float` - Returns confidence (0-1) if position is in inner mouth, `0.0` otherwise.
575
+ - `inLips(vec2 pos) -> float` - Returns confidence (0-1) if position is in lips, `0.0` otherwise.
576
+
577
+ **Landmark Constants:**
578
+
579
+ - `FACE_LANDMARK_L_EYE_CENTER` - Left eye center landmark index
580
+ - `FACE_LANDMARK_R_EYE_CENTER` - Right eye center landmark index
581
+ - `FACE_LANDMARK_NOSE_TIP` - Nose tip landmark index
582
+ - `FACE_LANDMARK_FACE_CENTER` - Face center landmark index (custom, calculated from all landmarks)
583
+ - `FACE_LANDMARK_MOUTH_CENTER` - Mouth center landmark index (custom, calculated from inner mouth landmarks)
584
+
585
+ **Example usage:**
586
+
587
+ ```glsl
588
+ // Get a specific landmark position.
589
+ vec2 nosePos = vec2(faceLandmark(0, FACE_LANDMARK_NOSE_TIP));
590
+
591
+ // Use in* convenience functions for simple confidence checks.
592
+ float eyeMask = inEye(v_uv);
593
+
594
+ // Use faceLandmark or *At functions when you need to check a specific face index.
595
+ vec2 leftEye = leftEyeAt(v_uv);
596
+ for (int i = 0; i < u_nFaces; ++i) {
597
+ vec4 leftEyeCenter = faceLandmark(i, FACE_LANDMARK_L_EYE_CENTER);
598
+ vec4 rightEyeCenter = faceLandmark(i, FACE_LANDMARK_R_EYE_CENTER);
599
+ if (leftEye.x > 0.0 && int(leftEye.y) == i) {
600
+ // Position is inside the left eye of face i.
601
+ }
602
+ // ...
603
+ }
604
+ ```
605
+
606
+ [Landmark indices are documented here.](https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker#face_landmarker_model) This library adds two custom landmarks: `FACE_CENTER` and `MOUTH_CENTER`. This brings the total landmark count to 480.
607
+
608
+ **Note:** Install `@mediapipe/tasks-vision` when using the face plugin.
609
+
610
+ **Note:** Confidence values are currently limited to 0.0 or 1.0.
611
+
612
+ #### pose
613
+
614
+ The `pose` plugin uses [MediaPipe Pose Landmarker](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker) to expose a flat array of 2D landmarks. Each pose contributes 39 landmarks (33 standard + 6 custom), enumerated below.
615
+
616
+ ```typescript
617
+ import pose from 'shaderpad/plugins/pose';
618
+ const shader = new ShaderPad(fragmentShaderSrc, {
619
+ plugins: [pose({ textureName: 'u_video', options: { maxPoses: 3 } })],
620
+ });
621
+ ```
622
+
623
+ **Options:**
624
+
625
+ - `maxPoses?: number` - Maximum poses to detect (default: 1)
626
+ - `history?: number` - Frames of history to store for landmarks and mask textures
627
+
628
+ **Note:** Install `@mediapipe/tasks-vision` when using the pose plugin.
629
+
630
+ **Events:**
631
+
632
+ | Event | Callback Arguments | Description |
633
+ | ------------- | -------------------------------- | ---------------------------------------- |
634
+ | `pose:ready` | none | Fired when the detection model is loaded |
635
+ | `pose:result` | `(result: PoseLandmarkerResult)` | Fired with detection results each frame |
636
+
637
+ **Uniforms:**
638
+
639
+ | Uniform | Type | Description |
640
+ | -------------------- | ----------------------------- | ----------------------------------------------------------------------------- |
641
+ | `u_maxPoses` | int | Maximum number of poses to track |
642
+ | `u_nPoses` | int | Current number of detected poses |
643
+ | `u_poseLandmarksTex` | sampler2D (or sampler2DArray) | Raw landmark data texture (RGBA: x, y, z, visibility) |
644
+ | `u_poseMask` | sampler2D (or sampler2DArray) | Pose mask texture (R: body detected, G: confidence, B: normalized pose index) |
645
+
646
+ **Helper functions:**
647
+
648
+ When `history` is enabled, all functions accept an optional `int framesAgo` parameter.
649
+
650
+ - `poseLandmark(int poseIndex, int landmarkIndex) -> vec4` - Returns landmark data as `vec4(x, y, z, visibility)`. Use `vec2(poseLandmark(...))` to get just the screen position.
651
+ - `poseAt(vec2 pos) -> vec2` - Returns `vec2(confidence, poseIndex)`. poseIndex is 0-indexed (-1 = no pose), confidence is the segmentation confidence.
652
+ - `inPose(vec2 pos) -> float` - Returns confidence (0-1) if position is in any pose, `0.0` otherwise.
653
+
654
+ **Constants:**
655
+
656
+ - `POSE_LANDMARK_LEFT_EYE` - Left eye landmark index (2)
657
+ - `POSE_LANDMARK_RIGHT_EYE` - Right eye landmark index (5)
658
+ - `POSE_LANDMARK_LEFT_SHOULDER` - Left shoulder landmark index (11)
659
+ - `POSE_LANDMARK_RIGHT_SHOULDER` - Right shoulder landmark index (12)
660
+ - `POSE_LANDMARK_LEFT_ELBOW` - Left elbow landmark index (13)
661
+ - `POSE_LANDMARK_RIGHT_ELBOW` - Right elbow landmark index (14)
662
+ - `POSE_LANDMARK_LEFT_HIP` - Left hip landmark index (23)
663
+ - `POSE_LANDMARK_RIGHT_HIP` - Right hip landmark index (24)
664
+ - `POSE_LANDMARK_LEFT_KNEE` - Left knee landmark index (25)
665
+ - `POSE_LANDMARK_RIGHT_KNEE` - Right knee landmark index (26)
666
+ - `POSE_LANDMARK_BODY_CENTER` - Body center landmark index (33, custom, calculated from all landmarks)
667
+ - `POSE_LANDMARK_LEFT_HAND_CENTER` - Left hand center landmark index (34, custom, calculated from pinky, thumb, wrist, index)
668
+ - `POSE_LANDMARK_RIGHT_HAND_CENTER` - Right hand center landmark index (35, custom, calculated from pinky, thumb, wrist, index)
669
+ - `POSE_LANDMARK_LEFT_FOOT_CENTER` - Left foot center landmark index (36, custom, calculated from ankle, heel, foot index)
670
+ - `POSE_LANDMARK_RIGHT_FOOT_CENTER` - Right foot center landmark index (37, custom, calculated from ankle, heel, foot index)
671
+ - `POSE_LANDMARK_TORSO_CENTER` - Torso center landmark index (38, custom, calculated from shoulders and hips)
672
+
673
+ **Note:** For connecting pose landmarks (e.g., drawing skeleton lines), `PoseLandmarker.POSE_CONNECTIONS` from `@mediapipe/tasks-vision` provides an array of `{ start, end }` pairs that define which landmarks should be connected.
674
+
675
+ Use `poseLandmark(int poseIndex, int landmarkIndex)` in GLSL to retrieve a specific point. Landmark indices are:
676
+
677
+ | Index | Landmark | Index | Landmark |
678
+ | ----- | ----------------- | ----- | -------------------------- |
679
+ | 0 | nose | 20 | right index |
680
+ | 1 | left eye (inner) | 21 | left thumb |
681
+ | 2 | left eye | 22 | right thumb |
682
+ | 3 | left eye (outer) | 23 | left hip |
683
+ | 4 | right eye (inner) | 24 | right hip |
684
+ | 5 | right eye | 25 | left knee |
685
+ | 6 | right eye (outer) | 26 | right knee |
686
+ | 7 | left ear | 27 | left ankle |
687
+ | 8 | right ear | 28 | right ankle |
688
+ | 9 | mouth (left) | 29 | left heel |
689
+ | 10 | mouth (right) | 30 | right heel |
690
+ | 11 | left shoulder | 31 | left foot index |
691
+ | 12 | right shoulder | 32 | right foot index |
692
+ | 13 | left elbow | 33 | body center (custom) |
693
+ | 14 | right elbow | 34 | left hand center (custom) |
694
+ | 15 | left wrist | 35 | right hand center (custom) |
695
+ | 16 | right wrist | 36 | left foot center (custom) |
696
+ | 17 | left pinky | 37 | right foot center (custom) |
697
+ | 18 | right pinky | 38 | torso center (custom) |
698
+ | 19 | left index | | |
699
+
700
+ [Source](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker#pose_landmarker_model)
701
+
702
+ [Landmark indices are documented here.](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker#pose_landmarker_model) This library adds six custom landmarks: `BODY_CENTER`, `LEFT_HAND_CENTER`, `RIGHT_HAND_CENTER`, `LEFT_FOOT_CENTER`, `RIGHT_FOOT_CENTER`, and `TORSO_CENTER`. This brings the total landmark count to 39.
703
+
704
+ A minimal fragment shader loop looks like:
705
+
706
+ ```glsl
707
+ for (int i = 0; i < u_maxPoses; ++i) {
708
+ if (i >= u_nPoses) break;
709
+ vec2 leftHip = vec2(poseLandmark(i, POSE_LANDMARK_LEFT_HIP));
710
+ vec2 rightHip = vec2(poseLandmark(i, POSE_LANDMARK_RIGHT_HIP));
711
+ // …
712
+ }
713
+ ```
714
+
715
+ #### hands
716
+
717
+ The `hands` plugin uses [MediaPipe Hand Landmarker](https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker) to expose a flat array of 2D landmarks. Each hand contributes 22 landmarks, enumerated below.
718
+
719
+ ```typescript
720
+ import hands from 'shaderpad/plugins/hands';
721
+ const shader = new ShaderPad(fragmentShaderSrc, {
722
+ plugins: [hands({ textureName: 'u_video', options: { maxHands: 2 } })],
723
+ });
724
+ ```
725
+
726
+ **Options:**
727
+
728
+ - `maxHands?: number` - Maximum hands to detect (default: 2)
729
+ - `history?: number` - Frames of history to store for landmarks texture
730
+
731
+ **Events:**
732
+
733
+ | Event | Callback Arguments | Description |
734
+ | -------------- | -------------------------------- | ---------------------------------------- |
735
+ | `hands:ready` | none | Fired when the detection model is loaded |
736
+ | `hands:result` | `(result: HandLandmarkerResult)` | Fired with detection results each frame |
737
+
738
+ **Uniforms:**
739
+
740
+ | Uniform | Type | Description |
741
+ | -------------------- | ----------------------------- | ----------------------------------------------------- |
742
+ | `u_maxHands` | int | Maximum number of hands to track |
743
+ | `u_nHands` | int | Current number of detected hands |
744
+ | `u_handLandmarksTex` | sampler2D (or sampler2DArray) | Raw landmark data texture (RGBA: x, y, z, handedness) |
745
+
746
+ **Helper functions:**
747
+
748
+ When `history` is enabled, all functions accept an optional `int framesAgo` parameter.
749
+
750
+ - `handLandmark(int handIndex, int landmarkIndex) -> vec4` - Returns landmark data as `vec4(x, y, z, handedness)`. Use `vec2(handLandmark(...))` to get just the screen position. Handedness: 0.0 = left hand, 1.0 = right hand.
751
+ - `isRightHand(int handIndex) -> float` - Returns 1.0 if the hand is a right hand, 0.0 if left.
752
+ - `isLeftHand(int handIndex) -> float` - Returns 1.0 if the hand is a left hand, 0.0 if right.
753
+
754
+ **Landmark Indices:**
755
+
756
+ | Index | Landmark | Index | Landmark |
757
+ | ----- | ----------------- | ----- | ----------------- |
758
+ | 0 | WRIST | 11 | MIDDLE_FINGER_DIP |
759
+ | 1 | THUMB_CMC | 12 | MIDDLE_FINGER_TIP |
760
+ | 2 | THUMB_MCP | 13 | RING_FINGER_MCP |
761
+ | 3 | THUMB_IP | 14 | RING_FINGER_PIP |
762
+ | 4 | THUMB_TIP | 15 | RING_FINGER_DIP |
763
+ | 5 | INDEX_FINGER_MCP | 16 | RING_FINGER_TIP |
764
+ | 6 | INDEX_FINGER_PIP | 17 | PINKY_MCP |
765
+ | 7 | INDEX_FINGER_DIP | 18 | PINKY_PIP |
766
+ | 8 | INDEX_FINGER_TIP | 19 | PINKY_DIP |
767
+ | 9 | MIDDLE_FINGER_MCP | 20 | PINKY_TIP |
768
+ | 10 | MIDDLE_FINGER_PIP | 21 | HAND_CENTER |
769
+
770
+ [Source](https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker#models)
771
+
772
+ A minimal fragment shader loop looks like:
773
+
774
+ ```glsl
775
+ #define WRIST 0
776
+ #define THUMB_TIP 4
777
+ #define INDEX_TIP 8
778
+ #define HAND_CENTER 21
779
+
780
+ for (int i = 0; i < u_maxHands; ++i) {
781
+ if (i >= u_nHands) break;
782
+ vec2 wrist = vec2(handLandmark(i, WRIST));
783
+ vec2 thumbTip = vec2(handLandmark(i, THUMB_TIP));
784
+ vec2 indexTip = vec2(handLandmark(i, INDEX_TIP));
785
+ vec2 handCenter = vec2(handLandmark(i, HAND_CENTER));
786
+
787
+ // Use handedness for coloring (0.0 = left/black, 1.0 = right/white).
788
+ vec3 handColor = vec3(isRightHand(i));
789
+ // …
790
+ }
791
+ ```
792
+
793
+ **Note:** Install `@mediapipe/tasks-vision` when using the hands plugin.
794
+
795
+ #### segmenter
796
+
797
+ The `segmenter` plugin uses [MediaPipe Image Segmenter](https://ai.google.dev/edge/mediapipe/solutions/vision/image_segmenter) to segment objects in video or image textures. It supports models with multiple categories (e.g., background, hair, chair, dog…). By default, it uses MediaPipe’s selfie segmenter model.
798
+
799
+ ```typescript
800
+ import ShaderPad from 'shaderpad';
801
+ import segmenter from 'shaderpad/plugins/segmenter';
802
+
803
+ const shader = new ShaderPad(fragmentShaderSrc, {
804
+ plugins: [
805
+ segmenter({
806
+ textureName: 'u_webcam',
807
+ options: {
808
+ modelPath:
809
+ 'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_multiclass_256x256/float32/latest/selfie_multiclass_256x256.tflite',
810
+ },
811
+ }),
812
+ ],
813
+ });
814
+ ```
815
+
816
+ **Options:**
817
+
818
+ - `modelPath?: string` - Path to the segmentation model (default: MediaPipe selfie segmenter)
819
+ - `outputConfidenceMasks?: boolean` - Whether to output per-category confidence masks (default: `false`). If `false`, confidence is always 1.
820
+ - `history?: number` - Frames of history to store for mask texture
821
+
822
+ **Events:**
823
+
824
+ | Event | Callback Arguments | Description |
825
+ | ------------------ | -------------------------------- | ------------------------------------------- |
826
+ | `segmenter:ready` | none | Fired when the segmentation model is loaded |
827
+ | `segmenter:result` | `(result: ImageSegmenterResult)` | Fired with segmentation results each frame |
828
+
829
+ **Uniforms:**
830
+
831
+ | Uniform | Type | Description |
832
+ | ----------------- | ----------------------------- | ----------------------------------------------------------------------- |
833
+ | `u_segmentMask` | sampler2D (or sampler2DArray) | Segment mask texture (R: confidence, G: normalized category, B: unused) |
834
+ | `u_numCategories` | int | Number of segmentation categories (including background) |
835
+
836
+ **Helper functions:**
837
+
838
+ When `history` is enabled, all functions accept an optional `int framesAgo` parameter.
839
+
840
+ - `segmentAt(vec2 pos) -> vec2` - Returns `vec2(confidence, category)`. confidence is the segmentation confidence (0–1, or 1 if `outputConfidenceMasks` is false). category is the normalized category index (0–1).
841
+
842
+ **Example usage:**
843
+
844
+ ```glsl
845
+ vec2 segment = segmentAt(v_uv);
846
+ float confidence = segment.x;
847
+ float category = segment.y;
848
+ float isForeground = (1.0 - step(category, 0.0)) * confidence;
849
+ color = mix(color, vec3(1.0, 0.0, 1.0), isForeground);
850
+ ```
851
+
852
+ **Note:** Install `@mediapipe/tasks-vision` when using the segmenter plugin.
853
+
854
+ #### autosize
855
+
856
+ The `autosize` plugin handles automatic canvas resolution updates with ResizeObserver.
857
+
858
+ **Options:**
859
+
860
+ - `scale?: number` - Resolution multiplier relative to CSS pixels (default: current `devicePixelRatio`; use `1` to match CSS pixel size)
861
+ - `target?: Element | Window` - What to observe for resize (default: canvas itself for HTMLCanvasElement, window for OffscreenCanvas)
862
+ - `throttle?: number` - Throttle interval in milliseconds (default: 33ms)
863
+
864
+ **Events:**
865
+
866
+ | Event | Callback Arguments | Description |
867
+ | ----------------- | --------------------------------- | ---------------------------------------------------- |
868
+ | `autosize:resize` | `(width: number, height: number)` | Fired when the canvas drawing buffer size is updated |
869
+
870
+ ## Contributing
871
+
872
+ ### Running an example
873
+
874
+ ```bash
875
+ git clone https://github.com/miseryco/shaderpad.git
876
+ cd shaderpad
877
+ npm install
878
+ npm run docs:dev
879
+ ```
880
+
881
+ ### Adding an example
882
+
883
+ Add a `.ts` file in `docs/src/examples/demos/` that exports `init` and `destroy` functions. Then register it in [`docs/src/examples/registry-data.mjs`](./docs/src/examples/registry-data.mjs) and [`docs/src/examples/registry.ts`](./docs/src/examples/registry.ts).
884
+
885
+ ## License
886
+
887
+ This project is licensed under [MIT](./LICENSE).