@forgeax/engine-rhi-debug 0.0.0-dev.8d955ade1c79
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/LICENSE +202 -0
- package/README.md +178 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/bootstrap-integration.test.d.ts +2 -0
- package/dist/__tests__/bootstrap-integration.test.d.ts.map +1 -0
- package/dist/__tests__/consumer-inventory.unit.test.d.ts +2 -0
- package/dist/__tests__/consumer-inventory.unit.test.d.ts.map +1 -0
- package/dist/__tests__/copy-src-usage-validity.dawn.test.d.ts +15 -0
- package/dist/__tests__/copy-src-usage-validity.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/coverage-invariant.test-d.d.ts +2 -0
- package/dist/__tests__/coverage-invariant.test-d.d.ts.map +1 -0
- package/dist/__tests__/coverage-invariant.unit.test.d.ts +2 -0
- package/dist/__tests__/coverage-invariant.unit.test.d.ts.map +1 -0
- package/dist/__tests__/e2e.browser.test.d.ts +2 -0
- package/dist/__tests__/e2e.browser.test.d.ts.map +1 -0
- package/dist/__tests__/error-code-narrowing.test-d.d.ts +2 -0
- package/dist/__tests__/error-code-narrowing.test-d.d.ts.map +1 -0
- package/dist/__tests__/errors-v7.test-d.d.ts +2 -0
- package/dist/__tests__/errors-v7.test-d.d.ts.map +1 -0
- package/dist/__tests__/errors-v7.unit.test.d.ts +2 -0
- package/dist/__tests__/errors-v7.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors.unit.test.d.ts +2 -0
- package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/event-semantics-coverage.unit.test.d.ts +2 -0
- package/dist/__tests__/event-semantics-coverage.unit.test.d.ts.map +1 -0
- package/dist/__tests__/format-info.unit.test.d.ts +2 -0
- package/dist/__tests__/format-info.unit.test.d.ts.map +1 -0
- package/dist/__tests__/frame-model-parity.test-d.d.ts +2 -0
- package/dist/__tests__/frame-model-parity.test-d.d.ts.map +1 -0
- package/dist/__tests__/frame-model-parity.unit.test.d.ts +2 -0
- package/dist/__tests__/frame-model-parity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guard-gates.test.d.ts +2 -0
- package/dist/__tests__/guard-gates.test.d.ts.map +1 -0
- package/dist/__tests__/public-surface.integration.test.d.ts +2 -0
- package/dist/__tests__/public-surface.integration.test.d.ts.map +1 -0
- package/dist/__tests__/readback-fault-cleanup.unit.test.d.ts +2 -0
- package/dist/__tests__/readback-fault-cleanup.unit.test.d.ts.map +1 -0
- package/dist/__tests__/readback-format-matrix.dawn.test.d.ts +2 -0
- package/dist/__tests__/readback-format-matrix.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/readback-format-matrix.unit.test.d.ts +2 -0
- package/dist/__tests__/readback-format-matrix.unit.test.d.ts.map +1 -0
- package/dist/__tests__/readback-matrix-fixture.d.ts +32 -0
- package/dist/__tests__/readback-matrix-fixture.d.ts.map +1 -0
- package/dist/__tests__/recorder-bootstrap.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-bootstrap.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-budget.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-budget.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-fail-fast.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-fail-fast.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-push-event.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-push-event.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-session.integration.test.d.ts +2 -0
- package/dist/__tests__/recorder-session.integration.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-session.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-session.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-steady-frame.dawn.test.d.ts +2 -0
- package/dist/__tests__/recorder-steady-frame.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/replay-fail-fast.unit.test.d.ts +2 -0
- package/dist/__tests__/replay-fail-fast.unit.test.d.ts.map +1 -0
- package/dist/__tests__/replay-session.dawn.test.d.ts +2 -0
- package/dist/__tests__/replay-session.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/replay-session.test-d.d.ts +2 -0
- package/dist/__tests__/replay-session.test-d.d.ts.map +1 -0
- package/dist/__tests__/replay-session.unit.test.d.ts +2 -0
- package/dist/__tests__/replay-session.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-texture-descriptor.unit.test.d.ts +2 -0
- package/dist/__tests__/resolve-texture-descriptor.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resource-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/resource-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-debug-fresh-replay.dawn.test.d.ts +2 -0
- package/dist/__tests__/rhi-debug-fresh-replay.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/tape-index.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-index.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tape-v7-invalid.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-v7-invalid.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tape-v7-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-v7-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tape-v7-version.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-v7-version.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tree-shake.unit.test.d.ts +2 -0
- package/dist/__tests__/tree-shake.unit.test.d.ts.map +1 -0
- package/dist/__tests__/types.test.d.ts +2 -0
- package/dist/__tests__/types.test.d.ts.map +1 -0
- package/dist/browser.d.ts +45 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.mjs +83 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/frame-model.d.ts +158 -0
- package/dist/frame-model.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +10415 -0
- package/dist/index.mjs.map +1 -0
- package/dist/protocol/codec.d.ts +6 -0
- package/dist/protocol/codec.d.ts.map +1 -0
- package/dist/protocol/event-semantics.d.ts +15 -0
- package/dist/protocol/event-semantics.d.ts.map +1 -0
- package/dist/protocol/tape-index.d.ts +30 -0
- package/dist/protocol/tape-index.d.ts.map +1 -0
- package/dist/protocol/types.d.ts +44 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/validation.d.ts +7 -0
- package/dist/protocol/validation.d.ts.map +1 -0
- package/dist/readback.d.ts +156 -0
- package/dist/readback.d.ts.map +1 -0
- package/dist/recorder/assemble.d.ts +11 -0
- package/dist/recorder/assemble.d.ts.map +1 -0
- package/dist/recorder/closure.d.ts +39 -0
- package/dist/recorder/closure.d.ts.map +1 -0
- package/dist/recorder/core.d.ts +296 -0
- package/dist/recorder/core.d.ts.map +1 -0
- package/dist/recorder/device.d.ts +4 -0
- package/dist/recorder/device.d.ts.map +1 -0
- package/dist/recorder/encoder.d.ts +5 -0
- package/dist/recorder/encoder.d.ts.map +1 -0
- package/dist/recorder/lifecycle.d.ts +20 -0
- package/dist/recorder/lifecycle.d.ts.map +1 -0
- package/dist/recorder/pass.d.ts +6 -0
- package/dist/recorder/pass.d.ts.map +1 -0
- package/dist/recorder/proxy.d.ts +19 -0
- package/dist/recorder/proxy.d.ts.map +1 -0
- package/dist/recorder/queue.d.ts +4 -0
- package/dist/recorder/queue.d.ts.map +1 -0
- package/dist/recorder/resource-registry.d.ts +15 -0
- package/dist/recorder/resource-registry.d.ts.map +1 -0
- package/dist/recorder/session.d.ts +25 -0
- package/dist/recorder/session.d.ts.map +1 -0
- package/dist/recorder/shader.d.ts +3 -0
- package/dist/recorder/shader.d.ts.map +1 -0
- package/dist/recorder/snapshot.d.ts +10 -0
- package/dist/recorder/snapshot.d.ts.map +1 -0
- package/dist/recorder/wrap.d.ts +4 -0
- package/dist/recorder/wrap.d.ts.map +1 -0
- package/dist/recorder.d.ts +7 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/replay/device-request.d.ts +8 -0
- package/dist/replay/device-request.d.ts.map +1 -0
- package/dist/replay/execute-support.d.ts +33 -0
- package/dist/replay/execute-support.d.ts.map +1 -0
- package/dist/replay/execute.d.ts +18 -0
- package/dist/replay/execute.d.ts.map +1 -0
- package/dist/replay/readback.d.ts +32 -0
- package/dist/replay/readback.d.ts.map +1 -0
- package/dist/replay/resources.d.ts +57 -0
- package/dist/replay/resources.d.ts.map +1 -0
- package/dist/replay/session.d.ts +49 -0
- package/dist/replay/session.d.ts.map +1 -0
- package/dist/replay/texture-format.d.ts +21 -0
- package/dist/replay/texture-format.d.ts.map +1 -0
- package/dist/texel-decode.d.ts +35 -0
- package/dist/texel-decode.d.ts.map +1 -0
- package/dist/texel-layout.d.ts +76 -0
- package/dist/texel-layout.d.ts.map +1 -0
- package/dist/types.d.ts +722 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +72 -0
- package/src/__tests__/bootstrap-integration.test.ts +268 -0
- package/src/__tests__/consumer-inventory.unit.test.ts +154 -0
- package/src/__tests__/copy-src-usage-validity.dawn.test.ts +117 -0
- package/src/__tests__/coverage-invariant.test-d.ts +115 -0
- package/src/__tests__/coverage-invariant.unit.test.ts +45 -0
- package/src/__tests__/e2e.browser.test.ts +239 -0
- package/src/__tests__/error-code-narrowing.test-d.ts +32 -0
- package/src/__tests__/errors-v7.test-d.ts +19 -0
- package/src/__tests__/errors-v7.unit.test.ts +15 -0
- package/src/__tests__/errors.unit.test.ts +83 -0
- package/src/__tests__/event-semantics-coverage.unit.test.ts +22 -0
- package/src/__tests__/format-info.unit.test.ts +85 -0
- package/src/__tests__/frame-model-parity.test-d.ts +20 -0
- package/src/__tests__/frame-model-parity.unit.test.ts +285 -0
- package/src/__tests__/guard-gates.test.ts +317 -0
- package/src/__tests__/public-surface.integration.test.ts +38 -0
- package/src/__tests__/readback-fault-cleanup.unit.test.ts +66 -0
- package/src/__tests__/readback-format-matrix.dawn.test.ts +378 -0
- package/src/__tests__/readback-format-matrix.unit.test.ts +120 -0
- package/src/__tests__/readback-matrix-fixture.ts +20 -0
- package/src/__tests__/recorder-bootstrap.unit.test.ts +302 -0
- package/src/__tests__/recorder-budget.unit.test.ts +42 -0
- package/src/__tests__/recorder-fail-fast.unit.test.ts +434 -0
- package/src/__tests__/recorder-lifecycle.unit.test.ts +42 -0
- package/src/__tests__/recorder-push-event.unit.test.ts +573 -0
- package/src/__tests__/recorder-session.integration.test.ts +32 -0
- package/src/__tests__/recorder-session.unit.test.ts +63 -0
- package/src/__tests__/recorder-steady-frame.dawn.test.ts +52 -0
- package/src/__tests__/replay-fail-fast.unit.test.ts +155 -0
- package/src/__tests__/replay-session.dawn.test.ts +220 -0
- package/src/__tests__/replay-session.test-d.ts +27 -0
- package/src/__tests__/replay-session.unit.test.ts +171 -0
- package/src/__tests__/resolve-texture-descriptor.unit.test.ts +144 -0
- package/src/__tests__/resource-lifecycle.unit.test.ts +114 -0
- package/src/__tests__/rhi-debug-fresh-replay.dawn.test.ts +178 -0
- package/src/__tests__/tape-index.unit.test.ts +56 -0
- package/src/__tests__/tape-v7-invalid.unit.test.ts +45 -0
- package/src/__tests__/tape-v7-roundtrip.unit.test.ts +66 -0
- package/src/__tests__/tape-v7-version.unit.test.ts +13 -0
- package/src/__tests__/tree-shake.unit.test.ts +151 -0
- package/src/__tests__/types.test.ts +257 -0
- package/src/browser.ts +146 -0
- package/src/errors.ts +173 -0
- package/src/frame-model.ts +753 -0
- package/src/index.ts +92 -0
- package/src/pako.d.ts +8 -0
- package/src/protocol/codec.ts +220 -0
- package/src/protocol/event-semantics.ts +180 -0
- package/src/protocol/tape-index.ts +120 -0
- package/src/protocol/types.ts +60 -0
- package/src/protocol/validation.ts +173 -0
- package/src/readback.ts +804 -0
- package/src/recorder/assemble.ts +192 -0
- package/src/recorder/closure.ts +408 -0
- package/src/recorder/core.ts +587 -0
- package/src/recorder/device.ts +474 -0
- package/src/recorder/encoder.ts +251 -0
- package/src/recorder/lifecycle.ts +847 -0
- package/src/recorder/pass.ts +241 -0
- package/src/recorder/proxy.ts +59 -0
- package/src/recorder/queue.ts +109 -0
- package/src/recorder/resource-registry.ts +53 -0
- package/src/recorder/session.ts +152 -0
- package/src/recorder/shader.ts +47 -0
- package/src/recorder/snapshot.ts +38 -0
- package/src/recorder/wrap.ts +157 -0
- package/src/recorder.ts +15 -0
- package/src/replay/device-request.ts +38 -0
- package/src/replay/execute-support.ts +243 -0
- package/src/replay/execute.ts +870 -0
- package/src/replay/readback.ts +671 -0
- package/src/replay/resources.ts +154 -0
- package/src/replay/session.ts +509 -0
- package/src/replay/texture-format.ts +99 -0
- package/src/texel-decode.ts +289 -0
- package/src/texel-layout.ts +324 -0
- package/src/types.ts +888 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
// @forgeax/engine-rhi-debug/src/recorder/device -- device proxy owner.
|
|
2
|
+
|
|
3
|
+
/// <reference types="@webgpu/types" />
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
BindGroupDescriptor,
|
|
7
|
+
BindGroupLayoutDescriptor,
|
|
8
|
+
Buffer,
|
|
9
|
+
BufferDescriptor,
|
|
10
|
+
CommandEncoderDescriptor,
|
|
11
|
+
ComputePipelineDescriptor,
|
|
12
|
+
PipelineLayoutDescriptor,
|
|
13
|
+
QuerySet,
|
|
14
|
+
QuerySetDescriptor,
|
|
15
|
+
RenderPipelineDescriptor,
|
|
16
|
+
RhiCommandEncoder,
|
|
17
|
+
RhiDevice,
|
|
18
|
+
SamplerDescriptor,
|
|
19
|
+
Texture,
|
|
20
|
+
TextureDescriptor,
|
|
21
|
+
TextureViewDescriptor,
|
|
22
|
+
} from '@forgeax/engine-rhi';
|
|
23
|
+
import { RhiError } from '@forgeax/engine-rhi';
|
|
24
|
+
import { err as makeErr, ok as makeOk, type Result } from '@forgeax/engine-types';
|
|
25
|
+
import type { HandleId, RhiBindResourceKind, RhiCallEvent } from '../types';
|
|
26
|
+
import type { RecorderInternal } from './core';
|
|
27
|
+
import {
|
|
28
|
+
allocHandleId,
|
|
29
|
+
ensureTextureCreateEvent,
|
|
30
|
+
getHandleId,
|
|
31
|
+
hasBootstrapDependency,
|
|
32
|
+
isDepthOrStencilFormat,
|
|
33
|
+
promoteBufferUsage,
|
|
34
|
+
pushEvent,
|
|
35
|
+
registerHandle,
|
|
36
|
+
retainsCaptureBootstrap,
|
|
37
|
+
shouldRecord,
|
|
38
|
+
TEXTURE_USAGE_BINDING,
|
|
39
|
+
TEXTURE_USAGE_COPY_DST,
|
|
40
|
+
TEXTURE_USAGE_COPY_SRC,
|
|
41
|
+
} from './core';
|
|
42
|
+
import { createCommandEncoderProxy } from './encoder';
|
|
43
|
+
import { createQueueProxy } from './queue';
|
|
44
|
+
|
|
45
|
+
export function createDeviceProxy(s: RecorderInternal, realDevice: RhiDevice): RhiDevice {
|
|
46
|
+
const proxiedQueue = createQueueProxy(s, realDevice.queue);
|
|
47
|
+
|
|
48
|
+
// Expose the real RhiDevice for standalone createShaderModule calls.
|
|
49
|
+
// engine-rhi-webgpu's createShaderModule uses RAW_DEVICE_MAP (WeakMap)
|
|
50
|
+
// to reverse-lookup the GPUDevice. The proxy is a different JS object
|
|
51
|
+
// from the RhiDevice that makeRhiDevice registered, so WeakMap.get(proxy)
|
|
52
|
+
// returns undefined and createShaderModule returns shader-compile-failed.
|
|
53
|
+
// The _realDevice property lets callers pass the real RhiDevice directly.
|
|
54
|
+
type RhiDeviceWithReal = RhiDevice & { _realDevice: RhiDevice };
|
|
55
|
+
|
|
56
|
+
const d: RhiDeviceWithReal = {
|
|
57
|
+
_realDevice: realDevice,
|
|
58
|
+
|
|
59
|
+
get caps() {
|
|
60
|
+
return realDevice.caps;
|
|
61
|
+
},
|
|
62
|
+
get features() {
|
|
63
|
+
return realDevice.features;
|
|
64
|
+
},
|
|
65
|
+
get limits() {
|
|
66
|
+
return realDevice.limits;
|
|
67
|
+
},
|
|
68
|
+
get queue() {
|
|
69
|
+
return proxiedQueue;
|
|
70
|
+
},
|
|
71
|
+
get lost() {
|
|
72
|
+
return realDevice.lost;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
createBuffer(desc: BufferDescriptor) {
|
|
76
|
+
// COPY_SRC promotion (D-5): every recorded resource must be readable
|
|
77
|
+
// back via copyBufferToBuffer so snapshotResource can capture its GPU
|
|
78
|
+
// bytes at frame-header time. Promote the live resource's usage too,
|
|
79
|
+
// not just the recorded event — a buffer created without COPY_SRC is
|
|
80
|
+
// an invalid copy source on the real device. Mappable buffers are
|
|
81
|
+
// skipped (MAP_READ|COPY_SRC is invalid) — they are staging buffers,
|
|
82
|
+
// never snapshot targets.
|
|
83
|
+
const promotedUsage = promoteBufferUsage(desc.usage ?? 0);
|
|
84
|
+
const res = realDevice.createBuffer({ ...desc, usage: promotedUsage });
|
|
85
|
+
if (!res.ok) return res;
|
|
86
|
+
const event: RhiCallEvent = {
|
|
87
|
+
kind: 'createBuffer',
|
|
88
|
+
handleId: '' as HandleId,
|
|
89
|
+
desc: {
|
|
90
|
+
size: desc.size ?? 0,
|
|
91
|
+
usage: promotedUsage,
|
|
92
|
+
mappedAtCreation: desc.mappedAtCreation,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
const bufResource = res.value as object;
|
|
96
|
+
const bufHandleId = registerHandle(s, bufResource, 'buffer', event);
|
|
97
|
+
s.descriptorTable.set(bufHandleId, {
|
|
98
|
+
kind: 'buffer',
|
|
99
|
+
size: desc.size ?? 0,
|
|
100
|
+
usage: promotedUsage,
|
|
101
|
+
resource: bufResource,
|
|
102
|
+
});
|
|
103
|
+
pushEvent(s, event);
|
|
104
|
+
return res;
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
createTexture(desc: TextureDescriptor) {
|
|
108
|
+
// Snapshot promotion (D-5): a texture needs COPY_SRC for GPU readback
|
|
109
|
+
// and COPY_DST for queue.writeTexture bootstrap seeding. Promote the
|
|
110
|
+
// live resource's usage as well as the recorded event's so replay can
|
|
111
|
+
// restore the captured color bytes on a fresh device.
|
|
112
|
+
const promotedUsage =
|
|
113
|
+
(desc.usage ?? 0) |
|
|
114
|
+
TEXTURE_USAGE_COPY_SRC |
|
|
115
|
+
TEXTURE_USAGE_COPY_DST |
|
|
116
|
+
(isDepthOrStencilFormat(desc.format) ? TEXTURE_USAGE_BINDING : 0);
|
|
117
|
+
const res = realDevice.createTexture({ ...desc, usage: promotedUsage });
|
|
118
|
+
if (!res.ok) return res;
|
|
119
|
+
const event: RhiCallEvent = {
|
|
120
|
+
kind: 'createTexture',
|
|
121
|
+
handleId: '' as HandleId,
|
|
122
|
+
desc: {
|
|
123
|
+
size: desc.size ?? { width: 1, height: 1 },
|
|
124
|
+
mipLevelCount: desc.mipLevelCount,
|
|
125
|
+
sampleCount: desc.sampleCount,
|
|
126
|
+
dimension: desc.dimension,
|
|
127
|
+
format: desc.format ?? ('bgra8unorm' as GPUTextureFormat),
|
|
128
|
+
usage: promotedUsage,
|
|
129
|
+
viewFormats: desc.viewFormats,
|
|
130
|
+
textureBindingViewDimension: desc.textureBindingViewDimension,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
const texResource = res.value as object;
|
|
134
|
+
const texHandleId = registerHandle(s, texResource, 'texture', event);
|
|
135
|
+
s.descriptorTable.set(texHandleId, {
|
|
136
|
+
kind: 'texture',
|
|
137
|
+
size: desc.size ?? { width: 1, height: 1 },
|
|
138
|
+
format: desc.format ?? ('bgra8unorm' as GPUTextureFormat),
|
|
139
|
+
...(desc.sampleCount !== undefined ? { sampleCount: desc.sampleCount } : {}),
|
|
140
|
+
...(desc.mipLevelCount !== undefined ? { mipLevelCount: desc.mipLevelCount } : {}),
|
|
141
|
+
usage: promotedUsage,
|
|
142
|
+
resource: texResource,
|
|
143
|
+
});
|
|
144
|
+
pushEvent(s, event);
|
|
145
|
+
return res;
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
createTextureView(texture: Texture, desc: TextureViewDescriptor) {
|
|
149
|
+
const res = realDevice.createTextureView(texture, desc);
|
|
150
|
+
if (!res.ok) return res;
|
|
151
|
+
const srcId = getHandleId(s, texture as object, 'texture');
|
|
152
|
+
const textureError = ensureTextureCreateEvent(s, texture as object, srcId, desc.format);
|
|
153
|
+
if (textureError !== undefined) {
|
|
154
|
+
return makeErr(
|
|
155
|
+
new RhiError({
|
|
156
|
+
code: 'webgpu-runtime-error',
|
|
157
|
+
expected: 'texture-view creation to remain representable in the capture graph',
|
|
158
|
+
hint: textureError.hint,
|
|
159
|
+
detail: {
|
|
160
|
+
error: {
|
|
161
|
+
code: textureError.code,
|
|
162
|
+
message: textureError.hint,
|
|
163
|
+
name: 'RhiDebugError',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
const viewId = registerHandle(s, res.value as object, 'textureView');
|
|
170
|
+
s.textureViewHandleMap.set(res.value, viewId);
|
|
171
|
+
|
|
172
|
+
const event: RhiCallEvent = {
|
|
173
|
+
kind: 'createTextureView',
|
|
174
|
+
sourceHandleId: srcId,
|
|
175
|
+
resultHandleId: viewId,
|
|
176
|
+
desc: {
|
|
177
|
+
format: desc.format,
|
|
178
|
+
dimension: desc.dimension,
|
|
179
|
+
usage: desc.usage,
|
|
180
|
+
aspect: desc.aspect,
|
|
181
|
+
baseMipLevel: desc.baseMipLevel,
|
|
182
|
+
mipLevelCount: desc.mipLevelCount,
|
|
183
|
+
baseArrayLayer: desc.baseArrayLayer,
|
|
184
|
+
arrayLayerCount: desc.arrayLayerCount,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
s.bootstrapCreates.set(viewId, event);
|
|
188
|
+
pushEvent(s, event);
|
|
189
|
+
return res;
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
createSampler(desc?: SamplerDescriptor | undefined) {
|
|
193
|
+
const res = realDevice.createSampler(desc);
|
|
194
|
+
if (!res.ok) return res;
|
|
195
|
+
const event: RhiCallEvent = {
|
|
196
|
+
kind: 'createSampler',
|
|
197
|
+
handleId: '' as HandleId,
|
|
198
|
+
desc: desc as Partial<GPUSamplerDescriptor> | undefined,
|
|
199
|
+
};
|
|
200
|
+
registerHandle(s, res.value as object, 'sampler', event);
|
|
201
|
+
pushEvent(s, event);
|
|
202
|
+
return res;
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
createBindGroupLayout(desc: BindGroupLayoutDescriptor) {
|
|
206
|
+
const res = realDevice.createBindGroupLayout(desc);
|
|
207
|
+
if (!res.ok) return res;
|
|
208
|
+
const event: RhiCallEvent = {
|
|
209
|
+
kind: 'createBindGroupLayout',
|
|
210
|
+
handleId: '' as HandleId,
|
|
211
|
+
desc: { label: desc.label, entries: desc.entries ?? [] },
|
|
212
|
+
};
|
|
213
|
+
registerHandle(s, res.value as object, 'bindGroupLayout', event);
|
|
214
|
+
pushEvent(s, event);
|
|
215
|
+
return res;
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
createBindGroup(desc: BindGroupDescriptor) {
|
|
219
|
+
const res = realDevice.createBindGroup(desc);
|
|
220
|
+
if (!res.ok) return res;
|
|
221
|
+
const layoutId = getHandleId(s, desc.layout as object, 'bindGroupLayout');
|
|
222
|
+
const entries = Array.from(desc.entries);
|
|
223
|
+
const resourceKinds: RhiBindResourceKind[] = entries.map((e) => e.resource.kind);
|
|
224
|
+
const resourceHandleIds: HandleId[] = entries.map((e) => {
|
|
225
|
+
const r = e.resource;
|
|
226
|
+
switch (r.kind) {
|
|
227
|
+
case 'sampler':
|
|
228
|
+
return getHandleId(s, r.value as object, 'sampler');
|
|
229
|
+
case 'buffer':
|
|
230
|
+
return getHandleId(s, r.value.buffer as object, 'buffer');
|
|
231
|
+
case 'textureView':
|
|
232
|
+
return getHandleId(s, r.value as object, 'textureView');
|
|
233
|
+
case 'externalTexture':
|
|
234
|
+
return 'externalTexture:unknown';
|
|
235
|
+
}
|
|
236
|
+
return 'externalTexture:unknown' as HandleId;
|
|
237
|
+
});
|
|
238
|
+
const event: RhiCallEvent = {
|
|
239
|
+
kind: 'createBindGroup',
|
|
240
|
+
handleId: '' as HandleId,
|
|
241
|
+
layoutHandleId: layoutId,
|
|
242
|
+
entries: entries.map((e, idx) => {
|
|
243
|
+
const entry: {
|
|
244
|
+
binding: number;
|
|
245
|
+
resourceKind: RhiBindResourceKind;
|
|
246
|
+
bufferOffset?: number;
|
|
247
|
+
bufferSize?: number;
|
|
248
|
+
} = {
|
|
249
|
+
binding: e.binding,
|
|
250
|
+
resourceKind: resourceKinds[idx] as RhiBindResourceKind,
|
|
251
|
+
};
|
|
252
|
+
// Capture the bound sub-range for buffer entries so a
|
|
253
|
+
// dynamic-offset slice (e.g. a 256 B view of a 256 KiB pool)
|
|
254
|
+
// replays as that slice, not the whole buffer.
|
|
255
|
+
if (e.resource.kind === 'buffer') {
|
|
256
|
+
const { offset, size } = e.resource.value;
|
|
257
|
+
if (offset !== undefined) entry.bufferOffset = offset;
|
|
258
|
+
if (size !== undefined) entry.bufferSize = size;
|
|
259
|
+
}
|
|
260
|
+
return entry;
|
|
261
|
+
}),
|
|
262
|
+
resourceHandleIds,
|
|
263
|
+
};
|
|
264
|
+
registerHandle(s, res.value as object, 'bindGroup', event);
|
|
265
|
+
pushEvent(s, event);
|
|
266
|
+
return res;
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
createPipelineLayout(desc: PipelineLayoutDescriptor) {
|
|
270
|
+
const res = realDevice.createPipelineLayout(desc);
|
|
271
|
+
if (!res.ok) return res;
|
|
272
|
+
const bglIds = Array.from(desc.bindGroupLayouts).map((bgl) =>
|
|
273
|
+
getHandleId(s, bgl as object, 'bindGroupLayout'),
|
|
274
|
+
);
|
|
275
|
+
const event: RhiCallEvent = {
|
|
276
|
+
kind: 'createPipelineLayout',
|
|
277
|
+
handleId: '' as HandleId,
|
|
278
|
+
bglHandleIds: bglIds,
|
|
279
|
+
};
|
|
280
|
+
registerHandle(s, res.value as object, 'pipelineLayout', event);
|
|
281
|
+
pushEvent(s, event);
|
|
282
|
+
return res;
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
createRenderPipeline(desc: RenderPipelineDescriptor) {
|
|
286
|
+
const res = realDevice.createRenderPipeline(desc);
|
|
287
|
+
if (!res.ok) return res;
|
|
288
|
+
let layoutId: HandleId;
|
|
289
|
+
if (typeof desc.layout === 'string') {
|
|
290
|
+
layoutId = 'layout:auto';
|
|
291
|
+
} else {
|
|
292
|
+
layoutId = getHandleId(s, desc.layout as object, 'pipelineLayout');
|
|
293
|
+
}
|
|
294
|
+
let vertexShaderModuleHandleId: HandleId | undefined;
|
|
295
|
+
if (desc.vertex !== undefined) {
|
|
296
|
+
vertexShaderModuleHandleId = getHandleId(s, desc.vertex.module as object, 'shaderModule');
|
|
297
|
+
}
|
|
298
|
+
let fragmentShaderModuleHandleId: HandleId | undefined;
|
|
299
|
+
if (desc.fragment !== undefined) {
|
|
300
|
+
fragmentShaderModuleHandleId = getHandleId(
|
|
301
|
+
s,
|
|
302
|
+
desc.fragment.module as object,
|
|
303
|
+
'shaderModule',
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
const { module: _vertexModule, constants: vertexConstants, ...vertexFields } = desc.vertex;
|
|
307
|
+
const recordedVertex =
|
|
308
|
+
vertexConstants === undefined
|
|
309
|
+
? vertexFields
|
|
310
|
+
: { ...vertexFields, constants: vertexConstants };
|
|
311
|
+
const recordedFragment =
|
|
312
|
+
desc.fragment === undefined
|
|
313
|
+
? undefined
|
|
314
|
+
: (() => {
|
|
315
|
+
const {
|
|
316
|
+
module: _fragmentModule,
|
|
317
|
+
constants: fragmentConstants,
|
|
318
|
+
...fragmentFields
|
|
319
|
+
} = desc.fragment;
|
|
320
|
+
return fragmentConstants === undefined
|
|
321
|
+
? fragmentFields
|
|
322
|
+
: { ...fragmentFields, constants: fragmentConstants };
|
|
323
|
+
})();
|
|
324
|
+
const event: RhiCallEvent = {
|
|
325
|
+
kind: 'createRenderPipeline',
|
|
326
|
+
handleId: '' as HandleId,
|
|
327
|
+
desc: {
|
|
328
|
+
vertex: recordedVertex,
|
|
329
|
+
primitive: desc.primitive,
|
|
330
|
+
depthStencil: desc.depthStencil,
|
|
331
|
+
multisample: desc.multisample,
|
|
332
|
+
...(recordedFragment === undefined ? {} : { fragment: recordedFragment }),
|
|
333
|
+
},
|
|
334
|
+
layoutHandleId: layoutId,
|
|
335
|
+
vertexShaderModuleHandleId,
|
|
336
|
+
fragmentShaderModuleHandleId,
|
|
337
|
+
};
|
|
338
|
+
registerHandle(s, res.value as object, 'renderPipeline', event);
|
|
339
|
+
pushEvent(s, event);
|
|
340
|
+
return res;
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
createComputePipeline(desc: ComputePipelineDescriptor) {
|
|
344
|
+
const res = realDevice.createComputePipeline(desc);
|
|
345
|
+
if (!res.ok) return res;
|
|
346
|
+
let layoutId: HandleId;
|
|
347
|
+
if (typeof desc.layout === 'string') {
|
|
348
|
+
layoutId = 'layout:auto';
|
|
349
|
+
} else {
|
|
350
|
+
layoutId = getHandleId(s, desc.layout as object, 'pipelineLayout');
|
|
351
|
+
}
|
|
352
|
+
const computeShaderModuleHandleId = getHandleId(
|
|
353
|
+
s,
|
|
354
|
+
desc.compute.module as object,
|
|
355
|
+
'shaderModule',
|
|
356
|
+
);
|
|
357
|
+
const event: RhiCallEvent = {
|
|
358
|
+
kind: 'createComputePipeline',
|
|
359
|
+
handleId: '' as HandleId,
|
|
360
|
+
desc: { compute: JSON.parse(JSON.stringify(desc.compute)) },
|
|
361
|
+
layoutHandleId: layoutId,
|
|
362
|
+
computeShaderModuleHandleId,
|
|
363
|
+
};
|
|
364
|
+
registerHandle(s, res.value as object, 'computePipeline', event);
|
|
365
|
+
pushEvent(s, event);
|
|
366
|
+
return res;
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
createQuerySet(desc: QuerySetDescriptor) {
|
|
370
|
+
return realDevice.createQuerySet(desc);
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
destroyBuffer(buf: Buffer) {
|
|
374
|
+
const hId = s.handleMap.get(buf as object);
|
|
375
|
+
const res = realDevice.destroyBuffer(buf);
|
|
376
|
+
if (!res.ok) return res;
|
|
377
|
+
if (hId !== undefined) {
|
|
378
|
+
s.descriptorTable.delete(hId);
|
|
379
|
+
// Bound bootstrapCreates growth: an idle-destroyed resource can never
|
|
380
|
+
// be referenced by a future frame's events, so drop its create event.
|
|
381
|
+
// Only when NOT recording — mid-capture, an earlier frame event may
|
|
382
|
+
// still reference this handle, and the tape prefix needs its create
|
|
383
|
+
// event to stay self-contained (tape-invalid otherwise).
|
|
384
|
+
//
|
|
385
|
+
// Known bounded residual: a resource destroyed DURING a capture keeps
|
|
386
|
+
// its bootstrapCreates entry forever — the entry is only ever revisited
|
|
387
|
+
// by another destroy* of the SAME handle, which can't recur after the
|
|
388
|
+
// resource is gone. This leaks one create event per resource that is
|
|
389
|
+
// both created and destroyed inside a recording window (a narrow set;
|
|
390
|
+
// most resources are long-lived). Fixing it needs a defer-delete list
|
|
391
|
+
// swept at the next arm() — deliberately not added: the extra state
|
|
392
|
+
// costs more than the leak it plugs (see PR discussion).
|
|
393
|
+
//
|
|
394
|
+
// Separate, wider residual: bootstrapCreates entries for
|
|
395
|
+
// textureView / bindGroup / pipeline / sampler are NEVER pruned,
|
|
396
|
+
// because RhiDevice exposes destroy only for buffer / texture -- a
|
|
397
|
+
// faithful mirror of WebGPU, whose spec puts .destroy() solely on
|
|
398
|
+
// GPUBuffer / GPUTexture (the objects holding large, eagerly-freeable
|
|
399
|
+
// backing memory); views / bind groups / pipelines / samplers are
|
|
400
|
+
// lightweight reference objects left to GC. So there is no destroy
|
|
401
|
+
// signal to hook for them. An app that rebuilds these per frame grows
|
|
402
|
+
// bootstrapCreates under a long idle run. The GC-aligned fix is a
|
|
403
|
+
// WeakRef + FinalizationRegistry over the create-event objects (same
|
|
404
|
+
// spirit as handleMap's WeakMap, but keyed by handleId so it needs the
|
|
405
|
+
// WeakRef wrapper) -- a finalization-semantics change, out of scope here.
|
|
406
|
+
if (
|
|
407
|
+
!retainsCaptureBootstrap(s) &&
|
|
408
|
+
!s.snapshotSeededHandles.has(hId) &&
|
|
409
|
+
!hasBootstrapDependency(s, hId)
|
|
410
|
+
) {
|
|
411
|
+
s.bootstrapCreates.delete(hId);
|
|
412
|
+
}
|
|
413
|
+
pushEvent(s, { kind: 'destroyBuffer', handleId: hId });
|
|
414
|
+
}
|
|
415
|
+
return res;
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
destroyQuerySet(querySet: QuerySet) {
|
|
419
|
+
return realDevice.destroyQuerySet(querySet);
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
destroyTexture(tex: Texture) {
|
|
423
|
+
const hId = s.handleMap.get(tex as object);
|
|
424
|
+
const res = realDevice.destroyTexture(tex);
|
|
425
|
+
if (!res.ok) return res;
|
|
426
|
+
if (hId !== undefined) {
|
|
427
|
+
s.descriptorTable.delete(hId);
|
|
428
|
+
// Same gate + same bounded residual as destroyBuffer above.
|
|
429
|
+
if (
|
|
430
|
+
!retainsCaptureBootstrap(s) &&
|
|
431
|
+
!s.snapshotSeededHandles.has(hId) &&
|
|
432
|
+
!hasBootstrapDependency(s, hId)
|
|
433
|
+
) {
|
|
434
|
+
s.bootstrapCreates.delete(hId);
|
|
435
|
+
}
|
|
436
|
+
pushEvent(s, { kind: 'destroyTexture', handleId: hId });
|
|
437
|
+
}
|
|
438
|
+
return res;
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
createCommandEncoder(desc?: CommandEncoderDescriptor | undefined) {
|
|
442
|
+
const res = realDevice.createCommandEncoder(desc);
|
|
443
|
+
if (!res.ok) return res;
|
|
444
|
+
// Idle fast-path: return the real (un-proxied) encoder when not
|
|
445
|
+
// recording. One gate here elides the proxy wrapper AND every
|
|
446
|
+
// beginRenderPass/draw/setBindGroup/copy* call that would otherwise
|
|
447
|
+
// route through proxyCmdEncoder + proxyRenderPass only to be dropped
|
|
448
|
+
// by the pushEvent gate. Safe because a frame body runs synchronously
|
|
449
|
+
// in one rAF callback: createCommandEncoder -> passes -> queue.submit
|
|
450
|
+
// all observe the same recorder state (arm() only fires between frames),
|
|
451
|
+
// so an idle-created encoder's whole frame is consistently un-recorded,
|
|
452
|
+
// matching the now-gated queue.submit which skips its handle lookup.
|
|
453
|
+
if (!shouldRecord(s)) {
|
|
454
|
+
return res;
|
|
455
|
+
}
|
|
456
|
+
const cmdId = allocHandleId('commandEncoder');
|
|
457
|
+
pushEvent(s, {
|
|
458
|
+
kind: 'createCommandEncoder',
|
|
459
|
+
cmdHandleId: cmdId,
|
|
460
|
+
desc: desc as Partial<GPUCommandEncoderDescriptor> | undefined,
|
|
461
|
+
});
|
|
462
|
+
const proxyEnc = createCommandEncoderProxy(s, res.value, cmdId);
|
|
463
|
+
return makeOk(proxyEnc as RhiCommandEncoder) as Result<
|
|
464
|
+
RhiCommandEncoder,
|
|
465
|
+
import('@forgeax/engine-rhi').RhiError
|
|
466
|
+
>;
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
return d;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// --------------------------------------------------
|
|
473
|
+
// wrap RhiInstance
|
|
474
|
+
// --------------------------------------------------
|