@flighthq/render-wgpu 0.5.0-next.1944.cd6b6e7 → 0.5.1-edge.638.d4dbd4e
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 +2 -2
- package/dist/contract.d.ts +3 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +3 -0
- package/dist/contract.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/wgpuAntialias.d.ts +8 -4
- package/dist/wgpuAntialias.d.ts.map +1 -1
- package/dist/wgpuAntialias.js +23 -4
- package/dist/wgpuAntialias.js.map +1 -1
- package/dist/wgpuBackground.d.ts +10 -4
- package/dist/wgpuBackground.d.ts.map +1 -1
- package/dist/wgpuBackground.js +87 -3
- package/dist/wgpuBackground.js.map +1 -1
- package/dist/wgpuCompressedTexture.js +9 -9
- package/dist/wgpuCompressedTexture.js.map +1 -1
- package/dist/wgpuDeviceLoss.d.ts +21 -0
- package/dist/wgpuDeviceLoss.d.ts.map +1 -0
- package/dist/wgpuDeviceLoss.js +62 -0
- package/dist/wgpuDeviceLoss.js.map +1 -0
- package/dist/wgpuDraw.d.ts +0 -1
- package/dist/wgpuDraw.d.ts.map +1 -1
- package/dist/wgpuDraw.js +73 -96
- package/dist/wgpuDraw.js.map +1 -1
- package/dist/wgpuElement.d.ts +5 -0
- package/dist/wgpuElement.d.ts.map +1 -1
- package/dist/wgpuElement.js +17 -6
- package/dist/wgpuElement.js.map +1 -1
- package/dist/wgpuExternalTexture.d.ts.map +1 -1
- package/dist/wgpuExternalTexture.js +12 -11
- package/dist/wgpuExternalTexture.js.map +1 -1
- package/dist/wgpuFullscreenPass.js +1 -1
- package/dist/wgpuFullscreenPass.js.map +1 -1
- package/dist/wgpuHost.d.ts +7 -0
- package/dist/wgpuHost.d.ts.map +1 -0
- package/dist/wgpuHost.js +80 -0
- package/dist/wgpuHost.js.map +1 -0
- package/dist/wgpuMipmap.js +3 -3
- package/dist/wgpuMipmap.js.map +1 -1
- package/dist/wgpuPipeline.d.ts +5 -0
- package/dist/wgpuPipeline.d.ts.map +1 -0
- package/dist/wgpuPipeline.js +29 -0
- package/dist/wgpuPipeline.js.map +1 -0
- package/dist/wgpuRenderState.d.ts +13 -6
- package/dist/wgpuRenderState.d.ts.map +1 -1
- package/dist/wgpuRenderState.js +220 -271
- package/dist/wgpuRenderState.js.map +1 -1
- package/dist/wgpuRenderTarget.d.ts.map +1 -1
- package/dist/wgpuRenderTarget.js +6 -5
- package/dist/wgpuRenderTarget.js.map +1 -1
- package/dist/wgpuRenderTexture.js +4 -4
- package/dist/wgpuRenderTexture.js.map +1 -1
- package/dist/wgpuShader.d.ts.map +1 -1
- package/dist/wgpuShader.js +6 -4
- package/dist/wgpuShader.js.map +1 -1
- package/dist/wgpuSurface.d.ts +5 -5
- package/dist/wgpuSurface.d.ts.map +1 -1
- package/dist/wgpuSurface.js +2 -2
- package/dist/wgpuSurface.js.map +1 -1
- package/dist/wgpuTestHelper.d.ts +2 -2
- package/dist/wgpuTestHelper.d.ts.map +1 -1
- package/dist/wgpuTestHelper.js +6 -2
- package/dist/wgpuTestHelper.js.map +1 -1
- package/package.json +11 -11
- package/src/wgpuAntialias.test.ts +26 -4
- package/src/wgpuBackground.test.ts +43 -73
- package/src/wgpuDeviceLoss.test.ts +240 -0
- package/src/wgpuDraw.test.ts +99 -24
- package/src/wgpuElement.test.ts +127 -13
- package/src/wgpuHost.test.ts +227 -0
- package/src/wgpuMaterialRegistry.test.ts +6 -3
- package/src/wgpuMipmap.test.ts +3 -3
- package/src/wgpuPipeline.test.ts +52 -0
- package/src/wgpuRenderState.test.ts +679 -88
- package/src/wgpuRenderTarget.test.ts +12 -8
- package/src/wgpuRenderTexture.test.ts +1 -1
- package/src/wgpuShader.test.ts +2 -2
package/dist/wgpuRenderState.js
CHANGED
|
@@ -1,107 +1,96 @@
|
|
|
1
|
+
import { createEntity } from '@flighthq/entity/contract';
|
|
1
2
|
import { createMatrix } from '@flighthq/geometry/contract';
|
|
2
|
-
import {
|
|
3
|
-
import { copyAllRenderersFromRenderState, copyRenderStateRegistrations, createRenderState as _createRenderState, createRenderStateRuntime, destroyRenderState, setRenderStateBackgroundColor, } from '@flighthq/render/contract';
|
|
3
|
+
import { createRenderState as _createRenderState, createRenderStateRuntime, destroyRenderState, setRenderStateBackgroundColor, } from '@flighthq/render/contract';
|
|
4
4
|
import { EntityRuntimeKey, RegistryEntryState } from '@flighthq/types/contract';
|
|
5
|
+
import { observeWgpuDeviceLoss } from './wgpuDeviceLoss';
|
|
5
6
|
import { warmWgpuPipelines } from './wgpuDraw';
|
|
7
|
+
import { getWgpuHostBackend } from './wgpuHost';
|
|
8
|
+
import { createEmptyWgpuRegistries, createWgpuPipeline } from './wgpuPipeline';
|
|
6
9
|
import { createWgpuBindGroupLayouts, UNIFORM_BYTE_SIZE } from './wgpuShader';
|
|
7
10
|
// Ring buffer: 4096 draw slots per frame. Stride is clamped to at least 256 by the spec.
|
|
8
11
|
const RING_SLOT_COUNT = 4096;
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// Acquires host handles the CALLER owns. Ownership is `caller`, so Flight never releases these: the caller
|
|
13
|
+
// ends their life with `releaseWgpuAcquisition`, and a state built on them leaves them intact when it is
|
|
14
|
+
// destroyed. Returns `null` rather than throwing, because "this environment has no WebGPU" is an expected
|
|
15
|
+
// outcome and not a programmer error.
|
|
16
|
+
export async function createWgpuAcquisitionFromCanvasElement(canvas, options = {}) {
|
|
17
|
+
try {
|
|
18
|
+
const acquired = await getWgpuHostBackend().acquire(canvas, options);
|
|
19
|
+
return { ...acquired, ownership: 'caller' };
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function createWgpuDeviceState(device) {
|
|
26
|
+
const deviceRuntime = createMinimalDeviceRuntime(device);
|
|
27
|
+
const state = createEntity({ device });
|
|
28
|
+
state[EntityRuntimeKey] = deviceRuntime;
|
|
29
|
+
return state;
|
|
30
|
+
}
|
|
31
|
+
export function createWgpuOffscreenRenderState(sourceOrDeviceState, pipeline, options = {}) {
|
|
32
|
+
if (pipeline !== undefined)
|
|
33
|
+
return initializeWgpuDeviceRenderState(sourceOrDeviceState, pipeline, options);
|
|
34
|
+
const source = sourceOrDeviceState;
|
|
14
35
|
const sourceRuntime = getWgpuRenderStateRuntime(source);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
shapeRasterizer: sourceRuntime.registries.shapeRasterizer,
|
|
36
|
-
strokeTessellator: targetRuntime.registries.strokeTessellator,
|
|
37
|
-
textureResolvers: sourceRuntime.registries.textureResolvers,
|
|
38
|
-
velocityWriters: sourceRuntime.registries.velocityWriters,
|
|
39
|
-
};
|
|
40
|
-
targetRuntime.wgpuRenderTextureGuard = sourceRuntime.wgpuRenderTextureGuard;
|
|
41
|
-
copyRenderStateRegistrations(target, source);
|
|
36
|
+
const lost = sourceRuntime.context.lost;
|
|
37
|
+
if (lost !== null)
|
|
38
|
+
return { reason: 'device-lost', info: lost };
|
|
39
|
+
const derivedPipeline = createWgpuPipeline(sourceRuntime.registries);
|
|
40
|
+
const state = initializeWgpuDeviceRenderState(source.deviceState, derivedPipeline, {
|
|
41
|
+
backgroundColor: source.backgroundColor,
|
|
42
|
+
format: source.format,
|
|
43
|
+
imageSmoothingEnabled: source.allowSmoothing,
|
|
44
|
+
pixelRatio: source.pixelRatio,
|
|
45
|
+
roundPixels: source.roundPixels,
|
|
46
|
+
sceneGraphSyncPolicy: source.sceneGraphSyncPolicy,
|
|
47
|
+
});
|
|
48
|
+
const runtime = getWgpuRenderStateRuntime(state);
|
|
49
|
+
runtime.applyBlendModeParent = source;
|
|
50
|
+
runtime.defaultBitmapShader = sourceRuntime.defaultBitmapShader;
|
|
51
|
+
runtime.mipmapDegradedGuard = sourceRuntime.mipmapDegradedGuard;
|
|
52
|
+
runtime.mipmapGenerator = sourceRuntime.mipmapGenerator;
|
|
53
|
+
runtime.webgpuShaderBindingResolver = sourceRuntime.webgpuShaderBindingResolver;
|
|
54
|
+
runtime.wgpuRenderTextureGuard = sourceRuntime.wgpuRenderTextureGuard;
|
|
55
|
+
return { reason: 'ok', state };
|
|
42
56
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
// Synchronous: with the handles already in hand there is nothing left to await. Everything asynchronous
|
|
58
|
+
// lives in acquisition.
|
|
59
|
+
export function createWgpuRenderState(acquisition, pipeline, options = {}) {
|
|
60
|
+
const hostBackend = getWgpuHostBackend();
|
|
61
|
+
try {
|
|
62
|
+
return initializeWgpuPresentationRenderState(options, acquisition, pipeline, hostBackend);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
// ★ NEVER release what the caller owns, not even while unwinding a failure. This is the path where
|
|
66
|
+
// borrowed handles were previously destroyed on the way out, and it was safe only because the shipped
|
|
67
|
+
// backend happened to check. The policy belongs here, where it holds for every backend.
|
|
68
|
+
if (acquisition.ownership !== 'caller')
|
|
69
|
+
hostBackend.release(acquisition);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// The canvas convenience: acquire, then build. The handles are `flight`-owned, so destroying the state
|
|
74
|
+
// releases them — the behavior every existing caller had when it passed a canvas.
|
|
75
|
+
export async function createWgpuRenderStateFromCanvasElement(canvas, pipeline, options = {}) {
|
|
76
|
+
const acquisition = await getWgpuHostBackend().acquire(canvas, {
|
|
77
|
+
format: options.format,
|
|
78
|
+
powerPreference: options.powerPreference,
|
|
62
79
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
warmWgpuPipelines(state);
|
|
75
|
-
return state;
|
|
80
|
+
return createWgpuRenderState(acquisition, pipeline, options);
|
|
81
|
+
}
|
|
82
|
+
export function createWgpuRenderStateRuntime(deviceStateOrRuntime, pipeline) {
|
|
83
|
+
const deviceRuntime = EntityRuntimeKey in deviceStateOrRuntime
|
|
84
|
+
? getWgpuDeviceRuntime(deviceStateOrRuntime)
|
|
85
|
+
: deviceStateOrRuntime.context;
|
|
86
|
+
const resolvedPipeline = pipeline ??
|
|
87
|
+
createWgpuPipeline(EntityRuntimeKey in deviceStateOrRuntime
|
|
88
|
+
? createEmptyWgpuRegistries()
|
|
89
|
+
: deviceStateOrRuntime.registries);
|
|
90
|
+
return createWgpuRenderStateRuntimeInternal(deviceRuntime, resolvedPipeline);
|
|
76
91
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
throw new Error('WebGPU is not supported in this browser.');
|
|
80
|
-
const adapter = await navigator.gpu.requestAdapter(options.powerPreference != null ? { powerPreference: options.powerPreference } : undefined);
|
|
81
|
-
if (!adapter)
|
|
82
|
-
throw new Error('Failed to get WebGPU adapter.');
|
|
83
|
-
// The forward-lit 3D pipeline binds 5 groups (Frame, Draw, Material, Shadow, Ibl); WebGPU's
|
|
84
|
-
// guaranteed baseline maxBindGroups is only 4, so request 5 when the adapter allows it. Guarded by
|
|
85
|
-
// the adapter limit: requiredLimits above the adapter's support makes requestDevice reject, and a
|
|
86
|
-
// baseline-4 adapter simply keeps 4 (the 5-group lit pipeline is unavailable there until shadow+IBL
|
|
87
|
-
// are folded into one group — a portability follow-up). 2D and unlit paths use ≤4 groups regardless.
|
|
88
|
-
const requiredLimits = {};
|
|
89
|
-
if (adapter.limits.maxBindGroups >= 5)
|
|
90
|
-
requiredLimits.maxBindGroups = 5;
|
|
91
|
-
// Compression features must be enabled at device creation; there is no later extension activation
|
|
92
|
-
// like WebGL. Enable every family the adapter exposes so the opt-in compressed uploader can use the
|
|
93
|
-
// native path when it is registered, while unsupported families retain their CPU decode fallback.
|
|
94
|
-
const requiredFeatures = ['texture-compression-bc', 'texture-compression-etc2', 'texture-compression-astc'].filter((feature) => adapter.features.has(feature));
|
|
95
|
-
const deviceDescriptor = {};
|
|
96
|
-
if (Object.keys(requiredLimits).length > 0)
|
|
97
|
-
deviceDescriptor.requiredLimits = requiredLimits;
|
|
98
|
-
if (requiredFeatures.length > 0)
|
|
99
|
-
deviceDescriptor.requiredFeatures = requiredFeatures;
|
|
100
|
-
const device = await adapter.requestDevice(deviceDescriptor);
|
|
101
|
-
const format = options.format ?? navigator.gpu.getPreferredCanvasFormat();
|
|
102
|
-
const context = canvas.getContext('webgpu');
|
|
103
|
-
if (!context)
|
|
104
|
-
throw new Error('Failed to get WebGPU canvas context.');
|
|
92
|
+
function initializeWgpuPresentationRenderState(options, acquisition, pipeline, hostBackend) {
|
|
93
|
+
const { context, device, format } = acquisition;
|
|
105
94
|
// COPY_SRC lets the canvas texture be read back via copyTextureToBuffer (createBitmapFromWgpuRenderState).
|
|
106
95
|
// It is the only reliable way to read a Wgpu frame in headless/software contexts, where canvas
|
|
107
96
|
// presentation does not surface the swapchain; it also backs user-facing screenshot/save-pixels needs.
|
|
@@ -111,7 +100,19 @@ export async function createWgpuRenderState(canvas, options = {}) {
|
|
|
111
100
|
alphaMode: 'premultiplied',
|
|
112
101
|
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
|
|
113
102
|
});
|
|
114
|
-
|
|
103
|
+
const deviceState = createWgpuDeviceState(device);
|
|
104
|
+
const ownership = { acquisition, hostBackend, references: 0 };
|
|
105
|
+
_acquisitionByDeviceRuntime.set(getWgpuDeviceRuntime(deviceState), ownership);
|
|
106
|
+
const state = initializeWgpuDeviceRenderState(deviceState, pipeline, { ...options, format });
|
|
107
|
+
Object.assign(state, { context, surface: acquisition.surface });
|
|
108
|
+
getWgpuRenderStateRuntime(state).surfaceAntialiasEnabled = options.antialias ?? false;
|
|
109
|
+
return state;
|
|
110
|
+
}
|
|
111
|
+
function initializeWgpuDeviceRenderState(deviceState, pipeline, options) {
|
|
112
|
+
const device = deviceState.device;
|
|
113
|
+
const format = options.format ?? 'bgra8unorm';
|
|
114
|
+
const deviceRuntime = getWgpuDeviceRuntime(deviceState);
|
|
115
|
+
initializeWgpuDeviceRuntime(deviceRuntime);
|
|
115
116
|
const uniformStride = Math.max(256, device.limits.minUniformBufferOffsetAlignment, UNIFORM_BYTE_SIZE);
|
|
116
117
|
const ringByteSize = uniformStride * RING_SLOT_COUNT;
|
|
117
118
|
const uniformBuffer = device.createBuffer({
|
|
@@ -119,24 +120,10 @@ export async function createWgpuRenderState(canvas, options = {}) {
|
|
|
119
120
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
120
121
|
});
|
|
121
122
|
const uniformData = new Float32Array(ringByteSize / 4);
|
|
122
|
-
const uniformDataU32 = new Uint32Array(uniformData.buffer);
|
|
123
|
-
const { uniformBindGroupLayout, textureBindGroupLayout } = createWgpuBindGroupLayouts(device);
|
|
124
123
|
const uniformBindGroup = device.createBindGroup({
|
|
125
|
-
layout: uniformBindGroupLayout,
|
|
124
|
+
layout: deviceRuntime.uniformBindGroupLayout,
|
|
126
125
|
entries: [{ binding: 0, resource: { buffer: uniformBuffer, size: UNIFORM_BYTE_SIZE } }],
|
|
127
126
|
});
|
|
128
|
-
const linearSampler = device.createSampler({
|
|
129
|
-
minFilter: 'linear',
|
|
130
|
-
magFilter: 'linear',
|
|
131
|
-
addressModeU: 'clamp-to-edge',
|
|
132
|
-
addressModeV: 'clamp-to-edge',
|
|
133
|
-
});
|
|
134
|
-
const nearestSampler = device.createSampler({
|
|
135
|
-
minFilter: 'nearest',
|
|
136
|
-
magFilter: 'nearest',
|
|
137
|
-
addressModeU: 'clamp-to-edge',
|
|
138
|
-
addressModeV: 'clamp-to-edge',
|
|
139
|
-
});
|
|
140
127
|
const state = _createRenderState({
|
|
141
128
|
allowSmoothing: options.imageSmoothingEnabled ?? true,
|
|
142
129
|
pixelRatio: options.pixelRatio ?? 1,
|
|
@@ -147,36 +134,21 @@ export async function createWgpuRenderState(canvas, options = {}) {
|
|
|
147
134
|
if (options.backgroundColor != null)
|
|
148
135
|
setRenderStateBackgroundColor(state, options.backgroundColor);
|
|
149
136
|
state.applyBlendMode = null;
|
|
150
|
-
state
|
|
151
|
-
|
|
152
|
-
state.device = device;
|
|
153
|
-
state.format = format;
|
|
154
|
-
const runtime = createWgpuRenderStateRuntime();
|
|
137
|
+
Object.assign(state, { deviceState, device, format, pipeline });
|
|
138
|
+
const runtime = createWgpuRenderStateRuntime(deviceState, pipeline);
|
|
155
139
|
state[EntityRuntimeKey] = runtime;
|
|
156
|
-
runtime.surfaceAntialiasEnabled =
|
|
140
|
+
runtime.surfaceAntialiasEnabled = false;
|
|
157
141
|
runtime.currentBlendMode = null;
|
|
158
142
|
runtime.currentRenderTarget = null;
|
|
159
|
-
runtime.uniformBindGroupLayout = uniformBindGroupLayout;
|
|
160
|
-
runtime.textureBindGroupLayout = textureBindGroupLayout;
|
|
161
143
|
runtime.uniformBuffer = uniformBuffer;
|
|
162
144
|
runtime.uniformData = uniformData;
|
|
163
|
-
runtime.uniformDataU32 =
|
|
145
|
+
runtime.uniformDataU32 = new Uint32Array(uniformData.buffer);
|
|
164
146
|
runtime.uniformOffset = 0;
|
|
165
147
|
runtime.uniformStride = uniformStride;
|
|
166
148
|
runtime.uniformBindGroup = uniformBindGroup;
|
|
167
149
|
runtime.matrixArray = new Float32Array(9);
|
|
168
|
-
runtime.pipelineCache = new Map();
|
|
169
|
-
runtime.linearSampler = linearSampler;
|
|
170
|
-
runtime.nearestSampler = nearestSampler;
|
|
171
|
-
runtime.samplerCache = new Map();
|
|
172
150
|
runtime.mipmapDegradedGuard = null;
|
|
173
151
|
runtime.mipmapGenerator = null;
|
|
174
|
-
runtime.mipmapPipelineCache = new Map();
|
|
175
|
-
runtime.textureCache = new WeakMap();
|
|
176
|
-
runtime.textureSourcePremultipliedTextureCache = new WeakMap();
|
|
177
|
-
runtime.textureSourcePremultipliedSrgbTextureCache = new WeakMap();
|
|
178
|
-
runtime.textureSourceStraightTextureCache = new WeakMap();
|
|
179
|
-
runtime.textureSourceStraightSrgbTextureCache = new WeakMap();
|
|
180
152
|
runtime.defaultBitmapShader = null;
|
|
181
153
|
runtime.particleInstanceBuffer = null;
|
|
182
154
|
runtime.particleInstanceData = null;
|
|
@@ -191,14 +163,18 @@ export async function createWgpuRenderState(canvas, options = {}) {
|
|
|
191
163
|
runtime.quadBatchWriterTexture = null;
|
|
192
164
|
runtime.quadBatchWriterSampler = null;
|
|
193
165
|
runtime.quadBatchWriterSmoothing = null;
|
|
194
|
-
// Color-adjustment fold state (mode/data + the folded module) is not allocated here: it is owned by
|
|
195
|
-
// the opt-in registerWgpuColorAdjustmentMaterialFeature, so a state that never tints carries none of it.
|
|
196
166
|
runtime.quadBatchWriterBufferPool = [];
|
|
197
167
|
runtime.quadBatchWriterBufferCursor = 0;
|
|
198
168
|
runtime.commandEncoder = null;
|
|
199
169
|
runtime.renderPass = null;
|
|
200
170
|
runtime.canvasTextureView = null;
|
|
201
171
|
runtime.canvasViewCleared = false;
|
|
172
|
+
runtime.frameCaptureEnabled = false;
|
|
173
|
+
runtime.frameCaptureTexture = null;
|
|
174
|
+
runtime.frameCaptureBuffer = null;
|
|
175
|
+
runtime.frameCaptureBytesPerRow = 0;
|
|
176
|
+
runtime.frameCaptureWidth = 0;
|
|
177
|
+
runtime.frameCaptureHeight = 0;
|
|
202
178
|
runtime.depthStencilTexture = null;
|
|
203
179
|
runtime.depthStencilView = null;
|
|
204
180
|
runtime.depthStencilWidth = 0;
|
|
@@ -216,66 +192,23 @@ export async function createWgpuRenderState(canvas, options = {}) {
|
|
|
216
192
|
warmWgpuPipelines(state);
|
|
217
193
|
return state;
|
|
218
194
|
}
|
|
219
|
-
// Allocates the package-private GPU runtime for a WgpuRenderState. createWgpuRenderState attaches
|
|
220
|
-
// one to each state under EntityRuntimeKey and populates its fields; getWgpuRenderStateRuntime reads
|
|
221
|
-
// it back. The render path writes the returned object every frame, so the return is intentionally
|
|
222
|
-
// mutable (not Readonly).
|
|
223
|
-
export function createWgpuRenderStateRuntime(sharedRuntime) {
|
|
224
|
-
const runtime = createRenderStateRuntime();
|
|
225
|
-
runtime.applyBlendModeParent = null;
|
|
226
|
-
runtime.surfaceAntialiasEnabled = false;
|
|
227
|
-
runtime.surfaceAntialiasTexture = null;
|
|
228
|
-
runtime.surfaceAntialiasView = null;
|
|
229
|
-
runtime.surfaceAntialiasWidth = 0;
|
|
230
|
-
runtime.surfaceAntialiasHeight = 0;
|
|
231
|
-
runtime.surfaceAntialiasResolveBindGroupLayout = null;
|
|
232
|
-
runtime.surfaceAntialiasResolvePipeline = null;
|
|
233
|
-
runtime.surfaceAntialiasResolveBindGroup = null;
|
|
234
|
-
runtime.surfacePresentationView = null;
|
|
235
|
-
runtime.registries = {
|
|
236
|
-
compressedTextureDecoder: createSlotTable('WgpuCompressedTextureDecoder', 'Unregistered'),
|
|
237
|
-
compressedTextureUpload: createSlotTable('WgpuCompressedTextureUpload', 'Unregistered'),
|
|
238
|
-
customMaterialShaders: createKeyedTable('WgpuCustomMaterialShader', 'Unregistered'),
|
|
239
|
-
materialRenderers: createKeyedTable('WgpuMaterialRenderer', 'StandardMaterial'),
|
|
240
|
-
meshMaterialRenderers: createKeyedTable('WgpuMeshMaterialRenderer', 'StandardMaterial'),
|
|
241
|
-
modifierSnippets: createKeyedTable('WgpuModifierSnippet', 'Unregistered'),
|
|
242
|
-
modifierSnippetRevision: 0,
|
|
243
|
-
renderEffects: createKeyedTable('WgpuRenderEffect', 'Unregistered'),
|
|
244
|
-
renderers: runtime.registries.renderers,
|
|
245
|
-
shapeRasterizer: createSlotTable('WgpuShapeRasterizer', 'Unregistered'),
|
|
246
|
-
strokeTessellator: runtime.registries.strokeTessellator,
|
|
247
|
-
textureResolvers: createKeyedTable('WgpuTextureResolver', 'Unregistered'),
|
|
248
|
-
velocityWriters: createKeyedTable('WgpuVelocityWriter', 'Unregistered'),
|
|
249
|
-
};
|
|
250
|
-
const deviceRuntime = sharedRuntime === undefined ? { fields: {}, references: 0 } : getWgpuDeviceRuntime(sharedRuntime);
|
|
251
|
-
deviceRuntime.references++;
|
|
252
|
-
_deviceRuntimeByStateRuntime.set(runtime, deviceRuntime);
|
|
253
|
-
for (const key of WGPU_DEVICE_RUNTIME_KEYS) {
|
|
254
|
-
Object.defineProperty(runtime, key, {
|
|
255
|
-
configurable: true,
|
|
256
|
-
enumerable: true,
|
|
257
|
-
get: () => deviceRuntime.fields[key],
|
|
258
|
-
set: (value) => {
|
|
259
|
-
deviceRuntime.fields[key] = value;
|
|
260
|
-
},
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
return runtime;
|
|
264
|
-
}
|
|
265
195
|
// Destroys the GPU buffers and textures createWgpuRenderState (and the lazy quad-batch writer/particle
|
|
266
196
|
// paths) allocated on `state`: the uniform buffer, particle instance buffer, depth-stencil texture,
|
|
267
197
|
// and every quad-batch writer pool slot's instance/material buffers. Call when the render state is no
|
|
268
198
|
// longer needed.
|
|
269
199
|
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
200
|
+
// GC-managed Wgpu objects with no destroy() (pipelines, bind groups, layouts, samplers, shader
|
|
201
|
+
// modules, texture views) are not touched. textureCache is a WeakMap and cannot be enumerated; its
|
|
202
|
+
// entries' textures are freed per-node by the dispose* paths. The shared device tier routes its
|
|
203
|
+
// acquisition through the originating host backend when its last state is destroyed.
|
|
274
204
|
export function destroyWgpuRenderState(state) {
|
|
275
205
|
if (_destroyedStates.has(state))
|
|
276
206
|
return;
|
|
277
207
|
_destroyedStates.add(state);
|
|
278
208
|
const runtime = getWgpuRenderStateRuntime(state);
|
|
209
|
+
for (const teardown of [...runtime.teardowns])
|
|
210
|
+
teardown(state);
|
|
211
|
+
runtime.teardowns.length = 0;
|
|
279
212
|
destroyRenderState(state);
|
|
280
213
|
runtime.uniformBuffer?.destroy();
|
|
281
214
|
runtime.particleInstanceBuffer?.destroy();
|
|
@@ -285,7 +218,45 @@ export function destroyWgpuRenderState(state) {
|
|
|
285
218
|
slot.instanceBuffer?.destroy();
|
|
286
219
|
slot.materialBuffer?.destroy();
|
|
287
220
|
}
|
|
288
|
-
|
|
221
|
+
const ctx = runtime.context;
|
|
222
|
+
ctx.references--;
|
|
223
|
+
if (ctx.references === 0) {
|
|
224
|
+
const device = ctx.device;
|
|
225
|
+
for (const teardown of ctx.teardowns)
|
|
226
|
+
teardown(device);
|
|
227
|
+
ctx.teardowns.length = 0;
|
|
228
|
+
}
|
|
229
|
+
const ownership = _acquisitionByStateRuntime.get(runtime);
|
|
230
|
+
if (ownership !== undefined) {
|
|
231
|
+
ownership.references--;
|
|
232
|
+
if (ownership.references === 0 && ownership.acquisition.ownership !== 'caller') {
|
|
233
|
+
ownership.hostBackend.release(ownership.acquisition);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function createWgpuRenderStateRuntimeInternal(deviceRuntime, pipeline) {
|
|
238
|
+
const runtime = createRenderStateRuntime();
|
|
239
|
+
runtime.applyBlendModeParent = null;
|
|
240
|
+
runtime.surfaceAntialiasEnabled = false;
|
|
241
|
+
runtime.surfaceAntialiasTexture = null;
|
|
242
|
+
runtime.surfaceAntialiasView = null;
|
|
243
|
+
runtime.surfaceAntialiasWidth = 0;
|
|
244
|
+
runtime.surfaceAntialiasHeight = 0;
|
|
245
|
+
runtime.surfaceAntialiasResolveBindGroupLayout = null;
|
|
246
|
+
runtime.surfaceAntialiasResolvePipeline = null;
|
|
247
|
+
runtime.surfaceAntialiasResolveBindGroup = null;
|
|
248
|
+
runtime.surfacePresentationView = null;
|
|
249
|
+
runtime.registries = { ...pipeline.registries };
|
|
250
|
+
runtime.teardowns = [];
|
|
251
|
+
runtime.borrowedSurfaceExtent = null;
|
|
252
|
+
const ownership = _acquisitionByDeviceRuntime.get(deviceRuntime);
|
|
253
|
+
if (ownership !== undefined) {
|
|
254
|
+
ownership.references++;
|
|
255
|
+
_acquisitionByStateRuntime.set(runtime, ownership);
|
|
256
|
+
}
|
|
257
|
+
deviceRuntime.references++;
|
|
258
|
+
runtime.context = deviceRuntime;
|
|
259
|
+
return runtime;
|
|
289
260
|
}
|
|
290
261
|
export function getWgpuColorAdjustmentMaterialFeature(state) {
|
|
291
262
|
const entry = getWgpuRenderStateRuntime(state).registries.colorAdjustmentFeature?.entry;
|
|
@@ -295,6 +266,9 @@ export function getWgpuColorAdjustmentMaterialFeatureGuard(state) {
|
|
|
295
266
|
const entry = getWgpuRenderStateRuntime(state).registries.colorAdjustmentFeatureGuard?.entry;
|
|
296
267
|
return entry?.state === RegistryEntryState.Bound ? entry.value : null;
|
|
297
268
|
}
|
|
269
|
+
export function getWgpuDeviceRuntime(deviceState) {
|
|
270
|
+
return deviceState[EntityRuntimeKey];
|
|
271
|
+
}
|
|
298
272
|
// Resolves the package-private GPU runtime attached to a WgpuRenderState. Mutable by design: the
|
|
299
273
|
// render path writes its fields every frame.
|
|
300
274
|
export function getWgpuRenderStateRuntime(state) {
|
|
@@ -323,7 +297,8 @@ export function getWgpuSampler(state, minFilter, magFilter, wrapU, wrapV, mipmap
|
|
|
323
297
|
(SAMPLER_WRAP_BITS[wrapV] << 4) |
|
|
324
298
|
((effectiveMipmapFilter === undefined ? 0 : SAMPLER_MIPMAP_BITS[effectiveMipmapFilter]) << 6) |
|
|
325
299
|
(anisotropy << 8);
|
|
326
|
-
|
|
300
|
+
const cache = runtime.context.samplerCache;
|
|
301
|
+
let sampler = cache.get(key);
|
|
327
302
|
if (sampler === undefined) {
|
|
328
303
|
const descriptor = {
|
|
329
304
|
minFilter: effectiveMinFilter,
|
|
@@ -336,108 +311,82 @@ export function getWgpuSampler(state, minFilter, magFilter, wrapU, wrapV, mipmap
|
|
|
336
311
|
if (anisotropy > 1)
|
|
337
312
|
descriptor.maxAnisotropy = anisotropy;
|
|
338
313
|
sampler = state.device.createSampler(descriptor);
|
|
339
|
-
|
|
314
|
+
cache.set(key, sampler);
|
|
340
315
|
}
|
|
341
316
|
return sampler;
|
|
342
317
|
}
|
|
343
318
|
export function isWgpuSupported() {
|
|
344
|
-
return
|
|
319
|
+
return getWgpuHostBackend().isSupported();
|
|
320
|
+
}
|
|
321
|
+
export function registerWgpuDeviceTeardown(state, teardown) {
|
|
322
|
+
getWgpuRenderStateRuntime(state).context.teardowns.push(teardown);
|
|
323
|
+
}
|
|
324
|
+
export function registerWgpuRenderStateTeardown(state, teardown) {
|
|
325
|
+
getWgpuRenderStateRuntime(state).teardowns.push(teardown);
|
|
345
326
|
}
|
|
346
327
|
// Small-integer codes for the sampler-cache numeric key (see getWgpuSampler). Module-level so the key
|
|
347
328
|
// packing reads a field instead of allocating — no per-call table construction.
|
|
348
329
|
const SAMPLER_FILTER_BITS = { nearest: 0, linear: 1 };
|
|
349
330
|
const SAMPLER_WRAP_BITS = { 'clamp-to-edge': 0, 'mirror-repeat': 1, repeat: 2 };
|
|
350
331
|
const SAMPLER_MIPMAP_BITS = { nearest: 1, linear: 2 };
|
|
351
|
-
function
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
332
|
+
function initializeWgpuDeviceRuntime(runtime) {
|
|
333
|
+
if (runtime.uniformBindGroupLayout !== null)
|
|
334
|
+
return;
|
|
335
|
+
const device = runtime.device;
|
|
336
|
+
const { uniformBindGroupLayout, textureBindGroupLayout } = createWgpuBindGroupLayouts(device);
|
|
337
|
+
runtime.uniformBindGroupLayout = uniformBindGroupLayout;
|
|
338
|
+
runtime.textureBindGroupLayout = textureBindGroupLayout;
|
|
339
|
+
runtime.pipelineCache = new Map();
|
|
340
|
+
runtime.linearSampler = device.createSampler({
|
|
341
|
+
minFilter: 'linear',
|
|
342
|
+
magFilter: 'linear',
|
|
343
|
+
addressModeU: 'clamp-to-edge',
|
|
344
|
+
addressModeV: 'clamp-to-edge',
|
|
356
345
|
});
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
runtime.uniformBuffer = uniformBuffer;
|
|
363
|
-
runtime.uniformData = uniformData;
|
|
364
|
-
runtime.uniformDataU32 = new Uint32Array(uniformData.buffer);
|
|
365
|
-
runtime.uniformOffset = 0;
|
|
366
|
-
runtime.uniformStride = screenRuntime.uniformStride;
|
|
367
|
-
runtime.uniformBindGroup = state.device.createBindGroup({
|
|
368
|
-
layout: runtime.uniformBindGroupLayout,
|
|
369
|
-
entries: [{ binding: 0, resource: { buffer: uniformBuffer, size: UNIFORM_BYTE_SIZE } }],
|
|
346
|
+
runtime.nearestSampler = device.createSampler({
|
|
347
|
+
minFilter: 'nearest',
|
|
348
|
+
magFilter: 'nearest',
|
|
349
|
+
addressModeU: 'clamp-to-edge',
|
|
350
|
+
addressModeV: 'clamp-to-edge',
|
|
370
351
|
});
|
|
371
|
-
runtime.
|
|
372
|
-
runtime.
|
|
373
|
-
runtime.
|
|
374
|
-
runtime.
|
|
375
|
-
runtime.
|
|
376
|
-
runtime.
|
|
377
|
-
runtime.
|
|
378
|
-
runtime.quadBatchWriterMaterialRenderer = null;
|
|
379
|
-
runtime.quadBatchWriterMaterialFloats = 0;
|
|
380
|
-
runtime.quadBatchWriterCount = 0;
|
|
381
|
-
runtime.quadBatchWriterInstanceData = new Float32Array(13 * 256);
|
|
382
|
-
runtime.quadBatchWriterMaterialData = new Float32Array(8 * 256);
|
|
383
|
-
runtime.quadBatchWriterTexture = null;
|
|
384
|
-
runtime.quadBatchWriterSampler = null;
|
|
385
|
-
runtime.quadBatchWriterSmoothing = null;
|
|
386
|
-
runtime.quadBatchWriterBufferPool = [];
|
|
387
|
-
runtime.quadBatchWriterBufferCursor = 0;
|
|
388
|
-
runtime.commandEncoder = null;
|
|
389
|
-
runtime.renderPass = null;
|
|
390
|
-
runtime.canvasTextureView = null;
|
|
391
|
-
runtime.canvasViewCleared = false;
|
|
392
|
-
runtime.frameCaptureEnabled = false;
|
|
393
|
-
runtime.frameCaptureTexture = null;
|
|
394
|
-
runtime.frameCaptureBuffer = null;
|
|
395
|
-
runtime.frameCaptureBytesPerRow = 0;
|
|
396
|
-
runtime.frameCaptureWidth = 0;
|
|
397
|
-
runtime.frameCaptureHeight = 0;
|
|
398
|
-
runtime.depthStencilTexture = null;
|
|
399
|
-
runtime.depthStencilView = null;
|
|
400
|
-
runtime.depthStencilWidth = 0;
|
|
401
|
-
runtime.depthStencilHeight = 0;
|
|
402
|
-
runtime.currentMaskDepth = 0;
|
|
403
|
-
runtime.maskWriteMode = false;
|
|
404
|
-
runtime.clipContourPipelines = undefined;
|
|
405
|
-
runtime.clipContourStack = [];
|
|
406
|
-
runtime.shapeMeshPipelines = undefined;
|
|
407
|
-
runtime.clipForms = [];
|
|
408
|
-
runtime.scissorStack = [];
|
|
409
|
-
runtime.currentScissorRect = null;
|
|
410
|
-
runtime.renderTargetViewport = null;
|
|
411
|
-
runtime.renderTargetStack = [];
|
|
352
|
+
runtime.samplerCache = new Map();
|
|
353
|
+
runtime.mipmapPipelineCache = new Map();
|
|
354
|
+
runtime.textureCache = new WeakMap();
|
|
355
|
+
runtime.textureSourcePremultipliedTextureCache = new WeakMap();
|
|
356
|
+
runtime.textureSourcePremultipliedSrgbTextureCache = new WeakMap();
|
|
357
|
+
runtime.textureSourceStraightTextureCache = new WeakMap();
|
|
358
|
+
runtime.textureSourceStraightSrgbTextureCache = new WeakMap();
|
|
412
359
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
360
|
+
// The caller's own teardown for an acquisition they own. Unconditional by design: the caller is asking.
|
|
361
|
+
export function releaseWgpuAcquisition(acquisition) {
|
|
362
|
+
getWgpuHostBackend().release(acquisition);
|
|
363
|
+
}
|
|
364
|
+
function createMinimalDeviceRuntime(device) {
|
|
365
|
+
const runtime = {
|
|
366
|
+
binding: null,
|
|
367
|
+
device,
|
|
368
|
+
lost: null,
|
|
369
|
+
signals: null,
|
|
370
|
+
references: 0,
|
|
371
|
+
teardowns: [],
|
|
372
|
+
uniformBindGroupLayout: null,
|
|
373
|
+
textureBindGroupLayout: null,
|
|
374
|
+
pipelineCache: new Map(),
|
|
375
|
+
linearSampler: null,
|
|
376
|
+
nearestSampler: null,
|
|
377
|
+
samplerCache: new Map(),
|
|
378
|
+
mipmapPipelineCache: new Map(),
|
|
379
|
+
textureCache: new WeakMap(),
|
|
380
|
+
textureSourcePremultipliedTextureCache: new WeakMap(),
|
|
381
|
+
textureSourcePremultipliedSrgbTextureCache: new WeakMap(),
|
|
382
|
+
textureSourceStraightTextureCache: new WeakMap(),
|
|
383
|
+
textureSourceStraightSrgbTextureCache: new WeakMap(),
|
|
384
|
+
};
|
|
385
|
+
observeWgpuDeviceLoss(runtime);
|
|
386
|
+
return runtime;
|
|
418
387
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const WGPU_DEVICE_RUNTIME_KEYS = [
|
|
422
|
-
'uniformBindGroupLayout',
|
|
423
|
-
'textureBindGroupLayout',
|
|
424
|
-
'pipelineCache',
|
|
425
|
-
'linearSampler',
|
|
426
|
-
'nearestSampler',
|
|
427
|
-
'samplerCache',
|
|
428
|
-
'mipmapPipelineCache',
|
|
429
|
-
'textureCache',
|
|
430
|
-
'textureSourcePremultipliedTextureCache',
|
|
431
|
-
'textureSourcePremultipliedSrgbTextureCache',
|
|
432
|
-
'textureSourceStraightTextureCache',
|
|
433
|
-
'textureSourceStraightSrgbTextureCache',
|
|
434
|
-
'videoTextureCache',
|
|
435
|
-
'videoSrgbTextureCache',
|
|
436
|
-
'wgpuExternalTextureCache',
|
|
437
|
-
'wgpuRenderTextureCache',
|
|
438
|
-
'sceneMeshUploadCache',
|
|
439
|
-
];
|
|
440
|
-
const _deviceRuntimeByStateRuntime = new WeakMap();
|
|
388
|
+
const _acquisitionByStateRuntime = new WeakMap();
|
|
389
|
+
const _acquisitionByDeviceRuntime = new WeakMap();
|
|
441
390
|
const _destroyedStates = new WeakSet();
|
|
442
391
|
// Resolve the blend hook at the draw seam rather than hiding derived-state delegation behind an
|
|
443
392
|
// entity accessor. A locally installed hook wins; otherwise walk toward the screen pipeline so
|