shaderpad 1.0.0-beta.29 → 1.0.0-beta.31
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 +178 -63
- package/dist/index.d.mts +29 -4
- package/dist/index.d.ts +29 -4
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/dist/plugins/face.d.mts +2 -0
- package/dist/plugins/face.d.ts +2 -0
- package/dist/plugins/face.js +17 -12
- package/dist/plugins/face.js.map +1 -1
- package/dist/plugins/face.mjs +17 -12
- package/dist/plugins/face.mjs.map +1 -1
- package/dist/plugins/hands.d.mts +2 -0
- package/dist/plugins/hands.d.ts +2 -0
- package/dist/plugins/hands.js +8 -5
- package/dist/plugins/hands.js.map +1 -1
- package/dist/plugins/hands.mjs +8 -5
- package/dist/plugins/hands.mjs.map +1 -1
- package/dist/plugins/pose.d.mts +2 -0
- package/dist/plugins/pose.d.ts +2 -0
- package/dist/plugins/pose.js +26 -7
- package/dist/plugins/pose.js.map +1 -1
- package/dist/plugins/pose.mjs +26 -7
- package/dist/plugins/pose.mjs.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -178,7 +178,7 @@ shader.updateUniforms(
|
|
|
178
178
|
|
|
179
179
|
#### `initializeTexture(name, source, options?)`
|
|
180
180
|
|
|
181
|
-
Initialize a texture from an image, video, or
|
|
181
|
+
Initialize a texture from an image, video, canvas element, or typed array.
|
|
182
182
|
|
|
183
183
|
```typescript
|
|
184
184
|
// Initialize a texture from an image.
|
|
@@ -188,15 +188,49 @@ img.onload = () => {
|
|
|
188
188
|
shader.initializeTexture('u_texture', img);
|
|
189
189
|
};
|
|
190
190
|
|
|
191
|
+
// Initialize a texture from a typed array (Float32Array, Uint8Array, etc.).
|
|
192
|
+
const data = new Float32Array(width * height * 4); // RGBA data
|
|
193
|
+
shader.initializeTexture(
|
|
194
|
+
'u_custom',
|
|
195
|
+
{
|
|
196
|
+
data,
|
|
197
|
+
width,
|
|
198
|
+
height,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
internalFormat: gl.RGBA32F,
|
|
202
|
+
type: gl.FLOAT,
|
|
203
|
+
minFilter: gl.NEAREST,
|
|
204
|
+
magFilter: gl.NEAREST,
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
|
|
191
208
|
// Initialize a texture with history (stores previous frames).
|
|
192
209
|
shader.initializeTexture('u_webcam', videoElement, { history: 30 });
|
|
210
|
+
|
|
211
|
+
// Preserve Y orientation for DOM sources (don't flip vertically).
|
|
212
|
+
shader.initializeTexture('u_canvas', canvasElement, { preserveY: true });
|
|
193
213
|
```
|
|
194
214
|
|
|
195
215
|
**Parameters:**
|
|
196
216
|
|
|
197
217
|
- `name` (string): The name of the texture uniform as declared in your shader
|
|
198
|
-
- `source` (HTMLImageElement | HTMLVideoElement | HTMLCanvasElement): The texture source
|
|
199
|
-
- `options` (optional):
|
|
218
|
+
- `source` (HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | CustomTexture): The texture source
|
|
219
|
+
- `options` (optional): Texture options (see below)
|
|
220
|
+
|
|
221
|
+
**Texture Options:**
|
|
222
|
+
|
|
223
|
+
- `history?: number` - Number of previous frames to store (creates a `sampler2DArray`)
|
|
224
|
+
- `preserveY?: boolean` - For DOM sources only: if `true`, don't flip vertically (default: `false`, flips to match WebGL's bottom-up convention)
|
|
225
|
+
- `internalFormat?: number` - WebGL internal format (e.g., `gl.RGBA8`, `gl.RGBA32F`)
|
|
226
|
+
- `format?: number` - WebGL format (default: `gl.RGBA`)
|
|
227
|
+
- `type?: number` - WebGL data type (default: `gl.UNSIGNED_BYTE` for DOM sources, must be specified for typed arrays)
|
|
228
|
+
- `minFilter?: number` - Minification filter (default: `gl.LINEAR`)
|
|
229
|
+
- `magFilter?: number` - Magnification filter (default: `gl.LINEAR`)
|
|
230
|
+
- `wrapS?: number` - Wrap mode for S coordinate (default: `gl.CLAMP_TO_EDGE`)
|
|
231
|
+
- `wrapT?: number` - Wrap mode for T coordinate (default: `gl.CLAMP_TO_EDGE`)
|
|
232
|
+
|
|
233
|
+
**Note:** For typed array sources (`CustomTexture`), you must provide data in bottom-up orientation (WebGL convention). The `preserveY` option is ignored for typed arrays.
|
|
200
234
|
|
|
201
235
|
#### `updateTextures(updates)`
|
|
202
236
|
|
|
@@ -206,12 +240,29 @@ Update one or more textures. Useful for updating video textures each frame.
|
|
|
206
240
|
shader.updateTextures({
|
|
207
241
|
u_webcam: videoElement,
|
|
208
242
|
u_overlay: overlayCanvas,
|
|
243
|
+
u_custom: {
|
|
244
|
+
data: typedArray,
|
|
245
|
+
width,
|
|
246
|
+
height,
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Typed arrays can be partially updated.
|
|
251
|
+
shader.updateTextures({
|
|
252
|
+
u_custom: {
|
|
253
|
+
data: partialData,
|
|
254
|
+
width: regionWidth,
|
|
255
|
+
height: regionHeight,
|
|
256
|
+
isPartial: true,
|
|
257
|
+
x: offsetX,
|
|
258
|
+
y: offsetY,
|
|
259
|
+
},
|
|
209
260
|
});
|
|
210
261
|
```
|
|
211
262
|
|
|
212
263
|
**Parameters:**
|
|
213
264
|
|
|
214
|
-
- `updates` (Record<string, TextureSource>): Object mapping texture names to their new sources
|
|
265
|
+
- `updates` (Record<string, TextureSource | PartialCustomTexture>): Object mapping texture names to their new sources
|
|
215
266
|
|
|
216
267
|
### Lifecycle methods
|
|
217
268
|
|
|
@@ -289,6 +340,14 @@ float zIndex = historyZ(u_webcam, u_webcamFrameOffset, nFramesAgo);
|
|
|
289
340
|
vec4 historyColor = texture(u_webcam, vec3(v_uv, zIndex));
|
|
290
341
|
```
|
|
291
342
|
|
|
343
|
+
### debug
|
|
344
|
+
|
|
345
|
+
The `debug` option controls whether debug logging is enabled. When enabled, ShaderPad will log warnings when uniforms or textures are not found in the shader. Defaults to `true` in development (when `process.env.NODE_ENV !== 'production'`) and `false` in production builds.
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
const shader = new ShaderPad(fragmentShaderSrc, { debug: true }); // Explicitly enable debug logging.
|
|
349
|
+
```
|
|
350
|
+
|
|
292
351
|
### plugins
|
|
293
352
|
|
|
294
353
|
ShaderPad supports plugins to add additional functionality. Plugins are imported from separate paths to keep bundle sizes small.
|
|
@@ -317,7 +376,7 @@ import ShaderPad from 'shaderpad';
|
|
|
317
376
|
import save, { WithSave } from 'shaderpad/plugins/save';
|
|
318
377
|
|
|
319
378
|
const shader = new ShaderPad(fragmentShaderSrc, { plugins: [save()] }) as WithSave<ShaderPad>;
|
|
320
|
-
shader.save('
|
|
379
|
+
shader.save('filename', 'Optional mobile share text');
|
|
321
380
|
```
|
|
322
381
|
|
|
323
382
|
#### face
|
|
@@ -340,27 +399,53 @@ const shader = new ShaderPad(fragmentShaderSrc, {
|
|
|
340
399
|
|
|
341
400
|
**Uniforms:**
|
|
342
401
|
|
|
343
|
-
| Uniform
|
|
344
|
-
|
|
|
345
|
-
| `u_maxFaces`
|
|
346
|
-
| `u_nFaces`
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
402
|
+
| Uniform | Type | Description |
|
|
403
|
+
| -------------------- | --------- | ---------------------------------------------------------- |
|
|
404
|
+
| `u_maxFaces` | int | Maximum number of faces to detect |
|
|
405
|
+
| `u_nFaces` | int | Current number of detected faces |
|
|
406
|
+
| `u_faceLandmarksTex` | sampler2D | Raw landmark data texture (use `faceLandmark()` to access) |
|
|
407
|
+
| `u_faceMask` | sampler2D | Face mask texture (R: mouth, G: face, B: eyes) |
|
|
408
|
+
|
|
409
|
+
**Helper functions:**
|
|
410
|
+
|
|
411
|
+
- `faceLandmark(int faceIndex, int landmarkIndex) -> vec4` - Returns landmark data as `vec4(x, y, z, visibility)`. Use `vec2(faceLandmark(...))` to get just the screen position.
|
|
412
|
+
- `inFace(vec2 pos) -> float` - Returns face mask value at position (green channel)
|
|
413
|
+
- `inEye(vec2 pos) -> float` - Returns eye mask value at position (blue channel)
|
|
414
|
+
- `inMouth(vec2 pos) -> float` - Returns mouth mask value at position (red channel)
|
|
354
415
|
|
|
355
|
-
**
|
|
416
|
+
**Constants:**
|
|
356
417
|
|
|
357
|
-
|
|
418
|
+
- `FACE_LANDMARK_L_EYE_CENTER` - Left eye center landmark index
|
|
419
|
+
- `FACE_LANDMARK_R_EYE_CENTER` - Right eye center landmark index
|
|
420
|
+
- `FACE_LANDMARK_NOSE_TIP` - Nose tip landmark index
|
|
421
|
+
- `FACE_LANDMARK_FACE_CENTER` - Face center landmark index (custom, calculated from all landmarks)
|
|
422
|
+
- `FACE_LANDMARK_MOUTH_CENTER` - Mouth center landmark index (custom, calculated from inner lip landmarks)
|
|
423
|
+
|
|
424
|
+
**Example usage:**
|
|
425
|
+
|
|
426
|
+
```glsl
|
|
427
|
+
// Get a specific landmark position.
|
|
428
|
+
vec2 nosePos = vec2(faceLandmark(0, FACE_LANDMARK_NOSE_TIP));
|
|
429
|
+
|
|
430
|
+
if (inMouth(v_uv) > 0.0) {
|
|
431
|
+
// Position is inside a mouth.
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Iterate through all faces and landmarks.
|
|
435
|
+
for (int i = 0; i < u_nFaces; ++i) {
|
|
436
|
+
vec4 leftEye = faceLandmark(i, FACE_LANDMARK_L_EYE_CENTER);
|
|
437
|
+
vec4 rightEye = faceLandmark(i, FACE_LANDMARK_R_EYE_CENTER);
|
|
438
|
+
// ...
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
[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.
|
|
358
443
|
|
|
359
444
|
**Note:** The face plugin requires `@mediapipe/tasks-vision` as a peer dependency.
|
|
360
445
|
|
|
361
446
|
#### pose
|
|
362
447
|
|
|
363
|
-
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
|
|
448
|
+
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.
|
|
364
449
|
|
|
365
450
|
```typescript
|
|
366
451
|
import ShaderPad from 'shaderpad';
|
|
@@ -373,49 +458,75 @@ const shader = new ShaderPad(fragmentShaderSrc, {
|
|
|
373
458
|
|
|
374
459
|
**Uniforms:**
|
|
375
460
|
|
|
376
|
-
| Uniform
|
|
377
|
-
|
|
|
378
|
-
| `u_maxPoses`
|
|
379
|
-
| `u_nPoses`
|
|
380
|
-
| `
|
|
381
|
-
| `u_poseMask`
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
461
|
+
| Uniform | Type | Description |
|
|
462
|
+
| -------------------- | --------- | ----------------------------------------------------- |
|
|
463
|
+
| `u_maxPoses` | int | Maximum number of poses to track |
|
|
464
|
+
| `u_nPoses` | int | Current number of detected poses |
|
|
465
|
+
| `u_poseLandmarksTex` | sampler2D | Raw landmark data texture (RGBA: x, y, z, visibility) |
|
|
466
|
+
| `u_poseMask` | sampler2D | Pose mask texture (G: body) |
|
|
467
|
+
|
|
468
|
+
**Helper functions:**
|
|
469
|
+
|
|
470
|
+
- `poseLandmark(int poseIndex, int landmarkIndex) -> vec4` - Returns landmark data as `vec4(x, y, z, visibility)`. Use `vec2(poseLandmark(...))` to get just the screen position.
|
|
471
|
+
- `inBody(vec2 pos) -> float` - Returns body mask value at position (green channel)
|
|
472
|
+
|
|
473
|
+
**Constants:**
|
|
474
|
+
|
|
475
|
+
- `POSE_LANDMARK_LEFT_EYE` - Left eye landmark index (2)
|
|
476
|
+
- `POSE_LANDMARK_RIGHT_EYE` - Right eye landmark index (5)
|
|
477
|
+
- `POSE_LANDMARK_LEFT_SHOULDER` - Left shoulder landmark index (11)
|
|
478
|
+
- `POSE_LANDMARK_RIGHT_SHOULDER` - Right shoulder landmark index (12)
|
|
479
|
+
- `POSE_LANDMARK_LEFT_ELBOW` - Left elbow landmark index (13)
|
|
480
|
+
- `POSE_LANDMARK_RIGHT_ELBOW` - Right elbow landmark index (14)
|
|
481
|
+
- `POSE_LANDMARK_LEFT_HIP` - Left hip landmark index (23)
|
|
482
|
+
- `POSE_LANDMARK_RIGHT_HIP` - Right hip landmark index (24)
|
|
483
|
+
- `POSE_LANDMARK_LEFT_KNEE` - Left knee landmark index (25)
|
|
484
|
+
- `POSE_LANDMARK_RIGHT_KNEE` - Right knee landmark index (26)
|
|
485
|
+
- `POSE_LANDMARK_BODY_CENTER` - Body center landmark index (33, custom, calculated from all landmarks)
|
|
486
|
+
- `POSE_LANDMARK_LEFT_HAND_CENTER` - Left hand center landmark index (34, custom, calculated from pinky, thumb, wrist, index)
|
|
487
|
+
- `POSE_LANDMARK_RIGHT_HAND_CENTER` - Right hand center landmark index (35, custom, calculated from pinky, thumb, wrist, index)
|
|
488
|
+
- `POSE_LANDMARK_LEFT_FOOT_CENTER` - Left foot center landmark index (36, custom, calculated from ankle, heel, foot index)
|
|
489
|
+
- `POSE_LANDMARK_RIGHT_FOOT_CENTER` - Right foot center landmark index (37, custom, calculated from ankle, heel, foot index)
|
|
490
|
+
- `POSE_LANDMARK_TORSO_CENTER` - Torso center landmark index (38, custom, calculated from shoulders and hips)
|
|
491
|
+
|
|
492
|
+
**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.
|
|
385
493
|
|
|
386
494
|
Use `poseLandmark(int poseIndex, int landmarkIndex)` in GLSL to retrieve a specific point. Landmark indices are:
|
|
387
495
|
|
|
388
|
-
| Index | Landmark | Index | Landmark
|
|
389
|
-
| ----- | ----------------- | ----- |
|
|
390
|
-
| 0 | nose |
|
|
391
|
-
| 1 | left eye (inner) |
|
|
392
|
-
| 2 | left eye |
|
|
393
|
-
| 3 | left eye (outer) |
|
|
394
|
-
| 4 | right eye (inner) |
|
|
395
|
-
| 5 | right eye |
|
|
396
|
-
| 6 | right eye (outer) |
|
|
397
|
-
| 7 | left ear |
|
|
398
|
-
| 8 | right ear |
|
|
399
|
-
| 9 | mouth (left) |
|
|
400
|
-
| 10 | mouth (right) |
|
|
401
|
-
| 11 | left shoulder |
|
|
402
|
-
| 12 | right shoulder |
|
|
403
|
-
| 13 | left elbow |
|
|
404
|
-
| 14 | right elbow |
|
|
405
|
-
| 15 | left wrist |
|
|
406
|
-
| 16 | right wrist |
|
|
496
|
+
| Index | Landmark | Index | Landmark |
|
|
497
|
+
| ----- | ----------------- | ----- | -------------------------- |
|
|
498
|
+
| 0 | nose | 20 | right index |
|
|
499
|
+
| 1 | left eye (inner) | 21 | left thumb |
|
|
500
|
+
| 2 | left eye | 22 | right thumb |
|
|
501
|
+
| 3 | left eye (outer) | 23 | left hip |
|
|
502
|
+
| 4 | right eye (inner) | 24 | right hip |
|
|
503
|
+
| 5 | right eye | 25 | left knee |
|
|
504
|
+
| 6 | right eye (outer) | 26 | right knee |
|
|
505
|
+
| 7 | left ear | 27 | left ankle |
|
|
506
|
+
| 8 | right ear | 28 | right ankle |
|
|
507
|
+
| 9 | mouth (left) | 29 | left heel |
|
|
508
|
+
| 10 | mouth (right) | 30 | right heel |
|
|
509
|
+
| 11 | left shoulder | 31 | left foot index |
|
|
510
|
+
| 12 | right shoulder | 32 | right foot index |
|
|
511
|
+
| 13 | left elbow | 33 | body center (custom) |
|
|
512
|
+
| 14 | right elbow | 34 | left hand center (custom) |
|
|
513
|
+
| 15 | left wrist | 35 | right hand center (custom) |
|
|
514
|
+
| 16 | right wrist | 36 | left foot center (custom) |
|
|
515
|
+
| 17 | left pinky | 37 | right foot center (custom) |
|
|
516
|
+
| 18 | right pinky | 38 | torso center (custom) |
|
|
517
|
+
| 19 | left index | | |
|
|
407
518
|
|
|
408
519
|
[Source](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker#pose_landmarker_model)
|
|
409
520
|
|
|
521
|
+
[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.
|
|
522
|
+
|
|
410
523
|
A minimal fragment shader loop looks like:
|
|
411
524
|
|
|
412
525
|
```glsl
|
|
413
|
-
int POSE_LANDMARK_LEFT_HIP = 23;
|
|
414
|
-
int POSE_LANDMARK_RIGHT_HIP = 24;
|
|
415
526
|
for (int i = 0; i < u_maxPoses; ++i) {
|
|
416
527
|
if (i >= u_nPoses) break;
|
|
417
|
-
vec2 leftHip = poseLandmark(i, POSE_LANDMARK_LEFT_HIP);
|
|
418
|
-
vec2 rightHip = poseLandmark(i, POSE_LANDMARK_RIGHT_HIP);
|
|
528
|
+
vec2 leftHip = vec2(poseLandmark(i, POSE_LANDMARK_LEFT_HIP));
|
|
529
|
+
vec2 rightHip = vec2(poseLandmark(i, POSE_LANDMARK_RIGHT_HIP));
|
|
419
530
|
// …
|
|
420
531
|
}
|
|
421
532
|
```
|
|
@@ -435,13 +546,15 @@ const shader = new ShaderPad(fragmentShaderSrc, {
|
|
|
435
546
|
|
|
436
547
|
**Uniforms:**
|
|
437
548
|
|
|
438
|
-
| Uniform
|
|
439
|
-
|
|
|
440
|
-
| `u_maxHands`
|
|
441
|
-
| `u_nHands`
|
|
442
|
-
| `
|
|
549
|
+
| Uniform | Type | Description |
|
|
550
|
+
| -------------------- | --------- | ----------------------------------------------------- |
|
|
551
|
+
| `u_maxHands` | int | Maximum number of hands to track |
|
|
552
|
+
| `u_nHands` | int | Current number of detected hands |
|
|
553
|
+
| `u_handLandmarksTex` | sampler2D | Raw landmark data texture (RGBA: x, y, z, visibility) |
|
|
554
|
+
|
|
555
|
+
**Helper functions:**
|
|
443
556
|
|
|
444
|
-
|
|
557
|
+
- `handLandmark(int handIndex, int landmarkIndex) -> vec4` - Returns landmark data as `vec4(x, y, z, visibility)`. Use `vec2(handLandmark(...))` to get just the screen position.
|
|
445
558
|
|
|
446
559
|
Use `handLandmark(int handIndex, int landmarkIndex)` in GLSL to retrieve a specific point. Landmark indices are:
|
|
447
560
|
|
|
@@ -464,14 +577,16 @@ Use `handLandmark(int handIndex, int landmarkIndex)` in GLSL to retrieve a speci
|
|
|
464
577
|
A minimal fragment shader loop looks like:
|
|
465
578
|
|
|
466
579
|
```glsl
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
580
|
+
#define HAND_LANDMARK_WRIST 0
|
|
581
|
+
#define HAND_LANDMARK_THUMB_TIP 4
|
|
582
|
+
#define HAND_LANDMARK_INDEX_TIP 8
|
|
583
|
+
#define HAND_LANDMARK_HAND_CENTER 21
|
|
470
584
|
for (int i = 0; i < u_maxHands; ++i) {
|
|
471
585
|
if (i >= u_nHands) break;
|
|
472
|
-
vec2 wrist = handLandmark(i, HAND_LANDMARK_WRIST);
|
|
473
|
-
vec2 thumbTip = handLandmark(i, HAND_LANDMARK_THUMB_TIP);
|
|
474
|
-
vec2 indexTip = handLandmark(i, HAND_LANDMARK_INDEX_TIP);
|
|
586
|
+
vec2 wrist = vec2(handLandmark(i, HAND_LANDMARK_WRIST));
|
|
587
|
+
vec2 thumbTip = vec2(handLandmark(i, HAND_LANDMARK_THUMB_TIP));
|
|
588
|
+
vec2 indexTip = vec2(handLandmark(i, HAND_LANDMARK_INDEX_TIP));
|
|
589
|
+
vec2 handCenter = vec2(handLandmark(i, HAND_LANDMARK_HAND_CENTER));
|
|
475
590
|
// …
|
|
476
591
|
}
|
|
477
592
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -4,6 +4,16 @@ interface Uniform {
|
|
|
4
4
|
location: WebGLUniformLocation;
|
|
5
5
|
arrayLength?: number;
|
|
6
6
|
}
|
|
7
|
+
interface TextureOptions {
|
|
8
|
+
internalFormat?: number;
|
|
9
|
+
format?: number;
|
|
10
|
+
type?: number;
|
|
11
|
+
minFilter?: number;
|
|
12
|
+
magFilter?: number;
|
|
13
|
+
wrapS?: number;
|
|
14
|
+
wrapT?: number;
|
|
15
|
+
preserveY?: boolean;
|
|
16
|
+
}
|
|
7
17
|
interface Texture {
|
|
8
18
|
texture: WebGLTexture;
|
|
9
19
|
unitIndex: number;
|
|
@@ -13,8 +23,20 @@ interface Texture {
|
|
|
13
23
|
depth: number;
|
|
14
24
|
writeIndex: number;
|
|
15
25
|
};
|
|
26
|
+
options?: TextureOptions;
|
|
27
|
+
}
|
|
28
|
+
interface CustomTexture {
|
|
29
|
+
data: ArrayBufferView | null;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
}
|
|
33
|
+
interface PartialCustomTexture extends CustomTexture {
|
|
34
|
+
isPartial?: boolean;
|
|
35
|
+
x?: number;
|
|
36
|
+
y?: number;
|
|
16
37
|
}
|
|
17
|
-
type TextureSource = HTMLImageElement | HTMLVideoElement | HTMLCanvasElement;
|
|
38
|
+
type TextureSource = HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | CustomTexture;
|
|
39
|
+
type UpdateTextureSource = Exclude<TextureSource, CustomTexture> | PartialCustomTexture;
|
|
18
40
|
interface PluginContext {
|
|
19
41
|
gl: WebGL2RenderingContext;
|
|
20
42
|
uniforms: Map<string, Uniform>;
|
|
@@ -31,6 +53,7 @@ interface Options {
|
|
|
31
53
|
canvas?: HTMLCanvasElement | null;
|
|
32
54
|
plugins?: Plugin[];
|
|
33
55
|
history?: number;
|
|
56
|
+
debug?: boolean;
|
|
34
57
|
}
|
|
35
58
|
declare class ShaderPad {
|
|
36
59
|
private isInternalCanvas;
|
|
@@ -57,6 +80,7 @@ declare class ShaderPad {
|
|
|
57
80
|
onResize?: (width: number, height: number) => void;
|
|
58
81
|
private hooks;
|
|
59
82
|
private historyDepth;
|
|
83
|
+
private debug;
|
|
60
84
|
constructor(fragmentShaderSrc: string, options?: Options);
|
|
61
85
|
registerHook(name: LifecycleMethod, fn: Function): void;
|
|
62
86
|
private init;
|
|
@@ -72,15 +96,16 @@ declare class ShaderPad {
|
|
|
72
96
|
initializeUniform(name: string, type: 'float' | 'int', value: number | number[] | (number | number[])[], options?: {
|
|
73
97
|
arrayLength?: number;
|
|
74
98
|
}): void;
|
|
99
|
+
private log;
|
|
75
100
|
updateUniforms(updates: Record<string, number | number[] | (number | number[])[]>, options?: {
|
|
76
101
|
startIndex?: number;
|
|
77
102
|
}): void;
|
|
78
103
|
private createTexture;
|
|
79
104
|
private _initializeTexture;
|
|
80
|
-
initializeTexture(name: string, source: TextureSource, options?: {
|
|
105
|
+
initializeTexture(name: string, source: TextureSource, options?: TextureOptions & {
|
|
81
106
|
history?: number;
|
|
82
107
|
}): void;
|
|
83
|
-
updateTextures(updates: Record<string,
|
|
108
|
+
updateTextures(updates: Record<string, UpdateTextureSource>): void;
|
|
84
109
|
private updateTexture;
|
|
85
110
|
draw(): void;
|
|
86
111
|
step(time: number): void;
|
|
@@ -90,4 +115,4 @@ declare class ShaderPad {
|
|
|
90
115
|
destroy(): void;
|
|
91
116
|
}
|
|
92
117
|
|
|
93
|
-
export { type Options, type PluginContext, type TextureSource, ShaderPad as default };
|
|
118
|
+
export { type CustomTexture, type Options, type PartialCustomTexture, type PluginContext, type TextureOptions, type TextureSource, ShaderPad as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,16 @@ interface Uniform {
|
|
|
4
4
|
location: WebGLUniformLocation;
|
|
5
5
|
arrayLength?: number;
|
|
6
6
|
}
|
|
7
|
+
interface TextureOptions {
|
|
8
|
+
internalFormat?: number;
|
|
9
|
+
format?: number;
|
|
10
|
+
type?: number;
|
|
11
|
+
minFilter?: number;
|
|
12
|
+
magFilter?: number;
|
|
13
|
+
wrapS?: number;
|
|
14
|
+
wrapT?: number;
|
|
15
|
+
preserveY?: boolean;
|
|
16
|
+
}
|
|
7
17
|
interface Texture {
|
|
8
18
|
texture: WebGLTexture;
|
|
9
19
|
unitIndex: number;
|
|
@@ -13,8 +23,20 @@ interface Texture {
|
|
|
13
23
|
depth: number;
|
|
14
24
|
writeIndex: number;
|
|
15
25
|
};
|
|
26
|
+
options?: TextureOptions;
|
|
27
|
+
}
|
|
28
|
+
interface CustomTexture {
|
|
29
|
+
data: ArrayBufferView | null;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
}
|
|
33
|
+
interface PartialCustomTexture extends CustomTexture {
|
|
34
|
+
isPartial?: boolean;
|
|
35
|
+
x?: number;
|
|
36
|
+
y?: number;
|
|
16
37
|
}
|
|
17
|
-
type TextureSource = HTMLImageElement | HTMLVideoElement | HTMLCanvasElement;
|
|
38
|
+
type TextureSource = HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | CustomTexture;
|
|
39
|
+
type UpdateTextureSource = Exclude<TextureSource, CustomTexture> | PartialCustomTexture;
|
|
18
40
|
interface PluginContext {
|
|
19
41
|
gl: WebGL2RenderingContext;
|
|
20
42
|
uniforms: Map<string, Uniform>;
|
|
@@ -31,6 +53,7 @@ interface Options {
|
|
|
31
53
|
canvas?: HTMLCanvasElement | null;
|
|
32
54
|
plugins?: Plugin[];
|
|
33
55
|
history?: number;
|
|
56
|
+
debug?: boolean;
|
|
34
57
|
}
|
|
35
58
|
declare class ShaderPad {
|
|
36
59
|
private isInternalCanvas;
|
|
@@ -57,6 +80,7 @@ declare class ShaderPad {
|
|
|
57
80
|
onResize?: (width: number, height: number) => void;
|
|
58
81
|
private hooks;
|
|
59
82
|
private historyDepth;
|
|
83
|
+
private debug;
|
|
60
84
|
constructor(fragmentShaderSrc: string, options?: Options);
|
|
61
85
|
registerHook(name: LifecycleMethod, fn: Function): void;
|
|
62
86
|
private init;
|
|
@@ -72,15 +96,16 @@ declare class ShaderPad {
|
|
|
72
96
|
initializeUniform(name: string, type: 'float' | 'int', value: number | number[] | (number | number[])[], options?: {
|
|
73
97
|
arrayLength?: number;
|
|
74
98
|
}): void;
|
|
99
|
+
private log;
|
|
75
100
|
updateUniforms(updates: Record<string, number | number[] | (number | number[])[]>, options?: {
|
|
76
101
|
startIndex?: number;
|
|
77
102
|
}): void;
|
|
78
103
|
private createTexture;
|
|
79
104
|
private _initializeTexture;
|
|
80
|
-
initializeTexture(name: string, source: TextureSource, options?: {
|
|
105
|
+
initializeTexture(name: string, source: TextureSource, options?: TextureOptions & {
|
|
81
106
|
history?: number;
|
|
82
107
|
}): void;
|
|
83
|
-
updateTextures(updates: Record<string,
|
|
108
|
+
updateTextures(updates: Record<string, UpdateTextureSource>): void;
|
|
84
109
|
private updateTexture;
|
|
85
110
|
draw(): void;
|
|
86
111
|
step(time: number): void;
|
|
@@ -90,4 +115,4 @@ declare class ShaderPad {
|
|
|
90
115
|
destroy(): void;
|
|
91
116
|
}
|
|
92
117
|
|
|
93
|
-
export { type Options, type PluginContext, type TextureSource, ShaderPad as default };
|
|
118
|
+
export { type CustomTexture, type Options, type PartialCustomTexture, type PluginContext, type TextureOptions, type TextureSource, ShaderPad as default };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"use strict";var f=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var
|
|
1
|
+
"use strict";var f=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var E=(n,t)=>{for(var i in t)f(n,i,{get:t[i],enumerable:!0})},y=(n,t,i,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of v(t))!b.call(n,r)&&r!==i&&f(n,r,{get:()=>t[r],enumerable:!(e=T(t,r))||e.enumerable});return n};var w=n=>y(f({},"__esModule",{value:!0}),n);var A={};E(A,{default:()=>_});module.exports=w(A);var R=`#version 300 es
|
|
2
2
|
in vec2 aPosition;
|
|
3
3
|
out vec2 v_uv;
|
|
4
4
|
void main() {
|
|
5
5
|
v_uv = aPosition * 0.5 + 0.5;
|
|
6
6
|
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
7
7
|
}
|
|
8
|
-
`,
|
|
8
|
+
`,U=33.333333333333336,d=Symbol("u_history");function L(n,t){if(!t?.length)return n;let i=n.split(`
|
|
9
9
|
`),e=i.findLastIndex(r=>{let s=r.trimStart();return s.startsWith("precision ")||s.startsWith("#version ")})+1;return i.splice(e,0,...t),i.join(`
|
|
10
|
-
`)}function p(n){if(n instanceof HTMLVideoElement)return{width:n.videoWidth,height:n.videoHeight};if(n instanceof HTMLCanvasElement){let t=n.getContext("webgl2");return t?{width:t.drawingBufferWidth,height:t.drawingBufferHeight}:{width:n.width,height:n.height}}return{width:n.naturalWidth??n.width,height:n.naturalHeight??n.height}}function g(n){return typeof n=="symbol"?n.description??"":n}var m=class{isInternalCanvas=!1;isTouchDevice=!1;gl;fragmentShaderSrc;uniforms=new Map;textures=new Map;textureUnitPool;buffer=null;program=null;animationFrameId;resolutionObserver;resizeObserver;resizeTimeout=null;lastResizeTime=-1/0;eventListeners=new Map;frame=0;startTime=0;cursorPosition=[.5,.5];clickPosition=[.5,.5];isMouseDown=!1;canvas;onResize;hooks=new Map;historyDepth;constructor(t,i={}){if(this.canvas=i.canvas||document.createElement("canvas"),i.canvas||(this.isInternalCanvas=!0,this.canvas.style.position="fixed",this.canvas.style.inset="0",this.canvas.style.height="100dvh",this.canvas.style.width="100dvw",document.body.appendChild(this.canvas)),this.gl=this.canvas.getContext("webgl2",{antialias:!1}),!this.gl)throw new Error("WebGL2 not supported. Please use a browser that supports WebGL2.");this.textureUnitPool={free:[],next:0,max:this.gl.getParameter(this.gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS)},this.historyDepth=i.history??0,this.animationFrameId=null,this.resolutionObserver=new MutationObserver(()=>this.updateResolution()),this.resizeObserver=new ResizeObserver(()=>this.throttledHandleResize());let e=[];if(i.plugins){let r={gl:this.gl,uniforms:this.uniforms,textures:this.textures,canvas:this.canvas,reserveTextureUnit:this.reserveTextureUnit.bind(this),releaseTextureUnit:this.releaseTextureUnit.bind(this),injectGLSL:s=>{e.push(s)}};Object.defineProperty(r,"program",{get:()=>this.program,enumerable:!0,configurable:!0}),i.plugins.forEach(s=>s(this,r))}this.fragmentShaderSrc=L(t,e),this.init(),this.addEventListeners()}registerHook(t,i){this.hooks.has(t)||this.hooks.set(t,[]),this.hooks.get(t).push(i)}init(){let t=w;if(this.program=this.gl.createProgram(),!this.program)throw new Error("Failed to create WebGL program");let i=this.createShader(this.gl.VERTEX_SHADER,t),e=this.createShader(this.gl.FRAGMENT_SHADER,this.fragmentShaderSrc);if(this.gl.attachShader(this.program,i),this.gl.attachShader(this.program,e),this.gl.linkProgram(this.program),this.gl.deleteShader(i),this.gl.deleteShader(e),!this.gl.getProgramParameter(this.program,this.gl.LINK_STATUS))throw console.error("Program link error:",this.gl.getProgramInfoLog(this.program)),this.gl.deleteProgram(this.program),new Error("Failed to link WebGL program");let r=this.gl.getAttribLocation(this.program,"aPosition");this.setupBuffer(r),this.gl.useProgram(this.program),this.resolutionObserver.observe(this.canvas,{attributes:!0,attributeFilter:["width","height"]}),this.resizeObserver.observe(this.canvas),this.isInternalCanvas||this.updateResolution(),this.initializeUniform("u_cursor","float",this.cursorPosition),this.initializeUniform("u_click","float",[...this.clickPosition,this.isMouseDown?1:0]),this.initializeUniform("u_time","float",0),this.initializeUniform("u_frame","int",0),this.historyDepth>0&&this._initializeTexture(c,this.canvas,{history:this.historyDepth}),this.hooks.get("init")?.forEach(s=>s.call(this))}createShader(t,i){let e=this.gl.createShader(t);if(this.gl.shaderSource(e,i),this.gl.compileShader(e),!this.gl.getShaderParameter(e,this.gl.COMPILE_STATUS))throw console.error("Shader compilation failed:",i),console.error(this.gl.getShaderInfoLog(e)),this.gl.deleteShader(e),new Error("Shader compilation failed");return e}setupBuffer(t){let i=new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]);this.buffer=this.gl.createBuffer(),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.buffer),this.gl.bufferData(this.gl.ARRAY_BUFFER,i,this.gl.STATIC_DRAW),this.gl.viewport(0,0,this.gl.drawingBufferWidth,this.gl.drawingBufferHeight),this.gl.enableVertexAttribArray(t),this.gl.vertexAttribPointer(t,2,this.gl.FLOAT,!1,0,0)}throttledHandleResize(){clearTimeout(this.resizeTimeout);let t=performance.now(),i=this.lastResizeTime+R-t;i<=0?(this.lastResizeTime=t,this.handleResize()):this.resizeTimeout=setTimeout(()=>this.throttledHandleResize(),i)}handleResize(){let t=window.devicePixelRatio||1,i=this.canvas.clientWidth*t,e=this.canvas.clientHeight*t;this.isInternalCanvas&&(this.canvas.width!==i||this.canvas.height!==e)&&(this.canvas.width=i,this.canvas.height=e),this.onResize?.(i,e)}addEventListeners(){let t=(e,r)=>{if(!this.uniforms.has("u_cursor"))return;let s=this.canvas.getBoundingClientRect();this.cursorPosition[0]=(e-s.left)/s.width,this.cursorPosition[1]=1-(r-s.top)/s.height,this.updateUniforms({u_cursor:this.cursorPosition})},i=(e,r,s)=>{if(this.uniforms.has("u_click")){if(this.isMouseDown=e,e){let o=this.canvas.getBoundingClientRect(),h=r,l=s;this.clickPosition[0]=(h-o.left)/o.width,this.clickPosition[1]=1-(l-o.top)/o.height}this.updateUniforms({u_click:[...this.clickPosition,this.isMouseDown?1:0]})}};this.eventListeners.set("mousemove",e=>{let r=e;this.isTouchDevice||t(r.clientX,r.clientY)}),this.eventListeners.set("mousedown",e=>{let r=e;this.isTouchDevice||r.button===0&&(this.isMouseDown=!0,i(!0,r.clientX,r.clientY))}),this.eventListeners.set("mouseup",e=>{let r=e;this.isTouchDevice||r.button===0&&i(!1)}),this.eventListeners.set("touchmove",e=>{let r=e;r.touches.length>0&&t(r.touches[0].clientX,r.touches[0].clientY)}),this.eventListeners.set("touchstart",e=>{let r=e;this.isTouchDevice=!0,r.touches.length>0&&(t(r.touches[0].clientX,r.touches[0].clientY),i(!0,r.touches[0].clientX,r.touches[0].clientY))}),this.eventListeners.set("touchend",e=>{e.touches.length===0&&i(!1)}),this.eventListeners.forEach((e,r)=>{this.canvas.addEventListener(r,e)})}updateResolution(){let t=[this.gl.drawingBufferWidth,this.gl.drawingBufferHeight];this.gl.viewport(0,0,...t),this.uniforms.has("u_resolution")?this.updateUniforms({u_resolution:t}):this.initializeUniform("u_resolution","float",t),this.hooks.get("updateResolution")?.forEach(i=>i.call(this))}reserveTextureUnit(t){let i=this.textures.get(t);if(i)return i.unitIndex;if(this.textureUnitPool.free.length>0)return this.textureUnitPool.free.pop();if(this.textureUnitPool.next>=this.textureUnitPool.max)throw new Error("Exceeded the available texture units for this device.");return this.textureUnitPool.next++}releaseTextureUnit(t){let i=this.textures.get(t);i&&this.textureUnitPool.free.push(i.unitIndex)}clearHistoryTextureLayers(t){if(!t.history)return;let i=new Uint8Array(t.width*t.height*4);this.gl.activeTexture(this.gl.TEXTURE0+t.unitIndex),this.gl.bindTexture(this.gl.TEXTURE_2D_ARRAY,t.texture),this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,!1);for(let e=0;e<t.history.depth;++e)this.gl.texSubImage3D(this.gl.TEXTURE_2D_ARRAY,0,0,0,e,t.width,t.height,1,this.gl.RGBA,this.gl.UNSIGNED_BYTE,i);this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,!0)}initializeUniform(t,i,e,r){let s=r?.arrayLength;if(this.uniforms.has(t))throw new Error(`${t} is already initialized.`);if(i!=="float"&&i!=="int")throw new Error(`Invalid uniform type: ${i}. Expected 'float' or 'int'.`);if(s&&!(Array.isArray(e)&&e.length===s))throw new Error(`${t} array length mismatch: must initialize with ${s} elements.`);let o=this.gl.getUniformLocation(this.program,t);if(!o&&s&&(o=this.gl.getUniformLocation(this.program,`${t}[0]`)),!o){console.debug(`${t} not found in fragment shader. Skipping initialization.`);return}let h=s?e[0]:e,l=Array.isArray(h)?h.length:1;this.uniforms.set(t,{type:i,length:l,location:o,arrayLength:s});try{this.updateUniforms({[t]:e})}catch(a){throw this.uniforms.delete(t),a}this.hooks.get("initializeUniform")?.forEach(a=>a.call(this,...arguments))}updateUniforms(t,i){Object.entries(t).forEach(([e,r])=>{let s=this.uniforms.get(e);if(!s){console.debug(`${e} not found in fragment shader. Skipping update.`);return}let o=`uniform${s.length}${s.type.charAt(0)}`;if(s.arrayLength){if(!Array.isArray(r))throw new Error(`${e} is an array, but the value passed to updateUniforms is not an array.`);let h=r.length;if(!h)return;if(h>s.arrayLength)throw new Error(`${e} received ${h} values, but maximum length is ${s.arrayLength}.`);if(r.some(u=>(Array.isArray(u)?u.length:1)!==s.length))throw new Error(`Tried to update ${e} with some elements that are not length ${s.length}.`);let l=new(s.type==="float"?Float32Array:Int32Array)(r.flat()),a=s.location;if(i?.startIndex){let u=this.gl.getUniformLocation(this.program,`${e}[${i.startIndex}]`);if(!u)throw new Error(`${e}[${i.startIndex}] not found in fragment shader. Did you pass an invalid startIndex?`);a=u}this.gl[o+"v"](s.location,l)}else{if(Array.isArray(r)||(r=[r]),r.length!==s.length)throw new Error(`Invalid uniform value length: ${r.length}. Expected ${s.length}.`);this.gl[o](s.location,...r)}}),this.hooks.get("updateUniforms")?.forEach(e=>e.call(this,...arguments))}createTexture(t,i,e){let{width:r,height:s}=i,o=i.history?.depth??0,h=this.gl.createTexture();if(!h)throw new Error("Failed to create texture");if(typeof e!="number")try{e=this.reserveTextureUnit(t)}catch(u){throw this.gl.deleteTexture(h),u}let l=o>0,a=l?this.gl.TEXTURE_2D_ARRAY:this.gl.TEXTURE_2D;return this.gl.activeTexture(this.gl.TEXTURE0+e),this.gl.bindTexture(a,h),this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,!0),this.gl.texParameteri(a,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(a,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(a,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR),this.gl.texParameteri(a,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),l&&this.gl.texStorage3D(a,1,this.gl.RGBA8,r,s,o),{texture:h,unitIndex:e}}_initializeTexture(t,i,e){if(this.textures.has(t))throw new Error(`Texture '${g(t)}' is already initialized.`);let r=e?.history??0,{width:s,height:o}=p(i);if(!s||!o)throw new Error("Texture source must have valid dimensions");let h={width:s,height:o};r>0&&(h.history={depth:r,writeIndex:0});let{texture:l,unitIndex:a}=this.createTexture(t,h),u={texture:l,unitIndex:a,...h};r>0&&(this.initializeUniform(`${g(t)}FrameOffset`,"int",0),this.clearHistoryTextureLayers(u)),this.textures.set(t,u),this.updateTexture(t,i);let d=this.gl.getUniformLocation(this.program,g(t));d&&this.gl.uniform1i(d,a)}initializeTexture(t,i,e){this._initializeTexture(t,i,e),this.hooks.get("initializeTexture")?.forEach(r=>r.call(this,...arguments))}updateTextures(t){this.hooks.get("updateTextures")?.forEach(i=>i.call(this,...arguments)),Object.entries(t).forEach(([i,e])=>{this.updateTexture(i,e)})}updateTexture(t,i){let e=this.textures.get(t);if(!e)throw new Error(`Texture '${g(t)}' is not initialized.`);let{width:r,height:s}=p(i);if(e.width!==r||e.height!==s){this.gl.deleteTexture(e.texture),e.width=r,e.height=s;let{texture:o}=this.createTexture(t,e,e.unitIndex);e.texture=o,e.history&&(e.history.writeIndex=0,this.clearHistoryTextureLayers(e))}if(e.history){let o=t===c;this.gl.activeTexture(this.gl.TEXTURE0+e.unitIndex),this.gl.bindTexture(this.gl.TEXTURE_2D_ARRAY,e.texture),o?(this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,!1),this.gl.copyTexSubImage3D(this.gl.TEXTURE_2D_ARRAY,0,0,0,e.history.writeIndex,0,0,r,s),this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,!0)):this.gl.texSubImage3D(this.gl.TEXTURE_2D_ARRAY,0,0,0,e.history.writeIndex,r,s,1,this.gl.RGBA,this.gl.UNSIGNED_BYTE,i);let h=`${g(t)}FrameOffset`;this.updateUniforms({[h]:e.history.writeIndex}),e.history.writeIndex=(e.history.writeIndex+1)%e.history.depth}else this.gl.activeTexture(this.gl.TEXTURE0+e.unitIndex),this.gl.bindTexture(this.gl.TEXTURE_2D,e.texture),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,this.gl.RGBA,this.gl.UNSIGNED_BYTE,i)}draw(){let t=this.gl;t.clear(t.COLOR_BUFFER_BIT),t.drawArrays(t.TRIANGLES,0,6)}step(t){this.uniforms.has("u_time")&&this.updateUniforms({u_time:t}),this.uniforms.has("u_frame")&&this.updateUniforms({u_frame:this.frame}),this.draw(),this.textures.get(c)&&this.updateTexture(c,this.canvas),this.hooks.get("step")?.forEach(i=>i.call(this,t,this.frame)),++this.frame}play(t){this.pause();let i=e=>{e=(e-this.startTime)/1e3,this.step(e),this.animationFrameId=requestAnimationFrame(i),t&&t(e,this.frame)};this.animationFrameId=requestAnimationFrame(i)}pause(){this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null)}reset(){this.frame=0,this.startTime=performance.now(),this.textures.forEach(t=>{t.history&&(t.history.writeIndex=0,this.clearHistoryTextureLayers(t))}),this.hooks.get("reset")?.forEach(t=>t.call(this))}destroy(){this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null),this.resolutionObserver.disconnect(),this.resizeObserver.disconnect(),this.eventListeners.forEach((t,i)=>{this.canvas.removeEventListener(i,t)}),this.program&&this.gl.deleteProgram(this.program),this.textures.forEach(t=>{this.gl.deleteTexture(t.texture)}),this.textureUnitPool.free=[],this.textureUnitPool.next=0,this.buffer&&(this.gl.deleteBuffer(this.buffer),this.buffer=null),this.hooks.get("destroy")?.forEach(t=>t.call(this)),this.isInternalCanvas&&this.canvas.remove()}},_=m;
|
|
10
|
+
`)}function x(n){if("data"in n)return{width:n.width,height:n.height};if(n instanceof HTMLVideoElement)return{width:n.videoWidth,height:n.videoHeight};if(n instanceof HTMLCanvasElement){let t=n.getContext("webgl2");return t?{width:t.drawingBufferWidth,height:t.drawingBufferHeight}:{width:n.width,height:n.height}}return{width:n.naturalWidth??n.width,height:n.naturalHeight??n.height}}function c(n){return typeof n=="symbol"?n.description??"":n}var p=class{isInternalCanvas=!1;isTouchDevice=!1;gl;fragmentShaderSrc;uniforms=new Map;textures=new Map;textureUnitPool;buffer=null;program=null;animationFrameId;resolutionObserver;resizeObserver;resizeTimeout=null;lastResizeTime=-1/0;eventListeners=new Map;frame=0;startTime=0;cursorPosition=[.5,.5];clickPosition=[.5,.5];isMouseDown=!1;canvas;onResize;hooks=new Map;historyDepth;debug;constructor(t,i={}){if(this.canvas=i.canvas||document.createElement("canvas"),i.canvas||(this.isInternalCanvas=!0,this.canvas.style.position="fixed",this.canvas.style.inset="0",this.canvas.style.height="100dvh",this.canvas.style.width="100dvw",document.body.appendChild(this.canvas)),this.gl=this.canvas.getContext("webgl2",{antialias:!1}),!this.gl)throw new Error("WebGL2 not supported. Please use a browser that supports WebGL2.");this.textureUnitPool={free:[],next:0,max:this.gl.getParameter(this.gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS)},this.historyDepth=i.history??0,this.debug=i.debug??(typeof process<"u"&&!1),this.animationFrameId=null,this.resolutionObserver=new MutationObserver(()=>this.updateResolution()),this.resizeObserver=new ResizeObserver(()=>this.throttledHandleResize());let e=[];if(i.plugins){let r={gl:this.gl,uniforms:this.uniforms,textures:this.textures,canvas:this.canvas,reserveTextureUnit:this.reserveTextureUnit.bind(this),releaseTextureUnit:this.releaseTextureUnit.bind(this),injectGLSL:s=>{e.push(s)}};Object.defineProperty(r,"program",{get:()=>this.program,enumerable:!0,configurable:!0}),i.plugins.forEach(s=>s(this,r))}this.fragmentShaderSrc=L(t,e),this.init(),this.addEventListeners()}registerHook(t,i){this.hooks.has(t)||this.hooks.set(t,[]),this.hooks.get(t).push(i)}init(){let t=R;if(this.program=this.gl.createProgram(),!this.program)throw new Error("Failed to create WebGL program");let i=this.createShader(this.gl.VERTEX_SHADER,t),e=this.createShader(this.gl.FRAGMENT_SHADER,this.fragmentShaderSrc);if(this.gl.attachShader(this.program,i),this.gl.attachShader(this.program,e),this.gl.linkProgram(this.program),this.gl.deleteShader(i),this.gl.deleteShader(e),!this.gl.getProgramParameter(this.program,this.gl.LINK_STATUS))throw console.error("Program link error:",this.gl.getProgramInfoLog(this.program)),this.gl.deleteProgram(this.program),new Error("Failed to link WebGL program");let r=this.gl.getAttribLocation(this.program,"aPosition");this.setupBuffer(r),this.gl.useProgram(this.program),this.resolutionObserver.observe(this.canvas,{attributes:!0,attributeFilter:["width","height"]}),this.resizeObserver.observe(this.canvas),this.isInternalCanvas||this.updateResolution(),this.initializeUniform("u_cursor","float",this.cursorPosition),this.initializeUniform("u_click","float",[...this.clickPosition,this.isMouseDown?1:0]),this.initializeUniform("u_time","float",0),this.initializeUniform("u_frame","int",0),this.historyDepth>0&&this._initializeTexture(d,this.canvas,{history:this.historyDepth}),this.hooks.get("init")?.forEach(s=>s.call(this))}createShader(t,i){let e=this.gl.createShader(t);if(this.gl.shaderSource(e,i),this.gl.compileShader(e),!this.gl.getShaderParameter(e,this.gl.COMPILE_STATUS))throw console.error("Shader compilation failed:",i),console.error(this.gl.getShaderInfoLog(e)),this.gl.deleteShader(e),new Error("Shader compilation failed");return e}setupBuffer(t){let i=new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]);this.buffer=this.gl.createBuffer(),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.buffer),this.gl.bufferData(this.gl.ARRAY_BUFFER,i,this.gl.STATIC_DRAW),this.gl.viewport(0,0,this.gl.drawingBufferWidth,this.gl.drawingBufferHeight),this.gl.enableVertexAttribArray(t),this.gl.vertexAttribPointer(t,2,this.gl.FLOAT,!1,0,0)}throttledHandleResize(){clearTimeout(this.resizeTimeout);let t=performance.now(),i=this.lastResizeTime+U-t;i<=0?(this.lastResizeTime=t,this.handleResize()):this.resizeTimeout=setTimeout(()=>this.throttledHandleResize(),i)}handleResize(){let t=window.devicePixelRatio||1,i=this.canvas.clientWidth*t,e=this.canvas.clientHeight*t;this.isInternalCanvas&&(this.canvas.width!==i||this.canvas.height!==e)&&(this.canvas.width=i,this.canvas.height=e),this.onResize?.(i,e)}addEventListeners(){let t=(e,r)=>{if(!this.uniforms.has("u_cursor"))return;let s=this.canvas.getBoundingClientRect();this.cursorPosition[0]=(e-s.left)/s.width,this.cursorPosition[1]=1-(r-s.top)/s.height,this.updateUniforms({u_cursor:this.cursorPosition})},i=(e,r,s)=>{if(this.uniforms.has("u_click")){if(this.isMouseDown=e,e){let o=this.canvas.getBoundingClientRect(),h=r,a=s;this.clickPosition[0]=(h-o.left)/o.width,this.clickPosition[1]=1-(a-o.top)/o.height}this.updateUniforms({u_click:[...this.clickPosition,this.isMouseDown?1:0]})}};this.eventListeners.set("mousemove",e=>{let r=e;this.isTouchDevice||t(r.clientX,r.clientY)}),this.eventListeners.set("mousedown",e=>{let r=e;this.isTouchDevice||r.button===0&&(this.isMouseDown=!0,i(!0,r.clientX,r.clientY))}),this.eventListeners.set("mouseup",e=>{let r=e;this.isTouchDevice||r.button===0&&i(!1)}),this.eventListeners.set("touchmove",e=>{let r=e;r.touches.length>0&&t(r.touches[0].clientX,r.touches[0].clientY)}),this.eventListeners.set("touchstart",e=>{let r=e;this.isTouchDevice=!0,r.touches.length>0&&(t(r.touches[0].clientX,r.touches[0].clientY),i(!0,r.touches[0].clientX,r.touches[0].clientY))}),this.eventListeners.set("touchend",e=>{e.touches.length===0&&i(!1)}),this.eventListeners.forEach((e,r)=>{this.canvas.addEventListener(r,e)})}updateResolution(){let t=[this.gl.drawingBufferWidth,this.gl.drawingBufferHeight];this.gl.viewport(0,0,...t),this.uniforms.has("u_resolution")?this.updateUniforms({u_resolution:t}):this.initializeUniform("u_resolution","float",t),this.hooks.get("updateResolution")?.forEach(i=>i.call(this))}reserveTextureUnit(t){let i=this.textures.get(t);if(i)return i.unitIndex;if(this.textureUnitPool.free.length>0)return this.textureUnitPool.free.pop();if(this.textureUnitPool.next>=this.textureUnitPool.max)throw new Error("Exceeded the available texture units for this device.");return this.textureUnitPool.next++}releaseTextureUnit(t){let i=this.textures.get(t);i&&this.textureUnitPool.free.push(i.unitIndex)}clearHistoryTextureLayers(t){if(!t.history)return;let i=t.options?.type??this.gl.UNSIGNED_BYTE,e=i===this.gl.FLOAT?new Float32Array(t.width*t.height*4):new Uint8Array(t.width*t.height*4);this.gl.activeTexture(this.gl.TEXTURE0+t.unitIndex),this.gl.bindTexture(this.gl.TEXTURE_2D_ARRAY,t.texture);for(let r=0;r<t.history.depth;++r)this.gl.texSubImage3D(this.gl.TEXTURE_2D_ARRAY,0,0,0,r,t.width,t.height,1,t.options?.format??this.gl.RGBA,i,e)}initializeUniform(t,i,e,r){let s=r?.arrayLength;if(this.uniforms.has(t))throw new Error(`${t} is already initialized.`);if(i!=="float"&&i!=="int")throw new Error(`Invalid uniform type: ${i}. Expected 'float' or 'int'.`);if(s&&!(Array.isArray(e)&&e.length===s))throw new Error(`${t} array length mismatch: must initialize with ${s} elements.`);let o=this.gl.getUniformLocation(this.program,t);if(!o&&s&&(o=this.gl.getUniformLocation(this.program,`${t}[0]`)),!o){this.log(`${t} not found in fragment shader. Skipping initialization.`);return}let h=s?e[0]:e,a=Array.isArray(h)?h.length:1;this.uniforms.set(t,{type:i,length:a,location:o,arrayLength:s});try{this.updateUniforms({[t]:e})}catch(u){throw this.uniforms.delete(t),u}this.hooks.get("initializeUniform")?.forEach(u=>u.call(this,...arguments))}log(...t){this.debug&&console.debug(...t)}updateUniforms(t,i){Object.entries(t).forEach(([e,r])=>{let s=this.uniforms.get(e);if(!s){this.log(`${e} not found in fragment shader. Skipping update.`);return}let o=`uniform${s.length}${s.type.charAt(0)}`;if(s.arrayLength){if(!Array.isArray(r))throw new Error(`${e} is an array, but the value passed to updateUniforms is not an array.`);let h=r.length;if(!h)return;if(h>s.arrayLength)throw new Error(`${e} received ${h} values, but maximum length is ${s.arrayLength}.`);if(r.some(l=>(Array.isArray(l)?l.length:1)!==s.length))throw new Error(`Tried to update ${e} with some elements that are not length ${s.length}.`);let a=new(s.type==="float"?Float32Array:Int32Array)(r.flat()),u=s.location;if(i?.startIndex){let l=this.gl.getUniformLocation(this.program,`${e}[${i.startIndex}]`);if(!l)throw new Error(`${e}[${i.startIndex}] not found in fragment shader. Did you pass an invalid startIndex?`);u=l}this.gl[o+"v"](s.location,a)}else{if(Array.isArray(r)||(r=[r]),r.length!==s.length)throw new Error(`Invalid uniform value length: ${r.length}. Expected ${s.length}.`);this.gl[o](s.location,...r)}}),this.hooks.get("updateUniforms")?.forEach(e=>e.call(this,...arguments))}createTexture(t,i,e){let{width:r,height:s}=i,o=i.history?.depth??0,h=this.gl.createTexture();if(!h)throw new Error("Failed to create texture");let a=e?.unitIndex;if(typeof a!="number")try{a=this.reserveTextureUnit(t)}catch(g){throw this.gl.deleteTexture(h),g}let u=o>0,l=u?this.gl.TEXTURE_2D_ARRAY:this.gl.TEXTURE_2D;if(this.gl.activeTexture(this.gl.TEXTURE0+a),this.gl.bindTexture(l,h),this.gl.texParameteri(l,this.gl.TEXTURE_WRAP_S,e?.wrapS??this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(l,this.gl.TEXTURE_WRAP_T,e?.wrapT??this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(l,this.gl.TEXTURE_MIN_FILTER,e?.minFilter??this.gl.LINEAR),this.gl.texParameteri(l,this.gl.TEXTURE_MAG_FILTER,e?.magFilter??this.gl.LINEAR),u){let g=e?.type??this.gl.UNSIGNED_BYTE,m=e?.internalFormat??(g===this.gl.FLOAT?this.gl.RGBA32F:this.gl.RGBA8);this.gl.texStorage3D(l,1,m,r,s,o)}return{texture:h,unitIndex:a}}_initializeTexture(t,i,e){if(this.textures.has(t))throw new Error(`Texture '${c(t)}' is already initialized.`);let{history:r=0,...s}=e??{},{width:o,height:h}=x(i);if(!o||!h)throw new Error("Texture source must have valid dimensions");let a={width:o,height:h};r>0&&(a.history={depth:r,writeIndex:0});let{texture:u,unitIndex:l}=this.createTexture(t,a,s),g={texture:u,unitIndex:l,...a,options:s};r>0&&(this.initializeUniform(`${c(t)}FrameOffset`,"int",0),this.clearHistoryTextureLayers(g)),this.textures.set(t,g),this.updateTexture(t,i);let m=this.gl.getUniformLocation(this.program,c(t));m&&this.gl.uniform1i(m,l)}initializeTexture(t,i,e){this._initializeTexture(t,i,e),this.hooks.get("initializeTexture")?.forEach(r=>r.call(this,...arguments))}updateTextures(t){this.hooks.get("updateTextures")?.forEach(i=>i.call(this,...arguments)),Object.entries(t).forEach(([i,e])=>{this.updateTexture(i,e)})}updateTexture(t,i){let e=this.textures.get(t);if(!e)throw new Error(`Texture '${c(t)}' is not initialized.`);let{width:r,height:s}=x(i);if(!r||!s)return;let o="isPartial"in i&&i.isPartial;if(!o&&(e.width!==r||e.height!==s)){this.gl.deleteTexture(e.texture),e.width=r,e.height=s;let{texture:a}=this.createTexture(t,e,{...e.options,unitIndex:e.unitIndex});e.texture=a,e.history&&(e.history.writeIndex=0,this.clearHistoryTextureLayers(e))}let h=!e.options?.preserveY;if(e.history){let a=t===d;this.gl.activeTexture(this.gl.TEXTURE0+e.unitIndex),this.gl.bindTexture(this.gl.TEXTURE_2D_ARRAY,e.texture),a?(this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,!1),this.gl.copyTexSubImage3D(this.gl.TEXTURE_2D_ARRAY,0,0,0,e.history.writeIndex,0,0,r,s)):(this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,h),this.gl.texSubImage3D(this.gl.TEXTURE_2D_ARRAY,0,0,0,e.history.writeIndex,r,s,1,e.options?.format??this.gl.RGBA,e.options?.type??this.gl.UNSIGNED_BYTE,i.data??i));let u=`${c(t)}FrameOffset`;this.updateUniforms({[u]:e.history.writeIndex}),e.history.writeIndex=(e.history.writeIndex+1)%e.history.depth}else{this.gl.activeTexture(this.gl.TEXTURE0+e.unitIndex),this.gl.bindTexture(this.gl.TEXTURE_2D,e.texture),this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,h);let a=e.options?.format??this.gl.RGBA,u=e.options?.type??this.gl.UNSIGNED_BYTE;if(o)this.gl.texSubImage2D(this.gl.TEXTURE_2D,0,i.x??0,i.y??0,r,s,a,u,i.data);else{let l="data"in i&&i.data,g=e.options?.internalFormat??(l?u===this.gl.FLOAT?this.gl.RGBA32F:this.gl.RGBA8:this.gl.RGBA);this.gl.texImage2D(this.gl.TEXTURE_2D,0,g,r,s,0,a,u,i.data??i)}}}draw(){let t=this.gl;t.clear(t.COLOR_BUFFER_BIT),t.drawArrays(t.TRIANGLES,0,6)}step(t){this.uniforms.has("u_time")&&this.updateUniforms({u_time:t}),this.uniforms.has("u_frame")&&this.updateUniforms({u_frame:this.frame}),this.draw(),this.textures.get(d)&&this.updateTexture(d,this.canvas),this.hooks.get("step")?.forEach(i=>i.call(this,t,this.frame)),++this.frame}play(t){this.pause();let i=e=>{e=(e-this.startTime)/1e3,this.step(e),this.animationFrameId=requestAnimationFrame(i),t&&t(e,this.frame)};this.animationFrameId=requestAnimationFrame(i)}pause(){this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null)}reset(){this.frame=0,this.startTime=performance.now(),this.textures.forEach(t=>{t.history&&(t.history.writeIndex=0,this.clearHistoryTextureLayers(t))}),this.hooks.get("reset")?.forEach(t=>t.call(this))}destroy(){this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null),this.resolutionObserver.disconnect(),this.resizeObserver.disconnect(),this.eventListeners.forEach((t,i)=>{this.canvas.removeEventListener(i,t)}),this.program&&this.gl.deleteProgram(this.program),this.textures.forEach(t=>{this.gl.deleteTexture(t.texture)}),this.textureUnitPool.free=[],this.textureUnitPool.next=0,this.buffer&&(this.gl.deleteBuffer(this.buffer),this.buffer=null),this.hooks.get("destroy")?.forEach(t=>t.call(this)),this.isInternalCanvas&&this.canvas.remove()}},_=p;
|
|
11
11
|
//# sourceMappingURL=index.js.map
|