@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/core/renderer.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { packUniformsInto } from "./uniforms.js";
|
|
2
2
|
import { getTextureMipLevelCount, normalizeTextureDefinitions, resolveTextureSize, resolveTextureUpdateMode, toTextureData } from "./textures.js";
|
|
3
|
+
import { normalizeStorageBufferDefinition } from "./storage-buffers.js";
|
|
3
4
|
import { attachShaderCompilationDiagnostics } from "./error-diagnostics.js";
|
|
4
5
|
import { buildShaderSourceWithMap, formatShaderSourceLocation } from "./shader.js";
|
|
5
6
|
import { buildRenderTargetSignature, resolveRenderTargetDefinitions } from "./render-targets.js";
|
|
6
7
|
import { planRenderGraph } from "./render-graph.js";
|
|
8
|
+
import { buildComputeShaderSourceWithMap, buildPingPongComputeShaderSourceWithMap, extractWorkgroupSize, storageTextureSampleScalarType } from "./compute-shader.js";
|
|
9
|
+
import { createComputeStorageBindGroupCache } from "./compute-bindgroup-cache.js";
|
|
7
10
|
//#region src/lib/core/renderer.ts
|
|
8
11
|
/**
|
|
9
12
|
* Binding index for frame uniforms (`time`, `delta`, `resolution`).
|
|
@@ -28,6 +31,14 @@ function getTextureBindings(index) {
|
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
34
|
+
* Maps WGSL scalar texture type to WebGPU sampled texture bind-group sample type.
|
|
35
|
+
*/
|
|
36
|
+
function toGpuTextureSampleType(type) {
|
|
37
|
+
if (type === "u32") return "uint";
|
|
38
|
+
if (type === "i32") return "sint";
|
|
39
|
+
return "float";
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
31
42
|
* Resizes canvas backing store to match client size and DPR.
|
|
32
43
|
*/
|
|
33
44
|
function resizeCanvas(canvas, dprInput, cssSize) {
|
|
@@ -64,10 +75,13 @@ async function assertCompilation(module, options) {
|
|
|
64
75
|
if (contextLabel.length === 0) return diagnostic.message;
|
|
65
76
|
return `[${contextLabel.join(" | ")}] ${diagnostic.message}`;
|
|
66
77
|
}).join("\n");
|
|
67
|
-
|
|
78
|
+
const prefix = options?.errorPrefix ?? "WGSL compilation failed";
|
|
79
|
+
throw attachShaderCompilationDiagnostics(/* @__PURE__ */ new Error(`${prefix}:\n${summary}`), {
|
|
68
80
|
kind: "shader-compilation",
|
|
81
|
+
...options?.shaderStage !== void 0 ? { shaderStage: options.shaderStage } : {},
|
|
69
82
|
diagnostics,
|
|
70
83
|
fragmentSource: options?.fragmentSource ?? "",
|
|
84
|
+
...options?.computeSource !== void 0 ? { computeSource: options.computeSource } : {},
|
|
71
85
|
includeSources: options?.includeSources ?? {},
|
|
72
86
|
...options?.defineBlockSource !== void 0 ? { defineBlockSource: options.defineBlockSource } : {},
|
|
73
87
|
materialSource: options?.materialSource ?? null,
|
|
@@ -77,6 +91,41 @@ async function assertCompilation(module, options) {
|
|
|
77
91
|
function toSortedUniqueStrings(values) {
|
|
78
92
|
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
79
93
|
}
|
|
94
|
+
function extractGeneratedLineFromComputeError(message) {
|
|
95
|
+
const lineMatch = message.match(/\bline\s+(\d+)\b/i);
|
|
96
|
+
if (lineMatch) {
|
|
97
|
+
const parsed = Number.parseInt(lineMatch[1] ?? "", 10);
|
|
98
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
99
|
+
}
|
|
100
|
+
const colonMatch = message.match(/:(\d+):\d+/);
|
|
101
|
+
if (colonMatch) {
|
|
102
|
+
const parsed = Number.parseInt(colonMatch[1] ?? "", 10);
|
|
103
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
function toComputeCompilationError(input) {
|
|
108
|
+
const baseError = input.error instanceof Error ? input.error : new Error(String(input.error ?? "Unknown error"));
|
|
109
|
+
const generatedLine = extractGeneratedLineFromComputeError(baseError.message) ?? 0;
|
|
110
|
+
const sourceLocation = generatedLine > 0 ? input.lineMap[generatedLine] ?? null : null;
|
|
111
|
+
const diagnostics = [{
|
|
112
|
+
generatedLine,
|
|
113
|
+
message: baseError.message,
|
|
114
|
+
sourceLocation
|
|
115
|
+
}];
|
|
116
|
+
const contextLabel = [formatShaderSourceLocation(sourceLocation), generatedLine > 0 ? `generated WGSL line ${generatedLine}` : null].filter((value) => Boolean(value));
|
|
117
|
+
const summary = contextLabel.length > 0 ? `[${contextLabel.join(" | ")}] ${baseError.message}` : baseError.message;
|
|
118
|
+
return attachShaderCompilationDiagnostics(/* @__PURE__ */ new Error(`Compute shader compilation failed:\n${summary}`), {
|
|
119
|
+
kind: "shader-compilation",
|
|
120
|
+
shaderStage: "compute",
|
|
121
|
+
diagnostics,
|
|
122
|
+
fragmentSource: "",
|
|
123
|
+
computeSource: input.computeSource,
|
|
124
|
+
includeSources: {},
|
|
125
|
+
materialSource: null,
|
|
126
|
+
runtimeContext: input.runtimeContext
|
|
127
|
+
});
|
|
128
|
+
}
|
|
80
129
|
function buildPassGraphSnapshot(passes) {
|
|
81
130
|
const declaredPasses = passes ?? [];
|
|
82
131
|
let enabledPassCount = 0;
|
|
@@ -85,9 +134,11 @@ function buildPassGraphSnapshot(passes) {
|
|
|
85
134
|
for (const pass of declaredPasses) {
|
|
86
135
|
if (pass.enabled === false) continue;
|
|
87
136
|
enabledPassCount += 1;
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
137
|
+
if ("isCompute" in pass && pass.isCompute === true) continue;
|
|
138
|
+
const rp = pass;
|
|
139
|
+
const needsSwap = rp.needsSwap ?? true;
|
|
140
|
+
const input = rp.input ?? "source";
|
|
141
|
+
const output = rp.output ?? (needsSwap ? "target" : "source");
|
|
91
142
|
inputs.push(input);
|
|
92
143
|
outputs.push(output);
|
|
93
144
|
}
|
|
@@ -385,9 +436,12 @@ async function createRenderer(options) {
|
|
|
385
436
|
try {
|
|
386
437
|
const runtimeContext = buildShaderCompilationRuntimeContext(options);
|
|
387
438
|
const convertLinearToSrgb = shouldConvertLinearToSrgb(options.outputColorSpace, format);
|
|
388
|
-
const
|
|
439
|
+
const fragmentTextureKeys = options.textureKeys.filter((key) => options.textureDefinitions[key]?.fragmentVisible !== false);
|
|
440
|
+
const builtShader = buildShaderSourceWithMap(options.fragmentWgsl, options.uniformLayout, fragmentTextureKeys, {
|
|
389
441
|
convertLinearToSrgb,
|
|
390
|
-
fragmentLineMap: options.fragmentLineMap
|
|
442
|
+
fragmentLineMap: options.fragmentLineMap,
|
|
443
|
+
...options.storageBufferKeys !== void 0 ? { storageBufferKeys: options.storageBufferKeys } : {},
|
|
444
|
+
...options.storageBufferDefinitions !== void 0 ? { storageBufferDefinitions: options.storageBufferDefinitions } : {}
|
|
391
445
|
});
|
|
392
446
|
const shaderModule = device.createShaderModule({ code: builtShader.code });
|
|
393
447
|
await assertCompilation(shaderModule, {
|
|
@@ -399,10 +453,17 @@ async function createRenderer(options) {
|
|
|
399
453
|
runtimeContext
|
|
400
454
|
});
|
|
401
455
|
const normalizedTextureDefinitions = normalizeTextureDefinitions(options.textureDefinitions, options.textureKeys);
|
|
402
|
-
const
|
|
456
|
+
const storageBufferKeys = options.storageBufferKeys ?? [];
|
|
457
|
+
const storageBufferDefinitions = options.storageBufferDefinitions ?? {};
|
|
458
|
+
const storageTextureKeys = options.storageTextureKeys ?? [];
|
|
459
|
+
const storageTextureKeySet = new Set(storageTextureKeys);
|
|
460
|
+
const fragmentTextureIndexByKey = new Map(fragmentTextureKeys.map((key, index) => [key, index]));
|
|
461
|
+
const textureBindings = options.textureKeys.map((key) => {
|
|
403
462
|
const config = normalizedTextureDefinitions[key];
|
|
404
463
|
if (!config) throw new Error(`Missing texture definition for "${key}"`);
|
|
405
|
-
const
|
|
464
|
+
const fragmentTextureIndex = fragmentTextureIndexByKey.get(key);
|
|
465
|
+
const fragmentVisible = fragmentTextureIndex !== void 0;
|
|
466
|
+
const { samplerBinding, textureBinding } = getTextureBindings(fragmentTextureIndex ?? 0);
|
|
406
467
|
const sampler = device.createSampler({
|
|
407
468
|
magFilter: config.filter,
|
|
408
469
|
minFilter: config.filter,
|
|
@@ -411,7 +472,7 @@ async function createRenderer(options) {
|
|
|
411
472
|
addressModeV: config.addressModeV,
|
|
412
473
|
maxAnisotropy: config.filter === "linear" ? config.anisotropy : 1
|
|
413
474
|
});
|
|
414
|
-
const fallbackTexture = createFallbackTexture(device, config.format);
|
|
475
|
+
const fallbackTexture = createFallbackTexture(device, config.storage ? "rgba8unorm" : config.format);
|
|
415
476
|
registerInitializationCleanup(() => {
|
|
416
477
|
fallbackTexture.destroy();
|
|
417
478
|
});
|
|
@@ -420,6 +481,7 @@ async function createRenderer(options) {
|
|
|
420
481
|
key,
|
|
421
482
|
samplerBinding,
|
|
422
483
|
textureBinding,
|
|
484
|
+
fragmentVisible,
|
|
423
485
|
sampler,
|
|
424
486
|
fallbackTexture,
|
|
425
487
|
fallbackView,
|
|
@@ -442,10 +504,60 @@ async function createRenderer(options) {
|
|
|
442
504
|
lastToken: null
|
|
443
505
|
};
|
|
444
506
|
if (config.update !== void 0) runtimeBinding.defaultUpdate = config.update;
|
|
507
|
+
if (config.storage && config.width && config.height) {
|
|
508
|
+
const storageUsage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST;
|
|
509
|
+
const storageTexture = device.createTexture({
|
|
510
|
+
size: {
|
|
511
|
+
width: config.width,
|
|
512
|
+
height: config.height,
|
|
513
|
+
depthOrArrayLayers: 1
|
|
514
|
+
},
|
|
515
|
+
format: config.format,
|
|
516
|
+
usage: storageUsage
|
|
517
|
+
});
|
|
518
|
+
registerInitializationCleanup(() => {
|
|
519
|
+
storageTexture.destroy();
|
|
520
|
+
});
|
|
521
|
+
runtimeBinding.texture = storageTexture;
|
|
522
|
+
runtimeBinding.view = storageTexture.createView();
|
|
523
|
+
runtimeBinding.width = config.width;
|
|
524
|
+
runtimeBinding.height = config.height;
|
|
525
|
+
}
|
|
445
526
|
return runtimeBinding;
|
|
446
527
|
});
|
|
447
|
-
const
|
|
448
|
-
const
|
|
528
|
+
const textureBindingByKey = new Map(textureBindings.map((binding) => [binding.key, binding]));
|
|
529
|
+
const fragmentTextureBindings = textureBindings.filter((binding) => binding.fragmentVisible);
|
|
530
|
+
const computeStorageBufferLayoutEntries = storageBufferKeys.map((key, index) => {
|
|
531
|
+
const bufferType = (storageBufferDefinitions[key]?.access ?? "read-write") === "read" ? "read-only-storage" : "storage";
|
|
532
|
+
return {
|
|
533
|
+
binding: index,
|
|
534
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
535
|
+
buffer: { type: bufferType }
|
|
536
|
+
};
|
|
537
|
+
});
|
|
538
|
+
const computeStorageBufferTopologyKey = storageBufferKeys.map((key) => `${key}:${storageBufferDefinitions[key]?.access ?? "read-write"}`).join("|");
|
|
539
|
+
const computeStorageTextureLayoutEntries = storageTextureKeys.map((key, index) => {
|
|
540
|
+
const config = normalizedTextureDefinitions[key];
|
|
541
|
+
return {
|
|
542
|
+
binding: index,
|
|
543
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
544
|
+
storageTexture: {
|
|
545
|
+
access: "write-only",
|
|
546
|
+
format: config?.format ?? "rgba8unorm",
|
|
547
|
+
viewDimension: "2d"
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
});
|
|
551
|
+
const computeStorageTextureTopologyKey = storageTextureKeys.map((key) => `${key}:${normalizedTextureDefinitions[key]?.format ?? "rgba8unorm"}`).join("|");
|
|
552
|
+
const computeStorageBufferBindGroupCache = createComputeStorageBindGroupCache(device);
|
|
553
|
+
const computeStorageTextureBindGroupCache = createComputeStorageBindGroupCache(device);
|
|
554
|
+
const bindGroupLayout = device.createBindGroupLayout({ entries: createBindGroupLayoutEntries(fragmentTextureBindings) });
|
|
555
|
+
const fragmentStorageBindGroupLayout = storageBufferKeys.length > 0 ? device.createBindGroupLayout({ entries: storageBufferKeys.map((_, index) => ({
|
|
556
|
+
binding: index,
|
|
557
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
558
|
+
buffer: { type: "read-only-storage" }
|
|
559
|
+
})) }) : null;
|
|
560
|
+
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: fragmentStorageBindGroupLayout ? [bindGroupLayout, fragmentStorageBindGroupLayout] : [bindGroupLayout] });
|
|
449
561
|
const pipeline = device.createRenderPipeline({
|
|
450
562
|
layout: pipelineLayout,
|
|
451
563
|
vertex: {
|
|
@@ -495,6 +607,278 @@ async function createRenderer(options) {
|
|
|
495
607
|
addressModeV: "clamp-to-edge"
|
|
496
608
|
});
|
|
497
609
|
let blitBindGroupByView = /* @__PURE__ */ new WeakMap();
|
|
610
|
+
const storageBufferMap = /* @__PURE__ */ new Map();
|
|
611
|
+
const pingPongTexturePairs = /* @__PURE__ */ new Map();
|
|
612
|
+
for (const key of storageBufferKeys) {
|
|
613
|
+
const definition = storageBufferDefinitions[key];
|
|
614
|
+
if (!definition) continue;
|
|
615
|
+
const normalized = normalizeStorageBufferDefinition(definition);
|
|
616
|
+
const buffer = device.createBuffer({
|
|
617
|
+
size: normalized.size,
|
|
618
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
|
|
619
|
+
});
|
|
620
|
+
registerInitializationCleanup(() => {
|
|
621
|
+
buffer.destroy();
|
|
622
|
+
});
|
|
623
|
+
if (definition.initialData) {
|
|
624
|
+
const data = definition.initialData;
|
|
625
|
+
device.queue.writeBuffer(buffer, 0, data.buffer, data.byteOffset, data.byteLength);
|
|
626
|
+
}
|
|
627
|
+
storageBufferMap.set(key, buffer);
|
|
628
|
+
}
|
|
629
|
+
const fragmentStorageBindGroup = fragmentStorageBindGroupLayout && storageBufferKeys.length > 0 ? device.createBindGroup({
|
|
630
|
+
layout: fragmentStorageBindGroupLayout,
|
|
631
|
+
entries: storageBufferKeys.map((key, index) => {
|
|
632
|
+
const buffer = storageBufferMap.get(key);
|
|
633
|
+
if (!buffer) throw new Error(`Storage buffer "${key}" not allocated.`);
|
|
634
|
+
return {
|
|
635
|
+
binding: index,
|
|
636
|
+
resource: { buffer }
|
|
637
|
+
};
|
|
638
|
+
})
|
|
639
|
+
}) : null;
|
|
640
|
+
const ensurePingPongTexturePair = (target) => {
|
|
641
|
+
const existing = pingPongTexturePairs.get(target);
|
|
642
|
+
if (existing) return existing;
|
|
643
|
+
const config = normalizedTextureDefinitions[target];
|
|
644
|
+
if (!config || !config.storage) throw new Error(`PingPongComputePass target "${target}" must reference a texture declared with storage:true.`);
|
|
645
|
+
if (!config.width || !config.height) throw new Error(`PingPongComputePass target "${target}" requires explicit texture width and height.`);
|
|
646
|
+
const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST;
|
|
647
|
+
const textureA = device.createTexture({
|
|
648
|
+
size: {
|
|
649
|
+
width: config.width,
|
|
650
|
+
height: config.height,
|
|
651
|
+
depthOrArrayLayers: 1
|
|
652
|
+
},
|
|
653
|
+
format: config.format,
|
|
654
|
+
usage
|
|
655
|
+
});
|
|
656
|
+
const textureB = device.createTexture({
|
|
657
|
+
size: {
|
|
658
|
+
width: config.width,
|
|
659
|
+
height: config.height,
|
|
660
|
+
depthOrArrayLayers: 1
|
|
661
|
+
},
|
|
662
|
+
format: config.format,
|
|
663
|
+
usage
|
|
664
|
+
});
|
|
665
|
+
registerInitializationCleanup(() => {
|
|
666
|
+
textureA.destroy();
|
|
667
|
+
});
|
|
668
|
+
registerInitializationCleanup(() => {
|
|
669
|
+
textureB.destroy();
|
|
670
|
+
});
|
|
671
|
+
const sampleType = toGpuTextureSampleType(storageTextureSampleScalarType(config.format));
|
|
672
|
+
const bindGroupLayout = device.createBindGroupLayout({ entries: [{
|
|
673
|
+
binding: 0,
|
|
674
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
675
|
+
texture: {
|
|
676
|
+
sampleType,
|
|
677
|
+
viewDimension: "2d",
|
|
678
|
+
multisampled: false
|
|
679
|
+
}
|
|
680
|
+
}, {
|
|
681
|
+
binding: 1,
|
|
682
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
683
|
+
storageTexture: {
|
|
684
|
+
access: "write-only",
|
|
685
|
+
format: config.format,
|
|
686
|
+
viewDimension: "2d"
|
|
687
|
+
}
|
|
688
|
+
}] });
|
|
689
|
+
const pair = {
|
|
690
|
+
target,
|
|
691
|
+
format: config.format,
|
|
692
|
+
width: config.width,
|
|
693
|
+
height: config.height,
|
|
694
|
+
textureA,
|
|
695
|
+
viewA: textureA.createView(),
|
|
696
|
+
textureB,
|
|
697
|
+
viewB: textureB.createView(),
|
|
698
|
+
bindGroupLayout,
|
|
699
|
+
readAWriteBBindGroup: null,
|
|
700
|
+
readBWriteABindGroup: null
|
|
701
|
+
};
|
|
702
|
+
pingPongTexturePairs.set(target, pair);
|
|
703
|
+
return pair;
|
|
704
|
+
};
|
|
705
|
+
const computePipelineCache = /* @__PURE__ */ new Map();
|
|
706
|
+
const buildComputePipelineEntry = (buildOptions) => {
|
|
707
|
+
const cacheKey = buildOptions.pingPongTarget && buildOptions.pingPongFormat ? `pingpong:${buildOptions.pingPongTarget}:${buildOptions.pingPongFormat}:${buildOptions.computeSource}` : `compute:${buildOptions.computeSource}`;
|
|
708
|
+
const cached = computePipelineCache.get(cacheKey);
|
|
709
|
+
if (cached) return cached;
|
|
710
|
+
const storageBufferDefs = {};
|
|
711
|
+
for (const key of storageBufferKeys) {
|
|
712
|
+
const def = storageBufferDefinitions[key];
|
|
713
|
+
if (def) {
|
|
714
|
+
const norm = normalizeStorageBufferDefinition(def);
|
|
715
|
+
storageBufferDefs[key] = {
|
|
716
|
+
type: norm.type,
|
|
717
|
+
access: norm.access
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
const storageTextureDefs = {};
|
|
722
|
+
for (const key of storageTextureKeys) {
|
|
723
|
+
const texDef = options.textureDefinitions[key];
|
|
724
|
+
if (texDef?.format) storageTextureDefs[key] = { format: texDef.format };
|
|
725
|
+
}
|
|
726
|
+
const isPingPongPipeline = Boolean(buildOptions.pingPongTarget && buildOptions.pingPongFormat);
|
|
727
|
+
const builtComputeShader = isPingPongPipeline ? buildPingPongComputeShaderSourceWithMap({
|
|
728
|
+
compute: buildOptions.computeSource,
|
|
729
|
+
uniformLayout: options.uniformLayout,
|
|
730
|
+
storageBufferKeys,
|
|
731
|
+
storageBufferDefinitions: storageBufferDefs,
|
|
732
|
+
target: buildOptions.pingPongTarget,
|
|
733
|
+
targetFormat: buildOptions.pingPongFormat
|
|
734
|
+
}) : buildComputeShaderSourceWithMap({
|
|
735
|
+
compute: buildOptions.computeSource,
|
|
736
|
+
uniformLayout: options.uniformLayout,
|
|
737
|
+
storageBufferKeys,
|
|
738
|
+
storageBufferDefinitions: storageBufferDefs,
|
|
739
|
+
storageTextureKeys,
|
|
740
|
+
storageTextureDefinitions: storageTextureDefs
|
|
741
|
+
});
|
|
742
|
+
const computeShaderModule = device.createShaderModule({ code: builtComputeShader.code });
|
|
743
|
+
const workgroupSize = extractWorkgroupSize(buildOptions.computeSource);
|
|
744
|
+
const computeUniformBGL = device.createBindGroupLayout({ entries: [{
|
|
745
|
+
binding: FRAME_BINDING,
|
|
746
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
747
|
+
buffer: {
|
|
748
|
+
type: "uniform",
|
|
749
|
+
minBindingSize: 16
|
|
750
|
+
}
|
|
751
|
+
}, {
|
|
752
|
+
binding: UNIFORM_BINDING,
|
|
753
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
754
|
+
buffer: { type: "uniform" }
|
|
755
|
+
}] });
|
|
756
|
+
const storageBGL = computeStorageBufferLayoutEntries.length > 0 ? device.createBindGroupLayout({ entries: computeStorageBufferLayoutEntries }) : null;
|
|
757
|
+
const storageTextureBGLEntries = isPingPongPipeline ? [{
|
|
758
|
+
binding: 0,
|
|
759
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
760
|
+
texture: {
|
|
761
|
+
sampleType: toGpuTextureSampleType(storageTextureSampleScalarType(buildOptions.pingPongFormat)),
|
|
762
|
+
viewDimension: "2d",
|
|
763
|
+
multisampled: false
|
|
764
|
+
}
|
|
765
|
+
}, {
|
|
766
|
+
binding: 1,
|
|
767
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
768
|
+
storageTexture: {
|
|
769
|
+
access: "write-only",
|
|
770
|
+
format: buildOptions.pingPongFormat,
|
|
771
|
+
viewDimension: "2d"
|
|
772
|
+
}
|
|
773
|
+
}] : computeStorageTextureLayoutEntries;
|
|
774
|
+
const storageTextureBGL = storageTextureBGLEntries.length > 0 ? device.createBindGroupLayout({ entries: storageTextureBGLEntries }) : null;
|
|
775
|
+
const bindGroupLayouts = [computeUniformBGL];
|
|
776
|
+
if (storageBGL || storageTextureBGL) bindGroupLayouts.push(storageBGL ?? device.createBindGroupLayout({ entries: [] }));
|
|
777
|
+
if (storageTextureBGL) bindGroupLayouts.push(storageTextureBGL);
|
|
778
|
+
const computePipelineLayout = device.createPipelineLayout({ bindGroupLayouts });
|
|
779
|
+
let pipeline;
|
|
780
|
+
try {
|
|
781
|
+
pipeline = device.createComputePipeline({
|
|
782
|
+
layout: computePipelineLayout,
|
|
783
|
+
compute: {
|
|
784
|
+
module: computeShaderModule,
|
|
785
|
+
entryPoint: "compute"
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
} catch (error) {
|
|
789
|
+
throw toComputeCompilationError({
|
|
790
|
+
error,
|
|
791
|
+
lineMap: builtComputeShader.lineMap,
|
|
792
|
+
computeSource: buildOptions.computeSource,
|
|
793
|
+
runtimeContext
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
const computeUniformBindGroup = device.createBindGroup({
|
|
797
|
+
layout: computeUniformBGL,
|
|
798
|
+
entries: [{
|
|
799
|
+
binding: FRAME_BINDING,
|
|
800
|
+
resource: { buffer: frameBuffer }
|
|
801
|
+
}, {
|
|
802
|
+
binding: UNIFORM_BINDING,
|
|
803
|
+
resource: { buffer: uniformBuffer }
|
|
804
|
+
}]
|
|
805
|
+
});
|
|
806
|
+
const entry = {
|
|
807
|
+
pipeline,
|
|
808
|
+
bindGroup: computeUniformBindGroup,
|
|
809
|
+
workgroupSize,
|
|
810
|
+
computeSource: buildOptions.computeSource
|
|
811
|
+
};
|
|
812
|
+
computePipelineCache.set(cacheKey, entry);
|
|
813
|
+
return entry;
|
|
814
|
+
};
|
|
815
|
+
const getComputeStorageBindGroup = () => {
|
|
816
|
+
if (computeStorageBufferLayoutEntries.length === 0) return null;
|
|
817
|
+
const resources = storageBufferKeys.map((key) => {
|
|
818
|
+
const buffer = storageBufferMap.get(key);
|
|
819
|
+
if (!buffer) throw new Error(`Storage buffer "${key}" not allocated.`);
|
|
820
|
+
return buffer;
|
|
821
|
+
});
|
|
822
|
+
const storageEntries = resources.map((buffer, index) => {
|
|
823
|
+
return {
|
|
824
|
+
binding: index,
|
|
825
|
+
resource: { buffer }
|
|
826
|
+
};
|
|
827
|
+
});
|
|
828
|
+
return computeStorageBufferBindGroupCache.getOrCreate({
|
|
829
|
+
topologyKey: computeStorageBufferTopologyKey,
|
|
830
|
+
layoutEntries: computeStorageBufferLayoutEntries,
|
|
831
|
+
bindGroupEntries: storageEntries,
|
|
832
|
+
resourceRefs: resources
|
|
833
|
+
});
|
|
834
|
+
};
|
|
835
|
+
const getComputeStorageTextureBindGroup = () => {
|
|
836
|
+
if (computeStorageTextureLayoutEntries.length === 0) return null;
|
|
837
|
+
const resources = storageTextureKeys.map((key) => {
|
|
838
|
+
const binding = textureBindingByKey.get(key);
|
|
839
|
+
if (!binding || !binding.texture) throw new Error(`Storage texture "${key}" not allocated.`);
|
|
840
|
+
return binding.view;
|
|
841
|
+
});
|
|
842
|
+
const bgEntries = resources.map((view, index) => {
|
|
843
|
+
return {
|
|
844
|
+
binding: index,
|
|
845
|
+
resource: view
|
|
846
|
+
};
|
|
847
|
+
});
|
|
848
|
+
return computeStorageTextureBindGroupCache.getOrCreate({
|
|
849
|
+
topologyKey: computeStorageTextureTopologyKey,
|
|
850
|
+
layoutEntries: computeStorageTextureLayoutEntries,
|
|
851
|
+
bindGroupEntries: bgEntries,
|
|
852
|
+
resourceRefs: resources
|
|
853
|
+
});
|
|
854
|
+
};
|
|
855
|
+
const getPingPongStorageTextureBindGroup = (target, readFromA) => {
|
|
856
|
+
const pair = ensurePingPongTexturePair(target);
|
|
857
|
+
if (readFromA) {
|
|
858
|
+
if (!pair.readAWriteBBindGroup) pair.readAWriteBBindGroup = device.createBindGroup({
|
|
859
|
+
layout: pair.bindGroupLayout,
|
|
860
|
+
entries: [{
|
|
861
|
+
binding: 0,
|
|
862
|
+
resource: pair.viewA
|
|
863
|
+
}, {
|
|
864
|
+
binding: 1,
|
|
865
|
+
resource: pair.viewB
|
|
866
|
+
}]
|
|
867
|
+
});
|
|
868
|
+
return pair.readAWriteBBindGroup;
|
|
869
|
+
}
|
|
870
|
+
if (!pair.readBWriteABindGroup) pair.readBWriteABindGroup = device.createBindGroup({
|
|
871
|
+
layout: pair.bindGroupLayout,
|
|
872
|
+
entries: [{
|
|
873
|
+
binding: 0,
|
|
874
|
+
resource: pair.viewB
|
|
875
|
+
}, {
|
|
876
|
+
binding: 1,
|
|
877
|
+
resource: pair.viewA
|
|
878
|
+
}]
|
|
879
|
+
});
|
|
880
|
+
return pair.readBWriteABindGroup;
|
|
881
|
+
};
|
|
498
882
|
const frameBuffer = device.createBuffer({
|
|
499
883
|
size: 16,
|
|
500
884
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
@@ -524,7 +908,7 @@ async function createRenderer(options) {
|
|
|
524
908
|
binding: UNIFORM_BINDING,
|
|
525
909
|
resource: { buffer: uniformBuffer }
|
|
526
910
|
}];
|
|
527
|
-
for (const binding of
|
|
911
|
+
for (const binding of fragmentTextureBindings) {
|
|
528
912
|
entries.push({
|
|
529
913
|
binding: binding.samplerBinding,
|
|
530
914
|
resource: binding.sampler
|
|
@@ -588,6 +972,8 @@ async function createRenderer(options) {
|
|
|
588
972
|
binding.lastToken = value;
|
|
589
973
|
return false;
|
|
590
974
|
}
|
|
975
|
+
let textureUsage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT;
|
|
976
|
+
if (storageTextureKeySet.has(binding.key)) textureUsage |= GPUTextureUsage.STORAGE_BINDING;
|
|
591
977
|
const texture = device.createTexture({
|
|
592
978
|
size: {
|
|
593
979
|
width,
|
|
@@ -596,7 +982,7 @@ async function createRenderer(options) {
|
|
|
596
982
|
},
|
|
597
983
|
format,
|
|
598
984
|
mipLevelCount,
|
|
599
|
-
usage:
|
|
985
|
+
usage: textureUsage
|
|
600
986
|
});
|
|
601
987
|
registerInitializationCleanup(() => {
|
|
602
988
|
texture.destroy();
|
|
@@ -618,7 +1004,10 @@ async function createRenderer(options) {
|
|
|
618
1004
|
binding.lastToken = value;
|
|
619
1005
|
return true;
|
|
620
1006
|
};
|
|
621
|
-
for (const binding of textureBindings)
|
|
1007
|
+
for (const binding of textureBindings) {
|
|
1008
|
+
if (storageTextureKeySet.has(binding.key)) continue;
|
|
1009
|
+
updateTextureBinding(binding, normalizedTextureDefinitions[binding.key]?.source ?? null, "always");
|
|
1010
|
+
}
|
|
622
1011
|
let bindGroup = createBindGroup();
|
|
623
1012
|
let sourceSlotTarget = null;
|
|
624
1013
|
let targetSlotTarget = null;
|
|
@@ -667,10 +1056,11 @@ async function createRenderer(options) {
|
|
|
667
1056
|
if (cachedGraphPasses.length !== passes.length) return false;
|
|
668
1057
|
for (let index = 0; index < passes.length; index += 1) {
|
|
669
1058
|
const pass = passes[index];
|
|
1059
|
+
const rp = pass;
|
|
670
1060
|
const snapshot = cachedGraphPasses[index];
|
|
671
1061
|
if (!pass || !snapshot || snapshot.pass !== pass) return false;
|
|
672
|
-
if (snapshot.enabled !== pass.enabled || snapshot.needsSwap !==
|
|
673
|
-
const passClearColor =
|
|
1062
|
+
if (snapshot.enabled !== pass.enabled || snapshot.needsSwap !== rp.needsSwap || snapshot.input !== rp.input || snapshot.output !== rp.output || snapshot.clear !== rp.clear || snapshot.preserve !== rp.preserve) return false;
|
|
1063
|
+
const passClearColor = rp.clearColor;
|
|
674
1064
|
const hasPassClearColor = passClearColor !== void 0;
|
|
675
1065
|
if (snapshot.hasClearColor !== hasPassClearColor) return false;
|
|
676
1066
|
if (passClearColor) {
|
|
@@ -692,18 +1082,19 @@ async function createRenderer(options) {
|
|
|
692
1082
|
cachedGraphPasses.length = passes.length;
|
|
693
1083
|
let index = 0;
|
|
694
1084
|
for (const pass of passes) {
|
|
695
|
-
const
|
|
1085
|
+
const rp = pass;
|
|
1086
|
+
const passClearColor = rp.clearColor;
|
|
696
1087
|
const hasPassClearColor = passClearColor !== void 0;
|
|
697
1088
|
const snapshot = cachedGraphPasses[index];
|
|
698
1089
|
if (!snapshot) {
|
|
699
1090
|
cachedGraphPasses[index] = {
|
|
700
1091
|
pass,
|
|
701
1092
|
enabled: pass.enabled,
|
|
702
|
-
needsSwap:
|
|
703
|
-
input:
|
|
704
|
-
output:
|
|
705
|
-
clear:
|
|
706
|
-
preserve:
|
|
1093
|
+
needsSwap: rp.needsSwap,
|
|
1094
|
+
input: rp.input,
|
|
1095
|
+
output: rp.output,
|
|
1096
|
+
clear: rp.clear,
|
|
1097
|
+
preserve: rp.preserve,
|
|
707
1098
|
hasClearColor: hasPassClearColor,
|
|
708
1099
|
clearColor0: passClearColor?.[0] ?? 0,
|
|
709
1100
|
clearColor1: passClearColor?.[1] ?? 0,
|
|
@@ -715,11 +1106,11 @@ async function createRenderer(options) {
|
|
|
715
1106
|
}
|
|
716
1107
|
snapshot.pass = pass;
|
|
717
1108
|
snapshot.enabled = pass.enabled;
|
|
718
|
-
snapshot.needsSwap =
|
|
719
|
-
snapshot.input =
|
|
720
|
-
snapshot.output =
|
|
721
|
-
snapshot.clear =
|
|
722
|
-
snapshot.preserve =
|
|
1109
|
+
snapshot.needsSwap = rp.needsSwap;
|
|
1110
|
+
snapshot.input = rp.input;
|
|
1111
|
+
snapshot.output = rp.output;
|
|
1112
|
+
snapshot.clear = rp.clear;
|
|
1113
|
+
snapshot.preserve = rp.preserve;
|
|
723
1114
|
snapshot.hasClearColor = hasPassClearColor;
|
|
724
1115
|
snapshot.clearColor0 = passClearColor?.[0] ?? 0;
|
|
725
1116
|
snapshot.clearColor1 = passClearColor?.[1] ?? 0;
|
|
@@ -845,7 +1236,7 @@ async function createRenderer(options) {
|
|
|
845
1236
|
/**
|
|
846
1237
|
* Executes a full frame render.
|
|
847
1238
|
*/
|
|
848
|
-
const render = ({ time, delta, renderMode, uniforms, textures, canvasSize }) => {
|
|
1239
|
+
const render = ({ time, delta, renderMode, uniforms, textures, canvasSize, pendingStorageWrites }) => {
|
|
849
1240
|
if (deviceLostMessage) throw new Error(deviceLostMessage);
|
|
850
1241
|
if (uncapturedErrorMessage) {
|
|
851
1242
|
const message = uncapturedErrorMessage;
|
|
@@ -883,8 +1274,18 @@ async function createRenderer(options) {
|
|
|
883
1274
|
if (dirtyRanges.length > 0) uniformPrevious.set(uniformScratch);
|
|
884
1275
|
}
|
|
885
1276
|
let bindGroupDirty = false;
|
|
886
|
-
for (const binding of textureBindings)
|
|
1277
|
+
for (const binding of textureBindings) {
|
|
1278
|
+
if (storageTextureKeySet.has(binding.key)) continue;
|
|
1279
|
+
if (updateTextureBinding(binding, textures[binding.key] ?? normalizedTextureDefinitions[binding.key]?.source ?? null, renderMode) && binding.fragmentVisible) bindGroupDirty = true;
|
|
1280
|
+
}
|
|
887
1281
|
if (bindGroupDirty) bindGroup = createBindGroup();
|
|
1282
|
+
if (pendingStorageWrites) for (const write of pendingStorageWrites) {
|
|
1283
|
+
const buffer = storageBufferMap.get(write.name);
|
|
1284
|
+
if (buffer) {
|
|
1285
|
+
const data = write.data;
|
|
1286
|
+
device.queue.writeBuffer(buffer, write.offset, data.buffer, data.byteOffset, data.byteLength);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
888
1289
|
const commandEncoder = device.createCommandEncoder();
|
|
889
1290
|
const passes = resolvePasses();
|
|
890
1291
|
const clearColor = options.getClearColor();
|
|
@@ -909,6 +1310,49 @@ async function createRenderer(options) {
|
|
|
909
1310
|
canvas: canvasSurface
|
|
910
1311
|
} : null;
|
|
911
1312
|
const sceneOutput = slots ? slots.source : canvasSurface;
|
|
1313
|
+
if (slots) for (const step of graphPlan.steps) {
|
|
1314
|
+
if (step.kind !== "compute") continue;
|
|
1315
|
+
const computePass = step.pass;
|
|
1316
|
+
if (computePass.getCompute && computePass.resolveDispatch && computePass.getWorkgroupSize) {
|
|
1317
|
+
const computeSource = computePass.getCompute();
|
|
1318
|
+
const pingPongTarget = computePass.isPingPong && computePass.getTarget ? computePass.getTarget() : void 0;
|
|
1319
|
+
if (computePass.isPingPong && !pingPongTarget) throw new Error("PingPongComputePass must provide a target texture key.");
|
|
1320
|
+
const pingPongPair = pingPongTarget ? ensurePingPongTexturePair(pingPongTarget) : null;
|
|
1321
|
+
const pipelineEntry = buildComputePipelineEntry({
|
|
1322
|
+
computeSource,
|
|
1323
|
+
...pingPongPair ? {
|
|
1324
|
+
pingPongTarget: pingPongPair.target,
|
|
1325
|
+
pingPongFormat: pingPongPair.format
|
|
1326
|
+
} : {}
|
|
1327
|
+
});
|
|
1328
|
+
const workgroupSize = computePass.getWorkgroupSize();
|
|
1329
|
+
const storageBindGroup = getComputeStorageBindGroup();
|
|
1330
|
+
const storageTextureBindGroup = getComputeStorageTextureBindGroup();
|
|
1331
|
+
const iterations = computePass.isPingPong && computePass.getIterations ? computePass.getIterations() : 1;
|
|
1332
|
+
const currentOutput = computePass.isPingPong && computePass.getCurrentOutput ? computePass.getCurrentOutput() : null;
|
|
1333
|
+
const readFromAAtIterationZero = pingPongPair && currentOutput ? currentOutput !== `${pingPongPair.target}B` : true;
|
|
1334
|
+
for (let iter = 0; iter < iterations; iter += 1) {
|
|
1335
|
+
const dispatch = computePass.resolveDispatch({
|
|
1336
|
+
width,
|
|
1337
|
+
height,
|
|
1338
|
+
time,
|
|
1339
|
+
delta,
|
|
1340
|
+
workgroupSize
|
|
1341
|
+
});
|
|
1342
|
+
const cPass = commandEncoder.beginComputePass();
|
|
1343
|
+
cPass.setPipeline(pipelineEntry.pipeline);
|
|
1344
|
+
cPass.setBindGroup(0, pipelineEntry.bindGroup);
|
|
1345
|
+
if (storageBindGroup) cPass.setBindGroup(1, storageBindGroup);
|
|
1346
|
+
if (pingPongPair) {
|
|
1347
|
+
const readFromA = iter % 2 === 0 ? readFromAAtIterationZero : !readFromAAtIterationZero;
|
|
1348
|
+
cPass.setBindGroup(2, getPingPongStorageTextureBindGroup(pingPongPair.target, readFromA));
|
|
1349
|
+
} else if (storageTextureBindGroup) cPass.setBindGroup(2, storageTextureBindGroup);
|
|
1350
|
+
cPass.dispatchWorkgroups(dispatch[0], dispatch[1], dispatch[2]);
|
|
1351
|
+
cPass.end();
|
|
1352
|
+
}
|
|
1353
|
+
if (computePass.isPingPong && computePass.advanceFrame) computePass.advanceFrame();
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
912
1356
|
const scenePass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
913
1357
|
view: sceneOutput.view,
|
|
914
1358
|
clearValue: {
|
|
@@ -922,6 +1366,7 @@ async function createRenderer(options) {
|
|
|
922
1366
|
}] });
|
|
923
1367
|
scenePass.setPipeline(pipeline);
|
|
924
1368
|
scenePass.setBindGroup(0, bindGroup);
|
|
1369
|
+
if (fragmentStorageBindGroup) scenePass.setBindGroup(1, fragmentStorageBindGroup);
|
|
925
1370
|
scenePass.draw(3);
|
|
926
1371
|
scenePass.end();
|
|
927
1372
|
if (slots) {
|
|
@@ -934,6 +1379,7 @@ async function createRenderer(options) {
|
|
|
934
1379
|
return named;
|
|
935
1380
|
};
|
|
936
1381
|
for (const step of graphPlan.steps) {
|
|
1382
|
+
if (step.kind === "compute") continue;
|
|
937
1383
|
const input = resolveStepSurface(step.input);
|
|
938
1384
|
const output = resolveStepSurface(step.output);
|
|
939
1385
|
step.pass.render({
|
|
@@ -983,11 +1429,25 @@ async function createRenderer(options) {
|
|
|
983
1429
|
initializationCleanups.length = 0;
|
|
984
1430
|
return {
|
|
985
1431
|
render,
|
|
1432
|
+
getStorageBuffer: (name) => {
|
|
1433
|
+
return storageBufferMap.get(name);
|
|
1434
|
+
},
|
|
1435
|
+
getDevice: () => {
|
|
1436
|
+
return device;
|
|
1437
|
+
},
|
|
986
1438
|
destroy: () => {
|
|
987
1439
|
isDestroyed = true;
|
|
988
1440
|
device.removeEventListener("uncapturederror", handleUncapturedError);
|
|
989
1441
|
frameBuffer.destroy();
|
|
990
1442
|
uniformBuffer.destroy();
|
|
1443
|
+
for (const buffer of storageBufferMap.values()) buffer.destroy();
|
|
1444
|
+
storageBufferMap.clear();
|
|
1445
|
+
for (const pair of pingPongTexturePairs.values()) {
|
|
1446
|
+
pair.textureA.destroy();
|
|
1447
|
+
pair.textureB.destroy();
|
|
1448
|
+
}
|
|
1449
|
+
pingPongTexturePairs.clear();
|
|
1450
|
+
computePipelineCache.clear();
|
|
991
1451
|
destroyRenderTexture(sourceSlotTarget);
|
|
992
1452
|
destroyRenderTexture(targetSlotTarget);
|
|
993
1453
|
for (const target of runtimeRenderTargets.values()) target.texture.destroy();
|