@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,99 @@
|
|
|
1
|
+
/// <reference types="@webgpu/types" />
|
|
2
|
+
|
|
3
|
+
export interface TextureReadbackDescriptor {
|
|
4
|
+
readonly format: string;
|
|
5
|
+
readonly dimension: string;
|
|
6
|
+
readonly aspect?: GPUTextureAspect | undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type TextureReadbackPlan =
|
|
10
|
+
| {
|
|
11
|
+
readonly supported: true;
|
|
12
|
+
readonly format: string;
|
|
13
|
+
readonly bytesPerTexel: number;
|
|
14
|
+
readonly blockWidth: 1;
|
|
15
|
+
readonly blockHeight: 1;
|
|
16
|
+
}
|
|
17
|
+
| {
|
|
18
|
+
readonly supported: false;
|
|
19
|
+
readonly format: string;
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const BYTES_PER_TEXEL: Readonly<Record<string, number>> = {
|
|
24
|
+
r8unorm: 1,
|
|
25
|
+
rg8unorm: 2,
|
|
26
|
+
rgba8unorm: 4,
|
|
27
|
+
'rgba8unorm-srgb': 4,
|
|
28
|
+
bgra8unorm: 4,
|
|
29
|
+
'bgra8unorm-srgb': 4,
|
|
30
|
+
r16float: 2,
|
|
31
|
+
rg16float: 4,
|
|
32
|
+
rgba16float: 8,
|
|
33
|
+
r32float: 4,
|
|
34
|
+
rg32float: 8,
|
|
35
|
+
rgba32float: 16,
|
|
36
|
+
rgb10a2unorm: 4,
|
|
37
|
+
rg11b10ufloat: 4,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const DEPTH_FORMATS = new Set([
|
|
41
|
+
'depth16unorm',
|
|
42
|
+
'depth24plus',
|
|
43
|
+
'depth24plus-stencil8',
|
|
44
|
+
'depth32float',
|
|
45
|
+
'depth32float-stencil8',
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
export function isDepthTextureFormat(format: string): boolean {
|
|
49
|
+
return DEPTH_FORMATS.has(format);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isDepthStencilTextureFormat(format: string): boolean {
|
|
53
|
+
return format.endsWith('-stencil8');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getTextureReadbackPlan(descriptor: TextureReadbackDescriptor): TextureReadbackPlan {
|
|
57
|
+
if (descriptor.dimension === '3d') {
|
|
58
|
+
return {
|
|
59
|
+
supported: false,
|
|
60
|
+
format: descriptor.format,
|
|
61
|
+
reason: '3D texture volume readback is outside the v7 core matrix',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (/^(bc|etc2|eac|astc)-/.test(descriptor.format)) {
|
|
65
|
+
return {
|
|
66
|
+
supported: false,
|
|
67
|
+
format: descriptor.format,
|
|
68
|
+
reason: 'compressed texture readback has no core decoder',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (DEPTH_FORMATS.has(descriptor.format)) {
|
|
72
|
+
return {
|
|
73
|
+
supported: true,
|
|
74
|
+
format: descriptor.format,
|
|
75
|
+
bytesPerTexel: 4,
|
|
76
|
+
blockWidth: 1,
|
|
77
|
+
blockHeight: 1,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const bytesPerTexel = BYTES_PER_TEXEL[descriptor.format];
|
|
81
|
+
if (bytesPerTexel === undefined) {
|
|
82
|
+
return {
|
|
83
|
+
supported: false,
|
|
84
|
+
format: descriptor.format,
|
|
85
|
+
reason: 'texture format is not in the v7 readback matrix',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
supported: true,
|
|
90
|
+
format: descriptor.format,
|
|
91
|
+
bytesPerTexel,
|
|
92
|
+
blockWidth: 1,
|
|
93
|
+
blockHeight: 1,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function textureBytesPerTexel(format: string): number | undefined {
|
|
98
|
+
return BYTES_PER_TEXEL[format] ?? (DEPTH_FORMATS.has(format) ? 4 : undefined);
|
|
99
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// texel-decode.ts -- CPU decode of each uncompressed color format to RGBA8 for preview.
|
|
2
|
+
//
|
|
3
|
+
// The recorder promotes COPY_SRC onto every createTexture, so the replayer's
|
|
4
|
+
// copyTextureToBuffer reads back each uncompressed color texture's raw bytes
|
|
5
|
+
// faithfully (no format change -- replay fidelity intact). This module turns
|
|
6
|
+
// those raw bytes into displayable RGBA8 entirely on the host:
|
|
7
|
+
//
|
|
8
|
+
// raw bytes --formatInfo--> per-channel values --[display map]--> RGBA8
|
|
9
|
+
//
|
|
10
|
+
// Display map (RenderDoc's default initial state, confirmed with the user):
|
|
11
|
+
// float/ufloat -> clamp(v, 0, 1) * 255 (HDR highlights >1 saturate to white)
|
|
12
|
+
// unorm -> v * 255 (already 0..1)
|
|
13
|
+
// snorm -> clamp(v, 0, 1) * 255 (negatives clip to black)
|
|
14
|
+
// uint/sint -> clamp(v, 0, 255) (small integer ids stay visible)
|
|
15
|
+
//
|
|
16
|
+
// Channels -> RGBA: 1ch grayscale (R=G=B), 2ch (R,G,B=0), 3ch (RGB,A=255), 4ch.
|
|
17
|
+
//
|
|
18
|
+
// Related: plan "all-format color preview"; reuses formatInfo (rhi-debug texel-layout).
|
|
19
|
+
|
|
20
|
+
import { bytesPerTexel, type FormatInfo, formatInfo } from './texel-layout';
|
|
21
|
+
|
|
22
|
+
/** Decode a single IEEE half-float (16-bit) bit pattern to a JS number. */
|
|
23
|
+
export function halfToFloat(h: number): number {
|
|
24
|
+
const sign = (h & 0x8000) >> 15;
|
|
25
|
+
const exp = (h & 0x7c00) >> 10;
|
|
26
|
+
const frac = h & 0x03ff;
|
|
27
|
+
const s = sign === 0 ? 1 : -1;
|
|
28
|
+
if (exp === 0) {
|
|
29
|
+
// Subnormal (or zero): 2^-14 * (frac / 1024).
|
|
30
|
+
return s * 2 ** -14 * (frac / 1024);
|
|
31
|
+
}
|
|
32
|
+
if (exp === 0x1f) {
|
|
33
|
+
// Inf / NaN.
|
|
34
|
+
return frac === 0 ? s * Number.POSITIVE_INFINITY : Number.NaN;
|
|
35
|
+
}
|
|
36
|
+
// Normal: 2^(exp-15) * (1 + frac/1024).
|
|
37
|
+
return s * 2 ** (exp - 15) * (1 + frac / 1024);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Decode an unsigned-int bit pattern of an 11- or 10-bit unsigned float
|
|
42
|
+
* (rg11b10ufloat layout: no sign bit). 11-bit = 5 exp + 6 mantissa; 10-bit =
|
|
43
|
+
* 5 exp + 5 mantissa. Bias 15, matching the f16 exponent.
|
|
44
|
+
*/
|
|
45
|
+
function smallUFloatToFloat(bits: number, mantissaBits: number): number {
|
|
46
|
+
const expBits = 5;
|
|
47
|
+
const exp = (bits >> mantissaBits) & ((1 << expBits) - 1);
|
|
48
|
+
const mantMax = 1 << mantissaBits;
|
|
49
|
+
const frac = bits & (mantMax - 1);
|
|
50
|
+
if (exp === 0) return 2 ** -14 * (frac / mantMax);
|
|
51
|
+
if (exp === 0x1f) return frac === 0 ? Number.POSITIVE_INFINITY : Number.NaN;
|
|
52
|
+
return 2 ** (exp - 15) * (1 + frac / mantMax);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const clamp01 = (v: number) => (v < 0 ? 0 : v > 1 ? 1 : v);
|
|
56
|
+
|
|
57
|
+
/** Map one decoded channel value to a 0..255 display byte by channel type. */
|
|
58
|
+
function toByte(v: number, channelType: FormatInfo['channelType']): number {
|
|
59
|
+
// uint/sint: small integer ids stay visible (clamp 0..255, no normalize).
|
|
60
|
+
if (channelType === 'uint' || channelType === 'sint') {
|
|
61
|
+
return Math.round(v < 0 ? 0 : v > 255 ? 255 : v);
|
|
62
|
+
}
|
|
63
|
+
// float/ufloat clamp HDR to [0,1]; unorm already 0..1; snorm clips negatives.
|
|
64
|
+
return Math.round(clamp01(v) * 255);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Read the `channels` raw channel values of one texel at byte offset `off`.
|
|
69
|
+
* Plain formats read each channel at a uniform per-channel byte width; packed
|
|
70
|
+
* formats unpack the single 32-bit word by bit-field. Values are returned in the
|
|
71
|
+
* format's native channel order (BGRA swizzle handled by the caller).
|
|
72
|
+
*/
|
|
73
|
+
function readTexel(
|
|
74
|
+
view: DataView,
|
|
75
|
+
off: number,
|
|
76
|
+
info: FormatInfo,
|
|
77
|
+
channelBytes: number,
|
|
78
|
+
): [number, number, number, number] {
|
|
79
|
+
const out: [number, number, number, number] = [0, 0, 0, 1];
|
|
80
|
+
|
|
81
|
+
if (info.packed === 'rgb10a2unorm') {
|
|
82
|
+
const word = view.getUint32(off, true);
|
|
83
|
+
out[0] = (word & 0x3ff) / 1023;
|
|
84
|
+
out[1] = ((word >> 10) & 0x3ff) / 1023;
|
|
85
|
+
out[2] = ((word >> 20) & 0x3ff) / 1023;
|
|
86
|
+
out[3] = ((word >> 30) & 0x3) / 3;
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
if (info.packed === 'rg11b10ufloat') {
|
|
90
|
+
const word = view.getUint32(off, true);
|
|
91
|
+
out[0] = smallUFloatToFloat(word & 0x7ff, 6);
|
|
92
|
+
out[1] = smallUFloatToFloat((word >> 11) & 0x7ff, 6);
|
|
93
|
+
out[2] = smallUFloatToFloat((word >> 22) & 0x3ff, 5);
|
|
94
|
+
out[3] = 1;
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (let c = 0; c < info.channels; c++) {
|
|
99
|
+
const co = off + c * channelBytes;
|
|
100
|
+
let v: number;
|
|
101
|
+
switch (info.channelType) {
|
|
102
|
+
case 'float':
|
|
103
|
+
v = channelBytes === 2 ? halfToFloat(view.getUint16(co, true)) : view.getFloat32(co, true);
|
|
104
|
+
break;
|
|
105
|
+
case 'unorm':
|
|
106
|
+
v =
|
|
107
|
+
channelBytes === 1
|
|
108
|
+
? view.getUint8(co) / 255
|
|
109
|
+
: channelBytes === 2
|
|
110
|
+
? view.getUint16(co, true) / 65535
|
|
111
|
+
: view.getUint32(co, true) / 4294967295;
|
|
112
|
+
break;
|
|
113
|
+
case 'snorm': {
|
|
114
|
+
const raw =
|
|
115
|
+
channelBytes === 1
|
|
116
|
+
? view.getInt8(co)
|
|
117
|
+
: channelBytes === 2
|
|
118
|
+
? view.getInt16(co, true)
|
|
119
|
+
: view.getInt32(co, true);
|
|
120
|
+
const denom = channelBytes === 1 ? 127 : channelBytes === 2 ? 32767 : 2147483647;
|
|
121
|
+
v = Math.max(-1, raw / denom);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'uint':
|
|
125
|
+
v =
|
|
126
|
+
channelBytes === 1
|
|
127
|
+
? view.getUint8(co)
|
|
128
|
+
: channelBytes === 2
|
|
129
|
+
? view.getUint16(co, true)
|
|
130
|
+
: view.getUint32(co, true);
|
|
131
|
+
break;
|
|
132
|
+
case 'sint':
|
|
133
|
+
v =
|
|
134
|
+
channelBytes === 1
|
|
135
|
+
? view.getInt8(co)
|
|
136
|
+
: channelBytes === 2
|
|
137
|
+
? view.getInt16(co, true)
|
|
138
|
+
: view.getInt32(co, true);
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
v = 0;
|
|
142
|
+
}
|
|
143
|
+
out[c] = v;
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Decode tight raw GPU bytes of a supported color or depth/stencil plane into
|
|
150
|
+
* RGBA8 the canvas can paint via putImageData. Returns null when the format has
|
|
151
|
+
* no {@link formatInfo} entry or the requested aspect is invalid.
|
|
152
|
+
*
|
|
153
|
+
* @param bytes - Tight readback bytes (no row padding), length = w*h*bytesPerTexel.
|
|
154
|
+
* @param format - The texture's real format string.
|
|
155
|
+
* @param width - Texture width in pixels.
|
|
156
|
+
* @param height - Texture height in pixels.
|
|
157
|
+
* @param aspect - Explicit plane selected by readback for depth/stencil formats.
|
|
158
|
+
*/
|
|
159
|
+
export function decodeToRgba8(
|
|
160
|
+
bytes: Uint8Array,
|
|
161
|
+
format: string,
|
|
162
|
+
width: number,
|
|
163
|
+
height: number,
|
|
164
|
+
aspect: 'all' | 'depth-only' | 'stencil-only' = 'all',
|
|
165
|
+
): Uint8ClampedArray<ArrayBuffer> | null {
|
|
166
|
+
if (format.startsWith('depth')) {
|
|
167
|
+
return decodeDepthToRgba8(bytes, format, width, height, aspect);
|
|
168
|
+
}
|
|
169
|
+
const info = formatInfo(format);
|
|
170
|
+
const texBytes = bytesPerTexel(format as never);
|
|
171
|
+
if (!info || texBytes === undefined) return null;
|
|
172
|
+
|
|
173
|
+
// Per-channel byte width for plain formats (packed formats ignore this).
|
|
174
|
+
const channelBytes = info.packed ? texBytes : texBytes / info.channels;
|
|
175
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
176
|
+
// Explicit ArrayBuffer backing (not ArrayBufferLike) so ImageData accepts it.
|
|
177
|
+
const out = new Uint8ClampedArray(new ArrayBuffer(width * height * 4));
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < width * height; i++) {
|
|
180
|
+
const [c0, c1, c2, c3] = readTexel(view, i * texBytes, info, channelBytes);
|
|
181
|
+
const di = i * 4;
|
|
182
|
+
if (info.channels === 1) {
|
|
183
|
+
const g = toByte(c0, info.channelType);
|
|
184
|
+
out[di] = g;
|
|
185
|
+
out[di + 1] = g;
|
|
186
|
+
out[di + 2] = g;
|
|
187
|
+
out[di + 3] = 255;
|
|
188
|
+
} else if (info.channels === 2) {
|
|
189
|
+
out[di] = toByte(c0, info.channelType);
|
|
190
|
+
out[di + 1] = toByte(c1, info.channelType);
|
|
191
|
+
out[di + 2] = 0;
|
|
192
|
+
out[di + 3] = 255;
|
|
193
|
+
} else {
|
|
194
|
+
// 3 or 4 channels. BGRA stores blue-first -> swizzle B<->R.
|
|
195
|
+
const r = info.bgra ? c2 : c0;
|
|
196
|
+
const b = info.bgra ? c0 : c2;
|
|
197
|
+
out[di] = toByte(r, info.channelType);
|
|
198
|
+
out[di + 1] = toByte(c1, info.channelType);
|
|
199
|
+
out[di + 2] = toByte(b, info.channelType);
|
|
200
|
+
out[di + 3] = info.channels === 4 ? toByte(c3, info.channelType) : 255;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function decodeDepthToRgba8(
|
|
207
|
+
bytes: Uint8Array,
|
|
208
|
+
format: string,
|
|
209
|
+
width: number,
|
|
210
|
+
height: number,
|
|
211
|
+
aspect: 'all' | 'depth-only' | 'stencil-only',
|
|
212
|
+
): Uint8ClampedArray<ArrayBuffer> | null {
|
|
213
|
+
const combined = format.endsWith('-stencil8');
|
|
214
|
+
if (combined && aspect === 'all') return null;
|
|
215
|
+
if (!combined && aspect === 'stencil-only') return null;
|
|
216
|
+
const stencil = aspect === 'stencil-only';
|
|
217
|
+
const bytesPerValue = stencil ? 1 : 4;
|
|
218
|
+
const texelCount = width * height;
|
|
219
|
+
if (bytes.byteLength < texelCount * bytesPerValue) return null;
|
|
220
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
221
|
+
const out = new Uint8ClampedArray(new ArrayBuffer(texelCount * 4));
|
|
222
|
+
for (let index = 0; index < texelCount; index++) {
|
|
223
|
+
const value = stencil
|
|
224
|
+
? view.getUint8(index)
|
|
225
|
+
: Math.round(clamp01(view.getFloat32(index * 4, true)) * 255);
|
|
226
|
+
const target = index * 4;
|
|
227
|
+
out[target] = value;
|
|
228
|
+
out[target + 1] = value;
|
|
229
|
+
out[target + 2] = value;
|
|
230
|
+
out[target + 3] = 255;
|
|
231
|
+
}
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Decode a single texel from tight raw GPU bytes into raw float RGBA values
|
|
237
|
+
* without the display clamp (D-4: raw byte bypass for HDR fidelity).
|
|
238
|
+
*
|
|
239
|
+
* Unlike `decodeToRgba8` which clamps float/ufloat to [0,1] for display, this
|
|
240
|
+
* returns the decoded per-channel values as-is: float formats keep true values
|
|
241
|
+
* (2.5 stays 2.5, negative stays negative), unorm/snorm stay in their native
|
|
242
|
+
* ranges, uint/sint stay as integers cast to number.
|
|
243
|
+
*
|
|
244
|
+
* Channels: 1ch grayscale (R=G=B, A=1), 2ch (RG, B=0, A=1), 3/4ch (RGBA or
|
|
245
|
+
* BGRA→swizzled). Returns null when the format has no {@link formatInfo} entry.
|
|
246
|
+
*
|
|
247
|
+
* @param bytes - Tight readback bytes, length = width*height*bytesPerTexel.
|
|
248
|
+
* @param format - The texture's real format string.
|
|
249
|
+
* @param width - Texture width in pixels.
|
|
250
|
+
* @param height - Texture height in pixels.
|
|
251
|
+
* @param texelX - 0-based column of the target texel.
|
|
252
|
+
* @param texelY - 0-based row of the target texel.
|
|
253
|
+
*/
|
|
254
|
+
export function decodeTexelRaw(
|
|
255
|
+
bytes: Uint8Array,
|
|
256
|
+
format: string,
|
|
257
|
+
width: number,
|
|
258
|
+
height: number,
|
|
259
|
+
texelX: number,
|
|
260
|
+
texelY: number,
|
|
261
|
+
): [number, number, number, number] | null {
|
|
262
|
+
if (texelX < 0 || texelX >= width || texelY < 0 || texelY >= height) return null;
|
|
263
|
+
|
|
264
|
+
const info = formatInfo(format);
|
|
265
|
+
const texBytes = bytesPerTexel(format as never);
|
|
266
|
+
if (!info || texBytes === undefined) return null;
|
|
267
|
+
|
|
268
|
+
const texelIdx = texelY * width + texelX;
|
|
269
|
+
const byteOffset = texelIdx * texBytes;
|
|
270
|
+
|
|
271
|
+
// Guard: the buffer must be large enough for this texel.
|
|
272
|
+
if (byteOffset + texBytes > bytes.byteLength) return null;
|
|
273
|
+
|
|
274
|
+
const channelBytes = info.packed ? texBytes : texBytes / info.channels;
|
|
275
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
276
|
+
const channels = readTexel(view, byteOffset, info, channelBytes);
|
|
277
|
+
|
|
278
|
+
if (info.channels === 1) {
|
|
279
|
+
return [channels[0], channels[0], channels[0], 1];
|
|
280
|
+
}
|
|
281
|
+
if (info.channels === 2) {
|
|
282
|
+
return [channels[0], channels[1], 0, 1];
|
|
283
|
+
}
|
|
284
|
+
// 3 or 4 channels. BGRA swizzle B<->R.
|
|
285
|
+
const r = info.bgra ? channels[2] : channels[0];
|
|
286
|
+
const b = info.bgra ? channels[0] : channels[2];
|
|
287
|
+
const a = info.channels === 4 ? channels[3] : 1;
|
|
288
|
+
return [r, channels[1], b, a];
|
|
289
|
+
}
|