@motion-core/motion-gpu 0.4.2 → 0.5.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.
- package/README.md +99 -0
- package/dist/advanced.js +3 -1
- package/dist/core/advanced.js +3 -1
- package/dist/core/compute-shader.d.ts +87 -0
- package/dist/core/compute-shader.d.ts.map +1 -0
- package/dist/core/compute-shader.js +205 -0
- package/dist/core/compute-shader.js.map +1 -0
- package/dist/core/error-report.d.ts +1 -1
- package/dist/core/error-report.d.ts.map +1 -1
- package/dist/core/error-report.js +63 -0
- package/dist/core/error-report.js.map +1 -1
- package/dist/core/frame-registry.d.ts.map +1 -1
- package/dist/core/frame-registry.js +1 -1
- package/dist/core/frame-registry.js.map +1 -1
- package/dist/core/index.d.ts +5 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -1
- package/dist/core/material-preprocess.d.ts.map +1 -1
- package/dist/core/material-preprocess.js +5 -3
- package/dist/core/material-preprocess.js.map +1 -1
- package/dist/core/material.d.ts +22 -6
- package/dist/core/material.d.ts.map +1 -1
- package/dist/core/material.js +30 -3
- package/dist/core/material.js.map +1 -1
- package/dist/core/render-graph.d.ts +7 -3
- package/dist/core/render-graph.d.ts.map +1 -1
- package/dist/core/render-graph.js +22 -6
- package/dist/core/render-graph.js.map +1 -1
- package/dist/core/renderer.d.ts.map +1 -1
- package/dist/core/renderer.js +418 -23
- package/dist/core/renderer.js.map +1 -1
- package/dist/core/runtime-loop.d.ts +2 -2
- package/dist/core/runtime-loop.d.ts.map +1 -1
- package/dist/core/runtime-loop.js +49 -1
- package/dist/core/runtime-loop.js.map +1 -1
- package/dist/core/shader.d.ts +9 -1
- package/dist/core/shader.d.ts.map +1 -1
- package/dist/core/shader.js +21 -2
- package/dist/core/shader.js.map +1 -1
- package/dist/core/storage-buffers.d.ts +37 -0
- package/dist/core/storage-buffers.d.ts.map +1 -0
- package/dist/core/storage-buffers.js +95 -0
- package/dist/core/storage-buffers.js.map +1 -0
- package/dist/core/texture-loader.d.ts.map +1 -1
- package/dist/core/texture-loader.js +4 -0
- package/dist/core/texture-loader.js.map +1 -1
- package/dist/core/textures.d.ts +12 -0
- package/dist/core/textures.d.ts.map +1 -1
- package/dist/core/textures.js +7 -2
- package/dist/core/textures.js.map +1 -1
- package/dist/core/types.d.ts +146 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/passes/ComputePass.d.ts +83 -0
- package/dist/passes/ComputePass.d.ts.map +1 -0
- package/dist/passes/ComputePass.js +92 -0
- package/dist/passes/ComputePass.js.map +1 -0
- package/dist/passes/PingPongComputePass.d.ts +104 -0
- package/dist/passes/PingPongComputePass.d.ts.map +1 -0
- package/dist/passes/PingPongComputePass.js +132 -0
- package/dist/passes/PingPongComputePass.js.map +1 -0
- package/dist/passes/ShaderPass.d.ts.map +1 -1
- package/dist/passes/ShaderPass.js +2 -1
- package/dist/passes/ShaderPass.js.map +1 -1
- package/dist/passes/index.d.ts +2 -0
- package/dist/passes/index.d.ts.map +1 -1
- package/dist/passes/index.js +3 -1
- package/dist/react/FragCanvas.d.ts +2 -2
- package/dist/react/FragCanvas.d.ts.map +1 -1
- package/dist/react/FragCanvas.js.map +1 -1
- package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -1
- package/dist/react/MotionGPUErrorOverlay.js +123 -20
- package/dist/react/MotionGPUErrorOverlay.js.map +1 -1
- package/dist/react/advanced.js +3 -1
- package/dist/react/index.d.ts +5 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +3 -1
- package/dist/svelte/FragCanvas.svelte +2 -2
- package/dist/svelte/FragCanvas.svelte.d.ts +2 -2
- package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -1
- package/dist/svelte/MotionGPUErrorOverlay.svelte +137 -7
- package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -1
- package/dist/svelte/advanced.js +3 -1
- package/dist/svelte/index.d.ts +5 -2
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +3 -1
- package/package.json +1 -1
- package/src/lib/core/compute-shader.ts +326 -0
- package/src/lib/core/error-report.ts +129 -0
- package/src/lib/core/frame-registry.ts +2 -1
- package/src/lib/core/index.ts +18 -1
- package/src/lib/core/material-preprocess.ts +17 -6
- package/src/lib/core/material.ts +101 -20
- package/src/lib/core/render-graph.ts +39 -9
- package/src/lib/core/renderer.ts +655 -41
- package/src/lib/core/runtime-loop.ts +82 -3
- package/src/lib/core/shader.ts +45 -2
- package/src/lib/core/storage-buffers.ts +142 -0
- package/src/lib/core/texture-loader.ts +6 -0
- package/src/lib/core/textures.ts +24 -2
- package/src/lib/core/types.ts +165 -4
- package/src/lib/passes/ComputePass.ts +136 -0
- package/src/lib/passes/PingPongComputePass.ts +180 -0
- package/src/lib/passes/ShaderPass.ts +2 -1
- package/src/lib/passes/index.ts +6 -0
- package/src/lib/react/FragCanvas.tsx +3 -3
- package/src/lib/react/MotionGPUErrorOverlay.tsx +137 -5
- package/src/lib/react/index.ts +18 -1
- package/src/lib/svelte/FragCanvas.svelte +2 -2
- package/src/lib/svelte/MotionGPUErrorOverlay.svelte +137 -7
- package/src/lib/svelte/index.ts +18 -1
|
@@ -17,13 +17,22 @@ export type MotionGPUErrorCode =
|
|
|
17
17
|
| 'WEBGPU_ADAPTER_UNAVAILABLE'
|
|
18
18
|
| 'WEBGPU_CONTEXT_UNAVAILABLE'
|
|
19
19
|
| 'WGSL_COMPILATION_FAILED'
|
|
20
|
+
| 'MATERIAL_PREPROCESS_FAILED'
|
|
20
21
|
| 'WEBGPU_DEVICE_LOST'
|
|
21
22
|
| 'WEBGPU_UNCAPTURED_ERROR'
|
|
22
23
|
| 'BIND_GROUP_MISMATCH'
|
|
24
|
+
| 'RUNTIME_RESOURCE_MISSING'
|
|
25
|
+
| 'UNIFORM_VALUE_INVALID'
|
|
26
|
+
| 'STORAGE_BUFFER_OUT_OF_BOUNDS'
|
|
27
|
+
| 'STORAGE_BUFFER_READ_FAILED'
|
|
28
|
+
| 'RENDER_GRAPH_INVALID'
|
|
29
|
+
| 'PINGPONG_CONFIGURATION_INVALID'
|
|
23
30
|
| 'TEXTURE_USAGE_INVALID'
|
|
24
31
|
| 'TEXTURE_REQUEST_FAILED'
|
|
25
32
|
| 'TEXTURE_DECODE_UNAVAILABLE'
|
|
26
33
|
| 'TEXTURE_REQUEST_ABORTED'
|
|
34
|
+
| 'COMPUTE_COMPILATION_FAILED'
|
|
35
|
+
| 'COMPUTE_CONTRACT_INVALID'
|
|
27
36
|
| 'MOTIONGPU_RUNTIME_ERROR';
|
|
28
37
|
|
|
29
38
|
/**
|
|
@@ -274,6 +283,50 @@ function classifyErrorMessage(
|
|
|
274
283
|
};
|
|
275
284
|
}
|
|
276
285
|
|
|
286
|
+
if (
|
|
287
|
+
message.includes('Invalid include directive in fragment shader.') ||
|
|
288
|
+
message.includes('Unknown include "') ||
|
|
289
|
+
message.includes('Circular include detected for "') ||
|
|
290
|
+
message.includes('Invalid define value for "') ||
|
|
291
|
+
message.includes('Invalid include "')
|
|
292
|
+
) {
|
|
293
|
+
return {
|
|
294
|
+
code: 'MATERIAL_PREPROCESS_FAILED',
|
|
295
|
+
severity: 'error',
|
|
296
|
+
recoverable: true,
|
|
297
|
+
title: 'Material preprocess failed',
|
|
298
|
+
hint: 'Validate #include keys, define values and include expansion order before retrying.'
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (message.includes('Compute shader compilation failed')) {
|
|
303
|
+
return {
|
|
304
|
+
code: 'COMPUTE_COMPILATION_FAILED',
|
|
305
|
+
severity: 'error',
|
|
306
|
+
recoverable: true,
|
|
307
|
+
title: 'Compute shader compilation failed',
|
|
308
|
+
hint: 'Check WGSL compute shader sources below and verify storage bindings.'
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (
|
|
313
|
+
message.includes(
|
|
314
|
+
'Compute shader must declare `@compute @workgroup_size(...) fn compute(...)`.'
|
|
315
|
+
) ||
|
|
316
|
+
message.includes('Compute shader must include a `@builtin(global_invocation_id)` parameter.') ||
|
|
317
|
+
message.includes('Could not extract @workgroup_size from compute shader source.') ||
|
|
318
|
+
message.includes('@workgroup_size dimensions must be integers in range') ||
|
|
319
|
+
message.includes('Unsupported storage buffer access mode "')
|
|
320
|
+
) {
|
|
321
|
+
return {
|
|
322
|
+
code: 'COMPUTE_CONTRACT_INVALID',
|
|
323
|
+
severity: 'error',
|
|
324
|
+
recoverable: true,
|
|
325
|
+
title: 'Compute contract is invalid',
|
|
326
|
+
hint: 'Ensure compute shader contract (@compute, @workgroup_size, global_invocation_id, storage access) is valid.'
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
277
330
|
if (message.includes('WebGPU device lost') || message.includes('Device Lost')) {
|
|
278
331
|
return {
|
|
279
332
|
code: 'WEBGPU_DEVICE_LOST',
|
|
@@ -304,6 +357,82 @@ function classifyErrorMessage(
|
|
|
304
357
|
};
|
|
305
358
|
}
|
|
306
359
|
|
|
360
|
+
if (message.includes('Storage buffer "') && message.includes('write out of bounds:')) {
|
|
361
|
+
return {
|
|
362
|
+
code: 'STORAGE_BUFFER_OUT_OF_BOUNDS',
|
|
363
|
+
severity: 'error',
|
|
364
|
+
recoverable: true,
|
|
365
|
+
title: 'Storage buffer write out of bounds',
|
|
366
|
+
hint: 'Ensure offset + write byte length does not exceed declared storage buffer size.'
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (
|
|
371
|
+
message.includes('Cannot read storage buffer "') ||
|
|
372
|
+
message.includes('Cannot read storage buffer: GPU device unavailable.') ||
|
|
373
|
+
message.includes('not allocated on GPU.')
|
|
374
|
+
) {
|
|
375
|
+
return {
|
|
376
|
+
code: 'STORAGE_BUFFER_READ_FAILED',
|
|
377
|
+
severity: 'error',
|
|
378
|
+
recoverable: true,
|
|
379
|
+
title: 'Storage buffer read failed',
|
|
380
|
+
hint: 'Readbacks require an initialized renderer, allocated GPU buffer and active device.'
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (
|
|
385
|
+
message.includes('Unknown uniform "') ||
|
|
386
|
+
message.includes('Unknown uniform type for "') ||
|
|
387
|
+
message.includes('Unknown texture "') ||
|
|
388
|
+
message.includes('Unknown storage buffer "') ||
|
|
389
|
+
message.includes('Missing definition for storage buffer "') ||
|
|
390
|
+
message.includes('Missing texture definition for "') ||
|
|
391
|
+
(message.includes('Storage buffer "') && message.includes('" not allocated.')) ||
|
|
392
|
+
(message.includes('Storage texture "') && message.includes('" not allocated.'))
|
|
393
|
+
) {
|
|
394
|
+
return {
|
|
395
|
+
code: 'RUNTIME_RESOURCE_MISSING',
|
|
396
|
+
severity: 'error',
|
|
397
|
+
recoverable: true,
|
|
398
|
+
title: 'Runtime resource binding failed',
|
|
399
|
+
hint: 'Check material declarations and runtime keys for uniforms, textures and storage resources.'
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (message.includes('Uniform ') && message.includes(' value must')) {
|
|
404
|
+
return {
|
|
405
|
+
code: 'UNIFORM_VALUE_INVALID',
|
|
406
|
+
severity: 'error',
|
|
407
|
+
recoverable: true,
|
|
408
|
+
title: 'Uniform value is invalid',
|
|
409
|
+
hint: 'Provide finite values with tuple/matrix sizes matching the uniform type.'
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (
|
|
414
|
+
message.includes('Render pass #') ||
|
|
415
|
+
message.includes('Render graph references unknown runtime target')
|
|
416
|
+
) {
|
|
417
|
+
return {
|
|
418
|
+
code: 'RENDER_GRAPH_INVALID',
|
|
419
|
+
severity: 'error',
|
|
420
|
+
recoverable: true,
|
|
421
|
+
title: 'Render graph configuration is invalid',
|
|
422
|
+
hint: 'Verify pass inputs/outputs, declared render targets and execution order.'
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (message.includes('PingPongComputePass must provide a target texture key.')) {
|
|
427
|
+
return {
|
|
428
|
+
code: 'PINGPONG_CONFIGURATION_INVALID',
|
|
429
|
+
severity: 'error',
|
|
430
|
+
recoverable: true,
|
|
431
|
+
title: 'Ping-pong compute pass is misconfigured',
|
|
432
|
+
hint: 'Configure a valid target texture key for PingPongComputePass.'
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
307
436
|
if (message.includes('Destination texture needs to have CopyDst')) {
|
|
308
437
|
return {
|
|
309
438
|
code: 'TEXTURE_USAGE_INVALID',
|
|
@@ -1003,7 +1003,8 @@ export function createFrameRegistry(options?: {
|
|
|
1003
1003
|
stop,
|
|
1004
1004
|
started: internalTask.startedStore,
|
|
1005
1005
|
unsubscribe: () => {
|
|
1006
|
-
|
|
1006
|
+
const current = stage.tasks.get(key);
|
|
1007
|
+
if (current === internalTask && stage.tasks.delete(key)) {
|
|
1007
1008
|
markScheduleDirty();
|
|
1008
1009
|
}
|
|
1009
1010
|
}
|
package/src/lib/core/index.ts
CHANGED
|
@@ -9,7 +9,13 @@ export { createCurrentWritable } from './current-value.js';
|
|
|
9
9
|
export { createFrameRegistry } from './frame-registry.js';
|
|
10
10
|
export { createMotionGPURuntimeLoop } from './runtime-loop.js';
|
|
11
11
|
export { loadTexturesFromUrls } from './texture-loader.js';
|
|
12
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
BlitPass,
|
|
14
|
+
CopyPass,
|
|
15
|
+
ShaderPass,
|
|
16
|
+
ComputePass,
|
|
17
|
+
PingPongComputePass
|
|
18
|
+
} from '../passes/index.js';
|
|
13
19
|
export type { CurrentReadable, CurrentWritable, Subscribable } from './current-value.js';
|
|
14
20
|
export type {
|
|
15
21
|
MotionGPUErrorCode,
|
|
@@ -51,6 +57,8 @@ export type {
|
|
|
51
57
|
FrameInvalidationToken,
|
|
52
58
|
FrameState,
|
|
53
59
|
OutputColorSpace,
|
|
60
|
+
AnyPass,
|
|
61
|
+
ComputePassLike,
|
|
54
62
|
RenderPass,
|
|
55
63
|
RenderPassContext,
|
|
56
64
|
RenderPassFlags,
|
|
@@ -75,3 +83,12 @@ export type {
|
|
|
75
83
|
UniformType,
|
|
76
84
|
UniformValue
|
|
77
85
|
} from './types.js';
|
|
86
|
+
export type {
|
|
87
|
+
StorageBufferAccess,
|
|
88
|
+
StorageBufferDefinition,
|
|
89
|
+
StorageBufferDefinitionMap,
|
|
90
|
+
StorageBufferType,
|
|
91
|
+
ComputePassContext
|
|
92
|
+
} from './types.js';
|
|
93
|
+
export type { ComputePassOptions, ComputeDispatchContext } from '../passes/ComputePass.js';
|
|
94
|
+
export type { PingPongComputePassOptions } from '../passes/PingPongComputePass.js';
|
|
@@ -175,7 +175,8 @@ function expandChunk(
|
|
|
175
175
|
kind: 'fragment' | 'include',
|
|
176
176
|
includeName: string | undefined,
|
|
177
177
|
includes: Record<string, string>,
|
|
178
|
-
stack: string[]
|
|
178
|
+
stack: string[],
|
|
179
|
+
expandedIncludes: Set<string>
|
|
179
180
|
): { lines: string[]; mapEntries: MaterialSourceLocation[] } {
|
|
180
181
|
const sourceLines = source.split('\n');
|
|
181
182
|
const lines: string[] = [];
|
|
@@ -215,10 +216,19 @@ function expandChunk(
|
|
|
215
216
|
);
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
if (expandedIncludes.has(includeKey)) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
expandedIncludes.add(includeKey);
|
|
223
|
+
|
|
224
|
+
const nested = expandChunk(
|
|
225
|
+
includeSource,
|
|
226
|
+
'include',
|
|
227
|
+
includeKey,
|
|
228
|
+
includes,
|
|
229
|
+
[...stack, includeKey],
|
|
230
|
+
expandedIncludes
|
|
231
|
+
);
|
|
222
232
|
lines.push(...nested.lines);
|
|
223
233
|
mapEntries.push(...nested.mapEntries);
|
|
224
234
|
}
|
|
@@ -245,7 +255,8 @@ export function preprocessMaterialFragment<
|
|
|
245
255
|
'fragment',
|
|
246
256
|
undefined,
|
|
247
257
|
normalizedIncludes,
|
|
248
|
-
[]
|
|
258
|
+
[],
|
|
259
|
+
new Set()
|
|
249
260
|
);
|
|
250
261
|
const defineEntries = (
|
|
251
262
|
Object.entries(normalizedDefines) as Array<[TDefineKey, MaterialDefineValue]>
|
package/src/lib/core/material.ts
CHANGED
|
@@ -14,7 +14,10 @@ import {
|
|
|
14
14
|
type MaterialLineMap,
|
|
15
15
|
type PreprocessedMaterialFragment
|
|
16
16
|
} from './material-preprocess.js';
|
|
17
|
+
import { assertStorageBufferDefinition, assertStorageTextureFormat } from './storage-buffers.js';
|
|
17
18
|
import type {
|
|
19
|
+
StorageBufferDefinition,
|
|
20
|
+
StorageBufferDefinitionMap,
|
|
18
21
|
TextureData,
|
|
19
22
|
TextureDefinition,
|
|
20
23
|
TextureDefinitionMap,
|
|
@@ -71,7 +74,8 @@ export interface FragMaterialInput<
|
|
|
71
74
|
TUniformKey extends string = string,
|
|
72
75
|
TTextureKey extends string = string,
|
|
73
76
|
TDefineKey extends string = string,
|
|
74
|
-
TIncludeKey extends string = string
|
|
77
|
+
TIncludeKey extends string = string,
|
|
78
|
+
TStorageBufferKey extends string = string
|
|
75
79
|
> {
|
|
76
80
|
/**
|
|
77
81
|
* User WGSL source containing `frag(uv: vec2f) -> vec4f`.
|
|
@@ -93,6 +97,10 @@ export interface FragMaterialInput<
|
|
|
93
97
|
* Optional WGSL include chunks used by `#include <name>` directives.
|
|
94
98
|
*/
|
|
95
99
|
includes?: MaterialIncludes<TIncludeKey>;
|
|
100
|
+
/**
|
|
101
|
+
* Optional storage buffer definitions for compute shaders.
|
|
102
|
+
*/
|
|
103
|
+
storageBuffers?: StorageBufferDefinitionMap<TStorageBufferKey>;
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
/**
|
|
@@ -102,7 +110,8 @@ export interface FragMaterial<
|
|
|
102
110
|
TUniformKey extends string = string,
|
|
103
111
|
TTextureKey extends string = string,
|
|
104
112
|
TDefineKey extends string = string,
|
|
105
|
-
TIncludeKey extends string = string
|
|
113
|
+
TIncludeKey extends string = string,
|
|
114
|
+
TStorageBufferKey extends string = string
|
|
106
115
|
> {
|
|
107
116
|
/**
|
|
108
117
|
* User WGSL source containing `frag(uv: vec2f) -> vec4f`.
|
|
@@ -124,6 +133,10 @@ export interface FragMaterial<
|
|
|
124
133
|
* Optional WGSL include chunks used by `#include <name>` directives.
|
|
125
134
|
*/
|
|
126
135
|
readonly includes: Readonly<MaterialIncludes<TIncludeKey>>;
|
|
136
|
+
/**
|
|
137
|
+
* Storage buffer definitions for compute shaders. Empty when not provided.
|
|
138
|
+
*/
|
|
139
|
+
readonly storageBuffers: Readonly<StorageBufferDefinitionMap<TStorageBufferKey>>;
|
|
127
140
|
}
|
|
128
141
|
|
|
129
142
|
/**
|
|
@@ -132,7 +145,8 @@ export interface FragMaterial<
|
|
|
132
145
|
export interface ResolvedMaterial<
|
|
133
146
|
TUniformKey extends string = string,
|
|
134
147
|
TTextureKey extends string = string,
|
|
135
|
-
TIncludeKey extends string = string
|
|
148
|
+
TIncludeKey extends string = string,
|
|
149
|
+
TStorageBufferKey extends string = string
|
|
136
150
|
> {
|
|
137
151
|
/**
|
|
138
152
|
* Final fragment WGSL after define injection.
|
|
@@ -178,6 +192,14 @@ export interface ResolvedMaterial<
|
|
|
178
192
|
* Source metadata used for diagnostics.
|
|
179
193
|
*/
|
|
180
194
|
source: Readonly<MaterialSourceMetadata> | null;
|
|
195
|
+
/**
|
|
196
|
+
* Sorted storage buffer keys. Empty array when no storage buffers declared.
|
|
197
|
+
*/
|
|
198
|
+
storageBufferKeys: TStorageBufferKey[];
|
|
199
|
+
/**
|
|
200
|
+
* Sorted storage texture keys (textures with storage: true).
|
|
201
|
+
*/
|
|
202
|
+
storageTextureKeys: TTextureKey[];
|
|
181
203
|
}
|
|
182
204
|
|
|
183
205
|
/**
|
|
@@ -190,8 +212,8 @@ const FRAGMENT_FUNCTION_NAME_PATTERN = /\bfn\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
|
|
|
190
212
|
/**
|
|
191
213
|
* Cache of resolved material snapshots keyed by immutable material instance.
|
|
192
214
|
*/
|
|
193
|
-
type AnyFragMaterial = FragMaterial<string, string, string, string>;
|
|
194
|
-
type AnyResolvedMaterial = ResolvedMaterial<string, string, string>;
|
|
215
|
+
type AnyFragMaterial = FragMaterial<string, string, string, string, string>;
|
|
216
|
+
type AnyResolvedMaterial = ResolvedMaterial<string, string, string, string>;
|
|
195
217
|
|
|
196
218
|
const resolvedMaterialCache = new WeakMap<AnyFragMaterial, AnyResolvedMaterial>();
|
|
197
219
|
const preprocessedFragmentCache = new WeakMap<AnyFragMaterial, PreprocessedMaterialFragment>();
|
|
@@ -200,17 +222,18 @@ const materialSourceMetadataCache = new WeakMap<AnyFragMaterial, MaterialSourceM
|
|
|
200
222
|
function getCachedResolvedMaterial<
|
|
201
223
|
TUniformKey extends string,
|
|
202
224
|
TTextureKey extends string,
|
|
203
|
-
TIncludeKey extends string
|
|
225
|
+
TIncludeKey extends string,
|
|
226
|
+
TStorageBufferKey extends string
|
|
204
227
|
>(
|
|
205
|
-
material: FragMaterial<TUniformKey, TTextureKey, string, TIncludeKey>
|
|
206
|
-
): ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey> | null {
|
|
228
|
+
material: FragMaterial<TUniformKey, TTextureKey, string, TIncludeKey, TStorageBufferKey>
|
|
229
|
+
): ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> | null {
|
|
207
230
|
const cached = resolvedMaterialCache.get(material);
|
|
208
231
|
if (!cached) {
|
|
209
232
|
return null;
|
|
210
233
|
}
|
|
211
234
|
|
|
212
235
|
// Invariant: the cache key is the same material object used to produce this resolved payload.
|
|
213
|
-
return cached as ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey>;
|
|
236
|
+
return cached as ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey>;
|
|
214
237
|
}
|
|
215
238
|
|
|
216
239
|
const STACK_TRACE_CHROME_PATTERN = /^\s*at\s+(?:(.*?)\s+\()?(.+?):(\d+):(\d+)\)?$/;
|
|
@@ -576,10 +599,11 @@ export function defineMaterial<
|
|
|
576
599
|
TUniformKey extends string = string,
|
|
577
600
|
TTextureKey extends string = string,
|
|
578
601
|
TDefineKey extends string = string,
|
|
579
|
-
TIncludeKey extends string = string
|
|
602
|
+
TIncludeKey extends string = string,
|
|
603
|
+
TStorageBufferKey extends string = string
|
|
580
604
|
>(
|
|
581
|
-
input: FragMaterialInput<TUniformKey, TTextureKey, TDefineKey, TIncludeKey>
|
|
582
|
-
): FragMaterial<TUniformKey, TTextureKey, TDefineKey, TIncludeKey> {
|
|
605
|
+
input: FragMaterialInput<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey>
|
|
606
|
+
): FragMaterial<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey> {
|
|
583
607
|
const fragment = resolveFragment(input.fragment);
|
|
584
608
|
const uniforms = Object.freeze(resolveUniforms(input.uniforms));
|
|
585
609
|
const textures = Object.freeze(resolveTextures(input.textures));
|
|
@@ -587,18 +611,60 @@ export function defineMaterial<
|
|
|
587
611
|
const includes = Object.freeze(resolveIncludes(input.includes));
|
|
588
612
|
const source = Object.freeze(resolveSourceMetadata(undefined));
|
|
589
613
|
|
|
614
|
+
// Validate and freeze storage buffers
|
|
615
|
+
const rawStorageBuffers =
|
|
616
|
+
input.storageBuffers ?? ({} as StorageBufferDefinitionMap<TStorageBufferKey>);
|
|
617
|
+
for (const [name, definition] of Object.entries(rawStorageBuffers) as Array<
|
|
618
|
+
[string, StorageBufferDefinition]
|
|
619
|
+
>) {
|
|
620
|
+
assertStorageBufferDefinition(name, definition);
|
|
621
|
+
}
|
|
622
|
+
const storageBuffers = Object.freeze(
|
|
623
|
+
Object.fromEntries(
|
|
624
|
+
Object.entries(rawStorageBuffers).map(([name, definition]) => {
|
|
625
|
+
const def = definition as StorageBufferDefinition;
|
|
626
|
+
const cloned: StorageBufferDefinition = {
|
|
627
|
+
size: def.size,
|
|
628
|
+
type: def.type,
|
|
629
|
+
...(def.access !== undefined ? { access: def.access } : {}),
|
|
630
|
+
...(def.initialData !== undefined
|
|
631
|
+
? { initialData: def.initialData.slice() as typeof def.initialData }
|
|
632
|
+
: {})
|
|
633
|
+
};
|
|
634
|
+
return [name, Object.freeze(cloned)];
|
|
635
|
+
})
|
|
636
|
+
)
|
|
637
|
+
) as Readonly<StorageBufferDefinitionMap<TStorageBufferKey>>;
|
|
638
|
+
|
|
639
|
+
// Validate storage textures
|
|
640
|
+
for (const [name, definition] of Object.entries(textures) as Array<[string, TextureDefinition]>) {
|
|
641
|
+
if (definition?.storage) {
|
|
642
|
+
if (!definition.format) {
|
|
643
|
+
throw new Error(`Texture "${name}" with storage:true requires a \`format\` field.`);
|
|
644
|
+
}
|
|
645
|
+
assertStorageTextureFormat(name, definition.format);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
590
649
|
const preprocessed = preprocessMaterialFragment({
|
|
591
650
|
fragment,
|
|
592
651
|
defines,
|
|
593
652
|
includes
|
|
594
653
|
});
|
|
595
654
|
|
|
596
|
-
const material: FragMaterial<
|
|
655
|
+
const material: FragMaterial<
|
|
656
|
+
TUniformKey,
|
|
657
|
+
TTextureKey,
|
|
658
|
+
TDefineKey,
|
|
659
|
+
TIncludeKey,
|
|
660
|
+
TStorageBufferKey
|
|
661
|
+
> = Object.freeze({
|
|
597
662
|
fragment,
|
|
598
663
|
uniforms,
|
|
599
664
|
textures,
|
|
600
665
|
defines,
|
|
601
|
-
includes
|
|
666
|
+
includes,
|
|
667
|
+
storageBuffers
|
|
602
668
|
});
|
|
603
669
|
|
|
604
670
|
preprocessedFragmentCache.set(material, preprocessed);
|
|
@@ -616,10 +682,11 @@ export function resolveMaterial<
|
|
|
616
682
|
TUniformKey extends string = string,
|
|
617
683
|
TTextureKey extends string = string,
|
|
618
684
|
TDefineKey extends string = string,
|
|
619
|
-
TIncludeKey extends string = string
|
|
685
|
+
TIncludeKey extends string = string,
|
|
686
|
+
TStorageBufferKey extends string = string
|
|
620
687
|
>(
|
|
621
|
-
material: FragMaterial<TUniformKey, TTextureKey, TDefineKey, TIncludeKey>
|
|
622
|
-
): ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey> {
|
|
688
|
+
material: FragMaterial<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey>
|
|
689
|
+
): ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> {
|
|
623
690
|
assertDefinedMaterial(material);
|
|
624
691
|
|
|
625
692
|
const cached = getCachedResolvedMaterial(material);
|
|
@@ -641,14 +708,26 @@ export function resolveMaterial<
|
|
|
641
708
|
const fragmentWgsl = preprocessed.fragment;
|
|
642
709
|
const textureConfig = buildTextureConfigSignature(textures, textureKeys);
|
|
643
710
|
|
|
711
|
+
const storageBufferKeys = Object.keys(
|
|
712
|
+
material.storageBuffers ?? {}
|
|
713
|
+
).sort() as TStorageBufferKey[];
|
|
714
|
+
const storageTextureKeys = textureKeys.filter(
|
|
715
|
+
(key) => (textures[key] as TextureDefinition)?.storage === true
|
|
716
|
+
);
|
|
717
|
+
|
|
644
718
|
const signature = JSON.stringify({
|
|
645
719
|
fragmentWgsl,
|
|
646
720
|
uniforms: uniformLayout.entries.map((entry) => `${entry.name}:${entry.type}`),
|
|
647
721
|
textureKeys,
|
|
648
|
-
textureConfig
|
|
722
|
+
textureConfig,
|
|
723
|
+
storageBufferKeys: storageBufferKeys.map((key) => {
|
|
724
|
+
const def = (material.storageBuffers as StorageBufferDefinitionMap)[key];
|
|
725
|
+
return `${key}:${def?.type ?? '?'}:${def?.size ?? 0}:${def?.access ?? 'read-write'}`;
|
|
726
|
+
}),
|
|
727
|
+
storageTextureKeys
|
|
649
728
|
});
|
|
650
729
|
|
|
651
|
-
const resolved: ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey> = {
|
|
730
|
+
const resolved: ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> = {
|
|
652
731
|
fragmentWgsl,
|
|
653
732
|
fragmentLineMap: preprocessed.lineMap,
|
|
654
733
|
uniforms,
|
|
@@ -659,7 +738,9 @@ export function resolveMaterial<
|
|
|
659
738
|
fragmentSource: material.fragment,
|
|
660
739
|
includeSources: material.includes as MaterialIncludes<TIncludeKey>,
|
|
661
740
|
defineBlockSource: preprocessed.defineBlockSource,
|
|
662
|
-
source: materialSourceMetadataCache.get(material) ?? null
|
|
741
|
+
source: materialSourceMetadataCache.get(material) ?? null,
|
|
742
|
+
storageBufferKeys,
|
|
743
|
+
storageTextureKeys
|
|
663
744
|
};
|
|
664
745
|
|
|
665
746
|
resolvedMaterialCache.set(material, resolved);
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import type { RenderPass, RenderPassInputSlot, RenderPassOutputSlot } from './types.js';
|
|
1
|
+
import type { AnyPass, RenderPass, RenderPassInputSlot, RenderPassOutputSlot } from './types.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Resolved render-pass step with defaults applied.
|
|
5
5
|
*/
|
|
6
6
|
export interface RenderGraphStep {
|
|
7
|
+
/**
|
|
8
|
+
* Step kind. 'render' for existing passes, 'compute' for compute passes.
|
|
9
|
+
*/
|
|
10
|
+
kind: 'render' | 'compute';
|
|
7
11
|
/**
|
|
8
12
|
* User pass instance.
|
|
9
13
|
*/
|
|
10
|
-
pass:
|
|
14
|
+
pass: AnyPass;
|
|
11
15
|
/**
|
|
12
16
|
* Resolved input slot.
|
|
13
17
|
*/
|
|
@@ -65,7 +69,7 @@ function cloneClearColor(
|
|
|
65
69
|
* @returns Resolved render graph plan.
|
|
66
70
|
*/
|
|
67
71
|
export function planRenderGraph(
|
|
68
|
-
passes:
|
|
72
|
+
passes: AnyPass[] | undefined,
|
|
69
73
|
defaultClearColor: [number, number, number, number],
|
|
70
74
|
renderTargetSlots?: Iterable<string>
|
|
71
75
|
): RenderGraphPlan {
|
|
@@ -80,9 +84,27 @@ export function planRenderGraph(
|
|
|
80
84
|
continue;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
87
|
+
// Compute passes don't participate in slot routing
|
|
88
|
+
const isCompute = 'isCompute' in pass && (pass as { isCompute?: boolean }).isCompute === true;
|
|
89
|
+
if (isCompute) {
|
|
90
|
+
steps.push({
|
|
91
|
+
kind: 'compute',
|
|
92
|
+
pass,
|
|
93
|
+
input: 'source',
|
|
94
|
+
output: 'source',
|
|
95
|
+
needsSwap: false,
|
|
96
|
+
clear: false,
|
|
97
|
+
clearColor: cloneClearColor(defaultClearColor),
|
|
98
|
+
preserve: true
|
|
99
|
+
});
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// After compute guard, pass is a render pass
|
|
104
|
+
const rp = pass as RenderPass;
|
|
105
|
+
const needsSwap = rp.needsSwap ?? true;
|
|
106
|
+
const input: RenderPassInputSlot = rp.input ?? 'source';
|
|
107
|
+
const output: RenderPassOutputSlot = rp.output ?? (needsSwap ? 'target' : 'source');
|
|
86
108
|
|
|
87
109
|
if (input === 'canvas') {
|
|
88
110
|
throw new Error(`Render pass #${enabledIndex} cannot read from "canvas".`);
|
|
@@ -108,11 +130,12 @@ export function planRenderGraph(
|
|
|
108
130
|
throw new Error(`Render pass #${enabledIndex} reads "${input}" before it is written.`);
|
|
109
131
|
}
|
|
110
132
|
|
|
111
|
-
const clear =
|
|
112
|
-
const clearColor = cloneClearColor(
|
|
113
|
-
const preserve =
|
|
133
|
+
const clear = rp.clear ?? false;
|
|
134
|
+
const clearColor = cloneClearColor(rp.clearColor ?? defaultClearColor);
|
|
135
|
+
const preserve = rp.preserve ?? true;
|
|
114
136
|
|
|
115
137
|
steps.push({
|
|
138
|
+
kind: 'render',
|
|
116
139
|
pass,
|
|
117
140
|
input,
|
|
118
141
|
output,
|
|
@@ -136,6 +159,13 @@ export function planRenderGraph(
|
|
|
136
159
|
enabledIndex += 1;
|
|
137
160
|
}
|
|
138
161
|
|
|
162
|
+
// When steps exist (even compute-only) but no render pass changed
|
|
163
|
+
// finalOutput from 'canvas', the scene was drawn to 'source' and
|
|
164
|
+
// needs blitting to the canvas surface.
|
|
165
|
+
if (steps.length > 0 && enabledIndex === 0) {
|
|
166
|
+
finalOutput = 'source';
|
|
167
|
+
}
|
|
168
|
+
|
|
139
169
|
return {
|
|
140
170
|
steps,
|
|
141
171
|
finalOutput
|