@forgeax/engine-render 0.1.23 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -9
- package/dist/assembly/bundler-contract.d.ts +9 -0
- package/dist/assembly/bundler-contract.d.ts.map +1 -0
- package/dist/assembly/device-feature-admission.d.ts +8 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -1
- package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
- package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +7 -243
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +3 -2
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
- package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts +168 -0
- package/dist/assembly/material-shader-policy.d.ts.map +1 -0
- package/dist/assembly/material-shader-prewarm.d.ts +21 -0
- package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
- package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
- package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
- package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
- package/dist/assembly/recovery/generation.d.ts +46 -0
- package/dist/assembly/recovery/generation.d.ts.map +1 -0
- package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
- package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
- package/dist/assembly/renderer-facade.d.ts +10 -0
- package/dist/assembly/renderer-facade.d.ts.map +1 -0
- package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-helpers.d.ts +19 -0
- package/dist/assembly/renderer-helpers.d.ts.map +1 -0
- package/dist/assembly/renderer-lifecycle.d.ts +34 -0
- package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
- package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +55 -0
- package/dist/assembly/webgpu-ready.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +152 -0
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
- package/dist/chunk-GE3SDD72.mjs.map +1 -0
- package/dist/{chunk-HZUPSYW7.mjs → chunk-KZFTK6DO.mjs} +102 -15
- package/dist/chunk-KZFTK6DO.mjs.map +1 -0
- package/dist/{chunk-FF7K3FSO.mjs → chunk-Q752SHI5.mjs} +89 -89
- package/dist/{chunk-FF7K3FSO.mjs.map → chunk-Q752SHI5.mjs.map} +1 -1
- package/dist/{chunk-QODA5UKU.mjs → chunk-RR5L4SRS.mjs} +2 -2
- package/dist/chunk-RR5L4SRS.mjs.map +1 -0
- package/dist/{chunk-YALPS44A.mjs → chunk-TFH5I7JJ.mjs} +47 -47
- package/dist/chunk-TFH5I7JJ.mjs.map +1 -0
- package/dist/{chunk-FBC4R44J.mjs → chunk-UQYS7FRM.mjs} +57 -57
- package/dist/chunk-UQYS7FRM.mjs.map +1 -0
- package/dist/{chunk-4NPUASZR.mjs → chunk-YKB5DQV7.mjs} +161 -19
- package/dist/chunk-YKB5DQV7.mjs.map +1 -0
- package/dist/{chunk-ZUWKAZ2I.mjs → chunk-YRUYGA4A.mjs} +615 -237
- package/dist/chunk-YRUYGA4A.mjs.map +1 -0
- package/dist/{chunk-DIYW3M5D.mjs → chunk-Z3YYH2MB.mjs} +2210 -2213
- package/dist/chunk-Z3YYH2MB.mjs.map +1 -0
- package/dist/construct-renderer.mjs +19841 -16190
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/device-scope.d.ts +2 -0
- package/dist/device/device-scope.d.ts.map +1 -1
- package/dist/device/gpu-residency.d.ts +24 -4
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/frame.d.ts +5 -0
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/inspection.d.ts +7 -0
- package/dist/environment/inspection.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +20 -2
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +58 -2
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +13 -0
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +899 -0
- package/dist/extract/contracts.d.ts.map +1 -0
- package/dist/features/host.d.ts +2 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +3 -0
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/prepared-graphics-store.d.ts +2 -0
- package/dist/features/prepared-graphics-store.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +28 -0
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +1 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +7 -6
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +7 -1
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +6 -6
- package/dist/internal.mjs +6 -6
- package/dist/light-buffer-layout.d.ts +3 -0
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts +34 -10
- package/dist/materials.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +56 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/occlusion-proxy.d.ts +4 -0
- package/dist/occlusion-proxy.d.ts.map +1 -0
- package/dist/pbr-pipeline.d.ts +32 -9
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/points-lines/prepare.d.ts +3 -0
- package/dist/points-lines/prepare.d.ts.map +1 -1
- package/dist/points-lines/standard-owner.d.ts +33 -0
- package/dist/points-lines/standard-owner.d.ts.map +1 -0
- package/dist/record/frame-snapshot.d.ts +5 -0
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +14 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +14 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +7 -17
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +11 -3
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/recovery-pipeline.d.ts +30 -0
- package/dist/record/recovery-pipeline.d.ts.map +1 -0
- package/dist/record/render-context.d.ts +26 -4
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +1 -1
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +65 -0
- package/dist/recovery/render-system-candidate.d.ts.map +1 -0
- package/dist/render-contract.d.ts +64 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +12 -0
- package/dist/render-system-extract-tail.d.ts.map +1 -0
- package/dist/render-system-extract.d.ts +156 -22
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +128 -4
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +19 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -6
- package/dist/temporal/taa-history-store.d.ts +15 -1
- package/dist/temporal/taa-history-store.d.ts.map +1 -1
- package/dist/temporal/transaction.d.ts +5 -0
- package/dist/temporal/transaction.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/device-feature-admission.unit.test.ts +6 -0
- package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
- package/src/__tests__/environment-generation.integration.test.ts +23 -0
- package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
- package/src/__tests__/factory-contract.integration.test.ts +122 -7
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
- package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
- package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +24 -9
- package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
- package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
- package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
- package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
- package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +1 -1
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
- package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +85 -18
- package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
- package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
- package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
- package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
- package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
- package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
- package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
- package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
- package/src/__tests__/shader-manifest-fixture.ts +13 -16
- package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
- package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
- package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
- package/src/assembly/bundler-contract.ts +9 -0
- package/src/assembly/device-feature-admission.ts +15 -5
- package/src/assembly/extended-lighting-fallback.ts +216 -0
- package/src/assembly/factory.ts +81 -10088
- package/src/assembly/material/assembly.ts +18 -2
- package/src/assembly/material/standard-layer-projection.ts +16 -0
- package/src/assembly/material-shader-policy.ts +854 -0
- package/src/assembly/material-shader-prewarm.ts +88 -0
- package/src/assembly/mesh-ssbo-grow.ts +270 -0
- package/src/assembly/recovery/device-loss-fanout.ts +89 -0
- package/src/assembly/recovery/generation.ts +141 -0
- package/src/assembly/recovery/recovery-attempt.ts +800 -0
- package/src/assembly/recovery/renderer-recover.ts +906 -0
- package/src/assembly/renderer-facade.ts +342 -0
- package/src/assembly/renderer-frame-transaction.ts +45 -0
- package/src/assembly/renderer-helpers.ts +99 -0
- package/src/assembly/renderer-lifecycle.ts +159 -0
- package/src/assembly/shader-prewarm-policy.ts +157 -0
- package/src/assembly/webgpu-ready-contract.ts +14 -0
- package/src/assembly/webgpu-ready.ts +4079 -0
- package/src/assembly/webgpu-renderer.ts +4095 -0
- package/src/components/camera.ts +1 -1
- package/src/debug-draw-glue.ts +1 -1
- package/src/device/device-scope.ts +35 -6
- package/src/device/gpu-residency.ts +194 -26
- package/src/environment/frame.ts +6 -0
- package/src/environment/inspection.ts +10 -0
- package/src/environment/lifecycle.ts +114 -8
- package/src/errors/recover.ts +76 -5
- package/src/errors/render.ts +17 -0
- package/src/extract/contracts.ts +974 -0
- package/src/features/host.ts +16 -0
- package/src/features/prepared-gpu-work.ts +20 -2
- package/src/features/prepared-graphics-store.ts +17 -0
- package/src/gpu-driven/production-raster.ts +97 -2
- package/src/gpu-scene.ts +3 -2
- package/src/ibl/IblPipelineCache.ts +134 -50
- package/src/ibl/skylight-bind-group.ts +26 -0
- package/src/index.ts +9 -1
- package/src/light-buffer-layout.ts +3 -0
- package/src/materials.ts +262 -45
- package/src/mesh-material-bindings.ts +59 -0
- package/src/occlusion-proxy.ts +64 -0
- package/src/pbr-pipeline.ts +139 -15
- package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
- package/src/points-lines/prepare.ts +9 -0
- package/src/points-lines/standard-owner.ts +403 -0
- package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
- package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
- package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
- package/src/record/__tests__/temporal-payload.unit.test.ts +22 -43
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -1
- package/src/record/frame-snapshot.ts +15 -0
- package/src/record/frame-targets.ts +11 -1
- package/src/record/frame.ts +6 -2
- package/src/record/helpers.ts +1 -1
- package/src/record/main-pass-geometry.ts +230 -5
- package/src/record/main-pass-material.ts +316 -209
- package/src/record/main-pass-sprite-draws.ts +6 -7
- package/src/record/main-pass.ts +94 -6
- package/src/record/mesh-ssbo.ts +32 -10
- package/src/record/recovery-pipeline.ts +695 -0
- package/src/record/render-context.ts +35 -4
- package/src/record/typed-frame-graph.ts +45 -1
- package/src/record/view-ubo.ts +3 -3
- package/src/recovery/render-system-candidate.ts +839 -0
- package/src/render-contract.ts +65 -7
- package/src/render-system-extract-tail.ts +2983 -0
- package/src/render-system-extract.ts +124 -2977
- package/src/render-system.ts +868 -659
- package/src/scene/render-scene.ts +124 -1
- package/src/temporal/frame-coordinator.ts +4 -1
- package/src/temporal/taa-history-store.ts +46 -2
- package/src/temporal/temporal-view.ts +1 -1
- package/src/temporal/transaction.ts +9 -0
- package/src/tilemap-chunk-extract-system.ts +1 -1
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
- package/dist/chunk-4L3P6DT5.mjs.map +0 -1
- package/dist/chunk-4NPUASZR.mjs.map +0 -1
- package/dist/chunk-DIYW3M5D.mjs.map +0 -1
- package/dist/chunk-FBC4R44J.mjs.map +0 -1
- package/dist/chunk-HZUPSYW7.mjs.map +0 -1
- package/dist/chunk-QODA5UKU.mjs.map +0 -1
- package/dist/chunk-YALPS44A.mjs.map +0 -1
- package/dist/chunk-ZUWKAZ2I.mjs.map +0 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { RhiNullCommandEncoder, RhiNullDevice, RhiNullQueue, rhi } from '@forgeax/engine-rhi-null';
|
|
2
|
+
import { ok } from '@forgeax/engine-types';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import {
|
|
5
|
+
acquireDeviceGeneration,
|
|
6
|
+
probeCandidateGraphExecution,
|
|
7
|
+
runRecoveryStep,
|
|
8
|
+
} from '../assembly/recovery/recovery-attempt';
|
|
9
|
+
import {
|
|
10
|
+
createRecoveryContinuation,
|
|
11
|
+
createRecoveryDeadline,
|
|
12
|
+
createRendererLifecycle,
|
|
13
|
+
createSingleFlight,
|
|
14
|
+
RECOVERY_ADAPTER_DEADLINE_MS,
|
|
15
|
+
RECOVERY_DEVICE_DEADLINE_MS,
|
|
16
|
+
RECOVERY_PHASES,
|
|
17
|
+
RECOVERY_TOTAL_DEADLINE_MS,
|
|
18
|
+
} from '../assembly/renderer-lifecycle';
|
|
19
|
+
|
|
20
|
+
describe('renderer recovery lifecycle', () => {
|
|
21
|
+
it('exhausts the public lifecycle states and rejects illegal transitions', () => {
|
|
22
|
+
const lifecycle = createRendererLifecycle();
|
|
23
|
+
|
|
24
|
+
expect(lifecycle.state()).toBe('alive');
|
|
25
|
+
expect(lifecycle.transition('device-lost')).toBe(true);
|
|
26
|
+
expect(lifecycle.transition('recovering')).toBe(true);
|
|
27
|
+
expect(lifecycle.transition('alive')).toBe(true);
|
|
28
|
+
expect(lifecycle.transition('faulted')).toBe(false);
|
|
29
|
+
expect(lifecycle.transition('disposed')).toBe(true);
|
|
30
|
+
expect(lifecycle.transition('alive')).toBe(false);
|
|
31
|
+
expect(lifecycle.states()).toEqual([
|
|
32
|
+
'alive',
|
|
33
|
+
'device-lost',
|
|
34
|
+
'recovering',
|
|
35
|
+
'faulted',
|
|
36
|
+
'disposed',
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('shares one recovery promise and one candidate for concurrent callers', async () => {
|
|
41
|
+
let attempts = 0;
|
|
42
|
+
let release: (() => void) | undefined;
|
|
43
|
+
const flight = createSingleFlight(async () => {
|
|
44
|
+
attempts += 1;
|
|
45
|
+
await new Promise<void>((resolve) => {
|
|
46
|
+
release = resolve;
|
|
47
|
+
});
|
|
48
|
+
return { candidate: attempts };
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const first = flight.run();
|
|
52
|
+
const second = flight.run();
|
|
53
|
+
expect(first).toBe(second);
|
|
54
|
+
expect(flight.inFlight()).toBe(first);
|
|
55
|
+
expect(attempts).toBe(1);
|
|
56
|
+
|
|
57
|
+
release?.();
|
|
58
|
+
await expect(first).resolves.toEqual({ candidate: 1 });
|
|
59
|
+
expect(flight.inFlight()).toBeUndefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('enforces the 20 second attempt and 10 second acquisition budgets', () => {
|
|
63
|
+
const deadline = createRecoveryDeadline(1000);
|
|
64
|
+
|
|
65
|
+
expect(deadline.totalDeadlineMs).toBe(RECOVERY_TOTAL_DEADLINE_MS);
|
|
66
|
+
expect(deadline.adapterDeadlineMs).toBe(RECOVERY_ADAPTER_DEADLINE_MS);
|
|
67
|
+
expect(deadline.deviceDeadlineMs).toBe(RECOVERY_DEVICE_DEADLINE_MS);
|
|
68
|
+
expect(deadline.isValid('acquire-adapter', 10_999)).toBe(true);
|
|
69
|
+
expect(deadline.isValid('acquire-adapter', 11_000)).toBe(false);
|
|
70
|
+
expect(deadline.isValid('acquire-device', 11_000)).toBe(false);
|
|
71
|
+
expect(deadline.isValid('compile-graph', 20_999)).toBe(true);
|
|
72
|
+
expect(deadline.isValid('compile-graph', 21_000)).toBe(false);
|
|
73
|
+
expect(RECOVERY_PHASES).toEqual([
|
|
74
|
+
'quiesce',
|
|
75
|
+
'acquire-adapter',
|
|
76
|
+
'acquire-device',
|
|
77
|
+
'rehydrate',
|
|
78
|
+
'compile-graph',
|
|
79
|
+
'publish',
|
|
80
|
+
'cleanup',
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('reports acquisition phases in their execution order', async () => {
|
|
85
|
+
const phases: string[] = [];
|
|
86
|
+
const result = await acquireDeviceGeneration(
|
|
87
|
+
{ rhi },
|
|
88
|
+
{} as HTMLCanvasElement,
|
|
89
|
+
undefined,
|
|
90
|
+
undefined,
|
|
91
|
+
(phase) => phases.push(phase),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(result.ok).toBe(true);
|
|
95
|
+
expect(phases).toEqual(['acquire-adapter', 'acquire-device']);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('treats the null graph probe as structural-only and rejects a non-null pixel mismatch', async () => {
|
|
99
|
+
const adapter = (await rhi.requestAdapter()).unwrap();
|
|
100
|
+
const nullDevice = (await adapter.requestDevice()).unwrap();
|
|
101
|
+
const texture = nullDevice
|
|
102
|
+
.createTexture({
|
|
103
|
+
label: 'recovery-probe-test.texture',
|
|
104
|
+
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
|
|
105
|
+
mipLevelCount: 1,
|
|
106
|
+
sampleCount: 1,
|
|
107
|
+
dimension: '2d',
|
|
108
|
+
format: 'rgba8unorm',
|
|
109
|
+
usage: 0x11,
|
|
110
|
+
viewFormats: undefined,
|
|
111
|
+
textureBindingViewDimension: undefined,
|
|
112
|
+
})
|
|
113
|
+
.unwrap();
|
|
114
|
+
const view = nullDevice.createTextureView(texture, { dimension: '2d' }).unwrap();
|
|
115
|
+
const nullProbe = await probeCandidateGraphExecution(nullDevice, {
|
|
116
|
+
texture,
|
|
117
|
+
view,
|
|
118
|
+
format: 'rgba8unorm',
|
|
119
|
+
});
|
|
120
|
+
expect(nullProbe.ok).toBe(true);
|
|
121
|
+
|
|
122
|
+
class PixellessNonNullDevice extends RhiNullDevice {
|
|
123
|
+
override get caps() {
|
|
124
|
+
return { ...super.caps, backendKind: 'webgpu' as const };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const nonNullDevice = new PixellessNonNullDevice(
|
|
128
|
+
new RhiNullQueue(),
|
|
129
|
+
(bookkeeper, device) => new RhiNullCommandEncoder(bookkeeper, device),
|
|
130
|
+
);
|
|
131
|
+
const nonNullTexture = nonNullDevice
|
|
132
|
+
.createTexture({
|
|
133
|
+
label: 'recovery-probe-test.non-null-texture',
|
|
134
|
+
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
|
|
135
|
+
mipLevelCount: 1,
|
|
136
|
+
sampleCount: 1,
|
|
137
|
+
dimension: '2d',
|
|
138
|
+
format: 'rgba8unorm',
|
|
139
|
+
usage: 0x11,
|
|
140
|
+
viewFormats: undefined,
|
|
141
|
+
textureBindingViewDimension: undefined,
|
|
142
|
+
})
|
|
143
|
+
.unwrap();
|
|
144
|
+
const nonNullView = nonNullDevice
|
|
145
|
+
.createTextureView(nonNullTexture, { dimension: '2d' })
|
|
146
|
+
.unwrap();
|
|
147
|
+
const nonNullProbe = await probeCandidateGraphExecution(nonNullDevice, {
|
|
148
|
+
texture: nonNullTexture,
|
|
149
|
+
view: nonNullView,
|
|
150
|
+
format: 'rgba8unorm',
|
|
151
|
+
});
|
|
152
|
+
expect(nonNullProbe.ok).toBe(false);
|
|
153
|
+
if (!nonNullProbe.ok) expect(nonNullProbe.error.code).toBe('webgpu-runtime-error');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('invalidates late continuation and cleans the candidate exactly once', () => {
|
|
157
|
+
let cleanupCount = 0;
|
|
158
|
+
const continuation = createRecoveryContinuation(createRecoveryDeadline(0), () => {
|
|
159
|
+
cleanupCount += 1;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(continuation.isValid('compile-graph', 19_999)).toBe(true);
|
|
163
|
+
continuation.abandon(20_000);
|
|
164
|
+
expect(continuation.isValid('compile-graph', 20_000)).toBe(false);
|
|
165
|
+
continuation.cleanupOnce();
|
|
166
|
+
continuation.cleanupOnce();
|
|
167
|
+
expect(cleanupCount).toBe(1);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('invalidates an async step on timeout before a late completion can write', async () => {
|
|
171
|
+
let cleanupCount = 0;
|
|
172
|
+
let lateWrites = 0;
|
|
173
|
+
const startedAt = Date.now();
|
|
174
|
+
const deadline = {
|
|
175
|
+
startedAt,
|
|
176
|
+
totalDeadlineMs: 1,
|
|
177
|
+
adapterDeadlineMs: 1,
|
|
178
|
+
deviceDeadlineMs: 1,
|
|
179
|
+
deadlineAt: startedAt + 1,
|
|
180
|
+
beginDeviceAcquisition: (_now: number) => undefined,
|
|
181
|
+
isValid: (
|
|
182
|
+
_phase:
|
|
183
|
+
| 'quiesce'
|
|
184
|
+
| 'acquire-adapter'
|
|
185
|
+
| 'acquire-device'
|
|
186
|
+
| 'rehydrate'
|
|
187
|
+
| 'compile-graph'
|
|
188
|
+
| 'publish'
|
|
189
|
+
| 'cleanup',
|
|
190
|
+
now: number,
|
|
191
|
+
) => now < startedAt + 1,
|
|
192
|
+
elapsed: (now: number) => Math.max(0, now - startedAt),
|
|
193
|
+
};
|
|
194
|
+
const continuation = createRecoveryContinuation(deadline, () => {
|
|
195
|
+
cleanupCount += 1;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const outcome = await runRecoveryStep(
|
|
199
|
+
async (attemptToken) => {
|
|
200
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
201
|
+
if (attemptToken?.isValid('compile-graph', Date.now())) lateWrites += 1;
|
|
202
|
+
return ok(undefined);
|
|
203
|
+
},
|
|
204
|
+
'compile-graph',
|
|
205
|
+
deadline,
|
|
206
|
+
Date.now,
|
|
207
|
+
continuation,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
expect(outcome.kind).toBe('timeout');
|
|
211
|
+
if (outcome.kind === 'timeout') expect(outcome.timeout.cause.code).toBe('webgpu-runtime-error');
|
|
212
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
213
|
+
expect(lateWrites).toBe(0);
|
|
214
|
+
expect(cleanupCount).toBe(1);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { TaaHistoryStore } from '../temporal/taa-history-store';
|
|
3
|
+
|
|
4
|
+
type RecoveryReceipt = {
|
|
5
|
+
readonly deviceGeneration: number;
|
|
6
|
+
readonly completed: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type RecoverableHistory = TaaHistoryStore & {
|
|
10
|
+
recoverForGeneration(deviceGeneration: number): void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function recover(store: TaaHistoryStore, deviceGeneration: number): void {
|
|
14
|
+
(store as RecoverableHistory).recoverForGeneration(deviceGeneration);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function commit(store: TaaHistoryStore, receipt: RecoveryReceipt): void {
|
|
18
|
+
const attempt = store.begin('device-generation');
|
|
19
|
+
attempt.commit(receipt as never);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('M4 / m4_t2 — target history recovery keeps logical token', () => {
|
|
23
|
+
it('publishes uninitialized replacement storage with neutral fallback', () => {
|
|
24
|
+
const store = new TaaHistoryStore();
|
|
25
|
+
const token = (
|
|
26
|
+
store.inspect() as typeof store.inspect extends () => infer T ? T : never & { token: string }
|
|
27
|
+
).token;
|
|
28
|
+
|
|
29
|
+
recover(store, 8);
|
|
30
|
+
|
|
31
|
+
expect(store.inspect()).toMatchObject({
|
|
32
|
+
token,
|
|
33
|
+
deviceGeneration: 8,
|
|
34
|
+
content: 'uninitialized',
|
|
35
|
+
fallback: 'neutral-texture',
|
|
36
|
+
valid: false,
|
|
37
|
+
requestVersion: 0,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('raises history only for a matching completed receipt', () => {
|
|
42
|
+
const store = new TaaHistoryStore();
|
|
43
|
+
recover(store, 8);
|
|
44
|
+
const before = store.inspect();
|
|
45
|
+
|
|
46
|
+
commit(store, { deviceGeneration: 7, completed: true });
|
|
47
|
+
expect(store.inspect()).toMatchObject({
|
|
48
|
+
token: before.token,
|
|
49
|
+
deviceGeneration: 8,
|
|
50
|
+
content: 'uninitialized',
|
|
51
|
+
valid: false,
|
|
52
|
+
requestVersion: 0,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
commit(store, { deviceGeneration: 8, completed: false });
|
|
56
|
+
expect(store.inspect().valid).toBe(false);
|
|
57
|
+
|
|
58
|
+
commit(store, { deviceGeneration: 8, completed: true });
|
|
59
|
+
expect(store.inspect()).toMatchObject({
|
|
60
|
+
token: before.token,
|
|
61
|
+
deviceGeneration: 8,
|
|
62
|
+
content: 'active',
|
|
63
|
+
valid: true,
|
|
64
|
+
requestVersion: 1,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('does not invent once or on-demand requests during replacement', () => {
|
|
69
|
+
const store = new TaaHistoryStore();
|
|
70
|
+
recover(store, 9);
|
|
71
|
+
const inspection = store.inspect() as typeof store.inspect extends () => infer T
|
|
72
|
+
? T
|
|
73
|
+
: never & {
|
|
74
|
+
requestVersion: number;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
expect(inspection.requestVersion).toBe(0);
|
|
78
|
+
expect(inspection.content).toBe('uninitialized');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -58,22 +58,19 @@ const standardPbrVariants = (() => {
|
|
|
58
58
|
const variants = [];
|
|
59
59
|
for (const cluster of [false, true]) {
|
|
60
60
|
for (const storage of [false, true]) {
|
|
61
|
-
for (const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
: sortedDefines.map(([key, value]) => `${key}=${value}`).join('+');
|
|
75
|
-
variants.push({ definesKey, defines, composedWgsl: pbrWgsl });
|
|
76
|
-
}
|
|
61
|
+
for (const vertexColor of [false, true]) {
|
|
62
|
+
const defines = {
|
|
63
|
+
CLUSTER_FORWARD_AVAILABLE: cluster,
|
|
64
|
+
STORAGE_BUFFER_AVAILABLE: storage,
|
|
65
|
+
VERTEX_COLOR_AVAILABLE: vertexColor,
|
|
66
|
+
};
|
|
67
|
+
const sortedDefines = Object.entries(defines).sort(([left], [right]) =>
|
|
68
|
+
left < right ? -1 : left > right ? 1 : 0,
|
|
69
|
+
);
|
|
70
|
+
const definesKey = sortedDefines.every(([, value]) => value)
|
|
71
|
+
? ''
|
|
72
|
+
: sortedDefines.map(([key, value]) => `${key}=${value}`).join('+');
|
|
73
|
+
variants.push({ definesKey, defines, composedWgsl: pbrWgsl });
|
|
77
74
|
}
|
|
78
75
|
}
|
|
79
76
|
}
|
|
@@ -20,7 +20,7 @@ const fragmentConsumerSources = [
|
|
|
20
20
|
pbrPipelineSource,
|
|
21
21
|
iblPipelineCacheSource,
|
|
22
22
|
readFileSync(new URL('../ibl/skylight-bind-group.ts', import.meta.url), 'utf8'),
|
|
23
|
-
readFileSync(new URL('../assembly/
|
|
23
|
+
readFileSync(new URL('../assembly/webgpu-renderer.ts', import.meta.url), 'utf8'),
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
describe('shader stage owner', () => {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Materials } from '../materials.js';
|
|
3
|
+
|
|
4
|
+
describe('Standard pass projection', () => {
|
|
5
|
+
it('keeps default and custom base surfaces on the base pass family', () => {
|
|
6
|
+
const material = Materials.standard({ baseColor: [1, 1, 1, 1] });
|
|
7
|
+
expect(material.passes?.map((pass) => pass.name)).toEqual([
|
|
8
|
+
'forward',
|
|
9
|
+
'deferred',
|
|
10
|
+
'shadow-caster',
|
|
11
|
+
]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('uses the same physical forward-only projection for a zero-factor declaration', () => {
|
|
15
|
+
const material = Materials.standard({
|
|
16
|
+
baseColor: [1, 1, 1, 1],
|
|
17
|
+
clearcoat: 0,
|
|
18
|
+
clearcoatRoughness: 0,
|
|
19
|
+
});
|
|
20
|
+
expect(material.passes?.map((pass) => pass.name)).toEqual(['forward', 'shadow-caster']);
|
|
21
|
+
expect(material.parameters?.some((parameter) => parameter.name === 'clearcoat')).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
import {
|
|
3
3
|
type MaterialShaderManifestEntry,
|
|
4
4
|
selectHdrpPbrPrewarmVariants,
|
|
5
|
+
selectProbePrewarmVariants,
|
|
5
6
|
selectStandardPbrTransmissionPrewarmVariants,
|
|
6
7
|
} from '../assembly/factory.js';
|
|
7
8
|
|
|
@@ -9,10 +10,6 @@ const falseKey =
|
|
|
9
10
|
'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=false+VERTEX_COLOR_AVAILABLE=false';
|
|
10
11
|
const trueKey =
|
|
11
12
|
'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=true+VERTEX_COLOR_AVAILABLE=false';
|
|
12
|
-
const hdrpFalseKey =
|
|
13
|
-
'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=false+VERTEX_COLOR_AVAILABLE=false';
|
|
14
|
-
const hdrpTrueKey =
|
|
15
|
-
'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=true+VERTEX_COLOR_AVAILABLE=false';
|
|
16
13
|
const probeFalseKey =
|
|
17
14
|
'CLUSTER_FORWARD_AVAILABLE=false+PROBE_BLEND_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=false+VERTEX_COLOR_AVAILABLE=false';
|
|
18
15
|
const probeTrueKey =
|
|
@@ -30,29 +27,33 @@ function standardEntry(
|
|
|
30
27
|
};
|
|
31
28
|
}
|
|
32
29
|
|
|
33
|
-
function
|
|
30
|
+
function variant(cluster: boolean, storage: boolean, projector?: boolean) {
|
|
31
|
+
const defines = {
|
|
32
|
+
CLUSTER_FORWARD_AVAILABLE: cluster,
|
|
33
|
+
STORAGE_BUFFER_AVAILABLE: storage,
|
|
34
|
+
VERTEX_COLOR_AVAILABLE: false,
|
|
35
|
+
...(projector === undefined ? {} : { PROJECTOR_AVAILABLE: projector }),
|
|
36
|
+
};
|
|
34
37
|
return {
|
|
35
|
-
definesKey
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
},
|
|
42
|
-
composedWgsl: `standard-${transmission}`,
|
|
38
|
+
definesKey: Object.entries(defines)
|
|
39
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
40
|
+
.map(([name, value]) => `${name}=${value}`)
|
|
41
|
+
.join('+'),
|
|
42
|
+
defines,
|
|
43
|
+
composedWgsl: JSON.stringify(defines),
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
function
|
|
47
|
+
function transmissionVariant(definesKey: string, transmission: boolean) {
|
|
47
48
|
return {
|
|
48
49
|
definesKey,
|
|
49
50
|
defines: {
|
|
50
|
-
CLUSTER_FORWARD_AVAILABLE:
|
|
51
|
+
CLUSTER_FORWARD_AVAILABLE: false,
|
|
51
52
|
STORAGE_BUFFER_AVAILABLE: true,
|
|
52
53
|
TRANSMISSION_AVAILABLE: transmission,
|
|
53
54
|
VERTEX_COLOR_AVAILABLE: false,
|
|
54
55
|
},
|
|
55
|
-
composedWgsl: `
|
|
56
|
+
composedWgsl: `standard-${transmission}`,
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -60,103 +61,112 @@ function probeTransmissionVariant(definesKey: string, transmission: boolean) {
|
|
|
60
61
|
const base = transmissionVariant(definesKey, transmission);
|
|
61
62
|
return {
|
|
62
63
|
...base,
|
|
63
|
-
definesKey,
|
|
64
64
|
defines: { ...base.defines, PROBE_BLEND_AVAILABLE: true },
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function capabilityTransmissionVariant(
|
|
69
|
-
definesKey: string,
|
|
70
|
-
transmission: boolean,
|
|
71
|
-
extendedLighting: boolean,
|
|
72
|
-
) {
|
|
73
|
-
const base = transmissionVariant(definesKey, transmission);
|
|
74
|
-
return {
|
|
75
|
-
...base,
|
|
76
|
-
definesKey,
|
|
77
|
-
defines: {
|
|
78
|
-
...base.defines,
|
|
79
|
-
EXTENDED_LIGHTING_AVAILABLE: extendedLighting,
|
|
80
|
-
PROJECTOR_AVAILABLE: true,
|
|
81
|
-
},
|
|
82
|
-
composedWgsl: `standard-${extendedLighting ? 'extended' : 'fallback'}-${transmission}`,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
68
|
describe('Standard transmission exact-key prewarm selection', () => {
|
|
87
|
-
it('returns both declared variants
|
|
69
|
+
it('returns both declared transmission variants', () => {
|
|
88
70
|
const entry = standardEntry([
|
|
89
71
|
transmissionVariant(falseKey, false),
|
|
90
72
|
transmissionVariant(trueKey, true),
|
|
91
73
|
]);
|
|
92
|
-
|
|
93
|
-
|
|
74
|
+
expect(
|
|
75
|
+
selectStandardPbrTransmissionPrewarmVariants(entry, true).map((item) => item.definesKey),
|
|
76
|
+
).toEqual([falseKey, trueKey]);
|
|
94
77
|
});
|
|
95
78
|
|
|
96
|
-
it('
|
|
79
|
+
it('rejects a missing exact transmission variant', () => {
|
|
97
80
|
const entry = standardEntry([transmissionVariant(falseKey, false)]);
|
|
98
81
|
expect(() => selectStandardPbrTransmissionPrewarmVariants(entry, true)).toThrow(
|
|
99
82
|
'TRANSMISSION_AVAILABLE=true',
|
|
100
83
|
);
|
|
101
84
|
});
|
|
85
|
+
});
|
|
102
86
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
'manifest row is missing',
|
|
106
|
-
);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('keeps object-level probe variants out of the boot prewarm selection', () => {
|
|
87
|
+
describe('HDRP PBR capability prewarm selection', () => {
|
|
88
|
+
it('selects only the matching static capability/geometry state', () => {
|
|
110
89
|
const entry = standardEntry([
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
90
|
+
variant(true, true),
|
|
91
|
+
variant(true, false),
|
|
92
|
+
variant(false, true),
|
|
93
|
+
variant(false, false),
|
|
115
94
|
]);
|
|
116
|
-
expect(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
95
|
+
expect(selectHdrpPbrPrewarmVariants(entry, true).map((item) => item.definesKey)).toEqual([
|
|
96
|
+
variant(true, true).definesKey,
|
|
97
|
+
]);
|
|
98
|
+
// Cluster-forward requires the storage-backed group(2) ABI. A device
|
|
99
|
+
// without storage support must stay on the URP route and prewarm nothing.
|
|
100
|
+
expect(selectHdrpPbrPrewarmVariants(entry, false)).toEqual([]);
|
|
121
101
|
});
|
|
122
102
|
|
|
123
|
-
it('
|
|
124
|
-
const fallbackFalseKey = `${falseKey}+EXTENDED_LIGHTING_AVAILABLE=false+PROJECTOR_AVAILABLE=true`;
|
|
125
|
-
const fallbackTrueKey = `${trueKey}+EXTENDED_LIGHTING_AVAILABLE=false+PROJECTOR_AVAILABLE=true`;
|
|
126
|
-
const extendedFalseKey = `${falseKey}+EXTENDED_LIGHTING_AVAILABLE=true+PROJECTOR_AVAILABLE=true`;
|
|
127
|
-
const extendedTrueKey = `${trueKey}+EXTENDED_LIGHTING_AVAILABLE=true+PROJECTOR_AVAILABLE=true`;
|
|
103
|
+
it('respects an explicitly declared projector capability', () => {
|
|
128
104
|
const entry = standardEntry([
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
capabilityTransmissionVariant(extendedTrueKey, true, true),
|
|
105
|
+
variant(true, true, true),
|
|
106
|
+
variant(true, true, false),
|
|
107
|
+
variant(true, true),
|
|
133
108
|
]);
|
|
134
|
-
|
|
135
109
|
expect(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
),
|
|
139
|
-
).toEqual([fallbackFalseKey, fallbackTrueKey]);
|
|
140
|
-
expect(
|
|
141
|
-
selectStandardPbrTransmissionPrewarmVariants(entry, true, true, true, true).map(
|
|
142
|
-
(variant) => variant.definesKey,
|
|
143
|
-
),
|
|
144
|
-
).toEqual([extendedFalseKey, extendedTrueKey]);
|
|
110
|
+
selectHdrpPbrPrewarmVariants(entry, true, undefined, true).map((item) => item.definesKey),
|
|
111
|
+
).toEqual([variant(true, true, true).definesKey, variant(true, true).definesKey]);
|
|
145
112
|
});
|
|
146
113
|
});
|
|
147
114
|
|
|
148
|
-
describe('
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
).toEqual([
|
|
115
|
+
describe('probe material module prewarm capability selection', () => {
|
|
116
|
+
it('keeps probe variants out of the fallback tier and matches device-owned axes', () => {
|
|
117
|
+
const entry = standardEntry([
|
|
118
|
+
probeTransmissionVariant(probeFalseKey, false),
|
|
119
|
+
probeTransmissionVariant(probeTrueKey, true),
|
|
120
|
+
]);
|
|
121
|
+
expect(selectProbePrewarmVariants(entry, false)).toEqual([]);
|
|
122
|
+
expect(selectProbePrewarmVariants(entry, true).map((variant) => variant.definesKey)).toEqual([
|
|
123
|
+
probeFalseKey,
|
|
124
|
+
probeTrueKey,
|
|
125
|
+
]);
|
|
155
126
|
});
|
|
156
127
|
|
|
157
|
-
it('
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
128
|
+
it('filters backend and sampled-texture axes but retains geometry/topology choices', () => {
|
|
129
|
+
const goodKey =
|
|
130
|
+
'CLUSTER_FORWARD_AVAILABLE=true+EXTENDED_LIGHTING_AVAILABLE=false+PROBE_BLEND_AVAILABLE=true+PROJECTOR_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=false+VERTEX_COLOR_AVAILABLE=true';
|
|
131
|
+
const wrongExtendedKey = goodKey.replace(
|
|
132
|
+
'EXTENDED_LIGHTING_AVAILABLE=false',
|
|
133
|
+
'EXTENDED_LIGHTING_AVAILABLE=true',
|
|
134
|
+
);
|
|
135
|
+
const wrongProjectorKey = goodKey.replace(
|
|
136
|
+
'PROJECTOR_AVAILABLE=false',
|
|
137
|
+
'PROJECTOR_AVAILABLE=true',
|
|
138
|
+
);
|
|
139
|
+
const wrongTransmissionKey = goodKey.replace(
|
|
140
|
+
'TRANSMISSION_AVAILABLE=false',
|
|
141
|
+
'TRANSMISSION_AVAILABLE=true',
|
|
142
|
+
);
|
|
143
|
+
const variant = (definesKey: string, defines: Record<string, boolean>) => ({
|
|
144
|
+
definesKey,
|
|
145
|
+
defines,
|
|
146
|
+
composedWgsl: definesKey,
|
|
147
|
+
});
|
|
148
|
+
const baseDefines = {
|
|
149
|
+
CLUSTER_FORWARD_AVAILABLE: true,
|
|
150
|
+
EXTENDED_LIGHTING_AVAILABLE: false,
|
|
151
|
+
PROBE_BLEND_AVAILABLE: true,
|
|
152
|
+
PROJECTOR_AVAILABLE: false,
|
|
153
|
+
STORAGE_BUFFER_AVAILABLE: true,
|
|
154
|
+
TRANSMISSION_AVAILABLE: false,
|
|
155
|
+
VERTEX_COLOR_AVAILABLE: true,
|
|
156
|
+
};
|
|
157
|
+
const selected = selectProbePrewarmVariants(
|
|
158
|
+
standardEntry([
|
|
159
|
+
variant(goodKey, baseDefines),
|
|
160
|
+
variant(wrongExtendedKey, { ...baseDefines, EXTENDED_LIGHTING_AVAILABLE: true }),
|
|
161
|
+
variant(wrongProjectorKey, { ...baseDefines, PROJECTOR_AVAILABLE: true }),
|
|
162
|
+
variant(wrongTransmissionKey, { ...baseDefines, TRANSMISSION_AVAILABLE: true }),
|
|
163
|
+
]),
|
|
164
|
+
true,
|
|
165
|
+
false,
|
|
166
|
+
false,
|
|
167
|
+
true,
|
|
168
|
+
false,
|
|
169
|
+
);
|
|
170
|
+
expect(selected.map((candidate) => candidate.definesKey)).toEqual([goodKey]);
|
|
161
171
|
});
|
|
162
172
|
});
|
|
@@ -219,7 +219,9 @@ describe('volumetric fog RenderGraph topology', () => {
|
|
|
219
219
|
fileURLToPath(new URL('../render-system.ts', import.meta.url)),
|
|
220
220
|
'utf8',
|
|
221
221
|
);
|
|
222
|
-
const recoverStart = renderSystemSource.indexOf(
|
|
222
|
+
const recoverStart = renderSystemSource.indexOf(
|
|
223
|
+
'resetForRecover(retiringPipelineState?: PipelineState, replacementDevice?: RhiDevice): void {',
|
|
224
|
+
);
|
|
223
225
|
expect(recoverStart).toBeGreaterThanOrEqual(0);
|
|
224
226
|
const recoverEnd = renderSystemSource.indexOf(
|
|
225
227
|
'restorePostProcessResources(): void',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ImportTransport } from '@forgeax/engine-types';
|
|
2
|
+
|
|
3
|
+
/** Build-tool inputs forwarded from the host into renderer assembly. */
|
|
4
|
+
export interface BundlerOptions {
|
|
5
|
+
readonly importTransport?: ImportTransport | undefined;
|
|
6
|
+
readonly shaderManifestUrl?: string | undefined;
|
|
7
|
+
/** Exact checkout revision emitted by the build-tool adapter. */
|
|
8
|
+
readonly build?: string | undefined;
|
|
9
|
+
}
|
|
@@ -14,6 +14,14 @@ const COMPRESSION_FEATURES: GPUFeatureName[] = [
|
|
|
14
14
|
// request that larger limit when the adapter can provide it; adapter limits are
|
|
15
15
|
// not automatically inherited by requestDevice().
|
|
16
16
|
export const STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES = 17;
|
|
17
|
+
/**
|
|
18
|
+
* The complete Standard physical root can declare all optional texture slots.
|
|
19
|
+
* Its material group therefore needs one more sampled-texture entry than the
|
|
20
|
+
* extended-lighting topology alone. Request this ceiling when the adapter can
|
|
21
|
+
* provide it; lower-capability devices remain fail-closed at material
|
|
22
|
+
* pipeline admission instead of receiving an invalid WebGPU layout.
|
|
23
|
+
*/
|
|
24
|
+
export const STANDARD_PHYSICAL_REQUIRED_SAMPLED_TEXTURES = 21;
|
|
17
25
|
|
|
18
26
|
export interface DeviceFeatureAdmissionOptions {
|
|
19
27
|
readonly gpuPassTiming?: object | undefined;
|
|
@@ -41,11 +49,13 @@ export function deriveDeviceFeatureAdmission(
|
|
|
41
49
|
}
|
|
42
50
|
const adapterSampledTextureLimit = adapter.limits?.maxSampledTexturesPerShaderStage ?? 0;
|
|
43
51
|
const requiredSampledTextureLimit =
|
|
44
|
-
adapterSampledTextureLimit >=
|
|
45
|
-
?
|
|
46
|
-
: adapterSampledTextureLimit >=
|
|
47
|
-
?
|
|
48
|
-
:
|
|
52
|
+
adapterSampledTextureLimit >= STANDARD_PHYSICAL_REQUIRED_SAMPLED_TEXTURES
|
|
53
|
+
? STANDARD_PHYSICAL_REQUIRED_SAMPLED_TEXTURES
|
|
54
|
+
: adapterSampledTextureLimit >= EXTENDED_LIGHTING_REQUIRED_SAMPLED_TEXTURES
|
|
55
|
+
? EXTENDED_LIGHTING_REQUIRED_SAMPLED_TEXTURES
|
|
56
|
+
: adapterSampledTextureLimit >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES
|
|
57
|
+
? STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES
|
|
58
|
+
: undefined;
|
|
49
59
|
return {
|
|
50
60
|
requiredFeatures,
|
|
51
61
|
...(requiredSampledTextureLimit === undefined
|