@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.
- package/README.md +99 -0
- package/dist/advanced.js +3 -1
- package/dist/core/advanced.js +3 -1
- package/dist/core/compute-bindgroup-cache.d.ts +13 -0
- package/dist/core/compute-bindgroup-cache.d.ts.map +1 -0
- package/dist/core/compute-bindgroup-cache.js +45 -0
- package/dist/core/compute-bindgroup-cache.js.map +1 -0
- package/dist/core/compute-shader.d.ts +135 -0
- package/dist/core/compute-shader.d.ts.map +1 -0
- package/dist/core/compute-shader.js +238 -0
- package/dist/core/compute-shader.js.map +1 -0
- package/dist/core/error-diagnostics.d.ts +8 -1
- package/dist/core/error-diagnostics.d.ts.map +1 -1
- package/dist/core/error-diagnostics.js +7 -3
- package/dist/core/error-diagnostics.js.map +1 -1
- 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 +82 -1
- 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 +32 -4
- 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 +489 -29
- 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 +74 -14
- package/dist/core/runtime-loop.js.map +1 -1
- package/dist/core/shader.d.ts +16 -3
- package/dist/core/shader.d.ts.map +1 -1
- package/dist/core/shader.js +22 -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 +16 -0
- package/dist/core/textures.d.ts.map +1 -1
- package/dist/core/textures.js +8 -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-bindgroup-cache.ts +73 -0
- package/src/lib/core/compute-shader.ts +412 -0
- package/src/lib/core/error-diagnostics.ts +29 -4
- package/src/lib/core/error-report.ts +155 -1
- 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 +103 -21
- package/src/lib/core/render-graph.ts +39 -9
- package/src/lib/core/renderer.ts +768 -48
- package/src/lib/core/runtime-loop.ts +116 -16
- package/src/lib/core/shader.ts +58 -4
- package/src/lib/core/storage-buffers.ts +142 -0
- package/src/lib/core/texture-loader.ts +6 -0
- package/src/lib/core/textures.ts +29 -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
package/dist/svelte/index.js
CHANGED
|
@@ -2,9 +2,11 @@ import { defineMaterial } from "../core/material.js";
|
|
|
2
2
|
import { BlitPass } from "../passes/BlitPass.js";
|
|
3
3
|
import { CopyPass } from "../passes/CopyPass.js";
|
|
4
4
|
import { ShaderPass } from "../passes/ShaderPass.js";
|
|
5
|
+
import { ComputePass } from "../passes/ComputePass.js";
|
|
6
|
+
import { PingPongComputePass } from "../passes/PingPongComputePass.js";
|
|
5
7
|
import "../passes/index.js";
|
|
6
8
|
import { useMotionGPU } from "./motiongpu-context.js";
|
|
7
9
|
import { useFrame } from "./frame-context.js";
|
|
8
10
|
import { useTexture } from "./use-texture.js";
|
|
9
11
|
import FragCanvas from "./FragCanvas.svelte";
|
|
10
|
-
export { BlitPass, CopyPass, FragCanvas, ShaderPass, defineMaterial, useFrame, useMotionGPU, useTexture };
|
|
12
|
+
export { BlitPass, ComputePass, CopyPass, FragCanvas, PingPongComputePass, ShaderPass, defineMaterial, useFrame, useMotionGPU, useTexture };
|
package/package.json
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export interface ComputeStorageBindGroupCacheRequest {
|
|
2
|
+
topologyKey: string;
|
|
3
|
+
layoutEntries: GPUBindGroupLayoutEntry[];
|
|
4
|
+
bindGroupEntries: GPUBindGroupEntry[];
|
|
5
|
+
resourceRefs: readonly unknown[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ComputeStorageBindGroupCache {
|
|
9
|
+
getOrCreate: (request: ComputeStorageBindGroupCacheRequest) => GPUBindGroup | null;
|
|
10
|
+
reset: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function equalResourceRefs(previous: readonly unknown[], next: readonly unknown[]): boolean {
|
|
14
|
+
if (previous.length !== next.length) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (let index = 0; index < previous.length; index += 1) {
|
|
19
|
+
if (!Object.is(previous[index], next[index])) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createComputeStorageBindGroupCache(
|
|
28
|
+
device: GPUDevice
|
|
29
|
+
): ComputeStorageBindGroupCache {
|
|
30
|
+
let cachedTopologyKey: string | null = null;
|
|
31
|
+
let cachedLayout: GPUBindGroupLayout | null = null;
|
|
32
|
+
let cachedBindGroup: GPUBindGroup | null = null;
|
|
33
|
+
let cachedResourceRefs: readonly unknown[] = [];
|
|
34
|
+
|
|
35
|
+
const reset = (): void => {
|
|
36
|
+
cachedTopologyKey = null;
|
|
37
|
+
cachedLayout = null;
|
|
38
|
+
cachedBindGroup = null;
|
|
39
|
+
cachedResourceRefs = [];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
getOrCreate(request) {
|
|
44
|
+
if (request.layoutEntries.length === 0) {
|
|
45
|
+
reset();
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (cachedTopologyKey !== request.topologyKey) {
|
|
50
|
+
cachedTopologyKey = request.topologyKey;
|
|
51
|
+
cachedLayout = device.createBindGroupLayout({ entries: request.layoutEntries });
|
|
52
|
+
cachedBindGroup = null;
|
|
53
|
+
cachedResourceRefs = [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!cachedLayout) {
|
|
57
|
+
throw new Error('Compute storage bind group cache is missing a layout.');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (cachedBindGroup && equalResourceRefs(cachedResourceRefs, request.resourceRefs)) {
|
|
61
|
+
return cachedBindGroup;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
cachedBindGroup = device.createBindGroup({
|
|
65
|
+
layout: cachedLayout,
|
|
66
|
+
entries: request.bindGroupEntries
|
|
67
|
+
});
|
|
68
|
+
cachedResourceRefs = [...request.resourceRefs];
|
|
69
|
+
return cachedBindGroup;
|
|
70
|
+
},
|
|
71
|
+
reset
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import type { StorageBufferAccess, StorageBufferType, UniformLayout } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Regex contract for compute entrypoint.
|
|
5
|
+
* Matches: @compute @workgroup_size(...) fn compute(
|
|
6
|
+
* with @builtin(global_invocation_id) parameter.
|
|
7
|
+
*/
|
|
8
|
+
export const COMPUTE_ENTRY_CONTRACT = /@compute\s+@workgroup_size\s*\([^)]+\)\s*fn\s+compute\s*\(/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Regex to extract @workgroup_size values.
|
|
12
|
+
*/
|
|
13
|
+
const WORKGROUP_SIZE_PATTERN =
|
|
14
|
+
/@workgroup_size\s*\(\s*(\d+)(?:\s*,\s*(\d+))?(?:\s*,\s*(\d+))?\s*\)/;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Regex to verify @builtin(global_invocation_id) parameter.
|
|
18
|
+
*/
|
|
19
|
+
const GLOBAL_INVOCATION_ID_PATTERN = /@builtin\s*\(\s*global_invocation_id\s*\)/;
|
|
20
|
+
const WORKGROUP_DIMENSION_MIN = 1;
|
|
21
|
+
const WORKGROUP_DIMENSION_MAX = 65535;
|
|
22
|
+
|
|
23
|
+
function extractComputeParamList(compute: string): string | null {
|
|
24
|
+
const computeFnIndex = compute.indexOf('fn compute');
|
|
25
|
+
if (computeFnIndex === -1) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const openParenIndex = compute.indexOf('(', computeFnIndex);
|
|
30
|
+
if (openParenIndex === -1) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let depth = 0;
|
|
35
|
+
for (let index = openParenIndex; index < compute.length; index += 1) {
|
|
36
|
+
const char = compute[index];
|
|
37
|
+
if (char === '(') {
|
|
38
|
+
depth += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (char === ')') {
|
|
43
|
+
depth -= 1;
|
|
44
|
+
if (depth === 0) {
|
|
45
|
+
return compute.slice(openParenIndex + 1, index);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function assertWorkgroupDimension(value: number): void {
|
|
54
|
+
if (
|
|
55
|
+
!Number.isFinite(value) ||
|
|
56
|
+
!Number.isInteger(value) ||
|
|
57
|
+
value < WORKGROUP_DIMENSION_MIN ||
|
|
58
|
+
value > WORKGROUP_DIMENSION_MAX
|
|
59
|
+
) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`@workgroup_size dimensions must be integers in range ${WORKGROUP_DIMENSION_MIN}-${WORKGROUP_DIMENSION_MAX}, got ${value}.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Default uniform field used when no custom uniforms are provided in compute.
|
|
68
|
+
*/
|
|
69
|
+
const DEFAULT_UNIFORM_FIELD = 'motiongpu_unused: vec4f,';
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Validates compute shader user code matches the compute contract.
|
|
73
|
+
*
|
|
74
|
+
* @param compute - User compute shader WGSL source.
|
|
75
|
+
* @throws {Error} When shader does not match the compute contract.
|
|
76
|
+
*/
|
|
77
|
+
export function assertComputeContract(compute: string): void {
|
|
78
|
+
if (!COMPUTE_ENTRY_CONTRACT.test(compute)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
'Compute shader must declare `@compute @workgroup_size(...) fn compute(...)`. ' +
|
|
81
|
+
'Ensure the function is named `compute` and includes @compute and @workgroup_size annotations.'
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const params = extractComputeParamList(compute);
|
|
86
|
+
if (!params || !GLOBAL_INVOCATION_ID_PATTERN.test(params)) {
|
|
87
|
+
throw new Error('Compute shader must include a `@builtin(global_invocation_id)` parameter.');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
extractWorkgroupSize(compute);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Extracts @workgroup_size values from WGSL compute shader.
|
|
95
|
+
*
|
|
96
|
+
* @param compute - Validated compute shader source.
|
|
97
|
+
* @returns Tuple [x, y, z] with defaults of 1 for omitted dimensions.
|
|
98
|
+
*/
|
|
99
|
+
export function extractWorkgroupSize(compute: string): [number, number, number] {
|
|
100
|
+
const match = compute.match(WORKGROUP_SIZE_PATTERN);
|
|
101
|
+
if (!match) {
|
|
102
|
+
throw new Error('Could not extract @workgroup_size from compute shader source.');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const x = Number.parseInt(match[1] ?? '1', 10);
|
|
106
|
+
const y = Number.parseInt(match[2] ?? '1', 10);
|
|
107
|
+
const z = Number.parseInt(match[3] ?? '1', 10);
|
|
108
|
+
assertWorkgroupDimension(x);
|
|
109
|
+
assertWorkgroupDimension(y);
|
|
110
|
+
assertWorkgroupDimension(z);
|
|
111
|
+
|
|
112
|
+
return [x, y, z];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Maps StorageBufferAccess to WGSL var qualifier.
|
|
117
|
+
*/
|
|
118
|
+
function toWgslAccessMode(access: StorageBufferAccess): string {
|
|
119
|
+
switch (access) {
|
|
120
|
+
case 'read':
|
|
121
|
+
return 'read';
|
|
122
|
+
case 'read-write':
|
|
123
|
+
return 'read_write';
|
|
124
|
+
default:
|
|
125
|
+
throw new Error(`Unsupported storage buffer access mode "${String(access)}".`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Builds WGSL struct fields for uniforms used in compute shader preamble.
|
|
131
|
+
*/
|
|
132
|
+
function buildUniformStructForCompute(layout: UniformLayout): string {
|
|
133
|
+
if (layout.entries.length === 0) {
|
|
134
|
+
return DEFAULT_UNIFORM_FIELD;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return layout.entries.map((entry) => `${entry.name}: ${entry.type},`).join('\n\t');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Builds storage buffer binding declarations for compute shader.
|
|
142
|
+
*
|
|
143
|
+
* @param storageBufferKeys - Sorted buffer keys.
|
|
144
|
+
* @param definitions - Type/access definitions per key.
|
|
145
|
+
* @param groupIndex - Bind group index for storage buffers.
|
|
146
|
+
* @returns WGSL binding declaration string.
|
|
147
|
+
*/
|
|
148
|
+
export function buildComputeStorageBufferBindings(
|
|
149
|
+
storageBufferKeys: string[],
|
|
150
|
+
definitions: Record<string, { type: StorageBufferType; access: StorageBufferAccess }>,
|
|
151
|
+
groupIndex: number
|
|
152
|
+
): string {
|
|
153
|
+
if (storageBufferKeys.length === 0) {
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const declarations: string[] = [];
|
|
158
|
+
|
|
159
|
+
for (let index = 0; index < storageBufferKeys.length; index += 1) {
|
|
160
|
+
const key = storageBufferKeys[index];
|
|
161
|
+
if (key === undefined) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const definition = definitions[key];
|
|
166
|
+
if (!definition) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const accessMode = toWgslAccessMode(definition.access);
|
|
171
|
+
declarations.push(
|
|
172
|
+
`@group(${groupIndex}) @binding(${index}) var<storage, ${accessMode}> ${key}: ${definition.type};`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return declarations.join('\n');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Builds storage texture binding declarations for compute shader.
|
|
181
|
+
*
|
|
182
|
+
* @param storageTextureKeys - Sorted storage texture keys.
|
|
183
|
+
* @param definitions - Format definitions per key.
|
|
184
|
+
* @param groupIndex - Bind group index for storage textures.
|
|
185
|
+
* @returns WGSL binding declaration string.
|
|
186
|
+
*/
|
|
187
|
+
export function buildComputeStorageTextureBindings(
|
|
188
|
+
storageTextureKeys: string[],
|
|
189
|
+
definitions: Record<string, { format: GPUTextureFormat }>,
|
|
190
|
+
groupIndex: number
|
|
191
|
+
): string {
|
|
192
|
+
if (storageTextureKeys.length === 0) {
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const declarations: string[] = [];
|
|
197
|
+
|
|
198
|
+
for (let index = 0; index < storageTextureKeys.length; index += 1) {
|
|
199
|
+
const key = storageTextureKeys[index];
|
|
200
|
+
if (key === undefined) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const definition = definitions[key];
|
|
205
|
+
if (!definition) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
declarations.push(
|
|
210
|
+
`@group(${groupIndex}) @binding(${index}) var ${key}: texture_storage_2d<${definition.format}, write>;`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return declarations.join('\n');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Maps storage texture format to sampled texture scalar type for `texture_2d<T>`.
|
|
219
|
+
*/
|
|
220
|
+
export function storageTextureSampleScalarType(format: GPUTextureFormat): 'f32' | 'u32' | 'i32' {
|
|
221
|
+
const normalized = String(format).toLowerCase();
|
|
222
|
+
if (normalized.endsWith('uint')) {
|
|
223
|
+
return 'u32';
|
|
224
|
+
}
|
|
225
|
+
if (normalized.endsWith('sint')) {
|
|
226
|
+
return 'i32';
|
|
227
|
+
}
|
|
228
|
+
return 'f32';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Assembles compute shader WGSL for ping-pong workflows.
|
|
233
|
+
*
|
|
234
|
+
* Exposes two generated bindings under group(2):
|
|
235
|
+
* - `${target}A`: sampled read texture (`texture_2d<T>`)
|
|
236
|
+
* - `${target}B`: storage write texture (`texture_storage_2d<format, write>`)
|
|
237
|
+
*/
|
|
238
|
+
export function buildPingPongComputeShaderSource(options: {
|
|
239
|
+
compute: string;
|
|
240
|
+
uniformLayout: UniformLayout;
|
|
241
|
+
storageBufferKeys: string[];
|
|
242
|
+
storageBufferDefinitions: Record<
|
|
243
|
+
string,
|
|
244
|
+
{ type: StorageBufferType; access: StorageBufferAccess }
|
|
245
|
+
>;
|
|
246
|
+
target: string;
|
|
247
|
+
targetFormat: GPUTextureFormat;
|
|
248
|
+
}): string {
|
|
249
|
+
const uniformFields = buildUniformStructForCompute(options.uniformLayout);
|
|
250
|
+
const storageBufferBindings = buildComputeStorageBufferBindings(
|
|
251
|
+
options.storageBufferKeys,
|
|
252
|
+
options.storageBufferDefinitions,
|
|
253
|
+
1
|
|
254
|
+
);
|
|
255
|
+
const sampledType = storageTextureSampleScalarType(options.targetFormat);
|
|
256
|
+
const pingPongTextureBindings = [
|
|
257
|
+
`@group(2) @binding(0) var ${options.target}A: texture_2d<${sampledType}>;`,
|
|
258
|
+
`@group(2) @binding(1) var ${options.target}B: texture_storage_2d<${options.targetFormat}, write>;`
|
|
259
|
+
].join('\n');
|
|
260
|
+
|
|
261
|
+
return `struct MotionGPUFrame {
|
|
262
|
+
time: f32,
|
|
263
|
+
delta: f32,
|
|
264
|
+
resolution: vec2f,
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
struct MotionGPUUniforms {
|
|
268
|
+
${uniformFields}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
@group(0) @binding(0) var<uniform> motiongpuFrame: MotionGPUFrame;
|
|
272
|
+
@group(0) @binding(1) var<uniform> motiongpuUniforms: MotionGPUUniforms;
|
|
273
|
+
${storageBufferBindings ? '\n' + storageBufferBindings : ''}
|
|
274
|
+
${pingPongTextureBindings ? '\n' + pingPongTextureBindings : ''}
|
|
275
|
+
|
|
276
|
+
${options.compute}
|
|
277
|
+
`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Source location for generated compute shader lines.
|
|
282
|
+
*/
|
|
283
|
+
export interface ComputeShaderSourceLocation {
|
|
284
|
+
kind: 'compute';
|
|
285
|
+
line: number;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 1-based line map from generated compute WGSL to user compute source.
|
|
290
|
+
*/
|
|
291
|
+
export type ComputeShaderLineMap = Array<ComputeShaderSourceLocation | null>;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Result of compute shader source generation with line mapping metadata.
|
|
295
|
+
*/
|
|
296
|
+
export interface BuiltComputeShaderSource {
|
|
297
|
+
code: string;
|
|
298
|
+
lineMap: ComputeShaderLineMap;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Assembles full compute shader WGSL with preamble.
|
|
303
|
+
*
|
|
304
|
+
* @param options - Compute shader build options.
|
|
305
|
+
* @returns Complete WGSL source for compute stage.
|
|
306
|
+
*/
|
|
307
|
+
export function buildComputeShaderSource(options: {
|
|
308
|
+
compute: string;
|
|
309
|
+
uniformLayout: UniformLayout;
|
|
310
|
+
storageBufferKeys: string[];
|
|
311
|
+
storageBufferDefinitions: Record<
|
|
312
|
+
string,
|
|
313
|
+
{ type: StorageBufferType; access: StorageBufferAccess }
|
|
314
|
+
>;
|
|
315
|
+
storageTextureKeys: string[];
|
|
316
|
+
storageTextureDefinitions: Record<string, { format: GPUTextureFormat }>;
|
|
317
|
+
}): string {
|
|
318
|
+
const uniformFields = buildUniformStructForCompute(options.uniformLayout);
|
|
319
|
+
const storageBufferBindings = buildComputeStorageBufferBindings(
|
|
320
|
+
options.storageBufferKeys,
|
|
321
|
+
options.storageBufferDefinitions,
|
|
322
|
+
1
|
|
323
|
+
);
|
|
324
|
+
const storageTextureBindings = buildComputeStorageTextureBindings(
|
|
325
|
+
options.storageTextureKeys,
|
|
326
|
+
options.storageTextureDefinitions,
|
|
327
|
+
2
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
return `struct MotionGPUFrame {
|
|
331
|
+
time: f32,
|
|
332
|
+
delta: f32,
|
|
333
|
+
resolution: vec2f,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
struct MotionGPUUniforms {
|
|
337
|
+
${uniformFields}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
@group(0) @binding(0) var<uniform> motiongpuFrame: MotionGPUFrame;
|
|
341
|
+
@group(0) @binding(1) var<uniform> motiongpuUniforms: MotionGPUUniforms;
|
|
342
|
+
${storageBufferBindings ? '\n' + storageBufferBindings : ''}
|
|
343
|
+
${storageTextureBindings ? '\n' + storageTextureBindings : ''}
|
|
344
|
+
|
|
345
|
+
${options.compute}
|
|
346
|
+
`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function buildComputeLineMap(
|
|
350
|
+
generatedCode: string,
|
|
351
|
+
userComputeSource: string
|
|
352
|
+
): ComputeShaderLineMap {
|
|
353
|
+
const lineCount = generatedCode.split('\n').length;
|
|
354
|
+
const lineMap: ComputeShaderLineMap = new Array(lineCount + 1).fill(null);
|
|
355
|
+
const computeStartIndex = generatedCode.indexOf(userComputeSource);
|
|
356
|
+
if (computeStartIndex === -1) {
|
|
357
|
+
return lineMap;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const computeStartLine = generatedCode.slice(0, computeStartIndex).split('\n').length;
|
|
361
|
+
const computeLineCount = userComputeSource.split('\n').length;
|
|
362
|
+
for (let line = 0; line < computeLineCount; line += 1) {
|
|
363
|
+
lineMap[computeStartLine + line] = {
|
|
364
|
+
kind: 'compute',
|
|
365
|
+
line: line + 1
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return lineMap;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Assembles full compute shader WGSL with source line mapping metadata.
|
|
374
|
+
*/
|
|
375
|
+
export function buildComputeShaderSourceWithMap(options: {
|
|
376
|
+
compute: string;
|
|
377
|
+
uniformLayout: UniformLayout;
|
|
378
|
+
storageBufferKeys: string[];
|
|
379
|
+
storageBufferDefinitions: Record<
|
|
380
|
+
string,
|
|
381
|
+
{ type: StorageBufferType; access: StorageBufferAccess }
|
|
382
|
+
>;
|
|
383
|
+
storageTextureKeys: string[];
|
|
384
|
+
storageTextureDefinitions: Record<string, { format: GPUTextureFormat }>;
|
|
385
|
+
}): BuiltComputeShaderSource {
|
|
386
|
+
const code = buildComputeShaderSource(options);
|
|
387
|
+
return {
|
|
388
|
+
code,
|
|
389
|
+
lineMap: buildComputeLineMap(code, options.compute)
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Assembles ping-pong compute shader WGSL with source line mapping metadata.
|
|
395
|
+
*/
|
|
396
|
+
export function buildPingPongComputeShaderSourceWithMap(options: {
|
|
397
|
+
compute: string;
|
|
398
|
+
uniformLayout: UniformLayout;
|
|
399
|
+
storageBufferKeys: string[];
|
|
400
|
+
storageBufferDefinitions: Record<
|
|
401
|
+
string,
|
|
402
|
+
{ type: StorageBufferType; access: StorageBufferAccess }
|
|
403
|
+
>;
|
|
404
|
+
target: string;
|
|
405
|
+
targetFormat: GPUTextureFormat;
|
|
406
|
+
}): BuiltComputeShaderSource {
|
|
407
|
+
const code = buildPingPongComputeShaderSource(options);
|
|
408
|
+
return {
|
|
409
|
+
code,
|
|
410
|
+
lineMap: buildComputeLineMap(code, options.compute)
|
|
411
|
+
};
|
|
412
|
+
}
|
|
@@ -11,6 +11,13 @@ export interface MaterialSourceMetadata {
|
|
|
11
11
|
functionName?: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export interface ComputeSourceLocation {
|
|
15
|
+
kind: 'compute';
|
|
16
|
+
line: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ShaderSourceLocation = MaterialSourceLocation | ComputeSourceLocation;
|
|
20
|
+
|
|
14
21
|
/**
|
|
15
22
|
* One WGSL compiler diagnostic enriched with source-location metadata.
|
|
16
23
|
*/
|
|
@@ -19,7 +26,7 @@ export interface ShaderCompilationDiagnostic {
|
|
|
19
26
|
message: string;
|
|
20
27
|
linePos?: number;
|
|
21
28
|
lineLength?: number;
|
|
22
|
-
sourceLocation:
|
|
29
|
+
sourceLocation: ShaderSourceLocation | null;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
/**
|
|
@@ -41,8 +48,10 @@ export interface ShaderCompilationRuntimeContext {
|
|
|
41
48
|
*/
|
|
42
49
|
export interface ShaderCompilationDiagnosticsPayload {
|
|
43
50
|
kind: 'shader-compilation';
|
|
51
|
+
shaderStage?: 'fragment' | 'compute';
|
|
44
52
|
diagnostics: ShaderCompilationDiagnostic[];
|
|
45
53
|
fragmentSource: string;
|
|
54
|
+
computeSource?: string;
|
|
46
55
|
includeSources: Record<string, string>;
|
|
47
56
|
defineBlockSource?: string;
|
|
48
57
|
materialSource: MaterialSourceMetadata | null;
|
|
@@ -78,7 +87,7 @@ function isMaterialSourceMetadata(value: unknown): value is MaterialSourceMetada
|
|
|
78
87
|
return true;
|
|
79
88
|
}
|
|
80
89
|
|
|
81
|
-
function
|
|
90
|
+
function isShaderSourceLocation(value: unknown): value is ShaderSourceLocation | null {
|
|
82
91
|
if (value === null) {
|
|
83
92
|
return true;
|
|
84
93
|
}
|
|
@@ -89,7 +98,7 @@ function isMaterialSourceLocation(value: unknown): value is MaterialSourceLocati
|
|
|
89
98
|
|
|
90
99
|
const record = value as Record<string, unknown>;
|
|
91
100
|
const kind = record.kind;
|
|
92
|
-
if (kind !== 'fragment' && kind !== 'include' && kind !== 'define') {
|
|
101
|
+
if (kind !== 'fragment' && kind !== 'include' && kind !== 'define' && kind !== 'compute') {
|
|
93
102
|
return false;
|
|
94
103
|
}
|
|
95
104
|
|
|
@@ -114,7 +123,7 @@ function isShaderCompilationDiagnostic(value: unknown): value is ShaderCompilati
|
|
|
114
123
|
if (record.lineLength !== undefined && typeof record.lineLength !== 'number') {
|
|
115
124
|
return false;
|
|
116
125
|
}
|
|
117
|
-
if (!
|
|
126
|
+
if (!isShaderSourceLocation(record.sourceLocation)) {
|
|
118
127
|
return false;
|
|
119
128
|
}
|
|
120
129
|
|
|
@@ -191,6 +200,13 @@ export function getShaderCompilationDiagnostics(
|
|
|
191
200
|
if (record.kind !== 'shader-compilation') {
|
|
192
201
|
return null;
|
|
193
202
|
}
|
|
203
|
+
if (
|
|
204
|
+
record.shaderStage !== undefined &&
|
|
205
|
+
record.shaderStage !== 'fragment' &&
|
|
206
|
+
record.shaderStage !== 'compute'
|
|
207
|
+
) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
194
210
|
if (
|
|
195
211
|
!Array.isArray(record.diagnostics) ||
|
|
196
212
|
!record.diagnostics.every(isShaderCompilationDiagnostic)
|
|
@@ -200,6 +216,9 @@ export function getShaderCompilationDiagnostics(
|
|
|
200
216
|
if (typeof record.fragmentSource !== 'string') {
|
|
201
217
|
return null;
|
|
202
218
|
}
|
|
219
|
+
if (record.computeSource !== undefined && typeof record.computeSource !== 'string') {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
203
222
|
if (record.defineBlockSource !== undefined && typeof record.defineBlockSource !== 'string') {
|
|
204
223
|
return null;
|
|
205
224
|
}
|
|
@@ -222,8 +241,14 @@ export function getShaderCompilationDiagnostics(
|
|
|
222
241
|
|
|
223
242
|
return {
|
|
224
243
|
kind: 'shader-compilation',
|
|
244
|
+
...(record.shaderStage !== undefined
|
|
245
|
+
? { shaderStage: record.shaderStage as 'fragment' | 'compute' }
|
|
246
|
+
: {}),
|
|
225
247
|
diagnostics: record.diagnostics as ShaderCompilationDiagnostic[],
|
|
226
248
|
fragmentSource: record.fragmentSource,
|
|
249
|
+
...(record.computeSource !== undefined
|
|
250
|
+
? { computeSource: record.computeSource as string }
|
|
251
|
+
: {}),
|
|
227
252
|
includeSources: includeSources as Record<string, string>,
|
|
228
253
|
...(record.defineBlockSource !== undefined
|
|
229
254
|
? { defineBlockSource: record.defineBlockSource as string }
|