@forgeax/engine-render 0.1.21 → 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 +281 -38
- package/dist/assembly/backend-contract.d.ts +13 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -1
- 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 -235
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +10 -0
- package/dist/assembly/host-contract.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-PCHBPYML.mjs → chunk-GE3SDD72.mjs} +37 -5
- package/dist/chunk-GE3SDD72.mjs.map +1 -0
- package/dist/chunk-KZFTK6DO.mjs +1191 -0
- package/dist/chunk-KZFTK6DO.mjs.map +1 -0
- package/dist/{chunk-FS2KNUQH.mjs → chunk-Q752SHI5.mjs} +332 -568
- package/dist/chunk-Q752SHI5.mjs.map +1 -0
- package/dist/{chunk-6OPXDQ2E.mjs → chunk-RR5L4SRS.mjs} +33 -294
- package/dist/chunk-RR5L4SRS.mjs.map +1 -0
- package/dist/chunk-TFH5I7JJ.mjs +296 -0
- package/dist/chunk-TFH5I7JJ.mjs.map +1 -0
- package/dist/chunk-UQYS7FRM.mjs +1003 -0
- package/dist/chunk-UQYS7FRM.mjs.map +1 -0
- package/dist/chunk-YKB5DQV7.mjs +345 -0
- package/dist/chunk-YKB5DQV7.mjs.map +1 -0
- package/dist/{chunk-5YADUCA7.mjs → chunk-YRUYGA4A.mjs} +1189 -1217
- package/dist/chunk-YRUYGA4A.mjs.map +1 -0
- package/dist/chunk-Z3YYH2MB.mjs +5082 -0
- package/dist/chunk-Z3YYH2MB.mjs.map +1 -0
- package/dist/cluster-binner.d.ts +5 -0
- package/dist/cluster-binner.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts +4 -0
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/light-probe.d.ts +9 -0
- package/dist/components/light-probe.d.ts.map +1 -0
- package/dist/components/rect-area-light.d.ts +12 -0
- package/dist/components/rect-area-light.d.ts.map +1 -0
- package/dist/components/scene-instance.d.ts +4 -0
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/spot-light.d.ts +3 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +27276 -23232
- 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 +32 -5
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/lifecycle-transaction.d.ts +2 -0
- package/dist/device/lifecycle-transaction.d.ts.map +1 -0
- 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/gpu-driven.d.ts +26 -0
- package/dist/errors/gpu-driven.d.ts.map +1 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +67 -3
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +39 -1
- 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/extract/gpu-driven.d.ts +50 -0
- package/dist/extract/gpu-driven.d.ts.map +1 -0
- package/dist/extract/visibility.d.ts.map +1 -1
- 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/batch-topology.d.ts +34 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +63 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +126 -2
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +64 -1
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +14 -0
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +9 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +0 -1
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +3 -3
- package/dist/hdrp-buffers.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 +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +90 -5
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +163 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/internal.d.ts +9 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +23 -2
- package/dist/internal.mjs.map +1 -1
- package/dist/light-buffer-layout.d.ts +60 -148
- 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 +58 -21
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -0
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +1 -1
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/plugin.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/prepare/extended-lighting/ltc-resources.d.ts +31 -0
- package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/resources.d.ts +72 -0
- package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/state.d.ts +24 -0
- package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +8 -6
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +55 -10
- 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 +21 -5
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-timing.d.ts +19 -7
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/helpers.d.ts +0 -20
- package/dist/record/helpers.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +20 -2
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +10 -18
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.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 +82 -9
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +49 -2
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +1 -2
- package/dist/record/view-ubo.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/reflection/inspection.d.ts +10 -0
- package/dist/reflection/inspection.d.ts.map +1 -0
- package/dist/reflection/projection.d.ts +49 -0
- package/dist/reflection/projection.d.ts.map +1 -1
- package/dist/reflection/record-owner.d.ts +60 -0
- package/dist/reflection/record-owner.d.ts.map +1 -0
- package/dist/render-contract.d.ts +78 -15
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +7 -0
- package/dist/render-pipeline.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 +252 -30
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +140 -5
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/probe-blend-record.d.ts +22 -0
- package/dist/scene/probe-blend-record.d.ts.map +1 -0
- package/dist/scene/probe-blend.d.ts +142 -0
- package/dist/scene/probe-blend.d.ts.map +1 -0
- package/dist/scene/render-scene-types.d.ts +0 -6
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +122 -9
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/visibility/bounds.d.ts +20 -0
- package/dist/scene/visibility/bounds.d.ts.map +1 -0
- package/dist/scene/visibility/budget.d.ts +16 -0
- package/dist/scene/visibility/budget.d.ts.map +1 -0
- package/dist/scene/visibility/facet.d.ts +73 -0
- package/dist/scene/visibility/facet.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-lod.d.ts +46 -0
- package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
- package/dist/scene/visibility/inspection.d.ts +132 -0
- package/dist/scene/visibility/inspection.d.ts.map +1 -0
- package/dist/scene/visibility/lod-selector.d.ts +25 -0
- package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
- package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
- package/dist/scene/visibility/types.d.ts +34 -0
- package/dist/scene/visibility/types.d.ts.map +1 -0
- package/dist/ssr/admission.d.ts +122 -0
- package/dist/ssr/admission.d.ts.map +1 -0
- package/dist/ssr/composition.d.ts +32 -0
- package/dist/ssr/composition.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +39 -0
- package/dist/ssr/errors.d.ts.map +1 -0
- package/dist/ssr/identity.d.ts +13 -0
- package/dist/ssr/identity.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +1 -1
- package/dist/temporal/frame-coordinator.d.ts +13 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +2 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -5
- 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/dist/temporal/view.d.ts +7 -0
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/tilemap-chunk-extract-system.d.ts +1 -1
- package/dist/typed-render-graph-primitives.d.ts +4 -0
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +8 -4
- package/dist/volume/passes.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
- package/src/__tests__/detached-inspection.integration.test.ts +6 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +19 -1
- 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__/direct-light-snapshot.unit.test.ts +59 -3
- package/src/__tests__/environment-generation.integration.test.ts +39 -0
- package/src/__tests__/evidence-identity.ts +13 -0
- package/src/__tests__/evidence-identity.unit.test.ts +24 -0
- package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
- package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
- package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
- package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
- package/src/__tests__/factory-contract.integration.test.ts +154 -6
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
- package/src/__tests__/frame-observation.contract.test.ts +15 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
- package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
- package/src/__tests__/gpu-lod-evidence.ts +188 -0
- package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
- package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
- package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
- package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
- package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
- package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
- package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
- package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
- package/src/__tests__/gpu-scene.unit.test.ts +49 -3
- 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__/light-buffer-layout.unit.test.ts +113 -3
- package/src/__tests__/light-inspection.unit.test.ts +55 -0
- package/src/__tests__/light-resource-error.unit.test.ts +43 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
- package/src/__tests__/lod-bounds.unit.test.ts +42 -0
- package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
- package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
- package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
- package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
- package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
- package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
- package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
- package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
- package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
- package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
- package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
- package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
- package/src/__tests__/lod-selector.profile.test.ts +35 -0
- package/src/__tests__/lod-selector.unit.test.ts +68 -0
- package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
- package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
- package/src/__tests__/ltc-resources.unit.test.ts +32 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +41 -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__/occlusion-confidence.unit.test.ts +168 -0
- package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
- package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
- package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
- package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
- package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
- package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
- package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
- package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
- package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
- package/src/__tests__/occlusion-query.evidence.ts +178 -0
- package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
- package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
- package/src/__tests__/probe-bindings.unit.test.ts +200 -0
- package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
- package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
- package/src/__tests__/probe-capacity.integration.test.ts +97 -0
- package/src/__tests__/probe-recovery.integration.test.ts +104 -0
- package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
- package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
- package/src/__tests__/profiler-phase-token.test.ts +1 -0
- 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__/rect-area-boundary.unit.test.ts +24 -0
- package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
- package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
- package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
- package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
- package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
- package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
- package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
- package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
- package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +160 -17
- 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 +22 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
- package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
- package/src/__tests__/ssr-composition.unit.test.ts +46 -0
- package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
- package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
- package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
- package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
- package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
- package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
- package/src/__tests__/temporal-view.unit.test.ts +3 -3
- package/src/__tests__/transmission-prewarm.unit.test.ts +111 -32
- package/src/__tests__/visibility-budget.integration.test.ts +140 -0
- package/src/__tests__/visibility-budget.unit.test.ts +70 -0
- package/src/__tests__/visibility-extract.integration.test.ts +11 -6
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
- package/src/assembly/backend-contract.ts +13 -0
- package/src/assembly/bundler-contract.ts +9 -0
- package/src/assembly/device-feature-admission.ts +26 -9
- package/src/assembly/extended-lighting-fallback.ts +216 -0
- package/src/assembly/factory.ts +81 -9634
- package/src/assembly/host-contract.ts +10 -0
- 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/cluster-binner.ts +31 -0
- package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
- package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
- package/src/components/camera.ts +1 -1
- package/src/components/index.ts +3 -0
- package/src/components/light-helpers.ts +52 -1
- package/src/components/light-probe.ts +13 -0
- package/src/components/rect-area-light.ts +15 -0
- package/src/components/scene-instance.ts +6 -0
- package/src/components/spot-light.ts +3 -0
- package/src/debug-draw-glue.ts +1 -1
- package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
- package/src/device/device-scope.ts +35 -6
- package/src/device/gpu-residency.ts +334 -35
- package/src/device/lifecycle-transaction.ts +12 -0
- package/src/environment/frame.ts +6 -0
- package/src/environment/inspection.ts +10 -0
- package/src/environment/lifecycle.ts +122 -9
- package/src/errors/gpu-driven.ts +92 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/recover.ts +108 -5
- package/src/errors/render.ts +72 -1
- package/src/extract/camera.ts +3 -3
- package/src/extract/contracts.ts +974 -0
- package/src/extract/gpu-driven.ts +108 -0
- package/src/extract/visibility.ts +0 -23
- 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/batch-topology.ts +206 -28
- package/src/gpu-driven/prepared-draw.ts +208 -0
- package/src/gpu-driven/production-raster.ts +1046 -39
- package/src/gpu-driven/view-gpu.ts +411 -29
- package/src/gpu-scene-schema.ts +13 -0
- package/src/gpu-scene.ts +137 -67
- package/src/gpu-usage.ts +0 -1
- package/src/hdrp-buffers.ts +6 -6
- package/src/ibl/IblPipelineCache.ts +134 -50
- package/src/ibl/skylight-bind-group.ts +26 -0
- package/src/index.ts +83 -1
- package/src/inspection-types.ts +220 -1
- package/src/internal.ts +38 -0
- package/src/light-buffer-layout.ts +157 -324
- package/src/materials.ts +278 -47
- package/src/mesh-material-bindings.ts +59 -0
- package/src/occlusion-proxy.ts +64 -0
- package/src/pbr-pipeline.ts +290 -60
- package/src/pipeline/standard-forward-lane.ts +47 -0
- package/src/pipeline/standard-lighting/graph.ts +2 -1
- package/src/pipeline/standard-lighting/light-frame.ts +3 -2
- package/src/pipeline/standard-lighting/topology.ts +1 -1
- package/src/pipeline/standard-lighting/transport.ts +2 -1
- package/src/pipeline/standard-pipeline.ts +2 -0
- package/src/pipeline-builder.ts +5 -2
- package/src/pipeline-spec.ts +12 -1
- package/src/plugin.ts +4 -0
- 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/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
- package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
- package/src/prepare/extended-lighting/resources.ts +389 -0
- package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
- package/src/prepare/extended-lighting/state.ts +92 -0
- package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
- package/src/record/__tests__/gpu-timing.unit.test.ts +177 -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 +29 -28
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +21 -2
- package/src/record/frame-lighting.ts +252 -53
- package/src/record/frame-snapshot.ts +72 -12
- package/src/record/frame-targets.ts +11 -1
- package/src/record/frame.ts +416 -95
- package/src/record/gpu-timing.ts +108 -44
- package/src/record/helpers.ts +1 -59
- package/src/record/main-pass-geometry.ts +434 -44
- package/src/record/main-pass-material.ts +324 -210
- package/src/record/main-pass-sprite-draws.ts +58 -92
- package/src/record/main-pass.ts +94 -6
- package/src/record/mesh-ssbo.ts +79 -46
- package/src/record/recovery-pipeline.ts +695 -0
- package/src/record/render-context.ts +99 -7
- package/src/record/shadow-pass.ts +66 -40
- package/src/record/typed-frame-graph.ts +270 -84
- package/src/record/view-ubo.ts +17 -27
- package/src/recovery/render-system-candidate.ts +839 -0
- package/src/reflection/inspection.ts +67 -0
- package/src/reflection/projection.ts +196 -1
- package/src/reflection/record-owner.ts +1283 -0
- package/src/render-contract.ts +82 -12
- package/src/render-pipeline.ts +5 -0
- package/src/render-system-extract-tail.ts +2983 -0
- package/src/render-system-extract.ts +290 -2784
- package/src/render-system.ts +1597 -1192
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
- package/src/scene/probe-blend-record.ts +54 -0
- package/src/scene/probe-blend.ts +813 -0
- package/src/scene/render-scene-types.ts +0 -7
- package/src/scene/render-scene.ts +972 -184
- package/src/scene/visibility/bounds.ts +57 -0
- package/src/scene/visibility/budget.ts +40 -0
- package/src/scene/visibility/facet.ts +400 -0
- package/src/scene/visibility/gpu-frame-timing.ts +101 -0
- package/src/scene/visibility/gpu-lod.ts +119 -0
- package/src/scene/visibility/inspection.ts +313 -0
- package/src/scene/visibility/lod-selector.ts +84 -0
- package/src/scene/visibility/occlusion-confidence.ts +478 -0
- package/src/scene/visibility/occlusion-pass.ts +165 -0
- package/src/scene/visibility/occlusion-query-pool.ts +178 -0
- package/src/scene/visibility/occlusion-runtime.ts +672 -0
- package/src/scene/visibility/types.ts +50 -0
- package/src/ssr/admission.ts +451 -0
- package/src/ssr/composition.ts +73 -0
- package/src/ssr/errors.ts +76 -0
- package/src/ssr/identity.ts +12 -0
- package/src/systems/skin-palette-allocator.ts +1 -1
- package/src/temporal/frame-coordinator.ts +30 -1
- package/src/temporal/frame.ts +14 -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/temporal/view.ts +11 -0
- package/src/tilemap-chunk-extract-system.ts +5 -5
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
- package/src/typed-render-graph-primitives.ts +48 -0
- package/src/volume/passes.ts +46 -49
- package/dist/.tsbuildinfo +0 -1
- package/dist/chunk-5YADUCA7.mjs.map +0 -1
- package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
- package/dist/chunk-BXBQQKFQ.mjs +0 -67
- package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
- package/dist/chunk-FS2KNUQH.mjs.map +0 -1
- package/dist/chunk-PCHBPYML.mjs.map +0 -1
- package/dist/chunk-VFPLTKCU.mjs +0 -192
- package/dist/chunk-VFPLTKCU.mjs.map +0 -1
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
import { RenderGraphBuilder, type RenderGraphFrame } from '@forgeax/engine-render-graph';
|
|
2
|
+
import {
|
|
3
|
+
type Buffer,
|
|
4
|
+
err,
|
|
5
|
+
type MappedBuffer,
|
|
6
|
+
type RhiAdapter,
|
|
7
|
+
type RhiCanvasContext,
|
|
8
|
+
type RhiDevice,
|
|
9
|
+
RhiError,
|
|
10
|
+
type Texture,
|
|
11
|
+
type TextureView,
|
|
12
|
+
} from '@forgeax/engine-rhi';
|
|
13
|
+
import { ok, type Result } from '@forgeax/engine-types';
|
|
14
|
+
import type { RhiBackendPack } from '../backend-contract';
|
|
15
|
+
import {
|
|
16
|
+
RECOVERY_ADAPTER_DEADLINE_MS,
|
|
17
|
+
RECOVERY_DEVICE_DEADLINE_MS,
|
|
18
|
+
type RecoveryContinuation,
|
|
19
|
+
type RecoveryDeadline,
|
|
20
|
+
type RecoveryPhase,
|
|
21
|
+
} from '../renderer-lifecycle';
|
|
22
|
+
|
|
23
|
+
const COMPRESSION_FEATURES: GPUFeatureName[] = [
|
|
24
|
+
'texture-compression-bc',
|
|
25
|
+
'texture-compression-etc2',
|
|
26
|
+
'texture-compression-astc',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export const STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES = 17;
|
|
30
|
+
|
|
31
|
+
const PROBE_TEXTURE_USAGE_COPY_SRC = 0x01;
|
|
32
|
+
const PROBE_TEXTURE_USAGE_RENDER_ATTACHMENT = 0x10;
|
|
33
|
+
const PROBE_BUFFER_USAGE_MAP_READ = 0x01;
|
|
34
|
+
const PROBE_BUFFER_USAGE_COPY_DST = 0x08;
|
|
35
|
+
const PROBE_READBACK_BYTES_PER_ROW = 256;
|
|
36
|
+
const PROBE_CLEAR: readonly [number, number, number, number] = [0.125, 0.25, 0.5, 1];
|
|
37
|
+
type ProbeFormat = 'rgba16float' | 'rgba8unorm';
|
|
38
|
+
|
|
39
|
+
function halfToFloat(bits: number): number {
|
|
40
|
+
const sign = (bits & 0x8000) === 0 ? 1 : -1;
|
|
41
|
+
const exponent = (bits & 0x7c00) >> 10;
|
|
42
|
+
const fraction = bits & 0x03ff;
|
|
43
|
+
if (exponent === 0) return sign * 2 ** -14 * (fraction / 1024);
|
|
44
|
+
if (exponent === 0x1f) return fraction === 0 ? sign * Number.POSITIVE_INFINITY : Number.NaN;
|
|
45
|
+
return sign * 2 ** (exponent - 15) * (1 + fraction / 1024);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function recoveryDeviceProbeFailure(expected: string, hint: string, cause?: unknown): RhiError {
|
|
49
|
+
const inner =
|
|
50
|
+
cause instanceof RhiError
|
|
51
|
+
? cause
|
|
52
|
+
: {
|
|
53
|
+
code: 'recovery-device-probe-failed',
|
|
54
|
+
message: cause instanceof Error ? cause.message : String(cause ?? hint),
|
|
55
|
+
};
|
|
56
|
+
return new RhiError({
|
|
57
|
+
code: 'webgpu-runtime-error',
|
|
58
|
+
expected,
|
|
59
|
+
hint,
|
|
60
|
+
detail: { error: inner },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Prove that a freshly acquired candidate device can execute and expose one
|
|
66
|
+
* render attachment before recovery publishes it. Device creation and a
|
|
67
|
+
* successful queue.submit are not sufficient evidence: a replacement device
|
|
68
|
+
* can accept those operations while its first observable frame remains
|
|
69
|
+
* empty. This probe intentionally has no shader, asset, graph, or surface
|
|
70
|
+
* dependency, so its result cleanly separates backend readiness from renderer
|
|
71
|
+
* graph publication.
|
|
72
|
+
*
|
|
73
|
+
* The null backend is structural-only and deliberately has no CPU readback
|
|
74
|
+
* implementation; its contract is already exercised by command-shape tests.
|
|
75
|
+
*/
|
|
76
|
+
export async function probeCandidateDeviceExecution(
|
|
77
|
+
device: RhiDevice,
|
|
78
|
+
): Promise<import('@forgeax/engine-types').Result<void, RhiError>> {
|
|
79
|
+
if (device.caps.backendKind === 'null') return ok(undefined);
|
|
80
|
+
|
|
81
|
+
const format = device.caps.rgba16floatRenderable ? 'rgba16float' : 'rgba8unorm';
|
|
82
|
+
let texture: Texture | undefined;
|
|
83
|
+
let readback: Buffer | undefined;
|
|
84
|
+
let mapped: MappedBuffer | undefined;
|
|
85
|
+
let outcome: import('@forgeax/engine-types').Result<void, RhiError> = err(
|
|
86
|
+
recoveryDeviceProbeFailure(
|
|
87
|
+
'candidate device probe completes with a mapped pixel',
|
|
88
|
+
'discard the candidate and keep the renderer in device-lost until a usable device is acquired',
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
try {
|
|
92
|
+
const textureResult = device.createTexture({
|
|
93
|
+
label: 'renderer-recovery-device-probe.texture',
|
|
94
|
+
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
|
|
95
|
+
mipLevelCount: 1,
|
|
96
|
+
sampleCount: 1,
|
|
97
|
+
dimension: '2d',
|
|
98
|
+
format,
|
|
99
|
+
usage: PROBE_TEXTURE_USAGE_RENDER_ATTACHMENT | PROBE_TEXTURE_USAGE_COPY_SRC,
|
|
100
|
+
viewFormats: undefined,
|
|
101
|
+
textureBindingViewDimension: undefined,
|
|
102
|
+
});
|
|
103
|
+
if (!textureResult.ok) {
|
|
104
|
+
outcome = err(
|
|
105
|
+
recoveryDeviceProbeFailure(
|
|
106
|
+
'candidate device creates a 1x1 probe texture',
|
|
107
|
+
'inspect the candidate device format/resource capability before publishing recovery',
|
|
108
|
+
textureResult.error,
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
return outcome;
|
|
112
|
+
}
|
|
113
|
+
texture = textureResult.value;
|
|
114
|
+
|
|
115
|
+
const viewResult = device.createTextureView(texture, {
|
|
116
|
+
label: 'renderer-recovery-device-probe.view',
|
|
117
|
+
dimension: '2d',
|
|
118
|
+
});
|
|
119
|
+
if (!viewResult.ok) {
|
|
120
|
+
outcome = err(
|
|
121
|
+
recoveryDeviceProbeFailure(
|
|
122
|
+
'candidate device creates a view for the probe texture',
|
|
123
|
+
'inspect candidate texture-view creation before publishing recovery',
|
|
124
|
+
viewResult.error,
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
return outcome;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const readbackResult = device.createBuffer({
|
|
131
|
+
label: 'renderer-recovery-device-probe.readback',
|
|
132
|
+
size: PROBE_READBACK_BYTES_PER_ROW,
|
|
133
|
+
usage: PROBE_BUFFER_USAGE_MAP_READ | PROBE_BUFFER_USAGE_COPY_DST,
|
|
134
|
+
mappedAtCreation: false,
|
|
135
|
+
});
|
|
136
|
+
if (!readbackResult.ok) {
|
|
137
|
+
outcome = err(
|
|
138
|
+
recoveryDeviceProbeFailure(
|
|
139
|
+
'candidate device creates a mapped readback buffer',
|
|
140
|
+
'inspect candidate buffer creation before publishing recovery',
|
|
141
|
+
readbackResult.error,
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
return outcome;
|
|
145
|
+
}
|
|
146
|
+
readback = readbackResult.value;
|
|
147
|
+
|
|
148
|
+
const encoderResult = device.createCommandEncoder({
|
|
149
|
+
label: 'renderer-recovery-device-probe.encoder',
|
|
150
|
+
});
|
|
151
|
+
if (!encoderResult.ok) {
|
|
152
|
+
outcome = err(
|
|
153
|
+
recoveryDeviceProbeFailure(
|
|
154
|
+
'candidate device creates a probe command encoder',
|
|
155
|
+
'inspect candidate command encoding before publishing recovery',
|
|
156
|
+
encoderResult.error,
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
return outcome;
|
|
160
|
+
}
|
|
161
|
+
const encoder = encoderResult.value;
|
|
162
|
+
const pass = encoder.beginRenderPass({
|
|
163
|
+
label: 'renderer-recovery-device-probe.pass',
|
|
164
|
+
colorAttachments: [
|
|
165
|
+
{
|
|
166
|
+
view: viewResult.value,
|
|
167
|
+
loadOp: 'clear',
|
|
168
|
+
storeOp: 'store',
|
|
169
|
+
clearValue: {
|
|
170
|
+
r: PROBE_CLEAR[0],
|
|
171
|
+
g: PROBE_CLEAR[1],
|
|
172
|
+
b: PROBE_CLEAR[2],
|
|
173
|
+
a: PROBE_CLEAR[3],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
});
|
|
178
|
+
pass.end();
|
|
179
|
+
encoder.copyTextureToBuffer(
|
|
180
|
+
{ texture },
|
|
181
|
+
{
|
|
182
|
+
buffer: readback,
|
|
183
|
+
bytesPerRow: PROBE_READBACK_BYTES_PER_ROW,
|
|
184
|
+
rowsPerImage: 1,
|
|
185
|
+
},
|
|
186
|
+
{ width: 1, height: 1, depthOrArrayLayers: 1 },
|
|
187
|
+
);
|
|
188
|
+
const commandResult = encoder.finish();
|
|
189
|
+
if (!commandResult.ok) {
|
|
190
|
+
outcome = err(
|
|
191
|
+
recoveryDeviceProbeFailure(
|
|
192
|
+
'candidate probe encoder finishes after render and copy commands',
|
|
193
|
+
'inspect candidate command encoding before publishing recovery',
|
|
194
|
+
commandResult.error,
|
|
195
|
+
),
|
|
196
|
+
);
|
|
197
|
+
return outcome;
|
|
198
|
+
}
|
|
199
|
+
const submitted = device.queue.submit([commandResult.value]);
|
|
200
|
+
if (!submitted.ok) {
|
|
201
|
+
outcome = err(
|
|
202
|
+
recoveryDeviceProbeFailure(
|
|
203
|
+
'candidate probe command buffer submits successfully',
|
|
204
|
+
'inspect candidate queue submission before publishing recovery',
|
|
205
|
+
submitted.error,
|
|
206
|
+
),
|
|
207
|
+
);
|
|
208
|
+
return outcome;
|
|
209
|
+
}
|
|
210
|
+
await device.queue.onSubmittedWorkDone();
|
|
211
|
+
const mappedResult = await readback.mapAsync(PROBE_BUFFER_USAGE_MAP_READ);
|
|
212
|
+
if (!mappedResult.ok) {
|
|
213
|
+
outcome = err(
|
|
214
|
+
recoveryDeviceProbeFailure(
|
|
215
|
+
'candidate probe readback maps after queue completion',
|
|
216
|
+
'inspect candidate queue completion and readback mapping before publishing recovery',
|
|
217
|
+
mappedResult.error,
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
return outcome;
|
|
221
|
+
}
|
|
222
|
+
mapped = mappedResult.value;
|
|
223
|
+
const rangeResult = mapped.getMappedRange(0, format === 'rgba16float' ? 8 : 4);
|
|
224
|
+
if (!rangeResult.ok) {
|
|
225
|
+
outcome = err(
|
|
226
|
+
recoveryDeviceProbeFailure(
|
|
227
|
+
'candidate probe readback exposes its mapped pixel',
|
|
228
|
+
'inspect candidate mapped-buffer state before publishing recovery',
|
|
229
|
+
rangeResult.error,
|
|
230
|
+
),
|
|
231
|
+
);
|
|
232
|
+
return outcome;
|
|
233
|
+
}
|
|
234
|
+
const view = new DataView(rangeResult.value);
|
|
235
|
+
const actual =
|
|
236
|
+
format === 'rgba16float'
|
|
237
|
+
? [0, 1, 2, 3].map((channel) => halfToFloat(view.getUint16(channel * 2, true)))
|
|
238
|
+
: [0, 1, 2, 3].map((channel) => (view.getUint8(channel) ?? 0) / 255);
|
|
239
|
+
const epsilon = format === 'rgba16float' ? 0.02 : 2 / 255;
|
|
240
|
+
const matches = actual.every(
|
|
241
|
+
(value, index) => Math.abs(value - (PROBE_CLEAR[index] ?? 0)) <= epsilon,
|
|
242
|
+
);
|
|
243
|
+
if (!matches) {
|
|
244
|
+
outcome = err(
|
|
245
|
+
recoveryDeviceProbeFailure(
|
|
246
|
+
'candidate probe readback preserves the cleared pixel',
|
|
247
|
+
`candidate device returned ${actual.map((value) => value.toFixed(4)).join(',')}; discard it instead of publishing alive`,
|
|
248
|
+
),
|
|
249
|
+
);
|
|
250
|
+
return outcome;
|
|
251
|
+
}
|
|
252
|
+
outcome = ok(undefined);
|
|
253
|
+
} catch (cause) {
|
|
254
|
+
outcome = err(
|
|
255
|
+
recoveryDeviceProbeFailure(
|
|
256
|
+
'candidate device executes the minimal render/readback probe',
|
|
257
|
+
`discard the candidate after the execution probe raised: ${String(cause)}`,
|
|
258
|
+
cause,
|
|
259
|
+
),
|
|
260
|
+
);
|
|
261
|
+
} finally {
|
|
262
|
+
try {
|
|
263
|
+
mapped?.unmap();
|
|
264
|
+
} catch (cause) {
|
|
265
|
+
if (outcome.ok) {
|
|
266
|
+
outcome = err(
|
|
267
|
+
recoveryDeviceProbeFailure(
|
|
268
|
+
'candidate probe mapping closes after readback',
|
|
269
|
+
`probe unmap raised: ${String(cause)}`,
|
|
270
|
+
cause,
|
|
271
|
+
),
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (readback !== undefined) {
|
|
276
|
+
const destroyed = device.destroyBuffer(readback);
|
|
277
|
+
if (!destroyed.ok && outcome.ok) outcome = err(destroyed.error);
|
|
278
|
+
}
|
|
279
|
+
if (texture !== undefined) {
|
|
280
|
+
const destroyed = device.destroyTexture(texture);
|
|
281
|
+
if (!destroyed.ok && outcome.ok) outcome = err(destroyed.error);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return outcome;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Prove that the physical color target allocated by the candidate compiled
|
|
289
|
+
* graph is writable and observable on that same candidate device. This is
|
|
290
|
+
* intentionally a recovery-only boundary check: it does not run a renderer
|
|
291
|
+
* pass, bind a shader, or touch the canvas surface.
|
|
292
|
+
*/
|
|
293
|
+
async function probeCandidateGraphTarget(
|
|
294
|
+
device: RhiDevice,
|
|
295
|
+
target: { readonly texture: Texture; readonly view: TextureView; readonly format: ProbeFormat },
|
|
296
|
+
encode?: (encoder: import('@forgeax/engine-rhi').RhiCommandEncoder) => Result<void, RhiError>,
|
|
297
|
+
): Promise<import('@forgeax/engine-types').Result<void, RhiError>> {
|
|
298
|
+
let readback: Buffer | undefined;
|
|
299
|
+
let mapped: MappedBuffer | undefined;
|
|
300
|
+
let outcome: import('@forgeax/engine-types').Result<void, RhiError> = err(
|
|
301
|
+
recoveryDeviceProbeFailure(
|
|
302
|
+
'candidate compiled graph target exposes a mapped cleared pixel',
|
|
303
|
+
'discard the candidate and keep the renderer in device-lost until its graph target is usable',
|
|
304
|
+
),
|
|
305
|
+
);
|
|
306
|
+
try {
|
|
307
|
+
const readbackResult = device.createBuffer({
|
|
308
|
+
label: 'renderer-recovery-graph-probe.readback',
|
|
309
|
+
size: PROBE_READBACK_BYTES_PER_ROW,
|
|
310
|
+
usage: PROBE_BUFFER_USAGE_MAP_READ | PROBE_BUFFER_USAGE_COPY_DST,
|
|
311
|
+
mappedAtCreation: false,
|
|
312
|
+
});
|
|
313
|
+
if (!readbackResult.ok) {
|
|
314
|
+
outcome = err(
|
|
315
|
+
recoveryDeviceProbeFailure(
|
|
316
|
+
'candidate graph probe creates a mapped readback buffer',
|
|
317
|
+
'inspect candidate graph target readback allocation before publishing recovery',
|
|
318
|
+
readbackResult.error,
|
|
319
|
+
),
|
|
320
|
+
);
|
|
321
|
+
return outcome;
|
|
322
|
+
}
|
|
323
|
+
readback = readbackResult.value;
|
|
324
|
+
|
|
325
|
+
const encoderResult = device.createCommandEncoder({
|
|
326
|
+
label: 'renderer-recovery-graph-probe.encoder',
|
|
327
|
+
});
|
|
328
|
+
if (!encoderResult.ok) {
|
|
329
|
+
outcome = err(
|
|
330
|
+
recoveryDeviceProbeFailure(
|
|
331
|
+
'candidate graph probe creates a command encoder',
|
|
332
|
+
'inspect candidate graph target command encoding before publishing recovery',
|
|
333
|
+
encoderResult.error,
|
|
334
|
+
),
|
|
335
|
+
);
|
|
336
|
+
return outcome;
|
|
337
|
+
}
|
|
338
|
+
const encoder = encoderResult.value;
|
|
339
|
+
const encoded =
|
|
340
|
+
encode === undefined
|
|
341
|
+
? (() => {
|
|
342
|
+
const pass = encoder.beginRenderPass({
|
|
343
|
+
label: 'renderer-recovery-graph-probe.pass',
|
|
344
|
+
colorAttachments: [
|
|
345
|
+
{
|
|
346
|
+
view: target.view,
|
|
347
|
+
loadOp: 'clear',
|
|
348
|
+
storeOp: 'store',
|
|
349
|
+
clearValue: {
|
|
350
|
+
r: PROBE_CLEAR[0],
|
|
351
|
+
g: PROBE_CLEAR[1],
|
|
352
|
+
b: PROBE_CLEAR[2],
|
|
353
|
+
a: PROBE_CLEAR[3],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
});
|
|
358
|
+
pass.end();
|
|
359
|
+
return ok(undefined);
|
|
360
|
+
})()
|
|
361
|
+
: encode(encoder);
|
|
362
|
+
if (!encoded.ok) {
|
|
363
|
+
outcome = err(
|
|
364
|
+
recoveryDeviceProbeFailure(
|
|
365
|
+
'candidate graph probe encodes the actual compiled target',
|
|
366
|
+
'inspect the candidate RenderGraph execution before publishing recovery',
|
|
367
|
+
encoded.error,
|
|
368
|
+
),
|
|
369
|
+
);
|
|
370
|
+
return outcome;
|
|
371
|
+
}
|
|
372
|
+
encoder.copyTextureToBuffer(
|
|
373
|
+
{ texture: target.texture },
|
|
374
|
+
{
|
|
375
|
+
buffer: readback,
|
|
376
|
+
bytesPerRow: PROBE_READBACK_BYTES_PER_ROW,
|
|
377
|
+
rowsPerImage: 1,
|
|
378
|
+
},
|
|
379
|
+
{ width: 1, height: 1, depthOrArrayLayers: 1 },
|
|
380
|
+
);
|
|
381
|
+
const commandResult = encoder.finish();
|
|
382
|
+
if (!commandResult.ok) {
|
|
383
|
+
outcome = err(
|
|
384
|
+
recoveryDeviceProbeFailure(
|
|
385
|
+
'candidate graph probe finishes after clearing and copying the target',
|
|
386
|
+
'inspect candidate graph target command encoding before publishing recovery',
|
|
387
|
+
commandResult.error,
|
|
388
|
+
),
|
|
389
|
+
);
|
|
390
|
+
return outcome;
|
|
391
|
+
}
|
|
392
|
+
const submitted = device.queue.submit([commandResult.value]);
|
|
393
|
+
if (!submitted.ok) {
|
|
394
|
+
outcome = err(
|
|
395
|
+
recoveryDeviceProbeFailure(
|
|
396
|
+
'candidate graph probe command buffer submits successfully',
|
|
397
|
+
'inspect candidate graph target queue submission before publishing recovery',
|
|
398
|
+
submitted.error,
|
|
399
|
+
),
|
|
400
|
+
);
|
|
401
|
+
return outcome;
|
|
402
|
+
}
|
|
403
|
+
await device.queue.onSubmittedWorkDone();
|
|
404
|
+
const mappedResult = await readback.mapAsync(PROBE_BUFFER_USAGE_MAP_READ);
|
|
405
|
+
if (!mappedResult.ok) {
|
|
406
|
+
outcome = err(
|
|
407
|
+
recoveryDeviceProbeFailure(
|
|
408
|
+
'candidate graph probe readback maps after queue completion',
|
|
409
|
+
'inspect candidate graph target queue completion before publishing recovery',
|
|
410
|
+
mappedResult.error,
|
|
411
|
+
),
|
|
412
|
+
);
|
|
413
|
+
return outcome;
|
|
414
|
+
}
|
|
415
|
+
mapped = mappedResult.value;
|
|
416
|
+
const rangeResult = mapped.getMappedRange(0, target.format === 'rgba16float' ? 8 : 4);
|
|
417
|
+
if (!rangeResult.ok) {
|
|
418
|
+
outcome = err(
|
|
419
|
+
recoveryDeviceProbeFailure(
|
|
420
|
+
'candidate graph probe readback exposes its mapped pixel',
|
|
421
|
+
'inspect candidate graph target mapped-buffer state before publishing recovery',
|
|
422
|
+
rangeResult.error,
|
|
423
|
+
),
|
|
424
|
+
);
|
|
425
|
+
return outcome;
|
|
426
|
+
}
|
|
427
|
+
const view = new DataView(rangeResult.value);
|
|
428
|
+
const actual =
|
|
429
|
+
target.format === 'rgba16float'
|
|
430
|
+
? [0, 1, 2, 3].map((channel) => halfToFloat(view.getUint16(channel * 2, true)))
|
|
431
|
+
: [0, 1, 2, 3].map((channel) => (view.getUint8(channel) ?? 0) / 255);
|
|
432
|
+
const epsilon = target.format === 'rgba16float' ? 0.02 : 2 / 255;
|
|
433
|
+
if (!actual.every((value, index) => Math.abs(value - (PROBE_CLEAR[index] ?? 0)) <= epsilon)) {
|
|
434
|
+
outcome = err(
|
|
435
|
+
recoveryDeviceProbeFailure(
|
|
436
|
+
'candidate graph probe readback preserves the cleared target pixel',
|
|
437
|
+
`candidate graph target returned ${actual.map((value) => value.toFixed(4)).join(',')}; discard it instead of publishing alive`,
|
|
438
|
+
),
|
|
439
|
+
);
|
|
440
|
+
return outcome;
|
|
441
|
+
}
|
|
442
|
+
outcome = ok(undefined);
|
|
443
|
+
} catch (cause) {
|
|
444
|
+
outcome = err(
|
|
445
|
+
recoveryDeviceProbeFailure(
|
|
446
|
+
'candidate compiled graph target executes the minimal render/readback probe',
|
|
447
|
+
`discard the candidate after its graph target probe raised: ${String(cause)}`,
|
|
448
|
+
cause,
|
|
449
|
+
),
|
|
450
|
+
);
|
|
451
|
+
} finally {
|
|
452
|
+
try {
|
|
453
|
+
mapped?.unmap();
|
|
454
|
+
} catch (cause) {
|
|
455
|
+
if (outcome.ok) {
|
|
456
|
+
outcome = err(
|
|
457
|
+
recoveryDeviceProbeFailure(
|
|
458
|
+
'candidate graph probe mapping closes after readback',
|
|
459
|
+
`graph probe unmap raised: ${String(cause)}`,
|
|
460
|
+
cause,
|
|
461
|
+
),
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (readback !== undefined) {
|
|
466
|
+
const destroyed = device.destroyBuffer(readback);
|
|
467
|
+
if (!destroyed.ok && outcome.ok) outcome = err(destroyed.error);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return outcome;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Exercise the actual RenderGraphBuilder/CompiledRenderGraph execution path
|
|
475
|
+
* against the candidate graph's physical color target. The graph is tiny and
|
|
476
|
+
* imported-target-only: it isolates pass resolution/encoding from the full
|
|
477
|
+
* renderer topology while preserving the exact candidate texture and view.
|
|
478
|
+
*/
|
|
479
|
+
export async function probeCandidateGraphExecution(
|
|
480
|
+
device: RhiDevice,
|
|
481
|
+
target: { readonly texture: Texture; readonly view: TextureView; readonly format: ProbeFormat },
|
|
482
|
+
): Promise<import('@forgeax/engine-types').Result<void, RhiError>> {
|
|
483
|
+
// RhiNull is intentionally structural-only: it records graph/resource
|
|
484
|
+
// commands but has no pixel-producing queue or mapped readback contract.
|
|
485
|
+
// Real backends still take the complete graph execution/readback probe.
|
|
486
|
+
if (device.caps.backendKind === 'null') return ok(undefined);
|
|
487
|
+
|
|
488
|
+
const builder = new RenderGraphBuilder<RenderGraphFrame>();
|
|
489
|
+
const texture = builder.importTexture(
|
|
490
|
+
'renderer-recovery-graph-execution-probe.texture',
|
|
491
|
+
{
|
|
492
|
+
format: target.format,
|
|
493
|
+
size: { width: 1, height: 1 },
|
|
494
|
+
usage: PROBE_TEXTURE_USAGE_RENDER_ATTACHMENT | PROBE_TEXTURE_USAGE_COPY_SRC,
|
|
495
|
+
dimension: '2d',
|
|
496
|
+
},
|
|
497
|
+
() => target.texture,
|
|
498
|
+
);
|
|
499
|
+
if (!texture.ok) {
|
|
500
|
+
return err(
|
|
501
|
+
recoveryDeviceProbeFailure(
|
|
502
|
+
'candidate graph execution probe imports the physical target',
|
|
503
|
+
'inspect candidate graph probe resource declaration before publishing recovery',
|
|
504
|
+
texture.error,
|
|
505
|
+
),
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
const view = builder.importView(
|
|
509
|
+
texture.value,
|
|
510
|
+
{ label: 'renderer-recovery-graph-execution-probe.view', dimension: '2d' },
|
|
511
|
+
() => target.view,
|
|
512
|
+
);
|
|
513
|
+
if (!view.ok) {
|
|
514
|
+
return err(
|
|
515
|
+
recoveryDeviceProbeFailure(
|
|
516
|
+
'candidate graph execution probe imports the physical target view',
|
|
517
|
+
'inspect candidate graph probe view declaration before publishing recovery',
|
|
518
|
+
view.error,
|
|
519
|
+
),
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
const pass = builder.addRasterPass('renderer-recovery-graph-execution-probe', {
|
|
523
|
+
accesses: [{ resource: view.value, usage: 'color-attachment' }],
|
|
524
|
+
colorAttachments: [
|
|
525
|
+
{
|
|
526
|
+
view: view.value,
|
|
527
|
+
loadOp: 'clear',
|
|
528
|
+
storeOp: 'store',
|
|
529
|
+
clearValue: {
|
|
530
|
+
r: PROBE_CLEAR[0],
|
|
531
|
+
g: PROBE_CLEAR[1],
|
|
532
|
+
b: PROBE_CLEAR[2],
|
|
533
|
+
a: PROBE_CLEAR[3],
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
],
|
|
537
|
+
encode: () => undefined,
|
|
538
|
+
});
|
|
539
|
+
if (!pass.ok) {
|
|
540
|
+
return err(
|
|
541
|
+
recoveryDeviceProbeFailure(
|
|
542
|
+
'candidate graph execution probe adds its clear pass',
|
|
543
|
+
'inspect candidate graph probe pass declaration before publishing recovery',
|
|
544
|
+
pass.error,
|
|
545
|
+
),
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
const compiled = builder.compile({ device, surfaceSize: { width: 1, height: 1 } });
|
|
549
|
+
if (!compiled.ok) {
|
|
550
|
+
return err(
|
|
551
|
+
recoveryDeviceProbeFailure(
|
|
552
|
+
'candidate graph execution probe compiles against the candidate device',
|
|
553
|
+
'inspect candidate graph probe compilation before publishing recovery',
|
|
554
|
+
compiled.error,
|
|
555
|
+
),
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
return await probeCandidateGraphTarget(device, target, (encoder) => {
|
|
560
|
+
const executed = compiled.value.execute({ encoder });
|
|
561
|
+
return executed.ok
|
|
562
|
+
? ok(undefined)
|
|
563
|
+
: err(
|
|
564
|
+
recoveryDeviceProbeFailure(
|
|
565
|
+
'candidate graph execution probe records its clear pass',
|
|
566
|
+
'inspect candidate compiled graph resource resolution and pass encoding before publishing recovery',
|
|
567
|
+
executed.error,
|
|
568
|
+
),
|
|
569
|
+
);
|
|
570
|
+
});
|
|
571
|
+
} finally {
|
|
572
|
+
await compiled.value.retire();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export function deviceOptionsForAdapter(adapter: Pick<RhiAdapter, 'features' | 'limits'>):
|
|
577
|
+
| {
|
|
578
|
+
requiredFeatures?: GPUFeatureName[];
|
|
579
|
+
requiredLimits?: GPUDeviceDescriptor['requiredLimits'];
|
|
580
|
+
}
|
|
581
|
+
| undefined {
|
|
582
|
+
const requiredFeatures = COMPRESSION_FEATURES.filter((feature) => adapter.features.has(feature));
|
|
583
|
+
const supportsTransmission =
|
|
584
|
+
(adapter.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
585
|
+
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
586
|
+
return {
|
|
587
|
+
...(requiredFeatures.length > 0 ? { requiredFeatures } : {}),
|
|
588
|
+
...(supportsTransmission
|
|
589
|
+
? {
|
|
590
|
+
requiredLimits: {
|
|
591
|
+
maxSampledTexturesPerShaderStage: STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES,
|
|
592
|
+
},
|
|
593
|
+
}
|
|
594
|
+
: {}),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export interface AcquiredDeviceGeneration {
|
|
599
|
+
readonly adapter: RhiAdapter;
|
|
600
|
+
readonly device: RhiDevice;
|
|
601
|
+
readonly context: RhiCanvasContext;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export interface DeviceGenerationAcquisitionFailure {
|
|
605
|
+
readonly stage: 'adapter' | 'device' | 'context';
|
|
606
|
+
readonly error: RhiError;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export interface RecoveryStepTimeout {
|
|
610
|
+
readonly phase: RecoveryPhase;
|
|
611
|
+
readonly elapsedMs: number;
|
|
612
|
+
readonly cause: RhiError;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export type RecoveryStepOutcome<T, E> =
|
|
616
|
+
| { readonly kind: 'value'; readonly value: T }
|
|
617
|
+
| { readonly kind: 'error'; readonly error: E }
|
|
618
|
+
| { readonly kind: 'timeout'; readonly timeout: RecoveryStepTimeout };
|
|
619
|
+
|
|
620
|
+
export async function runRecoveryStep<T, E>(
|
|
621
|
+
operation: (continuation?: RecoveryContinuation) => Promise<Result<T, E>>,
|
|
622
|
+
phase: RecoveryPhase,
|
|
623
|
+
deadline: RecoveryDeadline | undefined,
|
|
624
|
+
now: () => number = () => Date.now(),
|
|
625
|
+
continuation?: RecoveryContinuation,
|
|
626
|
+
): Promise<RecoveryStepOutcome<T, E>> {
|
|
627
|
+
if (deadline === undefined) {
|
|
628
|
+
try {
|
|
629
|
+
const result = await operation(continuation);
|
|
630
|
+
return result.ok
|
|
631
|
+
? { kind: 'value', value: result.value }
|
|
632
|
+
: { kind: 'error', error: result.error };
|
|
633
|
+
} catch (cause) {
|
|
634
|
+
return { kind: 'error', error: structuredStepError(cause, phase) as E };
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const remaining = Math.max(0, deadline.deadlineAt - now());
|
|
638
|
+
const phaseLimit =
|
|
639
|
+
phase === 'acquire-adapter'
|
|
640
|
+
? RECOVERY_ADAPTER_DEADLINE_MS
|
|
641
|
+
: phase === 'acquire-device'
|
|
642
|
+
? RECOVERY_DEVICE_DEADLINE_MS
|
|
643
|
+
: remaining;
|
|
644
|
+
const timeoutMs = Math.min(remaining, phaseLimit);
|
|
645
|
+
if (timeoutMs <= 0 || !deadline.isValid(phase, now())) {
|
|
646
|
+
const elapsedMs = deadline.elapsed(now());
|
|
647
|
+
continuation?.abandon(now());
|
|
648
|
+
continuation?.cleanupOnce();
|
|
649
|
+
return {
|
|
650
|
+
kind: 'timeout',
|
|
651
|
+
timeout: { phase, elapsedMs, cause: timeoutError(phase, elapsedMs) },
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
655
|
+
const operationResult = Promise.resolve()
|
|
656
|
+
.then(() => operation(continuation))
|
|
657
|
+
.then((result) =>
|
|
658
|
+
result.ok
|
|
659
|
+
? ({ kind: 'value' as const, value: result.value } satisfies RecoveryStepOutcome<T, E>)
|
|
660
|
+
: ({ kind: 'error' as const, error: result.error } satisfies RecoveryStepOutcome<T, E>),
|
|
661
|
+
)
|
|
662
|
+
.catch(
|
|
663
|
+
(cause) =>
|
|
664
|
+
({
|
|
665
|
+
kind: 'error' as const,
|
|
666
|
+
error: structuredStepError(cause, phase) as E,
|
|
667
|
+
}) satisfies RecoveryStepOutcome<T, E>,
|
|
668
|
+
);
|
|
669
|
+
const outcome = await Promise.race([
|
|
670
|
+
operationResult,
|
|
671
|
+
new Promise<RecoveryStepOutcome<T, E>>((resolve) => {
|
|
672
|
+
timeoutHandle = setTimeout(() => {
|
|
673
|
+
const elapsedMs = deadline.elapsed(now());
|
|
674
|
+
continuation?.abandon(now());
|
|
675
|
+
continuation?.cleanupOnce();
|
|
676
|
+
resolve({
|
|
677
|
+
kind: 'timeout',
|
|
678
|
+
timeout: { phase, elapsedMs, cause: timeoutError(phase, elapsedMs) },
|
|
679
|
+
});
|
|
680
|
+
}, timeoutMs);
|
|
681
|
+
}),
|
|
682
|
+
]);
|
|
683
|
+
if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
|
|
684
|
+
if (continuation !== undefined && !continuation.isValid(phase, now())) {
|
|
685
|
+
const elapsedMs = deadline.elapsed(now());
|
|
686
|
+
continuation.abandon(now());
|
|
687
|
+
continuation.cleanupOnce();
|
|
688
|
+
return {
|
|
689
|
+
kind: 'timeout',
|
|
690
|
+
timeout: { phase, elapsedMs, cause: timeoutError(phase, elapsedMs) },
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
return outcome;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function timeoutError(phase: RecoveryPhase, elapsedMs: number): RhiError {
|
|
697
|
+
return new RhiError({
|
|
698
|
+
code: 'webgpu-runtime-error',
|
|
699
|
+
expected: `recovery ${phase} completes before its bounded deadline`,
|
|
700
|
+
hint: `recovery ${phase} exceeded its deadline after ${elapsedMs}ms; retry after the host observes the loss`,
|
|
701
|
+
detail: {
|
|
702
|
+
error: {
|
|
703
|
+
code: 'recovery-timeout',
|
|
704
|
+
message: `${phase} exceeded its deadline after ${elapsedMs}ms`,
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** Acquire the same backend generation inputs for boot and recovery. */
|
|
711
|
+
export async function acquireDeviceGeneration(
|
|
712
|
+
pack: RhiBackendPack,
|
|
713
|
+
canvas: HTMLCanvasElement | OffscreenCanvas,
|
|
714
|
+
deadline?: RecoveryDeadline,
|
|
715
|
+
continuation?: RecoveryContinuation,
|
|
716
|
+
onPhase?: (phase: RecoveryPhase) => void,
|
|
717
|
+
): Promise<Result<AcquiredDeviceGeneration, DeviceGenerationAcquisitionFailure>> {
|
|
718
|
+
onPhase?.('acquire-adapter');
|
|
719
|
+
const adapterOutcome = await runRecoveryStep(
|
|
720
|
+
(attempt) =>
|
|
721
|
+
pack.rhi.requestAdapter(undefined, canvas).then((result) => {
|
|
722
|
+
if (attempt !== undefined && !attempt.isValid('acquire-adapter', Date.now())) {
|
|
723
|
+
return err(timeoutError('acquire-adapter', 0));
|
|
724
|
+
}
|
|
725
|
+
return result;
|
|
726
|
+
}),
|
|
727
|
+
'acquire-adapter',
|
|
728
|
+
deadline,
|
|
729
|
+
undefined,
|
|
730
|
+
continuation,
|
|
731
|
+
);
|
|
732
|
+
if (adapterOutcome.kind === 'timeout') {
|
|
733
|
+
return err({
|
|
734
|
+
stage: 'adapter',
|
|
735
|
+
error: timeoutError('acquire-adapter', adapterOutcome.timeout.elapsedMs),
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
if (adapterOutcome.kind === 'error')
|
|
739
|
+
return err({ stage: 'adapter', error: adapterOutcome.error });
|
|
740
|
+
const adapter = adapterOutcome.value;
|
|
741
|
+
|
|
742
|
+
deadline?.beginDeviceAcquisition(Date.now());
|
|
743
|
+
onPhase?.('acquire-device');
|
|
744
|
+
let deviceResult: Result<RhiDevice, RhiError>;
|
|
745
|
+
try {
|
|
746
|
+
const deviceOutcome = await runRecoveryStep(
|
|
747
|
+
(attempt) =>
|
|
748
|
+
adapter.requestDevice(deviceOptionsForAdapter(adapter)).then((result) => {
|
|
749
|
+
if (attempt !== undefined && !attempt.isValid('acquire-device', Date.now())) {
|
|
750
|
+
return err(timeoutError('acquire-device', 0));
|
|
751
|
+
}
|
|
752
|
+
return result;
|
|
753
|
+
}),
|
|
754
|
+
'acquire-device',
|
|
755
|
+
deadline,
|
|
756
|
+
undefined,
|
|
757
|
+
continuation,
|
|
758
|
+
);
|
|
759
|
+
if (deviceOutcome.kind === 'timeout') {
|
|
760
|
+
return err({
|
|
761
|
+
stage: 'device',
|
|
762
|
+
error: timeoutError('acquire-device', deviceOutcome.timeout.elapsedMs),
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
if (deviceOutcome.kind === 'error') return err({ stage: 'device', error: deviceOutcome.error });
|
|
766
|
+
deviceResult = ok(deviceOutcome.value);
|
|
767
|
+
} catch (cause) {
|
|
768
|
+
return err({
|
|
769
|
+
stage: 'device',
|
|
770
|
+
error: new RhiError({
|
|
771
|
+
code: 'webgpu-runtime-error',
|
|
772
|
+
expected: 'backend adapter.requestDevice resolves with a Result',
|
|
773
|
+
hint: `backend device acquisition raised: ${String(cause)}`,
|
|
774
|
+
}),
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
const contextResult = pack.rhi.acquireCanvasContext(canvas);
|
|
778
|
+
if (!contextResult.ok) return err({ stage: 'context', error: contextResult.error });
|
|
779
|
+
return ok({
|
|
780
|
+
adapter,
|
|
781
|
+
device: deviceResult.value,
|
|
782
|
+
context: contextResult.value,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function structuredStepError(cause: unknown, phase: RecoveryPhase): RhiError {
|
|
787
|
+
if (cause instanceof RhiError) return cause;
|
|
788
|
+
return new RhiError({
|
|
789
|
+
code: 'webgpu-runtime-error',
|
|
790
|
+
expected: `recovery ${phase} operation rejects with a structured RhiError`,
|
|
791
|
+
hint: `inspect the structured cause from recovery ${phase} before retrying`,
|
|
792
|
+
detail: {
|
|
793
|
+
error: {
|
|
794
|
+
code: 'recovery-operation-rejected',
|
|
795
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
796
|
+
...(cause instanceof Error ? { name: cause.name } : {}),
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
});
|
|
800
|
+
}
|