@motion-core/motion-gpu 0.4.2 → 0.6.0

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 (121) hide show
  1. package/README.md +99 -0
  2. package/dist/advanced.js +3 -1
  3. package/dist/core/advanced.js +3 -1
  4. package/dist/core/compute-bindgroup-cache.d.ts +13 -0
  5. package/dist/core/compute-bindgroup-cache.d.ts.map +1 -0
  6. package/dist/core/compute-bindgroup-cache.js +45 -0
  7. package/dist/core/compute-bindgroup-cache.js.map +1 -0
  8. package/dist/core/compute-shader.d.ts +135 -0
  9. package/dist/core/compute-shader.d.ts.map +1 -0
  10. package/dist/core/compute-shader.js +238 -0
  11. package/dist/core/compute-shader.js.map +1 -0
  12. package/dist/core/error-diagnostics.d.ts +8 -1
  13. package/dist/core/error-diagnostics.d.ts.map +1 -1
  14. package/dist/core/error-diagnostics.js +7 -3
  15. package/dist/core/error-diagnostics.js.map +1 -1
  16. package/dist/core/error-report.d.ts +1 -1
  17. package/dist/core/error-report.d.ts.map +1 -1
  18. package/dist/core/error-report.js +82 -1
  19. package/dist/core/error-report.js.map +1 -1
  20. package/dist/core/frame-registry.d.ts.map +1 -1
  21. package/dist/core/frame-registry.js +1 -1
  22. package/dist/core/frame-registry.js.map +1 -1
  23. package/dist/core/index.d.ts +5 -2
  24. package/dist/core/index.d.ts.map +1 -1
  25. package/dist/core/index.js +3 -1
  26. package/dist/core/material-preprocess.d.ts.map +1 -1
  27. package/dist/core/material-preprocess.js +5 -3
  28. package/dist/core/material-preprocess.js.map +1 -1
  29. package/dist/core/material.d.ts +22 -6
  30. package/dist/core/material.d.ts.map +1 -1
  31. package/dist/core/material.js +32 -4
  32. package/dist/core/material.js.map +1 -1
  33. package/dist/core/render-graph.d.ts +7 -3
  34. package/dist/core/render-graph.d.ts.map +1 -1
  35. package/dist/core/render-graph.js +22 -6
  36. package/dist/core/render-graph.js.map +1 -1
  37. package/dist/core/renderer.d.ts.map +1 -1
  38. package/dist/core/renderer.js +489 -29
  39. package/dist/core/renderer.js.map +1 -1
  40. package/dist/core/runtime-loop.d.ts +2 -2
  41. package/dist/core/runtime-loop.d.ts.map +1 -1
  42. package/dist/core/runtime-loop.js +74 -14
  43. package/dist/core/runtime-loop.js.map +1 -1
  44. package/dist/core/shader.d.ts +16 -3
  45. package/dist/core/shader.d.ts.map +1 -1
  46. package/dist/core/shader.js +22 -2
  47. package/dist/core/shader.js.map +1 -1
  48. package/dist/core/storage-buffers.d.ts +37 -0
  49. package/dist/core/storage-buffers.d.ts.map +1 -0
  50. package/dist/core/storage-buffers.js +95 -0
  51. package/dist/core/storage-buffers.js.map +1 -0
  52. package/dist/core/texture-loader.d.ts.map +1 -1
  53. package/dist/core/texture-loader.js +4 -0
  54. package/dist/core/texture-loader.js.map +1 -1
  55. package/dist/core/textures.d.ts +16 -0
  56. package/dist/core/textures.d.ts.map +1 -1
  57. package/dist/core/textures.js +8 -2
  58. package/dist/core/textures.js.map +1 -1
  59. package/dist/core/types.d.ts +146 -4
  60. package/dist/core/types.d.ts.map +1 -1
  61. package/dist/index.js +3 -1
  62. package/dist/passes/ComputePass.d.ts +83 -0
  63. package/dist/passes/ComputePass.d.ts.map +1 -0
  64. package/dist/passes/ComputePass.js +92 -0
  65. package/dist/passes/ComputePass.js.map +1 -0
  66. package/dist/passes/PingPongComputePass.d.ts +104 -0
  67. package/dist/passes/PingPongComputePass.d.ts.map +1 -0
  68. package/dist/passes/PingPongComputePass.js +132 -0
  69. package/dist/passes/PingPongComputePass.js.map +1 -0
  70. package/dist/passes/ShaderPass.d.ts.map +1 -1
  71. package/dist/passes/ShaderPass.js +2 -1
  72. package/dist/passes/ShaderPass.js.map +1 -1
  73. package/dist/passes/index.d.ts +2 -0
  74. package/dist/passes/index.d.ts.map +1 -1
  75. package/dist/passes/index.js +3 -1
  76. package/dist/react/FragCanvas.d.ts +2 -2
  77. package/dist/react/FragCanvas.d.ts.map +1 -1
  78. package/dist/react/FragCanvas.js.map +1 -1
  79. package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -1
  80. package/dist/react/MotionGPUErrorOverlay.js +123 -20
  81. package/dist/react/MotionGPUErrorOverlay.js.map +1 -1
  82. package/dist/react/advanced.js +3 -1
  83. package/dist/react/index.d.ts +5 -2
  84. package/dist/react/index.d.ts.map +1 -1
  85. package/dist/react/index.js +3 -1
  86. package/dist/svelte/FragCanvas.svelte +2 -2
  87. package/dist/svelte/FragCanvas.svelte.d.ts +2 -2
  88. package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -1
  89. package/dist/svelte/MotionGPUErrorOverlay.svelte +137 -7
  90. package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -1
  91. package/dist/svelte/advanced.js +3 -1
  92. package/dist/svelte/index.d.ts +5 -2
  93. package/dist/svelte/index.d.ts.map +1 -1
  94. package/dist/svelte/index.js +3 -1
  95. package/package.json +1 -1
  96. package/src/lib/core/compute-bindgroup-cache.ts +73 -0
  97. package/src/lib/core/compute-shader.ts +412 -0
  98. package/src/lib/core/error-diagnostics.ts +29 -4
  99. package/src/lib/core/error-report.ts +155 -1
  100. package/src/lib/core/frame-registry.ts +2 -1
  101. package/src/lib/core/index.ts +18 -1
  102. package/src/lib/core/material-preprocess.ts +17 -6
  103. package/src/lib/core/material.ts +103 -21
  104. package/src/lib/core/render-graph.ts +39 -9
  105. package/src/lib/core/renderer.ts +768 -48
  106. package/src/lib/core/runtime-loop.ts +116 -16
  107. package/src/lib/core/shader.ts +58 -4
  108. package/src/lib/core/storage-buffers.ts +142 -0
  109. package/src/lib/core/texture-loader.ts +6 -0
  110. package/src/lib/core/textures.ts +29 -2
  111. package/src/lib/core/types.ts +165 -4
  112. package/src/lib/passes/ComputePass.ts +136 -0
  113. package/src/lib/passes/PingPongComputePass.ts +180 -0
  114. package/src/lib/passes/ShaderPass.ts +2 -1
  115. package/src/lib/passes/index.ts +6 -0
  116. package/src/lib/react/FragCanvas.tsx +3 -3
  117. package/src/lib/react/MotionGPUErrorOverlay.tsx +137 -5
  118. package/src/lib/react/index.ts +18 -1
  119. package/src/lib/svelte/FragCanvas.svelte +2 -2
  120. package/src/lib/svelte/MotionGPUErrorOverlay.svelte +137 -7
  121. package/src/lib/svelte/index.ts +18 -1
@@ -199,6 +199,26 @@ export interface TextureDefinition {
199
199
  * V axis address mode.
200
200
  */
201
201
  addressModeV?: GPUAddressMode;
202
+ /**
203
+ * When true, this texture is also writable by compute passes.
204
+ */
205
+ storage?: boolean;
206
+ /**
207
+ * Required when storage is true. Must be a storage-compatible format.
208
+ */
209
+ format?: GPUTextureFormat;
210
+ /**
211
+ * Explicit texture width. Required for storage textures without a source.
212
+ */
213
+ width?: number;
214
+ /**
215
+ * Explicit texture height. Required for storage textures without a source.
216
+ */
217
+ height?: number;
218
+ /**
219
+ * When true, texture is visible (sampled) in fragment shader. Default: true.
220
+ */
221
+ fragmentVisible?: boolean;
202
222
  }
203
223
 
204
224
  /**
@@ -211,6 +231,56 @@ export type TextureDefinitionMap<TKey extends string = string> = Record<TKey, Te
211
231
  */
212
232
  export type TextureMap<TKey extends string = string> = Record<TKey, TextureValue>;
213
233
 
234
+ // ── Storage buffer types ────────────────────────────────────────────────────
235
+
236
+ /**
237
+ * Access mode for storage buffers in compute shaders.
238
+ */
239
+ export type StorageBufferAccess = 'read' | 'read-write';
240
+
241
+ /**
242
+ * WGSL storage buffer element type.
243
+ */
244
+ export type StorageBufferType =
245
+ | 'array<f32>'
246
+ | 'array<vec2f>'
247
+ | 'array<vec3f>'
248
+ | 'array<vec4f>'
249
+ | 'array<u32>'
250
+ | 'array<i32>'
251
+ | 'array<vec4u>'
252
+ | 'array<vec4i>';
253
+
254
+ /**
255
+ * Definition of a single storage buffer resource.
256
+ */
257
+ export interface StorageBufferDefinition {
258
+ /**
259
+ * Buffer size in bytes. Must be > 0 and multiple of 4.
260
+ */
261
+ size: number;
262
+ /**
263
+ * WGSL type annotation for codegen.
264
+ */
265
+ type: StorageBufferType;
266
+ /**
267
+ * Access mode in compute shader. Default: 'read-write'.
268
+ */
269
+ access?: StorageBufferAccess;
270
+ /**
271
+ * Initial data uploaded on creation.
272
+ */
273
+ initialData?: Float32Array | Uint32Array | Int32Array;
274
+ }
275
+
276
+ /**
277
+ * Map of named storage buffer definitions.
278
+ */
279
+ export type StorageBufferDefinitionMap<TKey extends string = string> = Record<
280
+ TKey,
281
+ StorageBufferDefinition
282
+ >;
283
+
214
284
  /**
215
285
  * Output color space requested for final canvas presentation.
216
286
  */
@@ -368,6 +438,40 @@ export interface RenderPassContext extends Required<RenderPassFlags> {
368
438
  }) => GPURenderPassEncoder;
369
439
  }
370
440
 
441
+ /**
442
+ * Context provided to compute pass render calls.
443
+ */
444
+ export interface ComputePassContext {
445
+ /**
446
+ * Active GPU device.
447
+ */
448
+ device: GPUDevice;
449
+ /**
450
+ * Shared command encoder for this frame.
451
+ */
452
+ commandEncoder: GPUCommandEncoder;
453
+ /**
454
+ * Frame width in pixels.
455
+ */
456
+ width: number;
457
+ /**
458
+ * Frame height in pixels.
459
+ */
460
+ height: number;
461
+ /**
462
+ * Frame timestamp in seconds.
463
+ */
464
+ time: number;
465
+ /**
466
+ * Frame delta in seconds.
467
+ */
468
+ delta: number;
469
+ /**
470
+ * Begins a compute pass on the shared command encoder.
471
+ */
472
+ beginComputePass: () => GPUComputePassEncoder;
473
+ }
474
+
371
475
  /**
372
476
  * Formal render pass contract used by MotionGPU render graph.
373
477
  */
@@ -402,6 +506,22 @@ export interface RenderPass extends RenderPassFlags {
402
506
  dispose?: () => void;
403
507
  }
404
508
 
509
+ /**
510
+ * Minimal interface for compute passes in the render graph.
511
+ * Compute passes do not participate in slot routing.
512
+ */
513
+ export interface ComputePassLike {
514
+ readonly isCompute: true;
515
+ enabled?: boolean;
516
+ setSize?: (width: number, height: number) => void;
517
+ dispose?: () => void;
518
+ }
519
+
520
+ /**
521
+ * Union type for all pass types accepted by the render graph.
522
+ */
523
+ export type AnyPass = RenderPass | ComputePassLike;
524
+
405
525
  /**
406
526
  * Frame submission strategy for the scheduler.
407
527
  */
@@ -432,6 +552,14 @@ export interface FrameState {
432
552
  * Sets a texture value for current/next frame.
433
553
  */
434
554
  setTexture: (name: string, value: TextureValue) => void;
555
+ /**
556
+ * Writes data to a named storage buffer.
557
+ */
558
+ writeStorageBuffer: (name: string, data: ArrayBufferView, options?: { offset?: number }) => void;
559
+ /**
560
+ * Async readback of storage buffer data.
561
+ */
562
+ readStorageBuffer: (name: string) => Promise<ArrayBuffer>;
435
563
  /**
436
564
  * Invalidates frame for on-demand rendering.
437
565
  */
@@ -457,6 +585,18 @@ export interface FrameState {
457
585
  /**
458
586
  * Internal renderer construction options resolved from material/context state.
459
587
  */
588
+ /**
589
+ * Pending storage buffer write queued from FrameState.
590
+ */
591
+ export interface PendingStorageWrite {
592
+ /** Storage buffer name. */
593
+ name: string;
594
+ /** Data to write. */
595
+ data: ArrayBufferView;
596
+ /** Byte offset into the storage buffer. */
597
+ offset: number;
598
+ }
599
+
460
600
  export interface RendererOptions {
461
601
  /**
462
602
  * Target canvas.
@@ -513,22 +653,34 @@ export interface RendererOptions {
513
653
  * Texture definitions by key.
514
654
  */
515
655
  textureDefinitions: TextureDefinitionMap;
656
+ /**
657
+ * Sorted storage buffer keys.
658
+ */
659
+ storageBufferKeys?: string[];
660
+ /**
661
+ * Storage buffer definitions by key.
662
+ */
663
+ storageBufferDefinitions?: Record<string, import('./types.js').StorageBufferDefinition>;
664
+ /**
665
+ * Sorted storage texture keys (textures with storage:true).
666
+ */
667
+ storageTextureKeys?: string[];
516
668
  /**
517
669
  * Static render target definitions.
518
670
  */
519
671
  renderTargets?: RenderTargetDefinitionMap;
520
672
  /**
521
- * Static render passes.
673
+ * Static render and compute passes.
522
674
  */
523
- passes?: RenderPass[];
675
+ passes?: AnyPass[];
524
676
  /**
525
677
  * Dynamic render targets provider.
526
678
  */
527
679
  getRenderTargets?: () => RenderTargetDefinitionMap | undefined;
528
680
  /**
529
- * Dynamic render passes provider.
681
+ * Dynamic render and compute passes provider.
530
682
  */
531
- getPasses?: () => RenderPass[] | undefined;
683
+ getPasses?: () => AnyPass[] | undefined;
532
684
  /**
533
685
  * Requested output color space.
534
686
  */
@@ -574,7 +726,16 @@ export interface Renderer {
574
726
  width: number;
575
727
  height: number;
576
728
  };
729
+ pendingStorageWrites?: PendingStorageWrite[] | undefined;
577
730
  }) => void;
731
+ /**
732
+ * Returns the GPU buffer for a named storage buffer, if allocated.
733
+ */
734
+ getStorageBuffer?: (name: string) => GPUBuffer | undefined;
735
+ /**
736
+ * Returns the active GPU device (for readback operations).
737
+ */
738
+ getDevice?: () => GPUDevice;
578
739
  /**
579
740
  * Releases GPU resources and subscriptions.
580
741
  */
@@ -0,0 +1,136 @@
1
+ import { assertComputeContract, extractWorkgroupSize } from '../core/compute-shader.js';
2
+
3
+ /**
4
+ * Dispatch context provided to dynamic dispatch callbacks.
5
+ */
6
+ export interface ComputeDispatchContext {
7
+ width: number;
8
+ height: number;
9
+ time: number;
10
+ delta: number;
11
+ workgroupSize: [number, number, number];
12
+ }
13
+
14
+ /**
15
+ * Options for constructing a `ComputePass`.
16
+ */
17
+ export interface ComputePassOptions {
18
+ /**
19
+ * Compute shader WGSL source code.
20
+ * Must declare `@compute @workgroup_size(...) fn compute(@builtin(global_invocation_id) ...)`.
21
+ */
22
+ compute: string;
23
+ /**
24
+ * Dispatch workgroup counts.
25
+ * - Static tuple: `[x]`, `[x, y]`, or `[x, y, z]`
26
+ * - `'auto'`: derived from canvas size / workgroup size
27
+ * - Function: dynamic dispatch based on frame context
28
+ */
29
+ dispatch?:
30
+ | [number, number?, number?]
31
+ | 'auto'
32
+ | ((ctx: ComputeDispatchContext) => [number, number, number]);
33
+ /**
34
+ * Enables/disables this compute pass.
35
+ */
36
+ enabled?: boolean;
37
+ }
38
+
39
+ /**
40
+ * Compute pass class used within the render graph.
41
+ *
42
+ * Validates compute shader contract, parses workgroup size,
43
+ * and resolves dispatch dimensions. Does **not** manage GPU resources
44
+ * (that responsibility belongs to the renderer).
45
+ */
46
+ export class ComputePass {
47
+ /**
48
+ * Enables/disables this pass without removing it from graph.
49
+ */
50
+ enabled: boolean;
51
+
52
+ /**
53
+ * Discriminant flag for render graph to identify compute passes.
54
+ */
55
+ readonly isCompute = true as const;
56
+
57
+ private compute: string;
58
+ private workgroupSize: [number, number, number];
59
+ private dispatch: ComputePassOptions['dispatch'];
60
+
61
+ constructor(options: ComputePassOptions) {
62
+ assertComputeContract(options.compute);
63
+ const workgroupSize = extractWorkgroupSize(options.compute);
64
+ this.compute = options.compute;
65
+ this.workgroupSize = workgroupSize;
66
+ this.dispatch = options.dispatch ?? 'auto';
67
+ this.enabled = options.enabled ?? true;
68
+ }
69
+
70
+ /**
71
+ * Replaces current compute shader and updates workgroup size.
72
+ *
73
+ * @param compute - New compute shader WGSL source.
74
+ * @throws {Error} When shader does not match the compute contract.
75
+ */
76
+ setCompute(compute: string): void {
77
+ assertComputeContract(compute);
78
+ const workgroupSize = extractWorkgroupSize(compute);
79
+ this.compute = compute;
80
+ this.workgroupSize = workgroupSize;
81
+ }
82
+
83
+ /**
84
+ * Updates dispatch strategy.
85
+ */
86
+ setDispatch(dispatch: ComputePassOptions['dispatch']): void {
87
+ this.dispatch = dispatch ?? 'auto';
88
+ }
89
+
90
+ /**
91
+ * Returns current compute shader source.
92
+ */
93
+ getCompute(): string {
94
+ return this.compute;
95
+ }
96
+
97
+ /**
98
+ * Returns parsed workgroup size from current compute shader.
99
+ */
100
+ getWorkgroupSize(): [number, number, number] {
101
+ return [...this.workgroupSize];
102
+ }
103
+
104
+ /**
105
+ * Resolves dispatch workgroup counts for current frame.
106
+ *
107
+ * @param ctx - Dispatch context with canvas dimensions and timing.
108
+ * @returns Tuple [x, y, z] workgroup counts.
109
+ */
110
+ resolveDispatch(ctx: ComputeDispatchContext): [number, number, number] {
111
+ if (this.dispatch === 'auto') {
112
+ return [
113
+ Math.ceil(ctx.width / this.workgroupSize[0]),
114
+ Math.ceil(ctx.height / this.workgroupSize[1]),
115
+ Math.ceil(1 / this.workgroupSize[2])
116
+ ];
117
+ }
118
+
119
+ if (typeof this.dispatch === 'function') {
120
+ return this.dispatch(ctx);
121
+ }
122
+
123
+ if (Array.isArray(this.dispatch)) {
124
+ return [this.dispatch[0], this.dispatch[1] ?? 1, this.dispatch[2] ?? 1];
125
+ }
126
+
127
+ return [1, 1, 1];
128
+ }
129
+
130
+ /**
131
+ * Releases resources (no-op since GPU resource lifecycle is renderer-managed).
132
+ */
133
+ dispose(): void {
134
+ // No-op — GPU resources are managed by the renderer.
135
+ }
136
+ }
@@ -0,0 +1,180 @@
1
+ import { assertComputeContract, extractWorkgroupSize } from '../core/compute-shader.js';
2
+ import type { ComputePassOptions, ComputeDispatchContext } from './ComputePass.js';
3
+
4
+ /**
5
+ * Options for constructing a `PingPongComputePass`.
6
+ */
7
+ export interface PingPongComputePassOptions {
8
+ /**
9
+ * Compute shader WGSL source code.
10
+ */
11
+ compute: string;
12
+ /**
13
+ * Target texture key from `material.textures`.
14
+ * The engine will auto-generate `{target}A` and `{target}B` bindings.
15
+ */
16
+ target: string;
17
+ /**
18
+ * Number of compute iterations per frame. Default: 1.
19
+ */
20
+ iterations?: number;
21
+ /**
22
+ * Dispatch workgroup counts (same as ComputePass).
23
+ */
24
+ dispatch?: ComputePassOptions['dispatch'];
25
+ /**
26
+ * Enables/disables this pass.
27
+ */
28
+ enabled?: boolean;
29
+ }
30
+
31
+ /**
32
+ * Ping-pong compute pass for iterative GPU simulations.
33
+ *
34
+ * Manages two texture buffers (A/B) and alternates between them each iteration,
35
+ * enabling read-from-previous-write patterns commonly used in fluid simulations,
36
+ * reaction-diffusion, and particle systems.
37
+ */
38
+ export class PingPongComputePass {
39
+ /**
40
+ * Enables/disables this pass without removing it from graph.
41
+ */
42
+ enabled: boolean;
43
+
44
+ /**
45
+ * Discriminant flag for render graph to identify compute passes.
46
+ */
47
+ readonly isCompute = true as const;
48
+
49
+ /**
50
+ * Discriminant flag to identify ping-pong compute passes.
51
+ */
52
+ readonly isPingPong = true as const;
53
+
54
+ private compute: string;
55
+ private target: string;
56
+ private iterations: number;
57
+ private dispatch: ComputePassOptions['dispatch'];
58
+ private workgroupSize: [number, number, number];
59
+ private frameCount: number = 0;
60
+
61
+ constructor(options: PingPongComputePassOptions) {
62
+ assertComputeContract(options.compute);
63
+ const workgroupSize = extractWorkgroupSize(options.compute);
64
+ this.compute = options.compute;
65
+ this.target = options.target;
66
+ this.iterations = PingPongComputePass.assertIterations(options.iterations ?? 1);
67
+ this.dispatch = options.dispatch ?? 'auto';
68
+ this.enabled = options.enabled ?? true;
69
+ this.workgroupSize = workgroupSize;
70
+ }
71
+
72
+ private static assertIterations(count: number): number {
73
+ if (!Number.isFinite(count) || count < 1 || !Number.isInteger(count)) {
74
+ throw new Error(
75
+ `PingPongComputePass iterations must be a positive integer >= 1, got ${count}`
76
+ );
77
+ }
78
+ return count;
79
+ }
80
+
81
+ /**
82
+ * Returns the texture key holding the latest result.
83
+ * Alternates between `{target}A` and `{target}B` based on total iteration parity.
84
+ */
85
+ getCurrentOutput(): string {
86
+ const totalIterations = this.frameCount * this.iterations;
87
+ return totalIterations % 2 === 0 ? `${this.target}A` : `${this.target}B`;
88
+ }
89
+
90
+ /**
91
+ * Advances the internal frame counter (called by renderer after each frame's iterations).
92
+ */
93
+ advanceFrame(): void {
94
+ this.frameCount += 1;
95
+ }
96
+
97
+ /**
98
+ * Replaces compute shader and updates workgroup size.
99
+ */
100
+ setCompute(compute: string): void {
101
+ assertComputeContract(compute);
102
+ const workgroupSize = extractWorkgroupSize(compute);
103
+ this.compute = compute;
104
+ this.workgroupSize = workgroupSize;
105
+ }
106
+
107
+ /**
108
+ * Updates iteration count.
109
+ *
110
+ * @param count - Must be >= 1.
111
+ */
112
+ setIterations(count: number): void {
113
+ this.iterations = PingPongComputePass.assertIterations(count);
114
+ }
115
+
116
+ /**
117
+ * Updates dispatch strategy.
118
+ */
119
+ setDispatch(dispatch: ComputePassOptions['dispatch']): void {
120
+ this.dispatch = dispatch ?? 'auto';
121
+ }
122
+
123
+ /**
124
+ * Returns the target texture key.
125
+ */
126
+ getTarget(): string {
127
+ return this.target;
128
+ }
129
+
130
+ /**
131
+ * Returns the current iteration count.
132
+ */
133
+ getIterations(): number {
134
+ return this.iterations;
135
+ }
136
+
137
+ /**
138
+ * Returns current compute shader source.
139
+ */
140
+ getCompute(): string {
141
+ return this.compute;
142
+ }
143
+
144
+ /**
145
+ * Returns parsed workgroup size.
146
+ */
147
+ getWorkgroupSize(): [number, number, number] {
148
+ return [...this.workgroupSize];
149
+ }
150
+
151
+ /**
152
+ * Resolves dispatch workgroup counts for current frame.
153
+ */
154
+ resolveDispatch(ctx: ComputeDispatchContext): [number, number, number] {
155
+ if (this.dispatch === 'auto') {
156
+ return [
157
+ Math.ceil(ctx.width / this.workgroupSize[0]),
158
+ Math.ceil(ctx.height / this.workgroupSize[1]),
159
+ Math.ceil(1 / this.workgroupSize[2])
160
+ ];
161
+ }
162
+
163
+ if (typeof this.dispatch === 'function') {
164
+ return this.dispatch(ctx);
165
+ }
166
+
167
+ if (Array.isArray(this.dispatch)) {
168
+ return [this.dispatch[0], this.dispatch[1] ?? 1, this.dispatch[2] ?? 1];
169
+ }
170
+
171
+ return [1, 1, 1];
172
+ }
173
+
174
+ /**
175
+ * Releases resources (no-op, GPU lifecycle is renderer-managed).
176
+ */
177
+ dispose(): void {
178
+ // No-op
179
+ }
180
+ }
@@ -65,8 +65,9 @@ export class ShaderPass extends FullscreenPass {
65
65
  * Replaces current shader fragment and invalidates pipeline cache.
66
66
  */
67
67
  setFragment(fragment: string): void {
68
+ const nextProgram = buildShaderPassProgram(fragment);
68
69
  this.fragment = fragment;
69
- this.program = buildShaderPassProgram(fragment);
70
+ this.program = nextProgram;
70
71
  this.invalidateFullscreenCache();
71
72
  }
72
73
 
@@ -1,3 +1,9 @@
1
1
  export { BlitPass, type BlitPassOptions } from './BlitPass.js';
2
2
  export { CopyPass, type CopyPassOptions } from './CopyPass.js';
3
3
  export { ShaderPass, type ShaderPassOptions } from './ShaderPass.js';
4
+ export {
5
+ ComputePass,
6
+ type ComputePassOptions,
7
+ type ComputeDispatchContext
8
+ } from './ComputePass.js';
9
+ export { PingPongComputePass, type PingPongComputePassOptions } from './PingPongComputePass.js';
@@ -4,8 +4,8 @@ import type { FragMaterial } from '../core/material.js';
4
4
  import { createFrameRegistry } from '../core/frame-registry.js';
5
5
  import { createMotionGPURuntimeLoop } from '../core/runtime-loop.js';
6
6
  import type {
7
+ AnyPass,
7
8
  OutputColorSpace,
8
- RenderPass,
9
9
  RenderMode,
10
10
  RenderTargetDefinitionMap
11
11
  } from '../core/types.js';
@@ -17,7 +17,7 @@ import { MotionGPUReactContext, type MotionGPUContext } from './motiongpu-contex
17
17
  export interface FragCanvasProps {
18
18
  material: FragMaterial;
19
19
  renderTargets?: RenderTargetDefinitionMap;
20
- passes?: RenderPass[];
20
+ passes?: AnyPass[];
21
21
  clearColor?: [number, number, number, number];
22
22
  outputColorSpace?: OutputColorSpace;
23
23
  renderMode?: RenderMode;
@@ -39,7 +39,7 @@ export interface FragCanvasProps {
39
39
  interface RuntimePropsSnapshot {
40
40
  material: FragMaterial;
41
41
  renderTargets: RenderTargetDefinitionMap;
42
- passes: RenderPass[];
42
+ passes: AnyPass[];
43
43
  clearColor: [number, number, number, number];
44
44
  outputColorSpace: OutputColorSpace;
45
45
  adapterOptions: GPURequestAdapterOptions | undefined;