@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,587 @@
|
|
|
1
|
+
// @forgeax/engine-rhi-debug/src/recorder/core -- recorder state and event ownership.
|
|
2
|
+
|
|
3
|
+
/// <reference types="@webgpu/types" />
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
Result,
|
|
7
|
+
RhiDevice,
|
|
8
|
+
RhiInstance,
|
|
9
|
+
ShaderModule,
|
|
10
|
+
TextureView,
|
|
11
|
+
} from '@forgeax/engine-rhi';
|
|
12
|
+
import { createRhiDebugError, type RhiDebugError } from '../errors';
|
|
13
|
+
import { textureBlockLayout } from '../texel-layout';
|
|
14
|
+
import type { HandleId, RhiCallEvent, RhiCapsRecorded, Tape } from '../types';
|
|
15
|
+
import { _getCreateEventReferencedHandleIds } from './closure';
|
|
16
|
+
|
|
17
|
+
const SNAPSHOT_TIMEOUT_MS = 30_000;
|
|
18
|
+
|
|
19
|
+
// Result factories `makeOk` / `makeErr` re-import the canonical
|
|
20
|
+
// `ok` / `err` from `@forgeax/engine-types` (architecture-principles #1
|
|
21
|
+
// SSOT — same shape, same factory, no inline duplicate). Aliased on
|
|
22
|
+
// import to avoid a free-form rename diff in this file's existing
|
|
23
|
+
// `makeOk(...)` / `makeErr(...)` call sites; semantics identical.
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Constants
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
export const PER_EVENT_OVERHEAD = 192 as const;
|
|
30
|
+
|
|
31
|
+
// COPY_SRC promotion bit values (D-5). GPUBufferUsage and GPUTextureUsage have
|
|
32
|
+
// DIFFERENT bit layouts — COPY_SRC is 0x04 for buffers but 0x01 for textures:
|
|
33
|
+
// GPUBufferUsage: MAP_READ=0x01 MAP_WRITE=0x02 COPY_SRC=0x04 COPY_DST=0x08 ...
|
|
34
|
+
// GPUTextureUsage: COPY_SRC=0x01 COPY_DST=0x02 TEXTURE_BINDING=0x04 ...
|
|
35
|
+
// A buffer carrying MAP_READ / MAP_WRITE cannot also carry COPY_SRC (WebGPU
|
|
36
|
+
// validation: a mappable buffer's only other allowed usage is the matching
|
|
37
|
+
// COPY_DST / COPY_SRC), so promotion is skipped for mappable buffers — those
|
|
38
|
+
// are staging buffers, never frame-header snapshot targets.
|
|
39
|
+
const BUFFER_USAGE_COPY_SRC = 0x04;
|
|
40
|
+
const BUFFER_USAGE_MAP_READ = 0x01;
|
|
41
|
+
const BUFFER_USAGE_MAP_WRITE = 0x02;
|
|
42
|
+
const TEXTURE_USAGE_COPY_SRC = 0x01;
|
|
43
|
+
const TEXTURE_USAGE_COPY_DST = 0x02;
|
|
44
|
+
const TEXTURE_USAGE_BINDING = 0x04;
|
|
45
|
+
/** Bound staging allocation, command submission, and map concurrency per batch. */
|
|
46
|
+
export const SNAPSHOT_RESOURCE_BATCH_SIZE = 32;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* True for each depth / stencil texture format. Their content is render-pass
|
|
50
|
+
* output (shadow maps, z-buffers), never an uploaded byte payload, and
|
|
51
|
+
* queue.writeTexture rejects them (no CopyDst), so the frame-header snapshot
|
|
52
|
+
* loop skips them rather than emitting an un-seedable initialData event.
|
|
53
|
+
*/
|
|
54
|
+
function isDepthOrStencilFormat(format: GPUTextureFormat | undefined): boolean {
|
|
55
|
+
return format !== undefined && (format.startsWith('depth') || format.startsWith('stencil'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* True for a texture the frame-header snapshot can read back AND re-seed
|
|
60
|
+
* faithfully: each color format with a known texel-block footprint, at each
|
|
61
|
+
* array-layer count and each mip count. The readback + seed path
|
|
62
|
+
* (readbackTexturePixels + computeTextureLayout + replayInitialData) walks
|
|
63
|
+
* every (layer, mip) subresource with block-aware bytesPerRow, so ordinary
|
|
64
|
+
* texels and BC/ETC/ASTC compressed assets share one round-trip contract.
|
|
65
|
+
*
|
|
66
|
+
* Still skipped (no faithful path today, Fail Fast rather than corrupt seed):
|
|
67
|
+
* - depth/stencil formats: queue.writeTexture rejects them (no CopyDst seed).
|
|
68
|
+
* - multisample (sampleCount > 1): writeTexture rejects an MSAA target. MSAA
|
|
69
|
+
* attachments are transient (resolved into a single-sample texture that IS
|
|
70
|
+
* snapshottable), so skipping loses no seed.
|
|
71
|
+
*/
|
|
72
|
+
function isSnapshottableColorTexture(
|
|
73
|
+
format: GPUTextureFormat | undefined,
|
|
74
|
+
_size: number | GPUExtent3DStrict | undefined,
|
|
75
|
+
sampleCount?: number,
|
|
76
|
+
): boolean {
|
|
77
|
+
if (isDepthOrStencilFormat(format)) return false;
|
|
78
|
+
// Multisample textures reject queue.writeTexture; skip (resolved target seeds).
|
|
79
|
+
if (sampleCount !== undefined && sampleCount > 1) return false;
|
|
80
|
+
// Round-trippable iff its texel-block footprint is known.
|
|
81
|
+
return textureBlockLayout(format) !== undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Add COPY_SRC to a buffer usage unless it is a mappable (MAP_READ/WRITE) buffer. */
|
|
85
|
+
function promoteBufferUsage(usage: number): number {
|
|
86
|
+
if ((usage & (BUFFER_USAGE_MAP_READ | BUFFER_USAGE_MAP_WRITE)) !== 0) return usage;
|
|
87
|
+
return usage | BUFFER_USAGE_COPY_SRC;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* True for a mappable (MAP_READ / MAP_WRITE) buffer. These are staging buffers
|
|
92
|
+
* (e.g. shadow-probe-staging): promoteBufferUsage deliberately does NOT add
|
|
93
|
+
* COPY_SRC to them (MAP_READ|COPY_SRC is an invalid WebGPU usage combo), so they
|
|
94
|
+
* cannot be a copyBufferToBuffer source. The frame-header snapshot loop must skip
|
|
95
|
+
* them — driving readbackBufferBytes on one throws "usage doesn't include
|
|
96
|
+
* CopySrc". Their bytes are transient readback scratch, never seed payload, so
|
|
97
|
+
* losing them is correct (mirrors promoteBufferUsage's own exclusion).
|
|
98
|
+
*/
|
|
99
|
+
function isMappableBuffer(usage: number): boolean {
|
|
100
|
+
return (usage & (BUFFER_USAGE_MAP_READ | BUFFER_USAGE_MAP_WRITE)) !== 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { TAPE_FORMAT_VERSION } from '../protocol/types';
|
|
104
|
+
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// State machine
|
|
107
|
+
// ============================================================================
|
|
108
|
+
|
|
109
|
+
enum RecorderState {
|
|
110
|
+
Idle = 'idle',
|
|
111
|
+
Armed = 'armed',
|
|
112
|
+
Snapshotting = 'snapshotting',
|
|
113
|
+
Recording = 'recording',
|
|
114
|
+
Finalizing = 'finalizing',
|
|
115
|
+
Error = 'error',
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// Hash utility for blob dedup
|
|
120
|
+
// ============================================================================
|
|
121
|
+
|
|
122
|
+
/** @internal */
|
|
123
|
+
let _nextHandleId = 0;
|
|
124
|
+
|
|
125
|
+
function allocHandleId(kind: string): HandleId {
|
|
126
|
+
return `${kind}:${++_nextHandleId}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function fastHash(data: ArrayBuffer): string {
|
|
130
|
+
const view = new Uint8Array(data);
|
|
131
|
+
let hash = 5381;
|
|
132
|
+
for (let i = 0; i < view.length; i++) {
|
|
133
|
+
hash = ((hash << 5) + hash + (view[i] ?? 0)) | 0;
|
|
134
|
+
}
|
|
135
|
+
return (hash >>> 0).toString(16);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function storeBlob(state: RecorderInternal, data: ArrayBuffer): string {
|
|
139
|
+
const hash = fastHash(data);
|
|
140
|
+
if (!state.blobPool.has(hash)) {
|
|
141
|
+
state.blobPool.set(hash, data.slice(0) as ArrayBuffer);
|
|
142
|
+
}
|
|
143
|
+
return hash;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Narrow a readbackBufferBytes failure to the snapshot stage.
|
|
148
|
+
* readbackBufferBytes already tags `.detail.phase` with 'copy' | 'map'; carry
|
|
149
|
+
* it through so the re-wrapped error preserves the failure point. Falls back to
|
|
150
|
+
* 'copy' when the inner error lacks a snapshot detail.
|
|
151
|
+
*/
|
|
152
|
+
function snapshotStageOf(error: RhiDebugError): 'copy' | 'map' | 'store' {
|
|
153
|
+
const d = error.detail;
|
|
154
|
+
if ('phase' in d && d.phase !== undefined) return d.phase;
|
|
155
|
+
return 'copy';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Internal recorder state
|
|
160
|
+
// ============================================================================
|
|
161
|
+
|
|
162
|
+
type SnapshotProgress = {
|
|
163
|
+
readonly startedAt: number;
|
|
164
|
+
readonly stage: 'queue-drain' | 'resource-readback';
|
|
165
|
+
readonly totalResources: number;
|
|
166
|
+
readonly completedResources: number;
|
|
167
|
+
readonly skippedResources: number;
|
|
168
|
+
readonly currentHandleId: string | null;
|
|
169
|
+
readonly currentKind: 'buffer' | 'texture' | null;
|
|
170
|
+
readonly currentSizeBytes: number | null;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
interface RecorderInternal {
|
|
174
|
+
state: RecorderState;
|
|
175
|
+
requestedFrames: number;
|
|
176
|
+
recordedFrames: number;
|
|
177
|
+
events: RhiCallEvent[];
|
|
178
|
+
blobPool: Map<string, ArrayBuffer>;
|
|
179
|
+
handleMap: WeakMap<object, HandleId>;
|
|
180
|
+
textureViewHandleMap: WeakMap<TextureView, HandleId>;
|
|
181
|
+
/**
|
|
182
|
+
* @internal
|
|
183
|
+
* Bootstrap create-event table. Populated by registerHandle when called
|
|
184
|
+
* with a create event payload — records every create* (buffer, texture,
|
|
185
|
+
* pipeline, bindGroup, shaderModule, …) from the moment wrap() is called,
|
|
186
|
+
* independent of the recorder state machine (Idle / Armed / Recording).
|
|
187
|
+
* Preserved across arm() cycles (SSOT for closure computation in getTape).
|
|
188
|
+
*/
|
|
189
|
+
bootstrapCreates: Map<HandleId, RhiCallEvent>;
|
|
190
|
+
/** Handles whose initialData event was emitted by the current capture. */
|
|
191
|
+
snapshotSeededHandles: Set<HandleId>;
|
|
192
|
+
/** Generation token invalidating async snapshot work after timeout/error. */
|
|
193
|
+
snapshotGeneration: number;
|
|
194
|
+
/** Last observable progress of the current/most recent resource snapshot. */
|
|
195
|
+
snapshotProgress: SnapshotProgress | undefined;
|
|
196
|
+
/**
|
|
197
|
+
* @internal
|
|
198
|
+
* Descriptor registry of currently-live resources. Written by createBuffer /
|
|
199
|
+
* createTexture (after registerHandle) and cleared by destroyBuffer /
|
|
200
|
+
* destroyTexture. Distinct from handleMap (WeakMap, handle object -> handleId
|
|
201
|
+
* identity, one-way): descriptorTable carries the descriptor *content* (kind /
|
|
202
|
+
* size / format / usage) AND the resource object keyed by handleId, so
|
|
203
|
+
* snapshotResource can both determine a resource's shape and reach the object
|
|
204
|
+
* for readback at frame-header time without re-scanning the event stream or
|
|
205
|
+
* reverse-walking the WeakMap (which cannot be iterated). destroy* removes the
|
|
206
|
+
* entry so the live-resource set never grows unbounded (AC-09). One registry,
|
|
207
|
+
* one delete on destroy — shape and object share the same lifecycle (SSOT).
|
|
208
|
+
*/
|
|
209
|
+
descriptorTable: Map<
|
|
210
|
+
HandleId,
|
|
211
|
+
{
|
|
212
|
+
kind: 'buffer' | 'texture';
|
|
213
|
+
size?: number | GPUExtent3DStrict;
|
|
214
|
+
format?: GPUTextureFormat;
|
|
215
|
+
sampleCount?: number;
|
|
216
|
+
mipLevelCount?: number;
|
|
217
|
+
usage: number;
|
|
218
|
+
resource: object;
|
|
219
|
+
}
|
|
220
|
+
>;
|
|
221
|
+
/** @internal */
|
|
222
|
+
_skipRecord: boolean;
|
|
223
|
+
frameIdx: number;
|
|
224
|
+
bootstrap: boolean;
|
|
225
|
+
recordedCaps: RhiCapsRecorded | undefined;
|
|
226
|
+
onFrameEndUnsubscribe?: (() => void) | undefined;
|
|
227
|
+
/** true when the current recording is valid. */
|
|
228
|
+
valid: boolean;
|
|
229
|
+
/**
|
|
230
|
+
* @internal
|
|
231
|
+
* Most recent live RhiDevice produced by `requestAdapter().requestDevice()`
|
|
232
|
+
* via the recorder proxy chain. Captured so the adapter (I-2 fix) can
|
|
233
|
+
* reach the same device for replay without forcing the host to expose
|
|
234
|
+
* a separate channel.
|
|
235
|
+
*/
|
|
236
|
+
capturedDevice: RhiDevice | undefined;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function snapshotTimeoutDetail(
|
|
240
|
+
progress: SnapshotProgress | undefined,
|
|
241
|
+
timeoutMs: number,
|
|
242
|
+
): import('../errors').CaptureTimeoutDetail {
|
|
243
|
+
const current = progress ?? {
|
|
244
|
+
startedAt: Date.now(),
|
|
245
|
+
stage: 'queue-drain' as const,
|
|
246
|
+
totalResources: 0,
|
|
247
|
+
completedResources: 0,
|
|
248
|
+
skippedResources: 0,
|
|
249
|
+
currentHandleId: null,
|
|
250
|
+
currentKind: null,
|
|
251
|
+
currentSizeBytes: null,
|
|
252
|
+
};
|
|
253
|
+
return {
|
|
254
|
+
stage: 'snapshot',
|
|
255
|
+
cause: 'GPU readback did not complete before the bounded snapshot timeout',
|
|
256
|
+
timeoutMs,
|
|
257
|
+
progress: {
|
|
258
|
+
snapshotStage: current.stage,
|
|
259
|
+
totalResources: current.totalResources,
|
|
260
|
+
completedResources: current.completedResources,
|
|
261
|
+
skippedResources: current.skippedResources,
|
|
262
|
+
currentHandleId: current.currentHandleId,
|
|
263
|
+
currentKind: current.currentKind,
|
|
264
|
+
currentSizeBytes: current.currentSizeBytes,
|
|
265
|
+
elapsedMs: Math.max(0, Date.now() - current.startedAt),
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @internal
|
|
272
|
+
* True while the recorder is in a state that appends normal RHI events to the
|
|
273
|
+
* tape: Armed / Recording. Snapshotting is deliberately excluded: the async
|
|
274
|
+
* frame-header seed phase must not fold live viewport frames into the capture.
|
|
275
|
+
* This is the SSOT recording predicate —
|
|
276
|
+
* `pushEvent` gates on it, and the proxy fast-path (writeBuffer / writeTexture /
|
|
277
|
+
* createCommandEncoder) short-circuits when it is false so an idle recorder
|
|
278
|
+
* (FORGEAX_ENGINE_RHI_DEBUG=1 but no capture in flight) pays no per-call
|
|
279
|
+
* event-object allocation, no storeBlob hash+copy, and no proxy-encoder wrapping.
|
|
280
|
+
*
|
|
281
|
+
* Deliberately ignores `_skipRecord`: that flag suppresses recorder-internal
|
|
282
|
+
* RHI calls (such as snapshot readback staging) during either capture phase.
|
|
283
|
+
* `shouldRecord` folds it in for the normal pushEvent gate.
|
|
284
|
+
*/
|
|
285
|
+
function isRecordingActive(s: RecorderInternal): boolean {
|
|
286
|
+
return s.state === RecorderState.Armed || s.state === RecorderState.Recording;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* A frame-header snapshot is not a normal render-recording phase, but it can
|
|
291
|
+
* already have copied a live resource and still need that resource's create
|
|
292
|
+
* event when it appends `initialData`. Keep bootstrap ownership through that
|
|
293
|
+
* async window: per-frame feature resources may be released while readback is
|
|
294
|
+
* awaiting GPU completion.
|
|
295
|
+
*/
|
|
296
|
+
function retainsCaptureBootstrap(s: RecorderInternal): boolean {
|
|
297
|
+
return isRecordingActive(s) || s.state === RecorderState.Snapshotting;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @internal
|
|
302
|
+
* The exact pushEvent gate as a predicate: record iff not suppressed AND in an
|
|
303
|
+
* active recording state. Proxy methods that do pre-pushEvent work (storeBlob,
|
|
304
|
+
* event-object construction) check this first to skip that work when it would
|
|
305
|
+
* be discarded — same-condition-as-pushEvent guarantees no behavioural drift
|
|
306
|
+
* (a call that would record still does all its work).
|
|
307
|
+
*/
|
|
308
|
+
function shouldRecord(s: RecorderInternal): boolean {
|
|
309
|
+
return !s._skipRecord && isRecordingActive(s);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function pushEvent(s: RecorderInternal, event: RhiCallEvent): void {
|
|
313
|
+
if (!shouldRecord(s)) return;
|
|
314
|
+
s.events.push(event);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Append a frame-header seed without reopening the normal RHI event gate.
|
|
319
|
+
* Snapshotting is not a render-recording state, but its async readback still
|
|
320
|
+
* needs to emit initialData events for the resources that seed replay.
|
|
321
|
+
*/
|
|
322
|
+
function pushSnapshotEvent(s: RecorderInternal, event: RhiCallEvent): void {
|
|
323
|
+
if (s._skipRecord || (!isRecordingActive(s) && s.state !== RecorderState.Snapshotting)) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
s.events.push(event);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function registerHandle(
|
|
330
|
+
s: RecorderInternal,
|
|
331
|
+
handle: object,
|
|
332
|
+
kind: string,
|
|
333
|
+
createEvent?: RhiCallEvent,
|
|
334
|
+
): HandleId {
|
|
335
|
+
const hId = allocHandleId(kind);
|
|
336
|
+
s.handleMap.set(handle, hId);
|
|
337
|
+
if (createEvent !== undefined) {
|
|
338
|
+
if ('handleId' in createEvent) Object.assign(createEvent, { handleId: hId });
|
|
339
|
+
s.bootstrapCreates.set(hId, createEvent);
|
|
340
|
+
}
|
|
341
|
+
return hId;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function ensureTextureCreateEvent(
|
|
345
|
+
s: RecorderInternal,
|
|
346
|
+
texture: object,
|
|
347
|
+
textureId: HandleId,
|
|
348
|
+
viewFormat?: GPUTextureFormat,
|
|
349
|
+
): RhiDebugError | undefined {
|
|
350
|
+
const existing = s.bootstrapCreates.get(textureId);
|
|
351
|
+
if (existing !== undefined) {
|
|
352
|
+
if (
|
|
353
|
+
viewFormat !== undefined &&
|
|
354
|
+
existing.kind === 'createTexture' &&
|
|
355
|
+
existing.origin === 'swapchain'
|
|
356
|
+
) {
|
|
357
|
+
addSwapchainViewFormat(existing, viewFormat);
|
|
358
|
+
}
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const raw = texture as Record<string, unknown>;
|
|
363
|
+
const width = raw.width as number | undefined;
|
|
364
|
+
const height = raw.height as number | undefined;
|
|
365
|
+
const depthOrArrayLayers = (raw.depthOrArrayLayers as number | undefined) ?? 1;
|
|
366
|
+
const format = raw.format as string | undefined;
|
|
367
|
+
const rawUsage = raw.usage as number | undefined;
|
|
368
|
+
|
|
369
|
+
if (
|
|
370
|
+
width === undefined ||
|
|
371
|
+
height === undefined ||
|
|
372
|
+
format === undefined ||
|
|
373
|
+
rawUsage === undefined
|
|
374
|
+
) {
|
|
375
|
+
return createRhiDebugError('tape-invalid', {
|
|
376
|
+
stage: 'validate',
|
|
377
|
+
cause: `swapchain texture '${textureId}' has unreadable dimensions (width=${width}, height=${height}, format=${format}, usage=${rawUsage})`,
|
|
378
|
+
handleId: textureId,
|
|
379
|
+
eventIndex: -1,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const event: RhiCallEvent = {
|
|
384
|
+
kind: 'createTexture',
|
|
385
|
+
handleId: textureId,
|
|
386
|
+
origin: 'swapchain',
|
|
387
|
+
desc: {
|
|
388
|
+
size: { width, height, depthOrArrayLayers },
|
|
389
|
+
format: format as GPUTextureFormat,
|
|
390
|
+
usage: (rawUsage | TEXTURE_USAGE_COPY_SRC | TEXTURE_USAGE_COPY_DST) as GPUTextureUsageFlags,
|
|
391
|
+
...(viewFormat === undefined ? {} : { viewFormats: [viewFormat] }),
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
s.bootstrapCreates.set(textureId, event);
|
|
395
|
+
pushEvent(s, event);
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function addSwapchainViewFormat(
|
|
400
|
+
texture: Extract<RhiCallEvent, { kind: 'createTexture' }>,
|
|
401
|
+
viewFormat: GPUTextureFormat,
|
|
402
|
+
): void {
|
|
403
|
+
if (texture.desc.format === viewFormat || texture.origin !== 'swapchain') return;
|
|
404
|
+
const viewFormats = new Set(texture.desc.viewFormats ?? []);
|
|
405
|
+
viewFormats.add(viewFormat);
|
|
406
|
+
(texture as { desc: typeof texture.desc }).desc = {
|
|
407
|
+
...texture.desc,
|
|
408
|
+
viewFormats: [...viewFormats],
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function reconcileSwapchainViewFormats(s: RecorderInternal): void {
|
|
413
|
+
const events = [...s.events, ...s.bootstrapCreates.values()];
|
|
414
|
+
for (const event of events) {
|
|
415
|
+
if (event.kind !== 'createTextureView' || event.desc.format === undefined) continue;
|
|
416
|
+
const source = s.bootstrapCreates.get(event.sourceHandleId);
|
|
417
|
+
if (source?.kind === 'createTexture' && source.origin === 'swapchain') {
|
|
418
|
+
addSwapchainViewFormat(source, event.desc.format);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function hasBootstrapDependency(s: RecorderInternal, handleId: HandleId): boolean {
|
|
424
|
+
for (const event of s.bootstrapCreates.values()) {
|
|
425
|
+
if (_getCreateEventReferencedHandleIds(event).includes(handleId)) return true;
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function getHandleId(s: RecorderInternal, handle: object, kind: string): HandleId {
|
|
431
|
+
const id = s.handleMap.get(handle);
|
|
432
|
+
if (id !== undefined) return id;
|
|
433
|
+
const newId = registerHandle(s, handle, kind);
|
|
434
|
+
if (kind === 'texture') ensureTextureCreateEvent(s, handle, newId);
|
|
435
|
+
return newId;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ============================================================================
|
|
439
|
+
// Transitive closure — bootstrapCreates → self-contained tape prefix
|
|
440
|
+
// ============================================================================
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* @internal
|
|
444
|
+
* Collect all handleIds referenced by frame events in `s.events`.
|
|
445
|
+
*
|
|
446
|
+
* Scans events for ALL fields that reference resources — mirrors
|
|
447
|
+
* the reference categories checked by findDanglingHandleId in
|
|
448
|
+
* tape-format.ts to achieve producer/consumer convergence (D-2).
|
|
449
|
+
*
|
|
450
|
+
* Includes: buffer/texture/pipeline/bindGroup/sampler/textureView/
|
|
451
|
+
* shaderModule handles (persistent), plus passHandleId and cmdHandleId
|
|
452
|
+
* from pass/encoder events (per-frame transient). Transient handles
|
|
453
|
+
* that are declared in-frame are excluded later by the inFrameHandleIds
|
|
454
|
+
* filter in getTape().
|
|
455
|
+
*/
|
|
456
|
+
|
|
457
|
+
export interface DebugRhiInstance extends RhiInstance {
|
|
458
|
+
arm(frames: number): Result<void, RhiDebugError>;
|
|
459
|
+
onFrameEnd(): void;
|
|
460
|
+
getTape(): Tape | RhiDebugError | undefined;
|
|
461
|
+
getState(): string;
|
|
462
|
+
getEvents(): readonly RhiCallEvent[];
|
|
463
|
+
getBlobPool(): ReadonlyMap<string, ArrayBuffer>;
|
|
464
|
+
/** Transition to error state (e.g. on device.lost). Tape data preserved but valid=false. */
|
|
465
|
+
transitionToError(): void;
|
|
466
|
+
/** Clear error state to idle, allowing re-arm. */
|
|
467
|
+
disposeError(): void;
|
|
468
|
+
/**
|
|
469
|
+
* Snapshot a resource's GPU bytes into the tape as an initialData event.
|
|
470
|
+
*
|
|
471
|
+
* Reads the resource descriptor from the internal registry, copies the
|
|
472
|
+
* resource's bytes via copyToBuffer/mapAsync, stores the bytes into the
|
|
473
|
+
* blobPool (djb2 hash-dedup), and pushes an RhiCallEventInitialData into
|
|
474
|
+
* the event stream. Returns Result with {handleId, dataHash} on success,
|
|
475
|
+
* or capture-snapshot-failed on a readback/storeBlob failure.
|
|
476
|
+
*
|
|
477
|
+
* Async: the GPU readback chain (copyToBuffer -> submit ->
|
|
478
|
+
* onSubmittedWorkDone -> mapAsync) is inherently asynchronous.
|
|
479
|
+
*/
|
|
480
|
+
snapshotResource(
|
|
481
|
+
handleId: HandleId,
|
|
482
|
+
): Promise<Result<{ handleId: HandleId; dataHash: string }, RhiDebugError>>;
|
|
483
|
+
/**
|
|
484
|
+
* Frame-header snapshot loop: awaits all submitted GPU work, then snapshots
|
|
485
|
+
* every live resource in the descriptor registry (full-table dump, no
|
|
486
|
+
* trimming). Advances the recorder Armed -> Snapshotting -> Recording on
|
|
487
|
+
* success. Returns the first snapshot failure as a Result so the caller can
|
|
488
|
+
* fail fast rather than record a partial seed set.
|
|
489
|
+
*/
|
|
490
|
+
snapshotAllLiveResources(timeoutMs?: number): Promise<Result<void, RhiDebugError>>;
|
|
491
|
+
/**
|
|
492
|
+
* @internal
|
|
493
|
+
* Append an event from a standalone wrapper (e.g. `wrapCreateShaderModule`)
|
|
494
|
+
* through the same `_skipRecord` + state-machine guard that the proxy
|
|
495
|
+
* methods use. This exists so external wrappers cannot bypass recursion
|
|
496
|
+
* protection (I-12, round 1 implement-review). Not part of the AI-user
|
|
497
|
+
* contract — `wrap*` helpers in this package are the only callers.
|
|
498
|
+
*/
|
|
499
|
+
pushExternalEvent(event: RhiCallEvent): void;
|
|
500
|
+
/**
|
|
501
|
+
* @internal
|
|
502
|
+
* Register a shader module object in the recorder's handleMap so
|
|
503
|
+
* downstream pipeline events can look up its handleId via getHandleId.
|
|
504
|
+
*/
|
|
505
|
+
registerShaderModule(handle: ShaderModule, handleId: HandleId): void;
|
|
506
|
+
/**
|
|
507
|
+
* @internal
|
|
508
|
+
* Route a create event through registerHandle (alloc id + write bootstrapCreates)
|
|
509
|
+
* and pushEvent in a single call. For standalone wrappers that cannot access
|
|
510
|
+
* the internal registerHandle/pushEvent functions directly.
|
|
511
|
+
* Returns the allocated HandleId so the caller can use it for downstream
|
|
512
|
+
* registration (e.g. shaderModule → handleMap).
|
|
513
|
+
*/
|
|
514
|
+
pushExternalCreateEvent(handle: object, kind: string, event: RhiCallEvent): HandleId;
|
|
515
|
+
/**
|
|
516
|
+
* @internal
|
|
517
|
+
* Drop all device-bound recorder state after the host observes a real
|
|
518
|
+
* device loss. A tape recorded against the lost device cannot seed a fresh
|
|
519
|
+
* device, so the next capture must start from the rebuilt resource graph.
|
|
520
|
+
*/
|
|
521
|
+
resetForDeviceLoss(): void;
|
|
522
|
+
/** @internal Return whether the current capture is valid. */
|
|
523
|
+
valid(): boolean;
|
|
524
|
+
/**
|
|
525
|
+
* @internal
|
|
526
|
+
* Return the number of entries in bootstrapCreates. Test-only accessor
|
|
527
|
+
* so unit tests can verify bootstrapCreates write/retain semantics
|
|
528
|
+
* without going through getTape() closure computation (M2).
|
|
529
|
+
*/
|
|
530
|
+
bootstrapCreatesSize(): number;
|
|
531
|
+
/** @internal Return the create-event identities owned by the bootstrap registry. */
|
|
532
|
+
bootstrapEvents(): readonly RhiCallEvent[];
|
|
533
|
+
/**
|
|
534
|
+
* @internal
|
|
535
|
+
* Read-only view of the descriptor registry keyed by handleId. Test-only
|
|
536
|
+
* accessor so unit tests can verify create* register / destroy* remove
|
|
537
|
+
* semantics (AC-09) without reaching into the closed-over recorder state.
|
|
538
|
+
*/
|
|
539
|
+
descriptorTable(): ReadonlyMap<
|
|
540
|
+
HandleId,
|
|
541
|
+
{
|
|
542
|
+
kind: 'buffer' | 'texture';
|
|
543
|
+
size?: number | GPUExtent3DStrict;
|
|
544
|
+
format?: GPUTextureFormat;
|
|
545
|
+
usage: number;
|
|
546
|
+
resource: object;
|
|
547
|
+
}
|
|
548
|
+
>;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ============================================================================
|
|
552
|
+
// Type for standalone createShaderModule function (from rhi-webgpu)
|
|
553
|
+
// ============================================================================
|
|
554
|
+
|
|
555
|
+
export type CreateShaderModuleFn = (
|
|
556
|
+
device: RhiDevice,
|
|
557
|
+
desc: { code: string; label?: string | undefined },
|
|
558
|
+
) => Promise<Result<ShaderModule, import('@forgeax/engine-rhi').RhiError>>;
|
|
559
|
+
|
|
560
|
+
export type { RecorderInternal, SnapshotProgress };
|
|
561
|
+
export {
|
|
562
|
+
addSwapchainViewFormat,
|
|
563
|
+
allocHandleId,
|
|
564
|
+
ensureTextureCreateEvent,
|
|
565
|
+
fastHash,
|
|
566
|
+
getHandleId,
|
|
567
|
+
hasBootstrapDependency,
|
|
568
|
+
isDepthOrStencilFormat,
|
|
569
|
+
isMappableBuffer,
|
|
570
|
+
isRecordingActive,
|
|
571
|
+
isSnapshottableColorTexture,
|
|
572
|
+
promoteBufferUsage,
|
|
573
|
+
pushEvent,
|
|
574
|
+
pushSnapshotEvent,
|
|
575
|
+
RecorderState,
|
|
576
|
+
reconcileSwapchainViewFormats,
|
|
577
|
+
registerHandle,
|
|
578
|
+
retainsCaptureBootstrap,
|
|
579
|
+
SNAPSHOT_TIMEOUT_MS,
|
|
580
|
+
shouldRecord,
|
|
581
|
+
snapshotStageOf,
|
|
582
|
+
snapshotTimeoutDetail,
|
|
583
|
+
storeBlob,
|
|
584
|
+
TEXTURE_USAGE_BINDING,
|
|
585
|
+
TEXTURE_USAGE_COPY_DST,
|
|
586
|
+
TEXTURE_USAGE_COPY_SRC,
|
|
587
|
+
};
|