@forgeax/engine-render 0.1.23 → 0.1.25
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 +231 -10
- 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/surface-projection.d.ts +24 -0
- package/dist/assembly/material/surface-projection.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts +188 -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/assembly/webgpu-vertex-layouts.d.ts +89 -0
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
- package/dist/assets/asset-decoders.d.ts.map +1 -1
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-FF7K3FSO.mjs → chunk-ADHHVYLW.mjs} +89 -89
- package/dist/chunk-ADHHVYLW.mjs.map +1 -0
- package/dist/{chunk-YALPS44A.mjs → chunk-E34VL5VI.mjs} +47 -47
- package/dist/chunk-E34VL5VI.mjs.map +1 -0
- package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
- package/dist/chunk-GE3SDD72.mjs.map +1 -0
- package/dist/{chunk-HZUPSYW7.mjs → chunk-HKXTW355.mjs} +105 -15
- package/dist/chunk-HKXTW355.mjs.map +1 -0
- package/dist/chunk-JSEDTZYS.mjs +413 -0
- package/dist/chunk-JSEDTZYS.mjs.map +1 -0
- package/dist/{chunk-FBC4R44J.mjs → chunk-KM2NOX2I.mjs} +57 -57
- package/dist/chunk-KM2NOX2I.mjs.map +1 -0
- package/dist/{chunk-DIYW3M5D.mjs → chunk-N3RT2EUR.mjs} +2288 -2238
- package/dist/chunk-N3RT2EUR.mjs.map +1 -0
- package/dist/{chunk-ZUWKAZ2I.mjs → chunk-TCLDX7SZ.mjs} +1167 -368
- package/dist/chunk-TCLDX7SZ.mjs.map +1 -0
- package/dist/{chunk-QODA5UKU.mjs → chunk-ZZ4YQ474.mjs} +2 -2
- package/dist/chunk-ZZ4YQ474.mjs.map +1 -0
- package/dist/components/instances.d.ts +13 -31
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +20688 -16314
- 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 +34 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +5 -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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +27 -9
- package/dist/index.mjs.map +1 -1
- package/dist/instance-buffer-cache.d.ts +7 -1
- package/dist/instance-buffer-cache.d.ts.map +1 -1
- package/dist/instances.d.ts +101 -0
- package/dist/instances.d.ts.map +1 -0
- 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 +96 -11
- 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/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.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 +18 -2
- 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 +36 -4
- 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 +28 -5
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +11 -1
- package/dist/record/shadow-pass.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 +68 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +16 -0
- package/dist/render-system-extract-tail.d.ts.map +1 -0
- package/dist/render-system-extract.d.ts +180 -22
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +131 -4
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene-types.d.ts +2 -0
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +31 -1
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/systems/skin-palette-allocator.d.ts +6 -0
- package/dist/systems/skin-palette-allocator.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__/gpu-scene.unit.test.ts +38 -2
- 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__/instances-store.unit.test.ts +46 -0
- package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +29 -22
- package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
- package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
- package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
- 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__/material-surface-authoring.test-d.ts +62 -0
- package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
- package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -5
- 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 +127 -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__/skin-motion-regression.unit.test.ts +33 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
- package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
- package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
- package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
- package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
- package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
- package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
- package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
- 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 +83 -10088
- package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
- package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
- package/src/assembly/material/assembly.ts +18 -2
- package/src/assembly/material/standard-layer-projection.ts +16 -0
- package/src/assembly/material/surface-projection.ts +90 -0
- package/src/assembly/material-shader-policy.ts +932 -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 +4066 -0
- package/src/assembly/webgpu-renderer.ts +4027 -0
- package/src/assembly/webgpu-vertex-layouts.ts +132 -0
- package/src/assets/asset-decoders.ts +27 -4
- package/src/components/camera.ts +1 -1
- package/src/components/instances.ts +14 -197
- 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 +131 -3
- package/src/gpu-scene.ts +87 -31
- package/src/ibl/IblPipelineCache.ts +134 -50
- package/src/ibl/skylight-bind-group.ts +26 -0
- package/src/index.ts +10 -1
- package/src/instance-buffer-cache.ts +14 -1
- package/src/instances.ts +288 -0
- package/src/light-buffer-layout.ts +3 -0
- package/src/materials.ts +365 -53
- package/src/mesh-material-bindings.ts +59 -0
- package/src/occlusion-proxy.ts +64 -0
- package/src/pbr-pipeline.ts +139 -15
- package/src/pipeline-builder.ts +14 -10
- package/src/pipeline-spec.ts +6 -14
- 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 +34 -2
- package/src/record/frame-targets.ts +11 -1
- package/src/record/frame.ts +37 -4
- package/src/record/helpers.ts +1 -1
- package/src/record/main-pass-geometry.ts +727 -63
- package/src/record/main-pass-material.ts +316 -209
- package/src/record/main-pass-sprite-draws.ts +7 -8
- 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 +39 -4
- package/src/record/shadow-pass.ts +46 -99
- package/src/record/typed-frame-graph.ts +45 -1
- package/src/record/view-ubo.ts +3 -3
- package/src/recovery/render-system-candidate.ts +876 -0
- package/src/render-contract.ts +69 -7
- package/src/render-system-extract-tail.ts +3039 -0
- package/src/render-system-extract.ts +177 -3003
- package/src/render-system.ts +906 -662
- package/src/scene/render-scene-types.ts +2 -0
- package/src/scene/render-scene.ts +703 -37
- package/src/systems/skin-palette-allocator.ts +129 -7
- 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/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
- package/dist/chunk-4L3P6DT5.mjs.map +0 -1
- package/dist/chunk-4NPUASZR.mjs +0 -203
- 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-FF7K3FSO.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
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
import {
|
|
33
33
|
blitMipmapsSync,
|
|
34
34
|
getOrCreateMipmapPipeline,
|
|
35
|
+
type MipmapEncoderWork,
|
|
35
36
|
type MipmapShaderModuleFactory,
|
|
37
|
+
prepareMipmaps,
|
|
36
38
|
} from '@forgeax/engine-assets-runtime';
|
|
37
39
|
import type { World } from '@forgeax/engine-ecs';
|
|
38
40
|
import {
|
|
@@ -98,6 +100,7 @@ import {
|
|
|
98
100
|
runIblPrecompute,
|
|
99
101
|
} from '../ibl/IblPipelineCache';
|
|
100
102
|
import type { RhiErrorListenerRegistry } from '../lifecycle';
|
|
103
|
+
import type { RecoveryColdWorkGuard } from '../record/render-context';
|
|
101
104
|
import {
|
|
102
105
|
type CubeRenderData,
|
|
103
106
|
deriveRenderDataCubemap,
|
|
@@ -106,7 +109,7 @@ import {
|
|
|
106
109
|
type MeshRenderData,
|
|
107
110
|
type TextureRenderData,
|
|
108
111
|
} from '../render-data';
|
|
109
|
-
import type { DeviceScope } from './device-scope';
|
|
112
|
+
import type { DeviceScope, LifecycleResourceSpec } from './device-scope';
|
|
110
113
|
|
|
111
114
|
const DYNAMIC_OFFSET_STRIDE = 256;
|
|
112
115
|
const FACE_COUNT = 6;
|
|
@@ -182,6 +185,7 @@ export function writePrefilterUniforms(
|
|
|
182
185
|
subPassIdx: number,
|
|
183
186
|
roughness: number,
|
|
184
187
|
mipFaceSize: number,
|
|
188
|
+
sourceMipLevelCount = 1,
|
|
185
189
|
): Result<void, RhiError> {
|
|
186
190
|
if (subPassIdx < 0 || subPassIdx >= PREFILTER_SUBPASS_COUNT) {
|
|
187
191
|
return invalidUniformInput(
|
|
@@ -189,13 +193,20 @@ export function writePrefilterUniforms(
|
|
|
189
193
|
`writePrefilterUniforms received subPassIdx=${subPassIdx}`,
|
|
190
194
|
);
|
|
191
195
|
}
|
|
192
|
-
|
|
196
|
+
if (!Number.isInteger(sourceMipLevelCount) || sourceMipLevelCount < 1) {
|
|
197
|
+
return invalidUniformInput(
|
|
198
|
+
'sourceMipLevelCount is a positive integer matching the bound source cube mip levels',
|
|
199
|
+
`writePrefilterUniforms received sourceMipLevelCount=${sourceMipLevelCount}`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const payload = new Float32Array([roughness, mipFaceSize, sourceMipLevelCount, 0]);
|
|
193
203
|
return device.queue.writeBuffer(buffer, subPassIdx * DYNAMIC_OFFSET_STRIDE, payload);
|
|
194
204
|
}
|
|
195
205
|
|
|
196
206
|
export function writeAllPrefilterUniforms(
|
|
197
207
|
device: FaceUniformsDevice,
|
|
198
208
|
buffer: Buffer,
|
|
209
|
+
sourceMipLevelCount = 1,
|
|
199
210
|
): Result<void, RhiError> {
|
|
200
211
|
for (let mip = 0; mip < PREFILTER_MIP_LEVELS; mip++) {
|
|
201
212
|
const roughness = mip / (PREFILTER_MIP_LEVELS - 1);
|
|
@@ -207,6 +218,7 @@ export function writeAllPrefilterUniforms(
|
|
|
207
218
|
mip * FACE_COUNT + face,
|
|
208
219
|
roughness,
|
|
209
220
|
mipFaceSize,
|
|
221
|
+
sourceMipLevelCount,
|
|
210
222
|
);
|
|
211
223
|
if (!result.ok) return result;
|
|
212
224
|
}
|
|
@@ -303,6 +315,11 @@ export interface TextureGpuEntry {
|
|
|
303
315
|
readonly receipt: TextureResidencyReceipt;
|
|
304
316
|
}
|
|
305
317
|
|
|
318
|
+
/** Candidate-only residency work; no queue submission is hidden here. */
|
|
319
|
+
export interface RecoveryResidencyPreparation {
|
|
320
|
+
readonly mipmapWork?: MipmapEncoderWork;
|
|
321
|
+
}
|
|
322
|
+
|
|
306
323
|
// feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M2 / w11
|
|
307
324
|
// (D-3): the projection-status truth lives here (the store's cubemap map is the
|
|
308
325
|
// single authority — the SkylightSnapshot carries no status, D-3). `status`:
|
|
@@ -529,6 +546,7 @@ export class GpuResidencyCache {
|
|
|
529
546
|
// Hardware-probe caps injected at `configureGpuDevice`; guards the HDR cubemap
|
|
530
547
|
// path (_uploadCubemapFromEquirect) when `rgba16floatRenderable` is false.
|
|
531
548
|
private caps: RhiCaps | undefined = undefined;
|
|
549
|
+
private recoveryColdWorkGuard: RecoveryColdWorkGuard | undefined;
|
|
532
550
|
private deviceEpoch = 0;
|
|
533
551
|
private textureGeneration = 0;
|
|
534
552
|
|
|
@@ -607,6 +625,11 @@ export class GpuResidencyCache {
|
|
|
607
625
|
this.deviceScope = scope;
|
|
608
626
|
}
|
|
609
627
|
|
|
628
|
+
/** Arm the first-published-frame assertion on this generation's store. */
|
|
629
|
+
setRecoveryColdWorkGuard(guard: RecoveryColdWorkGuard | undefined): void {
|
|
630
|
+
this.recoveryColdWorkGuard = guard;
|
|
631
|
+
}
|
|
632
|
+
|
|
610
633
|
/**
|
|
611
634
|
* Wrap a raw RHI Buffer handle into a GpuBuffer. The wrapper holds a
|
|
612
635
|
* reference to the device so `.destroy()` forwards to
|
|
@@ -695,6 +718,39 @@ export class GpuResidencyCache {
|
|
|
695
718
|
this.meshGpuHandles.clear();
|
|
696
719
|
}
|
|
697
720
|
|
|
721
|
+
/** Number of candidate-owned GPU resources currently held by this cache. */
|
|
722
|
+
recoveryResourceCount(): number {
|
|
723
|
+
let cubemapResources = 0;
|
|
724
|
+
const seen = new Set<GpuTexture>();
|
|
725
|
+
for (const entry of this.cubemapGpuHandles.values()) {
|
|
726
|
+
if (entry.texture !== null && !seen.has(entry.texture)) {
|
|
727
|
+
seen.add(entry.texture);
|
|
728
|
+
cubemapResources += 1;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return (
|
|
732
|
+
this.meshGpuHandles.size +
|
|
733
|
+
this.textureGpuHandles.size +
|
|
734
|
+
this.samplerGpuHandles.size +
|
|
735
|
+
cubemapResources
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/** Candidate root for the cache owner, not a scalar readiness marker. */
|
|
740
|
+
createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown> {
|
|
741
|
+
return {
|
|
742
|
+
kind: 'texture',
|
|
743
|
+
create: () => {
|
|
744
|
+
if (!scope.isAlive()) throw new Error('GPU residency candidate scope is not active.');
|
|
745
|
+
if (this.recoveryResourceCount() === 0) {
|
|
746
|
+
throw new Error('GPU residency candidate has no prepared resources.');
|
|
747
|
+
}
|
|
748
|
+
return this;
|
|
749
|
+
},
|
|
750
|
+
cleanup: () => undefined,
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
698
754
|
/**
|
|
699
755
|
* Set the error registry channel (wired by createRenderer). evict /
|
|
700
756
|
* releaseUnreferenced fire structured errors here instead of throwing -
|
|
@@ -970,6 +1026,7 @@ export class GpuResidencyCache {
|
|
|
970
1026
|
const key = this.worldKey(handleSlot(handle), worldId);
|
|
971
1027
|
const existing = this.samplerGpuHandles.get(key);
|
|
972
1028
|
if (existing !== undefined) return ok(existing);
|
|
1029
|
+
this.recoveryColdWorkGuard?.noteUploadColdWork();
|
|
973
1030
|
const device = this.gpuDevice;
|
|
974
1031
|
if (device === undefined) {
|
|
975
1032
|
return err(
|
|
@@ -1102,6 +1159,7 @@ export class GpuResidencyCache {
|
|
|
1102
1159
|
const key = this.worldKey(id, worldId);
|
|
1103
1160
|
const existing = this.meshGpuHandles.get(key);
|
|
1104
1161
|
if (existing !== undefined) return ok(existing);
|
|
1162
|
+
this.recoveryColdWorkGuard?.noteUploadColdWork();
|
|
1105
1163
|
const projected = deriveRenderDataMesh(pod);
|
|
1106
1164
|
if (!projected.ok) return projected;
|
|
1107
1165
|
return this.uploadMeshById(key, pod, projected.value, lodMeshes);
|
|
@@ -1110,6 +1168,7 @@ export class GpuResidencyCache {
|
|
|
1110
1168
|
const texKey = this.worldKey(id, worldId);
|
|
1111
1169
|
const existing = this.textureGpuHandles.get(texKey);
|
|
1112
1170
|
if (existing !== undefined) return ok(existing);
|
|
1171
|
+
this.recoveryColdWorkGuard?.noteUploadColdWork();
|
|
1113
1172
|
const projected = deriveRenderDataTexture(pod);
|
|
1114
1173
|
if (!projected.ok) return projected;
|
|
1115
1174
|
const decoded: DecodedImage = decodedFromTexture(pod);
|
|
@@ -1124,6 +1183,101 @@ export class GpuResidencyCache {
|
|
|
1124
1183
|
}
|
|
1125
1184
|
}
|
|
1126
1185
|
|
|
1186
|
+
/**
|
|
1187
|
+
* Prepare one candidate-visible resource without the normal draw-time mip
|
|
1188
|
+
* submission. Base writes and resource creation stay owned by this cache;
|
|
1189
|
+
* generated mip passes are returned to the recovery transaction for one
|
|
1190
|
+
* explicit finish/submit before publication.
|
|
1191
|
+
*/
|
|
1192
|
+
prepareResidentForRecovery(
|
|
1193
|
+
handle: Handle<'MeshAsset', 'shared'>,
|
|
1194
|
+
pod: TypesMeshAsset,
|
|
1195
|
+
worldId?: World | number,
|
|
1196
|
+
): Result<RecoveryResidencyPreparation, RhiError | AssetError | ImageError>;
|
|
1197
|
+
prepareResidentForRecovery(
|
|
1198
|
+
handle: Handle<'TextureAsset', 'shared'>,
|
|
1199
|
+
pod: TextureAsset,
|
|
1200
|
+
worldId?: World | number,
|
|
1201
|
+
): Result<RecoveryResidencyPreparation, RhiError | AssetError | ImageError>;
|
|
1202
|
+
prepareResidentForRecovery(
|
|
1203
|
+
handle: Handle<'MeshAsset', 'shared'> | Handle<'TextureAsset', 'shared'>,
|
|
1204
|
+
pod: TypesMeshAsset | TextureAsset,
|
|
1205
|
+
worldId: World | number = 0,
|
|
1206
|
+
): Result<RecoveryResidencyPreparation, RhiError | AssetError | ImageError> {
|
|
1207
|
+
const id = handleSlot(handle);
|
|
1208
|
+
if (pod.kind === 'mesh') {
|
|
1209
|
+
const key = this.worldKey(id, worldId);
|
|
1210
|
+
if (this.meshGpuHandles.has(key)) return ok({});
|
|
1211
|
+
const projected = deriveRenderDataMesh(pod);
|
|
1212
|
+
if (!projected.ok) return projected;
|
|
1213
|
+
const uploaded = this.uploadMeshById(key, pod, projected.value);
|
|
1214
|
+
return uploaded.ok ? ok({}) : uploaded;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
const cacheKey = this.worldKey(id, worldId);
|
|
1218
|
+
if (this.textureGpuHandles.has(cacheKey)) return ok({});
|
|
1219
|
+
const projected = deriveRenderDataTexture(pod);
|
|
1220
|
+
if (!projected.ok) return projected;
|
|
1221
|
+
const decoded = decodedFromTexture(pod);
|
|
1222
|
+
const prepared = this.prepareTextureUpload(
|
|
1223
|
+
handle as Handle<'TextureAsset', 'shared'>,
|
|
1224
|
+
pod,
|
|
1225
|
+
decoded,
|
|
1226
|
+
projected.value,
|
|
1227
|
+
);
|
|
1228
|
+
if (!prepared.ok) return prepared;
|
|
1229
|
+
const device = this.gpuDevice;
|
|
1230
|
+
const gpuTexture = prepared.value.gpuTexture;
|
|
1231
|
+
if (device === undefined || gpuTexture === undefined) {
|
|
1232
|
+
return err(
|
|
1233
|
+
new RhiError({
|
|
1234
|
+
code: 'rhi-not-available',
|
|
1235
|
+
expected: 'GpuResidencyCache.configureGpuDevice wired before recovery residency',
|
|
1236
|
+
hint: 'configure the candidate store before preparing recovery resources',
|
|
1237
|
+
}),
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
let mipmapWork: MipmapEncoderWork | undefined;
|
|
1242
|
+
if (!projected.value.compressed && pod.mips.kind === 'generate' && prepared.value.levels > 1) {
|
|
1243
|
+
const preparedMipmaps = prepareMipmaps(device, gpuTexture, {
|
|
1244
|
+
format: projected.value.format,
|
|
1245
|
+
width: pod.shape.extent.width,
|
|
1246
|
+
height: pod.shape.extent.height,
|
|
1247
|
+
levels: prepared.value.levels,
|
|
1248
|
+
});
|
|
1249
|
+
if (!preparedMipmaps.ok) {
|
|
1250
|
+
new GpuTexture(device, gpuTexture).destroy();
|
|
1251
|
+
return preparedMipmaps;
|
|
1252
|
+
}
|
|
1253
|
+
mipmapWork = preparedMipmaps.value;
|
|
1254
|
+
}
|
|
1255
|
+
const viewRes = device.createTextureView(gpuTexture, {
|
|
1256
|
+
label: `texture-view-${cacheKey}`,
|
|
1257
|
+
dimension: projected.value.shape.viewDimension,
|
|
1258
|
+
});
|
|
1259
|
+
if (!viewRes.ok) {
|
|
1260
|
+
new GpuTexture(device, gpuTexture).destroy();
|
|
1261
|
+
return viewRes;
|
|
1262
|
+
}
|
|
1263
|
+
const previous = this.textureGpuHandles.get(cacheKey);
|
|
1264
|
+
const entry: TextureGpuEntry = {
|
|
1265
|
+
texture: this.wrapTex(gpuTexture),
|
|
1266
|
+
view: viewRes.value,
|
|
1267
|
+
receipt: this.textureReceipt(pod, projected.value),
|
|
1268
|
+
};
|
|
1269
|
+
this.textureGpuHandles.set(cacheKey, entry);
|
|
1270
|
+
this.materialResourceEpoch += 1;
|
|
1271
|
+
if (
|
|
1272
|
+
previous !== undefined &&
|
|
1273
|
+
previous.texture !== entry.texture &&
|
|
1274
|
+
!previous.texture.isDestroyed
|
|
1275
|
+
) {
|
|
1276
|
+
previous.texture.destroy();
|
|
1277
|
+
}
|
|
1278
|
+
return ok(mipmapWork === undefined ? {} : { mipmapWork });
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1127
1281
|
/**
|
|
1128
1282
|
* Eager public surface: upload decoded image bytes into a GPU texture and
|
|
1129
1283
|
* cache (texture + view) under the handle. The TextureAsset POD supplies the
|
|
@@ -1618,6 +1772,7 @@ export class GpuResidencyCache {
|
|
|
1618
1772
|
// universally renderable rgba8 path; the same format is threaded through
|
|
1619
1773
|
// the cubemap and all three IBL side textures by IblPipelineCache.
|
|
1620
1774
|
const outputFormat = this.caps?.rgba16floatRenderable === false ? 'rgba8unorm' : tex.format;
|
|
1775
|
+
const sourceMipLevelCount = 1;
|
|
1621
1776
|
|
|
1622
1777
|
// Lazy projection runs from the record arm, where the RHI device is always
|
|
1623
1778
|
// wired (research R-5). The IBL path is deliberately independent from the
|
|
@@ -1643,7 +1798,7 @@ export class GpuResidencyCache {
|
|
|
1643
1798
|
const gpuTextureResult = device.createTexture({
|
|
1644
1799
|
label: `cubemap-${sourceId}`,
|
|
1645
1800
|
size: { width: cubeFaceSize, height: cubeFaceSize, depthOrArrayLayers: 6 },
|
|
1646
|
-
mipLevelCount:
|
|
1801
|
+
mipLevelCount: sourceMipLevelCount,
|
|
1647
1802
|
sampleCount: 1,
|
|
1648
1803
|
dimension: '2d',
|
|
1649
1804
|
format: outputFormat,
|
|
@@ -1682,13 +1837,6 @@ export class GpuResidencyCache {
|
|
|
1682
1837
|
// surfacing exactly one destroy call per physical resource.
|
|
1683
1838
|
const gpuTextureWrapper = this.wrapTex(gpuTexture);
|
|
1684
1839
|
|
|
1685
|
-
this.cubemapGpuHandles.set(sourceId, {
|
|
1686
|
-
status: 'ready',
|
|
1687
|
-
texture: gpuTextureWrapper,
|
|
1688
|
-
view: cubeView,
|
|
1689
|
-
faceViews,
|
|
1690
|
-
});
|
|
1691
|
-
|
|
1692
1840
|
// The minted cubemap handle is a synthetic shared ref (identity token for
|
|
1693
1841
|
// the GPU cube residency); its POD is an EquirectAsset placeholder matching
|
|
1694
1842
|
// the projected dimensions/format (D-3: the retired cube-texture asset kind
|
|
@@ -1701,20 +1849,6 @@ export class GpuResidencyCache {
|
|
|
1701
1849
|
data: tex.data,
|
|
1702
1850
|
colorSpace: 'linear',
|
|
1703
1851
|
};
|
|
1704
|
-
const regResult = registerCube(world, cubeAsset);
|
|
1705
|
-
if (!regResult.ok) return recordFailed(regResult.error);
|
|
1706
|
-
const cubeHandle = regResult.value;
|
|
1707
|
-
const cubeId = handleSlot(cubeHandle);
|
|
1708
|
-
|
|
1709
|
-
this.cubemapGpuHandles.set(cubeId, {
|
|
1710
|
-
status: 'ready',
|
|
1711
|
-
texture: gpuTextureWrapper,
|
|
1712
|
-
view: cubeView,
|
|
1713
|
-
faceViews,
|
|
1714
|
-
});
|
|
1715
|
-
|
|
1716
|
-
this.cubemapIdempotentMap.set(sourceId, cubeHandle);
|
|
1717
|
-
|
|
1718
1852
|
const pipelinesResult = await createIblPipelines(
|
|
1719
1853
|
scope,
|
|
1720
1854
|
device,
|
|
@@ -1748,6 +1882,9 @@ export class GpuResidencyCache {
|
|
|
1748
1882
|
});
|
|
1749
1883
|
if (!equirectTextureResult.ok) return recordFailed(equirectTextureResult.error);
|
|
1750
1884
|
const equirectGpuTex = equirectTextureResult.value;
|
|
1885
|
+
scope._adopt('texture', equirectGpuTex, (texture) => {
|
|
1886
|
+
device.destroyTexture(texture);
|
|
1887
|
+
});
|
|
1751
1888
|
|
|
1752
1889
|
const equirectViewResult = device.createTextureView(equirectGpuTex, {
|
|
1753
1890
|
label: `equirect-view-${sourceId}`,
|
|
@@ -1775,9 +1912,15 @@ export class GpuResidencyCache {
|
|
|
1775
1912
|
const faceBufferResult = createFaceUniformsBuffer(device);
|
|
1776
1913
|
if (!faceBufferResult.ok) return recordFailed(faceBufferResult.error);
|
|
1777
1914
|
const faceBuf = faceBufferResult.value;
|
|
1915
|
+
scope._adopt('buffer', faceBuf, (buffer) => {
|
|
1916
|
+
device.destroyBuffer(buffer);
|
|
1917
|
+
});
|
|
1778
1918
|
const prefilterBufferResult = createPrefilterUniformsBuffer(device);
|
|
1779
1919
|
if (!prefilterBufferResult.ok) return recordFailed(prefilterBufferResult.error);
|
|
1780
1920
|
const prefBuf = prefilterBufferResult.value;
|
|
1921
|
+
scope._adopt('buffer', prefBuf, (buffer) => {
|
|
1922
|
+
device.destroyBuffer(buffer);
|
|
1923
|
+
});
|
|
1781
1924
|
|
|
1782
1925
|
const cubeVertexResult = device.createBuffer({
|
|
1783
1926
|
label: 'ibl-cube-verts',
|
|
@@ -1787,15 +1930,18 @@ export class GpuResidencyCache {
|
|
|
1787
1930
|
});
|
|
1788
1931
|
if (!cubeVertexResult.ok) return recordFailed(cubeVertexResult.error);
|
|
1789
1932
|
const cubeVertex = cubeVertexResult.value;
|
|
1933
|
+
scope._adopt('buffer', cubeVertex, (buffer) => {
|
|
1934
|
+
device.destroyBuffer(buffer);
|
|
1935
|
+
});
|
|
1790
1936
|
|
|
1791
1937
|
const cubeVertexWriteResult = device.queue.writeBuffer(cubeVertex, 0, CUBEMAP_FACE_VERTICES);
|
|
1792
1938
|
if (!cubeVertexWriteResult.ok) return recordFailed(cubeVertexWriteResult.error);
|
|
1793
1939
|
const faceUniformsResult = writeAllFaceUniforms(device, faceBuf);
|
|
1794
1940
|
if (!faceUniformsResult.ok) return recordFailed(faceUniformsResult.error);
|
|
1795
|
-
const prefilterUniformsResult = writeAllPrefilterUniforms(device, prefBuf);
|
|
1941
|
+
const prefilterUniformsResult = writeAllPrefilterUniforms(device, prefBuf, sourceMipLevelCount);
|
|
1796
1942
|
if (!prefilterUniformsResult.ok) return recordFailed(prefilterUniformsResult.error);
|
|
1797
1943
|
|
|
1798
|
-
const runResult = runIblPrecompute({
|
|
1944
|
+
const runResult = await runIblPrecompute({
|
|
1799
1945
|
scope,
|
|
1800
1946
|
device,
|
|
1801
1947
|
equirectGpuTex,
|
|
@@ -1817,6 +1963,28 @@ export class GpuResidencyCache {
|
|
|
1817
1963
|
);
|
|
1818
1964
|
}
|
|
1819
1965
|
|
|
1966
|
+
// A source becomes visible to the record arm only after all four passes
|
|
1967
|
+
// completed their queue fence. Registration and the idempotent map are
|
|
1968
|
+
// publication, not allocation, so a failed precompute cannot expose a
|
|
1969
|
+
// ready cube or a loadable handle.
|
|
1970
|
+
this.cubemapGpuHandles.set(sourceId, {
|
|
1971
|
+
status: 'ready',
|
|
1972
|
+
texture: gpuTextureWrapper,
|
|
1973
|
+
view: cubeView,
|
|
1974
|
+
faceViews,
|
|
1975
|
+
});
|
|
1976
|
+
const regResult = registerCube(world, cubeAsset);
|
|
1977
|
+
if (!regResult.ok) return recordFailed(regResult.error);
|
|
1978
|
+
const cubeHandle = regResult.value;
|
|
1979
|
+
const cubeId = handleSlot(cubeHandle);
|
|
1980
|
+
this.cubemapGpuHandles.set(cubeId, {
|
|
1981
|
+
status: 'ready',
|
|
1982
|
+
texture: gpuTextureWrapper,
|
|
1983
|
+
view: cubeView,
|
|
1984
|
+
faceViews,
|
|
1985
|
+
});
|
|
1986
|
+
this.cubemapIdempotentMap.set(sourceId, cubeHandle);
|
|
1987
|
+
|
|
1820
1988
|
return ok(cubeHandle);
|
|
1821
1989
|
}
|
|
1822
1990
|
|
package/src/environment/frame.ts
CHANGED
|
@@ -12,6 +12,12 @@ import { environmentFactSignatures } from './signature';
|
|
|
12
12
|
|
|
13
13
|
export type { EnvironmentCandidate, EnvironmentFrame, EnvironmentSource, FogCandidate, FogFrame };
|
|
14
14
|
|
|
15
|
+
/** Producer-local fallback while replacement environment storage is uninitialized. */
|
|
16
|
+
export const SKYLIGHT_RECOVERY_FALLBACK = Object.freeze({
|
|
17
|
+
kind: 'skylight',
|
|
18
|
+
active: false,
|
|
19
|
+
} as const);
|
|
20
|
+
|
|
15
21
|
export interface SunCandidate {
|
|
16
22
|
readonly entityKey: number;
|
|
17
23
|
readonly direction: readonly [number, number, number];
|
|
@@ -38,6 +38,14 @@ export interface EnvironmentInspection {
|
|
|
38
38
|
readonly residentBytes: number;
|
|
39
39
|
readonly retiringBytes: number;
|
|
40
40
|
readonly lastCandidateFailure: EnvironmentInspectionFailure | undefined;
|
|
41
|
+
readonly recovery:
|
|
42
|
+
| {
|
|
43
|
+
readonly token: number;
|
|
44
|
+
readonly deviceGeneration: number;
|
|
45
|
+
readonly status: 'uninitialized';
|
|
46
|
+
readonly fallback: 'skylight';
|
|
47
|
+
}
|
|
48
|
+
| undefined;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
export function inspectEnvironment(
|
|
@@ -50,6 +58,7 @@ export function inspectEnvironment(
|
|
|
50
58
|
readonly lkg?: EnvironmentInspectionGeneration;
|
|
51
59
|
readonly candidate?: EnvironmentInspectionGeneration;
|
|
52
60
|
readonly lastCandidateFailure?: EnvironmentInspectionFailure;
|
|
61
|
+
readonly recovery?: EnvironmentInspection['recovery'];
|
|
53
62
|
readonly retiringBytes?: number;
|
|
54
63
|
} = {},
|
|
55
64
|
): EnvironmentInspection {
|
|
@@ -83,5 +92,6 @@ export function inspectEnvironment(
|
|
|
83
92
|
residentBytes: active?.residentBytes ?? 0,
|
|
84
93
|
retiringBytes: details.retiringBytes ?? active?.retiringBytes ?? 0,
|
|
85
94
|
lastCandidateFailure: details.lastCandidateFailure,
|
|
95
|
+
recovery: details.recovery,
|
|
86
96
|
});
|
|
87
97
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
2
|
-
import type { DeviceScope } from '../device/device-scope';
|
|
2
|
+
import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
|
|
3
3
|
import { EnvironmentGenerationFailedError } from '../errors/render';
|
|
4
|
-
import type
|
|
4
|
+
import { type EnvironmentFrame, SKYLIGHT_RECOVERY_FALLBACK } from './frame';
|
|
5
5
|
import type { EnvironmentGeneration, EnvironmentGenerationFailure } from './generation';
|
|
6
6
|
import { sharedEnvironmentFrame } from './graph';
|
|
7
7
|
import {
|
|
@@ -20,8 +20,11 @@ interface RetiredGeneration {
|
|
|
20
20
|
readonly generation: EnvironmentGeneration;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
let nextEnvironmentToken = 1;
|
|
24
|
+
|
|
23
25
|
/** Owns candidate, active, LKG, and DeviceScope generation transitions. */
|
|
24
26
|
export class EnvironmentLifecycle {
|
|
27
|
+
private readonly token = nextEnvironmentToken++;
|
|
25
28
|
private currentScope: DeviceScope;
|
|
26
29
|
private nextGeneration = 0;
|
|
27
30
|
private active: EnvironmentGeneration | undefined;
|
|
@@ -31,6 +34,13 @@ export class EnvironmentLifecycle {
|
|
|
31
34
|
private readonly retired: RetiredGeneration[] = [];
|
|
32
35
|
private readonly leaseCounts = new Map<number, number>();
|
|
33
36
|
private lastCandidateFailure: EnvironmentInspectionFailure | undefined;
|
|
37
|
+
private recovery:
|
|
38
|
+
| {
|
|
39
|
+
readonly deviceGeneration: number;
|
|
40
|
+
readonly frame: EnvironmentFrame;
|
|
41
|
+
readonly lane: 'direct' | 'clustered';
|
|
42
|
+
}
|
|
43
|
+
| undefined;
|
|
34
44
|
|
|
35
45
|
constructor(scope: DeviceScope) {
|
|
36
46
|
this.currentScope = scope;
|
|
@@ -41,6 +51,63 @@ export class EnvironmentLifecycle {
|
|
|
41
51
|
this.currentScope = scope;
|
|
42
52
|
}
|
|
43
53
|
|
|
54
|
+
resetForRecover(deviceGeneration: number): void {
|
|
55
|
+
if (!Number.isSafeInteger(deviceGeneration) || deviceGeneration < 0) {
|
|
56
|
+
throw new RangeError('deviceGeneration must be a non-negative safe integer.');
|
|
57
|
+
}
|
|
58
|
+
const frame = this.active === undefined ? undefined : this.frames.get(this.active.signature);
|
|
59
|
+
if (frame !== undefined) {
|
|
60
|
+
this.recovery = Object.freeze({
|
|
61
|
+
deviceGeneration,
|
|
62
|
+
frame,
|
|
63
|
+
lane: this.active?.lane ?? 'direct',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create an environment owner for a recovery candidate without moving the
|
|
70
|
+
* active owner's scope or generation maps. Only the selected CPU frame and
|
|
71
|
+
* its lane are shared; every candidate generation/frame map starts empty.
|
|
72
|
+
*/
|
|
73
|
+
createRecoveryCandidate(scope: DeviceScope): EnvironmentLifecycle {
|
|
74
|
+
const candidate = new EnvironmentLifecycle(scope);
|
|
75
|
+
const activeFrame =
|
|
76
|
+
this.recovery?.frame ??
|
|
77
|
+
(this.active === undefined ? undefined : this.frames.get(this.active.signature));
|
|
78
|
+
if (activeFrame !== undefined) {
|
|
79
|
+
candidate.recovery = Object.freeze({
|
|
80
|
+
deviceGeneration: scope.generation,
|
|
81
|
+
frame: activeFrame,
|
|
82
|
+
lane: this.recovery?.lane ?? this.active?.lane ?? 'direct',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
candidate.lastCandidateFailure = this.lastCandidateFailure;
|
|
86
|
+
return candidate;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** True when a CPU environment frame is available for candidate recovery. */
|
|
90
|
+
hasRecoveryFrame(): boolean {
|
|
91
|
+
return (
|
|
92
|
+
this.recovery?.frame !== undefined ||
|
|
93
|
+
(this.active !== undefined && this.frames.has(this.active.signature))
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown> {
|
|
98
|
+
return {
|
|
99
|
+
kind: 'texture',
|
|
100
|
+
create: async () => {
|
|
101
|
+
const recovered = await this.recover(scope);
|
|
102
|
+
if (!recovered.ok) throw recovered.error;
|
|
103
|
+
return recovered.value;
|
|
104
|
+
},
|
|
105
|
+
cleanup: (value) => {
|
|
106
|
+
this.discard(value as EnvironmentGeneration);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
44
111
|
isActive(generation: EnvironmentGeneration): boolean {
|
|
45
112
|
return (
|
|
46
113
|
this.active?.generation === generation.generation &&
|
|
@@ -56,6 +123,14 @@ export class EnvironmentLifecycle {
|
|
|
56
123
|
this.frames.delete(generation.signature);
|
|
57
124
|
generation.retired = true;
|
|
58
125
|
generation.scope.retire();
|
|
126
|
+
if (this.recovery?.frame.signature === generation.signature) this.recovery = undefined;
|
|
127
|
+
if (this.candidates.size === 0) this.recovery = undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Drop every uncommitted generation owned by an abandoned candidate. */
|
|
131
|
+
discardRecoveryCandidates(): void {
|
|
132
|
+
for (const generation of [...this.candidates.values()]) this.discard(generation);
|
|
133
|
+
this.recovery = undefined;
|
|
59
134
|
}
|
|
60
135
|
|
|
61
136
|
recordCandidateFailure(error: EnvironmentGenerationFailedError): void {
|
|
@@ -157,6 +232,28 @@ export class EnvironmentLifecycle {
|
|
|
157
232
|
}
|
|
158
233
|
this.active = generation;
|
|
159
234
|
this.lkg = generation;
|
|
235
|
+
this.recovery = undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Close the old active generation before a replacement owner is installed. */
|
|
239
|
+
retireActiveForReplacement(): void {
|
|
240
|
+
const active = this.active;
|
|
241
|
+
if (active === undefined) return;
|
|
242
|
+
this.active = undefined;
|
|
243
|
+
if (this.lkg === active) this.lkg = undefined;
|
|
244
|
+
active.retired = true;
|
|
245
|
+
active.scope.retire();
|
|
246
|
+
this.recovery = undefined;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Close an already-published generation when its owning scope retires. */
|
|
250
|
+
retirePublishedGeneration(generation: EnvironmentGeneration): void {
|
|
251
|
+
if (this.active !== generation) return;
|
|
252
|
+
this.active = undefined;
|
|
253
|
+
if (this.lkg === generation) this.lkg = undefined;
|
|
254
|
+
generation.retired = true;
|
|
255
|
+
generation.scope.retire();
|
|
256
|
+
this.recovery = undefined;
|
|
160
257
|
}
|
|
161
258
|
|
|
162
259
|
acquire(): EnvironmentLease {
|
|
@@ -237,24 +334,33 @@ export class EnvironmentLifecycle {
|
|
|
237
334
|
...(this.lastCandidateFailure === undefined
|
|
238
335
|
? {}
|
|
239
336
|
: { lastCandidateFailure: this.lastCandidateFailure }),
|
|
337
|
+
...(this.recovery === undefined
|
|
338
|
+
? {}
|
|
339
|
+
: {
|
|
340
|
+
recovery: {
|
|
341
|
+
token: this.token,
|
|
342
|
+
deviceGeneration: this.recovery.deviceGeneration,
|
|
343
|
+
status: 'uninitialized' as const,
|
|
344
|
+
fallback: SKYLIGHT_RECOVERY_FALLBACK.kind,
|
|
345
|
+
},
|
|
346
|
+
}),
|
|
240
347
|
});
|
|
241
348
|
}
|
|
242
349
|
|
|
243
350
|
async recover(
|
|
244
351
|
scope: DeviceScope,
|
|
245
352
|
): Promise<Result<EnvironmentGeneration, EnvironmentGenerationFailedError>> {
|
|
246
|
-
if (this.active === undefined)
|
|
247
|
-
return err(new EnvironmentGenerationFailedError('none', 'prepare'));
|
|
248
353
|
this.currentScope = scope;
|
|
249
354
|
const previous = this.active;
|
|
250
|
-
|
|
251
|
-
|
|
355
|
+
const previousFrame =
|
|
356
|
+
this.recovery?.frame ??
|
|
357
|
+
(previous === undefined ? undefined : this.frames.get(previous.signature));
|
|
252
358
|
if (previousFrame === undefined) {
|
|
253
359
|
return err(new EnvironmentGenerationFailedError('none', 'prepare'));
|
|
254
360
|
}
|
|
255
361
|
const result = await this.ensure(
|
|
256
|
-
sharedEnvironmentFrame(previousFrame, previous
|
|
257
|
-
|
|
362
|
+
sharedEnvironmentFrame(previousFrame, previous?.lane ?? 'direct'),
|
|
363
|
+
this.recovery?.lane ?? previous?.lane ?? 'direct',
|
|
258
364
|
);
|
|
259
365
|
if (!result.ok) return result;
|
|
260
366
|
return ok(result.value);
|