@motion-core/motion-gpu 0.4.1 → 0.4.2
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/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +12 -6
- package/dist/core/advanced.d.ts +1 -0
- package/dist/core/advanced.d.ts.map +1 -0
- package/dist/core/advanced.js +12 -5
- package/dist/core/current-value.d.ts +1 -0
- package/dist/core/current-value.d.ts.map +1 -0
- package/dist/core/current-value.js +35 -34
- package/dist/core/current-value.js.map +1 -0
- package/dist/core/error-diagnostics.d.ts +1 -0
- package/dist/core/error-diagnostics.d.ts.map +1 -0
- package/dist/core/error-diagnostics.js +70 -137
- package/dist/core/error-diagnostics.js.map +1 -0
- package/dist/core/error-report.d.ts +1 -0
- package/dist/core/error-report.d.ts.map +1 -0
- package/dist/core/error-report.js +184 -233
- package/dist/core/error-report.js.map +1 -0
- package/dist/core/frame-registry.d.ts +1 -0
- package/dist/core/frame-registry.d.ts.map +1 -0
- package/dist/core/frame-registry.js +546 -662
- package/dist/core/frame-registry.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +11 -12
- package/dist/core/material-preprocess.d.ts +1 -0
- package/dist/core/material-preprocess.d.ts.map +1 -0
- package/dist/core/material-preprocess.js +128 -151
- package/dist/core/material-preprocess.js.map +1 -0
- package/dist/core/material.d.ts +1 -0
- package/dist/core/material.d.ts.map +1 -0
- package/dist/core/material.js +263 -317
- package/dist/core/material.js.map +1 -0
- package/dist/core/recompile-policy.d.ts +1 -0
- package/dist/core/recompile-policy.d.ts.map +1 -0
- package/dist/core/recompile-policy.js +18 -13
- package/dist/core/recompile-policy.js.map +1 -0
- package/dist/core/render-graph.d.ts +1 -0
- package/dist/core/render-graph.d.ts.map +1 -0
- package/dist/core/render-graph.js +61 -68
- package/dist/core/render-graph.js.map +1 -0
- package/dist/core/render-targets.d.ts +1 -0
- package/dist/core/render-targets.d.ts.map +1 -0
- package/dist/core/render-targets.js +52 -53
- package/dist/core/render-targets.js.map +1 -0
- package/dist/core/renderer.d.ts +1 -0
- package/dist/core/renderer.d.ts.map +1 -0
- package/dist/core/renderer.js +942 -1081
- package/dist/core/renderer.js.map +1 -0
- package/dist/core/runtime-loop.d.ts +1 -0
- package/dist/core/runtime-loop.d.ts.map +1 -0
- package/dist/core/runtime-loop.js +305 -362
- package/dist/core/runtime-loop.js.map +1 -0
- package/dist/core/scheduler-helpers.d.ts +1 -0
- package/dist/core/scheduler-helpers.d.ts.map +1 -0
- package/dist/core/scheduler-helpers.js +52 -51
- package/dist/core/scheduler-helpers.js.map +1 -0
- package/dist/core/shader.d.ts +1 -0
- package/dist/core/shader.d.ts.map +1 -0
- package/dist/core/shader.js +92 -117
- package/dist/core/shader.js.map +1 -0
- package/dist/core/texture-loader.d.ts +1 -0
- package/dist/core/texture-loader.d.ts.map +1 -0
- package/dist/core/texture-loader.js +205 -273
- package/dist/core/texture-loader.js.map +1 -0
- package/dist/core/textures.d.ts +1 -0
- package/dist/core/textures.d.ts.map +1 -0
- package/dist/core/textures.js +106 -116
- package/dist/core/textures.js.map +1 -0
- package/dist/core/types.d.ts +1 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +0 -4
- package/dist/core/uniforms.d.ts +1 -0
- package/dist/core/uniforms.d.ts.map +1 -0
- package/dist/core/uniforms.js +170 -191
- package/dist/core/uniforms.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -6
- package/dist/passes/BlitPass.d.ts +1 -0
- package/dist/passes/BlitPass.d.ts.map +1 -0
- package/dist/passes/BlitPass.js +23 -18
- package/dist/passes/BlitPass.js.map +1 -0
- package/dist/passes/CopyPass.d.ts +1 -0
- package/dist/passes/CopyPass.d.ts.map +1 -0
- package/dist/passes/CopyPass.js +58 -52
- package/dist/passes/CopyPass.js.map +1 -0
- package/dist/passes/FullscreenPass.d.ts +1 -0
- package/dist/passes/FullscreenPass.d.ts.map +1 -0
- package/dist/passes/FullscreenPass.js +127 -130
- package/dist/passes/FullscreenPass.js.map +1 -0
- package/dist/passes/ShaderPass.d.ts +1 -0
- package/dist/passes/ShaderPass.d.ts.map +1 -0
- package/dist/passes/ShaderPass.js +40 -37
- package/dist/passes/ShaderPass.js.map +1 -0
- package/dist/passes/index.d.ts +1 -0
- package/dist/passes/index.d.ts.map +1 -0
- package/dist/passes/index.js +4 -3
- package/dist/react/FragCanvas.d.ts +1 -0
- package/dist/react/FragCanvas.d.ts.map +1 -0
- package/dist/react/FragCanvas.js +234 -211
- package/dist/react/FragCanvas.js.map +1 -0
- package/dist/react/MotionGPUErrorOverlay.d.ts +1 -0
- package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -0
- package/dist/react/MotionGPUErrorOverlay.js +96 -13
- package/dist/react/MotionGPUErrorOverlay.js.map +1 -0
- package/dist/react/Portal.d.ts +1 -0
- package/dist/react/Portal.d.ts.map +1 -0
- package/dist/react/Portal.js +18 -21
- package/dist/react/Portal.js.map +1 -0
- package/dist/react/advanced.d.ts +1 -0
- package/dist/react/advanced.d.ts.map +1 -0
- package/dist/react/advanced.js +12 -6
- package/dist/react/frame-context.d.ts +1 -0
- package/dist/react/frame-context.d.ts.map +1 -0
- package/dist/react/frame-context.js +88 -94
- package/dist/react/frame-context.js.map +1 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +10 -9
- package/dist/react/motiongpu-context.d.ts +1 -0
- package/dist/react/motiongpu-context.d.ts.map +1 -0
- package/dist/react/motiongpu-context.js +18 -15
- package/dist/react/motiongpu-context.js.map +1 -0
- package/dist/react/use-motiongpu-user-context.d.ts +1 -0
- package/dist/react/use-motiongpu-user-context.d.ts.map +1 -0
- package/dist/react/use-motiongpu-user-context.js +83 -82
- package/dist/react/use-motiongpu-user-context.js.map +1 -0
- package/dist/react/use-texture.d.ts +1 -0
- package/dist/react/use-texture.d.ts.map +1 -0
- package/dist/react/use-texture.js +132 -152
- package/dist/react/use-texture.js.map +1 -0
- package/dist/svelte/FragCanvas.svelte.d.ts +1 -0
- package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -0
- package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts +1 -0
- package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -0
- package/dist/svelte/Portal.svelte.d.ts +1 -0
- package/dist/svelte/Portal.svelte.d.ts.map +1 -0
- package/dist/svelte/advanced.d.ts +1 -0
- package/dist/svelte/advanced.d.ts.map +1 -0
- package/dist/svelte/advanced.js +11 -6
- package/dist/svelte/frame-context.d.ts +1 -0
- package/dist/svelte/frame-context.d.ts.map +1 -0
- package/dist/svelte/frame-context.js +27 -27
- package/dist/svelte/frame-context.js.map +1 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +10 -9
- package/dist/svelte/motiongpu-context.d.ts +1 -0
- package/dist/svelte/motiongpu-context.d.ts.map +1 -0
- package/dist/svelte/motiongpu-context.js +24 -21
- package/dist/svelte/motiongpu-context.js.map +1 -0
- package/dist/svelte/use-motiongpu-user-context.d.ts +1 -0
- package/dist/svelte/use-motiongpu-user-context.d.ts.map +1 -0
- package/dist/svelte/use-motiongpu-user-context.js +69 -70
- package/dist/svelte/use-motiongpu-user-context.js.map +1 -0
- package/dist/svelte/use-texture.d.ts +1 -0
- package/dist/svelte/use-texture.d.ts.map +1 -0
- package/dist/svelte/use-texture.js +125 -147
- package/dist/svelte/use-texture.js.map +1 -0
- package/package.json +12 -7
- package/src/lib/advanced.ts +6 -0
- package/src/lib/core/advanced.ts +12 -0
- package/src/lib/core/current-value.ts +64 -0
- package/src/lib/core/error-diagnostics.ts +236 -0
- package/src/lib/core/error-report.ts +406 -0
- package/src/lib/core/frame-registry.ts +1189 -0
- package/src/lib/core/index.ts +77 -0
- package/src/lib/core/material-preprocess.ts +284 -0
- package/src/lib/core/material.ts +667 -0
- package/src/lib/core/recompile-policy.ts +31 -0
- package/src/lib/core/render-graph.ts +143 -0
- package/src/lib/core/render-targets.ts +107 -0
- package/src/lib/core/renderer.ts +1547 -0
- package/src/lib/core/runtime-loop.ts +458 -0
- package/src/lib/core/scheduler-helpers.ts +136 -0
- package/src/lib/core/shader.ts +258 -0
- package/src/lib/core/texture-loader.ts +476 -0
- package/src/lib/core/textures.ts +235 -0
- package/src/lib/core/types.ts +582 -0
- package/src/lib/core/uniforms.ts +282 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/passes/BlitPass.ts +54 -0
- package/src/lib/passes/CopyPass.ts +80 -0
- package/src/lib/passes/FullscreenPass.ts +173 -0
- package/src/lib/passes/ShaderPass.ts +88 -0
- package/src/lib/passes/index.ts +3 -0
- package/src/lib/react/FragCanvas.tsx +345 -0
- package/src/lib/react/MotionGPUErrorOverlay.tsx +392 -0
- package/src/lib/react/Portal.tsx +34 -0
- package/src/lib/react/advanced.ts +36 -0
- package/src/lib/react/frame-context.ts +169 -0
- package/src/lib/react/index.ts +51 -0
- package/src/lib/react/motiongpu-context.ts +88 -0
- package/src/lib/react/use-motiongpu-user-context.ts +186 -0
- package/src/lib/react/use-texture.ts +233 -0
- package/src/lib/svelte/FragCanvas.svelte +249 -0
- package/src/lib/svelte/MotionGPUErrorOverlay.svelte +382 -0
- package/src/lib/svelte/Portal.svelte +31 -0
- package/src/lib/svelte/advanced.ts +32 -0
- package/src/lib/svelte/frame-context.ts +87 -0
- package/src/lib/svelte/index.ts +51 -0
- package/src/lib/svelte/motiongpu-context.ts +97 -0
- package/src/lib/svelte/use-motiongpu-user-context.ts +145 -0
- package/src/lib/svelte/use-texture.ts +232 -0
package/dist/core/renderer.js
CHANGED
|
@@ -1,284 +1,289 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { packUniformsInto } from "./uniforms.js";
|
|
2
|
+
import { getTextureMipLevelCount, normalizeTextureDefinitions, resolveTextureSize, resolveTextureUpdateMode, toTextureData } from "./textures.js";
|
|
3
|
+
import { attachShaderCompilationDiagnostics } from "./error-diagnostics.js";
|
|
4
|
+
import { buildShaderSourceWithMap, formatShaderSourceLocation } from "./shader.js";
|
|
5
|
+
import { buildRenderTargetSignature, resolveRenderTargetDefinitions } from "./render-targets.js";
|
|
6
|
+
import { planRenderGraph } from "./render-graph.js";
|
|
7
|
+
//#region src/lib/core/renderer.ts
|
|
7
8
|
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
* Binding index for frame uniforms (`time`, `delta`, `resolution`).
|
|
10
|
+
*/
|
|
11
|
+
var FRAME_BINDING = 0;
|
|
11
12
|
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
* Binding index for material uniform buffer.
|
|
14
|
+
*/
|
|
15
|
+
var UNIFORM_BINDING = 1;
|
|
15
16
|
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
* First binding index used for texture sampler/texture pairs.
|
|
18
|
+
*/
|
|
19
|
+
var FIRST_TEXTURE_BINDING = 2;
|
|
19
20
|
/**
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
* Returns sampler/texture binding slots for a texture index.
|
|
22
|
+
*/
|
|
22
23
|
function getTextureBindings(index) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const samplerBinding = FIRST_TEXTURE_BINDING + index * 2;
|
|
25
|
+
return {
|
|
26
|
+
samplerBinding,
|
|
27
|
+
textureBinding: samplerBinding + 1
|
|
28
|
+
};
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
* Resizes canvas backing store to match client size and DPR.
|
|
32
|
+
*/
|
|
32
33
|
function resizeCanvas(canvas, dprInput, cssSize) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
const dpr = Number.isFinite(dprInput) && dprInput > 0 ? dprInput : 1;
|
|
35
|
+
const rect = cssSize ? null : canvas.getBoundingClientRect();
|
|
36
|
+
const cssWidth = Math.max(0, cssSize?.width ?? rect?.width ?? 0);
|
|
37
|
+
const cssHeight = Math.max(0, cssSize?.height ?? rect?.height ?? 0);
|
|
38
|
+
const width = Math.max(1, Math.floor((cssWidth || 1) * dpr));
|
|
39
|
+
const height = Math.max(1, Math.floor((cssHeight || 1) * dpr));
|
|
40
|
+
if (canvas.width !== width || canvas.height !== height) {
|
|
41
|
+
canvas.width = width;
|
|
42
|
+
canvas.height = height;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
width,
|
|
46
|
+
height
|
|
47
|
+
};
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
* Throws when a shader module contains WGSL compilation errors.
|
|
51
|
+
*/
|
|
48
52
|
async function assertCompilation(module, options) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const error = new Error(`WGSL compilation failed:\n${summary}`);
|
|
73
|
-
throw attachShaderCompilationDiagnostics(error, {
|
|
74
|
-
kind: 'shader-compilation',
|
|
75
|
-
diagnostics,
|
|
76
|
-
fragmentSource: options?.fragmentSource ?? '',
|
|
77
|
-
includeSources: options?.includeSources ?? {},
|
|
78
|
-
...(options?.defineBlockSource !== undefined
|
|
79
|
-
? { defineBlockSource: options.defineBlockSource }
|
|
80
|
-
: {}),
|
|
81
|
-
materialSource: options?.materialSource ?? null,
|
|
82
|
-
...(options?.runtimeContext !== undefined ? { runtimeContext: options.runtimeContext } : {})
|
|
83
|
-
});
|
|
53
|
+
const errors = (await module.getCompilationInfo()).messages.filter((message) => message.type === "error");
|
|
54
|
+
if (errors.length === 0) return;
|
|
55
|
+
const diagnostics = errors.map((message) => ({
|
|
56
|
+
generatedLine: message.lineNum,
|
|
57
|
+
message: message.message,
|
|
58
|
+
linePos: message.linePos,
|
|
59
|
+
lineLength: message.length,
|
|
60
|
+
sourceLocation: options?.lineMap?.[message.lineNum] ?? null
|
|
61
|
+
}));
|
|
62
|
+
const summary = diagnostics.map((diagnostic) => {
|
|
63
|
+
const contextLabel = [formatShaderSourceLocation(diagnostic.sourceLocation), diagnostic.generatedLine > 0 ? `generated WGSL line ${diagnostic.generatedLine}` : null].filter((value) => Boolean(value));
|
|
64
|
+
if (contextLabel.length === 0) return diagnostic.message;
|
|
65
|
+
return `[${contextLabel.join(" | ")}] ${diagnostic.message}`;
|
|
66
|
+
}).join("\n");
|
|
67
|
+
throw attachShaderCompilationDiagnostics(/* @__PURE__ */ new Error(`WGSL compilation failed:\n${summary}`), {
|
|
68
|
+
kind: "shader-compilation",
|
|
69
|
+
diagnostics,
|
|
70
|
+
fragmentSource: options?.fragmentSource ?? "",
|
|
71
|
+
includeSources: options?.includeSources ?? {},
|
|
72
|
+
...options?.defineBlockSource !== void 0 ? { defineBlockSource: options.defineBlockSource } : {},
|
|
73
|
+
materialSource: options?.materialSource ?? null,
|
|
74
|
+
...options?.runtimeContext !== void 0 ? { runtimeContext: options.runtimeContext } : {}
|
|
75
|
+
});
|
|
84
76
|
}
|
|
85
77
|
function toSortedUniqueStrings(values) {
|
|
86
|
-
|
|
78
|
+
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
87
79
|
}
|
|
88
80
|
function buildPassGraphSnapshot(passes) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
outputs: toSortedUniqueStrings(outputs)
|
|
109
|
-
};
|
|
81
|
+
const declaredPasses = passes ?? [];
|
|
82
|
+
let enabledPassCount = 0;
|
|
83
|
+
const inputs = [];
|
|
84
|
+
const outputs = [];
|
|
85
|
+
for (const pass of declaredPasses) {
|
|
86
|
+
if (pass.enabled === false) continue;
|
|
87
|
+
enabledPassCount += 1;
|
|
88
|
+
const needsSwap = pass.needsSwap ?? true;
|
|
89
|
+
const input = pass.input ?? "source";
|
|
90
|
+
const output = pass.output ?? (needsSwap ? "target" : "source");
|
|
91
|
+
inputs.push(input);
|
|
92
|
+
outputs.push(output);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
passCount: declaredPasses.length,
|
|
96
|
+
enabledPassCount,
|
|
97
|
+
inputs: toSortedUniqueStrings(inputs),
|
|
98
|
+
outputs: toSortedUniqueStrings(outputs)
|
|
99
|
+
};
|
|
110
100
|
}
|
|
111
101
|
function buildShaderCompilationRuntimeContext(options) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
102
|
+
const passList = options.getPasses?.() ?? options.passes;
|
|
103
|
+
const renderTargetMap = options.getRenderTargets?.() ?? options.renderTargets;
|
|
104
|
+
return {
|
|
105
|
+
...options.materialSignature ? { materialSignature: options.materialSignature } : {},
|
|
106
|
+
passGraph: buildPassGraphSnapshot(passList),
|
|
107
|
+
activeRenderTargets: Object.keys(renderTargetMap ?? {}).sort((a, b) => a.localeCompare(b))
|
|
108
|
+
};
|
|
119
109
|
}
|
|
120
110
|
/**
|
|
121
|
-
|
|
122
|
-
|
|
111
|
+
* Creates a 1x1 white fallback texture used before user textures become available.
|
|
112
|
+
*/
|
|
123
113
|
function createFallbackTexture(device, format) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
114
|
+
const texture = device.createTexture({
|
|
115
|
+
size: {
|
|
116
|
+
width: 1,
|
|
117
|
+
height: 1,
|
|
118
|
+
depthOrArrayLayers: 1
|
|
119
|
+
},
|
|
120
|
+
format,
|
|
121
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
122
|
+
});
|
|
123
|
+
const pixel = new Uint8Array([
|
|
124
|
+
255,
|
|
125
|
+
255,
|
|
126
|
+
255,
|
|
127
|
+
255
|
|
128
|
+
]);
|
|
129
|
+
device.queue.writeTexture({ texture }, pixel, {
|
|
130
|
+
offset: 0,
|
|
131
|
+
bytesPerRow: 4,
|
|
132
|
+
rowsPerImage: 1
|
|
133
|
+
}, {
|
|
134
|
+
width: 1,
|
|
135
|
+
height: 1,
|
|
136
|
+
depthOrArrayLayers: 1
|
|
137
|
+
});
|
|
138
|
+
return texture;
|
|
132
139
|
}
|
|
133
140
|
/**
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
* Creates an offscreen canvas used for CPU mipmap generation.
|
|
142
|
+
*/
|
|
136
143
|
function createMipmapCanvas(width, height) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
canvas.height = height;
|
|
143
|
-
return canvas;
|
|
144
|
+
if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(width, height);
|
|
145
|
+
const canvas = document.createElement("canvas");
|
|
146
|
+
canvas.width = width;
|
|
147
|
+
canvas.height = height;
|
|
148
|
+
return canvas;
|
|
144
149
|
}
|
|
145
150
|
/**
|
|
146
|
-
|
|
147
|
-
|
|
151
|
+
* Creates typed descriptor for `copyExternalImageToTexture`.
|
|
152
|
+
*/
|
|
148
153
|
function createExternalCopySource(source, options) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return descriptor;
|
|
154
|
+
return {
|
|
155
|
+
source,
|
|
156
|
+
...options.flipY ? { flipY: true } : {},
|
|
157
|
+
...options.premultipliedAlpha ? { premultipliedAlpha: true } : {}
|
|
158
|
+
};
|
|
155
159
|
}
|
|
156
160
|
/**
|
|
157
|
-
|
|
158
|
-
|
|
161
|
+
* Uploads source content to a GPU texture and optionally generates mip chain on CPU.
|
|
162
|
+
*/
|
|
159
163
|
function uploadTexture(device, texture, binding, source, width, height, mipLevelCount) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
164
|
+
device.queue.copyExternalImageToTexture(createExternalCopySource(source, {
|
|
165
|
+
flipY: binding.flipY,
|
|
166
|
+
premultipliedAlpha: binding.premultipliedAlpha
|
|
167
|
+
}), {
|
|
168
|
+
texture,
|
|
169
|
+
mipLevel: 0
|
|
170
|
+
}, {
|
|
171
|
+
width,
|
|
172
|
+
height,
|
|
173
|
+
depthOrArrayLayers: 1
|
|
174
|
+
});
|
|
175
|
+
if (!binding.generateMipmaps || mipLevelCount <= 1) return;
|
|
176
|
+
let previousSource = source;
|
|
177
|
+
let previousWidth = width;
|
|
178
|
+
let previousHeight = height;
|
|
179
|
+
for (let level = 1; level < mipLevelCount; level += 1) {
|
|
180
|
+
const nextWidth = Math.max(1, Math.floor(previousWidth / 2));
|
|
181
|
+
const nextHeight = Math.max(1, Math.floor(previousHeight / 2));
|
|
182
|
+
const canvas = createMipmapCanvas(nextWidth, nextHeight);
|
|
183
|
+
const context = canvas.getContext("2d");
|
|
184
|
+
if (!context) throw new Error("Unable to create 2D context for mipmap generation");
|
|
185
|
+
context.drawImage(previousSource, 0, 0, previousWidth, previousHeight, 0, 0, nextWidth, nextHeight);
|
|
186
|
+
device.queue.copyExternalImageToTexture(createExternalCopySource(canvas, { premultipliedAlpha: binding.premultipliedAlpha }), {
|
|
187
|
+
texture,
|
|
188
|
+
mipLevel: level
|
|
189
|
+
}, {
|
|
190
|
+
width: nextWidth,
|
|
191
|
+
height: nextHeight,
|
|
192
|
+
depthOrArrayLayers: 1
|
|
193
|
+
});
|
|
194
|
+
previousSource = canvas;
|
|
195
|
+
previousWidth = nextWidth;
|
|
196
|
+
previousHeight = nextHeight;
|
|
197
|
+
}
|
|
186
198
|
}
|
|
187
199
|
/**
|
|
188
|
-
|
|
189
|
-
|
|
200
|
+
* Creates bind group layout entries for frame/uniform buffers plus texture bindings.
|
|
201
|
+
*/
|
|
190
202
|
function createBindGroupLayoutEntries(textureBindings) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
203
|
+
const entries = [{
|
|
204
|
+
binding: FRAME_BINDING,
|
|
205
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
206
|
+
buffer: {
|
|
207
|
+
type: "uniform",
|
|
208
|
+
minBindingSize: 16
|
|
209
|
+
}
|
|
210
|
+
}, {
|
|
211
|
+
binding: UNIFORM_BINDING,
|
|
212
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
213
|
+
buffer: { type: "uniform" }
|
|
214
|
+
}];
|
|
215
|
+
for (const binding of textureBindings) {
|
|
216
|
+
entries.push({
|
|
217
|
+
binding: binding.samplerBinding,
|
|
218
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
219
|
+
sampler: { type: "filtering" }
|
|
220
|
+
});
|
|
221
|
+
entries.push({
|
|
222
|
+
binding: binding.textureBinding,
|
|
223
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
224
|
+
texture: {
|
|
225
|
+
sampleType: "float",
|
|
226
|
+
viewDimension: "2d",
|
|
227
|
+
multisampled: false
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return entries;
|
|
220
232
|
}
|
|
221
233
|
/**
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
234
|
+
* Maximum gap (in floats) between two dirty ranges that triggers merge.
|
|
235
|
+
*
|
|
236
|
+
* Set to 4 (16 bytes) which covers one vec4f alignment slot.
|
|
237
|
+
*/
|
|
238
|
+
var DIRTY_RANGE_MERGE_GAP = 4;
|
|
227
239
|
/**
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
else {
|
|
263
|
-
merged.push(curr);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return merged;
|
|
240
|
+
* Computes dirty float ranges between two uniform snapshots.
|
|
241
|
+
*
|
|
242
|
+
* Adjacent dirty ranges separated by a gap smaller than or equal to
|
|
243
|
+
* {@link DIRTY_RANGE_MERGE_GAP} are merged to reduce `writeBuffer` calls.
|
|
244
|
+
*/
|
|
245
|
+
function findDirtyFloatRanges(previous, next, mergeGapThreshold = DIRTY_RANGE_MERGE_GAP) {
|
|
246
|
+
const ranges = [];
|
|
247
|
+
let start = -1;
|
|
248
|
+
for (let index = 0; index < next.length; index += 1) {
|
|
249
|
+
if (previous[index] !== next[index]) {
|
|
250
|
+
if (start === -1) start = index;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (start !== -1) {
|
|
254
|
+
ranges.push({
|
|
255
|
+
start,
|
|
256
|
+
count: index - start
|
|
257
|
+
});
|
|
258
|
+
start = -1;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (start !== -1) ranges.push({
|
|
262
|
+
start,
|
|
263
|
+
count: next.length - start
|
|
264
|
+
});
|
|
265
|
+
if (ranges.length <= 1) return ranges;
|
|
266
|
+
const merged = [ranges[0]];
|
|
267
|
+
for (let index = 1; index < ranges.length; index += 1) {
|
|
268
|
+
const prev = merged[merged.length - 1];
|
|
269
|
+
const curr = ranges[index];
|
|
270
|
+
if (curr.start - (prev.start + prev.count) <= mergeGapThreshold) prev.count = curr.start + curr.count - prev.start;
|
|
271
|
+
else merged.push(curr);
|
|
272
|
+
}
|
|
273
|
+
return merged;
|
|
267
274
|
}
|
|
268
275
|
/**
|
|
269
|
-
|
|
270
|
-
|
|
276
|
+
* Determines whether shader output should perform linear-to-sRGB conversion.
|
|
277
|
+
*/
|
|
271
278
|
function shouldConvertLinearToSrgb(outputColorSpace, canvasFormat) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
return !canvasFormat.endsWith('-srgb');
|
|
279
|
+
if (outputColorSpace !== "srgb") return false;
|
|
280
|
+
return !canvasFormat.endsWith("-srgb");
|
|
276
281
|
}
|
|
277
282
|
/**
|
|
278
|
-
|
|
279
|
-
|
|
283
|
+
* WGSL shader used to blit an offscreen texture to the canvas.
|
|
284
|
+
*/
|
|
280
285
|
function createFullscreenBlitShader() {
|
|
281
|
-
|
|
286
|
+
return `
|
|
282
287
|
struct MotionGPUVertexOut {
|
|
283
288
|
@builtin(position) position: vec4f,
|
|
284
289
|
@location(0) uv: vec2f,
|
|
@@ -309,851 +314,707 @@ fn motiongpuBlitFragment(in: MotionGPUVertexOut) -> @location(0) vec4f {
|
|
|
309
314
|
`;
|
|
310
315
|
}
|
|
311
316
|
/**
|
|
312
|
-
|
|
313
|
-
|
|
317
|
+
* Allocates a render target texture with usage flags suitable for passes/blits.
|
|
318
|
+
*/
|
|
314
319
|
function createRenderTexture(device, width, height, format) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
320
|
+
const texture = device.createTexture({
|
|
321
|
+
size: {
|
|
322
|
+
width,
|
|
323
|
+
height,
|
|
324
|
+
depthOrArrayLayers: 1
|
|
325
|
+
},
|
|
326
|
+
format,
|
|
327
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
330
|
+
texture,
|
|
331
|
+
view: texture.createView(),
|
|
332
|
+
width,
|
|
333
|
+
height,
|
|
334
|
+
format
|
|
335
|
+
};
|
|
330
336
|
}
|
|
331
337
|
/**
|
|
332
|
-
|
|
333
|
-
|
|
338
|
+
* Destroys a render target texture if present.
|
|
339
|
+
*/
|
|
334
340
|
function destroyRenderTexture(target) {
|
|
335
|
-
|
|
341
|
+
target?.texture.destroy();
|
|
336
342
|
}
|
|
337
343
|
/**
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
: (() => {
|
|
1011
|
-
const nextPlan = planRenderGraph(passes, clearColor, renderTargetKeys);
|
|
1012
|
-
updateGraphPlanCache(passes, clearColor, nextPlan);
|
|
1013
|
-
return nextPlan;
|
|
1014
|
-
})();
|
|
1015
|
-
const canvasTexture = context.getCurrentTexture();
|
|
1016
|
-
const canvasSurface = {
|
|
1017
|
-
texture: canvasTexture,
|
|
1018
|
-
view: canvasTexture.createView(),
|
|
1019
|
-
width,
|
|
1020
|
-
height,
|
|
1021
|
-
format
|
|
1022
|
-
};
|
|
1023
|
-
const slots = graphPlan.steps.length > 0
|
|
1024
|
-
? {
|
|
1025
|
-
source: ensureSlotTarget('source', width, height),
|
|
1026
|
-
target: ensureSlotTarget('target', width, height),
|
|
1027
|
-
canvas: canvasSurface
|
|
1028
|
-
}
|
|
1029
|
-
: null;
|
|
1030
|
-
const sceneOutput = slots ? slots.source : canvasSurface;
|
|
1031
|
-
const scenePass = commandEncoder.beginRenderPass({
|
|
1032
|
-
colorAttachments: [
|
|
1033
|
-
{
|
|
1034
|
-
view: sceneOutput.view,
|
|
1035
|
-
clearValue: {
|
|
1036
|
-
r: clearColor[0],
|
|
1037
|
-
g: clearColor[1],
|
|
1038
|
-
b: clearColor[2],
|
|
1039
|
-
a: clearColor[3]
|
|
1040
|
-
},
|
|
1041
|
-
loadOp: 'clear',
|
|
1042
|
-
storeOp: 'store'
|
|
1043
|
-
}
|
|
1044
|
-
]
|
|
1045
|
-
});
|
|
1046
|
-
scenePass.setPipeline(pipeline);
|
|
1047
|
-
scenePass.setBindGroup(0, bindGroup);
|
|
1048
|
-
scenePass.draw(3);
|
|
1049
|
-
scenePass.end();
|
|
1050
|
-
if (slots) {
|
|
1051
|
-
const resolveStepSurface = (slot) => {
|
|
1052
|
-
if (slot === 'source') {
|
|
1053
|
-
return slots.source;
|
|
1054
|
-
}
|
|
1055
|
-
if (slot === 'target') {
|
|
1056
|
-
return slots.target;
|
|
1057
|
-
}
|
|
1058
|
-
if (slot === 'canvas') {
|
|
1059
|
-
return slots.canvas;
|
|
1060
|
-
}
|
|
1061
|
-
const named = runtimeTargets[slot];
|
|
1062
|
-
if (!named) {
|
|
1063
|
-
throw new Error(`Render graph references unknown runtime target "${slot}".`);
|
|
1064
|
-
}
|
|
1065
|
-
return named;
|
|
1066
|
-
};
|
|
1067
|
-
for (const step of graphPlan.steps) {
|
|
1068
|
-
const input = resolveStepSurface(step.input);
|
|
1069
|
-
const output = resolveStepSurface(step.output);
|
|
1070
|
-
step.pass.render({
|
|
1071
|
-
device,
|
|
1072
|
-
commandEncoder,
|
|
1073
|
-
source: slots.source,
|
|
1074
|
-
target: slots.target,
|
|
1075
|
-
canvas: slots.canvas,
|
|
1076
|
-
input,
|
|
1077
|
-
output,
|
|
1078
|
-
targets: runtimeTargets,
|
|
1079
|
-
time,
|
|
1080
|
-
delta,
|
|
1081
|
-
width,
|
|
1082
|
-
height,
|
|
1083
|
-
clear: step.clear,
|
|
1084
|
-
clearColor: step.clearColor,
|
|
1085
|
-
preserve: step.preserve,
|
|
1086
|
-
beginRenderPass: (passOptions) => {
|
|
1087
|
-
const clear = passOptions?.clear ?? step.clear;
|
|
1088
|
-
const clearColor = passOptions?.clearColor ?? step.clearColor;
|
|
1089
|
-
const preserve = passOptions?.preserve ?? step.preserve;
|
|
1090
|
-
return commandEncoder.beginRenderPass({
|
|
1091
|
-
colorAttachments: [
|
|
1092
|
-
{
|
|
1093
|
-
view: passOptions?.view ?? output.view,
|
|
1094
|
-
clearValue: {
|
|
1095
|
-
r: clearColor[0],
|
|
1096
|
-
g: clearColor[1],
|
|
1097
|
-
b: clearColor[2],
|
|
1098
|
-
a: clearColor[3]
|
|
1099
|
-
},
|
|
1100
|
-
loadOp: clear ? 'clear' : 'load',
|
|
1101
|
-
storeOp: preserve ? 'store' : 'discard'
|
|
1102
|
-
}
|
|
1103
|
-
]
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
});
|
|
1107
|
-
if (step.needsSwap) {
|
|
1108
|
-
const previousSource = slots.source;
|
|
1109
|
-
slots.source = slots.target;
|
|
1110
|
-
slots.target = previousSource;
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
if (graphPlan.finalOutput !== 'canvas') {
|
|
1114
|
-
const finalSurface = resolveStepSurface(graphPlan.finalOutput);
|
|
1115
|
-
blitToCanvas(commandEncoder, finalSurface.view, slots.canvas.view, clearColor);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
device.queue.submit([commandEncoder.finish()]);
|
|
1119
|
-
};
|
|
1120
|
-
acceptInitializationCleanups = false;
|
|
1121
|
-
initializationCleanups.length = 0;
|
|
1122
|
-
return {
|
|
1123
|
-
render,
|
|
1124
|
-
destroy: () => {
|
|
1125
|
-
isDestroyed = true;
|
|
1126
|
-
device.removeEventListener('uncapturederror', handleUncapturedError);
|
|
1127
|
-
frameBuffer.destroy();
|
|
1128
|
-
uniformBuffer.destroy();
|
|
1129
|
-
destroyRenderTexture(sourceSlotTarget);
|
|
1130
|
-
destroyRenderTexture(targetSlotTarget);
|
|
1131
|
-
for (const target of runtimeRenderTargets.values()) {
|
|
1132
|
-
target.texture.destroy();
|
|
1133
|
-
}
|
|
1134
|
-
runtimeRenderTargets.clear();
|
|
1135
|
-
for (const pass of activePasses) {
|
|
1136
|
-
pass.dispose?.();
|
|
1137
|
-
}
|
|
1138
|
-
activePasses.length = 0;
|
|
1139
|
-
lifecyclePassesRef = null;
|
|
1140
|
-
for (const binding of textureBindings) {
|
|
1141
|
-
binding.texture?.destroy();
|
|
1142
|
-
binding.fallbackTexture.destroy();
|
|
1143
|
-
}
|
|
1144
|
-
blitBindGroupByView = new WeakMap();
|
|
1145
|
-
cachedGraphPlan = null;
|
|
1146
|
-
cachedGraphPasses.length = 0;
|
|
1147
|
-
renderTargetSnapshot = {};
|
|
1148
|
-
renderTargetKeys = [];
|
|
1149
|
-
}
|
|
1150
|
-
};
|
|
1151
|
-
}
|
|
1152
|
-
catch (error) {
|
|
1153
|
-
isDestroyed = true;
|
|
1154
|
-
acceptInitializationCleanups = false;
|
|
1155
|
-
device.removeEventListener('uncapturederror', handleUncapturedError);
|
|
1156
|
-
runInitializationCleanups();
|
|
1157
|
-
throw error;
|
|
1158
|
-
}
|
|
344
|
+
* Creates the WebGPU renderer used by `FragCanvas`.
|
|
345
|
+
*
|
|
346
|
+
* @param options - Renderer creation options resolved from material/context state.
|
|
347
|
+
* @returns Renderer instance with `render` and `destroy`.
|
|
348
|
+
* @throws {Error} On WebGPU unavailability, shader compilation issues, or runtime setup failures.
|
|
349
|
+
*/
|
|
350
|
+
async function createRenderer(options) {
|
|
351
|
+
if (!navigator.gpu) throw new Error("WebGPU is not available in this browser");
|
|
352
|
+
const context = options.canvas.getContext("webgpu");
|
|
353
|
+
if (!context) throw new Error("Canvas does not support webgpu context");
|
|
354
|
+
const format = navigator.gpu.getPreferredCanvasFormat();
|
|
355
|
+
const adapter = await navigator.gpu.requestAdapter(options.adapterOptions);
|
|
356
|
+
if (!adapter) throw new Error("Unable to acquire WebGPU adapter");
|
|
357
|
+
const device = await adapter.requestDevice(options.deviceDescriptor);
|
|
358
|
+
let isDestroyed = false;
|
|
359
|
+
let deviceLostMessage = null;
|
|
360
|
+
let uncapturedErrorMessage = null;
|
|
361
|
+
const initializationCleanups = [];
|
|
362
|
+
let acceptInitializationCleanups = true;
|
|
363
|
+
const registerInitializationCleanup = (cleanup) => {
|
|
364
|
+
if (!acceptInitializationCleanups) return;
|
|
365
|
+
options.__onInitializationCleanupRegistered?.();
|
|
366
|
+
initializationCleanups.push(cleanup);
|
|
367
|
+
};
|
|
368
|
+
const runInitializationCleanups = () => {
|
|
369
|
+
for (let index = initializationCleanups.length - 1; index >= 0; index -= 1) try {
|
|
370
|
+
initializationCleanups[index]?.();
|
|
371
|
+
} catch {}
|
|
372
|
+
initializationCleanups.length = 0;
|
|
373
|
+
};
|
|
374
|
+
device.lost.then((info) => {
|
|
375
|
+
if (isDestroyed) return;
|
|
376
|
+
const reason = info.reason ? ` (${info.reason})` : "";
|
|
377
|
+
const details = info.message?.trim();
|
|
378
|
+
deviceLostMessage = details ? `WebGPU device lost: ${details}${reason}` : `WebGPU device lost${reason}`;
|
|
379
|
+
});
|
|
380
|
+
const handleUncapturedError = (event) => {
|
|
381
|
+
if (isDestroyed) return;
|
|
382
|
+
uncapturedErrorMessage = `WebGPU uncaptured error: ${event.error instanceof Error ? event.error.message : String(event.error?.message ?? event.error)}`;
|
|
383
|
+
};
|
|
384
|
+
device.addEventListener("uncapturederror", handleUncapturedError);
|
|
385
|
+
try {
|
|
386
|
+
const runtimeContext = buildShaderCompilationRuntimeContext(options);
|
|
387
|
+
const convertLinearToSrgb = shouldConvertLinearToSrgb(options.outputColorSpace, format);
|
|
388
|
+
const builtShader = buildShaderSourceWithMap(options.fragmentWgsl, options.uniformLayout, options.textureKeys, {
|
|
389
|
+
convertLinearToSrgb,
|
|
390
|
+
fragmentLineMap: options.fragmentLineMap
|
|
391
|
+
});
|
|
392
|
+
const shaderModule = device.createShaderModule({ code: builtShader.code });
|
|
393
|
+
await assertCompilation(shaderModule, {
|
|
394
|
+
lineMap: builtShader.lineMap,
|
|
395
|
+
fragmentSource: options.fragmentSource,
|
|
396
|
+
includeSources: options.includeSources,
|
|
397
|
+
...options.defineBlockSource !== void 0 ? { defineBlockSource: options.defineBlockSource } : {},
|
|
398
|
+
materialSource: options.materialSource ?? null,
|
|
399
|
+
runtimeContext
|
|
400
|
+
});
|
|
401
|
+
const normalizedTextureDefinitions = normalizeTextureDefinitions(options.textureDefinitions, options.textureKeys);
|
|
402
|
+
const textureBindings = options.textureKeys.map((key, index) => {
|
|
403
|
+
const config = normalizedTextureDefinitions[key];
|
|
404
|
+
if (!config) throw new Error(`Missing texture definition for "${key}"`);
|
|
405
|
+
const { samplerBinding, textureBinding } = getTextureBindings(index);
|
|
406
|
+
const sampler = device.createSampler({
|
|
407
|
+
magFilter: config.filter,
|
|
408
|
+
minFilter: config.filter,
|
|
409
|
+
mipmapFilter: config.generateMipmaps ? config.filter : "nearest",
|
|
410
|
+
addressModeU: config.addressModeU,
|
|
411
|
+
addressModeV: config.addressModeV,
|
|
412
|
+
maxAnisotropy: config.filter === "linear" ? config.anisotropy : 1
|
|
413
|
+
});
|
|
414
|
+
const fallbackTexture = createFallbackTexture(device, config.format);
|
|
415
|
+
registerInitializationCleanup(() => {
|
|
416
|
+
fallbackTexture.destroy();
|
|
417
|
+
});
|
|
418
|
+
const fallbackView = fallbackTexture.createView();
|
|
419
|
+
const runtimeBinding = {
|
|
420
|
+
key,
|
|
421
|
+
samplerBinding,
|
|
422
|
+
textureBinding,
|
|
423
|
+
sampler,
|
|
424
|
+
fallbackTexture,
|
|
425
|
+
fallbackView,
|
|
426
|
+
texture: null,
|
|
427
|
+
view: fallbackView,
|
|
428
|
+
source: null,
|
|
429
|
+
width: void 0,
|
|
430
|
+
height: void 0,
|
|
431
|
+
mipLevelCount: 1,
|
|
432
|
+
format: config.format,
|
|
433
|
+
colorSpace: config.colorSpace,
|
|
434
|
+
defaultColorSpace: config.colorSpace,
|
|
435
|
+
flipY: config.flipY,
|
|
436
|
+
defaultFlipY: config.flipY,
|
|
437
|
+
generateMipmaps: config.generateMipmaps,
|
|
438
|
+
defaultGenerateMipmaps: config.generateMipmaps,
|
|
439
|
+
premultipliedAlpha: config.premultipliedAlpha,
|
|
440
|
+
defaultPremultipliedAlpha: config.premultipliedAlpha,
|
|
441
|
+
update: config.update ?? "once",
|
|
442
|
+
lastToken: null
|
|
443
|
+
};
|
|
444
|
+
if (config.update !== void 0) runtimeBinding.defaultUpdate = config.update;
|
|
445
|
+
return runtimeBinding;
|
|
446
|
+
});
|
|
447
|
+
const bindGroupLayout = device.createBindGroupLayout({ entries: createBindGroupLayoutEntries(textureBindings) });
|
|
448
|
+
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] });
|
|
449
|
+
const pipeline = device.createRenderPipeline({
|
|
450
|
+
layout: pipelineLayout,
|
|
451
|
+
vertex: {
|
|
452
|
+
module: shaderModule,
|
|
453
|
+
entryPoint: "motiongpuVertex"
|
|
454
|
+
},
|
|
455
|
+
fragment: {
|
|
456
|
+
module: shaderModule,
|
|
457
|
+
entryPoint: "motiongpuFragment",
|
|
458
|
+
targets: [{ format }]
|
|
459
|
+
},
|
|
460
|
+
primitive: { topology: "triangle-list" }
|
|
461
|
+
});
|
|
462
|
+
const blitShaderModule = device.createShaderModule({ code: createFullscreenBlitShader() });
|
|
463
|
+
await assertCompilation(blitShaderModule);
|
|
464
|
+
const blitBindGroupLayout = device.createBindGroupLayout({ entries: [{
|
|
465
|
+
binding: 0,
|
|
466
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
467
|
+
sampler: { type: "filtering" }
|
|
468
|
+
}, {
|
|
469
|
+
binding: 1,
|
|
470
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
471
|
+
texture: {
|
|
472
|
+
sampleType: "float",
|
|
473
|
+
viewDimension: "2d",
|
|
474
|
+
multisampled: false
|
|
475
|
+
}
|
|
476
|
+
}] });
|
|
477
|
+
const blitPipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [blitBindGroupLayout] });
|
|
478
|
+
const blitPipeline = device.createRenderPipeline({
|
|
479
|
+
layout: blitPipelineLayout,
|
|
480
|
+
vertex: {
|
|
481
|
+
module: blitShaderModule,
|
|
482
|
+
entryPoint: "motiongpuBlitVertex"
|
|
483
|
+
},
|
|
484
|
+
fragment: {
|
|
485
|
+
module: blitShaderModule,
|
|
486
|
+
entryPoint: "motiongpuBlitFragment",
|
|
487
|
+
targets: [{ format }]
|
|
488
|
+
},
|
|
489
|
+
primitive: { topology: "triangle-list" }
|
|
490
|
+
});
|
|
491
|
+
const blitSampler = device.createSampler({
|
|
492
|
+
magFilter: "linear",
|
|
493
|
+
minFilter: "linear",
|
|
494
|
+
addressModeU: "clamp-to-edge",
|
|
495
|
+
addressModeV: "clamp-to-edge"
|
|
496
|
+
});
|
|
497
|
+
let blitBindGroupByView = /* @__PURE__ */ new WeakMap();
|
|
498
|
+
const frameBuffer = device.createBuffer({
|
|
499
|
+
size: 16,
|
|
500
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
501
|
+
});
|
|
502
|
+
registerInitializationCleanup(() => {
|
|
503
|
+
frameBuffer.destroy();
|
|
504
|
+
});
|
|
505
|
+
const uniformBuffer = device.createBuffer({
|
|
506
|
+
size: options.uniformLayout.byteLength,
|
|
507
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
508
|
+
});
|
|
509
|
+
registerInitializationCleanup(() => {
|
|
510
|
+
uniformBuffer.destroy();
|
|
511
|
+
});
|
|
512
|
+
const frameScratch = new Float32Array(4);
|
|
513
|
+
const uniformScratch = new Float32Array(options.uniformLayout.byteLength / 4);
|
|
514
|
+
const uniformPrevious = new Float32Array(options.uniformLayout.byteLength / 4);
|
|
515
|
+
let hasUniformSnapshot = false;
|
|
516
|
+
/**
|
|
517
|
+
* Rebuilds bind group using current texture views.
|
|
518
|
+
*/
|
|
519
|
+
const createBindGroup = () => {
|
|
520
|
+
const entries = [{
|
|
521
|
+
binding: FRAME_BINDING,
|
|
522
|
+
resource: { buffer: frameBuffer }
|
|
523
|
+
}, {
|
|
524
|
+
binding: UNIFORM_BINDING,
|
|
525
|
+
resource: { buffer: uniformBuffer }
|
|
526
|
+
}];
|
|
527
|
+
for (const binding of textureBindings) {
|
|
528
|
+
entries.push({
|
|
529
|
+
binding: binding.samplerBinding,
|
|
530
|
+
resource: binding.sampler
|
|
531
|
+
});
|
|
532
|
+
entries.push({
|
|
533
|
+
binding: binding.textureBinding,
|
|
534
|
+
resource: binding.view
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
return device.createBindGroup({
|
|
538
|
+
layout: bindGroupLayout,
|
|
539
|
+
entries
|
|
540
|
+
});
|
|
541
|
+
};
|
|
542
|
+
/**
|
|
543
|
+
* Synchronizes one runtime texture binding with incoming texture value.
|
|
544
|
+
*
|
|
545
|
+
* @returns `true` when bind group must be rebuilt.
|
|
546
|
+
*/
|
|
547
|
+
const updateTextureBinding = (binding, value, renderMode) => {
|
|
548
|
+
const nextData = toTextureData(value);
|
|
549
|
+
if (!nextData) {
|
|
550
|
+
if (binding.source === null && binding.texture === null) return false;
|
|
551
|
+
binding.texture?.destroy();
|
|
552
|
+
binding.texture = null;
|
|
553
|
+
binding.view = binding.fallbackView;
|
|
554
|
+
binding.source = null;
|
|
555
|
+
binding.width = void 0;
|
|
556
|
+
binding.height = void 0;
|
|
557
|
+
binding.lastToken = null;
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
const source = nextData.source;
|
|
561
|
+
const colorSpace = nextData.colorSpace ?? binding.defaultColorSpace;
|
|
562
|
+
const format = colorSpace === "linear" ? "rgba8unorm" : "rgba8unorm-srgb";
|
|
563
|
+
const flipY = nextData.flipY ?? binding.defaultFlipY;
|
|
564
|
+
const premultipliedAlpha = nextData.premultipliedAlpha ?? binding.defaultPremultipliedAlpha;
|
|
565
|
+
const generateMipmaps = nextData.generateMipmaps ?? binding.defaultGenerateMipmaps;
|
|
566
|
+
const update = resolveTextureUpdateMode({
|
|
567
|
+
source,
|
|
568
|
+
...nextData.update !== void 0 ? { override: nextData.update } : {},
|
|
569
|
+
...binding.defaultUpdate !== void 0 ? { defaultMode: binding.defaultUpdate } : {}
|
|
570
|
+
});
|
|
571
|
+
const { width, height } = resolveTextureSize(nextData);
|
|
572
|
+
const mipLevelCount = generateMipmaps ? getTextureMipLevelCount(width, height) : 1;
|
|
573
|
+
const sourceChanged = binding.source !== source;
|
|
574
|
+
const tokenChanged = binding.lastToken !== value;
|
|
575
|
+
if (!(binding.texture === null || binding.width !== width || binding.height !== height || binding.mipLevelCount !== mipLevelCount || binding.format !== format)) {
|
|
576
|
+
if ((sourceChanged || update === "perFrame" || update === "onInvalidate" && (renderMode !== "always" || tokenChanged)) && binding.texture) {
|
|
577
|
+
binding.flipY = flipY;
|
|
578
|
+
binding.generateMipmaps = generateMipmaps;
|
|
579
|
+
binding.premultipliedAlpha = premultipliedAlpha;
|
|
580
|
+
binding.colorSpace = colorSpace;
|
|
581
|
+
uploadTexture(device, binding.texture, binding, source, width, height, mipLevelCount);
|
|
582
|
+
}
|
|
583
|
+
binding.source = source;
|
|
584
|
+
binding.width = width;
|
|
585
|
+
binding.height = height;
|
|
586
|
+
binding.mipLevelCount = mipLevelCount;
|
|
587
|
+
binding.update = update;
|
|
588
|
+
binding.lastToken = value;
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
const texture = device.createTexture({
|
|
592
|
+
size: {
|
|
593
|
+
width,
|
|
594
|
+
height,
|
|
595
|
+
depthOrArrayLayers: 1
|
|
596
|
+
},
|
|
597
|
+
format,
|
|
598
|
+
mipLevelCount,
|
|
599
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
600
|
+
});
|
|
601
|
+
registerInitializationCleanup(() => {
|
|
602
|
+
texture.destroy();
|
|
603
|
+
});
|
|
604
|
+
binding.flipY = flipY;
|
|
605
|
+
binding.generateMipmaps = generateMipmaps;
|
|
606
|
+
binding.premultipliedAlpha = premultipliedAlpha;
|
|
607
|
+
binding.colorSpace = colorSpace;
|
|
608
|
+
binding.format = format;
|
|
609
|
+
uploadTexture(device, texture, binding, source, width, height, mipLevelCount);
|
|
610
|
+
binding.texture?.destroy();
|
|
611
|
+
binding.texture = texture;
|
|
612
|
+
binding.view = texture.createView();
|
|
613
|
+
binding.source = source;
|
|
614
|
+
binding.width = width;
|
|
615
|
+
binding.height = height;
|
|
616
|
+
binding.mipLevelCount = mipLevelCount;
|
|
617
|
+
binding.update = update;
|
|
618
|
+
binding.lastToken = value;
|
|
619
|
+
return true;
|
|
620
|
+
};
|
|
621
|
+
for (const binding of textureBindings) updateTextureBinding(binding, normalizedTextureDefinitions[binding.key]?.source ?? null, "always");
|
|
622
|
+
let bindGroup = createBindGroup();
|
|
623
|
+
let sourceSlotTarget = null;
|
|
624
|
+
let targetSlotTarget = null;
|
|
625
|
+
let renderTargetSignature = "";
|
|
626
|
+
let renderTargetSnapshot = {};
|
|
627
|
+
let renderTargetKeys = [];
|
|
628
|
+
let cachedGraphPlan = null;
|
|
629
|
+
let cachedGraphRenderTargetSignature = "";
|
|
630
|
+
const cachedGraphClearColor = [
|
|
631
|
+
NaN,
|
|
632
|
+
NaN,
|
|
633
|
+
NaN,
|
|
634
|
+
NaN
|
|
635
|
+
];
|
|
636
|
+
const cachedGraphPasses = [];
|
|
637
|
+
let contextConfigured = false;
|
|
638
|
+
let configuredWidth = 0;
|
|
639
|
+
let configuredHeight = 0;
|
|
640
|
+
const runtimeRenderTargets = /* @__PURE__ */ new Map();
|
|
641
|
+
const activePasses = [];
|
|
642
|
+
const lifecyclePreviousSet = /* @__PURE__ */ new Set();
|
|
643
|
+
const lifecycleNextSet = /* @__PURE__ */ new Set();
|
|
644
|
+
const lifecycleUniquePasses = [];
|
|
645
|
+
let lifecyclePassesRef = null;
|
|
646
|
+
let passWidth = 0;
|
|
647
|
+
let passHeight = 0;
|
|
648
|
+
/**
|
|
649
|
+
* Resolves active render pass list for current frame.
|
|
650
|
+
*/
|
|
651
|
+
const resolvePasses = () => {
|
|
652
|
+
return options.getPasses?.() ?? options.passes ?? [];
|
|
653
|
+
};
|
|
654
|
+
/**
|
|
655
|
+
* Resolves active render target declarations for current frame.
|
|
656
|
+
*/
|
|
657
|
+
const resolveRenderTargets = () => {
|
|
658
|
+
return options.getRenderTargets?.() ?? options.renderTargets;
|
|
659
|
+
};
|
|
660
|
+
/**
|
|
661
|
+
* Checks whether cached render-graph plan can be reused for this frame.
|
|
662
|
+
*/
|
|
663
|
+
const isGraphPlanCacheValid = (passes, clearColor) => {
|
|
664
|
+
if (!cachedGraphPlan) return false;
|
|
665
|
+
if (cachedGraphRenderTargetSignature !== renderTargetSignature) return false;
|
|
666
|
+
if (cachedGraphClearColor[0] !== clearColor[0] || cachedGraphClearColor[1] !== clearColor[1] || cachedGraphClearColor[2] !== clearColor[2] || cachedGraphClearColor[3] !== clearColor[3]) return false;
|
|
667
|
+
if (cachedGraphPasses.length !== passes.length) return false;
|
|
668
|
+
for (let index = 0; index < passes.length; index += 1) {
|
|
669
|
+
const pass = passes[index];
|
|
670
|
+
const snapshot = cachedGraphPasses[index];
|
|
671
|
+
if (!pass || !snapshot || snapshot.pass !== pass) return false;
|
|
672
|
+
if (snapshot.enabled !== pass.enabled || snapshot.needsSwap !== pass.needsSwap || snapshot.input !== pass.input || snapshot.output !== pass.output || snapshot.clear !== pass.clear || snapshot.preserve !== pass.preserve) return false;
|
|
673
|
+
const passClearColor = pass.clearColor;
|
|
674
|
+
const hasPassClearColor = passClearColor !== void 0;
|
|
675
|
+
if (snapshot.hasClearColor !== hasPassClearColor) return false;
|
|
676
|
+
if (passClearColor) {
|
|
677
|
+
if (snapshot.clearColor0 !== passClearColor[0] || snapshot.clearColor1 !== passClearColor[1] || snapshot.clearColor2 !== passClearColor[2] || snapshot.clearColor3 !== passClearColor[3]) return false;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return true;
|
|
681
|
+
};
|
|
682
|
+
/**
|
|
683
|
+
* Updates render-graph cache with current pass set.
|
|
684
|
+
*/
|
|
685
|
+
const updateGraphPlanCache = (passes, clearColor, graphPlan) => {
|
|
686
|
+
cachedGraphPlan = graphPlan;
|
|
687
|
+
cachedGraphRenderTargetSignature = renderTargetSignature;
|
|
688
|
+
cachedGraphClearColor[0] = clearColor[0];
|
|
689
|
+
cachedGraphClearColor[1] = clearColor[1];
|
|
690
|
+
cachedGraphClearColor[2] = clearColor[2];
|
|
691
|
+
cachedGraphClearColor[3] = clearColor[3];
|
|
692
|
+
cachedGraphPasses.length = passes.length;
|
|
693
|
+
let index = 0;
|
|
694
|
+
for (const pass of passes) {
|
|
695
|
+
const passClearColor = pass.clearColor;
|
|
696
|
+
const hasPassClearColor = passClearColor !== void 0;
|
|
697
|
+
const snapshot = cachedGraphPasses[index];
|
|
698
|
+
if (!snapshot) {
|
|
699
|
+
cachedGraphPasses[index] = {
|
|
700
|
+
pass,
|
|
701
|
+
enabled: pass.enabled,
|
|
702
|
+
needsSwap: pass.needsSwap,
|
|
703
|
+
input: pass.input,
|
|
704
|
+
output: pass.output,
|
|
705
|
+
clear: pass.clear,
|
|
706
|
+
preserve: pass.preserve,
|
|
707
|
+
hasClearColor: hasPassClearColor,
|
|
708
|
+
clearColor0: passClearColor?.[0] ?? 0,
|
|
709
|
+
clearColor1: passClearColor?.[1] ?? 0,
|
|
710
|
+
clearColor2: passClearColor?.[2] ?? 0,
|
|
711
|
+
clearColor3: passClearColor?.[3] ?? 0
|
|
712
|
+
};
|
|
713
|
+
index += 1;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
snapshot.pass = pass;
|
|
717
|
+
snapshot.enabled = pass.enabled;
|
|
718
|
+
snapshot.needsSwap = pass.needsSwap;
|
|
719
|
+
snapshot.input = pass.input;
|
|
720
|
+
snapshot.output = pass.output;
|
|
721
|
+
snapshot.clear = pass.clear;
|
|
722
|
+
snapshot.preserve = pass.preserve;
|
|
723
|
+
snapshot.hasClearColor = hasPassClearColor;
|
|
724
|
+
snapshot.clearColor0 = passClearColor?.[0] ?? 0;
|
|
725
|
+
snapshot.clearColor1 = passClearColor?.[1] ?? 0;
|
|
726
|
+
snapshot.clearColor2 = passClearColor?.[2] ?? 0;
|
|
727
|
+
snapshot.clearColor3 = passClearColor?.[3] ?? 0;
|
|
728
|
+
index += 1;
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
/**
|
|
732
|
+
* Synchronizes pass lifecycle callbacks and resize notifications.
|
|
733
|
+
*/
|
|
734
|
+
const syncPassLifecycle = (passes, width, height) => {
|
|
735
|
+
const resized = passWidth !== width || passHeight !== height;
|
|
736
|
+
if (!resized && lifecyclePassesRef === passes && passes.length === activePasses.length) {
|
|
737
|
+
let isSameOrder = true;
|
|
738
|
+
for (let index = 0; index < passes.length; index += 1) if (activePasses[index] !== passes[index]) {
|
|
739
|
+
isSameOrder = false;
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
if (isSameOrder) return;
|
|
743
|
+
}
|
|
744
|
+
lifecycleNextSet.clear();
|
|
745
|
+
lifecycleUniquePasses.length = 0;
|
|
746
|
+
for (const pass of passes) {
|
|
747
|
+
if (lifecycleNextSet.has(pass)) continue;
|
|
748
|
+
lifecycleNextSet.add(pass);
|
|
749
|
+
lifecycleUniquePasses.push(pass);
|
|
750
|
+
}
|
|
751
|
+
lifecyclePreviousSet.clear();
|
|
752
|
+
for (const pass of activePasses) lifecyclePreviousSet.add(pass);
|
|
753
|
+
for (const pass of activePasses) if (!lifecycleNextSet.has(pass)) pass.dispose?.();
|
|
754
|
+
for (const pass of lifecycleUniquePasses) if (resized || !lifecyclePreviousSet.has(pass)) pass.setSize?.(width, height);
|
|
755
|
+
activePasses.length = 0;
|
|
756
|
+
for (const pass of lifecycleUniquePasses) activePasses.push(pass);
|
|
757
|
+
lifecyclePassesRef = passes;
|
|
758
|
+
passWidth = width;
|
|
759
|
+
passHeight = height;
|
|
760
|
+
};
|
|
761
|
+
/**
|
|
762
|
+
* Ensures internal ping-pong slot texture matches current canvas size/format.
|
|
763
|
+
*/
|
|
764
|
+
const ensureSlotTarget = (slot, width, height) => {
|
|
765
|
+
const current = slot === "source" ? sourceSlotTarget : targetSlotTarget;
|
|
766
|
+
if (current && current.width === width && current.height === height && current.format === format) return current;
|
|
767
|
+
destroyRenderTexture(current);
|
|
768
|
+
const next = createRenderTexture(device, width, height, format);
|
|
769
|
+
if (slot === "source") sourceSlotTarget = next;
|
|
770
|
+
else targetSlotTarget = next;
|
|
771
|
+
return next;
|
|
772
|
+
};
|
|
773
|
+
/**
|
|
774
|
+
* Creates/updates runtime render targets and returns immutable pass snapshot.
|
|
775
|
+
*/
|
|
776
|
+
const syncRenderTargets = (canvasWidth, canvasHeight) => {
|
|
777
|
+
const resolvedDefinitions = resolveRenderTargetDefinitions(resolveRenderTargets(), canvasWidth, canvasHeight, format);
|
|
778
|
+
const nextSignature = buildRenderTargetSignature(resolvedDefinitions);
|
|
779
|
+
if (nextSignature !== renderTargetSignature) {
|
|
780
|
+
const activeKeys = new Set(resolvedDefinitions.map((definition) => definition.key));
|
|
781
|
+
for (const [key, target] of runtimeRenderTargets.entries()) if (!activeKeys.has(key)) {
|
|
782
|
+
target.texture.destroy();
|
|
783
|
+
runtimeRenderTargets.delete(key);
|
|
784
|
+
}
|
|
785
|
+
for (const definition of resolvedDefinitions) {
|
|
786
|
+
const current = runtimeRenderTargets.get(definition.key);
|
|
787
|
+
if (current && current.width === definition.width && current.height === definition.height && current.format === definition.format) continue;
|
|
788
|
+
current?.texture.destroy();
|
|
789
|
+
runtimeRenderTargets.set(definition.key, createRenderTexture(device, definition.width, definition.height, definition.format));
|
|
790
|
+
}
|
|
791
|
+
renderTargetSignature = nextSignature;
|
|
792
|
+
const nextSnapshot = {};
|
|
793
|
+
const nextKeys = [];
|
|
794
|
+
for (const definition of resolvedDefinitions) {
|
|
795
|
+
const target = runtimeRenderTargets.get(definition.key);
|
|
796
|
+
if (!target) continue;
|
|
797
|
+
nextKeys.push(definition.key);
|
|
798
|
+
nextSnapshot[definition.key] = {
|
|
799
|
+
texture: target.texture,
|
|
800
|
+
view: target.view,
|
|
801
|
+
width: target.width,
|
|
802
|
+
height: target.height,
|
|
803
|
+
format: target.format
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
renderTargetSnapshot = nextSnapshot;
|
|
807
|
+
renderTargetKeys = nextKeys;
|
|
808
|
+
}
|
|
809
|
+
return renderTargetSnapshot;
|
|
810
|
+
};
|
|
811
|
+
/**
|
|
812
|
+
* Blits a texture view to the current canvas texture.
|
|
813
|
+
*/
|
|
814
|
+
const blitToCanvas = (commandEncoder, sourceView, canvasView, clearColor) => {
|
|
815
|
+
let bindGroup = blitBindGroupByView.get(sourceView);
|
|
816
|
+
if (!bindGroup) {
|
|
817
|
+
bindGroup = device.createBindGroup({
|
|
818
|
+
layout: blitBindGroupLayout,
|
|
819
|
+
entries: [{
|
|
820
|
+
binding: 0,
|
|
821
|
+
resource: blitSampler
|
|
822
|
+
}, {
|
|
823
|
+
binding: 1,
|
|
824
|
+
resource: sourceView
|
|
825
|
+
}]
|
|
826
|
+
});
|
|
827
|
+
blitBindGroupByView.set(sourceView, bindGroup);
|
|
828
|
+
}
|
|
829
|
+
const pass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
830
|
+
view: canvasView,
|
|
831
|
+
clearValue: {
|
|
832
|
+
r: clearColor[0],
|
|
833
|
+
g: clearColor[1],
|
|
834
|
+
b: clearColor[2],
|
|
835
|
+
a: clearColor[3]
|
|
836
|
+
},
|
|
837
|
+
loadOp: "clear",
|
|
838
|
+
storeOp: "store"
|
|
839
|
+
}] });
|
|
840
|
+
pass.setPipeline(blitPipeline);
|
|
841
|
+
pass.setBindGroup(0, bindGroup);
|
|
842
|
+
pass.draw(3);
|
|
843
|
+
pass.end();
|
|
844
|
+
};
|
|
845
|
+
/**
|
|
846
|
+
* Executes a full frame render.
|
|
847
|
+
*/
|
|
848
|
+
const render = ({ time, delta, renderMode, uniforms, textures, canvasSize }) => {
|
|
849
|
+
if (deviceLostMessage) throw new Error(deviceLostMessage);
|
|
850
|
+
if (uncapturedErrorMessage) {
|
|
851
|
+
const message = uncapturedErrorMessage;
|
|
852
|
+
uncapturedErrorMessage = null;
|
|
853
|
+
throw new Error(message);
|
|
854
|
+
}
|
|
855
|
+
const { width, height } = resizeCanvas(options.canvas, options.getDpr(), canvasSize);
|
|
856
|
+
if (!contextConfigured || configuredWidth !== width || configuredHeight !== height) {
|
|
857
|
+
context.configure({
|
|
858
|
+
device,
|
|
859
|
+
format,
|
|
860
|
+
alphaMode: "premultiplied"
|
|
861
|
+
});
|
|
862
|
+
contextConfigured = true;
|
|
863
|
+
configuredWidth = width;
|
|
864
|
+
configuredHeight = height;
|
|
865
|
+
}
|
|
866
|
+
frameScratch[0] = time;
|
|
867
|
+
frameScratch[1] = delta;
|
|
868
|
+
frameScratch[2] = width;
|
|
869
|
+
frameScratch[3] = height;
|
|
870
|
+
device.queue.writeBuffer(frameBuffer, 0, frameScratch.buffer, frameScratch.byteOffset, frameScratch.byteLength);
|
|
871
|
+
packUniformsInto(uniforms, options.uniformLayout, uniformScratch);
|
|
872
|
+
if (!hasUniformSnapshot) {
|
|
873
|
+
device.queue.writeBuffer(uniformBuffer, 0, uniformScratch.buffer, uniformScratch.byteOffset, uniformScratch.byteLength);
|
|
874
|
+
uniformPrevious.set(uniformScratch);
|
|
875
|
+
hasUniformSnapshot = true;
|
|
876
|
+
} else {
|
|
877
|
+
const dirtyRanges = findDirtyFloatRanges(uniformPrevious, uniformScratch);
|
|
878
|
+
for (const range of dirtyRanges) {
|
|
879
|
+
const byteOffset = range.start * 4;
|
|
880
|
+
const byteLength = range.count * 4;
|
|
881
|
+
device.queue.writeBuffer(uniformBuffer, byteOffset, uniformScratch.buffer, uniformScratch.byteOffset + byteOffset, byteLength);
|
|
882
|
+
}
|
|
883
|
+
if (dirtyRanges.length > 0) uniformPrevious.set(uniformScratch);
|
|
884
|
+
}
|
|
885
|
+
let bindGroupDirty = false;
|
|
886
|
+
for (const binding of textureBindings) if (updateTextureBinding(binding, textures[binding.key] ?? normalizedTextureDefinitions[binding.key]?.source ?? null, renderMode)) bindGroupDirty = true;
|
|
887
|
+
if (bindGroupDirty) bindGroup = createBindGroup();
|
|
888
|
+
const commandEncoder = device.createCommandEncoder();
|
|
889
|
+
const passes = resolvePasses();
|
|
890
|
+
const clearColor = options.getClearColor();
|
|
891
|
+
syncPassLifecycle(passes, width, height);
|
|
892
|
+
const runtimeTargets = syncRenderTargets(width, height);
|
|
893
|
+
const graphPlan = isGraphPlanCacheValid(passes, clearColor) ? cachedGraphPlan : (() => {
|
|
894
|
+
const nextPlan = planRenderGraph(passes, clearColor, renderTargetKeys);
|
|
895
|
+
updateGraphPlanCache(passes, clearColor, nextPlan);
|
|
896
|
+
return nextPlan;
|
|
897
|
+
})();
|
|
898
|
+
const canvasTexture = context.getCurrentTexture();
|
|
899
|
+
const canvasSurface = {
|
|
900
|
+
texture: canvasTexture,
|
|
901
|
+
view: canvasTexture.createView(),
|
|
902
|
+
width,
|
|
903
|
+
height,
|
|
904
|
+
format
|
|
905
|
+
};
|
|
906
|
+
const slots = graphPlan.steps.length > 0 ? {
|
|
907
|
+
source: ensureSlotTarget("source", width, height),
|
|
908
|
+
target: ensureSlotTarget("target", width, height),
|
|
909
|
+
canvas: canvasSurface
|
|
910
|
+
} : null;
|
|
911
|
+
const sceneOutput = slots ? slots.source : canvasSurface;
|
|
912
|
+
const scenePass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
913
|
+
view: sceneOutput.view,
|
|
914
|
+
clearValue: {
|
|
915
|
+
r: clearColor[0],
|
|
916
|
+
g: clearColor[1],
|
|
917
|
+
b: clearColor[2],
|
|
918
|
+
a: clearColor[3]
|
|
919
|
+
},
|
|
920
|
+
loadOp: "clear",
|
|
921
|
+
storeOp: "store"
|
|
922
|
+
}] });
|
|
923
|
+
scenePass.setPipeline(pipeline);
|
|
924
|
+
scenePass.setBindGroup(0, bindGroup);
|
|
925
|
+
scenePass.draw(3);
|
|
926
|
+
scenePass.end();
|
|
927
|
+
if (slots) {
|
|
928
|
+
const resolveStepSurface = (slot) => {
|
|
929
|
+
if (slot === "source") return slots.source;
|
|
930
|
+
if (slot === "target") return slots.target;
|
|
931
|
+
if (slot === "canvas") return slots.canvas;
|
|
932
|
+
const named = runtimeTargets[slot];
|
|
933
|
+
if (!named) throw new Error(`Render graph references unknown runtime target "${slot}".`);
|
|
934
|
+
return named;
|
|
935
|
+
};
|
|
936
|
+
for (const step of graphPlan.steps) {
|
|
937
|
+
const input = resolveStepSurface(step.input);
|
|
938
|
+
const output = resolveStepSurface(step.output);
|
|
939
|
+
step.pass.render({
|
|
940
|
+
device,
|
|
941
|
+
commandEncoder,
|
|
942
|
+
source: slots.source,
|
|
943
|
+
target: slots.target,
|
|
944
|
+
canvas: slots.canvas,
|
|
945
|
+
input,
|
|
946
|
+
output,
|
|
947
|
+
targets: runtimeTargets,
|
|
948
|
+
time,
|
|
949
|
+
delta,
|
|
950
|
+
width,
|
|
951
|
+
height,
|
|
952
|
+
clear: step.clear,
|
|
953
|
+
clearColor: step.clearColor,
|
|
954
|
+
preserve: step.preserve,
|
|
955
|
+
beginRenderPass: (passOptions) => {
|
|
956
|
+
const clear = passOptions?.clear ?? step.clear;
|
|
957
|
+
const clearColor = passOptions?.clearColor ?? step.clearColor;
|
|
958
|
+
const preserve = passOptions?.preserve ?? step.preserve;
|
|
959
|
+
return commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
960
|
+
view: passOptions?.view ?? output.view,
|
|
961
|
+
clearValue: {
|
|
962
|
+
r: clearColor[0],
|
|
963
|
+
g: clearColor[1],
|
|
964
|
+
b: clearColor[2],
|
|
965
|
+
a: clearColor[3]
|
|
966
|
+
},
|
|
967
|
+
loadOp: clear ? "clear" : "load",
|
|
968
|
+
storeOp: preserve ? "store" : "discard"
|
|
969
|
+
}] });
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
if (step.needsSwap) {
|
|
973
|
+
const previousSource = slots.source;
|
|
974
|
+
slots.source = slots.target;
|
|
975
|
+
slots.target = previousSource;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (graphPlan.finalOutput !== "canvas") blitToCanvas(commandEncoder, resolveStepSurface(graphPlan.finalOutput).view, slots.canvas.view, clearColor);
|
|
979
|
+
}
|
|
980
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
981
|
+
};
|
|
982
|
+
acceptInitializationCleanups = false;
|
|
983
|
+
initializationCleanups.length = 0;
|
|
984
|
+
return {
|
|
985
|
+
render,
|
|
986
|
+
destroy: () => {
|
|
987
|
+
isDestroyed = true;
|
|
988
|
+
device.removeEventListener("uncapturederror", handleUncapturedError);
|
|
989
|
+
frameBuffer.destroy();
|
|
990
|
+
uniformBuffer.destroy();
|
|
991
|
+
destroyRenderTexture(sourceSlotTarget);
|
|
992
|
+
destroyRenderTexture(targetSlotTarget);
|
|
993
|
+
for (const target of runtimeRenderTargets.values()) target.texture.destroy();
|
|
994
|
+
runtimeRenderTargets.clear();
|
|
995
|
+
for (const pass of activePasses) pass.dispose?.();
|
|
996
|
+
activePasses.length = 0;
|
|
997
|
+
lifecyclePassesRef = null;
|
|
998
|
+
for (const binding of textureBindings) {
|
|
999
|
+
binding.texture?.destroy();
|
|
1000
|
+
binding.fallbackTexture.destroy();
|
|
1001
|
+
}
|
|
1002
|
+
blitBindGroupByView = /* @__PURE__ */ new WeakMap();
|
|
1003
|
+
cachedGraphPlan = null;
|
|
1004
|
+
cachedGraphPasses.length = 0;
|
|
1005
|
+
renderTargetSnapshot = {};
|
|
1006
|
+
renderTargetKeys = [];
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
isDestroyed = true;
|
|
1011
|
+
acceptInitializationCleanups = false;
|
|
1012
|
+
device.removeEventListener("uncapturederror", handleUncapturedError);
|
|
1013
|
+
runInitializationCleanups();
|
|
1014
|
+
throw error;
|
|
1015
|
+
}
|
|
1159
1016
|
}
|
|
1017
|
+
//#endregion
|
|
1018
|
+
export { createRenderer, findDirtyFloatRanges };
|
|
1019
|
+
|
|
1020
|
+
//# sourceMappingURL=renderer.js.map
|