@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
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Query, World } from '@forgeax/engine-ecs';
|
|
2
2
|
import { componentId } from '@forgeax/engine-ecs/internal';
|
|
3
|
-
import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
|
|
3
|
+
import type { RenderReadLease, RenderReadVersion } from '@forgeax/engine-ecs/projection';
|
|
4
4
|
import { box3, frustum, mat4 } from '@forgeax/engine-math';
|
|
5
5
|
import { type RhiDevice, RhiError } from '@forgeax/engine-rhi';
|
|
6
|
-
import { ChildOf, Transform } from '@forgeax/engine-scene';
|
|
6
|
+
import { ChildOf, GlobalTransform, Transform } from '@forgeax/engine-scene';
|
|
7
7
|
import { Skin } from '@forgeax/engine-skinning';
|
|
8
8
|
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
9
9
|
import {
|
|
@@ -11,8 +11,10 @@ import {
|
|
|
11
11
|
DirectionalLight,
|
|
12
12
|
Instances,
|
|
13
13
|
Layer,
|
|
14
|
+
LightProbe,
|
|
14
15
|
MeshFilter,
|
|
15
16
|
MeshRenderer,
|
|
17
|
+
MotionBlur,
|
|
16
18
|
PointLight,
|
|
17
19
|
PointLightShadow,
|
|
18
20
|
PostProcessParams,
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
SpriteRegionOverride,
|
|
25
27
|
Visibility,
|
|
26
28
|
} from '../components';
|
|
29
|
+
import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
|
|
27
30
|
import {
|
|
28
31
|
BatchTopology,
|
|
29
32
|
type BatchTopologyInspection,
|
|
@@ -34,6 +37,7 @@ import type { PersistentRenderSceneInspection } from '../inspection-types';
|
|
|
34
37
|
import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
|
|
35
38
|
import type { PointsLinesInspection } from '../points-lines/inspection';
|
|
36
39
|
import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
|
|
40
|
+
import { worldEntityKey } from '../record/frame-snapshot';
|
|
37
41
|
import {
|
|
38
42
|
type ReflectionProbeFact,
|
|
39
43
|
ReflectionProbeProjection,
|
|
@@ -55,21 +59,30 @@ import {
|
|
|
55
59
|
type TransmissionDemandOperation,
|
|
56
60
|
} from '../transmission/projection';
|
|
57
61
|
import { VolumetricFog } from '../volume/component';
|
|
62
|
+
import { ProbeBlendSceneProjection, type ProbeSceneObjectInput } from './probe-blend';
|
|
58
63
|
import type {
|
|
59
64
|
RenderSceneApplyResult,
|
|
60
65
|
RenderSceneBounds,
|
|
61
|
-
RenderSceneDelta,
|
|
62
66
|
RenderSceneInspection,
|
|
63
67
|
RenderSceneOperation,
|
|
64
68
|
RenderSceneRecord,
|
|
65
69
|
RenderSceneResyncReason,
|
|
66
70
|
RenderSceneSlot,
|
|
67
71
|
} from './render-scene-types';
|
|
72
|
+
import { createVisibilityBudget, type VisibilityBudget } from './visibility/budget';
|
|
73
|
+
import {
|
|
74
|
+
primitiveKey,
|
|
75
|
+
primitiveKeyId,
|
|
76
|
+
VisibilityFacetStore,
|
|
77
|
+
viewKey,
|
|
78
|
+
viewKeyId,
|
|
79
|
+
} from './visibility/facet';
|
|
80
|
+
import { decideVisibility } from './visibility/occlusion-confidence';
|
|
81
|
+
import type { PrimitiveKey, ViewKey, VisibilityCandidate } from './visibility/types';
|
|
68
82
|
|
|
69
83
|
export type {
|
|
70
84
|
RenderSceneApplyResult,
|
|
71
85
|
RenderSceneBounds,
|
|
72
|
-
RenderSceneDelta,
|
|
73
86
|
RenderSceneIdentity,
|
|
74
87
|
RenderSceneInspection,
|
|
75
88
|
RenderSceneOperation,
|
|
@@ -86,6 +99,14 @@ interface PendingIdentity {
|
|
|
86
99
|
removed: boolean;
|
|
87
100
|
}
|
|
88
101
|
|
|
102
|
+
interface WorldTransformSpan {
|
|
103
|
+
readonly worldId: number;
|
|
104
|
+
readonly entities: Readonly<Uint32Array>;
|
|
105
|
+
readonly worlds: Readonly<Float32Array>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type GlobalTransformChangeQuery = Query<readonly [typeof GlobalTransform]>;
|
|
109
|
+
|
|
89
110
|
export interface RenderSceneSubmissionCapture {
|
|
90
111
|
readonly revision: number;
|
|
91
112
|
readonly slots: readonly {
|
|
@@ -200,31 +221,55 @@ function intersects(left: RenderSceneBounds, right: RenderSceneBounds): boolean
|
|
|
200
221
|
);
|
|
201
222
|
}
|
|
202
223
|
|
|
203
|
-
function worldBounds(
|
|
224
|
+
function worldBounds(
|
|
225
|
+
snapshot: RenderableSnapshot,
|
|
226
|
+
target?: RenderSceneBounds,
|
|
227
|
+
): RenderSceneBounds | undefined {
|
|
204
228
|
const local = snapshot.localAabb;
|
|
205
229
|
if (local === undefined || local.length < 6) return undefined;
|
|
206
230
|
const world = snapshot.transform.world;
|
|
207
|
-
const min
|
|
208
|
-
const max
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
231
|
+
const min = (target?.min ?? [0, 0, 0]) as [number, number, number];
|
|
232
|
+
const max = (target?.max ?? [0, 0, 0]) as [number, number, number];
|
|
233
|
+
const centerX = ((local[0] ?? 0) + (local[3] ?? 0)) * 0.5;
|
|
234
|
+
const centerY = ((local[1] ?? 0) + (local[4] ?? 0)) * 0.5;
|
|
235
|
+
const centerZ = ((local[2] ?? 0) + (local[5] ?? 0)) * 0.5;
|
|
236
|
+
const extentX = ((local[3] ?? 0) - (local[0] ?? 0)) * 0.5;
|
|
237
|
+
const extentY = ((local[4] ?? 0) - (local[1] ?? 0)) * 0.5;
|
|
238
|
+
const extentZ = ((local[5] ?? 0) - (local[2] ?? 0)) * 0.5;
|
|
239
|
+
const worldCenterX =
|
|
240
|
+
(world[0] ?? 0) * centerX +
|
|
241
|
+
(world[4] ?? 0) * centerY +
|
|
242
|
+
(world[8] ?? 0) * centerZ +
|
|
243
|
+
(world[12] ?? 0);
|
|
244
|
+
const worldCenterY =
|
|
245
|
+
(world[1] ?? 0) * centerX +
|
|
246
|
+
(world[5] ?? 0) * centerY +
|
|
247
|
+
(world[9] ?? 0) * centerZ +
|
|
248
|
+
(world[13] ?? 0);
|
|
249
|
+
const worldCenterZ =
|
|
250
|
+
(world[2] ?? 0) * centerX +
|
|
251
|
+
(world[6] ?? 0) * centerY +
|
|
252
|
+
(world[10] ?? 0) * centerZ +
|
|
253
|
+
(world[14] ?? 0);
|
|
254
|
+
const worldExtentX =
|
|
255
|
+
Math.abs(world[0] ?? 0) * extentX +
|
|
256
|
+
Math.abs(world[4] ?? 0) * extentY +
|
|
257
|
+
Math.abs(world[8] ?? 0) * extentZ;
|
|
258
|
+
const worldExtentY =
|
|
259
|
+
Math.abs(world[1] ?? 0) * extentX +
|
|
260
|
+
Math.abs(world[5] ?? 0) * extentY +
|
|
261
|
+
Math.abs(world[9] ?? 0) * extentZ;
|
|
262
|
+
const worldExtentZ =
|
|
263
|
+
Math.abs(world[2] ?? 0) * extentX +
|
|
264
|
+
Math.abs(world[6] ?? 0) * extentY +
|
|
265
|
+
Math.abs(world[10] ?? 0) * extentZ;
|
|
266
|
+
min[0] = worldCenterX - worldExtentX;
|
|
267
|
+
min[1] = worldCenterY - worldExtentY;
|
|
268
|
+
min[2] = worldCenterZ - worldExtentZ;
|
|
269
|
+
max[0] = worldCenterX + worldExtentX;
|
|
270
|
+
max[1] = worldCenterY + worldExtentY;
|
|
271
|
+
max[2] = worldCenterZ + worldExtentZ;
|
|
272
|
+
return target ?? { min, max };
|
|
228
273
|
}
|
|
229
274
|
|
|
230
275
|
function boundsFromArray(bounds: ArrayLike<number>): RenderSceneBounds | undefined {
|
|
@@ -253,20 +298,23 @@ function boundsFromArray(bounds: ArrayLike<number>): RenderSceneBounds | undefin
|
|
|
253
298
|
export class RenderScene {
|
|
254
299
|
private readonly slots: Array<RenderSceneSlot | undefined> = [];
|
|
255
300
|
private readonly lastSubmittedSlots: Array<RenderSceneSlot | undefined> = [];
|
|
301
|
+
private readonly previousWorldBySlot: Array<Float32Array | undefined> = [];
|
|
256
302
|
private readonly generations: number[] = [];
|
|
257
303
|
private readonly freeSlots: number[] = [];
|
|
258
304
|
private readonly slotsByWorld = new Map<number, Map<number, number>>();
|
|
259
305
|
private readonly slotsByMaterial = new Map<number, Set<number>>();
|
|
260
306
|
private readonly dirtySinceSubmitted = new Set<number>();
|
|
307
|
+
private readonly transformUpdatedSlots: RenderSceneSlot[] = [];
|
|
308
|
+
private temporalTracking = false;
|
|
261
309
|
private lastSubmittedVisible = new Set<string>();
|
|
262
310
|
private submittedEpoch = 0;
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
private readonly
|
|
311
|
+
// Bounds are a derived column of the stable render slot, not metadata of a
|
|
312
|
+
// transient snapshot wrapper. Undefined means dirty; null means that the
|
|
313
|
+
// retained row has no usable bounds and must be conservatively visible.
|
|
314
|
+
private readonly worldBoundsBySlot: Array<RenderSceneBounds | null | undefined> = [];
|
|
267
315
|
private readonly instanceBoundsCache = new InstanceBoundsCache();
|
|
268
316
|
private orderedSlots: number[] = [];
|
|
269
|
-
private materialized:
|
|
317
|
+
private materialized: RenderableSnapshot[] | undefined;
|
|
270
318
|
private slotSnapshot: readonly RenderSceneSlot[] | undefined;
|
|
271
319
|
private revision = 0;
|
|
272
320
|
private noChangeFrames = 0;
|
|
@@ -374,6 +422,7 @@ export class RenderScene {
|
|
|
374
422
|
};
|
|
375
423
|
this.unindexMaterials(pending.initial);
|
|
376
424
|
this.slots[pending.initial.slot] = updatedSlot;
|
|
425
|
+
this.worldBoundsBySlot[updatedSlot.slot] = undefined;
|
|
377
426
|
this.indexMaterials(updatedSlot);
|
|
378
427
|
this.dirtySinceSubmitted.add(updatedSlot.slot);
|
|
379
428
|
updatedSlots.push(updatedSlot);
|
|
@@ -399,19 +448,55 @@ export class RenderScene {
|
|
|
399
448
|
};
|
|
400
449
|
}
|
|
401
450
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
451
|
+
/** Apply version-selected GlobalTransform columns without allocating per-entity operations. */
|
|
452
|
+
applyTransformSpans(spans: readonly WorldTransformSpan[]): RenderSceneApplyResult {
|
|
453
|
+
this.deltaFrames += 1;
|
|
454
|
+
let scanned = 0;
|
|
455
|
+
let updated = 0;
|
|
456
|
+
let ignoredLateUpdates = 0;
|
|
457
|
+
const updatedSlots = this.transformUpdatedSlots;
|
|
458
|
+
updatedSlots.length = 0;
|
|
459
|
+
for (const span of spans) {
|
|
460
|
+
scanned += span.entities.length;
|
|
461
|
+
for (let row = 0; row < span.entities.length; row += 1) {
|
|
462
|
+
const entity = span.entities[row];
|
|
463
|
+
if (entity === undefined) continue;
|
|
464
|
+
const slot = this.lookup(span.worldId, entity);
|
|
465
|
+
if (slot === undefined) {
|
|
466
|
+
ignoredLateUpdates += 1;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
const target = slot.snapshot.transform.world;
|
|
470
|
+
const sourceStart = row * 16;
|
|
471
|
+
for (let index = 0; index < 16; index += 1) {
|
|
472
|
+
target[index] = span.worlds[sourceStart + index] ?? target[index] ?? 0;
|
|
473
|
+
}
|
|
474
|
+
if (slot.snapshot.instances === undefined) {
|
|
475
|
+
const cached = this.worldBoundsBySlot[slot.slot];
|
|
476
|
+
const bounds = worldBounds(slot.snapshot, cached ?? undefined);
|
|
477
|
+
this.worldBoundsBySlot[slot.slot] = bounds ?? null;
|
|
478
|
+
} else {
|
|
479
|
+
this.worldBoundsBySlot[slot.slot] = undefined;
|
|
480
|
+
}
|
|
481
|
+
if (this.temporalTracking) this.dirtySinceSubmitted.add(slot.slot);
|
|
482
|
+
updatedSlots.push(slot);
|
|
483
|
+
updated += 1;
|
|
484
|
+
}
|
|
413
485
|
}
|
|
414
|
-
|
|
486
|
+
this.renderableScans = scanned;
|
|
487
|
+
if (updated > 0) this.revision += 1;
|
|
488
|
+
return {
|
|
489
|
+
created: 0,
|
|
490
|
+
updated,
|
|
491
|
+
removed: 0,
|
|
492
|
+
recreated: 0,
|
|
493
|
+
ignoredLateUpdates,
|
|
494
|
+
createdSlots: [],
|
|
495
|
+
updatedSlots,
|
|
496
|
+
removedSlots: [],
|
|
497
|
+
recreatedSlots: [],
|
|
498
|
+
resynced: 0,
|
|
499
|
+
};
|
|
415
500
|
}
|
|
416
501
|
|
|
417
502
|
/** Reconcile the current identity set while retaining slots for survivors. */
|
|
@@ -446,6 +531,7 @@ export class RenderScene {
|
|
|
446
531
|
};
|
|
447
532
|
this.unindexMaterials(existing);
|
|
448
533
|
this.slots[existing.slot] = updated;
|
|
534
|
+
this.worldBoundsBySlot[updated.slot] = undefined;
|
|
449
535
|
this.indexMaterials(updated);
|
|
450
536
|
this.dirtySinceSubmitted.add(updated.slot);
|
|
451
537
|
this.orderedSlots.push(existing.slot);
|
|
@@ -460,14 +546,25 @@ export class RenderScene {
|
|
|
460
546
|
this.reset(snapshots);
|
|
461
547
|
}
|
|
462
548
|
|
|
463
|
-
|
|
549
|
+
/** Enable previous-frame publication only while a temporal feature consumes it. */
|
|
550
|
+
setTemporalTracking(enabled: boolean): void {
|
|
551
|
+
if (enabled === this.temporalTracking) return;
|
|
552
|
+
this.temporalTracking = enabled;
|
|
553
|
+
if (!enabled) {
|
|
554
|
+
this.dirtySinceSubmitted.clear();
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
for (const slot of this.orderedSlots) this.dirtySinceSubmitted.add(slot);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
materialize(): RenderableSnapshot[] {
|
|
464
561
|
if (this.materialized !== undefined) return this.materialized;
|
|
465
562
|
const snapshots: RenderableSnapshot[] = [];
|
|
466
563
|
for (const slot of this.orderedSlots) {
|
|
467
564
|
const record = this.slots[slot];
|
|
468
565
|
if (record !== undefined) snapshots.push(record.snapshot);
|
|
469
566
|
}
|
|
470
|
-
this.materialized =
|
|
567
|
+
this.materialized = snapshots;
|
|
471
568
|
return this.materialized;
|
|
472
569
|
}
|
|
473
570
|
|
|
@@ -513,6 +610,15 @@ export class RenderScene {
|
|
|
513
610
|
}
|
|
514
611
|
for (const update of capture.slots) {
|
|
515
612
|
this.lastSubmittedSlots[update.slot] = update.current;
|
|
613
|
+
const currentWorld = update.current?.snapshot.transform.world;
|
|
614
|
+
if (currentWorld !== undefined) {
|
|
615
|
+
let previousWorld = this.previousWorldBySlot[update.slot];
|
|
616
|
+
if (previousWorld === undefined) {
|
|
617
|
+
previousWorld = new Float32Array(16);
|
|
618
|
+
this.previousWorldBySlot[update.slot] = previousWorld;
|
|
619
|
+
}
|
|
620
|
+
previousWorld.set(currentWorld);
|
|
621
|
+
}
|
|
516
622
|
this.dirtySinceSubmitted.delete(update.slot);
|
|
517
623
|
}
|
|
518
624
|
this.lastSubmittedVisible = new Set(capture.visible);
|
|
@@ -575,8 +681,7 @@ export class RenderScene {
|
|
|
575
681
|
for (const slot of this.orderedSlots) {
|
|
576
682
|
const record = this.slots[slot];
|
|
577
683
|
if (record === undefined) continue;
|
|
578
|
-
const
|
|
579
|
-
const candidate = this.cullingWorldBounds(snapshot);
|
|
684
|
+
const candidate = this.cullingWorldBoundsAt(record);
|
|
580
685
|
if (candidate === null || candidate === undefined) continue;
|
|
581
686
|
if (intersects(candidate, bounds)) records.push(record);
|
|
582
687
|
}
|
|
@@ -634,8 +739,15 @@ export class RenderScene {
|
|
|
634
739
|
entityKey: snapshot.entityKey,
|
|
635
740
|
snapshot: ownSnapshot(snapshot),
|
|
636
741
|
};
|
|
742
|
+
let previousWorld = this.previousWorldBySlot[slot];
|
|
743
|
+
if (previousWorld === undefined) {
|
|
744
|
+
previousWorld = new Float32Array(16);
|
|
745
|
+
this.previousWorldBySlot[slot] = previousWorld;
|
|
746
|
+
}
|
|
747
|
+
previousWorld.set(record.snapshot.transform.world);
|
|
637
748
|
this.generations[slot] = generation;
|
|
638
749
|
this.slots[slot] = record;
|
|
750
|
+
this.worldBoundsBySlot[slot] = undefined;
|
|
639
751
|
let entities = this.slotsByWorld.get(snapshot.worldId);
|
|
640
752
|
if (entities === undefined) {
|
|
641
753
|
entities = new Map<number, number>();
|
|
@@ -649,6 +761,7 @@ export class RenderScene {
|
|
|
649
761
|
|
|
650
762
|
private release(record: RenderSceneSlot): void {
|
|
651
763
|
this.slots[record.slot] = undefined;
|
|
764
|
+
this.worldBoundsBySlot[record.slot] = undefined;
|
|
652
765
|
this.instanceBoundsCache.invalidate(record.entityKey, record.worldId);
|
|
653
766
|
this.unindexMaterials(record);
|
|
654
767
|
const entities = this.slotsByWorld.get(record.worldId);
|
|
@@ -711,12 +824,16 @@ export class RenderScene {
|
|
|
711
824
|
reasons.includes('geometry-revision') ||
|
|
712
825
|
reasons.includes('material-revision');
|
|
713
826
|
const previous = seedCurrent || submitted === undefined ? slot.snapshot : submitted.snapshot;
|
|
827
|
+
const previousWorld = this.previousWorldBySlot[slot.slot];
|
|
714
828
|
return {
|
|
715
829
|
previousEpoch: seedCurrent ? undefined : this.submittedEpoch,
|
|
716
830
|
previousSource: seedCurrent ? 'current-seed' : 'last-submitted',
|
|
717
831
|
reactive: reasons.length > 0,
|
|
718
832
|
reactiveReasons: reasons,
|
|
719
|
-
previousTransform:
|
|
833
|
+
previousTransform:
|
|
834
|
+
seedCurrent || previousWorld === undefined
|
|
835
|
+
? previous.transform
|
|
836
|
+
: { ...previous.transform, world: previousWorld },
|
|
720
837
|
previousInstances: previous.instances,
|
|
721
838
|
previousSkin: previous.skin,
|
|
722
839
|
previousMorphWeights: previous.morph?.weights,
|
|
@@ -734,15 +851,14 @@ export class RenderScene {
|
|
|
734
851
|
* bounds for its independent per-instance visibility pass. Unknown or
|
|
735
852
|
* empty instance facts deliberately return undefined (conservative no-cull).
|
|
736
853
|
*/
|
|
737
|
-
|
|
738
|
-
let candidate = this.
|
|
739
|
-
if (candidate !== undefined
|
|
740
|
-
|
|
741
|
-
}
|
|
854
|
+
cullingWorldBoundsAt(slot: RenderSceneSlot): RenderSceneBounds | undefined {
|
|
855
|
+
let candidate = this.worldBoundsBySlot[slot.slot];
|
|
856
|
+
if (candidate !== undefined) return candidate ?? undefined;
|
|
857
|
+
const snapshot = slot.snapshot;
|
|
742
858
|
if (snapshot.instances !== undefined) {
|
|
743
859
|
const instanceCount = snapshot.instances.instanceCount;
|
|
744
860
|
if (instanceCount === 0 || snapshot.localAabb === undefined) {
|
|
745
|
-
this.
|
|
861
|
+
this.worldBoundsBySlot[slot.slot] = null;
|
|
746
862
|
return undefined;
|
|
747
863
|
}
|
|
748
864
|
const derived = this.instanceBoundsCache.get({
|
|
@@ -757,28 +873,92 @@ export class RenderScene {
|
|
|
757
873
|
});
|
|
758
874
|
candidate = boundsFromArray(derived ?? []);
|
|
759
875
|
} else {
|
|
760
|
-
candidate = worldBounds(snapshot);
|
|
876
|
+
candidate = worldBounds(snapshot, candidate ?? undefined);
|
|
761
877
|
}
|
|
762
|
-
this.
|
|
878
|
+
this.worldBoundsBySlot[slot.slot] = candidate ?? null;
|
|
763
879
|
return candidate;
|
|
764
880
|
}
|
|
765
|
-
}
|
|
766
881
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
882
|
+
cullingWorldBounds(snapshot: RenderableSnapshot): RenderSceneBounds | undefined {
|
|
883
|
+
const slot = this.lookup(snapshot.worldId, snapshot.entityKey);
|
|
884
|
+
return slot === undefined ? worldBounds(snapshot) : this.cullingWorldBoundsAt(slot);
|
|
885
|
+
}
|
|
770
886
|
}
|
|
771
887
|
|
|
772
888
|
export interface PersistentRenderSceneOptions {
|
|
773
889
|
readonly getDevice?: (() => RhiDevice) | undefined;
|
|
774
890
|
readonly onGpuError?: ((error: RhiError) => void) | undefined;
|
|
775
891
|
readonly onSharedRefMutation?: ((worldId: number, handle: number) => void) | undefined;
|
|
892
|
+
readonly configuredQueryBudget?: number | undefined;
|
|
776
893
|
}
|
|
777
894
|
|
|
778
895
|
export interface PersistentGpuDrivenState {
|
|
779
896
|
readonly scene: GpuScene;
|
|
780
897
|
readonly plan: SubmissionPlan;
|
|
781
898
|
readonly slots: readonly RenderSceneSlot[];
|
|
899
|
+
/** Stable renderer-local World keys aligned with RenderSceneSlot.worldId. */
|
|
900
|
+
readonly worldKeys?: readonly number[];
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
export interface CpuDirectViewInput {
|
|
904
|
+
readonly view: ViewKey;
|
|
905
|
+
readonly primitives: readonly PrimitiveKey[];
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
export interface CpuDirectCandidate {
|
|
909
|
+
readonly view: ViewKey;
|
|
910
|
+
readonly primitive: PrimitiveKey;
|
|
911
|
+
readonly candidate: VisibilityCandidate;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* The one renderer-owned submission projection for a display view.
|
|
916
|
+
*
|
|
917
|
+
* Occlusion completion updates the facet asynchronously, so extraction must
|
|
918
|
+
* retain every query candidate while record/prepare consume this projection.
|
|
919
|
+
* Keeping the dispatch reindex here prevents CPU direct and GPU indirect
|
|
920
|
+
* lanes from rebuilding a second visibility map with different identity
|
|
921
|
+
* semantics.
|
|
922
|
+
*/
|
|
923
|
+
export interface PersistentVisibilityProjection {
|
|
924
|
+
readonly renderables: readonly RenderableSnapshot[];
|
|
925
|
+
readonly dispatch: readonly DispatchEntry[];
|
|
926
|
+
readonly activeEntityKeys: ReadonlySet<number>;
|
|
927
|
+
/** Monotonic owner revision for exactly this active-key projection. */
|
|
928
|
+
readonly activeEntityRevision: number;
|
|
929
|
+
readonly suppressed: number;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/** Project one selected candidate per view and primitive from the facet owner. */
|
|
933
|
+
export function projectCpuDirectCandidates(
|
|
934
|
+
facets: VisibilityFacetStore,
|
|
935
|
+
views: readonly CpuDirectViewInput[],
|
|
936
|
+
): readonly CpuDirectCandidate[] {
|
|
937
|
+
const candidates: CpuDirectCandidate[] = [];
|
|
938
|
+
for (const { view, primitives } of views) {
|
|
939
|
+
for (const primitive of primitives) {
|
|
940
|
+
const candidate = facets.getCandidate(view, primitive);
|
|
941
|
+
if (candidate !== undefined) candidates.push({ view, primitive, candidate });
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return candidates;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/** Detached GPU scene prepared from the retained CPU composition. */
|
|
948
|
+
export interface PersistentGpuDrivenCandidate {
|
|
949
|
+
readonly state: PersistentGpuDrivenState | undefined;
|
|
950
|
+
/** Candidate-owned scene root; never references the active GPU scene. */
|
|
951
|
+
createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown>;
|
|
952
|
+
publish(): void;
|
|
953
|
+
discard(): void;
|
|
954
|
+
/** Release the candidate scene even after it has crossed publication. */
|
|
955
|
+
release(): void;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
export interface RenderSceneRecoveryRoot {
|
|
959
|
+
readonly generation: number;
|
|
960
|
+
readonly revision: number;
|
|
961
|
+
readonly visibleSlots: number;
|
|
782
962
|
}
|
|
783
963
|
|
|
784
964
|
type PersistentGpuSceneInspection = PersistentRenderSceneInspection['gpu'];
|
|
@@ -791,16 +971,29 @@ interface PersistentCompositionEntry {
|
|
|
791
971
|
readonly projection: RenderScene;
|
|
792
972
|
readonly topology: BatchTopology;
|
|
793
973
|
readonly transmissionDemand: PersistentTransmissionDemandProjection;
|
|
794
|
-
readonly
|
|
974
|
+
readonly structure: Pick<
|
|
975
|
+
ExtractedFrame,
|
|
976
|
+
| 'dispatch'
|
|
977
|
+
| 'visibilityStats'
|
|
978
|
+
| 'visibilitySnapshots'
|
|
979
|
+
| 'featureVisibilitySnapshots'
|
|
980
|
+
| 'hiddenEntityReports'
|
|
981
|
+
>;
|
|
982
|
+
/** Stable renderer-local World keys; unlike RenderableSnapshot.worldId they survive reorder. */
|
|
983
|
+
readonly worldKeys: readonly number[];
|
|
984
|
+
readonly readVersions: RenderReadVersion[];
|
|
795
985
|
readonly leaseIdentities: readonly string[];
|
|
986
|
+
readonly transformQueries: readonly GlobalTransformChangeQuery[];
|
|
796
987
|
catalogEpoch: number;
|
|
797
988
|
readonly rigidOnly: boolean;
|
|
798
|
-
|
|
799
|
-
|
|
989
|
+
readonly probeProjection: ProbeBlendSceneProjection;
|
|
990
|
+
temporalDemanded: boolean;
|
|
991
|
+
gpuDrivenSceneRequired: boolean;
|
|
800
992
|
}
|
|
801
993
|
|
|
802
994
|
const RENDER_RELEVANT_COMPONENT_IDS = new Set([
|
|
803
995
|
componentId(Transform),
|
|
996
|
+
componentId(GlobalTransform),
|
|
804
997
|
componentId(ChildOf),
|
|
805
998
|
componentId(MeshFilter),
|
|
806
999
|
componentId(MeshRenderer),
|
|
@@ -811,7 +1004,9 @@ const RENDER_RELEVANT_COMPONENT_IDS = new Set([
|
|
|
811
1004
|
componentId(Layer),
|
|
812
1005
|
componentId(Visibility),
|
|
813
1006
|
componentId(Camera),
|
|
1007
|
+
componentId(MotionBlur),
|
|
814
1008
|
componentId(DirectionalLight),
|
|
1009
|
+
componentId(LightProbe),
|
|
815
1010
|
componentId(PointLight),
|
|
816
1011
|
componentId(PointLightShadow),
|
|
817
1012
|
componentId(SpotLight),
|
|
@@ -822,12 +1017,20 @@ const RENDER_RELEVANT_COMPONENT_IDS = new Set([
|
|
|
822
1017
|
componentId(VolumetricFog),
|
|
823
1018
|
]);
|
|
824
1019
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1020
|
+
function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQuery {
|
|
1021
|
+
const result = world.query({ read: [GlobalTransform], changed: [GlobalTransform] });
|
|
1022
|
+
if (!result.ok) throw result.error;
|
|
1023
|
+
const query = result.value as GlobalTransformChangeQuery;
|
|
1024
|
+
for (const _span of query.spans().unwrap()) {
|
|
1025
|
+
// The full rebuild consumed these values; establish the observation baseline.
|
|
1026
|
+
}
|
|
1027
|
+
return query;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function frameDemandsTemporal(frame: ExtractedFrame): boolean {
|
|
1031
|
+
const camera = frame.cameras[0];
|
|
1032
|
+
return camera?.antialias === 'taa' || (camera?.motionBlur?.shutterAngle ?? 0) > 0;
|
|
1033
|
+
}
|
|
831
1034
|
|
|
832
1035
|
function cameraFrusta(cameras: readonly CameraSnapshot[]): readonly Float32Array[] {
|
|
833
1036
|
const planes: Float32Array[] = [];
|
|
@@ -845,8 +1048,8 @@ function cameraFrusta(cameras: readonly CameraSnapshot[]): readonly Float32Array
|
|
|
845
1048
|
projection,
|
|
846
1049
|
camera.orthoLeft,
|
|
847
1050
|
camera.orthoRight,
|
|
848
|
-
camera.orthoBottom,
|
|
849
1051
|
camera.orthoTop,
|
|
1052
|
+
camera.orthoBottom,
|
|
850
1053
|
camera.near,
|
|
851
1054
|
camera.far,
|
|
852
1055
|
);
|
|
@@ -864,15 +1067,23 @@ function cameraFrusta(cameras: readonly CameraSnapshot[]): readonly Float32Array
|
|
|
864
1067
|
return planes;
|
|
865
1068
|
}
|
|
866
1069
|
|
|
1070
|
+
let cullProjectedIndexScratch = new Int32Array(0);
|
|
1071
|
+
|
|
867
1072
|
function cullPersistentFrame(
|
|
868
1073
|
frame: ExtractedFrame,
|
|
869
1074
|
candidates: readonly RenderableSnapshot[],
|
|
870
|
-
boundsOf: (snapshot: RenderableSnapshot) => RenderSceneBounds | undefined =
|
|
1075
|
+
boundsOf: (snapshot: RenderableSnapshot, index: number) => RenderSceneBounds | undefined = (
|
|
1076
|
+
snapshot,
|
|
1077
|
+
) => worldBounds(snapshot),
|
|
871
1078
|
): ExtractedFrame {
|
|
872
1079
|
const planes = cameraFrusta(frame.cameras);
|
|
873
1080
|
const visible: RenderableSnapshot[] = [];
|
|
874
|
-
|
|
875
|
-
|
|
1081
|
+
if (cullProjectedIndexScratch.length < candidates.length) {
|
|
1082
|
+
cullProjectedIndexScratch = new Int32Array(candidates.length);
|
|
1083
|
+
}
|
|
1084
|
+
const projectedIndex = cullProjectedIndexScratch;
|
|
1085
|
+
projectedIndex.fill(-1, 0, candidates.length);
|
|
1086
|
+
const worldAabb = box3.create();
|
|
876
1087
|
let total = 0;
|
|
877
1088
|
let culled = 0;
|
|
878
1089
|
|
|
@@ -880,10 +1091,9 @@ function cullPersistentFrame(
|
|
|
880
1091
|
const candidate = candidates[index];
|
|
881
1092
|
if (candidate === undefined) continue;
|
|
882
1093
|
let isVisible = true;
|
|
883
|
-
const candidateBounds = boundsOf(candidate);
|
|
1094
|
+
const candidateBounds = boundsOf(candidate, index);
|
|
884
1095
|
if (candidateBounds !== undefined) {
|
|
885
1096
|
total += 1;
|
|
886
|
-
const worldAabb = box3.create();
|
|
887
1097
|
worldAabb[0] = candidateBounds.min[0] ?? 0;
|
|
888
1098
|
worldAabb[1] = candidateBounds.min[1] ?? 0;
|
|
889
1099
|
worldAabb[2] = candidateBounds.min[2] ?? 0;
|
|
@@ -906,7 +1116,12 @@ function cullPersistentFrame(
|
|
|
906
1116
|
continue;
|
|
907
1117
|
}
|
|
908
1118
|
projectedIndex[index] = visible.length;
|
|
909
|
-
|
|
1119
|
+
// The culling candidate is the persistent projection snapshot used for
|
|
1120
|
+
// bounds, while `frame.renderables[index]` may carry frame-owned
|
|
1121
|
+
// attachments (for example the probe blend record). Preserve that
|
|
1122
|
+
// attached renderable in the output; pushing the bare candidate here
|
|
1123
|
+
// silently drops the object-level Probe ABI before record selection.
|
|
1124
|
+
visible.push(frame.renderables[index] ?? candidate);
|
|
910
1125
|
}
|
|
911
1126
|
|
|
912
1127
|
const dispatch: DispatchEntry[] = [];
|
|
@@ -924,8 +1139,33 @@ function cullPersistentFrame(
|
|
|
924
1139
|
};
|
|
925
1140
|
}
|
|
926
1141
|
|
|
1142
|
+
function attachProbeRecords(
|
|
1143
|
+
frame: ExtractedFrame,
|
|
1144
|
+
candidates: RenderableSnapshot[],
|
|
1145
|
+
slots: readonly RenderSceneSlot[],
|
|
1146
|
+
projection: ProbeBlendSceneProjection,
|
|
1147
|
+
): ExtractedFrame {
|
|
1148
|
+
if (!projection.hasRecords()) {
|
|
1149
|
+
return frame.renderables === candidates ? frame : { ...frame, renderables: candidates };
|
|
1150
|
+
}
|
|
1151
|
+
const objectKeys = new Map<string, number>();
|
|
1152
|
+
for (const slot of slots)
|
|
1153
|
+
objectKeys.set(identityKey(slot.worldId, slot.snapshot.entityKey), slot.slot);
|
|
1154
|
+
const renderables = candidates.map((candidate) => {
|
|
1155
|
+
const slot = objectKeys.get(identityKey(candidate.worldId, candidate.entityKey));
|
|
1156
|
+
const record = slot === undefined ? undefined : projection.getRecord(slot);
|
|
1157
|
+
return record === undefined ? candidate : { ...candidate, probeBlendRecord: record };
|
|
1158
|
+
});
|
|
1159
|
+
return { ...frame, renderables };
|
|
1160
|
+
}
|
|
1161
|
+
|
|
927
1162
|
/** Persistent scene projection used by the renderer's multi-World composition path. */
|
|
928
1163
|
export class PersistentRenderScene {
|
|
1164
|
+
private readonly visibilityBudget: VisibilityBudget;
|
|
1165
|
+
private readonly visibilityFacets: VisibilityFacetStore;
|
|
1166
|
+
/** World identity is the author/runtime identity; array position is only a frame routing detail. */
|
|
1167
|
+
private readonly visibilityWorldKeys = new Map<string, number>();
|
|
1168
|
+
private nextVisibilityWorldKey = 0;
|
|
929
1169
|
private readonly materialSnapshotCaches: MaterialSnapshotCachesByWorld = new WeakMap();
|
|
930
1170
|
private fullRebuilds = 0;
|
|
931
1171
|
private worldEntitiesScanned = 0;
|
|
@@ -942,28 +1182,300 @@ export class PersistentRenderScene {
|
|
|
942
1182
|
private temporalCapture:
|
|
943
1183
|
| { readonly projection: RenderScene; readonly capture: RenderSceneSubmissionCapture }
|
|
944
1184
|
| undefined;
|
|
1185
|
+
/** Monotonic token for camera/dynamic frame facts consumed by visibility caches. */
|
|
1186
|
+
private frameEpoch = 0;
|
|
1187
|
+
private visibilityFacetFrame:
|
|
1188
|
+
| {
|
|
1189
|
+
readonly frameEpoch: number;
|
|
1190
|
+
readonly viewId: string;
|
|
1191
|
+
readonly renderables: readonly RenderableSnapshot[];
|
|
1192
|
+
}
|
|
1193
|
+
| undefined;
|
|
1194
|
+
private visibilityProjectionCache:
|
|
1195
|
+
| {
|
|
1196
|
+
readonly frameEpoch: number;
|
|
1197
|
+
readonly drawRevision: number;
|
|
1198
|
+
readonly viewId: string;
|
|
1199
|
+
readonly renderables: readonly RenderableSnapshot[];
|
|
1200
|
+
readonly dispatch: readonly DispatchEntry[];
|
|
1201
|
+
readonly projection: PersistentVisibilityProjection;
|
|
1202
|
+
}
|
|
1203
|
+
| undefined;
|
|
1204
|
+
private visibilityProjectionRevision = 0;
|
|
1205
|
+
private lastActiveEntityKeys: ReadonlySet<number> | undefined;
|
|
1206
|
+
/** Stable renderer-world/entity lookup; rebuilt only with composition topology. */
|
|
1207
|
+
private readonly stableSlotByEntity = new Map<number, RenderSceneSlot>();
|
|
945
1208
|
private pointsLinesInspections: readonly PointsLinesInspection[] = [];
|
|
946
1209
|
private readonly reflectionProbes = new ReflectionProbeProjection();
|
|
947
1210
|
|
|
948
|
-
constructor(private readonly options: PersistentRenderSceneOptions = {}) {
|
|
1211
|
+
constructor(private readonly options: PersistentRenderSceneOptions = {}) {
|
|
1212
|
+
this.visibilityBudget = createVisibilityBudget(options.configuredQueryBudget);
|
|
1213
|
+
this.visibilityFacets = new VisibilityFacetStore(this.visibilityBudget);
|
|
1214
|
+
}
|
|
949
1215
|
|
|
950
1216
|
/** RenderScene owns the per-World material fact cache used during extraction. */
|
|
951
1217
|
materialSnapshotCacheStore(): MaterialSnapshotCachesByWorld {
|
|
952
1218
|
return this.materialSnapshotCaches;
|
|
953
1219
|
}
|
|
954
1220
|
|
|
1221
|
+
/** The sole renderer-owned ViewKey x PrimitiveKey visibility state. */
|
|
1222
|
+
visibilityFacetStore(): VisibilityFacetStore {
|
|
1223
|
+
return this.visibilityFacets;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
visibilityBudgetValue(): VisibilityBudget {
|
|
1227
|
+
return this.visibilityBudget;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
private visibilityWorldKey(world: World | undefined, fallback: number): number {
|
|
1231
|
+
if (world === undefined) return fallback;
|
|
1232
|
+
const existing = this.visibilityWorldKeys.get(world.identity);
|
|
1233
|
+
if (existing !== undefined) return existing;
|
|
1234
|
+
const key = this.nextVisibilityWorldKey;
|
|
1235
|
+
this.nextVisibilityWorldKey += 1;
|
|
1236
|
+
this.visibilityWorldKeys.set(world.identity, key);
|
|
1237
|
+
return key;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
private worldKeyAt(worlds: readonly World[], worldId: number): number {
|
|
1241
|
+
return this.visibilityWorldKey(worlds[worldId], worldId);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/** Stable World routing keys for the current worlds[] frame projection. */
|
|
1245
|
+
visibilityWorldKeysFor(worlds: readonly World[]): readonly number[] {
|
|
1246
|
+
return worlds.map((world, worldId) => this.visibilityWorldKey(world, worldId));
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/** Current retained slots, available to CPU query transport as well as GPU. */
|
|
1250
|
+
compositionSlots(): readonly RenderSceneSlot[] {
|
|
1251
|
+
return this.composition?.projection.slotsSnapshot() ?? [];
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/** Stable renderer-world/entity lookup shared by query and final projection paths. */
|
|
1255
|
+
compositionSlotByStableEntity(): ReadonlyMap<number, RenderSceneSlot> {
|
|
1256
|
+
return this.stableSlotByEntity;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
private stableEntityKey(
|
|
1260
|
+
worlds: readonly World[],
|
|
1261
|
+
renderable: Pick<RenderableSnapshot, 'worldId' | 'entityKey'>,
|
|
1262
|
+
): number {
|
|
1263
|
+
return worldEntityKey(this.worldKeyAt(worlds, renderable.worldId), renderable.entityKey);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/** Feed the facet from extracted renderables using persistent slot identity. */
|
|
1267
|
+
updateVisibilityFacet(
|
|
1268
|
+
worlds: readonly World[],
|
|
1269
|
+
camera: CameraSnapshot | undefined,
|
|
1270
|
+
renderables: readonly RenderableSnapshot[],
|
|
1271
|
+
): void {
|
|
1272
|
+
const world = worlds[camera?.worldId ?? 0];
|
|
1273
|
+
if (world === undefined || camera === undefined) return;
|
|
1274
|
+
const view = viewKey({
|
|
1275
|
+
attachmentId: world.identity,
|
|
1276
|
+
cameraEntity: camera.entityKey ?? 0,
|
|
1277
|
+
viewRole: 'main',
|
|
1278
|
+
viewGeneration: camera.historyVersion ?? 0,
|
|
1279
|
+
});
|
|
1280
|
+
const viewId = viewKeyId(view);
|
|
1281
|
+
const previous = this.visibilityFacetFrame;
|
|
1282
|
+
if (
|
|
1283
|
+
previous?.frameEpoch === this.frameEpoch &&
|
|
1284
|
+
previous.viewId === viewId &&
|
|
1285
|
+
previous.renderables === renderables
|
|
1286
|
+
) {
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
this.visibilityFacets.activateView(view);
|
|
1290
|
+
const activePrimitiveIds = new Set<string>();
|
|
1291
|
+
for (const renderable of renderables) {
|
|
1292
|
+
const bounds = renderable.localAabb;
|
|
1293
|
+
if (
|
|
1294
|
+
renderable.lods === undefined ||
|
|
1295
|
+
renderable.lods.length === 0 ||
|
|
1296
|
+
bounds === undefined ||
|
|
1297
|
+
bounds.length < 6 ||
|
|
1298
|
+
[...bounds].some((value) => !Number.isFinite(value))
|
|
1299
|
+
) {
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
const slot = this.stableSlotByEntity.get(this.stableEntityKey(worlds, renderable));
|
|
1303
|
+
if (slot === undefined) continue;
|
|
1304
|
+
const primitive = primitiveKey({
|
|
1305
|
+
attachmentId: world.identity,
|
|
1306
|
+
worldGeneration: this.worldKeyAt(worlds, renderable.worldId),
|
|
1307
|
+
primitiveSlot: slot.slot,
|
|
1308
|
+
slotGeneration: slot.generation,
|
|
1309
|
+
});
|
|
1310
|
+
activePrimitiveIds.add(primitiveKeyId(primitive));
|
|
1311
|
+
this.visibilityFacets.setCandidate(view, primitive, { level: 0, confidence: 1 });
|
|
1312
|
+
}
|
|
1313
|
+
this.visibilityFacets.pruneCandidates(view, activePrimitiveIds);
|
|
1314
|
+
this.visibilityFacetFrame = { frameEpoch: this.frameEpoch, viewId, renderables };
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* Project the current facet into the final primary-raster submission.
|
|
1319
|
+
*
|
|
1320
|
+
* Only renderables carrying authored lower-detail levels are queryable. A
|
|
1321
|
+
* plain mesh therefore remains on the ordinary visibility path, while an
|
|
1322
|
+
* LOD candidate with two accepted zero-sample results is suppressed until
|
|
1323
|
+
* the confidence scheduler requests its bounded re-test. Unknown identity,
|
|
1324
|
+
* invalid bounds, and non-queryable confidence all remain conservatively
|
|
1325
|
+
* visible through `decideVisibility`.
|
|
1326
|
+
*/
|
|
1327
|
+
projectVisibility(
|
|
1328
|
+
worlds: readonly World[],
|
|
1329
|
+
camera: CameraSnapshot | undefined,
|
|
1330
|
+
renderables: readonly RenderableSnapshot[],
|
|
1331
|
+
dispatch: readonly DispatchEntry[],
|
|
1332
|
+
): PersistentVisibilityProjection {
|
|
1333
|
+
if (camera === undefined) {
|
|
1334
|
+
const activeEntityKeys = new Set(
|
|
1335
|
+
renderables.map((renderable) => this.stableEntityKey(worlds, renderable)),
|
|
1336
|
+
);
|
|
1337
|
+
return {
|
|
1338
|
+
renderables,
|
|
1339
|
+
dispatch,
|
|
1340
|
+
activeEntityKeys,
|
|
1341
|
+
activeEntityRevision: this.activeEntityRevision(activeEntityKeys),
|
|
1342
|
+
suppressed: 0,
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
const cameraWorld = worlds[camera.worldId ?? 0];
|
|
1346
|
+
if (cameraWorld === undefined) {
|
|
1347
|
+
const activeEntityKeys = new Set(
|
|
1348
|
+
renderables.map((renderable) => this.stableEntityKey(worlds, renderable)),
|
|
1349
|
+
);
|
|
1350
|
+
return {
|
|
1351
|
+
renderables,
|
|
1352
|
+
dispatch,
|
|
1353
|
+
activeEntityKeys,
|
|
1354
|
+
activeEntityRevision: this.activeEntityRevision(activeEntityKeys),
|
|
1355
|
+
suppressed: 0,
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
const view = viewKey({
|
|
1359
|
+
attachmentId: cameraWorld.identity,
|
|
1360
|
+
cameraEntity: camera.entityKey ?? 0,
|
|
1361
|
+
viewRole: 'main',
|
|
1362
|
+
viewGeneration: camera.historyVersion ?? 0,
|
|
1363
|
+
});
|
|
1364
|
+
const viewId = viewKeyId(view);
|
|
1365
|
+
const cached = this.visibilityProjectionCache;
|
|
1366
|
+
if (
|
|
1367
|
+
cached?.frameEpoch === this.frameEpoch &&
|
|
1368
|
+
cached.drawRevision === this.visibilityFacets.drawRevisionValue &&
|
|
1369
|
+
cached.viewId === viewId &&
|
|
1370
|
+
cached.renderables === renderables &&
|
|
1371
|
+
cached.dispatch === dispatch
|
|
1372
|
+
) {
|
|
1373
|
+
return cached.projection;
|
|
1374
|
+
}
|
|
1375
|
+
const projectedIndex = new Int32Array(renderables.length);
|
|
1376
|
+
projectedIndex.fill(-1);
|
|
1377
|
+
const projected: RenderableSnapshot[] = [];
|
|
1378
|
+
const activeEntityKeys = new Set<number>();
|
|
1379
|
+
let suppressed = 0;
|
|
1380
|
+
for (let index = 0; index < renderables.length; index += 1) {
|
|
1381
|
+
const renderable = renderables[index];
|
|
1382
|
+
if (renderable === undefined) continue;
|
|
1383
|
+
const slot = this.stableSlotByEntity.get(this.stableEntityKey(worlds, renderable));
|
|
1384
|
+
let draw = true;
|
|
1385
|
+
if (slot !== undefined && (renderable.lods?.length ?? 0) > 0) {
|
|
1386
|
+
const primitive = primitiveKey({
|
|
1387
|
+
attachmentId: cameraWorld.identity,
|
|
1388
|
+
worldGeneration: this.worldKeyAt(worlds, renderable.worldId),
|
|
1389
|
+
primitiveSlot: slot.slot,
|
|
1390
|
+
slotGeneration: slot.generation,
|
|
1391
|
+
});
|
|
1392
|
+
const bounds = renderable.localAabb;
|
|
1393
|
+
const validBounds =
|
|
1394
|
+
bounds !== undefined &&
|
|
1395
|
+
bounds.length >= 6 &&
|
|
1396
|
+
[...bounds].every((value) => Number.isFinite(value));
|
|
1397
|
+
draw = decideVisibility({
|
|
1398
|
+
authorVisible: true,
|
|
1399
|
+
validBounds,
|
|
1400
|
+
frustumVisible: true,
|
|
1401
|
+
lodReady: true,
|
|
1402
|
+
occlusion: this.visibilityFacets.getConfidence(view, primitive),
|
|
1403
|
+
lane: 'gpu',
|
|
1404
|
+
}).draw;
|
|
1405
|
+
}
|
|
1406
|
+
if (!draw) {
|
|
1407
|
+
suppressed += 1;
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
projectedIndex[index] = projected.length;
|
|
1411
|
+
projected.push(renderable);
|
|
1412
|
+
activeEntityKeys.add(this.stableEntityKey(worlds, renderable));
|
|
1413
|
+
}
|
|
1414
|
+
const projectedDispatch: DispatchEntry[] = [];
|
|
1415
|
+
for (const entry of dispatch) {
|
|
1416
|
+
const renderableIndex = projectedIndex[entry.renderableIndex];
|
|
1417
|
+
if (renderableIndex === undefined || renderableIndex < 0) continue;
|
|
1418
|
+
projectedDispatch.push({ ...entry, renderableIndex });
|
|
1419
|
+
}
|
|
1420
|
+
const projection = {
|
|
1421
|
+
renderables: Object.freeze(projected),
|
|
1422
|
+
dispatch: Object.freeze(projectedDispatch),
|
|
1423
|
+
activeEntityKeys,
|
|
1424
|
+
activeEntityRevision: this.activeEntityRevision(activeEntityKeys),
|
|
1425
|
+
suppressed,
|
|
1426
|
+
};
|
|
1427
|
+
this.visibilityProjectionCache = {
|
|
1428
|
+
frameEpoch: this.frameEpoch,
|
|
1429
|
+
drawRevision: this.visibilityFacets.drawRevisionValue,
|
|
1430
|
+
viewId,
|
|
1431
|
+
renderables,
|
|
1432
|
+
dispatch,
|
|
1433
|
+
projection,
|
|
1434
|
+
};
|
|
1435
|
+
return projection;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/**
|
|
1439
|
+
* Advance the projection revision only when the active stable-entity set
|
|
1440
|
+
* changes. Camera fingerprints and scene-plan identity cover view/topology
|
|
1441
|
+
* changes; keeping this token set-stable preserves the GPU filtered-plan
|
|
1442
|
+
* cache on ordinary no-change frames.
|
|
1443
|
+
*/
|
|
1444
|
+
private activeEntityRevision(activeEntityKeys: ReadonlySet<number>): number {
|
|
1445
|
+
const previous = this.lastActiveEntityKeys;
|
|
1446
|
+
if (
|
|
1447
|
+
previous === undefined ||
|
|
1448
|
+
previous.size !== activeEntityKeys.size ||
|
|
1449
|
+
[...previous].some((key) => !activeEntityKeys.has(key))
|
|
1450
|
+
) {
|
|
1451
|
+
this.lastActiveEntityKeys = new Set(activeEntityKeys);
|
|
1452
|
+
this.visibilityProjectionRevision += 1;
|
|
1453
|
+
}
|
|
1454
|
+
return this.visibilityProjectionRevision;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
private invalidateActiveEntityRevision(): void {
|
|
1458
|
+
this.lastActiveEntityKeys = undefined;
|
|
1459
|
+
this.visibilityProjectionRevision += 1;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
955
1462
|
/** Project probe facts and per-primitive selection into the persistent scene owner. */
|
|
956
1463
|
projectReflectionProbes(probes: readonly ReflectionProbeFact[]): void {
|
|
957
1464
|
const projection = this.composition?.projection;
|
|
958
1465
|
if (projection === undefined) return;
|
|
1466
|
+
if (probes.length === 0) {
|
|
1467
|
+
this.reflectionProbes.update([], []);
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
959
1470
|
const primitives = projection
|
|
960
1471
|
.slotsSnapshot()
|
|
961
1472
|
.map((slot) => {
|
|
962
|
-
const resolved =
|
|
1473
|
+
const resolved = projection.cullingWorldBoundsAt(slot);
|
|
963
1474
|
if (resolved === undefined || resolved === null) return undefined;
|
|
964
1475
|
return {
|
|
965
1476
|
worldId: slot.worldId,
|
|
966
1477
|
entityKey: slot.entityKey,
|
|
1478
|
+
renderableKey: `${slot.worldId}:${slot.entityKey}`,
|
|
967
1479
|
center: [
|
|
968
1480
|
(resolved.min[0] + resolved.max[0]) / 2,
|
|
969
1481
|
(resolved.min[1] + resolved.max[1]) / 2,
|
|
@@ -987,11 +1499,32 @@ export class PersistentRenderScene {
|
|
|
987
1499
|
worlds: readonly World[],
|
|
988
1500
|
owner: { readonly cameraOwner: number; readonly resourceOwner: number },
|
|
989
1501
|
catalogEpoch: number,
|
|
990
|
-
buildCandidateFrame: () => ExtractedFrame,
|
|
1502
|
+
buildCandidateFrame: (renderables: 'full' | 'none') => ExtractedFrame,
|
|
991
1503
|
leases?: readonly RenderReadLease[],
|
|
992
1504
|
): ExtractedFrame {
|
|
993
1505
|
this.worldEntitiesScanned = 0;
|
|
994
1506
|
const entry = this.composition;
|
|
1507
|
+
const sameWorldSet =
|
|
1508
|
+
entry !== undefined &&
|
|
1509
|
+
entry.worlds.length === worlds.length &&
|
|
1510
|
+
entry.worlds.every((world) => worlds.includes(world));
|
|
1511
|
+
const worldOrderChanged =
|
|
1512
|
+
entry !== undefined &&
|
|
1513
|
+
sameWorldSet &&
|
|
1514
|
+
entry.worlds.some((world, index) => world !== worlds[index]);
|
|
1515
|
+
const leaseSetPreserved =
|
|
1516
|
+
entry !== undefined &&
|
|
1517
|
+
leases !== undefined &&
|
|
1518
|
+
leases.length === worlds.length &&
|
|
1519
|
+
entry.leaseIdentities.length === leases.length &&
|
|
1520
|
+
entry.leaseIdentities.every((identity) =>
|
|
1521
|
+
leases.some((lease) => lease.worldIdentity === identity),
|
|
1522
|
+
);
|
|
1523
|
+
const preserveVisibilityOnReorder =
|
|
1524
|
+
entry !== undefined &&
|
|
1525
|
+
worldOrderChanged &&
|
|
1526
|
+
entry.catalogEpoch === catalogEpoch &&
|
|
1527
|
+
leaseSetPreserved;
|
|
995
1528
|
if (
|
|
996
1529
|
entry === undefined ||
|
|
997
1530
|
entry.catalogEpoch !== catalogEpoch ||
|
|
@@ -1002,13 +1535,19 @@ export class PersistentRenderScene {
|
|
|
1002
1535
|
leases === undefined ||
|
|
1003
1536
|
leases.length !== worlds.length
|
|
1004
1537
|
) {
|
|
1005
|
-
return this.rebuildComposition(
|
|
1538
|
+
return this.rebuildComposition(
|
|
1539
|
+
worlds,
|
|
1540
|
+
owner,
|
|
1541
|
+
catalogEpoch,
|
|
1542
|
+
buildCandidateFrame,
|
|
1543
|
+
leases,
|
|
1544
|
+
preserveVisibilityOnReorder,
|
|
1545
|
+
);
|
|
1006
1546
|
}
|
|
1007
1547
|
if (!entry.rigidOnly) {
|
|
1008
1548
|
return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
1009
1549
|
}
|
|
1010
1550
|
|
|
1011
|
-
const operations: RenderSceneOperation[] = [];
|
|
1012
1551
|
let requiresRebuild = false;
|
|
1013
1552
|
for (let worldId = 0; worldId < worlds.length; worldId += 1) {
|
|
1014
1553
|
const world = worlds[worldId];
|
|
@@ -1018,14 +1557,19 @@ export class PersistentRenderScene {
|
|
|
1018
1557
|
requiresRebuild = true;
|
|
1019
1558
|
break;
|
|
1020
1559
|
}
|
|
1560
|
+
const version = entry.readVersions[worldId];
|
|
1561
|
+
if (version === undefined) {
|
|
1562
|
+
requiresRebuild = true;
|
|
1563
|
+
break;
|
|
1564
|
+
}
|
|
1021
1565
|
let read: ReturnType<RenderReadLease['readChanges']>;
|
|
1022
1566
|
try {
|
|
1023
|
-
read = lease.readChanges(
|
|
1567
|
+
read = lease.readChanges(version);
|
|
1024
1568
|
} catch {
|
|
1025
1569
|
requiresRebuild = true;
|
|
1026
1570
|
break;
|
|
1027
1571
|
}
|
|
1028
|
-
if (read.status
|
|
1572
|
+
if (read.status !== 'ok') {
|
|
1029
1573
|
requiresRebuild = true;
|
|
1030
1574
|
break;
|
|
1031
1575
|
}
|
|
@@ -1038,119 +1582,86 @@ export class PersistentRenderScene {
|
|
|
1038
1582
|
requiresRebuild = true;
|
|
1039
1583
|
break;
|
|
1040
1584
|
}
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1585
|
+
for (const changedComponentId of changes.changedComponentIds) {
|
|
1586
|
+
if (!RENDER_RELEVANT_COMPONENT_IDS.has(changedComponentId)) continue;
|
|
1043
1587
|
if (
|
|
1044
|
-
|
|
1045
|
-
|
|
1588
|
+
changedComponentId !== componentId(Transform) &&
|
|
1589
|
+
changedComponentId !== componentId(GlobalTransform)
|
|
1046
1590
|
) {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
}
|
|
1050
|
-
for (const record of changes.records) {
|
|
1051
|
-
if (
|
|
1052
|
-
record.kind !== 'entity-removed' &&
|
|
1053
|
-
record.componentId !== undefined &&
|
|
1054
|
-
!RENDER_RELEVANT_COMPONENT_IDS.has(record.componentId)
|
|
1055
|
-
) {
|
|
1056
|
-
continue;
|
|
1057
|
-
}
|
|
1058
|
-
const authoredTransform =
|
|
1059
|
-
record.kind === 'component-changed' && record.componentId === componentId(Transform);
|
|
1060
|
-
const derivedTransform =
|
|
1061
|
-
record.kind === 'derived-component-changed' &&
|
|
1062
|
-
record.componentId === componentId(Transform);
|
|
1063
|
-
if (derivedTransform) continue;
|
|
1064
|
-
if (authoredTransform && derivedTransforms.has(record.entity as EntityHandle)) continue;
|
|
1065
|
-
if (
|
|
1066
|
-
(authoredTransform || record.componentId === componentId(Transform)) &&
|
|
1067
|
-
entry.projection.has(worldId, record.entity)
|
|
1068
|
-
) {
|
|
1069
|
-
const worldView = readTransformWorld(world, record.entity as EntityHandle);
|
|
1070
|
-
if (worldView === undefined) {
|
|
1071
|
-
requiresRebuild = true;
|
|
1072
|
-
break;
|
|
1073
|
-
}
|
|
1074
|
-
operations.push({
|
|
1075
|
-
kind: 'update-transform',
|
|
1076
|
-
worldId,
|
|
1077
|
-
entityKey: record.entity,
|
|
1078
|
-
world: new Float32Array(worldView),
|
|
1079
|
-
});
|
|
1080
|
-
continue;
|
|
1591
|
+
requiresRebuild = true;
|
|
1592
|
+
break;
|
|
1081
1593
|
}
|
|
1082
|
-
requiresRebuild = true;
|
|
1083
|
-
break;
|
|
1084
1594
|
}
|
|
1085
|
-
if (
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
}
|
|
1101
|
-
// Camera snapshots are rebuilt from the current candidate frame and
|
|
1102
|
-
// drive culling there; moving a camera must not invalidate the
|
|
1103
|
-
// retained scene projection or recreate its GPU tables.
|
|
1104
|
-
if (
|
|
1105
|
-
!world.hasComponent(entity, Camera) &&
|
|
1106
|
-
NON_RENDERABLE_TRANSFORM_CONSUMERS.some((component) =>
|
|
1107
|
-
world.hasComponent(entity, component),
|
|
1108
|
-
)
|
|
1109
|
-
) {
|
|
1110
|
-
requiresRebuild = true;
|
|
1111
|
-
break;
|
|
1112
|
-
}
|
|
1595
|
+
if (requiresRebuild) break;
|
|
1596
|
+
entry.readVersions[worldId] = read.version;
|
|
1597
|
+
}
|
|
1598
|
+
if (requiresRebuild) {
|
|
1599
|
+
return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
1600
|
+
}
|
|
1601
|
+
const transformSpans: WorldTransformSpan[] = [];
|
|
1602
|
+
for (let worldId = 0; worldId < entry.transformQueries.length; worldId += 1) {
|
|
1603
|
+
const query = entry.transformQueries[worldId];
|
|
1604
|
+
if (query === undefined) continue;
|
|
1605
|
+
for (const span of query.spans().unwrap()) {
|
|
1606
|
+
const changedWorlds = span.get(GlobalTransform).world;
|
|
1607
|
+
if (!(changedWorlds instanceof Float32Array)) {
|
|
1608
|
+
requiresRebuild = true;
|
|
1609
|
+
break;
|
|
1113
1610
|
}
|
|
1611
|
+
transformSpans.push({ worldId, entities: span.entities, worlds: changedWorlds });
|
|
1114
1612
|
}
|
|
1115
1613
|
if (requiresRebuild) break;
|
|
1116
|
-
entry.readCursors[worldId] = read.cursor;
|
|
1117
1614
|
}
|
|
1118
1615
|
if (requiresRebuild) {
|
|
1119
1616
|
return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
1120
1617
|
}
|
|
1121
|
-
if (
|
|
1618
|
+
if (transformSpans.length === 0) {
|
|
1122
1619
|
this.noChangeFrames += 1;
|
|
1123
1620
|
this.syncGpuOwner(entry.token, entry.projection);
|
|
1124
1621
|
return this.deriveFramePlan(entry, buildCandidateFrame);
|
|
1125
1622
|
}
|
|
1126
|
-
const delta = entry.projection.
|
|
1623
|
+
const delta = entry.projection.applyTransformSpans(transformSpans);
|
|
1127
1624
|
entry.topology.apply(delta);
|
|
1128
1625
|
this.syncGpuOwner(entry.token, entry.projection, delta);
|
|
1129
1626
|
this.deltaFrames += 1;
|
|
1130
|
-
this.transformUpdates +=
|
|
1627
|
+
this.transformUpdates += delta.updated;
|
|
1131
1628
|
return this.deriveFramePlan(entry, buildCandidateFrame);
|
|
1132
1629
|
}
|
|
1133
1630
|
|
|
1134
1631
|
invalidate(): void {
|
|
1135
1632
|
this.composition = undefined;
|
|
1136
1633
|
this.temporalCapture = undefined;
|
|
1634
|
+
this.visibilityFacets.clear();
|
|
1635
|
+
this.invalidateActiveEntityRevision();
|
|
1636
|
+
this.visibilityFacetFrame = undefined;
|
|
1637
|
+
this.visibilityProjectionCache = undefined;
|
|
1638
|
+
this.stableSlotByEntity.clear();
|
|
1137
1639
|
this.lastResyncReason = 'explicit-invalidate';
|
|
1138
1640
|
}
|
|
1139
1641
|
|
|
1140
|
-
|
|
1642
|
+
setCompositionGpuSceneDemand(sceneRowsRequired: boolean): void {
|
|
1141
1643
|
const entry = this.composition;
|
|
1142
1644
|
if (entry === undefined) return;
|
|
1143
|
-
entry.
|
|
1144
|
-
entry.gpuDrivenOwnsAll = ownsAll;
|
|
1645
|
+
entry.gpuDrivenSceneRequired = sceneRowsRequired;
|
|
1145
1646
|
}
|
|
1146
1647
|
|
|
1147
1648
|
detach(world: World): void {
|
|
1649
|
+
this.visibilityFacets.detachAttachment(world.identity);
|
|
1148
1650
|
this.materialSnapshotCaches.delete(world);
|
|
1149
1651
|
const detachedComposition = this.composition?.worlds.includes(world)
|
|
1150
1652
|
? this.composition
|
|
1151
1653
|
: undefined;
|
|
1152
1654
|
if (detachedComposition !== undefined) this.composition = undefined;
|
|
1153
1655
|
if (detachedComposition !== undefined) this.temporalCapture = undefined;
|
|
1656
|
+
if (detachedComposition !== undefined) {
|
|
1657
|
+
this.visibilityFacets.clear();
|
|
1658
|
+
this.invalidateActiveEntityRevision();
|
|
1659
|
+
}
|
|
1660
|
+
if (detachedComposition !== undefined) {
|
|
1661
|
+
this.visibilityFacetFrame = undefined;
|
|
1662
|
+
this.visibilityProjectionCache = undefined;
|
|
1663
|
+
this.stableSlotByEntity.clear();
|
|
1664
|
+
}
|
|
1154
1665
|
if (this.gpuOwner !== world && this.gpuOwner !== detachedComposition?.token) return;
|
|
1155
1666
|
this.gpuScene?.dispose();
|
|
1156
1667
|
this.gpuScene = undefined;
|
|
@@ -1161,13 +1672,118 @@ export class PersistentRenderScene {
|
|
|
1161
1672
|
|
|
1162
1673
|
/** Drop only device-owned tables; the CPU projection remains the recovery authority. */
|
|
1163
1674
|
resetGpuForRecover(): void {
|
|
1164
|
-
if (this.composition !== undefined) this.composition.
|
|
1675
|
+
if (this.composition !== undefined) this.composition.gpuDrivenSceneRequired = true;
|
|
1165
1676
|
this.gpuScene = undefined;
|
|
1166
1677
|
this.gpuOwner = undefined;
|
|
1167
1678
|
this.gpuDevice = undefined;
|
|
1168
1679
|
this.gpuStatus = this.options.getDevice === undefined ? 'inactive' : 'rebuild-pending';
|
|
1169
1680
|
}
|
|
1170
1681
|
|
|
1682
|
+
/** Prepare a detached GPU scene without changing the active scene owner. */
|
|
1683
|
+
prepareRecoveryGpuDrivenCandidate(
|
|
1684
|
+
device: RhiDevice,
|
|
1685
|
+
): Result<PersistentGpuDrivenCandidate, RhiError> {
|
|
1686
|
+
const composition = this.composition;
|
|
1687
|
+
if (composition === undefined) {
|
|
1688
|
+
return ok({
|
|
1689
|
+
state: undefined,
|
|
1690
|
+
createRecoveryRoot: (scope) => ({
|
|
1691
|
+
kind: 'scene-table',
|
|
1692
|
+
create: () => {
|
|
1693
|
+
if (!scope.isAlive()) throw new Error('GPU-driven candidate scope is not active.');
|
|
1694
|
+
throw new Error('GPU-driven candidate has no scene resource.');
|
|
1695
|
+
},
|
|
1696
|
+
cleanup: () => undefined,
|
|
1697
|
+
}),
|
|
1698
|
+
publish: () => undefined,
|
|
1699
|
+
discard: () => undefined,
|
|
1700
|
+
release: () => undefined,
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
const created = GpuScene.create(
|
|
1704
|
+
device,
|
|
1705
|
+
Math.max(256, composition.projection.inspect().slotCapacity),
|
|
1706
|
+
);
|
|
1707
|
+
if (!created.ok) return created;
|
|
1708
|
+
if (created.value.status === 'unavailable') {
|
|
1709
|
+
return ok({
|
|
1710
|
+
state: undefined,
|
|
1711
|
+
createRecoveryRoot: (scope) => ({
|
|
1712
|
+
kind: 'scene-table',
|
|
1713
|
+
create: () => {
|
|
1714
|
+
if (!scope.isAlive()) throw new Error('GPU-driven candidate scope is not active.');
|
|
1715
|
+
throw new Error('GPU-driven candidate has no scene resource.');
|
|
1716
|
+
},
|
|
1717
|
+
cleanup: () => undefined,
|
|
1718
|
+
}),
|
|
1719
|
+
publish: () => undefined,
|
|
1720
|
+
discard: () => undefined,
|
|
1721
|
+
release: () => undefined,
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
const scene = created.value.scene;
|
|
1725
|
+
const slots = composition.projection.slotsSnapshot();
|
|
1726
|
+
const rebuilt = scene.rebuild(slots);
|
|
1727
|
+
if (!rebuilt.ok) {
|
|
1728
|
+
scene.dispose();
|
|
1729
|
+
return rebuilt;
|
|
1730
|
+
}
|
|
1731
|
+
const state: PersistentGpuDrivenState = Object.freeze({
|
|
1732
|
+
scene,
|
|
1733
|
+
plan: composition.topology.plan(),
|
|
1734
|
+
slots,
|
|
1735
|
+
});
|
|
1736
|
+
let published = false;
|
|
1737
|
+
let released = false;
|
|
1738
|
+
return ok({
|
|
1739
|
+
state,
|
|
1740
|
+
createRecoveryRoot: (scope) => ({
|
|
1741
|
+
kind: 'scene-table',
|
|
1742
|
+
create: () => {
|
|
1743
|
+
if (!scope.isAlive()) throw new Error('GPU-driven candidate scope is not active.');
|
|
1744
|
+
return scene;
|
|
1745
|
+
},
|
|
1746
|
+
cleanup: () => undefined,
|
|
1747
|
+
}),
|
|
1748
|
+
publish: () => {
|
|
1749
|
+
if (published || released) return;
|
|
1750
|
+
published = true;
|
|
1751
|
+
this.gpuScene = scene;
|
|
1752
|
+
this.gpuOwner = composition.token;
|
|
1753
|
+
this.gpuDevice = device;
|
|
1754
|
+
this.gpuStatus = 'resident';
|
|
1755
|
+
},
|
|
1756
|
+
discard: () => {
|
|
1757
|
+
if (published || released) return;
|
|
1758
|
+
released = true;
|
|
1759
|
+
scene.dispose();
|
|
1760
|
+
},
|
|
1761
|
+
release: () => {
|
|
1762
|
+
if (published || released) return;
|
|
1763
|
+
released = true;
|
|
1764
|
+
scene.dispose();
|
|
1765
|
+
},
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown> {
|
|
1770
|
+
return {
|
|
1771
|
+
kind: 'scene-table',
|
|
1772
|
+
create: () => {
|
|
1773
|
+
const composition = this.composition;
|
|
1774
|
+
if (!scope.isAlive() || composition === undefined) {
|
|
1775
|
+
throw new Error('RenderScene CPU projection is not ready for recovery.');
|
|
1776
|
+
}
|
|
1777
|
+
return Object.freeze({
|
|
1778
|
+
generation: scope.generation,
|
|
1779
|
+
revision: composition.projection.inspect().revision,
|
|
1780
|
+
visibleSlots: composition.projection.slotsSnapshot().length,
|
|
1781
|
+
});
|
|
1782
|
+
},
|
|
1783
|
+
cleanup: () => undefined,
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1171
1787
|
setPointsLinesInspections(inspections: readonly PointsLinesInspection[]): void {
|
|
1172
1788
|
this.pointsLinesInspections = inspections.map((inspection) => ({
|
|
1173
1789
|
...inspection,
|
|
@@ -1184,6 +1800,11 @@ export class PersistentRenderScene {
|
|
|
1184
1800
|
this.gpuStatus = 'inactive';
|
|
1185
1801
|
this.composition = undefined;
|
|
1186
1802
|
this.temporalCapture = undefined;
|
|
1803
|
+
this.visibilityFacets.clear();
|
|
1804
|
+
this.invalidateActiveEntityRevision();
|
|
1805
|
+
this.visibilityFacetFrame = undefined;
|
|
1806
|
+
this.visibilityProjectionCache = undefined;
|
|
1807
|
+
this.stableSlotByEntity.clear();
|
|
1187
1808
|
this.pointsLinesInspections = [];
|
|
1188
1809
|
}
|
|
1189
1810
|
|
|
@@ -1198,6 +1819,7 @@ export class PersistentRenderScene {
|
|
|
1198
1819
|
transformUpdates: this.transformUpdates,
|
|
1199
1820
|
lastResyncReason: this.lastResyncReason,
|
|
1200
1821
|
projectionRecords: entry?.projection.inspect().records.length ?? 0,
|
|
1822
|
+
...(entry === undefined ? {} : { probeBlend: entry.probeProjection.inspect() }),
|
|
1201
1823
|
topology:
|
|
1202
1824
|
entry?.topology.inspect() ??
|
|
1203
1825
|
({
|
|
@@ -1222,12 +1844,15 @@ export class PersistentRenderScene {
|
|
|
1222
1844
|
scene: this.gpuScene,
|
|
1223
1845
|
plan: entry.topology.plan(),
|
|
1224
1846
|
slots: entry.projection.slotsSnapshot(),
|
|
1847
|
+
worldKeys: entry.worldKeys,
|
|
1225
1848
|
};
|
|
1226
1849
|
}
|
|
1227
1850
|
|
|
1228
1851
|
/** Publish renderer-owned previous transforms after queue submission. */
|
|
1229
1852
|
commitTemporalFrame(): Result<void, RhiError> {
|
|
1230
|
-
|
|
1853
|
+
// Previous transforms are consumed only by TAA and motion blur. Ordinary
|
|
1854
|
+
// frames must not copy and upload the complete GPU transform table.
|
|
1855
|
+
const gpuCommit = this.gpuScene?.commitTemporalFrame(this.temporalCapture !== undefined);
|
|
1231
1856
|
if (gpuCommit !== undefined && !gpuCommit.ok) return gpuCommit;
|
|
1232
1857
|
const temporalCapture = this.temporalCapture;
|
|
1233
1858
|
if (temporalCapture !== undefined) {
|
|
@@ -1279,19 +1904,76 @@ export class PersistentRenderScene {
|
|
|
1279
1904
|
worlds: readonly World[],
|
|
1280
1905
|
owner: { readonly cameraOwner: number; readonly resourceOwner: number },
|
|
1281
1906
|
catalogEpoch: number,
|
|
1282
|
-
buildCandidateFrame: () => ExtractedFrame,
|
|
1907
|
+
buildCandidateFrame: (renderables: 'full' | 'none') => ExtractedFrame,
|
|
1283
1908
|
leases?: readonly RenderReadLease[],
|
|
1909
|
+
preserveVisibilityOnReorder = false,
|
|
1284
1910
|
): ExtractedFrame {
|
|
1285
|
-
const
|
|
1911
|
+
const previousEntry = this.composition;
|
|
1912
|
+
const candidateFrame = buildCandidateFrame('full');
|
|
1286
1913
|
this.temporalCapture = undefined;
|
|
1287
1914
|
this.worldEntitiesScanned = candidateFrame.renderables.length;
|
|
1915
|
+
const worldKeys = this.visibilityWorldKeysFor(worlds);
|
|
1916
|
+
if (!preserveVisibilityOnReorder) {
|
|
1917
|
+
this.visibilityFacets.clear();
|
|
1918
|
+
this.invalidateActiveEntityRevision();
|
|
1919
|
+
} else if (previousEntry !== undefined) {
|
|
1920
|
+
const previousKeys = new Set(
|
|
1921
|
+
previousEntry.projection
|
|
1922
|
+
.slotsSnapshot()
|
|
1923
|
+
.map((slot) =>
|
|
1924
|
+
worldEntityKey(previousEntry.worldKeys[slot.worldId] ?? slot.worldId, slot.entityKey),
|
|
1925
|
+
),
|
|
1926
|
+
);
|
|
1927
|
+
const currentKeys = new Set(
|
|
1928
|
+
candidateFrame.renderables.map((renderable) =>
|
|
1929
|
+
worldEntityKey(worldKeys[renderable.worldId] ?? renderable.worldId, renderable.entityKey),
|
|
1930
|
+
),
|
|
1931
|
+
);
|
|
1932
|
+
if (
|
|
1933
|
+
previousKeys.size !== currentKeys.size ||
|
|
1934
|
+
[...previousKeys].some((key) => !currentKeys.has(key))
|
|
1935
|
+
) {
|
|
1936
|
+
this.visibilityFacets.clear();
|
|
1937
|
+
this.invalidateActiveEntityRevision();
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
// Slot allocation is renderer-owned identity. Sort by stable World identity
|
|
1941
|
+
// before allocating so a worlds[] reorder does not silently change a
|
|
1942
|
+
// primitive's slot/generation. Dispatch is remapped below to the sorted
|
|
1943
|
+
// renderable projection and remains frame-order independent.
|
|
1944
|
+
const sorted = candidateFrame.renderables
|
|
1945
|
+
.map((snapshot, sourceIndex) => ({ snapshot, sourceIndex }))
|
|
1946
|
+
.sort((left, right) => {
|
|
1947
|
+
const leftWorld = worldKeys[left.snapshot.worldId] ?? left.snapshot.worldId;
|
|
1948
|
+
const rightWorld = worldKeys[right.snapshot.worldId] ?? right.snapshot.worldId;
|
|
1949
|
+
return leftWorld - rightWorld || left.snapshot.entityKey - right.snapshot.entityKey;
|
|
1950
|
+
});
|
|
1951
|
+
const sortedRenderables = sorted.map(({ snapshot }) => snapshot);
|
|
1952
|
+
const renderableIndex = new Int32Array(candidateFrame.renderables.length);
|
|
1953
|
+
for (let index = 0; index < sorted.length; index += 1) {
|
|
1954
|
+
const sourceIndex = sorted[index]?.sourceIndex;
|
|
1955
|
+
if (sourceIndex !== undefined) renderableIndex[sourceIndex] = index;
|
|
1956
|
+
}
|
|
1957
|
+
const sortedDispatch = candidateFrame.dispatch.flatMap((entry) => {
|
|
1958
|
+
const index = renderableIndex[entry.renderableIndex];
|
|
1959
|
+
return index === undefined ? [] : [{ ...entry, renderableIndex: index }];
|
|
1960
|
+
});
|
|
1961
|
+
const stableCandidateFrame = {
|
|
1962
|
+
...candidateFrame,
|
|
1963
|
+
renderables: sortedRenderables,
|
|
1964
|
+
dispatch: sortedDispatch,
|
|
1965
|
+
};
|
|
1288
1966
|
const projection = new RenderScene();
|
|
1289
|
-
projection.reset(
|
|
1967
|
+
projection.reset(sortedRenderables);
|
|
1968
|
+
this.stableSlotByEntity.clear();
|
|
1969
|
+
for (const slot of projection.slotsSnapshot()) {
|
|
1970
|
+
const stableWorld = worldKeys[slot.worldId] ?? slot.worldId;
|
|
1971
|
+
this.stableSlotByEntity.set(worldEntityKey(stableWorld, slot.entityKey), slot);
|
|
1972
|
+
}
|
|
1290
1973
|
const transmissionDemand = new PersistentTransmissionDemandProjection();
|
|
1291
1974
|
transmissionDemand.apply(transmissionDemandOperations(candidateFrame.renderables));
|
|
1292
1975
|
const topology = new BatchTopology();
|
|
1293
1976
|
topology.rebuild(projection.slotsSnapshot());
|
|
1294
|
-
this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
|
|
1295
1977
|
const token = {};
|
|
1296
1978
|
const entry: PersistentCompositionEntry = {
|
|
1297
1979
|
token,
|
|
@@ -1301,31 +1983,69 @@ export class PersistentRenderScene {
|
|
|
1301
1983
|
projection,
|
|
1302
1984
|
topology,
|
|
1303
1985
|
transmissionDemand,
|
|
1304
|
-
|
|
1986
|
+
structure: {
|
|
1987
|
+
dispatch: stableCandidateFrame.dispatch,
|
|
1988
|
+
visibilityStats: stableCandidateFrame.visibilityStats,
|
|
1989
|
+
visibilitySnapshots: stableCandidateFrame.visibilitySnapshots,
|
|
1990
|
+
featureVisibilitySnapshots: stableCandidateFrame.featureVisibilitySnapshots,
|
|
1991
|
+
hiddenEntityReports: stableCandidateFrame.hiddenEntityReports,
|
|
1992
|
+
},
|
|
1993
|
+
worldKeys,
|
|
1994
|
+
readVersions:
|
|
1305
1995
|
leases === undefined || leases.length !== worlds.length
|
|
1306
1996
|
? []
|
|
1307
|
-
: leases.map((lease) => lease.
|
|
1997
|
+
: leases.map((lease) => lease.captureVersion()),
|
|
1308
1998
|
leaseIdentities:
|
|
1309
1999
|
leases === undefined || leases.length !== worlds.length
|
|
1310
2000
|
? []
|
|
1311
2001
|
: leases.map((lease) => lease.worldIdentity),
|
|
2002
|
+
transformQueries:
|
|
2003
|
+
leases === undefined || leases.length !== worlds.length
|
|
2004
|
+
? []
|
|
2005
|
+
: worlds.map(createGlobalTransformChangeQuery),
|
|
1312
2006
|
catalogEpoch,
|
|
1313
2007
|
rigidOnly: candidateFrame.renderables.every((renderable) => renderable.skin === undefined),
|
|
1314
|
-
|
|
1315
|
-
|
|
2008
|
+
probeProjection: new ProbeBlendSceneProjection(),
|
|
2009
|
+
temporalDemanded: frameDemandsTemporal(candidateFrame),
|
|
2010
|
+
gpuDrivenSceneRequired: true,
|
|
1316
2011
|
};
|
|
1317
2012
|
this.composition = entry;
|
|
2013
|
+
projection.setTemporalTracking(entry.temporalDemanded);
|
|
2014
|
+
// The projection must be installed before deriving probe primitives. On
|
|
2015
|
+
// the first composition rebuild `projectReflectionProbes` otherwise sees
|
|
2016
|
+
// no composition and silently drops every per-renderable selection,
|
|
2017
|
+
// leaving the producer on its neutral/no-demand path until an unrelated
|
|
2018
|
+
// later rebuild.
|
|
2019
|
+
this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
|
|
2020
|
+
this.projectProbes(entry, candidateFrame);
|
|
1318
2021
|
this.syncGpuOwner(token, projection, undefined, true);
|
|
1319
2022
|
this.fullRebuilds += 1;
|
|
1320
2023
|
this.lastResyncReason = 'attach';
|
|
1321
|
-
const
|
|
1322
|
-
|
|
1323
|
-
.map((snapshot) => projection.withTemporal(snapshot))
|
|
1324
|
-
|
|
1325
|
-
|
|
2024
|
+
const materialized = projection.materialize();
|
|
2025
|
+
const candidates = entry.temporalDemanded
|
|
2026
|
+
? materialized.map((snapshot) => projection.withTemporal(snapshot))
|
|
2027
|
+
: materialized;
|
|
2028
|
+
const frameWithProbes = attachProbeRecords(
|
|
2029
|
+
{ ...stableCandidateFrame, renderables: candidates },
|
|
1326
2030
|
candidates,
|
|
1327
|
-
|
|
2031
|
+
projection.slotsSnapshot(),
|
|
2032
|
+
entry.probeProjection,
|
|
2033
|
+
);
|
|
2034
|
+
const slots = projection.slotsSnapshot();
|
|
2035
|
+
const result = cullPersistentFrame(
|
|
2036
|
+
frameWithProbes,
|
|
2037
|
+
frameWithProbes.renderables,
|
|
2038
|
+
(snapshot, index) => {
|
|
2039
|
+
const slot = slots[index];
|
|
2040
|
+
return slot === undefined
|
|
2041
|
+
? projection.cullingWorldBounds(snapshot)
|
|
2042
|
+
: projection.cullingWorldBoundsAt(slot);
|
|
2043
|
+
},
|
|
1328
2044
|
);
|
|
2045
|
+
this.frameEpoch += 1;
|
|
2046
|
+
this.visibilityFacetFrame = undefined;
|
|
2047
|
+
this.visibilityProjectionCache = undefined;
|
|
2048
|
+
return result;
|
|
1329
2049
|
}
|
|
1330
2050
|
|
|
1331
2051
|
/** Internal topology fact; the renderer does not expose demand as public API. */
|
|
@@ -1340,22 +2060,89 @@ export class PersistentRenderScene {
|
|
|
1340
2060
|
|
|
1341
2061
|
/**
|
|
1342
2062
|
* Derive an ephemeral frame plan from persistent scene facts. The scene
|
|
1343
|
-
* retains identity/topology/
|
|
2063
|
+
* retains identity/topology/read versions; no prior frame object is an authority.
|
|
1344
2064
|
*/
|
|
1345
2065
|
private deriveFramePlan(
|
|
1346
2066
|
entry: PersistentCompositionEntry,
|
|
1347
|
-
buildCandidateFrame: () => ExtractedFrame,
|
|
2067
|
+
buildCandidateFrame: (renderables: 'full' | 'none') => ExtractedFrame,
|
|
1348
2068
|
): ExtractedFrame {
|
|
1349
|
-
const
|
|
2069
|
+
const resourceFrame = buildCandidateFrame('none');
|
|
2070
|
+
const candidateFrame: ExtractedFrame = { ...resourceFrame, ...entry.structure };
|
|
2071
|
+
entry.temporalDemanded = frameDemandsTemporal(candidateFrame);
|
|
2072
|
+
entry.projection.setTemporalTracking(entry.temporalDemanded);
|
|
2073
|
+
this.projectProbes(entry, candidateFrame);
|
|
1350
2074
|
this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
.map((snapshot) => entry.projection.withTemporal(snapshot))
|
|
1354
|
-
|
|
1355
|
-
|
|
2075
|
+
const materialized = entry.projection.materialize();
|
|
2076
|
+
const candidates = entry.temporalDemanded
|
|
2077
|
+
? materialized.map((snapshot) => entry.projection.withTemporal(snapshot))
|
|
2078
|
+
: materialized;
|
|
2079
|
+
const frameWithProbes = attachProbeRecords(
|
|
2080
|
+
{ ...candidateFrame, renderables: candidates },
|
|
1356
2081
|
candidates,
|
|
1357
|
-
|
|
2082
|
+
entry.projection.slotsSnapshot(),
|
|
2083
|
+
entry.probeProjection,
|
|
1358
2084
|
);
|
|
2085
|
+
const slots = entry.projection.slotsSnapshot();
|
|
2086
|
+
const result = cullPersistentFrame(
|
|
2087
|
+
frameWithProbes,
|
|
2088
|
+
frameWithProbes.renderables,
|
|
2089
|
+
(snapshot, index) => {
|
|
2090
|
+
const slot = slots[index];
|
|
2091
|
+
return slot === undefined
|
|
2092
|
+
? entry.projection.cullingWorldBounds(snapshot)
|
|
2093
|
+
: entry.projection.cullingWorldBoundsAt(slot);
|
|
2094
|
+
},
|
|
2095
|
+
);
|
|
2096
|
+
this.frameEpoch += 1;
|
|
2097
|
+
this.visibilityFacetFrame = undefined;
|
|
2098
|
+
this.visibilityProjectionCache = undefined;
|
|
2099
|
+
return result;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
private projectProbes(entry: PersistentCompositionEntry, frame: ExtractedFrame): void {
|
|
2103
|
+
const sky =
|
|
2104
|
+
frame.skylight === undefined
|
|
2105
|
+
? {
|
|
2106
|
+
available: false,
|
|
2107
|
+
irradiance: [0, 0, 0] as const,
|
|
2108
|
+
fallbackReason: 'no-skylight',
|
|
2109
|
+
}
|
|
2110
|
+
: {
|
|
2111
|
+
available: true,
|
|
2112
|
+
identity: `skylight:${frame.skylight.entityHandle}`,
|
|
2113
|
+
sourceKey:
|
|
2114
|
+
frame.skylight.equirectHandle > 0
|
|
2115
|
+
? `equirect:${frame.skylight.equirectHandle}`
|
|
2116
|
+
: `skylight:${frame.skylight.entityHandle}`,
|
|
2117
|
+
irradiance: [
|
|
2118
|
+
frame.skylight.color[0] * frame.skylight.intensity,
|
|
2119
|
+
frame.skylight.color[1] * frame.skylight.intensity,
|
|
2120
|
+
frame.skylight.color[2] * frame.skylight.intensity,
|
|
2121
|
+
] as [number, number, number],
|
|
2122
|
+
};
|
|
2123
|
+
const probes = frame.lightProbes ?? [];
|
|
2124
|
+
// Sky is evaluated by the global environment path. With no local light
|
|
2125
|
+
// probes there can be no per-object ProbeBlendRecord, regardless of how
|
|
2126
|
+
// many dynamic objects moved this frame. Preserve the projection call so
|
|
2127
|
+
// a transition from probes to no probes still clears retained records.
|
|
2128
|
+
if (probes.length === 0) {
|
|
2129
|
+
entry.probeProjection.apply({ objects: [], probes, sky });
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
const objects: ProbeSceneObjectInput[] = entry.projection.slotsSnapshot().map((slot) => ({
|
|
2133
|
+
objectKey: slot.slot,
|
|
2134
|
+
generation: slot.generation,
|
|
2135
|
+
position: [
|
|
2136
|
+
slot.snapshot.transform.world[12] ?? 0,
|
|
2137
|
+
slot.snapshot.transform.world[13] ?? 0,
|
|
2138
|
+
slot.snapshot.transform.world[14] ?? 0,
|
|
2139
|
+
],
|
|
2140
|
+
}));
|
|
2141
|
+
entry.probeProjection.apply({
|
|
2142
|
+
objects,
|
|
2143
|
+
probes,
|
|
2144
|
+
sky,
|
|
2145
|
+
});
|
|
1359
2146
|
}
|
|
1360
2147
|
|
|
1361
2148
|
private syncGpuOwner(
|
|
@@ -1364,6 +2151,7 @@ export class PersistentRenderScene {
|
|
|
1364
2151
|
delta?: RenderSceneApplyResult,
|
|
1365
2152
|
full = false,
|
|
1366
2153
|
): void {
|
|
2154
|
+
if (this.composition?.token === owner && !this.composition.gpuDrivenSceneRequired) return;
|
|
1367
2155
|
const acquired = this.acquireGpuScene(owner, projection);
|
|
1368
2156
|
if (acquired === undefined || acquired.rebuilt) return;
|
|
1369
2157
|
const result = full
|