@motion-core/motion-gpu 0.5.0 → 0.7.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 +35 -2
- 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 +48 -0
- package/dist/core/compute-shader.d.ts.map +1 -1
- package/dist/core/compute-shader.js +34 -1
- package/dist/core/compute-shader.js.map +1 -1
- 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.map +1 -1
- package/dist/core/error-report.js +19 -1
- package/dist/core/error-report.js.map +1 -1
- package/dist/core/material.d.ts.map +1 -1
- package/dist/core/material.js +2 -1
- package/dist/core/material.js.map +1 -1
- package/dist/core/pointer.d.ts +96 -0
- package/dist/core/pointer.d.ts.map +1 -0
- package/dist/core/pointer.js +71 -0
- package/dist/core/pointer.js.map +1 -0
- package/dist/core/renderer.d.ts.map +1 -1
- package/dist/core/renderer.js +150 -85
- package/dist/core/renderer.js.map +1 -1
- package/dist/core/runtime-loop.d.ts.map +1 -1
- package/dist/core/runtime-loop.js +26 -14
- package/dist/core/runtime-loop.js.map +1 -1
- package/dist/core/shader.d.ts +7 -2
- package/dist/core/shader.d.ts.map +1 -1
- package/dist/core/shader.js +1 -0
- package/dist/core/shader.js.map +1 -1
- package/dist/core/textures.d.ts +4 -0
- package/dist/core/textures.d.ts.map +1 -1
- package/dist/core/textures.js +2 -1
- package/dist/core/textures.js.map +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/react/advanced.js +2 -1
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -1
- package/dist/react/use-pointer.d.ts +94 -0
- package/dist/react/use-pointer.d.ts.map +1 -0
- package/dist/react/use-pointer.js +285 -0
- package/dist/react/use-pointer.js.map +1 -0
- package/dist/svelte/advanced.js +2 -1
- package/dist/svelte/index.d.ts +2 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +2 -1
- package/dist/svelte/use-pointer.d.ts +94 -0
- package/dist/svelte/use-pointer.d.ts.map +1 -0
- package/dist/svelte/use-pointer.js +292 -0
- package/dist/svelte/use-pointer.js.map +1 -0
- package/package.json +1 -1
- package/src/lib/core/compute-bindgroup-cache.ts +73 -0
- package/src/lib/core/compute-shader.ts +86 -0
- package/src/lib/core/error-diagnostics.ts +29 -4
- package/src/lib/core/error-report.ts +26 -1
- package/src/lib/core/material.ts +2 -1
- package/src/lib/core/pointer.ts +177 -0
- package/src/lib/core/renderer.ts +198 -92
- package/src/lib/core/runtime-loop.ts +37 -16
- package/src/lib/core/shader.ts +13 -2
- package/src/lib/core/textures.ts +6 -1
- package/src/lib/core/types.ts +1 -1
- package/src/lib/react/index.ts +10 -0
- package/src/lib/react/use-pointer.ts +515 -0
- package/src/lib/svelte/index.ts +10 -0
- package/src/lib/svelte/use-pointer.ts +507 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { RenderMode } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pointer kind normalized from DOM `PointerEvent.pointerType`.
|
|
5
|
+
*/
|
|
6
|
+
export type PointerKind = 'mouse' | 'pen' | 'touch';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 2D tuple used by pointer coordinate payloads.
|
|
10
|
+
*/
|
|
11
|
+
export type PointerVec2 = [number, number];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Normalized pointer coordinates exposed to runtime hooks.
|
|
15
|
+
*/
|
|
16
|
+
export interface PointerPoint {
|
|
17
|
+
/**
|
|
18
|
+
* CSS pixel coordinates relative to canvas top-left corner.
|
|
19
|
+
*/
|
|
20
|
+
px: PointerVec2;
|
|
21
|
+
/**
|
|
22
|
+
* UV coordinates in shader-friendly orientation (`y` grows upward).
|
|
23
|
+
*/
|
|
24
|
+
uv: PointerVec2;
|
|
25
|
+
/**
|
|
26
|
+
* Normalized device coordinates (`-1..1`, `y` grows upward).
|
|
27
|
+
*/
|
|
28
|
+
ndc: PointerVec2;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Mutable pointer state snapshot exposed by `usePointer`.
|
|
33
|
+
*/
|
|
34
|
+
export interface PointerState extends PointerPoint {
|
|
35
|
+
inside: boolean;
|
|
36
|
+
pressed: boolean;
|
|
37
|
+
dragging: boolean;
|
|
38
|
+
pointerType: PointerKind | null;
|
|
39
|
+
pointerId: number | null;
|
|
40
|
+
button: number | null;
|
|
41
|
+
buttons: number;
|
|
42
|
+
time: number;
|
|
43
|
+
downPx: PointerVec2 | null;
|
|
44
|
+
downUv: PointerVec2 | null;
|
|
45
|
+
deltaPx: PointerVec2;
|
|
46
|
+
deltaUv: PointerVec2;
|
|
47
|
+
velocityPx: PointerVec2;
|
|
48
|
+
velocityUv: PointerVec2;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Modifier key snapshot attached to pointer click events.
|
|
53
|
+
*/
|
|
54
|
+
export interface PointerModifiers {
|
|
55
|
+
alt: boolean;
|
|
56
|
+
ctrl: boolean;
|
|
57
|
+
shift: boolean;
|
|
58
|
+
meta: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Click/tap payload produced by `usePointer`.
|
|
63
|
+
*/
|
|
64
|
+
export interface PointerClick extends PointerPoint {
|
|
65
|
+
id: number;
|
|
66
|
+
time: number;
|
|
67
|
+
pointerType: PointerKind;
|
|
68
|
+
pointerId: number;
|
|
69
|
+
button: number;
|
|
70
|
+
modifiers: PointerModifiers;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Frame wake-up strategy for pointer-driven interactions.
|
|
75
|
+
*/
|
|
76
|
+
export type PointerFrameRequestMode = 'advance' | 'auto' | 'invalidate' | 'none';
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns a monotonic timestamp in seconds.
|
|
80
|
+
*/
|
|
81
|
+
export function getPointerNowSeconds(): number {
|
|
82
|
+
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
|
83
|
+
return performance.now() / 1000;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return Date.now() / 1000;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates the initial pointer state snapshot.
|
|
91
|
+
*/
|
|
92
|
+
export function createInitialPointerState(): PointerState {
|
|
93
|
+
return {
|
|
94
|
+
px: [0, 0],
|
|
95
|
+
uv: [0, 0],
|
|
96
|
+
ndc: [-1, -1],
|
|
97
|
+
inside: false,
|
|
98
|
+
pressed: false,
|
|
99
|
+
dragging: false,
|
|
100
|
+
pointerType: null,
|
|
101
|
+
pointerId: null,
|
|
102
|
+
button: null,
|
|
103
|
+
buttons: 0,
|
|
104
|
+
time: getPointerNowSeconds(),
|
|
105
|
+
downPx: null,
|
|
106
|
+
downUv: null,
|
|
107
|
+
deltaPx: [0, 0],
|
|
108
|
+
deltaUv: [0, 0],
|
|
109
|
+
velocityPx: [0, 0],
|
|
110
|
+
velocityUv: [0, 0]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Normalized coordinate payload for a pointer position against a canvas rect.
|
|
116
|
+
*/
|
|
117
|
+
export interface PointerCoordinates extends PointerPoint {
|
|
118
|
+
inside: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Converts client coordinates to canvas-relative pointer coordinates.
|
|
123
|
+
*/
|
|
124
|
+
export function getPointerCoordinates(
|
|
125
|
+
clientX: number,
|
|
126
|
+
clientY: number,
|
|
127
|
+
rect: Pick<DOMRectReadOnly, 'height' | 'left' | 'top' | 'width'>
|
|
128
|
+
): PointerCoordinates {
|
|
129
|
+
const width = Math.max(rect.width, 1);
|
|
130
|
+
const height = Math.max(rect.height, 1);
|
|
131
|
+
const nx = (clientX - rect.left) / width;
|
|
132
|
+
const ny = (clientY - rect.top) / height;
|
|
133
|
+
const pxX = clientX - rect.left;
|
|
134
|
+
const pxY = clientY - rect.top;
|
|
135
|
+
const uvX = nx;
|
|
136
|
+
const uvY = 1 - ny;
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
px: [pxX, pxY],
|
|
140
|
+
uv: [uvX, uvY],
|
|
141
|
+
ndc: [nx * 2 - 1, uvY * 2 - 1],
|
|
142
|
+
inside: nx >= 0 && nx <= 1 && ny >= 0 && ny <= 1
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resolves frame wake-up strategy for pointer-driven updates.
|
|
148
|
+
*/
|
|
149
|
+
export function resolvePointerFrameRequestMode(
|
|
150
|
+
mode: PointerFrameRequestMode,
|
|
151
|
+
renderMode: RenderMode
|
|
152
|
+
): Exclude<PointerFrameRequestMode, 'auto'> {
|
|
153
|
+
if (mode !== 'auto') {
|
|
154
|
+
return mode;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (renderMode === 'manual') {
|
|
158
|
+
return 'advance';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (renderMode === 'on-demand') {
|
|
162
|
+
return 'invalidate';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return 'none';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Normalizes unknown pointer kind values to the public `PointerKind`.
|
|
170
|
+
*/
|
|
171
|
+
export function normalizePointerKind(pointerType: string): PointerKind {
|
|
172
|
+
if (pointerType === 'mouse' || pointerType === 'pen' || pointerType === 'touch') {
|
|
173
|
+
return pointerType;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return 'mouse';
|
|
177
|
+
}
|
package/src/lib/core/renderer.ts
CHANGED
|
@@ -18,11 +18,12 @@ import {
|
|
|
18
18
|
} from './textures.js';
|
|
19
19
|
import { packUniformsInto } from './uniforms.js';
|
|
20
20
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
buildComputeShaderSourceWithMap,
|
|
22
|
+
buildPingPongComputeShaderSourceWithMap,
|
|
23
23
|
extractWorkgroupSize,
|
|
24
24
|
storageTextureSampleScalarType
|
|
25
25
|
} from './compute-shader.js';
|
|
26
|
+
import { createComputeStorageBindGroupCache } from './compute-bindgroup-cache.js';
|
|
26
27
|
import { normalizeStorageBufferDefinition } from './storage-buffers.js';
|
|
27
28
|
import type {
|
|
28
29
|
AnyPass,
|
|
@@ -62,6 +63,7 @@ interface RuntimeTextureBinding {
|
|
|
62
63
|
key: string;
|
|
63
64
|
samplerBinding: number;
|
|
64
65
|
textureBinding: number;
|
|
66
|
+
fragmentVisible: boolean;
|
|
65
67
|
sampler: GPUSampler;
|
|
66
68
|
fallbackTexture: GPUTexture;
|
|
67
69
|
fallbackView: GPUTextureView;
|
|
@@ -109,6 +111,8 @@ interface PingPongTexturePair {
|
|
|
109
111
|
textureB: GPUTexture;
|
|
110
112
|
viewB: GPUTextureView;
|
|
111
113
|
bindGroupLayout: GPUBindGroupLayout;
|
|
114
|
+
readAWriteBBindGroup: GPUBindGroup | null;
|
|
115
|
+
readBWriteABindGroup: GPUBindGroup | null;
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
/**
|
|
@@ -187,6 +191,7 @@ async function assertCompilation(
|
|
|
187
191
|
options?: {
|
|
188
192
|
lineMap?: ShaderLineMap;
|
|
189
193
|
fragmentSource?: string;
|
|
194
|
+
computeSource?: string;
|
|
190
195
|
includeSources?: Record<string, string>;
|
|
191
196
|
defineBlockSource?: string;
|
|
192
197
|
materialSource?: {
|
|
@@ -197,6 +202,8 @@ async function assertCompilation(
|
|
|
197
202
|
functionName?: string;
|
|
198
203
|
} | null;
|
|
199
204
|
runtimeContext?: ShaderCompilationRuntimeContext;
|
|
205
|
+
errorPrefix?: string;
|
|
206
|
+
shaderStage?: 'fragment' | 'compute';
|
|
200
207
|
}
|
|
201
208
|
): Promise<void> {
|
|
202
209
|
const info = await module.getCompilationInfo();
|
|
@@ -227,11 +234,14 @@ async function assertCompilation(
|
|
|
227
234
|
return `[${contextLabel.join(' | ')}] ${diagnostic.message}`;
|
|
228
235
|
})
|
|
229
236
|
.join('\n');
|
|
230
|
-
const
|
|
237
|
+
const prefix = options?.errorPrefix ?? 'WGSL compilation failed';
|
|
238
|
+
const error = new Error(`${prefix}:\n${summary}`);
|
|
231
239
|
throw attachShaderCompilationDiagnostics(error, {
|
|
232
240
|
kind: 'shader-compilation',
|
|
241
|
+
...(options?.shaderStage !== undefined ? { shaderStage: options.shaderStage } : {}),
|
|
233
242
|
diagnostics,
|
|
234
243
|
fragmentSource: options?.fragmentSource ?? '',
|
|
244
|
+
...(options?.computeSource !== undefined ? { computeSource: options.computeSource } : {}),
|
|
235
245
|
includeSources: options?.includeSources ?? {},
|
|
236
246
|
...(options?.defineBlockSource !== undefined
|
|
237
247
|
? { defineBlockSource: options.defineBlockSource }
|
|
@@ -245,6 +255,64 @@ function toSortedUniqueStrings(values: string[]): string[] {
|
|
|
245
255
|
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
246
256
|
}
|
|
247
257
|
|
|
258
|
+
function extractGeneratedLineFromComputeError(message: string): number | null {
|
|
259
|
+
const lineMatch = message.match(/\bline\s+(\d+)\b/i);
|
|
260
|
+
if (lineMatch) {
|
|
261
|
+
const parsed = Number.parseInt(lineMatch[1] ?? '', 10);
|
|
262
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
263
|
+
return parsed;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const colonMatch = message.match(/:(\d+):\d+/);
|
|
268
|
+
if (colonMatch) {
|
|
269
|
+
const parsed = Number.parseInt(colonMatch[1] ?? '', 10);
|
|
270
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
271
|
+
return parsed;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function toComputeCompilationError(input: {
|
|
279
|
+
error: unknown;
|
|
280
|
+
lineMap: ShaderLineMap;
|
|
281
|
+
computeSource: string;
|
|
282
|
+
runtimeContext: ShaderCompilationRuntimeContext;
|
|
283
|
+
}): Error {
|
|
284
|
+
const baseError =
|
|
285
|
+
input.error instanceof Error ? input.error : new Error(String(input.error ?? 'Unknown error'));
|
|
286
|
+
const generatedLine = extractGeneratedLineFromComputeError(baseError.message) ?? 0;
|
|
287
|
+
const sourceLocation = generatedLine > 0 ? (input.lineMap[generatedLine] ?? null) : null;
|
|
288
|
+
const diagnostics = [
|
|
289
|
+
{
|
|
290
|
+
generatedLine,
|
|
291
|
+
message: baseError.message,
|
|
292
|
+
sourceLocation
|
|
293
|
+
}
|
|
294
|
+
];
|
|
295
|
+
const sourceLabel = formatShaderSourceLocation(sourceLocation);
|
|
296
|
+
const generatedLineLabel = generatedLine > 0 ? `generated WGSL line ${generatedLine}` : null;
|
|
297
|
+
const contextLabel = [sourceLabel, generatedLineLabel].filter((value) => Boolean(value));
|
|
298
|
+
const summary =
|
|
299
|
+
contextLabel.length > 0
|
|
300
|
+
? `[${contextLabel.join(' | ')}] ${baseError.message}`
|
|
301
|
+
: baseError.message;
|
|
302
|
+
const wrapped = new Error(`Compute shader compilation failed:\n${summary}`);
|
|
303
|
+
|
|
304
|
+
return attachShaderCompilationDiagnostics(wrapped, {
|
|
305
|
+
kind: 'shader-compilation',
|
|
306
|
+
shaderStage: 'compute',
|
|
307
|
+
diagnostics,
|
|
308
|
+
fragmentSource: '',
|
|
309
|
+
computeSource: input.computeSource,
|
|
310
|
+
includeSources: {},
|
|
311
|
+
materialSource: null,
|
|
312
|
+
runtimeContext: input.runtimeContext
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
248
316
|
function buildPassGraphSnapshot(
|
|
249
317
|
passes: AnyPass[] | undefined
|
|
250
318
|
): NonNullable<ShaderCompilationRuntimeContext['passGraph']> {
|
|
@@ -667,10 +735,13 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
667
735
|
try {
|
|
668
736
|
const runtimeContext = buildShaderCompilationRuntimeContext(options);
|
|
669
737
|
const convertLinearToSrgb = shouldConvertLinearToSrgb(options.outputColorSpace, format);
|
|
738
|
+
const fragmentTextureKeys = options.textureKeys.filter(
|
|
739
|
+
(key) => options.textureDefinitions[key]?.fragmentVisible !== false
|
|
740
|
+
);
|
|
670
741
|
const builtShader = buildShaderSourceWithMap(
|
|
671
742
|
options.fragmentWgsl,
|
|
672
743
|
options.uniformLayout,
|
|
673
|
-
|
|
744
|
+
fragmentTextureKeys,
|
|
674
745
|
{
|
|
675
746
|
convertLinearToSrgb,
|
|
676
747
|
fragmentLineMap: options.fragmentLineMap,
|
|
@@ -702,13 +773,18 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
702
773
|
const storageBufferDefinitions = options.storageBufferDefinitions ?? {};
|
|
703
774
|
const storageTextureKeys = options.storageTextureKeys ?? [];
|
|
704
775
|
const storageTextureKeySet = new Set(storageTextureKeys);
|
|
705
|
-
const
|
|
776
|
+
const fragmentTextureIndexByKey = new Map(
|
|
777
|
+
fragmentTextureKeys.map((key, index) => [key, index] as const)
|
|
778
|
+
);
|
|
779
|
+
const textureBindings = options.textureKeys.map((key): RuntimeTextureBinding => {
|
|
706
780
|
const config = normalizedTextureDefinitions[key];
|
|
707
781
|
if (!config) {
|
|
708
782
|
throw new Error(`Missing texture definition for "${key}"`);
|
|
709
783
|
}
|
|
710
784
|
|
|
711
|
-
const
|
|
785
|
+
const fragmentTextureIndex = fragmentTextureIndexByKey.get(key);
|
|
786
|
+
const fragmentVisible = fragmentTextureIndex !== undefined;
|
|
787
|
+
const { samplerBinding, textureBinding } = getTextureBindings(fragmentTextureIndex ?? 0);
|
|
712
788
|
const sampler = device.createSampler({
|
|
713
789
|
magFilter: config.filter,
|
|
714
790
|
minFilter: config.filter,
|
|
@@ -731,6 +807,7 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
731
807
|
key,
|
|
732
808
|
samplerBinding,
|
|
733
809
|
textureBinding,
|
|
810
|
+
fragmentVisible,
|
|
734
811
|
sampler,
|
|
735
812
|
fallbackTexture,
|
|
736
813
|
fallbackView,
|
|
@@ -779,9 +856,49 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
779
856
|
|
|
780
857
|
return runtimeBinding;
|
|
781
858
|
});
|
|
859
|
+
const textureBindingByKey = new Map(textureBindings.map((binding) => [binding.key, binding]));
|
|
860
|
+
const fragmentTextureBindings = textureBindings.filter((binding) => binding.fragmentVisible);
|
|
861
|
+
|
|
862
|
+
const computeStorageBufferLayoutEntries: GPUBindGroupLayoutEntry[] = storageBufferKeys.map(
|
|
863
|
+
(key, index) => {
|
|
864
|
+
const def = storageBufferDefinitions[key];
|
|
865
|
+
const access = def?.access ?? 'read-write';
|
|
866
|
+
const bufferType: GPUBufferBindingType =
|
|
867
|
+
access === 'read' ? 'read-only-storage' : 'storage';
|
|
868
|
+
return {
|
|
869
|
+
binding: index,
|
|
870
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
871
|
+
buffer: { type: bufferType }
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
);
|
|
875
|
+
const computeStorageBufferTopologyKey = storageBufferKeys
|
|
876
|
+
.map((key) => `${key}:${storageBufferDefinitions[key]?.access ?? 'read-write'}`)
|
|
877
|
+
.join('|');
|
|
878
|
+
|
|
879
|
+
const computeStorageTextureLayoutEntries: GPUBindGroupLayoutEntry[] = storageTextureKeys.map(
|
|
880
|
+
(key, index) => {
|
|
881
|
+
const config = normalizedTextureDefinitions[key];
|
|
882
|
+
return {
|
|
883
|
+
binding: index,
|
|
884
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
885
|
+
storageTexture: {
|
|
886
|
+
access: 'write-only' as GPUStorageTextureAccess,
|
|
887
|
+
format: (config?.format ?? 'rgba8unorm') as GPUTextureFormat,
|
|
888
|
+
viewDimension: '2d'
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
);
|
|
893
|
+
const computeStorageTextureTopologyKey = storageTextureKeys
|
|
894
|
+
.map((key) => `${key}:${normalizedTextureDefinitions[key]?.format ?? 'rgba8unorm'}`)
|
|
895
|
+
.join('|');
|
|
896
|
+
|
|
897
|
+
const computeStorageBufferBindGroupCache = createComputeStorageBindGroupCache(device);
|
|
898
|
+
const computeStorageTextureBindGroupCache = createComputeStorageBindGroupCache(device);
|
|
782
899
|
|
|
783
900
|
const bindGroupLayout = device.createBindGroupLayout({
|
|
784
|
-
entries: createBindGroupLayoutEntries(
|
|
901
|
+
entries: createBindGroupLayoutEntries(fragmentTextureBindings)
|
|
785
902
|
});
|
|
786
903
|
const fragmentStorageBindGroupLayout =
|
|
787
904
|
storageBufferKeys.length > 0
|
|
@@ -977,7 +1094,9 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
977
1094
|
viewA: textureA.createView(),
|
|
978
1095
|
textureB,
|
|
979
1096
|
viewB: textureB.createView(),
|
|
980
|
-
bindGroupLayout
|
|
1097
|
+
bindGroupLayout,
|
|
1098
|
+
readAWriteBBindGroup: null,
|
|
1099
|
+
readBWriteABindGroup: null
|
|
981
1100
|
};
|
|
982
1101
|
pingPongTexturePairs.set(target, pair);
|
|
983
1102
|
return pair;
|
|
@@ -1028,8 +1147,8 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
1028
1147
|
const isPingPongPipeline = Boolean(
|
|
1029
1148
|
buildOptions.pingPongTarget && buildOptions.pingPongFormat
|
|
1030
1149
|
);
|
|
1031
|
-
const
|
|
1032
|
-
?
|
|
1150
|
+
const builtComputeShader = isPingPongPipeline
|
|
1151
|
+
? buildPingPongComputeShaderSourceWithMap({
|
|
1033
1152
|
compute: buildOptions.computeSource,
|
|
1034
1153
|
uniformLayout: options.uniformLayout,
|
|
1035
1154
|
storageBufferKeys,
|
|
@@ -1037,7 +1156,7 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
1037
1156
|
target: buildOptions.pingPongTarget!,
|
|
1038
1157
|
targetFormat: buildOptions.pingPongFormat!
|
|
1039
1158
|
})
|
|
1040
|
-
:
|
|
1159
|
+
: buildComputeShaderSourceWithMap({
|
|
1041
1160
|
compute: buildOptions.computeSource,
|
|
1042
1161
|
uniformLayout: options.uniformLayout,
|
|
1043
1162
|
storageBufferKeys,
|
|
@@ -1046,7 +1165,7 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
1046
1165
|
storageTextureDefinitions: storageTextureDefs
|
|
1047
1166
|
});
|
|
1048
1167
|
|
|
1049
|
-
const computeShaderModule = device.createShaderModule({ code:
|
|
1168
|
+
const computeShaderModule = device.createShaderModule({ code: builtComputeShader.code });
|
|
1050
1169
|
const workgroupSize = extractWorkgroupSize(buildOptions.computeSource);
|
|
1051
1170
|
|
|
1052
1171
|
// Compute bind group layout: group(0)=uniforms, group(1)=storage buffers, group(2)=storage textures
|
|
@@ -1065,20 +1184,9 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
1065
1184
|
]
|
|
1066
1185
|
});
|
|
1067
1186
|
|
|
1068
|
-
const storageBGLEntries: GPUBindGroupLayoutEntry[] = storageBufferKeys.map((key, index) => {
|
|
1069
|
-
const def = storageBufferDefinitions[key];
|
|
1070
|
-
const access = def?.access ?? 'read-write';
|
|
1071
|
-
const bufferType: GPUBufferBindingType =
|
|
1072
|
-
access === 'read' ? 'read-only-storage' : 'storage';
|
|
1073
|
-
return {
|
|
1074
|
-
binding: index,
|
|
1075
|
-
visibility: GPUShaderStage.COMPUTE,
|
|
1076
|
-
buffer: { type: bufferType }
|
|
1077
|
-
};
|
|
1078
|
-
});
|
|
1079
1187
|
const storageBGL =
|
|
1080
|
-
|
|
1081
|
-
? device.createBindGroupLayout({ entries:
|
|
1188
|
+
computeStorageBufferLayoutEntries.length > 0
|
|
1189
|
+
? device.createBindGroupLayout({ entries: computeStorageBufferLayoutEntries })
|
|
1082
1190
|
: null;
|
|
1083
1191
|
|
|
1084
1192
|
const storageTextureBGLEntries: GPUBindGroupLayoutEntry[] = isPingPongPipeline
|
|
@@ -1104,18 +1212,7 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
1104
1212
|
}
|
|
1105
1213
|
}
|
|
1106
1214
|
]
|
|
1107
|
-
:
|
|
1108
|
-
const texDef = options.textureDefinitions[key];
|
|
1109
|
-
return {
|
|
1110
|
-
binding: index,
|
|
1111
|
-
visibility: GPUShaderStage.COMPUTE,
|
|
1112
|
-
storageTexture: {
|
|
1113
|
-
access: 'write-only' as GPUStorageTextureAccess,
|
|
1114
|
-
format: (texDef?.format ?? 'rgba8unorm') as GPUTextureFormat,
|
|
1115
|
-
viewDimension: '2d'
|
|
1116
|
-
}
|
|
1117
|
-
};
|
|
1118
|
-
});
|
|
1215
|
+
: computeStorageTextureLayoutEntries;
|
|
1119
1216
|
const storageTextureBGL =
|
|
1120
1217
|
storageTextureBGLEntries.length > 0
|
|
1121
1218
|
? device.createBindGroupLayout({ entries: storageTextureBGLEntries })
|
|
@@ -1133,13 +1230,23 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
1133
1230
|
}
|
|
1134
1231
|
|
|
1135
1232
|
const computePipelineLayout = device.createPipelineLayout({ bindGroupLayouts });
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1233
|
+
let pipeline: GPUComputePipeline;
|
|
1234
|
+
try {
|
|
1235
|
+
pipeline = device.createComputePipeline({
|
|
1236
|
+
layout: computePipelineLayout,
|
|
1237
|
+
compute: {
|
|
1238
|
+
module: computeShaderModule,
|
|
1239
|
+
entryPoint: 'compute'
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
throw toComputeCompilationError({
|
|
1244
|
+
error,
|
|
1245
|
+
lineMap: builtComputeShader.lineMap,
|
|
1246
|
+
computeSource: buildOptions.computeSource,
|
|
1247
|
+
runtimeContext
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1143
1250
|
|
|
1144
1251
|
// Build uniform bind group for compute (group 0)
|
|
1145
1252
|
const computeUniformBindGroup = device.createBindGroup({
|
|
@@ -1162,63 +1269,49 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
1162
1269
|
|
|
1163
1270
|
// Helper to get the storage bind group for dispatch
|
|
1164
1271
|
const getComputeStorageBindGroup = (): GPUBindGroup | null => {
|
|
1165
|
-
if (
|
|
1272
|
+
if (computeStorageBufferLayoutEntries.length === 0) {
|
|
1166
1273
|
return null;
|
|
1167
1274
|
}
|
|
1168
|
-
|
|
1169
|
-
const storageBGLEntries: GPUBindGroupLayoutEntry[] = storageBufferKeys.map((key, index) => {
|
|
1170
|
-
const def = storageBufferDefinitions[key];
|
|
1171
|
-
const access = def?.access ?? 'read-write';
|
|
1172
|
-
const bufferType: GPUBufferBindingType =
|
|
1173
|
-
access === 'read' ? 'read-only-storage' : 'storage';
|
|
1174
|
-
return {
|
|
1175
|
-
binding: index,
|
|
1176
|
-
visibility: GPUShaderStage.COMPUTE,
|
|
1177
|
-
buffer: { type: bufferType }
|
|
1178
|
-
};
|
|
1179
|
-
});
|
|
1180
|
-
const storageBGL = device.createBindGroupLayout({ entries: storageBGLEntries });
|
|
1181
|
-
const storageEntries: GPUBindGroupEntry[] = storageBufferKeys.map((key, index) => {
|
|
1275
|
+
const resources: GPUBuffer[] = storageBufferKeys.map((key) => {
|
|
1182
1276
|
const buffer = storageBufferMap.get(key);
|
|
1183
1277
|
if (!buffer) {
|
|
1184
1278
|
throw new Error(`Storage buffer "${key}" not allocated.`);
|
|
1185
1279
|
}
|
|
1280
|
+
return buffer;
|
|
1281
|
+
});
|
|
1282
|
+
const storageEntries: GPUBindGroupEntry[] = resources.map((buffer, index) => {
|
|
1186
1283
|
return { binding: index, resource: { buffer } };
|
|
1187
1284
|
});
|
|
1188
|
-
return
|
|
1189
|
-
|
|
1190
|
-
|
|
1285
|
+
return computeStorageBufferBindGroupCache.getOrCreate({
|
|
1286
|
+
topologyKey: computeStorageBufferTopologyKey,
|
|
1287
|
+
layoutEntries: computeStorageBufferLayoutEntries,
|
|
1288
|
+
bindGroupEntries: storageEntries,
|
|
1289
|
+
resourceRefs: resources
|
|
1191
1290
|
});
|
|
1192
1291
|
};
|
|
1193
1292
|
|
|
1194
1293
|
// Helper to get the storage texture bind group for compute dispatch (group 2)
|
|
1195
1294
|
const getComputeStorageTextureBindGroup = (): GPUBindGroup | null => {
|
|
1196
|
-
if (
|
|
1295
|
+
if (computeStorageTextureLayoutEntries.length === 0) {
|
|
1197
1296
|
return null;
|
|
1198
1297
|
}
|
|
1199
|
-
const
|
|
1200
|
-
const
|
|
1201
|
-
return {
|
|
1202
|
-
binding: index,
|
|
1203
|
-
visibility: GPUShaderStage.COMPUTE,
|
|
1204
|
-
storageTexture: {
|
|
1205
|
-
access: 'write-only' as GPUStorageTextureAccess,
|
|
1206
|
-
format: (texDef?.format ?? 'rgba8unorm') as GPUTextureFormat,
|
|
1207
|
-
viewDimension: '2d' as GPUTextureViewDimension
|
|
1208
|
-
}
|
|
1209
|
-
};
|
|
1210
|
-
});
|
|
1211
|
-
const bgl = device.createBindGroupLayout({ entries });
|
|
1212
|
-
|
|
1213
|
-
const bgEntries: GPUBindGroupEntry[] = storageTextureKeys.map((key, index) => {
|
|
1214
|
-
const binding = textureBindings.find((b) => b.key === key);
|
|
1298
|
+
const resources: GPUTextureView[] = storageTextureKeys.map((key) => {
|
|
1299
|
+
const binding = textureBindingByKey.get(key);
|
|
1215
1300
|
if (!binding || !binding.texture) {
|
|
1216
1301
|
throw new Error(`Storage texture "${key}" not allocated.`);
|
|
1217
1302
|
}
|
|
1218
|
-
return
|
|
1303
|
+
return binding.view;
|
|
1304
|
+
});
|
|
1305
|
+
const bgEntries: GPUBindGroupEntry[] = resources.map((view, index) => {
|
|
1306
|
+
return { binding: index, resource: view };
|
|
1219
1307
|
});
|
|
1220
1308
|
|
|
1221
|
-
return
|
|
1309
|
+
return computeStorageTextureBindGroupCache.getOrCreate({
|
|
1310
|
+
topologyKey: computeStorageTextureTopologyKey,
|
|
1311
|
+
layoutEntries: computeStorageTextureLayoutEntries,
|
|
1312
|
+
bindGroupEntries: bgEntries,
|
|
1313
|
+
resourceRefs: resources
|
|
1314
|
+
});
|
|
1222
1315
|
};
|
|
1223
1316
|
|
|
1224
1317
|
// Helper to get ping-pong storage texture bind group for compute dispatch (group 2)
|
|
@@ -1227,15 +1320,28 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
1227
1320
|
readFromA: boolean
|
|
1228
1321
|
): GPUBindGroup => {
|
|
1229
1322
|
const pair = ensurePingPongTexturePair(target);
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1323
|
+
if (readFromA) {
|
|
1324
|
+
if (!pair.readAWriteBBindGroup) {
|
|
1325
|
+
pair.readAWriteBBindGroup = device.createBindGroup({
|
|
1326
|
+
layout: pair.bindGroupLayout,
|
|
1327
|
+
entries: [
|
|
1328
|
+
{ binding: 0, resource: pair.viewA },
|
|
1329
|
+
{ binding: 1, resource: pair.viewB }
|
|
1330
|
+
]
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
return pair.readAWriteBBindGroup;
|
|
1334
|
+
}
|
|
1335
|
+
if (!pair.readBWriteABindGroup) {
|
|
1336
|
+
pair.readBWriteABindGroup = device.createBindGroup({
|
|
1337
|
+
layout: pair.bindGroupLayout,
|
|
1338
|
+
entries: [
|
|
1339
|
+
{ binding: 0, resource: pair.viewB },
|
|
1340
|
+
{ binding: 1, resource: pair.viewA }
|
|
1341
|
+
]
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
return pair.readBWriteABindGroup;
|
|
1239
1345
|
};
|
|
1240
1346
|
|
|
1241
1347
|
const frameBuffer = device.createBuffer({
|
|
@@ -1267,7 +1373,7 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
1267
1373
|
{ binding: UNIFORM_BINDING, resource: { buffer: uniformBuffer } }
|
|
1268
1374
|
];
|
|
1269
1375
|
|
|
1270
|
-
for (const binding of
|
|
1376
|
+
for (const binding of fragmentTextureBindings) {
|
|
1271
1377
|
entries.push({
|
|
1272
1378
|
binding: binding.samplerBinding,
|
|
1273
1379
|
resource: binding.sampler
|
|
@@ -1839,7 +1945,7 @@ export async function createRenderer(options: RendererOptions): Promise<Renderer
|
|
|
1839
1945
|
if (storageTextureKeySet.has(binding.key)) continue;
|
|
1840
1946
|
const nextTexture =
|
|
1841
1947
|
textures[binding.key] ?? normalizedTextureDefinitions[binding.key]?.source ?? null;
|
|
1842
|
-
if (updateTextureBinding(binding, nextTexture, renderMode)) {
|
|
1948
|
+
if (updateTextureBinding(binding, nextTexture, renderMode) && binding.fragmentVisible) {
|
|
1843
1949
|
bindGroupDirty = true;
|
|
1844
1950
|
}
|
|
1845
1951
|
}
|