@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
package/src/render-system.ts
CHANGED
|
@@ -16,21 +16,21 @@
|
|
|
16
16
|
// .invert` (charter proposition 5: no math reinvention; render-system.test.ts
|
|
17
17
|
// asserts `/@forgeax\/engine-math/` shows up in render-system.ts source).
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
type MipmapEncoderWork,
|
|
21
|
+
numMipLevels,
|
|
22
|
+
resolveAssetHandle,
|
|
23
|
+
} from '@forgeax/engine-assets-runtime';
|
|
20
24
|
import type { World } from '@forgeax/engine-ecs';
|
|
21
25
|
import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
|
|
22
26
|
import type { RecorderSession } from '@forgeax/engine-profiler';
|
|
23
27
|
import type { CompiledRenderGraphInfo } from '@forgeax/engine-render-graph';
|
|
24
28
|
import type {
|
|
25
|
-
BindGroup,
|
|
26
29
|
BindGroupEntry,
|
|
27
30
|
BindGroupLayout,
|
|
28
31
|
Buffer,
|
|
29
32
|
RenderPipeline,
|
|
30
|
-
|
|
31
|
-
Texture,
|
|
32
|
-
TextureFormat,
|
|
33
|
-
TextureView,
|
|
33
|
+
RhiDevice,
|
|
34
34
|
} from '@forgeax/engine-rhi';
|
|
35
35
|
import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
|
|
36
36
|
import {
|
|
@@ -39,7 +39,6 @@ import {
|
|
|
39
39
|
type Handle,
|
|
40
40
|
type MaterialAsset,
|
|
41
41
|
type MaterialTextureValue,
|
|
42
|
-
type MeshAsset,
|
|
43
42
|
type RenderPipelineAsset,
|
|
44
43
|
RenderQueue,
|
|
45
44
|
type SamplerAsset,
|
|
@@ -52,14 +51,16 @@ import {
|
|
|
52
51
|
projectDirectionalShadowInspectionSource,
|
|
53
52
|
} from './assembly/directional-shadow-inspection';
|
|
54
53
|
import { createClusterBinScratch } from './cluster-binner';
|
|
54
|
+
import type { DeviceScope, LifecycleResourceSpec } from './device/device-scope';
|
|
55
|
+
import type { EnvironmentGeneration } from './environment/generation';
|
|
55
56
|
import { EnvironmentLifecycle } from './environment/lifecycle';
|
|
56
57
|
import {
|
|
57
58
|
type ObservationUnavailableError,
|
|
58
|
-
PointsLinesMaterialUnsupportedError,
|
|
59
59
|
type RenderError,
|
|
60
60
|
TransmissionCapabilityMissingError,
|
|
61
61
|
} from './errors/render';
|
|
62
62
|
import {
|
|
63
|
+
type RenderFeatureHost,
|
|
63
64
|
type RenderFeaturePreparedGraphicsResolverInput,
|
|
64
65
|
runRenderFeatureFrame,
|
|
65
66
|
settlePreparedGraphicsCompletion,
|
|
@@ -82,60 +83,40 @@ import { GpuDrivenProduction } from './gpu-driven/production-raster';
|
|
|
82
83
|
import type {
|
|
83
84
|
BloomInspection,
|
|
84
85
|
DirectionalShadowInspection,
|
|
86
|
+
GpuDrivenProductionInspection,
|
|
87
|
+
LodOcclusionInspection,
|
|
88
|
+
LodOcclusionInspectionRow,
|
|
89
|
+
LodOcclusionWorldInspection,
|
|
85
90
|
MotionBlurInspection,
|
|
86
91
|
ReflectionProbeInspection,
|
|
87
|
-
ReflectionProbeSelectionInspection,
|
|
88
92
|
RenderSceneInspection,
|
|
93
|
+
SsrDependenciesInspection,
|
|
89
94
|
TemporalTargetInspection,
|
|
90
95
|
TransmissionInspection,
|
|
91
96
|
} from './inspection-types';
|
|
92
|
-
import {
|
|
93
|
-
import {
|
|
97
|
+
import { buildOcclusionProxyVertices } from './occlusion-proxy';
|
|
98
|
+
import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
|
|
99
|
+
import { StandardPointsLinesOwner } from './points-lines/standard-owner';
|
|
94
100
|
import {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} from './
|
|
101
|
+
createRenderSystemRecovery,
|
|
102
|
+
materialSamplerHandles,
|
|
103
|
+
materialTextureHandles,
|
|
104
|
+
} from './recovery/render-system-candidate';
|
|
105
|
+
import { ReflectionProbeRecordOwner } from './reflection/record-owner';
|
|
99
106
|
import {
|
|
100
|
-
|
|
101
|
-
type
|
|
102
|
-
} from './
|
|
103
|
-
import {
|
|
104
|
-
import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
|
|
105
|
-
import type { PointsLinesRecordOwner, PointsLinesRecordSubmission } from './record/render-context';
|
|
107
|
+
OcclusionRenderRuntime,
|
|
108
|
+
type OcclusionRuntimeCandidate,
|
|
109
|
+
} from './scene/visibility/occlusion-runtime';
|
|
110
|
+
import { primitiveKey, viewKey } from './scene/visibility/types';
|
|
106
111
|
import { inspectVolumetricFog } from './volume/inspection';
|
|
107
112
|
|
|
108
113
|
export type { RenderSceneInspection } from './inspection-types';
|
|
109
114
|
|
|
110
|
-
import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
|
|
111
|
-
import { mat4, vec3 } from '@forgeax/engine-math';
|
|
112
|
-
import { buildCubeCameraFaceViews } from './capture/cube-views';
|
|
113
115
|
import { createCubeCaptureScheduler } from './capture/scheduler';
|
|
114
|
-
import {
|
|
115
|
-
createFaceUniformsBuffer,
|
|
116
|
-
createPrefilterUniformsBuffer,
|
|
117
|
-
writeAllFaceUniforms,
|
|
118
|
-
writeAllPrefilterUniforms,
|
|
119
|
-
} from './device/gpu-residency';
|
|
116
|
+
import type { GpuResidencyCache } from './device/gpu-residency';
|
|
120
117
|
import type { EnvironmentInspection } from './environment/inspection';
|
|
121
|
-
import {
|
|
122
|
-
GPU_TEXTURE_USAGE_COPY_SRC,
|
|
123
|
-
GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING,
|
|
124
|
-
} from './gpu-texture-usage';
|
|
125
|
-
import {
|
|
126
|
-
GPU_BUFFER_USAGE_COPY_DST,
|
|
127
|
-
GPU_BUFFER_USAGE_INDEX,
|
|
128
|
-
GPU_BUFFER_USAGE_UNIFORM,
|
|
129
|
-
GPU_BUFFER_USAGE_VERTEX,
|
|
130
|
-
} from './gpu-usage';
|
|
118
|
+
import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from './gpu-usage';
|
|
131
119
|
import { resetHdrpBuffers } from './hdrp-buffers';
|
|
132
|
-
import {
|
|
133
|
-
CUBEMAP_FACE_VERTICES,
|
|
134
|
-
createIblPipelines,
|
|
135
|
-
getOrCreateIblCache,
|
|
136
|
-
PREFILTER_MIP_LEVELS,
|
|
137
|
-
PREFILTER_SIZE,
|
|
138
|
-
} from './ibl/IblPipelineCache';
|
|
139
120
|
import { assembleMaterialWithSkylightEntries } from './ibl/skylight-bind-group';
|
|
140
121
|
import { disposeInstanceBuffers, disposeTransientInstanceBuffers } from './instance-buffer-cache';
|
|
141
122
|
import {
|
|
@@ -168,7 +149,7 @@ import type {
|
|
|
168
149
|
GraphTargetCaptureRequest,
|
|
169
150
|
RenderFrameState,
|
|
170
151
|
} from './record/frame-snapshot';
|
|
171
|
-
import { getTextureIdentity } from './record/frame-snapshot';
|
|
152
|
+
import { getTextureIdentity, worldEntityKey } from './record/frame-snapshot';
|
|
172
153
|
import type { GpuPassTimingReason } from './record/gpu-pass-timing/errors';
|
|
173
154
|
import type {
|
|
174
155
|
GpuPassTimingCapture,
|
|
@@ -177,27 +158,14 @@ import type {
|
|
|
177
158
|
} from './record/gpu-pass-timing/session';
|
|
178
159
|
import type { GpuTimingCapture } from './record/gpu-timing';
|
|
179
160
|
import { applyParamSnapshotToUbo, residentTextureView } from './record/main-pass-material';
|
|
180
|
-
import type {
|
|
181
|
-
import type {
|
|
161
|
+
import type { RecoveryPipelineReadiness } from './record/recovery-pipeline';
|
|
162
|
+
import type { PipelineState, RenderSystemInternals } from './record/render-context';
|
|
163
|
+
import type { CubeCaptureGraphState } from './record/typed-frame-graph';
|
|
182
164
|
import {
|
|
183
165
|
type RenderFeatureGraphCandidate,
|
|
184
166
|
resetRenderFeatureGraphState,
|
|
185
167
|
settleVolumetricFogGraphCandidate,
|
|
186
168
|
} from './record/typed-frame-graph';
|
|
187
|
-
import { REFLECTION_PROBE_VIEW_SLOT_BASE, VIEW_UNIFORM_SLOT_STRIDE } from './record/view-ubo';
|
|
188
|
-
import {
|
|
189
|
-
advanceProbeFilter,
|
|
190
|
-
commitProbeFilterStep,
|
|
191
|
-
createProbeFilterState,
|
|
192
|
-
type ProbeFilterState,
|
|
193
|
-
} from './reflection/filter';
|
|
194
|
-
import { buildReflectionProbeTable } from './reflection/gpu-table';
|
|
195
|
-
import {
|
|
196
|
-
admitReflectionProbe,
|
|
197
|
-
DEFAULT_REFLECTION_PROBE_LIMITS,
|
|
198
|
-
type ReflectionProbeFact,
|
|
199
|
-
type ReflectionProbeSelectionResult,
|
|
200
|
-
} from './reflection/projection';
|
|
201
169
|
import {
|
|
202
170
|
type CameraSnapshot,
|
|
203
171
|
type DrawOwnerOptions,
|
|
@@ -214,8 +182,10 @@ import type {
|
|
|
214
182
|
RenderableSnapshot,
|
|
215
183
|
} from './render-system-extract';
|
|
216
184
|
import { extractFrames } from './render-system-extract';
|
|
217
|
-
import { PersistentRenderScene } from './scene/render-scene';
|
|
185
|
+
import { type PersistentGpuDrivenCandidate, PersistentRenderScene } from './scene/render-scene';
|
|
186
|
+
import { inspectLodOcclusion } from './scene/visibility/inspection';
|
|
218
187
|
import { resolveSsaoParameters } from './ssao-config';
|
|
188
|
+
import { projectSsrDependencies, resolveSsrAdmissionGeneration } from './ssr/admission';
|
|
219
189
|
import {
|
|
220
190
|
getTransparentSortConfig,
|
|
221
191
|
TRANSPARENT_SORT_MODE_DISTANCE,
|
|
@@ -239,6 +209,149 @@ import {
|
|
|
239
209
|
inspectTransmissionFromAdmission,
|
|
240
210
|
} from './transmission/inspection';
|
|
241
211
|
|
|
212
|
+
function canonicalizeWorldComposition(
|
|
213
|
+
worlds: readonly World[],
|
|
214
|
+
owners: Pick<DrawOwnerOptions, 'cameraOwner' | 'resourceOwner'>,
|
|
215
|
+
leases: readonly RenderReadLease[] | undefined,
|
|
216
|
+
): {
|
|
217
|
+
readonly worlds: readonly World[];
|
|
218
|
+
readonly owners: Pick<DrawOwnerOptions, 'cameraOwner' | 'resourceOwner'>;
|
|
219
|
+
readonly leases: readonly RenderReadLease[] | undefined;
|
|
220
|
+
} {
|
|
221
|
+
const order = worlds
|
|
222
|
+
.map((world, index) => ({ world, index }))
|
|
223
|
+
.sort((left, right) => left.world.identity.localeCompare(right.world.identity));
|
|
224
|
+
const indexByOriginal = new Map(order.map((entry, index) => [entry.index, index]));
|
|
225
|
+
return {
|
|
226
|
+
worlds: Object.freeze(order.map((entry) => entry.world)),
|
|
227
|
+
owners: {
|
|
228
|
+
cameraOwner: indexByOriginal.get(owners.cameraOwner) ?? owners.cameraOwner,
|
|
229
|
+
resourceOwner: indexByOriginal.get(owners.resourceOwner) ?? owners.resourceOwner,
|
|
230
|
+
},
|
|
231
|
+
leases:
|
|
232
|
+
leases === undefined
|
|
233
|
+
? undefined
|
|
234
|
+
: Object.freeze(
|
|
235
|
+
order.map((entry) => leases[entry.index]).filter((lease) => lease !== undefined),
|
|
236
|
+
),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
interface LodInspectionQuerySummary {
|
|
241
|
+
readonly used: number;
|
|
242
|
+
readonly capacity: number;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function createLodWorldInspections(
|
|
246
|
+
worlds: readonly World[],
|
|
247
|
+
renderables: readonly RenderableSnapshot[],
|
|
248
|
+
submittedRenderables: readonly RenderableSnapshot[],
|
|
249
|
+
camera: CameraSnapshot,
|
|
250
|
+
frameId: number,
|
|
251
|
+
worldKeys: readonly number[],
|
|
252
|
+
slots: ReadonlyMap<number, { readonly slot: number; readonly generation: number }>,
|
|
253
|
+
query: LodInspectionQuerySummary,
|
|
254
|
+
fallback: LodOcclusionInspection['fallback'],
|
|
255
|
+
degradation: LodOcclusionInspection['degradation'],
|
|
256
|
+
): readonly LodOcclusionWorldInspection[] {
|
|
257
|
+
// The CPU projection knows which renderables belong to each World, but it
|
|
258
|
+
// does not receive the GPU selector/query counters split by World from the
|
|
259
|
+
// same submit. Keep these rows available for diagnostics while explicitly
|
|
260
|
+
// marking their attribution unavailable; producers must not promote them
|
|
261
|
+
// to World-reorder evidence by inferring facts from array order.
|
|
262
|
+
const attribution = Object.freeze({
|
|
263
|
+
status: 'unavailable' as const,
|
|
264
|
+
reason: 'projection-only' as const,
|
|
265
|
+
});
|
|
266
|
+
const submitted = new Set(submittedRenderables);
|
|
267
|
+
const sampleLimit = 64;
|
|
268
|
+
const accumulators = worlds.map(() => ({
|
|
269
|
+
allCount: 0,
|
|
270
|
+
allVisible: 0,
|
|
271
|
+
allSamples: [] as RenderableSnapshot[],
|
|
272
|
+
lodCount: 0,
|
|
273
|
+
lodVisible: 0,
|
|
274
|
+
lodSamples: [] as RenderableSnapshot[],
|
|
275
|
+
}));
|
|
276
|
+
// Keep this projection bounded: the old implementation filtered the full
|
|
277
|
+
// 100k renderable list twice per World on every frame and retained another
|
|
278
|
+
// Set-backed traversal just to derive the six counters below. One pass is
|
|
279
|
+
// enough because the inspection only needs counts, the first row identity,
|
|
280
|
+
// and at most 64 detached samples.
|
|
281
|
+
for (const renderable of renderables) {
|
|
282
|
+
const accumulator = accumulators[renderable.worldId];
|
|
283
|
+
if (accumulator === undefined) continue;
|
|
284
|
+
const visible = submitted.has(renderable);
|
|
285
|
+
accumulator.allCount += 1;
|
|
286
|
+
if (visible) accumulator.allVisible += 1;
|
|
287
|
+
if (accumulator.allSamples.length < sampleLimit) accumulator.allSamples.push(renderable);
|
|
288
|
+
if ((renderable.lods?.length ?? 0) > 0) {
|
|
289
|
+
accumulator.lodCount += 1;
|
|
290
|
+
if (visible) accumulator.lodVisible += 1;
|
|
291
|
+
if (accumulator.lodSamples.length < sampleLimit) accumulator.lodSamples.push(renderable);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const result: LodOcclusionWorldInspection[] = [];
|
|
295
|
+
for (let worldId = 0; worldId < worlds.length; worldId += 1) {
|
|
296
|
+
const world = worlds[worldId];
|
|
297
|
+
if (world === undefined) continue;
|
|
298
|
+
const accumulator = accumulators[worldId];
|
|
299
|
+
if (accumulator === undefined || accumulator.allCount === 0) {
|
|
300
|
+
result.push(
|
|
301
|
+
Object.freeze({ attachmentId: world.identity, rows: Object.freeze([]), attribution }),
|
|
302
|
+
);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const usesLod = accumulator.lodCount > 0;
|
|
306
|
+
const candidateCount = usesLod ? accumulator.lodCount : accumulator.allCount;
|
|
307
|
+
const visible = usesLod ? accumulator.lodVisible : accumulator.allVisible;
|
|
308
|
+
const samples = usesLod ? accumulator.lodSamples : accumulator.allSamples;
|
|
309
|
+
const first = samples[0];
|
|
310
|
+
const slot =
|
|
311
|
+
first === undefined
|
|
312
|
+
? undefined
|
|
313
|
+
: slots.get(worldEntityKey(worldKeys[first.worldId] ?? first.worldId, first.entityKey));
|
|
314
|
+
const row: LodOcclusionInspectionRow = {
|
|
315
|
+
root: {
|
|
316
|
+
guid: first === undefined ? 'none' : `asset-handle:${first.assetHandle}`,
|
|
317
|
+
sourceKey: `render-world:${world.identity}`,
|
|
318
|
+
},
|
|
319
|
+
view: {
|
|
320
|
+
attachmentId: world.identity,
|
|
321
|
+
cameraEntity: world === worlds[camera.worldId ?? 0] ? (camera.entityKey ?? 0) : 0,
|
|
322
|
+
viewRole: 'main',
|
|
323
|
+
viewGeneration: camera.historyVersion ?? 0,
|
|
324
|
+
},
|
|
325
|
+
slot: { primitiveSlot: slot?.slot ?? 0, slotGeneration: slot?.generation ?? 0 },
|
|
326
|
+
generation: frameId,
|
|
327
|
+
count: { candidates: candidateCount, visible, occluded: candidateCount - visible },
|
|
328
|
+
lodHistogram: [{ level: 0, count: candidateCount }],
|
|
329
|
+
queryLatencyUs: { median: 0, p95: 0, last: 0 },
|
|
330
|
+
pagePressure: query,
|
|
331
|
+
fallback,
|
|
332
|
+
degradation,
|
|
333
|
+
samples: samples.map((candidate, index) => ({
|
|
334
|
+
primitiveSlot:
|
|
335
|
+
slots.get(
|
|
336
|
+
worldEntityKey(worldKeys[candidate.worldId] ?? candidate.worldId, candidate.entityKey),
|
|
337
|
+
)?.slot ?? index,
|
|
338
|
+
level: 0,
|
|
339
|
+
visible: submitted.has(candidate),
|
|
340
|
+
})),
|
|
341
|
+
};
|
|
342
|
+
result.push(
|
|
343
|
+
Object.freeze({
|
|
344
|
+
attachmentId: world.identity,
|
|
345
|
+
rows: Object.freeze([row]),
|
|
346
|
+
attribution,
|
|
347
|
+
}),
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
return Object.freeze(
|
|
351
|
+
result.sort((left, right) => left.attachmentId.localeCompare(right.attachmentId)),
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
242
355
|
type TimingSessionHost = RenderSystemInternals & {
|
|
243
356
|
gpuPassTimingSession?: GpuPassTimingSession | undefined;
|
|
244
357
|
gpuPassTimingCapture?: GpuPassTimingCapture | undefined;
|
|
@@ -275,6 +388,128 @@ export {
|
|
|
275
388
|
selectSwapChainFormat,
|
|
276
389
|
} from './record/render-context';
|
|
277
390
|
|
|
391
|
+
export type RendererProducerRootKind =
|
|
392
|
+
| 'backend-surface'
|
|
393
|
+
| 'shader-material-pipeline'
|
|
394
|
+
| 'mesh-texture-sampler'
|
|
395
|
+
| 'render-scene'
|
|
396
|
+
| 'gpu-driven'
|
|
397
|
+
| 'feature'
|
|
398
|
+
| 'external-source'
|
|
399
|
+
| 'render-graph'
|
|
400
|
+
| 'target-history'
|
|
401
|
+
| 'observation-lease';
|
|
402
|
+
|
|
403
|
+
export interface RendererProducerRoot {
|
|
404
|
+
readonly kind: RendererProducerRootKind;
|
|
405
|
+
readonly owner: string;
|
|
406
|
+
readonly candidateScope: 'device-scope';
|
|
407
|
+
readonly visibility: 'visible-workset' | 'non-visible-lazy';
|
|
408
|
+
readonly disabledWork: 'zero' | 'lazy';
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Device-bound post-process resources prepared off to the side of the active renderer. */
|
|
412
|
+
export interface RecoveryPostProcessResources {
|
|
413
|
+
readonly device: RhiDevice;
|
|
414
|
+
readonly paramsBuffers: Map<string, Buffer>;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const PRODUCER_ROOT_MATRIX: readonly RendererProducerRoot[] = Object.freeze([
|
|
418
|
+
{
|
|
419
|
+
kind: 'backend-surface',
|
|
420
|
+
owner: 'backend-surface-owner',
|
|
421
|
+
candidateScope: 'device-scope',
|
|
422
|
+
visibility: 'visible-workset',
|
|
423
|
+
disabledWork: 'zero',
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
kind: 'shader-material-pipeline',
|
|
427
|
+
owner: 'shader-material-pipeline-owner',
|
|
428
|
+
candidateScope: 'device-scope',
|
|
429
|
+
visibility: 'visible-workset',
|
|
430
|
+
disabledWork: 'zero',
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
kind: 'mesh-texture-sampler',
|
|
434
|
+
owner: 'gpu-residency',
|
|
435
|
+
candidateScope: 'device-scope',
|
|
436
|
+
visibility: 'visible-workset',
|
|
437
|
+
disabledWork: 'lazy',
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
kind: 'render-scene',
|
|
441
|
+
owner: 'persistent-render-scene',
|
|
442
|
+
candidateScope: 'device-scope',
|
|
443
|
+
visibility: 'visible-workset',
|
|
444
|
+
disabledWork: 'zero',
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
kind: 'gpu-driven',
|
|
448
|
+
owner: 'gpu-driven-production',
|
|
449
|
+
candidateScope: 'device-scope',
|
|
450
|
+
visibility: 'visible-workset',
|
|
451
|
+
disabledWork: 'zero',
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
kind: 'feature',
|
|
455
|
+
owner: 'render-feature-host',
|
|
456
|
+
candidateScope: 'device-scope',
|
|
457
|
+
visibility: 'visible-workset',
|
|
458
|
+
disabledWork: 'lazy',
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
kind: 'external-source',
|
|
462
|
+
owner: 'external-source-owner',
|
|
463
|
+
candidateScope: 'device-scope',
|
|
464
|
+
visibility: 'non-visible-lazy',
|
|
465
|
+
disabledWork: 'zero',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
kind: 'render-graph',
|
|
469
|
+
owner: 'typed-frame-graph',
|
|
470
|
+
candidateScope: 'device-scope',
|
|
471
|
+
visibility: 'visible-workset',
|
|
472
|
+
disabledWork: 'zero',
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
kind: 'target-history',
|
|
476
|
+
owner: 'temporal-target-owner',
|
|
477
|
+
candidateScope: 'device-scope',
|
|
478
|
+
visibility: 'visible-workset',
|
|
479
|
+
disabledWork: 'zero',
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
kind: 'observation-lease',
|
|
483
|
+
owner: 'render-observation-owner',
|
|
484
|
+
candidateScope: 'device-scope',
|
|
485
|
+
visibility: 'non-visible-lazy',
|
|
486
|
+
disabledWork: 'zero',
|
|
487
|
+
},
|
|
488
|
+
]);
|
|
489
|
+
|
|
490
|
+
export function createRendererProducerRootMatrix(): readonly RendererProducerRoot[] {
|
|
491
|
+
return PRODUCER_ROOT_MATRIX;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Production-owned recovery facts. These values are projections of the live
|
|
496
|
+
* RenderSystem owners; they are not a test ledger or a second graph model.
|
|
497
|
+
*/
|
|
498
|
+
export interface RecoveryProductionEvidence {
|
|
499
|
+
readonly producerRoots: readonly RendererProducerRoot[];
|
|
500
|
+
readonly graph: {
|
|
501
|
+
readonly ready: boolean;
|
|
502
|
+
readonly generation: number;
|
|
503
|
+
readonly passCount: number;
|
|
504
|
+
readonly resourceCount: number;
|
|
505
|
+
};
|
|
506
|
+
readonly residency: { readonly meshResidencyEpoch: number };
|
|
507
|
+
readonly submissions: {
|
|
508
|
+
readonly count: number;
|
|
509
|
+
readonly lastGeneration: number | undefined;
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
278
513
|
/**
|
|
279
514
|
* Unified transparent-queue sub-sort covering all four
|
|
280
515
|
* {@link TransparentSortConfig} modes. Reorders the
|
|
@@ -323,7 +558,7 @@ function sortTransparentDispatch(
|
|
|
323
558
|
const camPos = camera.position;
|
|
324
559
|
|
|
325
560
|
// Squared camera distance per entry (negated → ascending = back-to-front).
|
|
326
|
-
// D-3: world position = translation column of
|
|
561
|
+
// D-3: world position = translation column of GlobalTransform.world (m[12,13,14]).
|
|
327
562
|
const negDistSq = (entry: DispatchEntry): number => {
|
|
328
563
|
const tx = renderables[entry.renderableIndex]?.transform;
|
|
329
564
|
if (tx === undefined) return 0;
|
|
@@ -422,6 +657,19 @@ export interface RenderSystem {
|
|
|
422
657
|
renderReadLeases?: readonly RenderReadLease[],
|
|
423
658
|
timingCapture?: GpuTimingCapture,
|
|
424
659
|
): boolean;
|
|
660
|
+
/** Prepare a detached graph candidate without entering the frame record path. */
|
|
661
|
+
prepareRecoveryGraphCandidate(
|
|
662
|
+
runtime: RecoveryGraphCandidateRuntime,
|
|
663
|
+
): RecoveryGraphCandidatePreparation;
|
|
664
|
+
/** Finish and submit detached candidate setup, never a frame receipt. */
|
|
665
|
+
submitCandidateSetup(
|
|
666
|
+
candidate: RecoveryGraphCandidate,
|
|
667
|
+
isValid: () => boolean,
|
|
668
|
+
): Result<RecoveryGraphSetupSubmission, RhiError>;
|
|
669
|
+
/** Publish a detached graph candidate at the generation publication boundary. */
|
|
670
|
+
publishRecoveryGraphCandidate(candidate: RecoveryGraphCandidate): void;
|
|
671
|
+
/** Discard a detached graph candidate that did not reach publication. */
|
|
672
|
+
discardRecoveryGraphCandidate(candidate: RecoveryGraphCandidate): void;
|
|
425
673
|
/** Release renderer-owned persistent state for one detached World. */
|
|
426
674
|
detachScene(world: World): void;
|
|
427
675
|
/** Release the profiler catalog contribution owned by this RenderSystem. */
|
|
@@ -429,6 +677,8 @@ export interface RenderSystem {
|
|
|
429
677
|
observeCurrentFrame(
|
|
430
678
|
options: FrameObservationOptions,
|
|
431
679
|
): Promise<Result<FrameObservation, ObservationUnavailableError>>;
|
|
680
|
+
/** Resolve the renderer-owned GPU LOD counters for the last accepted submit. */
|
|
681
|
+
observeLodOcclusion(): Promise<void>;
|
|
432
682
|
/** Internal same-frame graph target access for backend diagnostics. */
|
|
433
683
|
getCurrentGraphTarget(name: string): CurrentGraphTarget | undefined;
|
|
434
684
|
/** Internal test capture copied into the draw's command submission. */
|
|
@@ -446,6 +696,10 @@ export interface RenderSystem {
|
|
|
446
696
|
/** Persistent scene maintenance evidence from the ordinary single-World path. */
|
|
447
697
|
readonly renderScene: RenderSceneInspection;
|
|
448
698
|
readonly reflectionProbes: ReflectionProbeInspection;
|
|
699
|
+
/** Live producer/RHI/temporal receipts projected through SSR admission. */
|
|
700
|
+
readonly ssrDependencies: SsrDependenciesInspection;
|
|
701
|
+
/** Producer completion fence included in the public FrameReceipt. */
|
|
702
|
+
readonly reflectionFallbackCompletion: Promise<void> | undefined;
|
|
449
703
|
/** True while a progressive CubeCamera candidate still owns unfinished faces. */
|
|
450
704
|
isCubeCapturePending(target: RenderTarget): boolean;
|
|
451
705
|
readonly environment: EnvironmentInspection;
|
|
@@ -453,6 +707,9 @@ export interface RenderSystem {
|
|
|
453
707
|
readonly bloom: BloomInspection;
|
|
454
708
|
readonly motionBlurInspection: MotionBlurInspection | undefined;
|
|
455
709
|
readonly temporalTargetInspection: TemporalTargetInspection | undefined;
|
|
710
|
+
readonly lodOcclusionInspection: LodOcclusionInspection | undefined;
|
|
711
|
+
/** Bounded GPU-driven counters without materializing the persistent scene table. */
|
|
712
|
+
readonly gpuDrivenInspection: GpuDrivenProductionInspection;
|
|
456
713
|
/** Last completed transmission candidate facts, detached from GPU handles. */
|
|
457
714
|
readonly transmission: TransmissionInspection | undefined;
|
|
458
715
|
/** Single production projection of Directional author/extract/record facts. */
|
|
@@ -465,6 +722,8 @@ export interface RenderSystem {
|
|
|
465
722
|
readonly pointsLinesSnapshots: readonly PointsLinesRetainedSnapshot[];
|
|
466
723
|
/** Current mesh-slot provenance and active diagnostics from the last frame. */
|
|
467
724
|
readonly meshMaterialBindings: readonly MeshMaterialBindingObservation[];
|
|
725
|
+
/** Final-submit IBL binding-chain receipt for the last diagnostic frame. */
|
|
726
|
+
readonly iblBinding: import('./mesh-material-bindings').IblBindingInspection | undefined;
|
|
468
727
|
/**
|
|
469
728
|
* feat-20260531-bloom-first-declarative-render-graph-pass M4 fix-up w19:
|
|
470
729
|
* per-frame render-graph pass names in declaration order. Empty array
|
|
@@ -478,6 +737,8 @@ export interface RenderSystem {
|
|
|
478
737
|
readonly lastSuccessfulCameraAntialias: CameraSnapshot['antialias'] | undefined;
|
|
479
738
|
/** Detached compiled-graph facts used by the renderer inspection owner. */
|
|
480
739
|
readonly perFrameGraphInfo: CompiledRenderGraphInfo | undefined;
|
|
740
|
+
/** Production-owner recovery facts used by Renderer.inspect(). */
|
|
741
|
+
readonly recoveryEvidence: RecoveryProductionEvidence;
|
|
481
742
|
/**
|
|
482
743
|
* feat-20260531-per-frame-bind-group-cache M1 / w4: per-frame
|
|
483
744
|
* createBindGroup counter. Reset to 0 on every `draw([world], { cameraOwner: 0, resourceOwner: 0 })` entry,
|
|
@@ -505,6 +766,8 @@ export interface RenderSystem {
|
|
|
505
766
|
configureStandard(config: RenderPipelineAsset['config']): void;
|
|
506
767
|
/** Register one engine-owned post-process shader used by the Standard lane. */
|
|
507
768
|
registerBuiltinPostProcess(id: string, entry: PostProcessShaderEntry): () => void;
|
|
769
|
+
/** Resolve one active built-in post-process declaration for a detached candidate. */
|
|
770
|
+
readonly lookupPostProcess: (id: string) => PostProcessShaderEntry | undefined;
|
|
508
771
|
/**
|
|
509
772
|
* feat-20260612-rhi-destroy-renderer-dispose-gpu-lifecycle / M5 / w21:
|
|
510
773
|
* release the per-RenderSystem frame-state GPU bookkeeping during
|
|
@@ -528,9 +791,19 @@ export interface RenderSystem {
|
|
|
528
791
|
* handles and are rebuilt after the replacement device is live.
|
|
529
792
|
* Idempotent and graph-optional (no-op when nothing has compiled yet).
|
|
530
793
|
*/
|
|
531
|
-
resetForRecover(): void;
|
|
794
|
+
resetForRecover(retiringPipelineState?: PipelineState, replacementDevice?: RhiDevice): void;
|
|
795
|
+
/** Build post-process parameter buffers without mutating the active bundle. */
|
|
796
|
+
prepareRecoveryPostProcessResources(
|
|
797
|
+
device: RhiDevice,
|
|
798
|
+
): Result<RecoveryPostProcessResources, RhiError>;
|
|
799
|
+
/** Publish a previously prepared post-process bundle at the recovery boundary. */
|
|
800
|
+
publishRecoveryPostProcessResources(candidate: RecoveryPostProcessResources): void;
|
|
801
|
+
/** Release a candidate bundle that was never published. */
|
|
802
|
+
discardRecoveryPostProcessResources(candidate: RecoveryPostProcessResources): void;
|
|
532
803
|
/** Recreate post-process GPU parameter resources after a device rebuild. */
|
|
533
804
|
restorePostProcessResources(): void;
|
|
805
|
+
/** Build candidate roots from explicit candidate-owned device state. */
|
|
806
|
+
prepareRecoveryRoots(runtime: RecoveryRootRuntime): RecoveryRootBundle;
|
|
534
807
|
}
|
|
535
808
|
|
|
536
809
|
function isStructuredRendererError(error: unknown): error is RhiError | RenderError {
|
|
@@ -586,11 +859,12 @@ function makePreparedPipelinePendingError(): RhiError {
|
|
|
586
859
|
});
|
|
587
860
|
}
|
|
588
861
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
862
|
+
/** The single Points/Lines owner’s staged device-generation handoff. */
|
|
863
|
+
export interface RecoveryPointsLinesCandidate {
|
|
864
|
+
createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown>;
|
|
865
|
+
publish(): void;
|
|
866
|
+
release(): void;
|
|
867
|
+
}
|
|
594
868
|
|
|
595
869
|
function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationFailure | undefined {
|
|
596
870
|
if (typeof error !== 'object' || error === null) return undefined;
|
|
@@ -619,30 +893,56 @@ function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationF
|
|
|
619
893
|
};
|
|
620
894
|
}
|
|
621
895
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const add = (handle: Handle<'TextureAsset', 'shared'> | undefined): void => {
|
|
627
|
-
if (handle !== undefined) handles.set(Number(handle), handle);
|
|
628
|
-
};
|
|
629
|
-
for (const handle of material.textureHandles?.values() ?? []) add(handle);
|
|
630
|
-
add(material.baseColorTexture);
|
|
631
|
-
add(material.metallicRoughnessTexture);
|
|
632
|
-
add(material.normalTexture);
|
|
633
|
-
add(material.emissiveTexture);
|
|
634
|
-
add(material.occlusionTexture);
|
|
635
|
-
return [...handles.values()];
|
|
896
|
+
/** Candidate-only device/runtime inputs used by the recovery prepare seam. */
|
|
897
|
+
export interface RecoveryGraphCandidateRuntime {
|
|
898
|
+
readonly internals: RenderSystemInternals;
|
|
899
|
+
readonly pipelineState: PipelineState;
|
|
636
900
|
}
|
|
637
901
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
902
|
+
/** Detached graph state prepared on a replacement device, before publication. */
|
|
903
|
+
export interface RecoveryGraphCandidate {
|
|
904
|
+
readonly frameState: RenderFrameState;
|
|
905
|
+
readonly setupWorks: readonly MipmapEncoderWork[];
|
|
906
|
+
readonly device: RhiDevice;
|
|
907
|
+
readonly generation: number;
|
|
908
|
+
readonly gpuDrivenProduction?: GpuDrivenProduction;
|
|
909
|
+
readonly gpuDrivenScene?: PersistentGpuDrivenCandidate;
|
|
910
|
+
/** Staged CPU-expanded Points/Lines buffers owned by the same RenderSystem. */
|
|
911
|
+
readonly pointsLines?: RecoveryPointsLinesCandidate;
|
|
912
|
+
readonly featureHost?: RenderFeatureHost;
|
|
913
|
+
readonly featureGpuWork?: RenderFeatureGpuWorkOwner;
|
|
914
|
+
readonly featureGraphCandidate?: RenderFeatureGraphCandidate;
|
|
915
|
+
readonly postProcessPipelines?: ReadonlyMap<string, RenderPipeline>;
|
|
916
|
+
readonly recoveryReadiness: RecoveryPipelineReadiness;
|
|
917
|
+
/** Release all candidate roots before publication; safe to call repeatedly. */
|
|
918
|
+
readonly release: () => void;
|
|
919
|
+
/** Transfer candidate-root ownership to the published RenderSystem. */
|
|
920
|
+
readonly markPublished: () => void;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/** Candidate setup completion; completion is bounded by the recovery owner. */
|
|
924
|
+
export interface RecoveryGraphSetupSubmission {
|
|
925
|
+
readonly completion?: PromiseLike<unknown>;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
export type RecoveryGraphCandidatePreparation =
|
|
929
|
+
| { readonly kind: 'no-seed' }
|
|
930
|
+
| { readonly kind: 'ready'; readonly candidate: RecoveryGraphCandidate }
|
|
931
|
+
| { readonly kind: 'failed'; readonly reason: string; readonly cause?: unknown };
|
|
932
|
+
|
|
933
|
+
/** Explicit candidate-owned inputs for lifecycle-root preparation. */
|
|
934
|
+
export interface RecoveryRootRuntime {
|
|
935
|
+
readonly scope: DeviceScope;
|
|
936
|
+
readonly device: RhiDevice;
|
|
937
|
+
readonly gpuStore: GpuResidencyCache;
|
|
938
|
+
readonly graphCandidate?: RecoveryGraphCandidate | undefined;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/** Candidate roots plus the one synchronous owner switch at publication. */
|
|
942
|
+
export interface RecoveryRootBundle {
|
|
943
|
+
readonly roots: readonly LifecycleResourceSpec<unknown>[];
|
|
944
|
+
publish(): void;
|
|
945
|
+
discard(): void;
|
|
646
946
|
}
|
|
647
947
|
|
|
648
948
|
/**
|
|
@@ -761,812 +1061,6 @@ function observeMaterialResidency(
|
|
|
761
1061
|
};
|
|
762
1062
|
}
|
|
763
1063
|
|
|
764
|
-
/**
|
|
765
|
-
* The Standard renderer's single Points/Lines prepare owner.
|
|
766
|
-
*
|
|
767
|
-
* The retained snapshot is the identity boundary; this owner only resolves
|
|
768
|
-
* the source assets, runs admission, and publishes the existing preparation
|
|
769
|
-
* and record contracts to the main geometry loop. The expansion cache is
|
|
770
|
-
* shared across lanes and this owner keeps the one prepared vertex/index
|
|
771
|
-
* resource pair for each retained identity.
|
|
772
|
-
*/
|
|
773
|
-
class StandardPointsLinesOwner implements PointsLinesRecordOwner {
|
|
774
|
-
private readonly cache = new PointsLinesExpansionCache();
|
|
775
|
-
private readonly preparations = new Map<string, PointsLinesPreparationAdapter>();
|
|
776
|
-
private readonly active = new Map<string, PointsLinesInspection>();
|
|
777
|
-
private readonly layoutProjection = deriveVertexLayoutProjection({
|
|
778
|
-
position: new Float32Array(0),
|
|
779
|
-
normal: new Float32Array(0),
|
|
780
|
-
uv: new Float32Array(0),
|
|
781
|
-
tangent: new Float32Array(0),
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
constructor(private readonly internals: RenderSystemInternals) {}
|
|
785
|
-
|
|
786
|
-
beginFrame(): void {
|
|
787
|
-
this.active.clear();
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
prepare(
|
|
791
|
-
entry: import('./record/frame-snapshot').ValidatedRenderable,
|
|
792
|
-
clustered: boolean,
|
|
793
|
-
): PointsLinesRecordSubmission | undefined {
|
|
794
|
-
const snapshot = entry.source.pointsLines;
|
|
795
|
-
if (snapshot === undefined || snapshot.component === undefined || entry.world === undefined) {
|
|
796
|
-
return undefined;
|
|
797
|
-
}
|
|
798
|
-
const key = `${snapshot.worldId}:${snapshot.entityKey}`;
|
|
799
|
-
const topology = snapshot.component === 'Points' ? 'point-list' : 'line-list';
|
|
800
|
-
const backend = this.backend();
|
|
801
|
-
const lane = backend === 'wgpu-webgl2' ? 'cpu-webgl2' : clustered ? 'clustered' : 'direct';
|
|
802
|
-
const meshResult = resolveAssetHandle<MeshAsset>(
|
|
803
|
-
entry.world,
|
|
804
|
-
toShared<'MeshAsset'>(entry.source.assetHandle),
|
|
805
|
-
);
|
|
806
|
-
const materialResult = resolveAssetHandle<MaterialAsset>(
|
|
807
|
-
entry.world,
|
|
808
|
-
toShared<'MaterialAsset'>(snapshot.materialHandle),
|
|
809
|
-
);
|
|
810
|
-
if (!meshResult.ok || !materialResult.ok) {
|
|
811
|
-
this.publishRefusal(
|
|
812
|
-
snapshot,
|
|
813
|
-
new PointsLinesMaterialUnsupportedError({
|
|
814
|
-
entity: snapshot.entityKey,
|
|
815
|
-
material: 'unresolved',
|
|
816
|
-
pass: 'forward',
|
|
817
|
-
module: 'asset-resolution',
|
|
818
|
-
reason: 'source MeshAsset or MaterialAsset could not be resolved',
|
|
819
|
-
}),
|
|
820
|
-
);
|
|
821
|
-
return undefined;
|
|
822
|
-
}
|
|
823
|
-
const admission = admitPointsLines({
|
|
824
|
-
entity: snapshot.entityKey,
|
|
825
|
-
mesh: meshResult.value,
|
|
826
|
-
material: materialResult.value,
|
|
827
|
-
...(snapshot.style?.kind === 'points'
|
|
828
|
-
? {
|
|
829
|
-
points: {
|
|
830
|
-
sizePx: snapshot.style.sizePx,
|
|
831
|
-
shape: snapshot.style.shape === 'circle' ? 1 : 0,
|
|
832
|
-
},
|
|
833
|
-
}
|
|
834
|
-
: { lines: { widthPx: snapshot.style?.widthPx ?? 1 } }),
|
|
835
|
-
});
|
|
836
|
-
if (!admission.ok) {
|
|
837
|
-
this.publishRefusal(snapshot, admission.error);
|
|
838
|
-
return undefined;
|
|
839
|
-
}
|
|
840
|
-
const geometryKey = this.cache.getOrCreate(snapshot, meshResult.value).key;
|
|
841
|
-
const prepKey = `${geometryKey}:${lane}:${backend}`;
|
|
842
|
-
let preparation = this.preparations.get(prepKey);
|
|
843
|
-
if (preparation === undefined) {
|
|
844
|
-
preparation = createPointsLinesLanePreparationAdapter(lane, backend, {
|
|
845
|
-
cache: this.cache,
|
|
846
|
-
adapter: {
|
|
847
|
-
create: (geometry) => {
|
|
848
|
-
const vertex = this.internals.device.createBuffer({
|
|
849
|
-
label: `points-lines-vertices:${geometryKey}`,
|
|
850
|
-
size: Math.max(4, geometry.vertices.byteLength),
|
|
851
|
-
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
852
|
-
mappedAtCreation: false,
|
|
853
|
-
});
|
|
854
|
-
if (!vertex.ok) return err(vertex.error);
|
|
855
|
-
const index = this.internals.device.createBuffer({
|
|
856
|
-
label: `points-lines-indices:${geometryKey}`,
|
|
857
|
-
size: Math.max(4, geometry.indices.byteLength),
|
|
858
|
-
usage: GPU_BUFFER_USAGE_INDEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
859
|
-
mappedAtCreation: false,
|
|
860
|
-
});
|
|
861
|
-
if (!index.ok) {
|
|
862
|
-
this.internals.device.destroyBuffer(vertex.value);
|
|
863
|
-
return err(index.error);
|
|
864
|
-
}
|
|
865
|
-
return ok({ vertexBuffer: vertex.value, indexBuffer: index.value });
|
|
866
|
-
},
|
|
867
|
-
upload: (resource, geometry) => {
|
|
868
|
-
if (geometry.vertices.byteLength > 0) {
|
|
869
|
-
const vertexWrite = this.internals.device.queue.writeBuffer(
|
|
870
|
-
resource.vertexBuffer,
|
|
871
|
-
0,
|
|
872
|
-
geometry.vertices,
|
|
873
|
-
);
|
|
874
|
-
if (!vertexWrite.ok) return err(vertexWrite.error);
|
|
875
|
-
}
|
|
876
|
-
if (geometry.indices.byteLength > 0) {
|
|
877
|
-
const indexWrite = this.internals.device.queue.writeBuffer(
|
|
878
|
-
resource.indexBuffer,
|
|
879
|
-
0,
|
|
880
|
-
geometry.indices,
|
|
881
|
-
);
|
|
882
|
-
if (!indexWrite.ok) return err(indexWrite.error);
|
|
883
|
-
}
|
|
884
|
-
return ok(geometry.derivedBytes);
|
|
885
|
-
},
|
|
886
|
-
validate: (_resource, geometry) =>
|
|
887
|
-
geometry.expandedVertexCount > 0 && geometry.expandedIndexCount > 0
|
|
888
|
-
? ok(undefined)
|
|
889
|
-
: err(new Error('Points/Lines expansion contains no drawable triangles')),
|
|
890
|
-
destroy: (resource) => {
|
|
891
|
-
this.internals.device.destroyBuffer(resource.vertexBuffer);
|
|
892
|
-
this.internals.device.destroyBuffer(resource.indexBuffer);
|
|
893
|
-
},
|
|
894
|
-
},
|
|
895
|
-
});
|
|
896
|
-
this.preparations.set(prepKey, preparation);
|
|
897
|
-
}
|
|
898
|
-
const prepared = preparation.prepare(snapshot, meshResult.value);
|
|
899
|
-
if (!prepared.ok) {
|
|
900
|
-
const lkg = preparation.lastKnownGood();
|
|
901
|
-
if (lkg !== undefined) {
|
|
902
|
-
const lkgPlan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
|
|
903
|
-
lkg.snapshot,
|
|
904
|
-
lkg.geometry,
|
|
905
|
-
);
|
|
906
|
-
const state = preparation.inspect();
|
|
907
|
-
this.active.set(
|
|
908
|
-
key,
|
|
909
|
-
inspectPointsLines({
|
|
910
|
-
snapshot,
|
|
911
|
-
topology,
|
|
912
|
-
lane,
|
|
913
|
-
pointCount: lkg.geometry.pointCount,
|
|
914
|
-
segmentCount: lkg.geometry.segmentCount,
|
|
915
|
-
sourceBytes: lkg.geometry.sourceBytes,
|
|
916
|
-
derivedBytes: lkg.geometry.derivedBytes,
|
|
917
|
-
cache: { hit: true, rebuilds: state.rebuilds, evictions: 0 },
|
|
918
|
-
drawCount: lkgPlan.drawCount,
|
|
919
|
-
uploadBytes: 0,
|
|
920
|
-
lastKnownGood: true,
|
|
921
|
-
refusal: {
|
|
922
|
-
code: prepared.error.code,
|
|
923
|
-
expected: prepared.error.expected,
|
|
924
|
-
hint: prepared.error.hint,
|
|
925
|
-
detail: prepared.error.detail,
|
|
926
|
-
generation: prepared.error.detail.generation,
|
|
927
|
-
lastKnownGood: true,
|
|
928
|
-
},
|
|
929
|
-
}),
|
|
930
|
-
);
|
|
931
|
-
return {
|
|
932
|
-
plan: lkgPlan,
|
|
933
|
-
vertexBuffer: lkg.resource.vertexBuffer,
|
|
934
|
-
indexBuffer: lkg.resource.indexBuffer,
|
|
935
|
-
layoutProjection: this.layoutProjection,
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
this.publishRefusal(snapshot, prepared.error);
|
|
939
|
-
return undefined;
|
|
940
|
-
}
|
|
941
|
-
const plan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
|
|
942
|
-
snapshot,
|
|
943
|
-
prepared.value.geometry,
|
|
944
|
-
);
|
|
945
|
-
const state = preparation.inspect();
|
|
946
|
-
this.active.set(
|
|
947
|
-
key,
|
|
948
|
-
inspectPointsLines({
|
|
949
|
-
snapshot,
|
|
950
|
-
topology,
|
|
951
|
-
lane,
|
|
952
|
-
pointCount: prepared.value.geometry.pointCount,
|
|
953
|
-
segmentCount: prepared.value.geometry.segmentCount,
|
|
954
|
-
sourceBytes: prepared.value.geometry.sourceBytes,
|
|
955
|
-
derivedBytes: prepared.value.geometry.derivedBytes,
|
|
956
|
-
cache: {
|
|
957
|
-
hit: prepared.value.uploadedBytes === 0,
|
|
958
|
-
rebuilds: state.rebuilds,
|
|
959
|
-
evictions: 0,
|
|
960
|
-
},
|
|
961
|
-
drawCount: plan.drawCount,
|
|
962
|
-
uploadBytes: prepared.value.uploadedBytes,
|
|
963
|
-
lastKnownGood: prepared.value.lastKnownGood,
|
|
964
|
-
}),
|
|
965
|
-
);
|
|
966
|
-
return {
|
|
967
|
-
plan,
|
|
968
|
-
vertexBuffer: prepared.value.resource.vertexBuffer,
|
|
969
|
-
indexBuffer: prepared.value.resource.indexBuffer,
|
|
970
|
-
layoutProjection: this.layoutProjection,
|
|
971
|
-
};
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
resetForDeviceLoss(): void {
|
|
975
|
-
for (const preparation of this.preparations.values()) preparation.resetForDeviceLoss();
|
|
976
|
-
this.active.clear();
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
inspections(): readonly PointsLinesInspection[] {
|
|
980
|
-
return [...this.active.values()];
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
private backend(): PointsLinesBackend {
|
|
984
|
-
switch (this.internals.device.caps.backendKind) {
|
|
985
|
-
case 'wgpu-webgl2':
|
|
986
|
-
return 'wgpu-webgl2';
|
|
987
|
-
case 'null':
|
|
988
|
-
return 'null';
|
|
989
|
-
default:
|
|
990
|
-
return 'webgpu';
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
private publishRefusal(
|
|
995
|
-
snapshot: PointsLinesRetainedSnapshot,
|
|
996
|
-
error: PointsLinesSourceError,
|
|
997
|
-
): void {
|
|
998
|
-
this.active.set(
|
|
999
|
-
`${snapshot.worldId}:${snapshot.entityKey}`,
|
|
1000
|
-
inspectPointsLines({
|
|
1001
|
-
snapshot,
|
|
1002
|
-
topology: snapshot.component === 'Points' ? 'point-list' : 'line-list',
|
|
1003
|
-
lane: 'refused',
|
|
1004
|
-
pointCount: 0,
|
|
1005
|
-
segmentCount: 0,
|
|
1006
|
-
sourceBytes: 0,
|
|
1007
|
-
derivedBytes: 0,
|
|
1008
|
-
cache: { hit: false, rebuilds: 0, evictions: 0 },
|
|
1009
|
-
drawCount: 0,
|
|
1010
|
-
uploadBytes: 0,
|
|
1011
|
-
lastKnownGood: false,
|
|
1012
|
-
refusal: {
|
|
1013
|
-
code: error.code,
|
|
1014
|
-
expected: error.expected,
|
|
1015
|
-
hint: error.hint,
|
|
1016
|
-
detail: error.detail,
|
|
1017
|
-
generation: snapshot.meshGeneration,
|
|
1018
|
-
lastKnownGood: false,
|
|
1019
|
-
},
|
|
1020
|
-
}),
|
|
1021
|
-
);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
type ProbeCubeSet = {
|
|
1026
|
-
readonly texture: Texture;
|
|
1027
|
-
readonly cubeView: TextureView;
|
|
1028
|
-
readonly faceViews: readonly TextureView[];
|
|
1029
|
-
readonly mipViews: readonly (readonly TextureView[])[];
|
|
1030
|
-
readonly depthTexture: Texture;
|
|
1031
|
-
readonly depthView: TextureView;
|
|
1032
|
-
readonly size: number;
|
|
1033
|
-
};
|
|
1034
|
-
|
|
1035
|
-
type ProbeGpuResource = {
|
|
1036
|
-
fact: ReflectionProbeFact;
|
|
1037
|
-
index: number;
|
|
1038
|
-
filter: ProbeFilterState;
|
|
1039
|
-
rawFaceCursor: number;
|
|
1040
|
-
readonly raw: ProbeCubeSet;
|
|
1041
|
-
active: ProbeCubeSet | undefined;
|
|
1042
|
-
candidate: ProbeCubeSet;
|
|
1043
|
-
spare: ProbeCubeSet | undefined;
|
|
1044
|
-
readonly sampler: Sampler;
|
|
1045
|
-
readonly uniformBuffer: Buffer;
|
|
1046
|
-
readonly filterGroup1: BindGroup;
|
|
1047
|
-
pending?: {
|
|
1048
|
-
readonly rawCaptureFace: number | undefined;
|
|
1049
|
-
readonly step: ReturnType<typeof advanceProbeFilter>;
|
|
1050
|
-
};
|
|
1051
|
-
};
|
|
1052
|
-
|
|
1053
|
-
type ProbeSharedResources = {
|
|
1054
|
-
readonly faceUniforms: Buffer;
|
|
1055
|
-
readonly prefilterUniforms: Buffer;
|
|
1056
|
-
readonly vertexBuffer: Buffer;
|
|
1057
|
-
readonly sampler: Sampler;
|
|
1058
|
-
readonly filterPipeline: RenderPipeline;
|
|
1059
|
-
readonly cubeGroup1Bgl: BindGroupLayout;
|
|
1060
|
-
readonly prefilterGroup0Bgl: BindGroupLayout;
|
|
1061
|
-
};
|
|
1062
|
-
|
|
1063
|
-
/** Renderer-owned raw capture and incremental PMREM/LKG promotion owner. */
|
|
1064
|
-
class ReflectionProbeRecordOwner {
|
|
1065
|
-
private readonly resources = new Map<string, ProbeGpuResource>();
|
|
1066
|
-
private shared: ProbeSharedResources | undefined;
|
|
1067
|
-
private pipelineWarmup: Promise<void> | undefined;
|
|
1068
|
-
private pipelineWarmupFailure: string | undefined;
|
|
1069
|
-
private pipelineWarmupAttempts = 0;
|
|
1070
|
-
private lastFactCount = 0;
|
|
1071
|
-
private lastAcceptedCount = 0;
|
|
1072
|
-
private lastScheduledRawFaces = 0;
|
|
1073
|
-
private lastScheduledFilteredSteps = 0;
|
|
1074
|
-
private lastSelection: ReflectionProbeSelectionInspection = { kind: 'skylight' };
|
|
1075
|
-
private readonly outputFormat: TextureFormat;
|
|
1076
|
-
|
|
1077
|
-
constructor(private readonly internals: RenderSystemInternals) {
|
|
1078
|
-
this.outputFormat = internals.device.caps.rgba16floatRenderable ? 'rgba16float' : 'rgba8unorm';
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
inspect(): ReflectionProbeInspection {
|
|
1082
|
-
let rawFacesCaptured = 0;
|
|
1083
|
-
let filteredStepsCompleted = 0;
|
|
1084
|
-
const filteredMipLevels = new Set<number>();
|
|
1085
|
-
let activeCount = 0;
|
|
1086
|
-
for (const resource of this.resources.values()) {
|
|
1087
|
-
rawFacesCaptured += resource.rawFaceCursor;
|
|
1088
|
-
filteredStepsCompleted += resource.filter.cursor;
|
|
1089
|
-
const completedMipCount = Math.min(
|
|
1090
|
-
PREFILTER_MIP_LEVELS,
|
|
1091
|
-
Math.floor(resource.filter.cursor / resource.filter.faceCount),
|
|
1092
|
-
);
|
|
1093
|
-
for (let mip = 0; mip < completedMipCount; mip += 1) filteredMipLevels.add(mip);
|
|
1094
|
-
if (resource.active !== undefined) activeCount += 1;
|
|
1095
|
-
}
|
|
1096
|
-
return {
|
|
1097
|
-
selection: this.lastSelection,
|
|
1098
|
-
factCount: this.lastFactCount,
|
|
1099
|
-
acceptedCount: this.lastAcceptedCount,
|
|
1100
|
-
activeCount,
|
|
1101
|
-
rawFacesCaptured,
|
|
1102
|
-
filteredStepsCompleted,
|
|
1103
|
-
filteredMipLevels: [...filteredMipLevels].sort((a, b) => a - b),
|
|
1104
|
-
scheduledRawFaces: this.lastScheduledRawFaces,
|
|
1105
|
-
scheduledFilteredSteps: this.lastScheduledFilteredSteps,
|
|
1106
|
-
pipelineReady: this.shared !== undefined,
|
|
1107
|
-
pipelineWarmupAttempts: this.pipelineWarmupAttempts,
|
|
1108
|
-
...(this.pipelineWarmupFailure === undefined
|
|
1109
|
-
? {}
|
|
1110
|
-
: { pipelineWarmupFailure: this.pipelineWarmupFailure }),
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
private ensurePipelineWarmup(): void {
|
|
1115
|
-
if (this.shared !== undefined || this.pipelineWarmup !== undefined) return;
|
|
1116
|
-
const factory = this.internals.shaderModuleFactory;
|
|
1117
|
-
if (factory === undefined) return;
|
|
1118
|
-
const attempt = this.pipelineWarmupAttempts + 1;
|
|
1119
|
-
this.pipelineWarmupAttempts = attempt;
|
|
1120
|
-
this.pipelineWarmup = createIblPipelines(
|
|
1121
|
-
this.internals.deviceScope,
|
|
1122
|
-
this.internals.device,
|
|
1123
|
-
(device, descriptor) => {
|
|
1124
|
-
const labeled = {
|
|
1125
|
-
...descriptor,
|
|
1126
|
-
label: `${descriptor.label ?? 'ibl'}-reflection-probe-${attempt}`,
|
|
1127
|
-
};
|
|
1128
|
-
return this.internals.createShaderModule === undefined
|
|
1129
|
-
? Promise.resolve(factory.createShaderModule(labeled))
|
|
1130
|
-
: this.internals.createShaderModule(device, labeled);
|
|
1131
|
-
},
|
|
1132
|
-
this.outputFormat,
|
|
1133
|
-
).then((result) => {
|
|
1134
|
-
if (!result.ok) {
|
|
1135
|
-
this.pipelineWarmupFailure = `${result.error.code}: ${result.error.hint}`;
|
|
1136
|
-
console.warn('[forgeax] ReflectionProbe IBL warmup retry', this.pipelineWarmupFailure);
|
|
1137
|
-
this.pipelineWarmup = undefined;
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
this.shared = this.createSharedResources(result.value.prefilterPipeline);
|
|
1141
|
-
if (this.shared === undefined) {
|
|
1142
|
-
this.pipelineWarmupFailure = 'renderer-owned IBL resources could not be allocated';
|
|
1143
|
-
console.warn('[forgeax] ReflectionProbe IBL resource allocation retry');
|
|
1144
|
-
this.pipelineWarmup = undefined;
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
prepare(
|
|
1150
|
-
facts: readonly ReflectionProbeFact[],
|
|
1151
|
-
selections: ReadonlyMap<string, ReflectionProbeSelectionResult>,
|
|
1152
|
-
displayCamera: CameraSnapshot | undefined,
|
|
1153
|
-
): ReflectionProbeRecordState {
|
|
1154
|
-
this.lastFactCount = facts.length;
|
|
1155
|
-
this.lastScheduledRawFaces = 0;
|
|
1156
|
-
this.lastScheduledFilteredSteps = 0;
|
|
1157
|
-
const selectedProbe = [...selections.values()]
|
|
1158
|
-
.filter(
|
|
1159
|
-
(selection): selection is Extract<ReflectionProbeSelectionResult, { kind: 'probe' }> =>
|
|
1160
|
-
selection.kind === 'probe',
|
|
1161
|
-
)
|
|
1162
|
-
.sort((a, b) => a.worldId - b.worldId || a.entityKey - b.entityKey)[0];
|
|
1163
|
-
this.lastSelection =
|
|
1164
|
-
selectedProbe === undefined
|
|
1165
|
-
? { kind: 'skylight' }
|
|
1166
|
-
: { kind: 'probe', worldId: selectedProbe.worldId, entityKey: selectedProbe.entityKey };
|
|
1167
|
-
if (facts.length > 0) this.ensurePipelineWarmup();
|
|
1168
|
-
void this.pipelineWarmup;
|
|
1169
|
-
const ordered = facts
|
|
1170
|
-
.slice()
|
|
1171
|
-
.sort((a, b) => a.worldId - b.worldId || a.entityKey - b.entityKey);
|
|
1172
|
-
let acceptedCount = 0;
|
|
1173
|
-
let acceptedBytes = 0;
|
|
1174
|
-
const accepted = new Map<string, ProbeGpuResource>();
|
|
1175
|
-
for (const fact of ordered) {
|
|
1176
|
-
const admission = admitReflectionProbe(
|
|
1177
|
-
fact,
|
|
1178
|
-
{ acceptedCount, acceptedBytes },
|
|
1179
|
-
DEFAULT_REFLECTION_PROBE_LIMITS,
|
|
1180
|
-
);
|
|
1181
|
-
if (!admission.ok) continue;
|
|
1182
|
-
const key = `${fact.worldId}:${fact.entityKey}`;
|
|
1183
|
-
const resource = this.resourceFor(key, fact, acceptedCount);
|
|
1184
|
-
if (resource === undefined) continue;
|
|
1185
|
-
acceptedCount += 1;
|
|
1186
|
-
acceptedBytes = admission.acceptedBytes;
|
|
1187
|
-
accepted.set(key, resource);
|
|
1188
|
-
}
|
|
1189
|
-
for (const [key, resource] of this.resources) {
|
|
1190
|
-
if (!accepted.has(key)) {
|
|
1191
|
-
this.destroyProbeResource(resource);
|
|
1192
|
-
this.resources.delete(key);
|
|
1193
|
-
} else resource.index = [...accepted.keys()].indexOf(key);
|
|
1194
|
-
}
|
|
1195
|
-
const rows = [...accepted.values()]
|
|
1196
|
-
.map((resource) => {
|
|
1197
|
-
const active = resource.active;
|
|
1198
|
-
if (active === undefined) return undefined;
|
|
1199
|
-
return {
|
|
1200
|
-
primitiveKey: `${resource.fact.worldId}:${resource.fact.entityKey}`,
|
|
1201
|
-
index: resource.index,
|
|
1202
|
-
worldId: resource.fact.worldId,
|
|
1203
|
-
entityKey: resource.fact.entityKey,
|
|
1204
|
-
center: resource.fact.center,
|
|
1205
|
-
halfExtents: resource.fact.halfExtents,
|
|
1206
|
-
intensity: resource.fact.intensity,
|
|
1207
|
-
generation: resource.filter.activeGeneration,
|
|
1208
|
-
filteredView: active.cubeView,
|
|
1209
|
-
sampler: resource.sampler,
|
|
1210
|
-
uniformBuffer: resource.uniformBuffer,
|
|
1211
|
-
};
|
|
1212
|
-
})
|
|
1213
|
-
.filter((row): row is NonNullable<typeof row> => row !== undefined);
|
|
1214
|
-
const table = buildReflectionProbeTable(rows);
|
|
1215
|
-
const work: ReflectionProbeGraphWork[] = [];
|
|
1216
|
-
for (const resource of accepted.values()) {
|
|
1217
|
-
const rawCaptureFace =
|
|
1218
|
-
resource.rawFaceCursor < resource.raw.faceViews.length ? resource.rawFaceCursor : undefined;
|
|
1219
|
-
const step =
|
|
1220
|
-
resource.rawFaceCursor >= resource.raw.faceViews.length
|
|
1221
|
-
? advanceProbeFilter(resource.filter)
|
|
1222
|
-
: undefined;
|
|
1223
|
-
if (this.shared === undefined) continue;
|
|
1224
|
-
if (rawCaptureFace === undefined && step === undefined) continue;
|
|
1225
|
-
const filterGroup0 =
|
|
1226
|
-
step === undefined
|
|
1227
|
-
? undefined
|
|
1228
|
-
: this.internals.device.createBindGroup({
|
|
1229
|
-
label: `reflection-probe-${resource.index}-pmrem-${step.mipLevel}-${step.faceIndex}`,
|
|
1230
|
-
layout: this.shared.prefilterGroup0Bgl,
|
|
1231
|
-
entries: [
|
|
1232
|
-
{
|
|
1233
|
-
binding: 0,
|
|
1234
|
-
resource: {
|
|
1235
|
-
kind: 'buffer',
|
|
1236
|
-
value: {
|
|
1237
|
-
buffer: this.shared.faceUniforms,
|
|
1238
|
-
offset: step.faceIndex * 256,
|
|
1239
|
-
size: 64,
|
|
1240
|
-
},
|
|
1241
|
-
},
|
|
1242
|
-
},
|
|
1243
|
-
{
|
|
1244
|
-
binding: 1,
|
|
1245
|
-
resource: {
|
|
1246
|
-
kind: 'buffer',
|
|
1247
|
-
value: {
|
|
1248
|
-
buffer: this.shared.prefilterUniforms,
|
|
1249
|
-
offset: (step.mipLevel * 6 + step.faceIndex) * 256,
|
|
1250
|
-
size: 16,
|
|
1251
|
-
},
|
|
1252
|
-
},
|
|
1253
|
-
},
|
|
1254
|
-
],
|
|
1255
|
-
});
|
|
1256
|
-
if (filterGroup0 !== undefined && !filterGroup0.ok) continue;
|
|
1257
|
-
if (resource.pending !== undefined) continue;
|
|
1258
|
-
resource.pending = { rawCaptureFace, step };
|
|
1259
|
-
if (rawCaptureFace !== undefined) this.lastScheduledRawFaces += 1;
|
|
1260
|
-
if (step !== undefined) this.lastScheduledFilteredSteps += 1;
|
|
1261
|
-
const face =
|
|
1262
|
-
rawCaptureFace === undefined
|
|
1263
|
-
? undefined
|
|
1264
|
-
: buildCubeCameraFaceViews({
|
|
1265
|
-
position: resource.fact.center,
|
|
1266
|
-
near: 0.1,
|
|
1267
|
-
far: 100,
|
|
1268
|
-
})[rawCaptureFace];
|
|
1269
|
-
const faceCamera =
|
|
1270
|
-
face === undefined || displayCamera === undefined
|
|
1271
|
-
? undefined
|
|
1272
|
-
: {
|
|
1273
|
-
...displayCamera,
|
|
1274
|
-
position: vec3.create(
|
|
1275
|
-
resource.fact.center[0] ?? 0,
|
|
1276
|
-
resource.fact.center[1] ?? 0,
|
|
1277
|
-
resource.fact.center[2] ?? 0,
|
|
1278
|
-
),
|
|
1279
|
-
world: mat4.invert(mat4.create(), face.view),
|
|
1280
|
-
fov: Math.PI / 2,
|
|
1281
|
-
aspect: 1,
|
|
1282
|
-
near: face.near,
|
|
1283
|
-
far: face.far,
|
|
1284
|
-
projection: 'perspective' as const,
|
|
1285
|
-
};
|
|
1286
|
-
work.push({
|
|
1287
|
-
probeIndex: resource.index,
|
|
1288
|
-
rawTexture: resource.raw.texture,
|
|
1289
|
-
rawCubeView: resource.raw.cubeView,
|
|
1290
|
-
rawFaceViews: resource.raw.faceViews,
|
|
1291
|
-
rawDepthTexture: resource.raw.depthTexture,
|
|
1292
|
-
filteredTexture: resource.candidate.texture,
|
|
1293
|
-
filteredCubeView: resource.candidate.cubeView,
|
|
1294
|
-
filteredFaceViewsByMip: resource.candidate.mipViews,
|
|
1295
|
-
outputFormat: this.outputFormat,
|
|
1296
|
-
sampler: resource.sampler,
|
|
1297
|
-
filterPipeline: this.shared.filterPipeline,
|
|
1298
|
-
filterGroup0: filterGroup0?.value,
|
|
1299
|
-
filterGroup1: resource.filterGroup1,
|
|
1300
|
-
cubeVertexBuffer: this.shared.vertexBuffer,
|
|
1301
|
-
rawCaptureFace,
|
|
1302
|
-
rawDepthView: resource.raw.depthView,
|
|
1303
|
-
rawSize: resource.raw.size,
|
|
1304
|
-
...(faceCamera === undefined ? {} : { faceCamera }),
|
|
1305
|
-
...(rawCaptureFace === undefined
|
|
1306
|
-
? {}
|
|
1307
|
-
: {
|
|
1308
|
-
viewBindGroupDynamicOffset:
|
|
1309
|
-
(REFLECTION_PROBE_VIEW_SLOT_BASE + resource.index) * VIEW_UNIFORM_SLOT_STRIDE,
|
|
1310
|
-
}),
|
|
1311
|
-
filteredSize: resource.candidate.size,
|
|
1312
|
-
step,
|
|
1313
|
-
});
|
|
1314
|
-
}
|
|
1315
|
-
return {
|
|
1316
|
-
table,
|
|
1317
|
-
selections,
|
|
1318
|
-
graph: { work },
|
|
1319
|
-
completeSubmission: (submitted, completed) => this.completeSubmission(submitted, completed),
|
|
1320
|
-
};
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
private completeSubmission(submitted: boolean, completed?: Promise<unknown>): void {
|
|
1324
|
-
if (completed !== undefined) {
|
|
1325
|
-
void completed.then(
|
|
1326
|
-
() => this.completeSubmission(submitted),
|
|
1327
|
-
() => this.completeSubmission(false),
|
|
1328
|
-
);
|
|
1329
|
-
return;
|
|
1330
|
-
}
|
|
1331
|
-
for (const resource of this.resources.values()) {
|
|
1332
|
-
const pending = resource.pending;
|
|
1333
|
-
delete resource.pending;
|
|
1334
|
-
if (!submitted || pending === undefined) continue;
|
|
1335
|
-
if (pending.rawCaptureFace !== undefined) {
|
|
1336
|
-
resource.rawFaceCursor = Math.min(
|
|
1337
|
-
resource.rawFaceCursor + 1,
|
|
1338
|
-
resource.raw.faceViews.length,
|
|
1339
|
-
);
|
|
1340
|
-
continue;
|
|
1341
|
-
}
|
|
1342
|
-
if (pending.step === undefined) continue;
|
|
1343
|
-
resource.filter = commitProbeFilterStep(resource.filter, true);
|
|
1344
|
-
if (resource.filter.cursor >= resource.filter.faceCount * resource.filter.mipCount) {
|
|
1345
|
-
const previous = resource.active;
|
|
1346
|
-
resource.active = resource.candidate;
|
|
1347
|
-
resource.candidate = resource.spare ?? previous ?? resource.candidate;
|
|
1348
|
-
resource.spare = undefined;
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
dispose(): void {
|
|
1354
|
-
for (const resource of this.resources.values()) {
|
|
1355
|
-
this.destroyProbeResource(resource);
|
|
1356
|
-
}
|
|
1357
|
-
this.resources.clear();
|
|
1358
|
-
if (this.shared !== undefined) {
|
|
1359
|
-
this.internals.device.destroyBuffer(this.shared.faceUniforms);
|
|
1360
|
-
this.internals.device.destroyBuffer(this.shared.prefilterUniforms);
|
|
1361
|
-
this.internals.device.destroyBuffer(this.shared.vertexBuffer);
|
|
1362
|
-
}
|
|
1363
|
-
this.shared = undefined;
|
|
1364
|
-
this.pipelineWarmup = undefined;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
private destroyProbeResource(resource: ProbeGpuResource): void {
|
|
1368
|
-
const sets = new Set<ProbeCubeSet>([resource.raw, resource.candidate]);
|
|
1369
|
-
if (resource.active !== undefined) sets.add(resource.active);
|
|
1370
|
-
if (resource.spare !== undefined) sets.add(resource.spare);
|
|
1371
|
-
for (const set of sets) this.destroyCubeSet(set);
|
|
1372
|
-
this.internals.device.destroyBuffer(resource.uniformBuffer);
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
private destroyCubeSet(set: ProbeCubeSet): void {
|
|
1376
|
-
this.internals.device.destroyTexture(set.texture);
|
|
1377
|
-
this.internals.device.destroyTexture(set.depthTexture);
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
private resourceFor(
|
|
1381
|
-
key: string,
|
|
1382
|
-
fact: ReflectionProbeFact,
|
|
1383
|
-
index: number,
|
|
1384
|
-
): ProbeGpuResource | undefined {
|
|
1385
|
-
const existing = this.resources.get(key);
|
|
1386
|
-
if (existing !== undefined) {
|
|
1387
|
-
if (existing.fact.revision !== fact.revision) {
|
|
1388
|
-
existing.fact = fact;
|
|
1389
|
-
existing.filter = createProbeFilterState({
|
|
1390
|
-
probeIndex: index,
|
|
1391
|
-
mipCount: PREFILTER_MIP_LEVELS,
|
|
1392
|
-
activeGeneration: existing.filter.activeGeneration,
|
|
1393
|
-
});
|
|
1394
|
-
existing.rawFaceCursor = 0;
|
|
1395
|
-
} else {
|
|
1396
|
-
existing.fact = fact;
|
|
1397
|
-
existing.index = index;
|
|
1398
|
-
}
|
|
1399
|
-
return existing;
|
|
1400
|
-
}
|
|
1401
|
-
if (this.shared === undefined) return undefined;
|
|
1402
|
-
const raw = this.createCubeSet(`reflection-probe-${index}-raw`, fact.resolution);
|
|
1403
|
-
const candidate = this.createCubeSet(`reflection-probe-${index}-candidate`, fact.resolution);
|
|
1404
|
-
const spare = this.createCubeSet(`reflection-probe-${index}-spare`, fact.resolution);
|
|
1405
|
-
if (raw === undefined || candidate === undefined || spare === undefined) return undefined;
|
|
1406
|
-
const uniformBuffer = this.internals.device.createBuffer({
|
|
1407
|
-
label: `reflection-probe-${index}-uniforms`,
|
|
1408
|
-
size: 32,
|
|
1409
|
-
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1410
|
-
mappedAtCreation: false,
|
|
1411
|
-
});
|
|
1412
|
-
if (!uniformBuffer.ok) return undefined;
|
|
1413
|
-
const payload = new Float32Array([
|
|
1414
|
-
-Math.max(0, fact.intensity),
|
|
1415
|
-
fact.center[0],
|
|
1416
|
-
fact.center[1],
|
|
1417
|
-
fact.center[2],
|
|
1418
|
-
fact.halfExtents[0],
|
|
1419
|
-
fact.halfExtents[1],
|
|
1420
|
-
fact.halfExtents[2],
|
|
1421
|
-
0,
|
|
1422
|
-
]);
|
|
1423
|
-
const written = this.internals.device.queue.writeBuffer(uniformBuffer.value, 0, payload);
|
|
1424
|
-
if (!written.ok) return undefined;
|
|
1425
|
-
const filterGroup1 = this.internals.device.createBindGroup({
|
|
1426
|
-
label: `reflection-probe-${index}-filter-input`,
|
|
1427
|
-
layout: this.shared.cubeGroup1Bgl,
|
|
1428
|
-
entries: [
|
|
1429
|
-
{ binding: 0, resource: { kind: 'textureView', value: raw.cubeView } },
|
|
1430
|
-
{ binding: 1, resource: { kind: 'sampler', value: this.shared.sampler } },
|
|
1431
|
-
],
|
|
1432
|
-
});
|
|
1433
|
-
if (!filterGroup1.ok) return undefined;
|
|
1434
|
-
const resource: ProbeGpuResource = {
|
|
1435
|
-
fact,
|
|
1436
|
-
index,
|
|
1437
|
-
filter: createProbeFilterState({ probeIndex: index, mipCount: PREFILTER_MIP_LEVELS }),
|
|
1438
|
-
rawFaceCursor: 0,
|
|
1439
|
-
raw,
|
|
1440
|
-
active: undefined,
|
|
1441
|
-
candidate,
|
|
1442
|
-
spare,
|
|
1443
|
-
sampler: this.shared.sampler,
|
|
1444
|
-
uniformBuffer: uniformBuffer.value,
|
|
1445
|
-
filterGroup1: filterGroup1.value,
|
|
1446
|
-
};
|
|
1447
|
-
this.resources.set(key, resource);
|
|
1448
|
-
return resource;
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
private createSharedResources(filterPipeline: RenderPipeline): ProbeSharedResources | undefined {
|
|
1452
|
-
const faceUniforms = createFaceUniformsBuffer(this.internals.device);
|
|
1453
|
-
const prefilterUniforms = createPrefilterUniformsBuffer(this.internals.device);
|
|
1454
|
-
if (!faceUniforms.ok || !prefilterUniforms.ok) return undefined;
|
|
1455
|
-
if (!writeAllFaceUniforms(this.internals.device, faceUniforms.value).ok) return undefined;
|
|
1456
|
-
if (!writeAllPrefilterUniforms(this.internals.device, prefilterUniforms.value).ok)
|
|
1457
|
-
return undefined;
|
|
1458
|
-
const vertexBuffer = this.internals.device.createBuffer({
|
|
1459
|
-
label: 'reflection-probe-capture-cube-vertices',
|
|
1460
|
-
size: CUBEMAP_FACE_VERTICES.byteLength,
|
|
1461
|
-
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
1462
|
-
mappedAtCreation: false,
|
|
1463
|
-
});
|
|
1464
|
-
if (!vertexBuffer.ok) return undefined;
|
|
1465
|
-
const vertexWrite = this.internals.device.queue.writeBuffer(
|
|
1466
|
-
vertexBuffer.value,
|
|
1467
|
-
0,
|
|
1468
|
-
CUBEMAP_FACE_VERTICES,
|
|
1469
|
-
);
|
|
1470
|
-
if (!vertexWrite.ok) return undefined;
|
|
1471
|
-
const sampler = this.internals.device.createSampler({
|
|
1472
|
-
label: 'reflection-probe-pmrem-sampler',
|
|
1473
|
-
magFilter: 'linear',
|
|
1474
|
-
minFilter: 'linear',
|
|
1475
|
-
mipmapFilter: 'linear',
|
|
1476
|
-
addressModeU: 'clamp-to-edge',
|
|
1477
|
-
addressModeV: 'clamp-to-edge',
|
|
1478
|
-
addressModeW: 'clamp-to-edge',
|
|
1479
|
-
});
|
|
1480
|
-
const cache = getOrCreateIblCache(this.internals.deviceScope);
|
|
1481
|
-
if (
|
|
1482
|
-
!sampler.ok ||
|
|
1483
|
-
cache.cubeGroup1Bgl === undefined ||
|
|
1484
|
-
cache.prefilterGroup0Bgl === undefined
|
|
1485
|
-
) {
|
|
1486
|
-
return undefined;
|
|
1487
|
-
}
|
|
1488
|
-
return {
|
|
1489
|
-
faceUniforms: faceUniforms.value,
|
|
1490
|
-
prefilterUniforms: prefilterUniforms.value,
|
|
1491
|
-
vertexBuffer: vertexBuffer.value,
|
|
1492
|
-
sampler: sampler.value,
|
|
1493
|
-
filterPipeline,
|
|
1494
|
-
cubeGroup1Bgl: cache.cubeGroup1Bgl,
|
|
1495
|
-
prefilterGroup0Bgl: cache.prefilterGroup0Bgl,
|
|
1496
|
-
};
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
private createCubeSet(label: string, requestedSize: number): ProbeCubeSet | undefined {
|
|
1500
|
-
const size = Math.min(Math.max(1, Math.floor(requestedSize)), PREFILTER_SIZE);
|
|
1501
|
-
const mipCount = Math.min(PREFILTER_MIP_LEVELS, Math.floor(Math.log2(size)) + 1);
|
|
1502
|
-
const texture = this.internals.device.createTexture({
|
|
1503
|
-
label,
|
|
1504
|
-
size: { width: size, height: size, depthOrArrayLayers: 6 },
|
|
1505
|
-
mipLevelCount: mipCount,
|
|
1506
|
-
sampleCount: 1,
|
|
1507
|
-
dimension: '2d',
|
|
1508
|
-
format: this.outputFormat,
|
|
1509
|
-
usage: GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_SRC,
|
|
1510
|
-
viewFormats: [],
|
|
1511
|
-
textureBindingViewDimension: undefined,
|
|
1512
|
-
});
|
|
1513
|
-
if (!texture.ok) return undefined;
|
|
1514
|
-
const cubeView = this.internals.device.createTextureView(texture.value, {
|
|
1515
|
-
label: `${label}-cube`,
|
|
1516
|
-
dimension: 'cube',
|
|
1517
|
-
baseMipLevel: 0,
|
|
1518
|
-
mipLevelCount: mipCount,
|
|
1519
|
-
arrayLayerCount: 6,
|
|
1520
|
-
});
|
|
1521
|
-
if (!cubeView.ok) return undefined;
|
|
1522
|
-
const faceViews: TextureView[] = [];
|
|
1523
|
-
const mipViews: TextureView[][] = [];
|
|
1524
|
-
for (let mip = 0; mip < mipCount; mip += 1) {
|
|
1525
|
-
const faces: TextureView[] = [];
|
|
1526
|
-
for (let face = 0; face < 6; face += 1) {
|
|
1527
|
-
const view = this.internals.device.createTextureView(texture.value, {
|
|
1528
|
-
label: `${label}-mip${mip}-face${face}`,
|
|
1529
|
-
dimension: '2d',
|
|
1530
|
-
baseMipLevel: mip,
|
|
1531
|
-
mipLevelCount: 1,
|
|
1532
|
-
baseArrayLayer: face,
|
|
1533
|
-
arrayLayerCount: 1,
|
|
1534
|
-
});
|
|
1535
|
-
if (!view.ok) return undefined;
|
|
1536
|
-
if (mip === 0) faceViews.push(view.value);
|
|
1537
|
-
faces.push(view.value);
|
|
1538
|
-
}
|
|
1539
|
-
mipViews.push(faces);
|
|
1540
|
-
}
|
|
1541
|
-
const depthTexture = this.internals.device.createTexture({
|
|
1542
|
-
label: `${label}-depth`,
|
|
1543
|
-
size: { width: size, height: size, depthOrArrayLayers: 1 },
|
|
1544
|
-
mipLevelCount: 1,
|
|
1545
|
-
sampleCount: 1,
|
|
1546
|
-
dimension: '2d',
|
|
1547
|
-
format: 'depth24plus-stencil8',
|
|
1548
|
-
usage: 0x10,
|
|
1549
|
-
viewFormats: [],
|
|
1550
|
-
textureBindingViewDimension: undefined,
|
|
1551
|
-
});
|
|
1552
|
-
if (!depthTexture.ok) return undefined;
|
|
1553
|
-
const depthView = this.internals.device.createTextureView(depthTexture.value, {
|
|
1554
|
-
label: `${label}-depth-view`,
|
|
1555
|
-
dimension: '2d',
|
|
1556
|
-
});
|
|
1557
|
-
if (!depthView.ok) return undefined;
|
|
1558
|
-
return {
|
|
1559
|
-
texture: texture.value,
|
|
1560
|
-
cubeView: cubeView.value,
|
|
1561
|
-
faceViews,
|
|
1562
|
-
mipViews,
|
|
1563
|
-
depthTexture: depthTexture.value,
|
|
1564
|
-
depthView: depthView.value,
|
|
1565
|
-
size,
|
|
1566
|
-
};
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
1064
|
export function createRenderSystem(internals: RenderSystemInternals): RenderSystem {
|
|
1571
1065
|
const phaseCatalogRegistration = internals.profiler?.registerPhaseCatalog(
|
|
1572
1066
|
'render',
|
|
@@ -1578,6 +1072,38 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1578
1072
|
let latestCamera: CameraSnapshot | undefined;
|
|
1579
1073
|
const pointsLinesOwner = new StandardPointsLinesOwner(internals);
|
|
1580
1074
|
const reflectionProbeOwner = new ReflectionProbeRecordOwner(internals);
|
|
1075
|
+
let ssrFormatReceipt: import('@forgeax/engine-rhi').RhiTextureFormatCapabilityReceipt | undefined;
|
|
1076
|
+
let ssrFormatProbeGeneration = -1;
|
|
1077
|
+
let ssrFormatProbe: Promise<void> | undefined;
|
|
1078
|
+
let ssrFallbackGeneration: number | undefined;
|
|
1079
|
+
const ensureSsrFormatProbe = (): void => {
|
|
1080
|
+
if (internals.ssrIdentity === undefined) return;
|
|
1081
|
+
const generation = internals.deviceScope.generation;
|
|
1082
|
+
if (
|
|
1083
|
+
ssrFormatReceipt?.deviceGeneration === generation ||
|
|
1084
|
+
ssrFormatProbeGeneration === generation ||
|
|
1085
|
+
ssrFormatProbe !== undefined ||
|
|
1086
|
+
typeof internals.device.probeTextureFormatCapability !== 'function'
|
|
1087
|
+
) {
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
ssrFormatProbeGeneration = generation;
|
|
1091
|
+
const device = internals.device;
|
|
1092
|
+
ssrFormatProbe = device
|
|
1093
|
+
.probeTextureFormatCapability()
|
|
1094
|
+
.then((result) => {
|
|
1095
|
+
if (internals.device !== device || internals.deviceScope.generation !== generation) return;
|
|
1096
|
+
ssrFormatReceipt = result.ok ? result.value : undefined;
|
|
1097
|
+
})
|
|
1098
|
+
.catch(() => {
|
|
1099
|
+
if (internals.device === device && internals.deviceScope.generation === generation) {
|
|
1100
|
+
ssrFormatReceipt = undefined;
|
|
1101
|
+
}
|
|
1102
|
+
})
|
|
1103
|
+
.finally(() => {
|
|
1104
|
+
ssrFormatProbe = undefined;
|
|
1105
|
+
});
|
|
1106
|
+
};
|
|
1581
1107
|
const cubeCaptureScheduler = createCubeCaptureScheduler({ maxFacesPerFrame: 1 });
|
|
1582
1108
|
const cubeCaptureState: CubeCaptureGraphState = { work: [] };
|
|
1583
1109
|
const persistentRenderScene = new PersistentRenderScene({
|
|
@@ -1587,7 +1113,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1587
1113
|
internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
|
|
1588
1114
|
},
|
|
1589
1115
|
});
|
|
1590
|
-
|
|
1116
|
+
let environmentLifecycle = new EnvironmentLifecycle(internals.deviceScope);
|
|
1591
1117
|
const transmissionAdmission = new TransmissionCandidateAdmission();
|
|
1592
1118
|
let lastTransmissionInspection: TransmissionInspection | undefined;
|
|
1593
1119
|
let lastTransmissionKey = '';
|
|
@@ -1712,8 +1238,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1712
1238
|
});
|
|
1713
1239
|
lastTransmissionKey = key;
|
|
1714
1240
|
};
|
|
1241
|
+
// GPU-driven production uses the validated adapter for normal frames. The
|
|
1242
|
+
// assembly-supplied closure resolves its device-bound adapter at call time,
|
|
1243
|
+
// so recovery candidate preparation still follows candidateShaderState
|
|
1244
|
+
// rather than retaining a lost-generation module.
|
|
1715
1245
|
const gpuDrivenShaderFactory =
|
|
1716
1246
|
internals.shaderModuleFactory ??
|
|
1247
|
+
internals.immediateShaderModuleFactory ??
|
|
1717
1248
|
({
|
|
1718
1249
|
createShaderModule: () =>
|
|
1719
1250
|
err(
|
|
@@ -1724,35 +1255,54 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1724
1255
|
}),
|
|
1725
1256
|
),
|
|
1726
1257
|
} satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory);
|
|
1727
|
-
let gpuDrivenProduction =
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1258
|
+
let gpuDrivenProduction = GpuDrivenProduction.forDevice(internals.device, gpuDrivenShaderFactory);
|
|
1259
|
+
let occlusionRuntime: OcclusionRenderRuntime | undefined = new OcclusionRenderRuntime(
|
|
1260
|
+
internals.device,
|
|
1261
|
+
persistentRenderScene.visibilityFacetStore(),
|
|
1262
|
+
gpuDrivenShaderFactory,
|
|
1263
|
+
persistentRenderScene.visibilityBudgetValue(),
|
|
1264
|
+
);
|
|
1265
|
+
const ensureOcclusionRuntime = (): OcclusionRenderRuntime => {
|
|
1266
|
+
if (occlusionRuntime === undefined) {
|
|
1267
|
+
occlusionRuntime = new OcclusionRenderRuntime(
|
|
1268
|
+
internals.device,
|
|
1269
|
+
persistentRenderScene.visibilityFacetStore(),
|
|
1270
|
+
gpuDrivenShaderFactory,
|
|
1271
|
+
persistentRenderScene.visibilityBudgetValue(),
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
return occlusionRuntime;
|
|
1275
|
+
};
|
|
1276
|
+
const createFeatureGpuWorkOwner = (runtime: RenderSystemInternals): RenderFeatureGpuWorkOwner =>
|
|
1277
|
+
createRenderFeatureGpuWorkOwner({
|
|
1278
|
+
getDevice: () => runtime.device,
|
|
1279
|
+
getShaderModuleFactory: () =>
|
|
1280
|
+
runtime.shaderModuleFactory ??
|
|
1281
|
+
({
|
|
1282
|
+
createShaderModule: () =>
|
|
1283
|
+
err(
|
|
1284
|
+
new RhiError({
|
|
1285
|
+
code: 'rhi-not-available',
|
|
1286
|
+
expected: 'the renderer backend exposes shader module creation',
|
|
1287
|
+
hint: 'construct the renderer through the backend pack before activating GPU features',
|
|
1288
|
+
}),
|
|
1289
|
+
),
|
|
1290
|
+
} satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory),
|
|
1291
|
+
getImmediateShaderModuleFactory: () =>
|
|
1292
|
+
runtime.immediateShaderModuleFactory ??
|
|
1293
|
+
runtime.shaderModuleFactory ??
|
|
1294
|
+
({
|
|
1295
|
+
createShaderModule: () =>
|
|
1296
|
+
err(
|
|
1297
|
+
new RhiError({
|
|
1298
|
+
code: 'rhi-not-available',
|
|
1299
|
+
expected: 'the renderer backend exposes shader module creation',
|
|
1300
|
+
hint: 'construct the renderer through the backend pack before activating GPU features',
|
|
1301
|
+
}),
|
|
1302
|
+
),
|
|
1303
|
+
} satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory),
|
|
1304
|
+
});
|
|
1305
|
+
let featureGpuWork = createFeatureGpuWorkOwner(internals);
|
|
1756
1306
|
const disposeFeatureGpuWork = (): void => {
|
|
1757
1307
|
const disposed = featureGpuWork.dispose();
|
|
1758
1308
|
if (!disposed.ok) internals.errorRegistry.fire(disposed.error);
|
|
@@ -1772,11 +1322,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1772
1322
|
// raw (material-handle-based, cross-world collision semantically correct).
|
|
1773
1323
|
const frameState: RenderFrameState = {
|
|
1774
1324
|
frameNumber: 0,
|
|
1325
|
+
reflectionFallbackDemand: false,
|
|
1775
1326
|
graphGeneration: 0,
|
|
1776
1327
|
successfulTemporalFrameIndex: 0,
|
|
1777
1328
|
lastSuccessfulBloom: 'off',
|
|
1778
1329
|
temporalFrameTransaction: createTemporalFrameTransaction({ deviceEpoch: 0 }),
|
|
1779
1330
|
temporalFrameInput: undefined,
|
|
1331
|
+
reflectionFallbackReadback: undefined,
|
|
1780
1332
|
directionalShadowCache: null,
|
|
1781
1333
|
directionalShadowCacheRecorded: false,
|
|
1782
1334
|
compiledFrameGraph: null,
|
|
@@ -1798,6 +1350,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1798
1350
|
volumetricFogHistorySignature: null,
|
|
1799
1351
|
retiredCompiledFrameGraphs: new Set(),
|
|
1800
1352
|
currentFrameObservationSource: undefined,
|
|
1353
|
+
reflectionFallbackObservationSource: undefined,
|
|
1801
1354
|
lastSuccessfulCameraAntialias: undefined,
|
|
1802
1355
|
temporalGpuState: undefined,
|
|
1803
1356
|
activeTemporalGpuState: undefined,
|
|
@@ -1809,6 +1362,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1809
1362
|
currentDirectionalShadowView: null,
|
|
1810
1363
|
currentSpotShadowView: null,
|
|
1811
1364
|
instanceBuffers: new Map(),
|
|
1365
|
+
probeBlendBuffers: new Map(),
|
|
1366
|
+
probeBlendRecordBufferCapacity: 0,
|
|
1812
1367
|
morphBuffers: new Map(),
|
|
1813
1368
|
hdrpClusterBinScratch: createClusterBinScratch(),
|
|
1814
1369
|
hdrpClusterGridScratch: null,
|
|
@@ -1820,8 +1375,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1820
1375
|
transientInstanceBuffers: [],
|
|
1821
1376
|
warnedZeroLightStandard: false,
|
|
1822
1377
|
warnedMultiLightDirectional: false,
|
|
1823
|
-
warnedMultiLightPoint: false,
|
|
1824
|
-
warnedMultiLightSpot: false,
|
|
1825
1378
|
// feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M3 / w19:
|
|
1826
1379
|
// once-warn latches for >1 Skylight / >1 SkyboxBackground (names winner).
|
|
1827
1380
|
warnedMultiSkylight: false,
|
|
@@ -1891,6 +1444,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1891
1444
|
spotShadowSnapshots: [],
|
|
1892
1445
|
};
|
|
1893
1446
|
let directFrameId = 0;
|
|
1447
|
+
let submittedFrameCount = 0;
|
|
1448
|
+
let lastSubmittedGeneration: number | undefined;
|
|
1894
1449
|
let lastDirectionalShadowLights: ExtractedLights | undefined;
|
|
1895
1450
|
let lastDirectionalShadowCandidate: 'accepted' | 'failed' = 'failed';
|
|
1896
1451
|
let lastDirectionalShadowError: DirectionalShadowInspectionSource['error'];
|
|
@@ -1949,7 +1504,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1949
1504
|
// D-3 / D-8: per-shader params UBO resource table (id -> GPU Buffer).
|
|
1950
1505
|
// Eager-created at register time when entry.params is present (byteSize >= 16,
|
|
1951
1506
|
// defaultValue.length === byteSize); reused frame-to-frame via queue.writeBuffer.
|
|
1952
|
-
|
|
1507
|
+
let postProcessParamsBuffers = new Map<string, Buffer>();
|
|
1508
|
+
let postProcessParamsDevice = internals.device;
|
|
1953
1509
|
const builtinPostProcessEntries = new Map<string, PostProcessShaderEntry>();
|
|
1954
1510
|
let activeFeaturePostProcessEntries: ReadonlyMap<string, PostProcessShaderEntry> = new Map();
|
|
1955
1511
|
const lookupPostProcess = (id: string): PostProcessShaderEntry | undefined =>
|
|
@@ -1965,6 +1521,76 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1965
1521
|
// per-frame snapshot + bind it at group(1) binding(2).
|
|
1966
1522
|
const getPostProcessParamsBuffer = (id: string): Buffer | undefined =>
|
|
1967
1523
|
postProcessParamsBuffers.get(id);
|
|
1524
|
+
const postProcessDeclarations = (): readonly (readonly [string, PostProcessShaderEntry])[] => [
|
|
1525
|
+
...builtinPostProcessEntries.entries(),
|
|
1526
|
+
...activeFeaturePostProcessEntries.entries(),
|
|
1527
|
+
];
|
|
1528
|
+
const disposePostProcessResources = (resources: RecoveryPostProcessResources): void => {
|
|
1529
|
+
for (const buffer of resources.paramsBuffers.values()) {
|
|
1530
|
+
const destroyed = resources.device.destroyBuffer(buffer);
|
|
1531
|
+
if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
const prepareRecoveryPostProcessResources = (
|
|
1535
|
+
device: RhiDevice,
|
|
1536
|
+
): Result<RecoveryPostProcessResources, RhiError> => {
|
|
1537
|
+
const paramsBuffers = new Map<string, Buffer>();
|
|
1538
|
+
try {
|
|
1539
|
+
for (const [id, entry] of postProcessDeclarations()) {
|
|
1540
|
+
if (id === 'forgeax.taa-resolve') continue;
|
|
1541
|
+
let buffer: Buffer | undefined;
|
|
1542
|
+
if (entry.params !== undefined) {
|
|
1543
|
+
const created = device.createBuffer({
|
|
1544
|
+
label: `post-process-params-${id}`,
|
|
1545
|
+
size: entry.params.byteSize,
|
|
1546
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1547
|
+
mappedAtCreation: false,
|
|
1548
|
+
});
|
|
1549
|
+
if (!created.ok) throw created.error;
|
|
1550
|
+
buffer = created.value;
|
|
1551
|
+
paramsBuffers.set(id, buffer);
|
|
1552
|
+
const written = device.queue.writeBuffer(buffer, 0, entry.params.defaultValue);
|
|
1553
|
+
if (!written.ok) throw written.error;
|
|
1554
|
+
} else if (entryHasDepthRead(entry)) {
|
|
1555
|
+
const created = device.createBuffer({
|
|
1556
|
+
label: `post-process-params-${id}`,
|
|
1557
|
+
size: DEPTH_MIN_PARAMS_BYTE_SIZE,
|
|
1558
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1559
|
+
mappedAtCreation: false,
|
|
1560
|
+
});
|
|
1561
|
+
if (!created.ok) throw created.error;
|
|
1562
|
+
buffer = created.value;
|
|
1563
|
+
}
|
|
1564
|
+
if (buffer !== undefined) paramsBuffers.set(id, buffer);
|
|
1565
|
+
}
|
|
1566
|
+
} catch (cause) {
|
|
1567
|
+
disposePostProcessResources({ device, paramsBuffers });
|
|
1568
|
+
if (cause instanceof RhiError) return err(cause);
|
|
1569
|
+
return err(
|
|
1570
|
+
new RhiError({
|
|
1571
|
+
code: 'webgpu-runtime-error',
|
|
1572
|
+
expected: 'post-process recovery resources are created on the candidate device',
|
|
1573
|
+
hint: cause instanceof Error ? cause.message : String(cause),
|
|
1574
|
+
}),
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
return ok({ device, paramsBuffers });
|
|
1578
|
+
};
|
|
1579
|
+
const publishRecoveryPostProcessResources = (candidate: RecoveryPostProcessResources): void => {
|
|
1580
|
+
if (candidate.paramsBuffers === postProcessParamsBuffers) return;
|
|
1581
|
+
const previous = { device: postProcessParamsDevice, paramsBuffers: postProcessParamsBuffers };
|
|
1582
|
+
postProcessParamsDevice = candidate.device;
|
|
1583
|
+
postProcessParamsBuffers = candidate.paramsBuffers;
|
|
1584
|
+
disposePostProcessResources(previous);
|
|
1585
|
+
};
|
|
1586
|
+
const discardRecoveryPostProcessResources = (candidate: RecoveryPostProcessResources): void => {
|
|
1587
|
+
if (candidate.paramsBuffers === postProcessParamsBuffers) {
|
|
1588
|
+
postProcessParamsBuffers = new Map();
|
|
1589
|
+
disposePostProcessResources(candidate);
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
disposePostProcessResources(candidate);
|
|
1593
|
+
};
|
|
1968
1594
|
interface PostProcessParamsCandidate {
|
|
1969
1595
|
readonly buffers: ReadonlyMap<string, Buffer>;
|
|
1970
1596
|
readonly created: readonly Buffer[];
|
|
@@ -2064,6 +1690,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2064
1690
|
const key = `${id}|${colorFormat}|${signature}`;
|
|
2065
1691
|
const cached = postProcessPipelineCache.get(key);
|
|
2066
1692
|
if (cached !== undefined) return cached;
|
|
1693
|
+
internals.recoveryColdWorkGuard?.notePipelineColdWork();
|
|
2067
1694
|
const factory = internals.buildPostProcessPipeline;
|
|
2068
1695
|
if (factory === undefined) return null;
|
|
2069
1696
|
const built = factory(
|
|
@@ -2104,6 +1731,134 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2104
1731
|
const dispatchCounts: { unlit: number } = {
|
|
2105
1732
|
unlit: 0,
|
|
2106
1733
|
};
|
|
1734
|
+
let lastLodOcclusionInspection: LodOcclusionInspection | undefined;
|
|
1735
|
+
// The GPU selector returns stable numeric World keys with its same-submit
|
|
1736
|
+
// counters. Keep the attachment join from that submit, rather than using
|
|
1737
|
+
// the current worlds[] position when the asynchronous readback completes.
|
|
1738
|
+
let lastLodWorldAttachments: ReadonlyMap<number, string> = new Map();
|
|
1739
|
+
let occlusionCandidateCache:
|
|
1740
|
+
| {
|
|
1741
|
+
readonly renderables: readonly RenderableSnapshot[];
|
|
1742
|
+
readonly camera: CameraSnapshot;
|
|
1743
|
+
readonly cameraWorldIdentity: string;
|
|
1744
|
+
readonly candidates: readonly OcclusionRuntimeCandidate[];
|
|
1745
|
+
}
|
|
1746
|
+
| undefined;
|
|
1747
|
+
let lodCandidateCountCache:
|
|
1748
|
+
| { readonly renderables: readonly RenderableSnapshot[]; readonly count: number }
|
|
1749
|
+
| undefined;
|
|
1750
|
+
let lodInspectionRenderablesCache:
|
|
1751
|
+
| {
|
|
1752
|
+
readonly submissionRenderables: readonly RenderableSnapshot[];
|
|
1753
|
+
readonly lodCandidateCount: number;
|
|
1754
|
+
readonly values: readonly RenderableSnapshot[];
|
|
1755
|
+
}
|
|
1756
|
+
| undefined;
|
|
1757
|
+
let orderedDispatchCache:
|
|
1758
|
+
| {
|
|
1759
|
+
readonly source: readonly DispatchEntry[];
|
|
1760
|
+
readonly world: World;
|
|
1761
|
+
readonly camera: readonly CameraSnapshot[];
|
|
1762
|
+
readonly renderables: readonly RenderableSnapshot[];
|
|
1763
|
+
readonly value: readonly DispatchEntry[];
|
|
1764
|
+
}
|
|
1765
|
+
| undefined;
|
|
1766
|
+
const applyLodSelectionTelemetry = (
|
|
1767
|
+
selection:
|
|
1768
|
+
| {
|
|
1769
|
+
readonly candidateCount: number;
|
|
1770
|
+
readonly visible: number;
|
|
1771
|
+
readonly occluded: number;
|
|
1772
|
+
readonly lodHistogram: readonly { readonly level: number; readonly count: number }[];
|
|
1773
|
+
readonly worldSelections?: readonly {
|
|
1774
|
+
readonly worldKey: number;
|
|
1775
|
+
readonly primitiveSlot: number;
|
|
1776
|
+
readonly slotGeneration: number;
|
|
1777
|
+
readonly candidateCount: number;
|
|
1778
|
+
readonly visible: number;
|
|
1779
|
+
readonly occluded: number;
|
|
1780
|
+
readonly lodHistogram: readonly { readonly level: number; readonly count: number }[];
|
|
1781
|
+
}[];
|
|
1782
|
+
}
|
|
1783
|
+
| undefined,
|
|
1784
|
+
): void => {
|
|
1785
|
+
if (selection === undefined || lastLodOcclusionInspection === undefined) return;
|
|
1786
|
+
const inspection = lastLodOcclusionInspection;
|
|
1787
|
+
// The GPU readback histogram is the selector fact, while final submitted
|
|
1788
|
+
// counts are owned by the persistent visibility projection. Keeping the
|
|
1789
|
+
// projection count here excludes ordinary non-LOD geometry (for example a
|
|
1790
|
+
// benchmark occluder) from the LOD workload without inventing a second
|
|
1791
|
+
// visibility owner or mixing CPU/GPU admission decisions.
|
|
1792
|
+
let worlds = inspection.worlds;
|
|
1793
|
+
const worldSelections = selection.worldSelections;
|
|
1794
|
+
if (worldSelections !== undefined && worldSelections.length > 0) {
|
|
1795
|
+
const selectionsByAttachment = new Map<string, (typeof worldSelections)[number][]>();
|
|
1796
|
+
let attributionValid = true;
|
|
1797
|
+
for (const selected of worldSelections) {
|
|
1798
|
+
const attachmentId = lastLodWorldAttachments.get(selected.worldKey);
|
|
1799
|
+
if (attachmentId === undefined) {
|
|
1800
|
+
attributionValid = false;
|
|
1801
|
+
break;
|
|
1802
|
+
}
|
|
1803
|
+
const rows = selectionsByAttachment.get(attachmentId);
|
|
1804
|
+
if (rows === undefined) selectionsByAttachment.set(attachmentId, [selected]);
|
|
1805
|
+
else rows.push(selected);
|
|
1806
|
+
}
|
|
1807
|
+
if (attributionValid) {
|
|
1808
|
+
const nextWorlds = worlds.map((world) => {
|
|
1809
|
+
const selected = selectionsByAttachment.get(world.attachmentId);
|
|
1810
|
+
const baseRow = world.rows[0];
|
|
1811
|
+
if (selected === undefined || baseRow === undefined) {
|
|
1812
|
+
if (world.rows.length > 0) attributionValid = false;
|
|
1813
|
+
return world;
|
|
1814
|
+
}
|
|
1815
|
+
const candidateCount = selected.reduce((sum, row) => sum + row.candidateCount, 0);
|
|
1816
|
+
if (candidateCount !== baseRow.count.candidates) {
|
|
1817
|
+
attributionValid = false;
|
|
1818
|
+
return world;
|
|
1819
|
+
}
|
|
1820
|
+
return Object.freeze({
|
|
1821
|
+
...world,
|
|
1822
|
+
attribution: Object.freeze({
|
|
1823
|
+
status: 'same-submit' as const,
|
|
1824
|
+
submit: inspection.submit,
|
|
1825
|
+
}),
|
|
1826
|
+
rows: Object.freeze(
|
|
1827
|
+
selected.map((row) => ({
|
|
1828
|
+
...baseRow,
|
|
1829
|
+
slot: {
|
|
1830
|
+
primitiveSlot: row.primitiveSlot,
|
|
1831
|
+
slotGeneration: row.slotGeneration,
|
|
1832
|
+
},
|
|
1833
|
+
count: {
|
|
1834
|
+
candidates: row.candidateCount,
|
|
1835
|
+
visible: row.visible,
|
|
1836
|
+
occluded: row.occluded,
|
|
1837
|
+
},
|
|
1838
|
+
lodHistogram: row.lodHistogram,
|
|
1839
|
+
})),
|
|
1840
|
+
),
|
|
1841
|
+
});
|
|
1842
|
+
});
|
|
1843
|
+
if (attributionValid) worlds = Object.freeze(nextWorlds);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
lastLodOcclusionInspection = inspectLodOcclusion({
|
|
1847
|
+
...inspection,
|
|
1848
|
+
count: {
|
|
1849
|
+
...lastLodOcclusionInspection.count,
|
|
1850
|
+
candidates: selection.candidateCount,
|
|
1851
|
+
visible: selection.visible,
|
|
1852
|
+
occluded: selection.occluded,
|
|
1853
|
+
},
|
|
1854
|
+
lodHistogram: selection.lodHistogram,
|
|
1855
|
+
// Per-primitive samples are intentionally omitted until the GPU readback
|
|
1856
|
+
// carries stable primitive identities; the histogram is the authoritative
|
|
1857
|
+
// producer fact and is copied from the same GPU cull counters.
|
|
1858
|
+
samples: [],
|
|
1859
|
+
worlds,
|
|
1860
|
+
});
|
|
1861
|
+
};
|
|
2107
1862
|
// feat-20260531-per-frame-bind-group-cache M1 / w4: per-frame
|
|
2108
1863
|
// createBindGroup counter scaffolding. Reset on every draw([world], { cameraOwner: 0, resourceOwner: 0 }) entry,
|
|
2109
1864
|
// bumped on cache-miss in render-system-record.ts (M2-M4 bump points).
|
|
@@ -2112,44 +1867,60 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2112
1867
|
createBindGroup: 0,
|
|
2113
1868
|
keys: [],
|
|
2114
1869
|
};
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
1870
|
+
type PreparedResolverCaches = {
|
|
1871
|
+
readonly preparedPipelineIds: WeakMap<object, string>;
|
|
1872
|
+
readonly preparedMaterialPipelineShaders: WeakMap<object, string>;
|
|
1873
|
+
readonly preparedGroup0Pipelines: WeakSet<object>;
|
|
1874
|
+
readonly preparedViewOnlyPipelines: WeakSet<object>;
|
|
1875
|
+
readonly preparedRenderMaterialPipelines: WeakSet<object>;
|
|
1876
|
+
readonly preparedAssetHandles: WeakMap<World, Map<string, number>>;
|
|
1877
|
+
};
|
|
1878
|
+
const createPreparedResolverCaches = (): PreparedResolverCaches => ({
|
|
1879
|
+
preparedPipelineIds: new WeakMap(),
|
|
1880
|
+
preparedMaterialPipelineShaders: new WeakMap(),
|
|
1881
|
+
preparedGroup0Pipelines: new WeakSet(),
|
|
1882
|
+
preparedViewOnlyPipelines: new WeakSet(),
|
|
1883
|
+
preparedRenderMaterialPipelines: new WeakSet(),
|
|
1884
|
+
preparedAssetHandles: new WeakMap(),
|
|
1885
|
+
});
|
|
1886
|
+
const activePreparedResolverCaches = createPreparedResolverCaches();
|
|
2121
1887
|
const preparedHandle = <Brand extends string>(
|
|
1888
|
+
runtime: RenderSystemInternals,
|
|
1889
|
+
caches: PreparedResolverCaches,
|
|
2122
1890
|
world: World,
|
|
2123
1891
|
guid: string,
|
|
2124
1892
|
brand: Brand,
|
|
2125
1893
|
): Handle<Brand, 'shared'> | undefined => {
|
|
2126
|
-
let handles = preparedAssetHandles.get(world);
|
|
1894
|
+
let handles = caches.preparedAssetHandles.get(world);
|
|
2127
1895
|
if (handles === undefined) {
|
|
2128
1896
|
handles = new Map();
|
|
2129
|
-
preparedAssetHandles.set(world, handles);
|
|
1897
|
+
caches.preparedAssetHandles.set(world, handles);
|
|
2130
1898
|
}
|
|
2131
1899
|
const key = `${brand}:${guid.toLowerCase()}`;
|
|
2132
1900
|
const cached = handles.get(key);
|
|
2133
1901
|
if (cached !== undefined) return cached as Handle<Brand, 'shared'>;
|
|
2134
|
-
const asset =
|
|
1902
|
+
const asset = runtime.assets.lookup(guid);
|
|
2135
1903
|
if (asset === undefined) return undefined;
|
|
2136
1904
|
const handle = world.allocSharedRef(brand, asset);
|
|
2137
1905
|
handles.set(key, handle as number);
|
|
2138
1906
|
return handle;
|
|
2139
1907
|
};
|
|
2140
1908
|
const preparedMaterialBindings = (
|
|
1909
|
+
runtime: RenderSystemInternals,
|
|
1910
|
+
caches: PreparedResolverCaches,
|
|
1911
|
+
worlds: readonly World[],
|
|
2141
1912
|
materialShaderId: string,
|
|
2142
1913
|
worldIndex: number,
|
|
2143
1914
|
materialGuid: string,
|
|
2144
1915
|
layout: BindGroupLayout,
|
|
2145
1916
|
) => {
|
|
2146
|
-
const world =
|
|
2147
|
-
const material =
|
|
2148
|
-
const pipelineState =
|
|
1917
|
+
const world = worlds[worldIndex];
|
|
1918
|
+
const material = runtime.assets.lookup(materialGuid) as MaterialAsset | undefined;
|
|
1919
|
+
const pipelineState = runtime.getPipelineState();
|
|
2149
1920
|
if (world === undefined || material?.kind !== 'material' || pipelineState === null) {
|
|
2150
1921
|
return err(new Error('prepared material asset is unavailable'));
|
|
2151
1922
|
}
|
|
2152
|
-
const schema =
|
|
1923
|
+
const schema = runtime.getParamSchema?.(materialShaderId) ?? [];
|
|
2153
1924
|
const values = material.values ?? {};
|
|
2154
1925
|
const derived = derive(schema);
|
|
2155
1926
|
const fields = [...derived.textureFieldNames];
|
|
@@ -2162,7 +1933,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2162
1933
|
schema,
|
|
2163
1934
|
values as Readonly<Record<string, number | readonly number[]>>,
|
|
2164
1935
|
);
|
|
2165
|
-
const created =
|
|
1936
|
+
const created = runtime.device.createBuffer({
|
|
2166
1937
|
label: `prepared-material:${materialGuid}`,
|
|
2167
1938
|
size: payload.byteLength,
|
|
2168
1939
|
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
@@ -2170,9 +1941,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2170
1941
|
if (!created.ok) return created;
|
|
2171
1942
|
buffer = created.value;
|
|
2172
1943
|
payloadByteLength = payload.byteLength;
|
|
2173
|
-
const written =
|
|
1944
|
+
const written = runtime.device.queue.writeBuffer(buffer, 0, payload);
|
|
2174
1945
|
if (!written.ok) {
|
|
2175
|
-
|
|
1946
|
+
runtime.device.destroyBuffer(buffer);
|
|
2176
1947
|
return written;
|
|
2177
1948
|
}
|
|
2178
1949
|
}
|
|
@@ -2189,20 +1960,32 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2189
1960
|
: undefined;
|
|
2190
1961
|
let sampler = pipelineState.defaultSampler;
|
|
2191
1962
|
if (textureValue?.sampler !== undefined) {
|
|
2192
|
-
const handle = preparedHandle(
|
|
1963
|
+
const handle = preparedHandle(
|
|
1964
|
+
runtime,
|
|
1965
|
+
caches,
|
|
1966
|
+
world,
|
|
1967
|
+
String(textureValue.sampler),
|
|
1968
|
+
'SamplerAsset',
|
|
1969
|
+
);
|
|
2193
1970
|
if (handle !== undefined) {
|
|
2194
1971
|
const asset = resolveAssetHandle<SamplerAsset>(world, handle);
|
|
2195
1972
|
if (asset.ok) {
|
|
2196
|
-
const resident =
|
|
1973
|
+
const resident = runtime.gpuStore.ensureSamplerResident(handle, asset.value, world);
|
|
2197
1974
|
if (resident.ok) sampler = resident.value;
|
|
2198
1975
|
}
|
|
2199
1976
|
}
|
|
2200
1977
|
}
|
|
2201
1978
|
let view = pipelineState.defaultWhiteTextureView;
|
|
2202
1979
|
if (textureValue !== undefined) {
|
|
2203
|
-
const handle = preparedHandle(
|
|
1980
|
+
const handle = preparedHandle(
|
|
1981
|
+
runtime,
|
|
1982
|
+
caches,
|
|
1983
|
+
world,
|
|
1984
|
+
String(textureValue.texture),
|
|
1985
|
+
'TextureAsset',
|
|
1986
|
+
);
|
|
2204
1987
|
if (handle !== undefined) {
|
|
2205
|
-
view = residentTextureView(world,
|
|
1988
|
+
view = residentTextureView(world, runtime.gpuStore, runtime, handle) ?? view;
|
|
2206
1989
|
}
|
|
2207
1990
|
}
|
|
2208
1991
|
textureResources.push({ sampler, view });
|
|
@@ -2238,12 +2021,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2238
2021
|
textureIndex += 1;
|
|
2239
2022
|
continue;
|
|
2240
2023
|
}
|
|
2241
|
-
if (buffer !== undefined)
|
|
2024
|
+
if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
|
|
2242
2025
|
return err(new Error('prepared material schema contains unsupported binding kinds'));
|
|
2243
2026
|
}
|
|
2244
2027
|
const fallback = pipelineState.skylightFallback;
|
|
2245
2028
|
if (fallback === null) {
|
|
2246
|
-
if (buffer !== undefined)
|
|
2029
|
+
if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
|
|
2247
2030
|
return err(new Error('prepared material skylight fallback is unavailable'));
|
|
2248
2031
|
}
|
|
2249
2032
|
const completeEntries = assembleMaterialWithSkylightEntries(entries, {
|
|
@@ -2255,206 +2038,261 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2255
2038
|
brdfLutSampler: fallback.sampler,
|
|
2256
2039
|
intensityBuffer: fallback.intensityBuffer,
|
|
2257
2040
|
});
|
|
2258
|
-
const group =
|
|
2041
|
+
const group = runtime.device.createBindGroup({ layout, entries: completeEntries });
|
|
2259
2042
|
if (!group.ok) {
|
|
2260
|
-
if (buffer !== undefined)
|
|
2043
|
+
if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
|
|
2261
2044
|
return group;
|
|
2262
2045
|
}
|
|
2263
2046
|
if (buffer === undefined) return group;
|
|
2264
2047
|
return ok({
|
|
2265
2048
|
handle: group.value,
|
|
2266
2049
|
dynamicOffsets: [0, 0],
|
|
2267
|
-
release: () =>
|
|
2050
|
+
release: () => runtime.device.destroyBuffer(buffer),
|
|
2268
2051
|
});
|
|
2269
2052
|
};
|
|
2270
2053
|
const lastFrustumStats: { culled: number; total: number } = { culled: 0, total: 0 };
|
|
2271
2054
|
const lastVisibilityStats: { explicitlyHidden: number } = { explicitlyHidden: 0 };
|
|
2272
2055
|
let lastMeshMaterialBindings: readonly MeshMaterialBindingObservation[] = [];
|
|
2273
2056
|
let lastMeshMaterialBindingFrame: ExtractedFrame | undefined;
|
|
2274
|
-
const
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2057
|
+
const createPreparedResolverFactory =
|
|
2058
|
+
(
|
|
2059
|
+
runtime: RenderSystemInternals,
|
|
2060
|
+
gpuWork: RenderFeatureGpuWorkOwner,
|
|
2061
|
+
worlds: readonly World[],
|
|
2062
|
+
caches: PreparedResolverCaches,
|
|
2063
|
+
) =>
|
|
2064
|
+
(input: RenderFeaturePreparedGraphicsResolverInput): PreparedGraphicsResolver => {
|
|
2065
|
+
const {
|
|
2066
|
+
preparedPipelineIds,
|
|
2067
|
+
preparedMaterialPipelineShaders,
|
|
2068
|
+
preparedGroup0Pipelines,
|
|
2069
|
+
preparedViewOnlyPipelines,
|
|
2070
|
+
preparedRenderMaterialPipelines,
|
|
2071
|
+
} = caches;
|
|
2072
|
+
return createPreparedGraphicsResolver({
|
|
2073
|
+
device: runtime.device,
|
|
2074
|
+
featureIdentity: input.featureIdentity,
|
|
2075
|
+
generation: input.generation,
|
|
2076
|
+
capabilityAvailable: true,
|
|
2077
|
+
featureOrder: input.order,
|
|
2078
|
+
lookup: input.lookup,
|
|
2079
|
+
resolveGpuBuffer: (reference) => gpuWork.resolveBuffer(input.featureIdentity, reference),
|
|
2080
|
+
resolvePipeline: (descriptor) => {
|
|
2081
|
+
const postProcessEntry =
|
|
2082
|
+
input.fullscreenEffects.get(descriptor.shader) ??
|
|
2083
|
+
builtinPostProcessEntries.get(descriptor.shader);
|
|
2084
|
+
if (postProcessEntry !== undefined) {
|
|
2085
|
+
const fullscreen = buildFullscreenPostProcessPass(
|
|
2086
|
+
{ device: runtime.device, errorRegistry: runtime.errorRegistry },
|
|
2087
|
+
postProcessEntry,
|
|
2088
|
+
);
|
|
2089
|
+
if (fullscreen === null) return err(new Error('prepared post-process layout failed'));
|
|
2090
|
+
const pipeline = runtime.getPostProcessPipeline?.(
|
|
2091
|
+
descriptor.shader,
|
|
2092
|
+
fullscreen.bindGroupLayout,
|
|
2093
|
+
descriptor.colorFormats[0] as GPUTextureFormat,
|
|
2094
|
+
postProcessEntry,
|
|
2095
|
+
);
|
|
2096
|
+
if (pipeline !== null && pipeline !== undefined) {
|
|
2097
|
+
preparedPipelineIds.set(pipeline as object, descriptor.shader);
|
|
2098
|
+
if (runtime.getMaterialShaderBindingContract?.(descriptor.shader) === 'group-0') {
|
|
2099
|
+
preparedGroup0Pipelines.add(pipeline as object);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
return pipeline === null || pipeline === undefined
|
|
2103
|
+
? err(makePreparedPipelinePendingError())
|
|
2104
|
+
: ok(pipeline);
|
|
2105
|
+
}
|
|
2106
|
+
const preparedPipeline = runtime.getMaterialShaderPipeline?.(
|
|
2297
2107
|
descriptor.shader,
|
|
2298
|
-
|
|
2108
|
+
descriptor.colorFormats[0] === 'rgba16float',
|
|
2109
|
+
descriptor.renderState,
|
|
2110
|
+
descriptor.topology,
|
|
2111
|
+
descriptor.indexFormat,
|
|
2112
|
+
undefined,
|
|
2113
|
+
'forward',
|
|
2114
|
+
undefined,
|
|
2115
|
+
descriptor.sampleCount ?? 1,
|
|
2116
|
+
// Prepared graphics own their declared attachment format. Passing
|
|
2117
|
+
// it through keeps the PSO compatible with feature-target views
|
|
2118
|
+
// (the ordinary material path intentionally defaults to the
|
|
2119
|
+
// swap-chain view format).
|
|
2299
2120
|
descriptor.colorFormats[0] as GPUTextureFormat,
|
|
2300
|
-
|
|
2121
|
+
undefined,
|
|
2122
|
+
descriptor.depthFormat === undefined
|
|
2123
|
+
? null
|
|
2124
|
+
: (descriptor.depthFormat as GPUTextureFormat),
|
|
2125
|
+
descriptor.vertexLayout,
|
|
2126
|
+
undefined,
|
|
2127
|
+
input.shaderModuleMode,
|
|
2301
2128
|
);
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2129
|
+
// A requested material shader is an exact pipeline contract. During
|
|
2130
|
+
// async shader warmup, substituting the generic unlit pipeline can
|
|
2131
|
+
// mismatch vertex layouts and HDR attachment formats, turning a
|
|
2132
|
+
// retryable next-frame prepare into an invalid GPU command buffer.
|
|
2133
|
+
const pipeline = preparedPipeline ?? null;
|
|
2134
|
+
if (pipeline !== null) {
|
|
2135
|
+
preparedMaterialPipelineShaders.set(pipeline as object, descriptor.shader);
|
|
2136
|
+
const bindingContract = runtime.getMaterialShaderBindingContract?.(descriptor.shader);
|
|
2137
|
+
if (bindingContract === 'group-0') {
|
|
2305
2138
|
preparedGroup0Pipelines.add(pipeline as object);
|
|
2139
|
+
} else if (bindingContract === 'view-only') {
|
|
2140
|
+
preparedViewOnlyPipelines.add(pipeline as object);
|
|
2141
|
+
} else {
|
|
2142
|
+
preparedRenderMaterialPipelines.add(pipeline as object);
|
|
2306
2143
|
}
|
|
2307
2144
|
}
|
|
2308
|
-
return pipeline === null
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
undefined,
|
|
2321
|
-
descriptor.sampleCount ?? 1,
|
|
2322
|
-
// Prepared graphics own their declared attachment format. Passing
|
|
2323
|
-
// it through keeps the PSO compatible with feature-target views
|
|
2324
|
-
// (the ordinary material path intentionally defaults to the
|
|
2325
|
-
// swap-chain view format).
|
|
2326
|
-
descriptor.colorFormats[0] as GPUTextureFormat,
|
|
2327
|
-
undefined,
|
|
2328
|
-
descriptor.depthFormat === undefined
|
|
2329
|
-
? null
|
|
2330
|
-
: (descriptor.depthFormat as GPUTextureFormat),
|
|
2331
|
-
descriptor.vertexLayout,
|
|
2332
|
-
undefined,
|
|
2333
|
-
input.shaderModuleMode,
|
|
2334
|
-
);
|
|
2335
|
-
// A requested material shader is an exact pipeline contract. During
|
|
2336
|
-
// async shader warmup, substituting the generic unlit pipeline can
|
|
2337
|
-
// mismatch vertex layouts and HDR attachment formats, turning a
|
|
2338
|
-
// retryable next-frame prepare into an invalid GPU command buffer.
|
|
2339
|
-
const pipeline = preparedPipeline ?? null;
|
|
2340
|
-
if (pipeline !== null) {
|
|
2341
|
-
preparedMaterialPipelineShaders.set(pipeline as object, descriptor.shader);
|
|
2342
|
-
const bindingContract = internals.getMaterialShaderBindingContract?.(descriptor.shader);
|
|
2343
|
-
if (bindingContract === 'group-0') {
|
|
2344
|
-
preparedGroup0Pipelines.add(pipeline as object);
|
|
2345
|
-
} else if (bindingContract === 'view-only') {
|
|
2346
|
-
preparedViewOnlyPipelines.add(pipeline as object);
|
|
2347
|
-
} else {
|
|
2348
|
-
preparedRenderMaterialPipelines.add(pipeline as object);
|
|
2145
|
+
return pipeline === null ? err(makePreparedPipelinePendingError()) : ok(pipeline);
|
|
2146
|
+
},
|
|
2147
|
+
resolveBindings: (descriptor, pipeline) => {
|
|
2148
|
+
const materialShaderId = preparedMaterialPipelineShaders.get(pipeline as object);
|
|
2149
|
+
const bindingContract =
|
|
2150
|
+
materialShaderId === undefined
|
|
2151
|
+
? undefined
|
|
2152
|
+
: runtime.getMaterialShaderBindingContract?.(materialShaderId);
|
|
2153
|
+
if (bindingContract === 'group-0-resource') {
|
|
2154
|
+
return descriptor.values.sceneDepth === undefined
|
|
2155
|
+
? err(new Error('prepared group-0 resource pipeline requires a scene target'))
|
|
2156
|
+
: ok(undefined);
|
|
2349
2157
|
}
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2158
|
+
if (bindingContract === 'view-and-scene-depth') {
|
|
2159
|
+
return ok(undefined);
|
|
2160
|
+
}
|
|
2161
|
+
if (preparedGroup0Pipelines.has(pipeline as object)) {
|
|
2162
|
+
const layout =
|
|
2163
|
+
(materialShaderId === undefined
|
|
2164
|
+
? undefined
|
|
2165
|
+
: runtime.getMaterialBindGroupLayout?.(materialShaderId)) ??
|
|
2166
|
+
(
|
|
2167
|
+
pipeline as RenderPipeline & {
|
|
2168
|
+
getBindGroupLayout?: (index: number) => BindGroupLayout;
|
|
2169
|
+
}
|
|
2170
|
+
).getBindGroupLayout?.(0);
|
|
2171
|
+
return layout === undefined
|
|
2172
|
+
? err(new Error('prepared group-0 pipeline bind group layout is unavailable'))
|
|
2173
|
+
: runtime.device.createBindGroup({ layout, entries: [] });
|
|
2174
|
+
}
|
|
2175
|
+
if (
|
|
2176
|
+
preparedViewOnlyPipelines.has(pipeline as object) ||
|
|
2177
|
+
(preparedRenderMaterialPipelines.has(pipeline as object) &&
|
|
2178
|
+
descriptor.values.group === 0) ||
|
|
2179
|
+
pipeline === runtime.getPipelineState()?.unlitPipeline
|
|
2180
|
+
) {
|
|
2181
|
+
return ok(undefined);
|
|
2182
|
+
}
|
|
2183
|
+
const group = descriptor.values.group;
|
|
2368
2184
|
const layout =
|
|
2369
|
-
(materialShaderId
|
|
2370
|
-
?
|
|
2371
|
-
|
|
2185
|
+
(materialShaderId !== undefined
|
|
2186
|
+
? (runtime.getMaterialBindGroupLayout?.(materialShaderId) ??
|
|
2187
|
+
(group === 0 ? runtime.getPipelineState()?.materialBindGroupLayout : undefined))
|
|
2188
|
+
: undefined) ??
|
|
2372
2189
|
(
|
|
2373
2190
|
pipeline as RenderPipeline & {
|
|
2374
2191
|
getBindGroupLayout?: (index: number) => BindGroupLayout;
|
|
2375
2192
|
}
|
|
2376
|
-
).getBindGroupLayout?.(0);
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
pipeline as RenderPipeline & {
|
|
2397
|
-
getBindGroupLayout?: (index: number) => BindGroupLayout;
|
|
2398
|
-
}
|
|
2399
|
-
).getBindGroupLayout?.(group === 1 ? 1 : 0);
|
|
2400
|
-
const material = descriptor.values.material;
|
|
2401
|
-
if (
|
|
2402
|
-
group === 1 &&
|
|
2403
|
-
layout !== undefined &&
|
|
2404
|
-
materialShaderId !== undefined &&
|
|
2405
|
-
typeof material === 'object' &&
|
|
2406
|
-
material !== null &&
|
|
2407
|
-
'world' in material &&
|
|
2408
|
-
'guid' in material &&
|
|
2409
|
-
typeof material.world === 'number' &&
|
|
2410
|
-
typeof material.guid === 'string'
|
|
2411
|
-
) {
|
|
2412
|
-
return preparedMaterialBindings(materialShaderId, material.world, material.guid, layout);
|
|
2413
|
-
}
|
|
2414
|
-
return layout === undefined
|
|
2415
|
-
? err(new Error('prepared pipeline bind group layout is unavailable'))
|
|
2416
|
-
: internals.device.createBindGroup({
|
|
2193
|
+
).getBindGroupLayout?.(group === 1 ? 1 : 0);
|
|
2194
|
+
const material = descriptor.values.material;
|
|
2195
|
+
if (
|
|
2196
|
+
group === 1 &&
|
|
2197
|
+
layout !== undefined &&
|
|
2198
|
+
materialShaderId !== undefined &&
|
|
2199
|
+
typeof material === 'object' &&
|
|
2200
|
+
material !== null &&
|
|
2201
|
+
'world' in material &&
|
|
2202
|
+
'guid' in material &&
|
|
2203
|
+
typeof material.world === 'number' &&
|
|
2204
|
+
typeof material.guid === 'string'
|
|
2205
|
+
) {
|
|
2206
|
+
return preparedMaterialBindings(
|
|
2207
|
+
runtime,
|
|
2208
|
+
caches,
|
|
2209
|
+
worlds,
|
|
2210
|
+
materialShaderId,
|
|
2211
|
+
material.world,
|
|
2212
|
+
material.guid,
|
|
2417
2213
|
layout,
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
return layout === undefined
|
|
2217
|
+
? err(new Error('prepared pipeline bind group layout is unavailable'))
|
|
2218
|
+
: runtime.device.createBindGroup({
|
|
2219
|
+
layout,
|
|
2220
|
+
entries:
|
|
2221
|
+
group === 1 && preparedPipelineIds.has(pipeline as object)
|
|
2222
|
+
? [
|
|
2223
|
+
{
|
|
2224
|
+
binding: 0,
|
|
2225
|
+
resource: {
|
|
2226
|
+
kind: 'textureView',
|
|
2227
|
+
value: runtime.getPipelineState()?.fallbackTextureView as never,
|
|
2228
|
+
},
|
|
2426
2229
|
},
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2230
|
+
{
|
|
2231
|
+
binding: 1,
|
|
2232
|
+
resource: {
|
|
2233
|
+
kind: 'sampler',
|
|
2234
|
+
value: runtime.getPipelineState()?.defaultSampler as never,
|
|
2235
|
+
},
|
|
2433
2236
|
},
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2237
|
+
...(runtime.getPostProcessParamsBuffer?.(
|
|
2238
|
+
preparedPipelineIds.get(pipeline as object) ?? '',
|
|
2239
|
+
) === undefined
|
|
2240
|
+
? []
|
|
2241
|
+
: [
|
|
2242
|
+
{
|
|
2243
|
+
binding: 2,
|
|
2244
|
+
resource: {
|
|
2245
|
+
kind: 'buffer' as const,
|
|
2246
|
+
value: {
|
|
2247
|
+
buffer: runtime.getPostProcessParamsBuffer?.(
|
|
2248
|
+
preparedPipelineIds.get(pipeline as object) ?? '',
|
|
2249
|
+
) as never,
|
|
2250
|
+
},
|
|
2448
2251
|
},
|
|
2449
2252
|
},
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
]
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2253
|
+
]),
|
|
2254
|
+
]
|
|
2255
|
+
: [],
|
|
2256
|
+
});
|
|
2257
|
+
},
|
|
2258
|
+
});
|
|
2259
|
+
};
|
|
2260
|
+
const preparedResolverFactory = (
|
|
2261
|
+
input: RenderFeaturePreparedGraphicsResolverInput,
|
|
2262
|
+
): PreparedGraphicsResolver =>
|
|
2263
|
+
createPreparedResolverFactory(
|
|
2264
|
+
internals,
|
|
2265
|
+
featureGpuWork,
|
|
2266
|
+
preparedWorlds,
|
|
2267
|
+
activePreparedResolverCaches,
|
|
2268
|
+
)(input);
|
|
2269
|
+
const recoveryOwner = createRenderSystemRecovery({
|
|
2270
|
+
frameState,
|
|
2271
|
+
internals,
|
|
2272
|
+
getEnvironmentLifecycle: () => environmentLifecycle,
|
|
2273
|
+
getGpuDrivenProduction: () => gpuDrivenProduction,
|
|
2274
|
+
setGpuDrivenProduction: (production) => {
|
|
2275
|
+
gpuDrivenProduction = production;
|
|
2276
|
+
},
|
|
2277
|
+
disposeFeatureGpuWork,
|
|
2278
|
+
setFeatureGpuWork: (owner) => {
|
|
2279
|
+
featureGpuWork = owner;
|
|
2280
|
+
},
|
|
2281
|
+
getActiveFeaturePostProcessEntries: () => activeFeaturePostProcessEntries,
|
|
2282
|
+
setActiveFeaturePostProcessEntries: (entries) => {
|
|
2283
|
+
activeFeaturePostProcessEntries = entries;
|
|
2284
|
+
},
|
|
2285
|
+
postProcessPipelineCache,
|
|
2286
|
+
clearPostProcessPipelineCache,
|
|
2287
|
+
clearPostProcessPipelineEntry,
|
|
2288
|
+
invalidatePostProcessModule,
|
|
2289
|
+
persistentRenderScene,
|
|
2290
|
+
gpuDrivenShaderFactory,
|
|
2291
|
+
createFeatureGpuWorkOwner,
|
|
2292
|
+
pointsLinesOwner,
|
|
2293
|
+
createPreparedResolverFactory,
|
|
2294
|
+
createPreparedResolverCaches,
|
|
2295
|
+
});
|
|
2458
2296
|
return {
|
|
2459
2297
|
releaseProfilerCatalog(): void {
|
|
2460
2298
|
releaseProfilerCatalog?.();
|
|
@@ -2466,9 +2304,44 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2466
2304
|
gpuDriven: gpuDrivenProduction.inspect(),
|
|
2467
2305
|
};
|
|
2468
2306
|
},
|
|
2307
|
+
get gpuDrivenInspection(): GpuDrivenProductionInspection {
|
|
2308
|
+
return gpuDrivenProduction.inspect();
|
|
2309
|
+
},
|
|
2469
2310
|
get reflectionProbes(): ReflectionProbeInspection {
|
|
2470
2311
|
return reflectionProbeOwner.inspect();
|
|
2471
2312
|
},
|
|
2313
|
+
get ssrDependencies(): SsrDependenciesInspection {
|
|
2314
|
+
ensureSsrFormatProbe();
|
|
2315
|
+
const owner = reflectionProbeOwner.inspect();
|
|
2316
|
+
const nextGeneration = owner.reflectionFallback.projectionGeneration;
|
|
2317
|
+
const reset = resolveSsrAdmissionGeneration(ssrFallbackGeneration, nextGeneration);
|
|
2318
|
+
if (reset.changed) {
|
|
2319
|
+
frameState.temporalFrameTransaction.reset('signature-change');
|
|
2320
|
+
frameState.temporalFrame = undefined;
|
|
2321
|
+
frameState.temporalFrameInput = undefined;
|
|
2322
|
+
frameState.lastSuccessfulTemporalView = undefined;
|
|
2323
|
+
frameState.successfulTemporalFrameIndex = 0;
|
|
2324
|
+
frameState.pendingTemporalCommit = { kind: 'none' };
|
|
2325
|
+
}
|
|
2326
|
+
ssrFallbackGeneration = reset.generation;
|
|
2327
|
+
const temporal =
|
|
2328
|
+
frameState.lastSuccessfulTemporalView === undefined
|
|
2329
|
+
? undefined
|
|
2330
|
+
: ({
|
|
2331
|
+
successfulSubmit: true,
|
|
2332
|
+
generation: frameState.lastSuccessfulTemporalView.input.deviceGeneration,
|
|
2333
|
+
} as const);
|
|
2334
|
+
return projectSsrDependencies({
|
|
2335
|
+
requested: frameState.reflectionFallbackDemand === true,
|
|
2336
|
+
identity: internals.ssrIdentity,
|
|
2337
|
+
reflectionFallback: owner.reflectionFallback,
|
|
2338
|
+
format: ssrFormatReceipt,
|
|
2339
|
+
temporal,
|
|
2340
|
+
});
|
|
2341
|
+
},
|
|
2342
|
+
get reflectionFallbackCompletion(): Promise<void> | undefined {
|
|
2343
|
+
return frameState.reflectionFallbackCompletion;
|
|
2344
|
+
},
|
|
2472
2345
|
isCubeCapturePending(target: RenderTarget): boolean {
|
|
2473
2346
|
return cubeCaptureScheduler.inspect(target).candidateGeneration !== undefined;
|
|
2474
2347
|
},
|
|
@@ -2538,6 +2411,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2538
2411
|
}),
|
|
2539
2412
|
});
|
|
2540
2413
|
},
|
|
2414
|
+
get lodOcclusionInspection(): LodOcclusionInspection | undefined {
|
|
2415
|
+
return lastLodOcclusionInspection;
|
|
2416
|
+
},
|
|
2541
2417
|
get bloom(): BloomInspection {
|
|
2542
2418
|
const pipelineState = internals.getPipelineState();
|
|
2543
2419
|
const resources = pipelineState?.perPassResources;
|
|
@@ -2619,7 +2495,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2619
2495
|
renderReadLeases?: readonly RenderReadLease[],
|
|
2620
2496
|
timingCapture?: GpuTimingCapture,
|
|
2621
2497
|
): boolean {
|
|
2622
|
-
|
|
2498
|
+
const composition = canonicalizeWorldComposition(worlds, opts, renderReadLeases);
|
|
2499
|
+
const compositionWorlds = composition.worlds;
|
|
2500
|
+
const compositionLeases = composition.leases;
|
|
2501
|
+
preparedWorlds = compositionWorlds;
|
|
2623
2502
|
const profileSession = internals.profiler?.activeSession();
|
|
2624
2503
|
let ownsProfileFrame = false;
|
|
2625
2504
|
let submitted = false;
|
|
@@ -2635,7 +2514,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2635
2514
|
// cameraOwner drives the surfaced cameras + frustum
|
|
2636
2515
|
// cull; resourceOwner drives skylight/skybox/postProcess + per-world
|
|
2637
2516
|
// record config.
|
|
2638
|
-
const { cameraOwner, resourceOwner } =
|
|
2517
|
+
const { cameraOwner, resourceOwner } = composition.owners;
|
|
2639
2518
|
// Keep the compiled graph as last-known-good until the candidate topology
|
|
2640
2519
|
// compiles. The failed candidate frame does not execute it; successful
|
|
2641
2520
|
// replacement retires it atomically in ensureCompiledFrameGraph.
|
|
@@ -2670,22 +2549,28 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2670
2549
|
// the dedicated resourceOwner index. The variable is named
|
|
2671
2550
|
// `resourceWorld` so the record-stage reads are self-describing (they
|
|
2672
2551
|
// are resource-owner reads, not camera reads).
|
|
2673
|
-
const resourceWorld =
|
|
2552
|
+
const resourceWorld = compositionWorlds[resourceOwner] as World;
|
|
2674
2553
|
const frame = runProfiledRenderPhase(profileSession, 'extract', () => {
|
|
2675
2554
|
return persistentRenderScene.extractComposition(
|
|
2676
|
-
|
|
2555
|
+
compositionWorlds,
|
|
2677
2556
|
{ cameraOwner, resourceOwner },
|
|
2678
2557
|
internals.assets.catalogEpoch,
|
|
2679
|
-
() =>
|
|
2558
|
+
(renderables) =>
|
|
2680
2559
|
extractFrames(
|
|
2681
|
-
|
|
2560
|
+
compositionWorlds,
|
|
2682
2561
|
{ cameraOwner, resourceOwner },
|
|
2683
2562
|
internals.assets,
|
|
2684
2563
|
internals.getPipelineState(),
|
|
2685
2564
|
persistentRenderScene.materialSnapshotCacheStore(),
|
|
2686
|
-
{
|
|
2565
|
+
{
|
|
2566
|
+
cull: 'none',
|
|
2567
|
+
renderables,
|
|
2568
|
+
...(internals.getMaterialShaderArtifact === undefined
|
|
2569
|
+
? {}
|
|
2570
|
+
: { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
|
|
2571
|
+
},
|
|
2687
2572
|
),
|
|
2688
|
-
|
|
2573
|
+
compositionLeases,
|
|
2689
2574
|
);
|
|
2690
2575
|
});
|
|
2691
2576
|
const {
|
|
@@ -2701,7 +2586,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2701
2586
|
skylightCount,
|
|
2702
2587
|
skybox,
|
|
2703
2588
|
skyboxCount,
|
|
2704
|
-
fog,
|
|
2705
2589
|
fogFailure,
|
|
2706
2590
|
frustumStats,
|
|
2707
2591
|
visibilityStats,
|
|
@@ -2714,12 +2598,280 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2714
2598
|
frame.reflectionProbes ?? [],
|
|
2715
2599
|
persistentRenderScene.reflectionProbeProjection().selected,
|
|
2716
2600
|
cameras[0],
|
|
2601
|
+
frameState.frameNumber,
|
|
2602
|
+
skylight,
|
|
2717
2603
|
);
|
|
2718
2604
|
latestCamera = cameras[0];
|
|
2605
|
+
persistentRenderScene.updateVisibilityFacet(compositionWorlds, latestCamera, renderables);
|
|
2606
|
+
let occlusionProjection:
|
|
2607
|
+
| import('./scene/visibility/occlusion-runtime').OcclusionFrameProjection
|
|
2608
|
+
| undefined;
|
|
2609
|
+
let occlusionFallback: LodOcclusionInspection['fallback'] = { active: false };
|
|
2610
|
+
const worldKeys = persistentRenderScene.visibilityWorldKeysFor(compositionWorlds);
|
|
2611
|
+
const slotByEntity = persistentRenderScene.compositionSlotByStableEntity();
|
|
2612
|
+
if (latestCamera !== undefined) {
|
|
2613
|
+
const camera = latestCamera;
|
|
2614
|
+
const cameraWorld = compositionWorlds[camera.worldId ?? cameraOwner];
|
|
2615
|
+
const view = viewKey({
|
|
2616
|
+
attachmentId: cameraWorld?.identity ?? 'missing-camera-world',
|
|
2617
|
+
cameraEntity: camera.entityKey ?? 0,
|
|
2618
|
+
viewRole: 'main',
|
|
2619
|
+
viewGeneration: camera.historyVersion ?? 0,
|
|
2620
|
+
});
|
|
2621
|
+
const cachedCandidates = occlusionCandidateCache;
|
|
2622
|
+
const candidates =
|
|
2623
|
+
cachedCandidates?.renderables === renderables &&
|
|
2624
|
+
cachedCandidates.camera === camera &&
|
|
2625
|
+
cachedCandidates.cameraWorldIdentity === (cameraWorld?.identity ?? '')
|
|
2626
|
+
? cachedCandidates.candidates
|
|
2627
|
+
: renderables.flatMap((renderable) => {
|
|
2628
|
+
const world = compositionWorlds[renderable.worldId];
|
|
2629
|
+
const aabb = renderable.localAabb;
|
|
2630
|
+
// Occlusion is a LOD transport concern. A plain mesh has no
|
|
2631
|
+
// lower-detail range to select, so keep it on the ordinary CPU
|
|
2632
|
+
// visibility path and do not allocate query resources for it.
|
|
2633
|
+
if (
|
|
2634
|
+
world === undefined ||
|
|
2635
|
+
cameraWorld === undefined ||
|
|
2636
|
+
aabb === undefined ||
|
|
2637
|
+
aabb.length < 6 ||
|
|
2638
|
+
renderable.lods === undefined ||
|
|
2639
|
+
renderable.lods.length === 0
|
|
2640
|
+
)
|
|
2641
|
+
return [];
|
|
2642
|
+
const slot = slotByEntity.get(
|
|
2643
|
+
worldEntityKey(
|
|
2644
|
+
worldKeys[renderable.worldId] ?? renderable.worldId,
|
|
2645
|
+
renderable.entityKey,
|
|
2646
|
+
),
|
|
2647
|
+
);
|
|
2648
|
+
if (slot === undefined) return [];
|
|
2649
|
+
const primitive = primitiveKey({
|
|
2650
|
+
attachmentId: cameraWorld.identity,
|
|
2651
|
+
worldGeneration: worldKeys[renderable.worldId] ?? renderable.worldId,
|
|
2652
|
+
primitiveSlot: slot.slot,
|
|
2653
|
+
slotGeneration: slot.generation,
|
|
2654
|
+
});
|
|
2655
|
+
return [
|
|
2656
|
+
{
|
|
2657
|
+
view,
|
|
2658
|
+
primitive,
|
|
2659
|
+
epoch: 0,
|
|
2660
|
+
bounds: {
|
|
2661
|
+
min: [aabb[0] ?? 0, aabb[1] ?? 0, aabb[2] ?? 0] as [number, number, number],
|
|
2662
|
+
max: [aabb[3] ?? 0, aabb[4] ?? 0, aabb[5] ?? 0] as [number, number, number],
|
|
2663
|
+
},
|
|
2664
|
+
candidate: { level: 0, confidence: 1 },
|
|
2665
|
+
deviceGeneration: internals.deviceScope.generation,
|
|
2666
|
+
proxyVertices: buildOcclusionProxyVertices(
|
|
2667
|
+
aabb,
|
|
2668
|
+
renderable.transform.world,
|
|
2669
|
+
camera,
|
|
2670
|
+
),
|
|
2671
|
+
},
|
|
2672
|
+
];
|
|
2673
|
+
});
|
|
2674
|
+
occlusionCandidateCache = {
|
|
2675
|
+
renderables,
|
|
2676
|
+
camera,
|
|
2677
|
+
cameraWorldIdentity: cameraWorld?.identity ?? '',
|
|
2678
|
+
candidates,
|
|
2679
|
+
};
|
|
2680
|
+
occlusionProjection = runProfiledRenderPhase(profileSession, 'occlusion-prepare', () =>
|
|
2681
|
+
ensureOcclusionRuntime().prepareBatch(candidates, camera.antialias === 'msaa' ? 4 : 1),
|
|
2682
|
+
);
|
|
2683
|
+
const queryPool = ensureOcclusionRuntime().inspect();
|
|
2684
|
+
const queryUnavailable = ensureOcclusionRuntime().prepareUnavailable;
|
|
2685
|
+
const queryCapacityFallback =
|
|
2686
|
+
candidates.length > 0 && queryUnavailable && queryPool.availablePages === 0;
|
|
2687
|
+
occlusionFallback = queryCapacityFallback
|
|
2688
|
+
? {
|
|
2689
|
+
active: true as const,
|
|
2690
|
+
reason: 'page-exhausted' as const,
|
|
2691
|
+
error: {
|
|
2692
|
+
code: 'visibility-query-capacity-exhausted',
|
|
2693
|
+
expected: 'one available occlusion-query page for the LOD candidate batch',
|
|
2694
|
+
hint: 'retry the LOD query after an in-flight page completes',
|
|
2695
|
+
detail: {
|
|
2696
|
+
availablePages: queryPool.availablePages,
|
|
2697
|
+
pageCount: queryPool.pageCount,
|
|
2698
|
+
pageIndexLimit: queryPool.pageIndexLimit,
|
|
2699
|
+
},
|
|
2700
|
+
},
|
|
2701
|
+
}
|
|
2702
|
+
: ({ active: false } as const);
|
|
2703
|
+
}
|
|
2704
|
+
// Query transport is best-effort and must fail open. A candidate that
|
|
2705
|
+
// was hidden by an earlier completion becomes visible again when this
|
|
2706
|
+
// device/frame cannot reserve a fresh query page; the next successful
|
|
2707
|
+
// submission can rebuild confidence from a real result.
|
|
2708
|
+
if (
|
|
2709
|
+
occlusionProjection === undefined &&
|
|
2710
|
+
latestCamera !== undefined &&
|
|
2711
|
+
ensureOcclusionRuntime().prepareUnavailable
|
|
2712
|
+
) {
|
|
2713
|
+
const cameraWorld = compositionWorlds[latestCamera.worldId ?? cameraOwner];
|
|
2714
|
+
if (cameraWorld !== undefined) {
|
|
2715
|
+
const fallbackView = viewKey({
|
|
2716
|
+
attachmentId: cameraWorld.identity,
|
|
2717
|
+
cameraEntity: latestCamera.entityKey ?? 0,
|
|
2718
|
+
viewRole: 'main',
|
|
2719
|
+
viewGeneration: latestCamera.historyVersion ?? 0,
|
|
2720
|
+
});
|
|
2721
|
+
for (const renderable of renderables) {
|
|
2722
|
+
if ((renderable.lods?.length ?? 0) === 0) continue;
|
|
2723
|
+
const world = compositionWorlds[renderable.worldId];
|
|
2724
|
+
if (world === undefined) continue;
|
|
2725
|
+
const slot = slotByEntity.get(
|
|
2726
|
+
worldEntityKey(
|
|
2727
|
+
worldKeys[renderable.worldId] ?? renderable.worldId,
|
|
2728
|
+
renderable.entityKey,
|
|
2729
|
+
),
|
|
2730
|
+
);
|
|
2731
|
+
if (slot === undefined) continue;
|
|
2732
|
+
persistentRenderScene.visibilityFacetStore().applyConfidence(
|
|
2733
|
+
fallbackView,
|
|
2734
|
+
primitiveKey({
|
|
2735
|
+
attachmentId: cameraWorld.identity,
|
|
2736
|
+
worldGeneration: worldKeys[renderable.worldId] ?? renderable.worldId,
|
|
2737
|
+
primitiveSlot: slot.slot,
|
|
2738
|
+
slotGeneration: slot.generation,
|
|
2739
|
+
}),
|
|
2740
|
+
{ type: 'failure', submissionGeneration: frameState.frameNumber },
|
|
2741
|
+
);
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
const visibilityProjection = persistentRenderScene.projectVisibility(
|
|
2746
|
+
compositionWorlds,
|
|
2747
|
+
latestCamera,
|
|
2748
|
+
renderables,
|
|
2749
|
+
dispatch,
|
|
2750
|
+
);
|
|
2751
|
+
const submissionRenderables = visibilityProjection.renderables;
|
|
2752
|
+
const submissionDispatch = visibilityProjection.dispatch;
|
|
2753
|
+
const inspectionCamera = latestCamera;
|
|
2754
|
+
let lodCandidateCountForFrame = 0;
|
|
2755
|
+
if (inspectionCamera !== undefined) {
|
|
2756
|
+
const firstRenderable = renderables[0];
|
|
2757
|
+
const lodCandidateCount =
|
|
2758
|
+
lodCandidateCountCache?.renderables === renderables
|
|
2759
|
+
? lodCandidateCountCache.count
|
|
2760
|
+
: (() => {
|
|
2761
|
+
const count = renderables.reduce(
|
|
2762
|
+
(total, renderable) => total + ((renderable.lods?.length ?? 0) > 0 ? 1 : 0),
|
|
2763
|
+
0,
|
|
2764
|
+
);
|
|
2765
|
+
lodCandidateCountCache = { renderables, count };
|
|
2766
|
+
return count;
|
|
2767
|
+
})();
|
|
2768
|
+
lodCandidateCountForFrame = lodCandidateCount;
|
|
2769
|
+
const inspectionRenderables =
|
|
2770
|
+
lodInspectionRenderablesCache?.submissionRenderables === submissionRenderables &&
|
|
2771
|
+
lodInspectionRenderablesCache.lodCandidateCount === lodCandidateCount
|
|
2772
|
+
? lodInspectionRenderablesCache.values
|
|
2773
|
+
: (() => {
|
|
2774
|
+
const values =
|
|
2775
|
+
lodCandidateCount > 0
|
|
2776
|
+
? submissionRenderables.filter(
|
|
2777
|
+
(renderable) => (renderable.lods?.length ?? 0) > 0,
|
|
2778
|
+
)
|
|
2779
|
+
: submissionRenderables;
|
|
2780
|
+
lodInspectionRenderablesCache = {
|
|
2781
|
+
submissionRenderables,
|
|
2782
|
+
lodCandidateCount,
|
|
2783
|
+
values,
|
|
2784
|
+
};
|
|
2785
|
+
return values;
|
|
2786
|
+
})();
|
|
2787
|
+
const candidateCount =
|
|
2788
|
+
lodCandidateCount > 0 ? lodCandidateCount : renderables.length + frustumStats.culled;
|
|
2789
|
+
const inspectionQuery = ensureOcclusionRuntime().inspect();
|
|
2790
|
+
const inspectionBudget = persistentRenderScene.visibilityBudgetValue();
|
|
2791
|
+
const inspectionWorlds = createLodWorldInspections(
|
|
2792
|
+
compositionWorlds,
|
|
2793
|
+
renderables,
|
|
2794
|
+
submissionRenderables,
|
|
2795
|
+
inspectionCamera,
|
|
2796
|
+
frameState.frameNumber,
|
|
2797
|
+
worldKeys,
|
|
2798
|
+
slotByEntity,
|
|
2799
|
+
{
|
|
2800
|
+
used:
|
|
2801
|
+
inspectionQuery.pageCount * inspectionQuery.pageIndexLimit -
|
|
2802
|
+
inspectionQuery.availablePages * inspectionQuery.pageIndexLimit,
|
|
2803
|
+
capacity: inspectionQuery.pageCount * inspectionQuery.pageIndexLimit,
|
|
2804
|
+
},
|
|
2805
|
+
occlusionFallback,
|
|
2806
|
+
ensureOcclusionRuntime().prepareUnavailable
|
|
2807
|
+
? { active: true, reason: 'query-unavailable' as const }
|
|
2808
|
+
: { active: false },
|
|
2809
|
+
);
|
|
2810
|
+
const attachmentByWorldKey = new Map<number, string>();
|
|
2811
|
+
for (let worldId = 0; worldId < compositionWorlds.length; worldId += 1) {
|
|
2812
|
+
const world = compositionWorlds[worldId];
|
|
2813
|
+
if (world === undefined) continue;
|
|
2814
|
+
const worldKey = worldKeys[worldId] ?? worldId;
|
|
2815
|
+
if (attachmentByWorldKey.has(worldKey)) {
|
|
2816
|
+
attachmentByWorldKey.clear();
|
|
2817
|
+
break;
|
|
2818
|
+
}
|
|
2819
|
+
attachmentByWorldKey.set(worldKey, world.identity);
|
|
2820
|
+
}
|
|
2821
|
+
lastLodWorldAttachments = attachmentByWorldKey;
|
|
2822
|
+
lastLodOcclusionInspection = inspectLodOcclusion({
|
|
2823
|
+
root: {
|
|
2824
|
+
guid:
|
|
2825
|
+
firstRenderable === undefined
|
|
2826
|
+
? 'none'
|
|
2827
|
+
: `asset-handle:${firstRenderable.assetHandle}`,
|
|
2828
|
+
sourceKey: `render-frame:${frameState.frameNumber}`,
|
|
2829
|
+
},
|
|
2830
|
+
view: {
|
|
2831
|
+
attachmentId:
|
|
2832
|
+
compositionWorlds[inspectionCamera.worldId ?? cameraOwner]?.identity ?? 'none',
|
|
2833
|
+
cameraEntity: inspectionCamera.entityKey ?? 0,
|
|
2834
|
+
viewRole: 'main',
|
|
2835
|
+
viewGeneration: inspectionCamera.historyVersion ?? 0,
|
|
2836
|
+
},
|
|
2837
|
+
slot: {
|
|
2838
|
+
primitiveSlot: 0,
|
|
2839
|
+
slotGeneration: persistentRenderScene.inspect().topology.revision,
|
|
2840
|
+
},
|
|
2841
|
+
generation: frameState.frameNumber,
|
|
2842
|
+
count: {
|
|
2843
|
+
candidates: candidateCount,
|
|
2844
|
+
visible: inspectionRenderables.length,
|
|
2845
|
+
occluded: visibilityProjection.suppressed,
|
|
2846
|
+
},
|
|
2847
|
+
lodHistogram: [{ level: 0, count: inspectionRenderables.length }],
|
|
2848
|
+
queryLatencyUs: { median: 0, p95: 0, last: 0 },
|
|
2849
|
+
pagePressure: { used: 0, capacity: 3 * 4096 },
|
|
2850
|
+
fallback: occlusionFallback,
|
|
2851
|
+
degradation: ensureOcclusionRuntime().prepareUnavailable
|
|
2852
|
+
? { active: true, reason: 'query-unavailable' as const }
|
|
2853
|
+
: { active: false },
|
|
2854
|
+
samples: inspectionRenderables.slice(0, 64).map((_, index) => ({
|
|
2855
|
+
primitiveSlot: index,
|
|
2856
|
+
level: 0,
|
|
2857
|
+
visible: true,
|
|
2858
|
+
})),
|
|
2859
|
+
submit: {
|
|
2860
|
+
frameId: frameState.frameNumber,
|
|
2861
|
+
build: resolveInspectionBuild(internals),
|
|
2862
|
+
deviceGeneration: internals.deviceScope.generation,
|
|
2863
|
+
},
|
|
2864
|
+
budget: inspectionBudget,
|
|
2865
|
+
worlds: inspectionWorlds,
|
|
2866
|
+
});
|
|
2867
|
+
} else {
|
|
2868
|
+
lastLodOcclusionInspection = undefined;
|
|
2869
|
+
lastLodWorldAttachments = new Map();
|
|
2870
|
+
}
|
|
2719
2871
|
const temporalDemanded =
|
|
2720
2872
|
cameras[0]?.antialias === 'taa' || (cameras[0]?.motionBlur?.shutterAngle ?? 0) > 0;
|
|
2721
2873
|
if (temporalDemanded) {
|
|
2722
|
-
persistentRenderScene.prepareTemporalFrame(
|
|
2874
|
+
persistentRenderScene.prepareTemporalFrame(submissionRenderables);
|
|
2723
2875
|
}
|
|
2724
2876
|
environmentLifecycle.bindScope(internals.deviceScope);
|
|
2725
2877
|
if (fogFailure !== undefined) {
|
|
@@ -2819,7 +2971,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2819
2971
|
const preparedResourceBatches = runProfiledRenderPhase(profileSession, 'features', () => {
|
|
2820
2972
|
if (internals.featureHost === undefined) return [];
|
|
2821
2973
|
const featureFrame = runRenderFeatureFrame(internals.featureHost, {
|
|
2822
|
-
worlds,
|
|
2974
|
+
worlds: compositionWorlds,
|
|
2823
2975
|
owner: resourceOwner,
|
|
2824
2976
|
frameNumber: frameState.frameNumber,
|
|
2825
2977
|
visibilitySnapshots: frame.featureVisibilitySnapshots,
|
|
@@ -2897,9 +3049,31 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2897
3049
|
// world that owns skylight / skybox / singleton render state, w5 / D-3).
|
|
2898
3050
|
// Only the Transparent segment is reordered; queue ordering between
|
|
2899
3051
|
// segments (sortDispatchByQueue, stable) is preserved.
|
|
2900
|
-
const orderedDispatch = runProfiledRenderPhase(profileSession, 'sort', () =>
|
|
2901
|
-
|
|
2902
|
-
|
|
3052
|
+
const orderedDispatch = runProfiledRenderPhase(profileSession, 'sort', () => {
|
|
3053
|
+
const cached = orderedDispatchCache;
|
|
3054
|
+
if (
|
|
3055
|
+
cached?.source === submissionDispatch &&
|
|
3056
|
+
cached.world === resourceWorld &&
|
|
3057
|
+
cached.camera === cameras &&
|
|
3058
|
+
cached.renderables === submissionRenderables
|
|
3059
|
+
) {
|
|
3060
|
+
return cached.value;
|
|
3061
|
+
}
|
|
3062
|
+
const value = sortTransparentDispatch(
|
|
3063
|
+
[...submissionDispatch],
|
|
3064
|
+
resourceWorld,
|
|
3065
|
+
cameras,
|
|
3066
|
+
submissionRenderables,
|
|
3067
|
+
);
|
|
3068
|
+
orderedDispatchCache = {
|
|
3069
|
+
source: submissionDispatch,
|
|
3070
|
+
world: resourceWorld,
|
|
3071
|
+
camera: cameras,
|
|
3072
|
+
renderables: submissionRenderables,
|
|
3073
|
+
value,
|
|
3074
|
+
};
|
|
3075
|
+
return value;
|
|
3076
|
+
});
|
|
2903
3077
|
|
|
2904
3078
|
// M3 / w26: single dispatch list replaces old three-bucket model.
|
|
2905
3079
|
// Pass dispatch to recordFrame — the record stage iterates dispatch
|
|
@@ -2927,13 +3101,16 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2927
3101
|
// The same runner is also called by geometry/material helpers;
|
|
2928
3102
|
// invoking those wrappers would turn a pass probe into the
|
|
2929
3103
|
// high-overhead per-draw `nested` probe.
|
|
2930
|
-
if (
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
3104
|
+
if (profileSession.detail === 'passes') {
|
|
3105
|
+
const isOcclusionOwner =
|
|
3106
|
+
phase === 'record/occlusion-query-submit' ||
|
|
3107
|
+
phase === 'record/occlusion-global-advance';
|
|
3108
|
+
const isGraphOwner =
|
|
3109
|
+
phase === 'record/graph-execute' ||
|
|
3110
|
+
(phase.startsWith('record/graph-execute/') &&
|
|
3111
|
+
(!phase.slice('record/graph-execute/'.length).includes('/') ||
|
|
3112
|
+
phase.endsWith('/geometry-loop')));
|
|
3113
|
+
if (!isOcclusionOwner && !isGraphOwner) return action();
|
|
2937
3114
|
}
|
|
2938
3115
|
return runProfiledRenderPhase(profileSession, phase, action);
|
|
2939
3116
|
};
|
|
@@ -2946,7 +3123,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2946
3123
|
resourceWorld,
|
|
2947
3124
|
cameras,
|
|
2948
3125
|
lights,
|
|
2949
|
-
|
|
3126
|
+
[...submissionRenderables],
|
|
2950
3127
|
orderedDispatch,
|
|
2951
3128
|
frameState,
|
|
2952
3129
|
dispatchCounts,
|
|
@@ -2956,18 +3133,20 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2956
3133
|
skybox,
|
|
2957
3134
|
skyboxCount,
|
|
2958
3135
|
postProcessParams,
|
|
2959
|
-
|
|
2960
|
-
worlds,
|
|
3136
|
+
compositionWorlds,
|
|
2961
3137
|
recordProfilePhase,
|
|
2962
3138
|
{
|
|
2963
3139
|
owner: gpuDrivenProduction,
|
|
2964
3140
|
scene: persistentRenderScene.compositionGpuDrivenState(),
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
persistentRenderScene.setCompositionGpuDrivenCoverage(key, ownsAll);
|
|
3141
|
+
onSceneDemand: (sceneRowsRequired) => {
|
|
3142
|
+
persistentRenderScene.setCompositionGpuSceneDemand(sceneRowsRequired);
|
|
2968
3143
|
},
|
|
3144
|
+
activeEntityKeys: visibilityProjection.activeEntityKeys,
|
|
3145
|
+
activeEntityRevision: visibilityProjection.activeEntityRevision,
|
|
3146
|
+
telemetryCandidateCount: lodCandidateCountForFrame,
|
|
3147
|
+
...(occlusionProjection === undefined ? {} : { occlusion: occlusionProjection }),
|
|
2969
3148
|
},
|
|
2970
|
-
|
|
3149
|
+
compositionLeases,
|
|
2971
3150
|
featureGraphCandidate,
|
|
2972
3151
|
pointsLinesOwner,
|
|
2973
3152
|
{
|
|
@@ -2984,6 +3163,30 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2984
3163
|
timingCapture,
|
|
2985
3164
|
),
|
|
2986
3165
|
);
|
|
3166
|
+
if (submitted) {
|
|
3167
|
+
try {
|
|
3168
|
+
internals.recoveryColdWorkGuard?.finish();
|
|
3169
|
+
} catch (cause) {
|
|
3170
|
+
submitted = false;
|
|
3171
|
+
throw cause;
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
if (submitted && lastLodOcclusionInspection !== undefined) {
|
|
3175
|
+
const query = ensureOcclusionRuntime().inspect();
|
|
3176
|
+
lastLodOcclusionInspection = {
|
|
3177
|
+
...lastLodOcclusionInspection,
|
|
3178
|
+
pagePressure: {
|
|
3179
|
+
used:
|
|
3180
|
+
query.pageCount * query.pageIndexLimit -
|
|
3181
|
+
query.availablePages * query.pageIndexLimit,
|
|
3182
|
+
capacity: query.pageCount * query.pageIndexLimit,
|
|
3183
|
+
},
|
|
3184
|
+
fallback: occlusionFallback,
|
|
3185
|
+
degradation: ensureOcclusionRuntime().prepareUnavailable
|
|
3186
|
+
? { active: true, reason: 'query-unavailable' as const }
|
|
3187
|
+
: { active: false },
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
2987
3190
|
const directionalQuality = lights.directionalShadowQuality;
|
|
2988
3191
|
const directionalShadowReady =
|
|
2989
3192
|
directionalQuality === undefined ||
|
|
@@ -3007,6 +3210,21 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3007
3210
|
};
|
|
3008
3211
|
}
|
|
3009
3212
|
if (submitted) {
|
|
3213
|
+
recoveryOwner.setLastSuccessfulFrameSeed({
|
|
3214
|
+
frame,
|
|
3215
|
+
worlds: compositionWorlds,
|
|
3216
|
+
cameraOwner: composition.owners.cameraOwner,
|
|
3217
|
+
resourceOwner: composition.owners.resourceOwner,
|
|
3218
|
+
width: Math.max(1, internals.canvas.width),
|
|
3219
|
+
height: Math.max(1, internals.canvas.height),
|
|
3220
|
+
});
|
|
3221
|
+
submittedFrameCount += 1;
|
|
3222
|
+
lastSubmittedGeneration = internals.deviceScope?.generation;
|
|
3223
|
+
if (occlusionProjection === undefined) {
|
|
3224
|
+
runProfiledRenderPhase(profileSession, 'record/occlusion-global-advance', () =>
|
|
3225
|
+
ensureOcclusionRuntime().advanceSuccessfulSubmit(frameState.frameNumber),
|
|
3226
|
+
);
|
|
3227
|
+
}
|
|
3010
3228
|
const temporalCommit = persistentRenderScene.commitTemporalFrame();
|
|
3011
3229
|
if (!temporalCommit.ok) {
|
|
3012
3230
|
internals.errorRegistry.fire(temporalCommit.error);
|
|
@@ -3085,6 +3303,21 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3085
3303
|
const currentFrameId = frameState.frameNumber - 1;
|
|
3086
3304
|
return observeCurrentFrame(options, frameState.currentFrameObservationSource, currentFrameId);
|
|
3087
3305
|
},
|
|
3306
|
+
async observeLodOcclusion(): Promise<void> {
|
|
3307
|
+
await ensureOcclusionRuntime().waitForCompletions();
|
|
3308
|
+
applyLodSelectionTelemetry(await gpuDrivenProduction.readLodSelection());
|
|
3309
|
+
if (lastLodOcclusionInspection !== undefined) {
|
|
3310
|
+
const queryLatency = ensureOcclusionRuntime().inspectQueryLatency();
|
|
3311
|
+
lastLodOcclusionInspection = inspectLodOcclusion({
|
|
3312
|
+
...lastLodOcclusionInspection,
|
|
3313
|
+
queryLatencyUs: {
|
|
3314
|
+
median: queryLatency.median,
|
|
3315
|
+
p95: queryLatency.p95,
|
|
3316
|
+
last: queryLatency.last,
|
|
3317
|
+
},
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
},
|
|
3088
3321
|
getCurrentGraphTarget(name: string) {
|
|
3089
3322
|
const graph = frameState.perFrameGraph;
|
|
3090
3323
|
if (graph === undefined || graph === null) return undefined;
|
|
@@ -3123,6 +3356,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3123
3356
|
get meshMaterialBindings(): readonly MeshMaterialBindingObservation[] {
|
|
3124
3357
|
return lastMeshMaterialBindings;
|
|
3125
3358
|
},
|
|
3359
|
+
get iblBinding(): import('./mesh-material-bindings').IblBindingInspection | undefined {
|
|
3360
|
+
return frameState.iblBindingInspection;
|
|
3361
|
+
},
|
|
3126
3362
|
get perFramePassNames(): readonly string[] {
|
|
3127
3363
|
return frameState.compiledFrameGraph?.inspect().passes.map((pass) => pass.name) ?? [];
|
|
3128
3364
|
},
|
|
@@ -3132,6 +3368,25 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3132
3368
|
get perFrameGraphInfo(): CompiledRenderGraphInfo | undefined {
|
|
3133
3369
|
return frameState.compiledFrameGraph?.inspect();
|
|
3134
3370
|
},
|
|
3371
|
+
get recoveryEvidence(): RecoveryProductionEvidence {
|
|
3372
|
+
const graph = frameState.compiledFrameGraph?.inspect();
|
|
3373
|
+
return Object.freeze({
|
|
3374
|
+
producerRoots: createRendererProducerRootMatrix(),
|
|
3375
|
+
graph: Object.freeze({
|
|
3376
|
+
ready: graph !== undefined,
|
|
3377
|
+
generation: frameState.graphGeneration,
|
|
3378
|
+
passCount: graph?.passes.length ?? 0,
|
|
3379
|
+
resourceCount: graph?.resources.length ?? 0,
|
|
3380
|
+
}),
|
|
3381
|
+
residency: Object.freeze({
|
|
3382
|
+
meshResidencyEpoch: internals.gpuStore.meshResidencyEpoch,
|
|
3383
|
+
}),
|
|
3384
|
+
submissions: Object.freeze({
|
|
3385
|
+
count: submittedFrameCount,
|
|
3386
|
+
lastGeneration: lastSubmittedGeneration,
|
|
3387
|
+
}),
|
|
3388
|
+
});
|
|
3389
|
+
},
|
|
3135
3390
|
get volumetricFog(): import('./volume/inspection').VolumetricFogInspection {
|
|
3136
3391
|
return frameState.volumetricFogInspection;
|
|
3137
3392
|
},
|
|
@@ -3227,6 +3482,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3227
3482
|
throw cause;
|
|
3228
3483
|
}
|
|
3229
3484
|
},
|
|
3485
|
+
lookupPostProcess,
|
|
3486
|
+
prepareRecoveryGraphCandidate: recoveryOwner.prepareRecoveryGraphCandidate,
|
|
3487
|
+
submitCandidateSetup: recoveryOwner.submitCandidateSetup,
|
|
3488
|
+
publishRecoveryGraphCandidate: recoveryOwner.publishRecoveryGraphCandidate,
|
|
3489
|
+
discardRecoveryGraphCandidate: recoveryOwner.discardRecoveryGraphCandidate,
|
|
3230
3490
|
disposeFrameState(): void {
|
|
3231
3491
|
(internals as TimingSessionHost).gpuPassTimingSession?.dispose();
|
|
3232
3492
|
(internals as TimingSessionHost).gpuPassTimingSession = undefined;
|
|
@@ -3234,8 +3494,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3234
3494
|
(internals as TimingSessionHost).gpuPassTimingSubmittedWork = undefined;
|
|
3235
3495
|
(internals as TimingSessionHost).gpuPassTimingFrameIdentity = undefined;
|
|
3236
3496
|
(internals as TimingSessionHost).gpuPassTimingBeginReason = undefined;
|
|
3497
|
+
pointsLinesOwner.dispose();
|
|
3237
3498
|
persistentRenderScene.dispose();
|
|
3238
3499
|
gpuDrivenProduction.dispose();
|
|
3500
|
+
occlusionRuntime?.dispose();
|
|
3239
3501
|
disposeFeatureGpuWork();
|
|
3240
3502
|
reflectionProbeOwner.dispose();
|
|
3241
3503
|
// Bloom generations are renderer-owned rather than graph-owned. Drain
|
|
@@ -3304,6 +3566,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3304
3566
|
// dispose error strategy).
|
|
3305
3567
|
disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
|
|
3306
3568
|
disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
|
|
3569
|
+
if (frameState.probeBlendRecordBuffer !== undefined) {
|
|
3570
|
+
if (!frameState.probeBlendRecordBuffer.isDestroyed) {
|
|
3571
|
+
const result = frameState.probeBlendRecordBuffer.destroy();
|
|
3572
|
+
if (!result.ok) internals.errorRegistry.fire(result.error);
|
|
3573
|
+
}
|
|
3574
|
+
delete frameState.probeBlendRecordBuffer;
|
|
3575
|
+
}
|
|
3576
|
+
frameState.probeBlendRecordBufferCapacity = 0;
|
|
3577
|
+
frameState.probeBlendBuffers.clear();
|
|
3307
3578
|
if (frameState.morphBuffers !== undefined) {
|
|
3308
3579
|
for (const entry of frameState.morphBuffers.values()) {
|
|
3309
3580
|
if (!entry.buffer.isDestroyed) {
|
|
@@ -3323,7 +3594,18 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3323
3594
|
frameState.pointShadowAtlas = null;
|
|
3324
3595
|
}
|
|
3325
3596
|
},
|
|
3326
|
-
resetForRecover(): void {
|
|
3597
|
+
resetForRecover(retiringPipelineState?: PipelineState, replacementDevice?: RhiDevice): void {
|
|
3598
|
+
frameState.lastSuccessfulTemporalView = undefined;
|
|
3599
|
+
frameState.successfulTemporalFrameIndex = 0;
|
|
3600
|
+
frameState.pendingTemporalCommit = { kind: 'none' };
|
|
3601
|
+
// The active expansion buffers belong to the lost generation. Abandon
|
|
3602
|
+
// their handles without invoking destroy on a device that can no longer
|
|
3603
|
+
// service cleanup; the staged candidate owns the replacement buffers.
|
|
3604
|
+
pointsLinesOwner.abandonForDeviceLoss();
|
|
3605
|
+
ssrFormatReceipt = undefined;
|
|
3606
|
+
ssrFormatProbeGeneration = -1;
|
|
3607
|
+
ssrFormatProbe = undefined;
|
|
3608
|
+
ssrFallbackGeneration = undefined;
|
|
3327
3609
|
transmissionAdmission.markDeviceLost();
|
|
3328
3610
|
lastTransmissionKey = '';
|
|
3329
3611
|
transmissionCapabilityGeneration = -1;
|
|
@@ -3340,13 +3622,28 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3340
3622
|
updateTransmissionInspection(lastTransmissionAntialias, false);
|
|
3341
3623
|
persistentRenderScene.resetGpuForRecover();
|
|
3342
3624
|
gpuDrivenProduction.dispose();
|
|
3343
|
-
|
|
3625
|
+
// The active renderer still points at the lost device until the caller
|
|
3626
|
+
// completes the synchronous publication boundary. Bind the replacement
|
|
3627
|
+
// explicitly so the first published draw never routes through an owner
|
|
3628
|
+
// recreated against the old physical device.
|
|
3629
|
+
gpuDrivenProduction = GpuDrivenProduction.forDevice(
|
|
3630
|
+
replacementDevice ?? internals.device,
|
|
3631
|
+
gpuDrivenShaderFactory,
|
|
3632
|
+
);
|
|
3633
|
+
occlusionRuntime?.dispose();
|
|
3634
|
+
// The replacement device is installed by the host after this hook. Do
|
|
3635
|
+
// not recreate device-bound query resources against the lost device;
|
|
3636
|
+
// the first post-recovery frame lazily binds the current device.
|
|
3637
|
+
occlusionRuntime = undefined;
|
|
3344
3638
|
disposeFeatureGpuWork();
|
|
3639
|
+
featureGpuWork = createFeatureGpuWorkOwner(internals);
|
|
3345
3640
|
reflectionProbeOwner.dispose();
|
|
3346
3641
|
// A recovered device cannot retain handles from the lost device. The
|
|
3347
3642
|
// Bloom owner abandons candidates and retires every committed generation
|
|
3348
3643
|
// before the new PipelineState is rebuilt.
|
|
3349
|
-
|
|
3644
|
+
(
|
|
3645
|
+
retiringPipelineState ?? internals.getPipelineState()
|
|
3646
|
+
)?.perPassResources.drainBloomResources?.();
|
|
3350
3647
|
// feat-20260622-s5 M3 / B-2 / w18: recover() rebuild drops device-bound
|
|
3351
3648
|
// state minted by the lost device. The active graph and per-entity caches
|
|
3352
3649
|
// must be discarded, not merely marked for destruction: their opaque
|
|
@@ -3383,6 +3680,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3383
3680
|
frameState.directionalShadowCacheRecorded = false;
|
|
3384
3681
|
frameState.currentFrameObservationSource = undefined;
|
|
3385
3682
|
frameState.lastSuccessfulCameraAntialias = undefined;
|
|
3683
|
+
frameState.temporalFrameTransaction.reset('device-recovery');
|
|
3684
|
+
frameState.temporalFrame = undefined;
|
|
3685
|
+
frameState.temporalFrameInput = undefined;
|
|
3686
|
+
frameState.lastSuccessfulTemporalView = undefined;
|
|
3687
|
+
frameState.successfulTemporalFrameIndex = 0;
|
|
3688
|
+
frameState.pendingTemporalCommit = { kind: 'none' };
|
|
3386
3689
|
frameState.currentDirectionalShadowView = null;
|
|
3387
3690
|
frameState.currentSpotShadowView = null;
|
|
3388
3691
|
if (frameState.temporalGpuState !== undefined) {
|
|
@@ -3403,6 +3706,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3403
3706
|
}
|
|
3404
3707
|
frameState.retiredCompiledFrameGraphs.clear();
|
|
3405
3708
|
frameState.instanceBuffers.clear();
|
|
3709
|
+
delete frameState.probeBlendRecordBuffer;
|
|
3710
|
+
frameState.probeBlendRecordBufferCapacity = 0;
|
|
3711
|
+
frameState.probeBlendBuffers.clear();
|
|
3406
3712
|
frameState.morphBuffers?.clear();
|
|
3407
3713
|
frameState.transientInstanceBuffers = [];
|
|
3408
3714
|
frameState.pointShadowAtlas = null;
|
|
@@ -3423,53 +3729,152 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3423
3729
|
// the lost device, so recovery must invalidate this cache alongside the
|
|
3424
3730
|
// feature-host declarations and UBOs below.
|
|
3425
3731
|
postProcessPipelineCache.clear();
|
|
3426
|
-
// Logical declarations
|
|
3427
|
-
//
|
|
3428
|
-
postProcessParamsBuffers.clear();
|
|
3732
|
+
// Logical declarations and the active params bundle stay live until the
|
|
3733
|
+
// candidate post-process bundle crosses the publication boundary.
|
|
3429
3734
|
resetRenderFeatureGraphState(internals);
|
|
3430
3735
|
},
|
|
3736
|
+
prepareRecoveryPostProcessResources(device: RhiDevice) {
|
|
3737
|
+
return prepareRecoveryPostProcessResources(device);
|
|
3738
|
+
},
|
|
3739
|
+
publishRecoveryPostProcessResources(candidate: RecoveryPostProcessResources): void {
|
|
3740
|
+
publishRecoveryPostProcessResources(candidate);
|
|
3741
|
+
},
|
|
3742
|
+
discardRecoveryPostProcessResources(candidate: RecoveryPostProcessResources): void {
|
|
3743
|
+
discardRecoveryPostProcessResources(candidate);
|
|
3744
|
+
},
|
|
3431
3745
|
restorePostProcessResources(): void {
|
|
3432
|
-
const
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3746
|
+
const prepared = prepareRecoveryPostProcessResources(internals.device);
|
|
3747
|
+
if (!prepared.ok) throw prepared.error;
|
|
3748
|
+
publishRecoveryPostProcessResources(prepared.value);
|
|
3749
|
+
},
|
|
3750
|
+
prepareRecoveryRoots(runtime: RecoveryRootRuntime): RecoveryRootBundle {
|
|
3751
|
+
// Snapshot only CPU projections/declarations here. The root create
|
|
3752
|
+
// callbacks below must never close over a mutable active owner while
|
|
3753
|
+
// LifecycleTransaction is awaiting candidate construction.
|
|
3754
|
+
const candidateEnvironmentLifecycle = environmentLifecycle.createRecoveryCandidate(
|
|
3755
|
+
runtime.scope,
|
|
3756
|
+
);
|
|
3757
|
+
const roots: LifecycleResourceSpec<unknown>[] = [];
|
|
3758
|
+
let candidateEnvironmentGeneration: EnvironmentGeneration | undefined;
|
|
3759
|
+
let environmentRootCleaned = false;
|
|
3760
|
+
let published = false;
|
|
3761
|
+
if (candidateEnvironmentLifecycle.hasRecoveryFrame()) {
|
|
3762
|
+
const environmentRoot = candidateEnvironmentLifecycle.createRecoveryRoot(runtime.scope);
|
|
3763
|
+
roots.push({
|
|
3764
|
+
...environmentRoot,
|
|
3765
|
+
create: async () => {
|
|
3766
|
+
const generation = (await environmentRoot.create()) as EnvironmentGeneration;
|
|
3767
|
+
candidateEnvironmentGeneration = generation;
|
|
3768
|
+
return generation;
|
|
3769
|
+
},
|
|
3770
|
+
cleanup: (value) => {
|
|
3771
|
+
if (environmentRootCleaned) return;
|
|
3772
|
+
environmentRootCleaned = true;
|
|
3773
|
+
if (candidateEnvironmentGeneration === value) {
|
|
3774
|
+
candidateEnvironmentGeneration = undefined;
|
|
3775
|
+
}
|
|
3776
|
+
if (published) {
|
|
3777
|
+
candidateEnvironmentLifecycle.retirePublishedGeneration(
|
|
3778
|
+
value as EnvironmentGeneration,
|
|
3779
|
+
);
|
|
3780
|
+
} else {
|
|
3781
|
+
environmentRoot.cleanup(value);
|
|
3782
|
+
}
|
|
3783
|
+
},
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3786
|
+
const graphCandidate = runtime.graphCandidate;
|
|
3787
|
+
if (graphCandidate !== undefined) {
|
|
3788
|
+
const releaseGraphCandidate = (): void => graphCandidate.release();
|
|
3789
|
+
const compiledGraph = graphCandidate.frameState.compiledFrameGraph;
|
|
3790
|
+
if (compiledGraph !== null) {
|
|
3791
|
+
roots.push({
|
|
3792
|
+
kind: 'pipeline',
|
|
3793
|
+
create: () => {
|
|
3794
|
+
if (!runtime.scope.isAlive()) {
|
|
3795
|
+
throw new Error('Recovery graph candidate scope is not active.');
|
|
3796
|
+
}
|
|
3797
|
+
if (graphCandidate.frameState.compiledFrameGraph !== compiledGraph) {
|
|
3798
|
+
throw new Error('Recovery graph candidate was changed before aggregate assembly.');
|
|
3799
|
+
}
|
|
3800
|
+
return compiledGraph;
|
|
3801
|
+
},
|
|
3802
|
+
// All graph/feature/GPU-driven candidate resources share one
|
|
3803
|
+
// owner. The root is a real release seam, not a marker; the
|
|
3804
|
+
// candidate latch makes every root cleanup call harmless after
|
|
3805
|
+
// the first one.
|
|
3806
|
+
cleanup: releaseGraphCandidate,
|
|
3459
3807
|
});
|
|
3460
|
-
if (!created.ok) throw created.error;
|
|
3461
|
-
buffer = created.value;
|
|
3462
3808
|
}
|
|
3463
|
-
if (
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3809
|
+
if (graphCandidate.featureHost !== undefined) {
|
|
3810
|
+
const featureRoot = graphCandidate.featureHost.createRecoveryRoot(runtime.scope);
|
|
3811
|
+
roots.push({ ...featureRoot, cleanup: releaseGraphCandidate });
|
|
3812
|
+
}
|
|
3813
|
+
if (graphCandidate.featureGpuWork !== undefined) {
|
|
3814
|
+
const featureWorkRoot = graphCandidate.featureGpuWork.createRecoveryRoot(runtime.scope);
|
|
3815
|
+
roots.push({ ...featureWorkRoot, cleanup: releaseGraphCandidate });
|
|
3816
|
+
}
|
|
3817
|
+
if (graphCandidate.gpuDrivenProduction !== undefined) {
|
|
3818
|
+
const productionRoot = graphCandidate.gpuDrivenProduction.createRecoveryRoot(
|
|
3819
|
+
runtime.scope,
|
|
3820
|
+
);
|
|
3821
|
+
roots.push({ ...productionRoot, cleanup: releaseGraphCandidate });
|
|
3822
|
+
}
|
|
3823
|
+
if (graphCandidate.gpuDrivenScene?.state !== undefined) {
|
|
3824
|
+
const sceneRoot = graphCandidate.gpuDrivenScene.createRecoveryRoot(runtime.scope);
|
|
3825
|
+
roots.push({ ...sceneRoot, cleanup: releaseGraphCandidate });
|
|
3826
|
+
}
|
|
3827
|
+
if (graphCandidate.pointsLines !== undefined) {
|
|
3828
|
+
const pointsLinesRoot = graphCandidate.pointsLines.createRecoveryRoot(runtime.scope);
|
|
3829
|
+
roots.push({ ...pointsLinesRoot, cleanup: releaseGraphCandidate });
|
|
3830
|
+
}
|
|
3831
|
+
if (runtime.gpuStore.recoveryResourceCount() > 0) {
|
|
3832
|
+
const gpuStoreRoot = runtime.gpuStore.createRecoveryRoot(runtime.scope);
|
|
3833
|
+
roots.push({ ...gpuStoreRoot, cleanup: releaseGraphCandidate });
|
|
3467
3834
|
}
|
|
3468
3835
|
}
|
|
3836
|
+
let discarded = false;
|
|
3837
|
+
return {
|
|
3838
|
+
roots: Object.freeze(roots),
|
|
3839
|
+
publish: () => {
|
|
3840
|
+
if (published || discarded) return;
|
|
3841
|
+
if (
|
|
3842
|
+
candidateEnvironmentGeneration !== undefined &&
|
|
3843
|
+
!candidateEnvironmentGeneration.retired
|
|
3844
|
+
) {
|
|
3845
|
+
environmentLifecycle.retireActiveForReplacement();
|
|
3846
|
+
candidateEnvironmentLifecycle.publish(candidateEnvironmentGeneration);
|
|
3847
|
+
}
|
|
3848
|
+
published = true;
|
|
3849
|
+
environmentLifecycle = candidateEnvironmentLifecycle;
|
|
3850
|
+
Object.assign(frameState, { environmentLifecycle });
|
|
3851
|
+
},
|
|
3852
|
+
discard: () => {
|
|
3853
|
+
if (published || discarded) return;
|
|
3854
|
+
discarded = true;
|
|
3855
|
+
if (candidateEnvironmentGeneration !== undefined) {
|
|
3856
|
+
candidateEnvironmentLifecycle.discard(candidateEnvironmentGeneration);
|
|
3857
|
+
candidateEnvironmentGeneration = undefined;
|
|
3858
|
+
}
|
|
3859
|
+
candidateEnvironmentLifecycle.discardRecoveryCandidates();
|
|
3860
|
+
// The transaction can fail before it reaches any graph root (for
|
|
3861
|
+
// example while the environment root is created). Keep the graph,
|
|
3862
|
+
// feature, GPU-driven, and residency resources on the same real
|
|
3863
|
+
// candidate owner in that case; later scope/factory cleanup is a
|
|
3864
|
+
// harmless repeat through the candidate latch.
|
|
3865
|
+
graphCandidate?.release();
|
|
3866
|
+
runtime.scope.abandon();
|
|
3867
|
+
},
|
|
3868
|
+
};
|
|
3469
3869
|
},
|
|
3470
3870
|
};
|
|
3471
3871
|
}
|
|
3472
3872
|
|
|
3873
|
+
/** Resolve the build identity used by every inspection submit. */
|
|
3874
|
+
export function resolveInspectionBuild(internals: Pick<RenderSystemInternals, 'build'>): string {
|
|
3875
|
+
return internals.build ?? 'render-system';
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3473
3878
|
// @forgeax/engine-render - built-in record context extension.
|
|
3474
3879
|
//
|
|
3475
3880
|
// The public RenderPipelineContext is canonical in render-contract.ts. This
|