shaderpad 1.0.0-beta.17 → 1.0.0-beta.19

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.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
- # ShaderPad
1
+ ![ShaderPad logo](./ShaderPad.png)
2
2
 
3
- ShaderPad is a lightweight, dependency-free library to reduce boilerplate when writing fragment shaders. It provides a simple interface to initialize shaders, manage uniforms, maintain an animation loop, and handle user interactions such as mouse movements and window resizing.
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.
4
6
 
5
7
  ## Installation
6
8
 
@@ -21,7 +23,7 @@ precision highp float;
21
23
  in vec2 v_uv;
22
24
  uniform float u_time;
23
25
  uniform vec2 u_resolution;
24
- uniform vec4 u_cursor; // [cursorX, cursorY, scrollX, scrollY]
26
+ uniform vec2 u_cursor;
25
27
 
26
28
  // Custom variables.
27
29
  uniform vec3 u_cursorColor;
@@ -34,7 +36,7 @@ void main() {
34
36
  float dotDist = length(dotGrid);
35
37
  float dot = step(dotDist, 5.);
36
38
 
37
- float cursorDist = distance(uv, u_cursor.xy * u_resolution);
39
+ float cursorDist = distance(uv, u_cursor * u_resolution);
38
40
  float cursor = step(cursorDist, 25. + sin(u_time * 5.) * 5.);
39
41
 
40
42
  vec3 color = mix(vec3(0., 0., 1.), vec3(1.), dot);
@@ -71,6 +73,173 @@ shader.play(time => {
71
73
 
72
74
  See the [`examples/` directory](./examples/) for more.
73
75
 
76
+ ## Usage
77
+
78
+ ### Uniforms
79
+
80
+ #### `initializeUniform(name, type, value, options?)`
81
+
82
+ Initialize a uniform variable. The uniform must be declared in your fragment shader.
83
+
84
+ ```typescript
85
+ // Initialize a float uniform.
86
+ shader.initializeUniform('u_speed', 'float', 1.5);
87
+
88
+ // Initialize a vec3 uniform.
89
+ shader.initializeUniform('u_color', 'float', [1.0, 0.5, 0.0]);
90
+ ```
91
+
92
+ **Parameters:**
93
+
94
+ - `name` (string): The name of the uniform as declared in your shader
95
+ - `type` ('float' | 'int'): The uniform type
96
+ - `value` (number | number[] | (number | number[])[]): Initial value(s)
97
+ - `options` (optional): `{ arrayLength?: number }` - Required for uniform arrays
98
+
99
+ #### `updateUniforms(updates, options?)`
100
+
101
+ Update one or more uniform values.
102
+
103
+ ```typescript
104
+ shader.updateUniforms({
105
+ u_speed: 2.0,
106
+ u_color: [1.0, 0.0, 0.0],
107
+ });
108
+ ```
109
+
110
+ **Parameters:**
111
+
112
+ - `updates` (Record<string, number | number[] | (number | number[])[]>): Object mapping uniform names to their new values
113
+ - `options` (optional): `{ startIndex?: number }` - Starting index for partial array updates
114
+
115
+ #### Uniform arrays
116
+
117
+ ShaderPad supports uniform arrays. Initialize them by passing an `arrayLength` option:
118
+
119
+ ```typescript
120
+ // Initialize a vec2 array with 5 elements.
121
+ shader.initializeUniform(
122
+ 'u_positions',
123
+ 'float',
124
+ [
125
+ [0, 0],
126
+ [1, 1],
127
+ [2, 2],
128
+ [3, 3],
129
+ [4, 4],
130
+ ],
131
+ {
132
+ arrayLength: 5,
133
+ }
134
+ );
135
+
136
+ // Update all elements.
137
+ shader.updateUniforms({
138
+ u_positions: [
139
+ [0.1, 0.2],
140
+ [1.1, 1.2],
141
+ [2.1, 2.2],
142
+ [3.1, 3.2],
143
+ [4.1, 4.2],
144
+ ],
145
+ });
146
+
147
+ // Update a subset starting at index 2.
148
+ shader.updateUniforms(
149
+ {
150
+ u_positions: [
151
+ [2.5, 2.5],
152
+ [3.5, 3.5],
153
+ ],
154
+ },
155
+ { startIndex: 2 }
156
+ );
157
+ ```
158
+
159
+ #### Included uniforms
160
+
161
+ | Uniform | Type | Description |
162
+ | ---------------------- | -------------- | --------------------------------------------------------------------------------- |
163
+ | `u_time` | float | The current time in seconds. |
164
+ | `u_frame` | int | The current frame number. |
165
+ | `u_resolution` | float[2] | The canvas element's dimensions. |
166
+ | `u_cursor` | float[2] | Cursor position (x, y). |
167
+ | `u_click` | float[3] | Click position (x, y) and left click state (z). |
168
+ | `u_history` | sampler2DArray | Buffer texture of prior frames. Available if `history` option is set. |
169
+ | `u_historyFrameOffset` | int | Frame offset for accessing history texture. Available if `history` option is set. |
170
+
171
+ #### Included varyings
172
+
173
+ | Varying | Type | Description |
174
+ | ------- | -------- | ----------------------------------- |
175
+ | `v_uv` | float[2] | The UV coordinates of the fragment. |
176
+
177
+ ### Textures
178
+
179
+ #### `initializeTexture(name, source, options?)`
180
+
181
+ Initialize a texture from an image, video, or canvas element.
182
+
183
+ ```typescript
184
+ // Initialize a texture from an image.
185
+ const img = new Image();
186
+ img.src = 'texture.png';
187
+ img.onload = () => {
188
+ shader.initializeTexture('u_texture', img);
189
+ };
190
+
191
+ // Initialize a texture with history (stores previous frames).
192
+ shader.initializeTexture('u_webcam', videoElement, { history: 30 });
193
+ ```
194
+
195
+ **Parameters:**
196
+
197
+ - `name` (string): The name of the texture uniform as declared in your shader
198
+ - `source` (HTMLImageElement | HTMLVideoElement | HTMLCanvasElement): The texture source
199
+ - `options` (optional): `{ history?: number }` - Number of previous frames to store
200
+
201
+ #### `updateTextures(updates)`
202
+
203
+ Update one or more textures. Useful for updating video textures each frame.
204
+
205
+ ```typescript
206
+ shader.updateTextures({
207
+ u_webcam: videoElement,
208
+ u_overlay: overlayCanvas,
209
+ });
210
+ ```
211
+
212
+ **Parameters:**
213
+
214
+ - `updates` (Record<string, TextureSource>): Object mapping texture names to their new sources
215
+
216
+ ### Lifecycle methods
217
+
218
+ #### `play(callback?)`
219
+
220
+ Start the render loop. The callback is invoked each frame with the current time and frame number.
221
+
222
+ ```typescript
223
+ shader.play();
224
+ // Can optionally take a callback to invoke each frame.
225
+ shader.play((time, frame) => {
226
+ shader.updateTextures({ u_webcam: videoElement });
227
+ });
228
+ ```
229
+
230
+ #### `pause()`, `reset()`, `destroy()`
231
+
232
+ ```typescript
233
+ shader.pause(); // Pause the render loop.
234
+ shader.reset(); // Reset frame counter and clear history buffers.
235
+ shader.destroy(); // Clean up resources.
236
+ ```
237
+
238
+ ### Properties
239
+
240
+ - `canvas` (HTMLCanvasElement): The canvas element used for rendering
241
+ - `onResize?: (width: number, height: number) => void`: Callback fired when the canvas is resized
242
+
74
243
  ## Options
75
244
 
76
245
  ShaderPad’s constructor accepts an optional `options` object.
@@ -88,49 +257,219 @@ shader.onResize = (width, height) => {
88
257
  };
89
258
  ```
90
259
 
260
+ ### history
261
+
262
+ The `history` option enables framebuffer history, allowing you to access previous frames in your shader. Pass a number to specify how many frames to keep.
263
+
264
+ ```typescript
265
+ // Store the last 10 frames of shader output.
266
+ const shader = new ShaderPad(fragmentShaderSrc, { history: 10 });
267
+
268
+ // In your shader, access history using the u_history uniform:
269
+ // uniform highp sampler2DArray u_history;
270
+ // It’s stored as a ringbuffer, so you can access specific frames with the provided offset uniform:
271
+ // uniform int u_historyFrameOffset;
272
+ ```
273
+
274
+ You can also enable history for individual textures:
275
+
276
+ ```typescript
277
+ // Store the last 30 webcam frames.
278
+ shader.initializeTexture('u_webcam', videoElement, { history: 30 });
279
+ // In your shader, access history using the u_webcam and u_webcamFrameOffset uniforms:
280
+ // uniform highp sampler2DArray u_webcam;
281
+ // uniform int u_webcamFrameOffset;
282
+ ```
283
+
284
+ It’s recommended to use the `helpers` plugin to simplify access to history textures:
285
+
286
+ ```glsl
287
+ int nFramesAgo = 2; // Get the color 2 frames ago.
288
+ float zIndex = historyZ(u_webcam, u_webcamFrameOffset, nFramesAgo);
289
+ vec4 historyColor = texture(u_webcam, vec3(v_uv, zIndex));
290
+ ```
291
+
91
292
  ### plugins
92
293
 
93
- ShaderPad supports plugins to add additional functionality.
294
+ ShaderPad supports plugins to add additional functionality. Plugins are imported from separate paths to keep bundle sizes small.
94
295
 
95
- #### history
296
+ #### helpers
96
297
 
97
- The `history` plugin adds a frame history buffer to the shader. It is useful for effects like motion blur, feedback, and trails.
298
+ The `helpers` plugin provides convenience functions and constants. See [helpers.glsl](./src/plugins/helpers.glsl) for the implementation.
98
299
 
99
300
  ```typescript
100
- import ShaderPad, { history } from 'shaderpad';
101
- // 2-frame history (eg. for cellular automata).
102
- const shader = new ShaderPad(fragmentShaderSrc, { plugins: [history(2)] });
301
+ import ShaderPad from 'shaderpad';
302
+ import { helpers } from 'shaderpad/plugins/helpers';
103
303
 
104
- // 10-frame history (eg. for motion blur).
105
- const shader = new ShaderPad(fragmentShaderSrc, { plugins: [history(10)] });
304
+ const shader = new ShaderPad(fragmentShaderSrc, {
305
+ plugins: [helpers()],
306
+ });
106
307
  ```
107
308
 
108
309
  #### save
109
310
 
110
- The `save` plugin adds a `.save()` method to the shader that saves the current frame to a PNG file.
311
+ The `save` plugin adds a `.save()` method to the shader that saves the current frame to a PNG file. It works on desktop and mobile.
111
312
 
112
313
  ```typescript
113
- import ShaderPad, { save, WithSave } from 'shaderpad';
314
+ import ShaderPad from 'shaderpad';
315
+ import { save, WithSave } from 'shaderpad/plugins/save';
316
+
114
317
  const shader = new ShaderPad(fragmentShaderSrc, { plugins: [save()] }) as WithSave<ShaderPad>;
115
318
  shader.save('my-frame');
116
319
  ```
117
320
 
118
- ## Included uniforms
321
+ #### face
119
322
 
120
- | Uniform | Type | Description |
121
- | -------------- | -------------- | ------------------------------------------------------------------------------- |
122
- | `u_time` | float | The current time in seconds. |
123
- | `u_frame` | int | The current frame number. |
124
- | `u_resolution` | float[2] | The canvas element's dimensions. |
125
- | `u_cursor` | float[2] | Cursor position (x, y). |
126
- | `u_click` | float[3] | Click position (x, y) and left click state (z). |
127
- | `u_history` | sampler2DArray | Buffer texture of prior frames. Only available if the `history` plugin is used. |
323
+ The `face` plugin uses [MediaPipe](https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker) to detect faces in video or image textures.
128
324
 
129
- ## Included varyings
325
+ ```typescript
326
+ import ShaderPad from 'shaderpad';
327
+ import { face } from 'shaderpad/plugins/face';
328
+
329
+ const shader = new ShaderPad(fragmentShaderSrc, {
330
+ plugins: [
331
+ face({
332
+ textureName: 'u_webcam',
333
+ options: { maxFaces: 3 },
334
+ }),
335
+ ],
336
+ });
337
+ ```
130
338
 
131
- | Varying | Type | Description |
132
- | ------- | -------- | ----------------------------------- |
133
- | `v_uv` | float[2] | The UV coordinates of the fragment. |
339
+ **Uniforms:**
340
+
341
+ | Uniform | Type | Description |
342
+ | -------------- | -------------- | ---------------------------------------------- |
343
+ | `u_maxFaces` | int | Maximum number of faces to detect |
344
+ | `u_nFaces` | int | Current number of detected faces |
345
+ | `u_faceCenter` | vec2[maxFaces] | Face center positions |
346
+ | `u_leftEye` | vec2[maxFaces] | Left eye positions |
347
+ | `u_rightEye` | vec2[maxFaces] | Right eye positions |
348
+ | `u_noseTip` | vec2[maxFaces] | Nose tip positions |
349
+ | `u_faceMask` | sampler2D | Face mask texture (R: mouth, G: face, B: eyes) |
350
+
351
+ **Helper functions:** `getFace(vec2 pos)`, `getEye(vec2 pos)`, `getMouth(vec2 pos)`
352
+
353
+ **Note:** The face plugin requires `@mediapipe/tasks-vision` as a peer dependency.
354
+
355
+ #### pose
356
+
357
+ 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 33 landmarks, enumerated below.
358
+
359
+ ```typescript
360
+ import ShaderPad from 'shaderpad';
361
+ import { pose } from 'shaderpad/plugins/pose';
362
+
363
+ const shader = new ShaderPad(fragmentShaderSrc, {
364
+ plugins: [pose({ textureName: 'u_video', options: { maxPoses: 3 } })],
365
+ });
366
+ ```
367
+
368
+ **Uniforms:**
369
+
370
+ | Uniform | Type | Description |
371
+ | ----------------- | ------------------- | ---------------------------------------- |
372
+ | `u_maxPoses` | int | Maximum number of poses to track |
373
+ | `u_nPoses` | int | Current number of detected poses |
374
+ | `u_poseLandmarks` | vec2[maxPoses * 33] | Landmark positions in UV space |
375
+ | `u_poseMask` | sampler2D | Pose mask texture (G: body, B: skeleton) |
376
+
377
+ **Helper functions:** `poseLandmark(int poseIndex, int landmarkIndex)`, `getBody(vec2 pos)`, `getSkeleton(vec2 pos)`
378
+
379
+ Use `poseLandmark(int poseIndex, int landmarkIndex)` in GLSL to retrieve a specific point. Landmark indices are:
380
+
381
+ | Index | Landmark | Index | Landmark |
382
+ | ----- | ----------------- | ----- | ---------------- |
383
+ | 0 | nose | 17 | left pinky |
384
+ | 1 | left eye (inner) | 18 | right pinky |
385
+ | 2 | left eye | 19 | left index |
386
+ | 3 | left eye (outer) | 20 | right index |
387
+ | 4 | right eye (inner) | 21 | left thumb |
388
+ | 5 | right eye | 22 | right thumb |
389
+ | 6 | right eye (outer) | 23 | left hip |
390
+ | 7 | left ear | 24 | right hip |
391
+ | 8 | right ear | 25 | left knee |
392
+ | 9 | mouth (left) | 26 | right knee |
393
+ | 10 | mouth (right) | 27 | left ankle |
394
+ | 11 | left shoulder | 28 | right ankle |
395
+ | 12 | right shoulder | 29 | left heel |
396
+ | 13 | left elbow | 30 | right heel |
397
+ | 14 | right elbow | 31 | left foot index |
398
+ | 15 | left wrist | 32 | right foot index |
399
+ | 16 | right wrist | | |
400
+
401
+ [Source](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker#pose_landmarker_model)
402
+
403
+ A minimal fragment shader loop looks like:
404
+
405
+ ```glsl
406
+ int POSE_LANDMARK_LEFT_HIP = 23;
407
+ int POSE_LANDMARK_RIGHT_HIP = 24;
408
+ for (int i = 0; i < u_maxPoses; ++i) {
409
+ if (i >= u_nPoses) break;
410
+ vec2 leftHip = poseLandmark(i, POSE_LANDMARK_LEFT_HIP);
411
+ vec2 rightHip = poseLandmark(i, POSE_LANDMARK_RIGHT_HIP);
412
+ // …
413
+ }
414
+ ```
415
+
416
+ #### hands
417
+
418
+ 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.
419
+
420
+ ```typescript
421
+ import ShaderPad from 'shaderpad';
422
+ import { hands } from 'shaderpad/plugins/hands';
423
+
424
+ const shader = new ShaderPad(fragmentShaderSrc, {
425
+ plugins: [hands({ textureName: 'u_video', options: { maxHands: 2 } })],
426
+ });
427
+ ```
428
+
429
+ **Uniforms:**
430
+
431
+ | Uniform | Type | Description |
432
+ | ----------------- | ------------------- | -------------------------------- |
433
+ | `u_maxHands` | int | Maximum number of hands to track |
434
+ | `u_nHands` | int | Current number of detected hands |
435
+ | `u_handLandmarks` | vec2[maxHands * 22] | Landmark positions in UV space |
436
+
437
+ **Helper functions:** `handLandmark(int handIndex, int landmarkIndex)`
438
+
439
+ Use `handLandmark(int handIndex, int landmarkIndex)` in GLSL to retrieve a specific point. Landmark indices are:
440
+
441
+ | Index | Landmark | Index | Landmark |
442
+ | ----- | ----------------- | ----- | ----------------- |
443
+ | 0 | WRIST | 11 | MIDDLE_FINGER_DIP |
444
+ | 1 | THUMB_CMC | 12 | MIDDLE_FINGER_TIP |
445
+ | 2 | THUMB_MCP | 13 | RING_FINGER_MCP |
446
+ | 3 | THUMB_IP | 14 | RING_FINGER_PIP |
447
+ | 4 | THUMB_TIP | 15 | RING_FINGER_DIP |
448
+ | 5 | INDEX_FINGER_MCP | 16 | RING_FINGER_TIP |
449
+ | 6 | INDEX_FINGER_PIP | 17 | PINKY_MCP |
450
+ | 7 | INDEX_FINGER_DIP | 18 | PINKY_PIP |
451
+ | 8 | INDEX_FINGER_TIP | 19 | PINKY_DIP |
452
+ | 9 | MIDDLE_FINGER_MCP | 20 | PINKY_TIP |
453
+ | 10 | MIDDLE_FINGER_PIP | 21 | HAND_CENTER |
454
+
455
+ [Source](https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker#models)
456
+
457
+ A minimal fragment shader loop looks like:
458
+
459
+ ```glsl
460
+ int HAND_LANDMARK_WRIST = 0;
461
+ int HAND_LANDMARK_THUMB_TIP = 4;
462
+ int HAND_LANDMARK_INDEX_TIP = 8;
463
+ for (int i = 0; i < u_maxHands; ++i) {
464
+ if (i >= u_nHands) break;
465
+ vec2 wrist = handLandmark(i, HAND_LANDMARK_WRIST);
466
+ vec2 thumbTip = handLandmark(i, HAND_LANDMARK_THUMB_TIP);
467
+ vec2 indexTip = handLandmark(i, HAND_LANDMARK_INDEX_TIP);
468
+ // …
469
+ }
470
+ ```
471
+
472
+ **Note:** The hands plugin requires `@mediapipe/tasks-vision` as a peer dependency.
134
473
 
135
474
  ## Contributing
136
475
 
@@ -147,7 +486,7 @@ npm install
147
486
  npm run dev
148
487
  ```
149
488
 
150
- This will launch a local server. Open the provided URL (usually `http://localhost:5173`) in your browser to view and interact with the examples. Use the select box to view a different example.
489
+ This will launch a local server. Open the provided URL (usually `http://localhost:5173`) in your browser to view and interact with the examples. Use the select box to view different examples.
151
490
 
152
491
  ### Adding an example
153
492
 
package/dist/index.d.mts CHANGED
@@ -1,45 +1,45 @@
1
- declare function history(depth?: number): (shaderPad: ShaderPad, context: PluginContext) => void;
2
-
3
- declare module '../index' {
4
- interface ShaderPad {
5
- save(filename: string): Promise<void>;
6
- }
7
- }
8
- declare function save(): (shaderPad: ShaderPad, context: PluginContext) => void;
9
- type WithSave<T extends ShaderPad> = T & {
10
- save(filename: string): Promise<void>;
11
- };
12
-
13
1
  interface Uniform {
14
2
  type: 'float' | 'int';
15
3
  length: 1 | 2 | 3 | 4;
16
4
  location: WebGLUniformLocation;
5
+ arrayLength?: number;
17
6
  }
18
7
  interface Texture {
19
8
  texture: WebGLTexture;
20
9
  unitIndex: number;
10
+ width: number;
11
+ height: number;
12
+ history?: {
13
+ depth: number;
14
+ writeIndex: number;
15
+ };
21
16
  }
17
+ type TextureSource = HTMLImageElement | HTMLVideoElement | HTMLCanvasElement;
22
18
  interface PluginContext {
23
19
  gl: WebGL2RenderingContext;
24
20
  uniforms: Map<string, Uniform>;
25
- textures: Map<string, Texture>;
26
- program: WebGLProgram | null;
21
+ textures: Map<string | symbol, Texture>;
22
+ get program(): WebGLProgram | null;
27
23
  canvas: HTMLCanvasElement;
24
+ reserveTextureUnit: (name: string | symbol) => number;
25
+ releaseTextureUnit: (name: string | symbol) => void;
26
+ injectGLSL: (code: string) => void;
28
27
  }
29
28
  type Plugin = (shaderPad: ShaderPad, context: PluginContext) => void;
30
- type LifecycleMethod = 'init' | 'step' | 'destroy' | 'updateResolution' | 'reset';
29
+ type LifecycleMethod = 'init' | 'step' | 'destroy' | 'updateResolution' | 'reset' | 'initializeTexture' | 'updateTextures' | 'initializeUniform' | 'updateUniforms';
31
30
  interface Options {
32
31
  canvas?: HTMLCanvasElement | null;
33
32
  plugins?: Plugin[];
33
+ history?: number;
34
34
  }
35
35
  declare class ShaderPad {
36
36
  private isInternalCanvas;
37
37
  private isTouchDevice;
38
38
  private gl;
39
- private downloadLink;
40
39
  private fragmentShaderSrc;
41
40
  private uniforms;
42
41
  private textures;
42
+ private textureUnitPool;
43
43
  private buffer;
44
44
  private program;
45
45
  private animationFrameId;
@@ -56,6 +56,7 @@ declare class ShaderPad {
56
56
  canvas: HTMLCanvasElement;
57
57
  onResize?: (width: number, height: number) => void;
58
58
  private hooks;
59
+ private historyDepth;
59
60
  constructor(fragmentShaderSrc: string, options?: Options);
60
61
  registerHook(name: LifecycleMethod, fn: Function): void;
61
62
  private init;
@@ -65,16 +66,27 @@ declare class ShaderPad {
65
66
  private handleResize;
66
67
  private addEventListeners;
67
68
  private updateResolution;
68
- initializeUniform(name: string, type: 'float' | 'int', value: number | number[]): void;
69
- updateUniforms(updates: Record<string, number | number[]>): void;
69
+ private reserveTextureUnit;
70
+ private releaseTextureUnit;
71
+ private clearHistoryTextureLayers;
72
+ initializeUniform(name: string, type: 'float' | 'int', value: number | number[] | (number | number[])[], options?: {
73
+ arrayLength?: number;
74
+ }): void;
75
+ updateUniforms(updates: Record<string, number | number[] | (number | number[])[]>, options?: {
76
+ startIndex?: number;
77
+ }): void;
78
+ private createTexture;
79
+ private _initializeTexture;
80
+ initializeTexture(name: string, source: TextureSource, options?: {
81
+ history?: number;
82
+ }): void;
83
+ updateTextures(updates: Record<string, TextureSource>): void;
84
+ private updateTexture;
70
85
  step(time: number): void;
71
86
  play(callback?: (time: number, frame: number) => void): void;
72
87
  pause(): void;
73
88
  reset(): void;
74
- initializeTexture(name: string, source: HTMLImageElement | HTMLVideoElement): void;
75
- updateTextures(updates: Record<string, HTMLImageElement | HTMLVideoElement>): void;
76
- save(filename: string): Promise<void>;
77
89
  destroy(): void;
78
90
  }
79
91
 
80
- export { type PluginContext, type WithSave, ShaderPad as default, history, save };
92
+ export { type Options, type PluginContext, type TextureSource, ShaderPad as default };
package/dist/index.d.ts CHANGED
@@ -1,45 +1,45 @@
1
- declare function history(depth?: number): (shaderPad: ShaderPad, context: PluginContext) => void;
2
-
3
- declare module '../index' {
4
- interface ShaderPad {
5
- save(filename: string): Promise<void>;
6
- }
7
- }
8
- declare function save(): (shaderPad: ShaderPad, context: PluginContext) => void;
9
- type WithSave<T extends ShaderPad> = T & {
10
- save(filename: string): Promise<void>;
11
- };
12
-
13
1
  interface Uniform {
14
2
  type: 'float' | 'int';
15
3
  length: 1 | 2 | 3 | 4;
16
4
  location: WebGLUniformLocation;
5
+ arrayLength?: number;
17
6
  }
18
7
  interface Texture {
19
8
  texture: WebGLTexture;
20
9
  unitIndex: number;
10
+ width: number;
11
+ height: number;
12
+ history?: {
13
+ depth: number;
14
+ writeIndex: number;
15
+ };
21
16
  }
17
+ type TextureSource = HTMLImageElement | HTMLVideoElement | HTMLCanvasElement;
22
18
  interface PluginContext {
23
19
  gl: WebGL2RenderingContext;
24
20
  uniforms: Map<string, Uniform>;
25
- textures: Map<string, Texture>;
26
- program: WebGLProgram | null;
21
+ textures: Map<string | symbol, Texture>;
22
+ get program(): WebGLProgram | null;
27
23
  canvas: HTMLCanvasElement;
24
+ reserveTextureUnit: (name: string | symbol) => number;
25
+ releaseTextureUnit: (name: string | symbol) => void;
26
+ injectGLSL: (code: string) => void;
28
27
  }
29
28
  type Plugin = (shaderPad: ShaderPad, context: PluginContext) => void;
30
- type LifecycleMethod = 'init' | 'step' | 'destroy' | 'updateResolution' | 'reset';
29
+ type LifecycleMethod = 'init' | 'step' | 'destroy' | 'updateResolution' | 'reset' | 'initializeTexture' | 'updateTextures' | 'initializeUniform' | 'updateUniforms';
31
30
  interface Options {
32
31
  canvas?: HTMLCanvasElement | null;
33
32
  plugins?: Plugin[];
33
+ history?: number;
34
34
  }
35
35
  declare class ShaderPad {
36
36
  private isInternalCanvas;
37
37
  private isTouchDevice;
38
38
  private gl;
39
- private downloadLink;
40
39
  private fragmentShaderSrc;
41
40
  private uniforms;
42
41
  private textures;
42
+ private textureUnitPool;
43
43
  private buffer;
44
44
  private program;
45
45
  private animationFrameId;
@@ -56,6 +56,7 @@ declare class ShaderPad {
56
56
  canvas: HTMLCanvasElement;
57
57
  onResize?: (width: number, height: number) => void;
58
58
  private hooks;
59
+ private historyDepth;
59
60
  constructor(fragmentShaderSrc: string, options?: Options);
60
61
  registerHook(name: LifecycleMethod, fn: Function): void;
61
62
  private init;
@@ -65,16 +66,27 @@ declare class ShaderPad {
65
66
  private handleResize;
66
67
  private addEventListeners;
67
68
  private updateResolution;
68
- initializeUniform(name: string, type: 'float' | 'int', value: number | number[]): void;
69
- updateUniforms(updates: Record<string, number | number[]>): void;
69
+ private reserveTextureUnit;
70
+ private releaseTextureUnit;
71
+ private clearHistoryTextureLayers;
72
+ initializeUniform(name: string, type: 'float' | 'int', value: number | number[] | (number | number[])[], options?: {
73
+ arrayLength?: number;
74
+ }): void;
75
+ updateUniforms(updates: Record<string, number | number[] | (number | number[])[]>, options?: {
76
+ startIndex?: number;
77
+ }): void;
78
+ private createTexture;
79
+ private _initializeTexture;
80
+ initializeTexture(name: string, source: TextureSource, options?: {
81
+ history?: number;
82
+ }): void;
83
+ updateTextures(updates: Record<string, TextureSource>): void;
84
+ private updateTexture;
70
85
  step(time: number): void;
71
86
  play(callback?: (time: number, frame: number) => void): void;
72
87
  pause(): void;
73
88
  reset(): void;
74
- initializeTexture(name: string, source: HTMLImageElement | HTMLVideoElement): void;
75
- updateTextures(updates: Record<string, HTMLImageElement | HTMLVideoElement>): void;
76
- save(filename: string): Promise<void>;
77
89
  destroy(): void;
78
90
  }
79
91
 
80
- export { type PluginContext, type WithSave, ShaderPad as default, history, save };
92
+ export { type Options, type PluginContext, type TextureSource, ShaderPad as default };