@forgeax/engine-render 0.1.20 → 0.1.23
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 +365 -29
- package/dist/assembly/backend-contract.d.ts +13 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -1
- package/dist/assembly/device-feature-admission.d.ts +18 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -0
- package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
- package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +30 -19
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +19 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/render-target-host.d.ts +46 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -0
- package/dist/assets/render-asset-port.d.ts.map +1 -1
- package/dist/authoring.mjs +4 -1
- package/dist/authoring.mjs.map +1 -1
- package/dist/capture/cube-views.d.ts +21 -0
- package/dist/capture/cube-views.d.ts.map +1 -0
- package/dist/capture/scheduler.d.ts +54 -0
- package/dist/capture/scheduler.d.ts.map +1 -0
- package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
- package/dist/chunk-4L3P6DT5.mjs.map +1 -0
- package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
- package/dist/chunk-4NPUASZR.mjs.map +1 -0
- package/dist/chunk-DIYW3M5D.mjs +5085 -0
- package/dist/chunk-DIYW3M5D.mjs.map +1 -0
- package/dist/chunk-FBC4R44J.mjs +1003 -0
- package/dist/chunk-FBC4R44J.mjs.map +1 -0
- package/dist/{chunk-CDTWTHZR.mjs → chunk-FF7K3FSO.mjs} +474 -168
- package/dist/chunk-FF7K3FSO.mjs.map +1 -0
- package/dist/chunk-HZUPSYW7.mjs +1104 -0
- package/dist/chunk-HZUPSYW7.mjs.map +1 -0
- package/dist/{chunk-TBQJOK2L.mjs → chunk-QODA5UKU.mjs} +321 -269
- package/dist/chunk-QODA5UKU.mjs.map +1 -0
- package/dist/chunk-YALPS44A.mjs +296 -0
- package/dist/chunk-YALPS44A.mjs.map +1 -0
- package/dist/{chunk-4XINDLAY.mjs → chunk-ZUWKAZ2I.mjs} +1963 -2178
- package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
- package/dist/cluster-binner.d.ts +5 -0
- package/dist/cluster-binner.d.ts.map +1 -1
- package/dist/components/camera.d.ts +1 -0
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/cube-camera.d.ts +20 -0
- package/dist/components/cube-camera.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +11 -2
- package/dist/components/directional-light.d.ts.map +1 -1
- package/dist/components/directional-shadow-filter.d.ts +25 -0
- package/dist/components/directional-shadow-filter.d.ts.map +1 -0
- package/dist/components/index.d.ts +5 -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/reflection-probe.d.ts +17 -0
- package/dist/components/reflection-probe.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 +14836 -9620
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +8 -1
- 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.map +1 -1
- package/dist/environment/lifecycle.d.ts +5 -0
- 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 +9 -1
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +166 -93
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/directional-shadow-projection.d.ts +23 -0
- package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
- package/dist/extract/environment.d.ts +14 -0
- package/dist/extract/environment.d.ts.map +1 -1
- 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/fullscreen.d.ts.map +1 -1
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +10 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +13 -2
- package/dist/fullscreen-post-process-pass.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 +97 -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 +8 -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 +21 -19
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +4 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/kernel-cache.d.ts +36 -0
- package/dist/ibl/kernel-cache.d.ts.map +1 -0
- package/dist/index.d.ts +39 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +91 -6
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +214 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +50 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -0
- 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 +58 -140
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +54 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +38 -14
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts +2 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
- package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
- package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
- package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts +4 -13
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +5 -26
- package/dist/pipeline/standard-profile.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 +40 -7
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/point-shadow-inspection.d.ts +21 -0
- package/dist/point-shadow-inspection.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 +33 -36
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +78 -24
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts +5 -0
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +16 -6
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
- package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
- package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/index.d.ts +8 -0
- package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
- package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/session.d.ts +43 -0
- package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
- package/dist/record/gpu-timing.d.ts +20 -3
- 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 +6 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +25 -2
- 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 +2 -0
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +98 -6
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +119 -4
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +7 -5
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/reflection/filter.d.ts +22 -0
- package/dist/reflection/filter.d.ts.map +1 -0
- package/dist/reflection/gpu-table.d.ts +25 -0
- package/dist/reflection/gpu-table.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 +140 -0
- package/dist/reflection/projection.d.ts.map +1 -0
- 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 +62 -14
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +49 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +160 -31
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +28 -2
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer.d.ts +43 -0
- package/dist/renderer.d.ts.map +1 -0
- 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 +117 -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/shadow-atlas.d.ts +3 -3
- package/dist/shadow-atlas.d.ts.map +1 -1
- package/dist/ssao-buffers.d.ts +3 -0
- package/dist/ssao-buffers.d.ts.map +1 -1
- 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/targets/contracts.d.ts +66 -0
- package/dist/targets/contracts.d.ts.map +1 -0
- package/dist/targets/graph-projection.d.ts +29 -0
- package/dist/targets/graph-projection.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +34 -0
- package/dist/targets/material-source.d.ts.map +1 -0
- package/dist/targets/owner.d.ts +32 -0
- package/dist/targets/owner.d.ts.map +1 -0
- package/dist/targets/physical.d.ts +18 -0
- package/dist/targets/physical.d.ts.map +1 -0
- package/dist/targets/readback.d.ts +39 -0
- package/dist/targets/readback.d.ts.map +1 -0
- 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/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 +5 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +1 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +9 -5
- package/dist/volume/passes.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
- package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
- package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
- package/src/__tests__/cluster-binner.unit.test.ts +35 -0
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +12 -2
- package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
- package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
- package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
- package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
- package/src/__tests__/cube-camera.integration.test.ts +42 -0
- package/src/__tests__/detached-inspection.integration.test.ts +6 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +85 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +62 -6
- package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
- package/src/__tests__/environment-generation.integration.test.ts +16 -0
- package/src/__tests__/evidence-identity.ts +13 -0
- package/src/__tests__/evidence-identity.unit.test.ts +24 -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 +288 -29
- package/src/__tests__/fog-component.unit.test.ts +67 -0
- package/src/__tests__/fog.integration.test.ts +86 -0
- package/src/__tests__/frame-observation.contract.test.ts +15 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -8
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +18 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +579 -17
- 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 +101 -6
- 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-pass-timing-browser-runner.ts +281 -0
- package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
- package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
- package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
- package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
- package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
- package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
- package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -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__/hdrp-group2-visibility.unit.test.ts +24 -0
- package/src/__tests__/instances-culling.integration.test.ts +94 -0
- package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -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 +17 -0
- package/src/__tests__/material-observation.integration.test.ts +101 -0
- 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__/pbr-view-layout.unit.test.ts +16 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
- 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__/public-surface.test-d.ts +10 -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-authoring.unit.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
- package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
- package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
- package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
- package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
- package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
- package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +19 -3
- 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__/render-target-admission.unit.test.ts +80 -0
- package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
- package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
- package/src/__tests__/render-target-material-source.test-d.ts +30 -0
- package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
- package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
- package/src/__tests__/render-target-owner.unit.test.ts +122 -0
- package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
- package/src/__tests__/render-target-public-schema.json +107 -0
- package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
- package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
- package/src/__tests__/render-target-readback.integration.test.ts +90 -0
- package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +124 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
- package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
- package/src/__tests__/skin-motion-regression.unit.test.ts +154 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +13 -4
- 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-capability.integration.test.ts +63 -0
- package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +299 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +15 -5
- package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
- package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +441 -0
- package/src/__tests__/standard-lighting.integration.test.ts +67 -0
- package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
- package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
- package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
- 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 +69 -0
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
- package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
- 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__/visibility-resolver.unit.test.ts +15 -0
- 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/assembly/backend-contract.ts +13 -0
- package/src/assembly/device-feature-admission.ts +80 -0
- package/src/assembly/directional-shadow-inspection.ts +122 -0
- package/src/assembly/factory.ts +1411 -366
- package/src/assembly/host-contract.ts +35 -0
- package/src/assembly/render-target-host.ts +847 -0
- package/src/assets/asset-decoders.ts +1 -1
- package/src/assets/render-asset-port.ts +7 -1
- package/src/capture/cube-views.ts +69 -0
- package/src/capture/scheduler.ts +265 -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/__tests__/schema-contract.unit.test.ts +12 -0
- package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
- package/src/components/camera.ts +4 -0
- package/src/components/cube-camera.ts +52 -0
- package/src/components/directional-light.ts +22 -2
- package/src/components/directional-shadow-filter.ts +57 -0
- package/src/components/index.ts +5 -0
- package/src/components/light-helpers.ts +133 -8
- package/src/components/light-probe.ts +13 -0
- package/src/components/rect-area-light.ts +15 -0
- package/src/components/reflection-probe.ts +49 -0
- package/src/components/scene-instance.ts +6 -0
- package/src/components/spot-light.ts +3 -0
- package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
- package/src/device/gpu-residency.ts +140 -9
- package/src/device/lifecycle-transaction.ts +12 -0
- package/src/environment/frame.ts +6 -24
- package/src/environment/lifecycle.ts +20 -1
- package/src/errors/gpu-driven.ts +92 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/recover.ts +32 -0
- package/src/errors/render.ts +341 -143
- package/src/extract/camera.ts +3 -3
- package/src/extract/directional-shadow-projection.ts +61 -0
- package/src/extract/environment.ts +57 -0
- package/src/extract/gpu-driven.ts +108 -0
- package/src/extract/visibility.ts +2 -1
- package/src/features/fullscreen.ts +3 -0
- package/src/features/targets.ts +4 -1
- package/src/features/types.ts +10 -0
- package/src/fullscreen-post-process-pass.ts +43 -2
- package/src/gpu-driven/batch-topology.ts +207 -28
- package/src/gpu-driven/prepared-draw.ts +208 -0
- package/src/gpu-driven/production-raster.ts +953 -43
- package/src/gpu-driven/view-gpu.ts +411 -29
- package/src/gpu-scene-schema.ts +13 -0
- package/src/gpu-scene.ts +140 -64
- package/src/gpu-usage.ts +0 -1
- package/src/hdrp-buffers.ts +169 -63
- package/src/ibl/IblPipelineCache.ts +11 -0
- package/src/ibl/kernel-cache.ts +88 -0
- package/src/index.ts +222 -0
- package/src/inspection-types.ts +276 -1
- package/src/instances-derived-bounds.ts +171 -0
- package/src/internal.ts +38 -0
- package/src/light-buffer-layout.ts +155 -310
- package/src/materials.ts +16 -2
- package/src/mesh-material-bindings.ts +132 -0
- package/src/pbr-pipeline.ts +255 -60
- package/src/pipeline/standard-forward-lane.ts +93 -16
- package/src/pipeline/standard-lighting/errors.ts +6 -0
- package/src/pipeline/standard-lighting/graph.ts +158 -0
- package/src/pipeline/standard-lighting/inspection.ts +88 -0
- package/src/pipeline/standard-lighting/layout.ts +31 -0
- package/src/pipeline/standard-lighting/light-frame.ts +35 -0
- package/src/pipeline/standard-lighting/membership.ts +43 -0
- package/src/pipeline/standard-lighting/prepare.ts +37 -0
- package/src/pipeline/standard-lighting/topology.ts +120 -0
- package/src/pipeline/standard-lighting/transport.ts +78 -0
- package/src/pipeline/standard-pipeline.ts +124 -193
- package/src/pipeline/standard-post.ts +26 -7
- package/src/pipeline/standard-profile.ts +17 -47
- package/src/pipeline-builder.ts +5 -2
- package/src/pipeline-spec.ts +95 -21
- package/src/plugin.ts +4 -0
- package/src/point-shadow-inspection.ts +55 -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__/gpu-timing.unit.test.ts +177 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +64 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
- package/src/record/frame-lighting.ts +591 -452
- package/src/record/frame-snapshot.ts +92 -39
- package/src/record/frame-targets.ts +17 -0
- package/src/record/frame.ts +662 -158
- package/src/record/gpu-pass-timing/contract.ts +180 -0
- package/src/record/gpu-pass-timing/errors.ts +34 -0
- package/src/record/gpu-pass-timing/index.ts +37 -0
- package/src/record/gpu-pass-timing/parser.ts +97 -0
- package/src/record/gpu-pass-timing/session.ts +938 -0
- package/src/record/gpu-timing.ts +112 -39
- package/src/record/helpers.ts +0 -58
- package/src/record/main-pass-geometry.ts +324 -119
- package/src/record/main-pass-material.ts +103 -10
- package/src/record/main-pass-sprite-draws.ts +99 -103
- package/src/record/main-pass.ts +71 -15
- package/src/record/mesh-ssbo.ts +47 -36
- package/src/record/render-context.ts +136 -3
- package/src/record/shadow-pass.ts +83 -68
- package/src/record/skybox-post-pass.ts +20 -4
- package/src/record/typed-frame-graph.ts +1034 -43
- package/src/record/view-ubo.ts +72 -47
- package/src/reflection/filter.ts +81 -0
- package/src/reflection/gpu-table.ts +44 -0
- package/src/reflection/inspection.ts +67 -0
- package/src/reflection/projection.ts +476 -0
- package/src/reflection/record-owner.ts +1283 -0
- package/src/render-contract.ts +93 -10
- package/src/render-graph-primitives.ts +9 -4
- package/src/render-pipeline.ts +122 -2
- package/src/render-system-extract.ts +1041 -225
- package/src/render-system.ts +1310 -85
- package/src/renderer.ts +222 -0
- 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 +942 -171
- 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/shadow-atlas.ts +5 -5
- package/src/ssao-buffers.ts +9 -2
- 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/targets/contracts.ts +182 -0
- package/src/targets/graph-projection.ts +169 -0
- package/src/targets/material-source.ts +143 -0
- package/src/targets/owner.ts +214 -0
- package/src/targets/physical.ts +18 -0
- package/src/targets/readback.ts +153 -0
- package/src/temporal/frame-coordinator.ts +26 -0
- package/src/temporal/frame.ts +14 -1
- package/src/temporal/view.ts +11 -0
- package/src/tilemap-chunk-extract-system.ts +4 -4
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
- package/src/typed-render-graph-primitives.ts +53 -3
- package/src/typed-shadow-passes.ts +46 -40
- package/src/volume/passes.ts +61 -58
- package/dist/.tsbuildinfo +0 -1
- package/dist/chunk-4XINDLAY.mjs.map +0 -1
- package/dist/chunk-CDTWTHZR.mjs.map +0 -1
- package/dist/chunk-M2MP4ZOU.mjs +0 -73
- package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
- package/dist/chunk-PCHBPYML.mjs.map +0 -1
- package/dist/chunk-TBQJOK2L.mjs.map +0 -1
- 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,11 +11,14 @@ 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,
|
|
21
|
+
ReflectionProbe,
|
|
19
22
|
SkyboxBackground,
|
|
20
23
|
Skylight,
|
|
21
24
|
SpotLight,
|
|
@@ -30,8 +33,15 @@ import {
|
|
|
30
33
|
} from '../gpu-driven/batch-topology';
|
|
31
34
|
import { GpuScene } from '../gpu-scene';
|
|
32
35
|
import type { PersistentRenderSceneInspection } from '../inspection-types';
|
|
36
|
+
import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
|
|
33
37
|
import type { PointsLinesInspection } from '../points-lines/inspection';
|
|
34
38
|
import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
|
|
39
|
+
import { worldEntityKey } from '../record/frame-snapshot';
|
|
40
|
+
import {
|
|
41
|
+
type ReflectionProbeFact,
|
|
42
|
+
ReflectionProbeProjection,
|
|
43
|
+
type ReflectionProbeSelectionResult,
|
|
44
|
+
} from '../reflection/projection';
|
|
35
45
|
import type { CameraSnapshot } from '../render-contract';
|
|
36
46
|
import type {
|
|
37
47
|
DispatchEntry,
|
|
@@ -48,21 +58,30 @@ import {
|
|
|
48
58
|
type TransmissionDemandOperation,
|
|
49
59
|
} from '../transmission/projection';
|
|
50
60
|
import { VolumetricFog } from '../volume/component';
|
|
61
|
+
import { ProbeBlendSceneProjection, type ProbeSceneObjectInput } from './probe-blend';
|
|
51
62
|
import type {
|
|
52
63
|
RenderSceneApplyResult,
|
|
53
64
|
RenderSceneBounds,
|
|
54
|
-
RenderSceneDelta,
|
|
55
65
|
RenderSceneInspection,
|
|
56
66
|
RenderSceneOperation,
|
|
57
67
|
RenderSceneRecord,
|
|
58
68
|
RenderSceneResyncReason,
|
|
59
69
|
RenderSceneSlot,
|
|
60
70
|
} from './render-scene-types';
|
|
71
|
+
import { createVisibilityBudget, type VisibilityBudget } from './visibility/budget';
|
|
72
|
+
import {
|
|
73
|
+
primitiveKey,
|
|
74
|
+
primitiveKeyId,
|
|
75
|
+
VisibilityFacetStore,
|
|
76
|
+
viewKey,
|
|
77
|
+
viewKeyId,
|
|
78
|
+
} from './visibility/facet';
|
|
79
|
+
import { decideVisibility } from './visibility/occlusion-confidence';
|
|
80
|
+
import type { PrimitiveKey, ViewKey, VisibilityCandidate } from './visibility/types';
|
|
61
81
|
|
|
62
82
|
export type {
|
|
63
83
|
RenderSceneApplyResult,
|
|
64
84
|
RenderSceneBounds,
|
|
65
|
-
RenderSceneDelta,
|
|
66
85
|
RenderSceneIdentity,
|
|
67
86
|
RenderSceneInspection,
|
|
68
87
|
RenderSceneOperation,
|
|
@@ -79,6 +98,14 @@ interface PendingIdentity {
|
|
|
79
98
|
removed: boolean;
|
|
80
99
|
}
|
|
81
100
|
|
|
101
|
+
interface WorldTransformSpan {
|
|
102
|
+
readonly worldId: number;
|
|
103
|
+
readonly entities: Readonly<Uint32Array>;
|
|
104
|
+
readonly worlds: Readonly<Float32Array>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type GlobalTransformChangeQuery = Query<readonly [typeof GlobalTransform]>;
|
|
108
|
+
|
|
82
109
|
export interface RenderSceneSubmissionCapture {
|
|
83
110
|
readonly revision: number;
|
|
84
111
|
readonly slots: readonly {
|
|
@@ -193,29 +220,69 @@ function intersects(left: RenderSceneBounds, right: RenderSceneBounds): boolean
|
|
|
193
220
|
);
|
|
194
221
|
}
|
|
195
222
|
|
|
196
|
-
function worldBounds(
|
|
223
|
+
function worldBounds(
|
|
224
|
+
snapshot: RenderableSnapshot,
|
|
225
|
+
target?: RenderSceneBounds,
|
|
226
|
+
): RenderSceneBounds | undefined {
|
|
197
227
|
const local = snapshot.localAabb;
|
|
198
228
|
if (local === undefined || local.length < 6) return undefined;
|
|
199
229
|
const world = snapshot.transform.world;
|
|
200
|
-
const min
|
|
201
|
-
const max
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
230
|
+
const min = (target?.min ?? [0, 0, 0]) as [number, number, number];
|
|
231
|
+
const max = (target?.max ?? [0, 0, 0]) as [number, number, number];
|
|
232
|
+
const centerX = ((local[0] ?? 0) + (local[3] ?? 0)) * 0.5;
|
|
233
|
+
const centerY = ((local[1] ?? 0) + (local[4] ?? 0)) * 0.5;
|
|
234
|
+
const centerZ = ((local[2] ?? 0) + (local[5] ?? 0)) * 0.5;
|
|
235
|
+
const extentX = ((local[3] ?? 0) - (local[0] ?? 0)) * 0.5;
|
|
236
|
+
const extentY = ((local[4] ?? 0) - (local[1] ?? 0)) * 0.5;
|
|
237
|
+
const extentZ = ((local[5] ?? 0) - (local[2] ?? 0)) * 0.5;
|
|
238
|
+
const worldCenterX =
|
|
239
|
+
(world[0] ?? 0) * centerX +
|
|
240
|
+
(world[4] ?? 0) * centerY +
|
|
241
|
+
(world[8] ?? 0) * centerZ +
|
|
242
|
+
(world[12] ?? 0);
|
|
243
|
+
const worldCenterY =
|
|
244
|
+
(world[1] ?? 0) * centerX +
|
|
245
|
+
(world[5] ?? 0) * centerY +
|
|
246
|
+
(world[9] ?? 0) * centerZ +
|
|
247
|
+
(world[13] ?? 0);
|
|
248
|
+
const worldCenterZ =
|
|
249
|
+
(world[2] ?? 0) * centerX +
|
|
250
|
+
(world[6] ?? 0) * centerY +
|
|
251
|
+
(world[10] ?? 0) * centerZ +
|
|
252
|
+
(world[14] ?? 0);
|
|
253
|
+
const worldExtentX =
|
|
254
|
+
Math.abs(world[0] ?? 0) * extentX +
|
|
255
|
+
Math.abs(world[4] ?? 0) * extentY +
|
|
256
|
+
Math.abs(world[8] ?? 0) * extentZ;
|
|
257
|
+
const worldExtentY =
|
|
258
|
+
Math.abs(world[1] ?? 0) * extentX +
|
|
259
|
+
Math.abs(world[5] ?? 0) * extentY +
|
|
260
|
+
Math.abs(world[9] ?? 0) * extentZ;
|
|
261
|
+
const worldExtentZ =
|
|
262
|
+
Math.abs(world[2] ?? 0) * extentX +
|
|
263
|
+
Math.abs(world[6] ?? 0) * extentY +
|
|
264
|
+
Math.abs(world[10] ?? 0) * extentZ;
|
|
265
|
+
min[0] = worldCenterX - worldExtentX;
|
|
266
|
+
min[1] = worldCenterY - worldExtentY;
|
|
267
|
+
min[2] = worldCenterZ - worldExtentZ;
|
|
268
|
+
max[0] = worldCenterX + worldExtentX;
|
|
269
|
+
max[1] = worldCenterY + worldExtentY;
|
|
270
|
+
max[2] = worldCenterZ + worldExtentZ;
|
|
271
|
+
return target ?? { min, max };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function boundsFromArray(bounds: ArrayLike<number>): RenderSceneBounds | undefined {
|
|
275
|
+
if (bounds.length < 6) return undefined;
|
|
276
|
+
const min: [number, number, number] = [Number(bounds[0]), Number(bounds[1]), Number(bounds[2])];
|
|
277
|
+
const max: [number, number, number] = [Number(bounds[3]), Number(bounds[4]), Number(bounds[5])];
|
|
278
|
+
if (
|
|
279
|
+
!min.every(Number.isFinite) ||
|
|
280
|
+
!max.every(Number.isFinite) ||
|
|
281
|
+
min[0] > max[0] ||
|
|
282
|
+
min[1] > max[1] ||
|
|
283
|
+
min[2] > max[2]
|
|
284
|
+
) {
|
|
285
|
+
return undefined;
|
|
219
286
|
}
|
|
220
287
|
return { min, max };
|
|
221
288
|
}
|
|
@@ -230,19 +297,23 @@ function worldBounds(snapshot: RenderableSnapshot): RenderSceneBounds | undefine
|
|
|
230
297
|
export class RenderScene {
|
|
231
298
|
private readonly slots: Array<RenderSceneSlot | undefined> = [];
|
|
232
299
|
private readonly lastSubmittedSlots: Array<RenderSceneSlot | undefined> = [];
|
|
300
|
+
private readonly previousWorldBySlot: Array<Float32Array | undefined> = [];
|
|
233
301
|
private readonly generations: number[] = [];
|
|
234
302
|
private readonly freeSlots: number[] = [];
|
|
235
303
|
private readonly slotsByWorld = new Map<number, Map<number, number>>();
|
|
236
304
|
private readonly slotsByMaterial = new Map<number, Set<number>>();
|
|
237
305
|
private readonly dirtySinceSubmitted = new Set<number>();
|
|
306
|
+
private readonly transformUpdatedSlots: RenderSceneSlot[] = [];
|
|
307
|
+
private temporalTracking = false;
|
|
238
308
|
private lastSubmittedVisible = new Set<string>();
|
|
239
309
|
private submittedEpoch = 0;
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
private readonly
|
|
310
|
+
// Bounds are a derived column of the stable render slot, not metadata of a
|
|
311
|
+
// transient snapshot wrapper. Undefined means dirty; null means that the
|
|
312
|
+
// retained row has no usable bounds and must be conservatively visible.
|
|
313
|
+
private readonly worldBoundsBySlot: Array<RenderSceneBounds | null | undefined> = [];
|
|
314
|
+
private readonly instanceBoundsCache = new InstanceBoundsCache();
|
|
244
315
|
private orderedSlots: number[] = [];
|
|
245
|
-
private materialized:
|
|
316
|
+
private materialized: RenderableSnapshot[] | undefined;
|
|
246
317
|
private slotSnapshot: readonly RenderSceneSlot[] | undefined;
|
|
247
318
|
private revision = 0;
|
|
248
319
|
private noChangeFrames = 0;
|
|
@@ -350,6 +421,7 @@ export class RenderScene {
|
|
|
350
421
|
};
|
|
351
422
|
this.unindexMaterials(pending.initial);
|
|
352
423
|
this.slots[pending.initial.slot] = updatedSlot;
|
|
424
|
+
this.worldBoundsBySlot[updatedSlot.slot] = undefined;
|
|
353
425
|
this.indexMaterials(updatedSlot);
|
|
354
426
|
this.dirtySinceSubmitted.add(updatedSlot.slot);
|
|
355
427
|
updatedSlots.push(updatedSlot);
|
|
@@ -375,19 +447,55 @@ export class RenderScene {
|
|
|
375
447
|
};
|
|
376
448
|
}
|
|
377
449
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
450
|
+
/** Apply version-selected GlobalTransform columns without allocating per-entity operations. */
|
|
451
|
+
applyTransformSpans(spans: readonly WorldTransformSpan[]): RenderSceneApplyResult {
|
|
452
|
+
this.deltaFrames += 1;
|
|
453
|
+
let scanned = 0;
|
|
454
|
+
let updated = 0;
|
|
455
|
+
let ignoredLateUpdates = 0;
|
|
456
|
+
const updatedSlots = this.transformUpdatedSlots;
|
|
457
|
+
updatedSlots.length = 0;
|
|
458
|
+
for (const span of spans) {
|
|
459
|
+
scanned += span.entities.length;
|
|
460
|
+
for (let row = 0; row < span.entities.length; row += 1) {
|
|
461
|
+
const entity = span.entities[row];
|
|
462
|
+
if (entity === undefined) continue;
|
|
463
|
+
const slot = this.lookup(span.worldId, entity);
|
|
464
|
+
if (slot === undefined) {
|
|
465
|
+
ignoredLateUpdates += 1;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const target = slot.snapshot.transform.world;
|
|
469
|
+
const sourceStart = row * 16;
|
|
470
|
+
for (let index = 0; index < 16; index += 1) {
|
|
471
|
+
target[index] = span.worlds[sourceStart + index] ?? target[index] ?? 0;
|
|
472
|
+
}
|
|
473
|
+
if (slot.snapshot.instances === undefined) {
|
|
474
|
+
const cached = this.worldBoundsBySlot[slot.slot];
|
|
475
|
+
const bounds = worldBounds(slot.snapshot, cached ?? undefined);
|
|
476
|
+
this.worldBoundsBySlot[slot.slot] = bounds ?? null;
|
|
477
|
+
} else {
|
|
478
|
+
this.worldBoundsBySlot[slot.slot] = undefined;
|
|
479
|
+
}
|
|
480
|
+
if (this.temporalTracking) this.dirtySinceSubmitted.add(slot.slot);
|
|
481
|
+
updatedSlots.push(slot);
|
|
482
|
+
updated += 1;
|
|
483
|
+
}
|
|
389
484
|
}
|
|
390
|
-
|
|
485
|
+
this.renderableScans = scanned;
|
|
486
|
+
if (updated > 0) this.revision += 1;
|
|
487
|
+
return {
|
|
488
|
+
created: 0,
|
|
489
|
+
updated,
|
|
490
|
+
removed: 0,
|
|
491
|
+
recreated: 0,
|
|
492
|
+
ignoredLateUpdates,
|
|
493
|
+
createdSlots: [],
|
|
494
|
+
updatedSlots,
|
|
495
|
+
removedSlots: [],
|
|
496
|
+
recreatedSlots: [],
|
|
497
|
+
resynced: 0,
|
|
498
|
+
};
|
|
391
499
|
}
|
|
392
500
|
|
|
393
501
|
/** Reconcile the current identity set while retaining slots for survivors. */
|
|
@@ -422,6 +530,7 @@ export class RenderScene {
|
|
|
422
530
|
};
|
|
423
531
|
this.unindexMaterials(existing);
|
|
424
532
|
this.slots[existing.slot] = updated;
|
|
533
|
+
this.worldBoundsBySlot[updated.slot] = undefined;
|
|
425
534
|
this.indexMaterials(updated);
|
|
426
535
|
this.dirtySinceSubmitted.add(updated.slot);
|
|
427
536
|
this.orderedSlots.push(existing.slot);
|
|
@@ -436,14 +545,25 @@ export class RenderScene {
|
|
|
436
545
|
this.reset(snapshots);
|
|
437
546
|
}
|
|
438
547
|
|
|
439
|
-
|
|
548
|
+
/** Enable previous-frame publication only while a temporal feature consumes it. */
|
|
549
|
+
setTemporalTracking(enabled: boolean): void {
|
|
550
|
+
if (enabled === this.temporalTracking) return;
|
|
551
|
+
this.temporalTracking = enabled;
|
|
552
|
+
if (!enabled) {
|
|
553
|
+
this.dirtySinceSubmitted.clear();
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
for (const slot of this.orderedSlots) this.dirtySinceSubmitted.add(slot);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
materialize(): RenderableSnapshot[] {
|
|
440
560
|
if (this.materialized !== undefined) return this.materialized;
|
|
441
561
|
const snapshots: RenderableSnapshot[] = [];
|
|
442
562
|
for (const slot of this.orderedSlots) {
|
|
443
563
|
const record = this.slots[slot];
|
|
444
564
|
if (record !== undefined) snapshots.push(record.snapshot);
|
|
445
565
|
}
|
|
446
|
-
this.materialized =
|
|
566
|
+
this.materialized = snapshots;
|
|
447
567
|
return this.materialized;
|
|
448
568
|
}
|
|
449
569
|
|
|
@@ -489,6 +609,15 @@ export class RenderScene {
|
|
|
489
609
|
}
|
|
490
610
|
for (const update of capture.slots) {
|
|
491
611
|
this.lastSubmittedSlots[update.slot] = update.current;
|
|
612
|
+
const currentWorld = update.current?.snapshot.transform.world;
|
|
613
|
+
if (currentWorld !== undefined) {
|
|
614
|
+
let previousWorld = this.previousWorldBySlot[update.slot];
|
|
615
|
+
if (previousWorld === undefined) {
|
|
616
|
+
previousWorld = new Float32Array(16);
|
|
617
|
+
this.previousWorldBySlot[update.slot] = previousWorld;
|
|
618
|
+
}
|
|
619
|
+
previousWorld.set(currentWorld);
|
|
620
|
+
}
|
|
492
621
|
this.dirtySinceSubmitted.delete(update.slot);
|
|
493
622
|
}
|
|
494
623
|
this.lastSubmittedVisible = new Set(capture.visible);
|
|
@@ -551,12 +680,7 @@ export class RenderScene {
|
|
|
551
680
|
for (const slot of this.orderedSlots) {
|
|
552
681
|
const record = this.slots[slot];
|
|
553
682
|
if (record === undefined) continue;
|
|
554
|
-
const
|
|
555
|
-
let candidate = this.worldBoundsCache.get(snapshot);
|
|
556
|
-
if (candidate === undefined && !this.worldBoundsCache.has(snapshot)) {
|
|
557
|
-
candidate = worldBounds(snapshot) ?? null;
|
|
558
|
-
this.worldBoundsCache.set(snapshot, candidate);
|
|
559
|
-
}
|
|
683
|
+
const candidate = this.cullingWorldBoundsAt(record);
|
|
560
684
|
if (candidate === null || candidate === undefined) continue;
|
|
561
685
|
if (intersects(candidate, bounds)) records.push(record);
|
|
562
686
|
}
|
|
@@ -614,8 +738,15 @@ export class RenderScene {
|
|
|
614
738
|
entityKey: snapshot.entityKey,
|
|
615
739
|
snapshot: ownSnapshot(snapshot),
|
|
616
740
|
};
|
|
741
|
+
let previousWorld = this.previousWorldBySlot[slot];
|
|
742
|
+
if (previousWorld === undefined) {
|
|
743
|
+
previousWorld = new Float32Array(16);
|
|
744
|
+
this.previousWorldBySlot[slot] = previousWorld;
|
|
745
|
+
}
|
|
746
|
+
previousWorld.set(record.snapshot.transform.world);
|
|
617
747
|
this.generations[slot] = generation;
|
|
618
748
|
this.slots[slot] = record;
|
|
749
|
+
this.worldBoundsBySlot[slot] = undefined;
|
|
619
750
|
let entities = this.slotsByWorld.get(snapshot.worldId);
|
|
620
751
|
if (entities === undefined) {
|
|
621
752
|
entities = new Map<number, number>();
|
|
@@ -629,6 +760,8 @@ export class RenderScene {
|
|
|
629
760
|
|
|
630
761
|
private release(record: RenderSceneSlot): void {
|
|
631
762
|
this.slots[record.slot] = undefined;
|
|
763
|
+
this.worldBoundsBySlot[record.slot] = undefined;
|
|
764
|
+
this.instanceBoundsCache.invalidate(record.entityKey, record.worldId);
|
|
632
765
|
this.unindexMaterials(record);
|
|
633
766
|
const entities = this.slotsByWorld.get(record.worldId);
|
|
634
767
|
entities?.delete(record.entityKey);
|
|
@@ -690,12 +823,16 @@ export class RenderScene {
|
|
|
690
823
|
reasons.includes('geometry-revision') ||
|
|
691
824
|
reasons.includes('material-revision');
|
|
692
825
|
const previous = seedCurrent || submitted === undefined ? slot.snapshot : submitted.snapshot;
|
|
826
|
+
const previousWorld = this.previousWorldBySlot[slot.slot];
|
|
693
827
|
return {
|
|
694
828
|
previousEpoch: seedCurrent ? undefined : this.submittedEpoch,
|
|
695
829
|
previousSource: seedCurrent ? 'current-seed' : 'last-submitted',
|
|
696
830
|
reactive: reasons.length > 0,
|
|
697
831
|
reactiveReasons: reasons,
|
|
698
|
-
previousTransform:
|
|
832
|
+
previousTransform:
|
|
833
|
+
seedCurrent || previousWorld === undefined
|
|
834
|
+
? previous.transform
|
|
835
|
+
: { ...previous.transform, world: previousWorld },
|
|
699
836
|
previousInstances: previous.instances,
|
|
700
837
|
previousSkin: previous.skin,
|
|
701
838
|
previousMorphWeights: previous.morph?.weights,
|
|
@@ -706,23 +843,104 @@ export class RenderScene {
|
|
|
706
843
|
this.materialized = undefined;
|
|
707
844
|
this.slotSnapshot = undefined;
|
|
708
845
|
}
|
|
709
|
-
}
|
|
710
846
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
847
|
+
/**
|
|
848
|
+
* Return the CPU culling projection for one retained snapshot. Instances
|
|
849
|
+
* use a renderer-derived union, while the GPU scene keeps the mesh-local
|
|
850
|
+
* bounds for its independent per-instance visibility pass. Unknown or
|
|
851
|
+
* empty instance facts deliberately return undefined (conservative no-cull).
|
|
852
|
+
*/
|
|
853
|
+
cullingWorldBoundsAt(slot: RenderSceneSlot): RenderSceneBounds | undefined {
|
|
854
|
+
let candidate = this.worldBoundsBySlot[slot.slot];
|
|
855
|
+
if (candidate !== undefined) return candidate ?? undefined;
|
|
856
|
+
const snapshot = slot.snapshot;
|
|
857
|
+
if (snapshot.instances !== undefined) {
|
|
858
|
+
const instanceCount = snapshot.instances.instanceCount;
|
|
859
|
+
if (instanceCount === 0 || snapshot.localAabb === undefined) {
|
|
860
|
+
this.worldBoundsBySlot[slot.slot] = null;
|
|
861
|
+
return undefined;
|
|
862
|
+
}
|
|
863
|
+
const derived = this.instanceBoundsCache.get({
|
|
864
|
+
worldId: snapshot.worldId,
|
|
865
|
+
entityKey: snapshot.entityKey,
|
|
866
|
+
meshGeneration: fingerprintNumericArray(snapshot.localAabb) ^ (snapshot.assetHandle >>> 0),
|
|
867
|
+
transformGeneration: fingerprintNumericArray(snapshot.transform.world),
|
|
868
|
+
matrixGeneration: snapshot.instances.archVersion,
|
|
869
|
+
meshAabb: snapshot.localAabb,
|
|
870
|
+
entityWorld: snapshot.transform.world,
|
|
871
|
+
transforms: snapshot.instances.transforms,
|
|
872
|
+
});
|
|
873
|
+
candidate = boundsFromArray(derived ?? []);
|
|
874
|
+
} else {
|
|
875
|
+
candidate = worldBounds(snapshot, candidate ?? undefined);
|
|
876
|
+
}
|
|
877
|
+
this.worldBoundsBySlot[slot.slot] = candidate ?? null;
|
|
878
|
+
return candidate;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
cullingWorldBounds(snapshot: RenderableSnapshot): RenderSceneBounds | undefined {
|
|
882
|
+
const slot = this.lookup(snapshot.worldId, snapshot.entityKey);
|
|
883
|
+
return slot === undefined ? worldBounds(snapshot) : this.cullingWorldBoundsAt(slot);
|
|
884
|
+
}
|
|
714
885
|
}
|
|
715
886
|
|
|
716
887
|
export interface PersistentRenderSceneOptions {
|
|
717
888
|
readonly getDevice?: (() => RhiDevice) | undefined;
|
|
718
889
|
readonly onGpuError?: ((error: RhiError) => void) | undefined;
|
|
719
890
|
readonly onSharedRefMutation?: ((worldId: number, handle: number) => void) | undefined;
|
|
891
|
+
readonly configuredQueryBudget?: number | undefined;
|
|
720
892
|
}
|
|
721
893
|
|
|
722
894
|
export interface PersistentGpuDrivenState {
|
|
723
895
|
readonly scene: GpuScene;
|
|
724
896
|
readonly plan: SubmissionPlan;
|
|
725
897
|
readonly slots: readonly RenderSceneSlot[];
|
|
898
|
+
/** Stable renderer-local World keys aligned with RenderSceneSlot.worldId. */
|
|
899
|
+
readonly worldKeys?: readonly number[];
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
export interface CpuDirectViewInput {
|
|
903
|
+
readonly view: ViewKey;
|
|
904
|
+
readonly primitives: readonly PrimitiveKey[];
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
export interface CpuDirectCandidate {
|
|
908
|
+
readonly view: ViewKey;
|
|
909
|
+
readonly primitive: PrimitiveKey;
|
|
910
|
+
readonly candidate: VisibilityCandidate;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* The one renderer-owned submission projection for a display view.
|
|
915
|
+
*
|
|
916
|
+
* Occlusion completion updates the facet asynchronously, so extraction must
|
|
917
|
+
* retain every query candidate while record/prepare consume this projection.
|
|
918
|
+
* Keeping the dispatch reindex here prevents CPU direct and GPU indirect
|
|
919
|
+
* lanes from rebuilding a second visibility map with different identity
|
|
920
|
+
* semantics.
|
|
921
|
+
*/
|
|
922
|
+
export interface PersistentVisibilityProjection {
|
|
923
|
+
readonly renderables: readonly RenderableSnapshot[];
|
|
924
|
+
readonly dispatch: readonly DispatchEntry[];
|
|
925
|
+
readonly activeEntityKeys: ReadonlySet<number>;
|
|
926
|
+
/** Monotonic owner revision for exactly this active-key projection. */
|
|
927
|
+
readonly activeEntityRevision: number;
|
|
928
|
+
readonly suppressed: number;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/** Project one selected candidate per view and primitive from the facet owner. */
|
|
932
|
+
export function projectCpuDirectCandidates(
|
|
933
|
+
facets: VisibilityFacetStore,
|
|
934
|
+
views: readonly CpuDirectViewInput[],
|
|
935
|
+
): readonly CpuDirectCandidate[] {
|
|
936
|
+
const candidates: CpuDirectCandidate[] = [];
|
|
937
|
+
for (const { view, primitives } of views) {
|
|
938
|
+
for (const primitive of primitives) {
|
|
939
|
+
const candidate = facets.getCandidate(view, primitive);
|
|
940
|
+
if (candidate !== undefined) candidates.push({ view, primitive, candidate });
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return candidates;
|
|
726
944
|
}
|
|
727
945
|
|
|
728
946
|
type PersistentGpuSceneInspection = PersistentRenderSceneInspection['gpu'];
|
|
@@ -735,16 +953,29 @@ interface PersistentCompositionEntry {
|
|
|
735
953
|
readonly projection: RenderScene;
|
|
736
954
|
readonly topology: BatchTopology;
|
|
737
955
|
readonly transmissionDemand: PersistentTransmissionDemandProjection;
|
|
738
|
-
readonly
|
|
956
|
+
readonly structure: Pick<
|
|
957
|
+
ExtractedFrame,
|
|
958
|
+
| 'dispatch'
|
|
959
|
+
| 'visibilityStats'
|
|
960
|
+
| 'visibilitySnapshots'
|
|
961
|
+
| 'featureVisibilitySnapshots'
|
|
962
|
+
| 'hiddenEntityReports'
|
|
963
|
+
>;
|
|
964
|
+
/** Stable renderer-local World keys; unlike RenderableSnapshot.worldId they survive reorder. */
|
|
965
|
+
readonly worldKeys: readonly number[];
|
|
966
|
+
readonly readVersions: RenderReadVersion[];
|
|
739
967
|
readonly leaseIdentities: readonly string[];
|
|
968
|
+
readonly transformQueries: readonly GlobalTransformChangeQuery[];
|
|
740
969
|
catalogEpoch: number;
|
|
741
970
|
readonly rigidOnly: boolean;
|
|
742
|
-
|
|
743
|
-
|
|
971
|
+
readonly probeProjection: ProbeBlendSceneProjection;
|
|
972
|
+
temporalDemanded: boolean;
|
|
973
|
+
gpuDrivenSceneRequired: boolean;
|
|
744
974
|
}
|
|
745
975
|
|
|
746
976
|
const RENDER_RELEVANT_COMPONENT_IDS = new Set([
|
|
747
977
|
componentId(Transform),
|
|
978
|
+
componentId(GlobalTransform),
|
|
748
979
|
componentId(ChildOf),
|
|
749
980
|
componentId(MeshFilter),
|
|
750
981
|
componentId(MeshRenderer),
|
|
@@ -755,22 +986,33 @@ const RENDER_RELEVANT_COMPONENT_IDS = new Set([
|
|
|
755
986
|
componentId(Layer),
|
|
756
987
|
componentId(Visibility),
|
|
757
988
|
componentId(Camera),
|
|
989
|
+
componentId(MotionBlur),
|
|
758
990
|
componentId(DirectionalLight),
|
|
991
|
+
componentId(LightProbe),
|
|
759
992
|
componentId(PointLight),
|
|
760
993
|
componentId(PointLightShadow),
|
|
761
994
|
componentId(SpotLight),
|
|
762
995
|
componentId(Skylight),
|
|
763
996
|
componentId(SkyboxBackground),
|
|
764
997
|
componentId(PostProcessParams),
|
|
998
|
+
componentId(ReflectionProbe),
|
|
765
999
|
componentId(VolumetricFog),
|
|
766
1000
|
]);
|
|
767
1001
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1002
|
+
function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQuery {
|
|
1003
|
+
const result = world.query({ read: [GlobalTransform], changed: [GlobalTransform] });
|
|
1004
|
+
if (!result.ok) throw result.error;
|
|
1005
|
+
const query = result.value as GlobalTransformChangeQuery;
|
|
1006
|
+
for (const _span of query.spans().unwrap()) {
|
|
1007
|
+
// The full rebuild consumed these values; establish the observation baseline.
|
|
1008
|
+
}
|
|
1009
|
+
return query;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function frameDemandsTemporal(frame: ExtractedFrame): boolean {
|
|
1013
|
+
const camera = frame.cameras[0];
|
|
1014
|
+
return camera?.antialias === 'taa' || (camera?.motionBlur?.shutterAngle ?? 0) > 0;
|
|
1015
|
+
}
|
|
774
1016
|
|
|
775
1017
|
function cameraFrusta(cameras: readonly CameraSnapshot[]): readonly Float32Array[] {
|
|
776
1018
|
const planes: Float32Array[] = [];
|
|
@@ -807,14 +1049,23 @@ function cameraFrusta(cameras: readonly CameraSnapshot[]): readonly Float32Array
|
|
|
807
1049
|
return planes;
|
|
808
1050
|
}
|
|
809
1051
|
|
|
1052
|
+
let cullProjectedIndexScratch = new Int32Array(0);
|
|
1053
|
+
|
|
810
1054
|
function cullPersistentFrame(
|
|
811
1055
|
frame: ExtractedFrame,
|
|
812
1056
|
candidates: readonly RenderableSnapshot[],
|
|
1057
|
+
boundsOf: (snapshot: RenderableSnapshot, index: number) => RenderSceneBounds | undefined = (
|
|
1058
|
+
snapshot,
|
|
1059
|
+
) => worldBounds(snapshot),
|
|
813
1060
|
): ExtractedFrame {
|
|
814
1061
|
const planes = cameraFrusta(frame.cameras);
|
|
815
1062
|
const visible: RenderableSnapshot[] = [];
|
|
816
|
-
|
|
817
|
-
|
|
1063
|
+
if (cullProjectedIndexScratch.length < candidates.length) {
|
|
1064
|
+
cullProjectedIndexScratch = new Int32Array(candidates.length);
|
|
1065
|
+
}
|
|
1066
|
+
const projectedIndex = cullProjectedIndexScratch;
|
|
1067
|
+
projectedIndex.fill(-1, 0, candidates.length);
|
|
1068
|
+
const worldAabb = box3.create();
|
|
818
1069
|
let total = 0;
|
|
819
1070
|
let culled = 0;
|
|
820
1071
|
|
|
@@ -822,10 +1073,15 @@ function cullPersistentFrame(
|
|
|
822
1073
|
const candidate = candidates[index];
|
|
823
1074
|
if (candidate === undefined) continue;
|
|
824
1075
|
let isVisible = true;
|
|
825
|
-
|
|
1076
|
+
const candidateBounds = boundsOf(candidate, index);
|
|
1077
|
+
if (candidateBounds !== undefined) {
|
|
826
1078
|
total += 1;
|
|
827
|
-
|
|
828
|
-
|
|
1079
|
+
worldAabb[0] = candidateBounds.min[0] ?? 0;
|
|
1080
|
+
worldAabb[1] = candidateBounds.min[1] ?? 0;
|
|
1081
|
+
worldAabb[2] = candidateBounds.min[2] ?? 0;
|
|
1082
|
+
worldAabb[3] = candidateBounds.max[0] ?? 0;
|
|
1083
|
+
worldAabb[4] = candidateBounds.max[1] ?? 0;
|
|
1084
|
+
worldAabb[5] = candidateBounds.max[2] ?? 0;
|
|
829
1085
|
isVisible = planes.length === 0;
|
|
830
1086
|
for (const cameraPlanes of planes) {
|
|
831
1087
|
if (
|
|
@@ -842,7 +1098,12 @@ function cullPersistentFrame(
|
|
|
842
1098
|
continue;
|
|
843
1099
|
}
|
|
844
1100
|
projectedIndex[index] = visible.length;
|
|
845
|
-
|
|
1101
|
+
// The culling candidate is the persistent projection snapshot used for
|
|
1102
|
+
// bounds, while `frame.renderables[index]` may carry frame-owned
|
|
1103
|
+
// attachments (for example the probe blend record). Preserve that
|
|
1104
|
+
// attached renderable in the output; pushing the bare candidate here
|
|
1105
|
+
// silently drops the object-level Probe ABI before record selection.
|
|
1106
|
+
visible.push(frame.renderables[index] ?? candidate);
|
|
846
1107
|
}
|
|
847
1108
|
|
|
848
1109
|
const dispatch: DispatchEntry[] = [];
|
|
@@ -860,8 +1121,33 @@ function cullPersistentFrame(
|
|
|
860
1121
|
};
|
|
861
1122
|
}
|
|
862
1123
|
|
|
1124
|
+
function attachProbeRecords(
|
|
1125
|
+
frame: ExtractedFrame,
|
|
1126
|
+
candidates: RenderableSnapshot[],
|
|
1127
|
+
slots: readonly RenderSceneSlot[],
|
|
1128
|
+
projection: ProbeBlendSceneProjection,
|
|
1129
|
+
): ExtractedFrame {
|
|
1130
|
+
if (!projection.hasRecords()) {
|
|
1131
|
+
return frame.renderables === candidates ? frame : { ...frame, renderables: candidates };
|
|
1132
|
+
}
|
|
1133
|
+
const objectKeys = new Map<string, number>();
|
|
1134
|
+
for (const slot of slots)
|
|
1135
|
+
objectKeys.set(identityKey(slot.worldId, slot.snapshot.entityKey), slot.slot);
|
|
1136
|
+
const renderables = candidates.map((candidate) => {
|
|
1137
|
+
const slot = objectKeys.get(identityKey(candidate.worldId, candidate.entityKey));
|
|
1138
|
+
const record = slot === undefined ? undefined : projection.getRecord(slot);
|
|
1139
|
+
return record === undefined ? candidate : { ...candidate, probeBlendRecord: record };
|
|
1140
|
+
});
|
|
1141
|
+
return { ...frame, renderables };
|
|
1142
|
+
}
|
|
1143
|
+
|
|
863
1144
|
/** Persistent scene projection used by the renderer's multi-World composition path. */
|
|
864
1145
|
export class PersistentRenderScene {
|
|
1146
|
+
private readonly visibilityBudget: VisibilityBudget;
|
|
1147
|
+
private readonly visibilityFacets: VisibilityFacetStore;
|
|
1148
|
+
/** World identity is the author/runtime identity; array position is only a frame routing detail. */
|
|
1149
|
+
private readonly visibilityWorldKeys = new Map<string, number>();
|
|
1150
|
+
private nextVisibilityWorldKey = 0;
|
|
865
1151
|
private readonly materialSnapshotCaches: MaterialSnapshotCachesByWorld = new WeakMap();
|
|
866
1152
|
private fullRebuilds = 0;
|
|
867
1153
|
private worldEntitiesScanned = 0;
|
|
@@ -878,24 +1164,349 @@ export class PersistentRenderScene {
|
|
|
878
1164
|
private temporalCapture:
|
|
879
1165
|
| { readonly projection: RenderScene; readonly capture: RenderSceneSubmissionCapture }
|
|
880
1166
|
| undefined;
|
|
1167
|
+
/** Monotonic token for camera/dynamic frame facts consumed by visibility caches. */
|
|
1168
|
+
private frameEpoch = 0;
|
|
1169
|
+
private visibilityFacetFrame:
|
|
1170
|
+
| {
|
|
1171
|
+
readonly frameEpoch: number;
|
|
1172
|
+
readonly viewId: string;
|
|
1173
|
+
readonly renderables: readonly RenderableSnapshot[];
|
|
1174
|
+
}
|
|
1175
|
+
| undefined;
|
|
1176
|
+
private visibilityProjectionCache:
|
|
1177
|
+
| {
|
|
1178
|
+
readonly frameEpoch: number;
|
|
1179
|
+
readonly drawRevision: number;
|
|
1180
|
+
readonly viewId: string;
|
|
1181
|
+
readonly renderables: readonly RenderableSnapshot[];
|
|
1182
|
+
readonly dispatch: readonly DispatchEntry[];
|
|
1183
|
+
readonly projection: PersistentVisibilityProjection;
|
|
1184
|
+
}
|
|
1185
|
+
| undefined;
|
|
1186
|
+
private visibilityProjectionRevision = 0;
|
|
1187
|
+
private lastActiveEntityKeys: ReadonlySet<number> | undefined;
|
|
1188
|
+
/** Stable renderer-world/entity lookup; rebuilt only with composition topology. */
|
|
1189
|
+
private readonly stableSlotByEntity = new Map<number, RenderSceneSlot>();
|
|
881
1190
|
private pointsLinesInspections: readonly PointsLinesInspection[] = [];
|
|
1191
|
+
private readonly reflectionProbes = new ReflectionProbeProjection();
|
|
882
1192
|
|
|
883
|
-
constructor(private readonly options: PersistentRenderSceneOptions = {}) {
|
|
1193
|
+
constructor(private readonly options: PersistentRenderSceneOptions = {}) {
|
|
1194
|
+
this.visibilityBudget = createVisibilityBudget(options.configuredQueryBudget);
|
|
1195
|
+
this.visibilityFacets = new VisibilityFacetStore(this.visibilityBudget);
|
|
1196
|
+
}
|
|
884
1197
|
|
|
885
1198
|
/** RenderScene owns the per-World material fact cache used during extraction. */
|
|
886
1199
|
materialSnapshotCacheStore(): MaterialSnapshotCachesByWorld {
|
|
887
1200
|
return this.materialSnapshotCaches;
|
|
888
1201
|
}
|
|
889
1202
|
|
|
1203
|
+
/** The sole renderer-owned ViewKey x PrimitiveKey visibility state. */
|
|
1204
|
+
visibilityFacetStore(): VisibilityFacetStore {
|
|
1205
|
+
return this.visibilityFacets;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
visibilityBudgetValue(): VisibilityBudget {
|
|
1209
|
+
return this.visibilityBudget;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
private visibilityWorldKey(world: World | undefined, fallback: number): number {
|
|
1213
|
+
if (world === undefined) return fallback;
|
|
1214
|
+
const existing = this.visibilityWorldKeys.get(world.identity);
|
|
1215
|
+
if (existing !== undefined) return existing;
|
|
1216
|
+
const key = this.nextVisibilityWorldKey;
|
|
1217
|
+
this.nextVisibilityWorldKey += 1;
|
|
1218
|
+
this.visibilityWorldKeys.set(world.identity, key);
|
|
1219
|
+
return key;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
private worldKeyAt(worlds: readonly World[], worldId: number): number {
|
|
1223
|
+
return this.visibilityWorldKey(worlds[worldId], worldId);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/** Stable World routing keys for the current worlds[] frame projection. */
|
|
1227
|
+
visibilityWorldKeysFor(worlds: readonly World[]): readonly number[] {
|
|
1228
|
+
return worlds.map((world, worldId) => this.visibilityWorldKey(world, worldId));
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/** Current retained slots, available to CPU query transport as well as GPU. */
|
|
1232
|
+
compositionSlots(): readonly RenderSceneSlot[] {
|
|
1233
|
+
return this.composition?.projection.slotsSnapshot() ?? [];
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/** Stable renderer-world/entity lookup shared by query and final projection paths. */
|
|
1237
|
+
compositionSlotByStableEntity(): ReadonlyMap<number, RenderSceneSlot> {
|
|
1238
|
+
return this.stableSlotByEntity;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
private stableEntityKey(
|
|
1242
|
+
worlds: readonly World[],
|
|
1243
|
+
renderable: Pick<RenderableSnapshot, 'worldId' | 'entityKey'>,
|
|
1244
|
+
): number {
|
|
1245
|
+
return worldEntityKey(this.worldKeyAt(worlds, renderable.worldId), renderable.entityKey);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/** Feed the facet from extracted renderables using persistent slot identity. */
|
|
1249
|
+
updateVisibilityFacet(
|
|
1250
|
+
worlds: readonly World[],
|
|
1251
|
+
camera: CameraSnapshot | undefined,
|
|
1252
|
+
renderables: readonly RenderableSnapshot[],
|
|
1253
|
+
): void {
|
|
1254
|
+
const world = worlds[camera?.worldId ?? 0];
|
|
1255
|
+
if (world === undefined || camera === undefined) return;
|
|
1256
|
+
const view = viewKey({
|
|
1257
|
+
attachmentId: world.identity,
|
|
1258
|
+
cameraEntity: camera.entityKey ?? 0,
|
|
1259
|
+
viewRole: 'main',
|
|
1260
|
+
viewGeneration: camera.historyVersion ?? 0,
|
|
1261
|
+
});
|
|
1262
|
+
const viewId = viewKeyId(view);
|
|
1263
|
+
const previous = this.visibilityFacetFrame;
|
|
1264
|
+
if (
|
|
1265
|
+
previous?.frameEpoch === this.frameEpoch &&
|
|
1266
|
+
previous.viewId === viewId &&
|
|
1267
|
+
previous.renderables === renderables
|
|
1268
|
+
) {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
this.visibilityFacets.activateView(view);
|
|
1272
|
+
const activePrimitiveIds = new Set<string>();
|
|
1273
|
+
for (const renderable of renderables) {
|
|
1274
|
+
const bounds = renderable.localAabb;
|
|
1275
|
+
if (
|
|
1276
|
+
renderable.lods === undefined ||
|
|
1277
|
+
renderable.lods.length === 0 ||
|
|
1278
|
+
bounds === undefined ||
|
|
1279
|
+
bounds.length < 6 ||
|
|
1280
|
+
[...bounds].some((value) => !Number.isFinite(value))
|
|
1281
|
+
) {
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
const slot = this.stableSlotByEntity.get(this.stableEntityKey(worlds, renderable));
|
|
1285
|
+
if (slot === undefined) continue;
|
|
1286
|
+
const primitive = primitiveKey({
|
|
1287
|
+
attachmentId: world.identity,
|
|
1288
|
+
worldGeneration: this.worldKeyAt(worlds, renderable.worldId),
|
|
1289
|
+
primitiveSlot: slot.slot,
|
|
1290
|
+
slotGeneration: slot.generation,
|
|
1291
|
+
});
|
|
1292
|
+
activePrimitiveIds.add(primitiveKeyId(primitive));
|
|
1293
|
+
this.visibilityFacets.setCandidate(view, primitive, { level: 0, confidence: 1 });
|
|
1294
|
+
}
|
|
1295
|
+
this.visibilityFacets.pruneCandidates(view, activePrimitiveIds);
|
|
1296
|
+
this.visibilityFacetFrame = { frameEpoch: this.frameEpoch, viewId, renderables };
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* Project the current facet into the final primary-raster submission.
|
|
1301
|
+
*
|
|
1302
|
+
* Only renderables carrying authored lower-detail levels are queryable. A
|
|
1303
|
+
* plain mesh therefore remains on the ordinary visibility path, while an
|
|
1304
|
+
* LOD candidate with two accepted zero-sample results is suppressed until
|
|
1305
|
+
* the confidence scheduler requests its bounded re-test. Unknown identity,
|
|
1306
|
+
* invalid bounds, and non-queryable confidence all remain conservatively
|
|
1307
|
+
* visible through `decideVisibility`.
|
|
1308
|
+
*/
|
|
1309
|
+
projectVisibility(
|
|
1310
|
+
worlds: readonly World[],
|
|
1311
|
+
camera: CameraSnapshot | undefined,
|
|
1312
|
+
renderables: readonly RenderableSnapshot[],
|
|
1313
|
+
dispatch: readonly DispatchEntry[],
|
|
1314
|
+
): PersistentVisibilityProjection {
|
|
1315
|
+
if (camera === undefined) {
|
|
1316
|
+
const activeEntityKeys = new Set(
|
|
1317
|
+
renderables.map((renderable) => this.stableEntityKey(worlds, renderable)),
|
|
1318
|
+
);
|
|
1319
|
+
return {
|
|
1320
|
+
renderables,
|
|
1321
|
+
dispatch,
|
|
1322
|
+
activeEntityKeys,
|
|
1323
|
+
activeEntityRevision: this.activeEntityRevision(activeEntityKeys),
|
|
1324
|
+
suppressed: 0,
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
const cameraWorld = worlds[camera.worldId ?? 0];
|
|
1328
|
+
if (cameraWorld === undefined) {
|
|
1329
|
+
const activeEntityKeys = new Set(
|
|
1330
|
+
renderables.map((renderable) => this.stableEntityKey(worlds, renderable)),
|
|
1331
|
+
);
|
|
1332
|
+
return {
|
|
1333
|
+
renderables,
|
|
1334
|
+
dispatch,
|
|
1335
|
+
activeEntityKeys,
|
|
1336
|
+
activeEntityRevision: this.activeEntityRevision(activeEntityKeys),
|
|
1337
|
+
suppressed: 0,
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
const view = viewKey({
|
|
1341
|
+
attachmentId: cameraWorld.identity,
|
|
1342
|
+
cameraEntity: camera.entityKey ?? 0,
|
|
1343
|
+
viewRole: 'main',
|
|
1344
|
+
viewGeneration: camera.historyVersion ?? 0,
|
|
1345
|
+
});
|
|
1346
|
+
const viewId = viewKeyId(view);
|
|
1347
|
+
const cached = this.visibilityProjectionCache;
|
|
1348
|
+
if (
|
|
1349
|
+
cached?.frameEpoch === this.frameEpoch &&
|
|
1350
|
+
cached.drawRevision === this.visibilityFacets.drawRevisionValue &&
|
|
1351
|
+
cached.viewId === viewId &&
|
|
1352
|
+
cached.renderables === renderables &&
|
|
1353
|
+
cached.dispatch === dispatch
|
|
1354
|
+
) {
|
|
1355
|
+
return cached.projection;
|
|
1356
|
+
}
|
|
1357
|
+
const projectedIndex = new Int32Array(renderables.length);
|
|
1358
|
+
projectedIndex.fill(-1);
|
|
1359
|
+
const projected: RenderableSnapshot[] = [];
|
|
1360
|
+
const activeEntityKeys = new Set<number>();
|
|
1361
|
+
let suppressed = 0;
|
|
1362
|
+
for (let index = 0; index < renderables.length; index += 1) {
|
|
1363
|
+
const renderable = renderables[index];
|
|
1364
|
+
if (renderable === undefined) continue;
|
|
1365
|
+
const slot = this.stableSlotByEntity.get(this.stableEntityKey(worlds, renderable));
|
|
1366
|
+
let draw = true;
|
|
1367
|
+
if (slot !== undefined && (renderable.lods?.length ?? 0) > 0) {
|
|
1368
|
+
const primitive = primitiveKey({
|
|
1369
|
+
attachmentId: cameraWorld.identity,
|
|
1370
|
+
worldGeneration: this.worldKeyAt(worlds, renderable.worldId),
|
|
1371
|
+
primitiveSlot: slot.slot,
|
|
1372
|
+
slotGeneration: slot.generation,
|
|
1373
|
+
});
|
|
1374
|
+
const bounds = renderable.localAabb;
|
|
1375
|
+
const validBounds =
|
|
1376
|
+
bounds !== undefined &&
|
|
1377
|
+
bounds.length >= 6 &&
|
|
1378
|
+
[...bounds].every((value) => Number.isFinite(value));
|
|
1379
|
+
draw = decideVisibility({
|
|
1380
|
+
authorVisible: true,
|
|
1381
|
+
validBounds,
|
|
1382
|
+
frustumVisible: true,
|
|
1383
|
+
lodReady: true,
|
|
1384
|
+
occlusion: this.visibilityFacets.getConfidence(view, primitive),
|
|
1385
|
+
lane: 'gpu',
|
|
1386
|
+
}).draw;
|
|
1387
|
+
}
|
|
1388
|
+
if (!draw) {
|
|
1389
|
+
suppressed += 1;
|
|
1390
|
+
continue;
|
|
1391
|
+
}
|
|
1392
|
+
projectedIndex[index] = projected.length;
|
|
1393
|
+
projected.push(renderable);
|
|
1394
|
+
activeEntityKeys.add(this.stableEntityKey(worlds, renderable));
|
|
1395
|
+
}
|
|
1396
|
+
const projectedDispatch: DispatchEntry[] = [];
|
|
1397
|
+
for (const entry of dispatch) {
|
|
1398
|
+
const renderableIndex = projectedIndex[entry.renderableIndex];
|
|
1399
|
+
if (renderableIndex === undefined || renderableIndex < 0) continue;
|
|
1400
|
+
projectedDispatch.push({ ...entry, renderableIndex });
|
|
1401
|
+
}
|
|
1402
|
+
const projection = {
|
|
1403
|
+
renderables: Object.freeze(projected),
|
|
1404
|
+
dispatch: Object.freeze(projectedDispatch),
|
|
1405
|
+
activeEntityKeys,
|
|
1406
|
+
activeEntityRevision: this.activeEntityRevision(activeEntityKeys),
|
|
1407
|
+
suppressed,
|
|
1408
|
+
};
|
|
1409
|
+
this.visibilityProjectionCache = {
|
|
1410
|
+
frameEpoch: this.frameEpoch,
|
|
1411
|
+
drawRevision: this.visibilityFacets.drawRevisionValue,
|
|
1412
|
+
viewId,
|
|
1413
|
+
renderables,
|
|
1414
|
+
dispatch,
|
|
1415
|
+
projection,
|
|
1416
|
+
};
|
|
1417
|
+
return projection;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* Advance the projection revision only when the active stable-entity set
|
|
1422
|
+
* changes. Camera fingerprints and scene-plan identity cover view/topology
|
|
1423
|
+
* changes; keeping this token set-stable preserves the GPU filtered-plan
|
|
1424
|
+
* cache on ordinary no-change frames.
|
|
1425
|
+
*/
|
|
1426
|
+
private activeEntityRevision(activeEntityKeys: ReadonlySet<number>): number {
|
|
1427
|
+
const previous = this.lastActiveEntityKeys;
|
|
1428
|
+
if (
|
|
1429
|
+
previous === undefined ||
|
|
1430
|
+
previous.size !== activeEntityKeys.size ||
|
|
1431
|
+
[...previous].some((key) => !activeEntityKeys.has(key))
|
|
1432
|
+
) {
|
|
1433
|
+
this.lastActiveEntityKeys = new Set(activeEntityKeys);
|
|
1434
|
+
this.visibilityProjectionRevision += 1;
|
|
1435
|
+
}
|
|
1436
|
+
return this.visibilityProjectionRevision;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
private invalidateActiveEntityRevision(): void {
|
|
1440
|
+
this.lastActiveEntityKeys = undefined;
|
|
1441
|
+
this.visibilityProjectionRevision += 1;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
/** Project probe facts and per-primitive selection into the persistent scene owner. */
|
|
1445
|
+
projectReflectionProbes(probes: readonly ReflectionProbeFact[]): void {
|
|
1446
|
+
const projection = this.composition?.projection;
|
|
1447
|
+
if (projection === undefined) return;
|
|
1448
|
+
if (probes.length === 0) {
|
|
1449
|
+
this.reflectionProbes.update([], []);
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
const primitives = projection
|
|
1453
|
+
.slotsSnapshot()
|
|
1454
|
+
.map((slot) => {
|
|
1455
|
+
const resolved = projection.cullingWorldBoundsAt(slot);
|
|
1456
|
+
if (resolved === undefined || resolved === null) return undefined;
|
|
1457
|
+
return {
|
|
1458
|
+
worldId: slot.worldId,
|
|
1459
|
+
entityKey: slot.entityKey,
|
|
1460
|
+
renderableKey: `${slot.worldId}:${slot.entityKey}`,
|
|
1461
|
+
center: [
|
|
1462
|
+
(resolved.min[0] + resolved.max[0]) / 2,
|
|
1463
|
+
(resolved.min[1] + resolved.max[1]) / 2,
|
|
1464
|
+
(resolved.min[2] + resolved.max[2]) / 2,
|
|
1465
|
+
] as [number, number, number],
|
|
1466
|
+
};
|
|
1467
|
+
})
|
|
1468
|
+
.filter((primitive): primitive is NonNullable<typeof primitive> => primitive !== undefined);
|
|
1469
|
+
this.reflectionProbes.update(probes, primitives);
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
reflectionProbeSelection(worldId: number, entityKey: number): ReflectionProbeSelectionResult {
|
|
1473
|
+
return this.reflectionProbes.selection(worldId, entityKey);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
reflectionProbeProjection(): ReturnType<ReflectionProbeProjection['snapshot']> {
|
|
1477
|
+
return this.reflectionProbes.snapshot();
|
|
1478
|
+
}
|
|
1479
|
+
|
|
890
1480
|
extractComposition(
|
|
891
1481
|
worlds: readonly World[],
|
|
892
1482
|
owner: { readonly cameraOwner: number; readonly resourceOwner: number },
|
|
893
1483
|
catalogEpoch: number,
|
|
894
|
-
buildCandidateFrame: () => ExtractedFrame,
|
|
1484
|
+
buildCandidateFrame: (renderables: 'full' | 'none') => ExtractedFrame,
|
|
895
1485
|
leases?: readonly RenderReadLease[],
|
|
896
1486
|
): ExtractedFrame {
|
|
897
1487
|
this.worldEntitiesScanned = 0;
|
|
898
1488
|
const entry = this.composition;
|
|
1489
|
+
const sameWorldSet =
|
|
1490
|
+
entry !== undefined &&
|
|
1491
|
+
entry.worlds.length === worlds.length &&
|
|
1492
|
+
entry.worlds.every((world) => worlds.includes(world));
|
|
1493
|
+
const worldOrderChanged =
|
|
1494
|
+
entry !== undefined &&
|
|
1495
|
+
sameWorldSet &&
|
|
1496
|
+
entry.worlds.some((world, index) => world !== worlds[index]);
|
|
1497
|
+
const leaseSetPreserved =
|
|
1498
|
+
entry !== undefined &&
|
|
1499
|
+
leases !== undefined &&
|
|
1500
|
+
leases.length === worlds.length &&
|
|
1501
|
+
entry.leaseIdentities.length === leases.length &&
|
|
1502
|
+
entry.leaseIdentities.every((identity) =>
|
|
1503
|
+
leases.some((lease) => lease.worldIdentity === identity),
|
|
1504
|
+
);
|
|
1505
|
+
const preserveVisibilityOnReorder =
|
|
1506
|
+
entry !== undefined &&
|
|
1507
|
+
worldOrderChanged &&
|
|
1508
|
+
entry.catalogEpoch === catalogEpoch &&
|
|
1509
|
+
leaseSetPreserved;
|
|
899
1510
|
if (
|
|
900
1511
|
entry === undefined ||
|
|
901
1512
|
entry.catalogEpoch !== catalogEpoch ||
|
|
@@ -906,13 +1517,19 @@ export class PersistentRenderScene {
|
|
|
906
1517
|
leases === undefined ||
|
|
907
1518
|
leases.length !== worlds.length
|
|
908
1519
|
) {
|
|
909
|
-
return this.rebuildComposition(
|
|
1520
|
+
return this.rebuildComposition(
|
|
1521
|
+
worlds,
|
|
1522
|
+
owner,
|
|
1523
|
+
catalogEpoch,
|
|
1524
|
+
buildCandidateFrame,
|
|
1525
|
+
leases,
|
|
1526
|
+
preserveVisibilityOnReorder,
|
|
1527
|
+
);
|
|
910
1528
|
}
|
|
911
1529
|
if (!entry.rigidOnly) {
|
|
912
1530
|
return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
913
1531
|
}
|
|
914
1532
|
|
|
915
|
-
const operations: RenderSceneOperation[] = [];
|
|
916
1533
|
let requiresRebuild = false;
|
|
917
1534
|
for (let worldId = 0; worldId < worlds.length; worldId += 1) {
|
|
918
1535
|
const world = worlds[worldId];
|
|
@@ -922,14 +1539,19 @@ export class PersistentRenderScene {
|
|
|
922
1539
|
requiresRebuild = true;
|
|
923
1540
|
break;
|
|
924
1541
|
}
|
|
1542
|
+
const version = entry.readVersions[worldId];
|
|
1543
|
+
if (version === undefined) {
|
|
1544
|
+
requiresRebuild = true;
|
|
1545
|
+
break;
|
|
1546
|
+
}
|
|
925
1547
|
let read: ReturnType<RenderReadLease['readChanges']>;
|
|
926
1548
|
try {
|
|
927
|
-
read = lease.readChanges(
|
|
1549
|
+
read = lease.readChanges(version);
|
|
928
1550
|
} catch {
|
|
929
1551
|
requiresRebuild = true;
|
|
930
1552
|
break;
|
|
931
1553
|
}
|
|
932
|
-
if (read.status
|
|
1554
|
+
if (read.status !== 'ok') {
|
|
933
1555
|
requiresRebuild = true;
|
|
934
1556
|
break;
|
|
935
1557
|
}
|
|
@@ -942,119 +1564,86 @@ export class PersistentRenderScene {
|
|
|
942
1564
|
requiresRebuild = true;
|
|
943
1565
|
break;
|
|
944
1566
|
}
|
|
945
|
-
const
|
|
946
|
-
|
|
1567
|
+
for (const changedComponentId of changes.changedComponentIds) {
|
|
1568
|
+
if (!RENDER_RELEVANT_COMPONENT_IDS.has(changedComponentId)) continue;
|
|
947
1569
|
if (
|
|
948
|
-
|
|
949
|
-
|
|
1570
|
+
changedComponentId !== componentId(Transform) &&
|
|
1571
|
+
changedComponentId !== componentId(GlobalTransform)
|
|
950
1572
|
) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
}
|
|
954
|
-
for (const record of changes.records) {
|
|
955
|
-
if (
|
|
956
|
-
record.kind !== 'entity-removed' &&
|
|
957
|
-
record.componentId !== undefined &&
|
|
958
|
-
!RENDER_RELEVANT_COMPONENT_IDS.has(record.componentId)
|
|
959
|
-
) {
|
|
960
|
-
continue;
|
|
961
|
-
}
|
|
962
|
-
const authoredTransform =
|
|
963
|
-
record.kind === 'component-changed' && record.componentId === componentId(Transform);
|
|
964
|
-
const derivedTransform =
|
|
965
|
-
record.kind === 'derived-component-changed' &&
|
|
966
|
-
record.componentId === componentId(Transform);
|
|
967
|
-
if (derivedTransform) continue;
|
|
968
|
-
if (authoredTransform && derivedTransforms.has(record.entity as EntityHandle)) continue;
|
|
969
|
-
if (
|
|
970
|
-
(authoredTransform || record.componentId === componentId(Transform)) &&
|
|
971
|
-
entry.projection.has(worldId, record.entity)
|
|
972
|
-
) {
|
|
973
|
-
const worldView = readTransformWorld(world, record.entity as EntityHandle);
|
|
974
|
-
if (worldView === undefined) {
|
|
975
|
-
requiresRebuild = true;
|
|
976
|
-
break;
|
|
977
|
-
}
|
|
978
|
-
operations.push({
|
|
979
|
-
kind: 'update-transform',
|
|
980
|
-
worldId,
|
|
981
|
-
entityKey: record.entity,
|
|
982
|
-
world: new Float32Array(worldView),
|
|
983
|
-
});
|
|
984
|
-
continue;
|
|
1573
|
+
requiresRebuild = true;
|
|
1574
|
+
break;
|
|
985
1575
|
}
|
|
986
|
-
requiresRebuild = true;
|
|
987
|
-
break;
|
|
988
1576
|
}
|
|
989
|
-
if (
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
}
|
|
1005
|
-
// Camera snapshots are rebuilt from the current candidate frame and
|
|
1006
|
-
// drive culling there; moving a camera must not invalidate the
|
|
1007
|
-
// retained scene projection or recreate its GPU tables.
|
|
1008
|
-
if (
|
|
1009
|
-
!world.hasComponent(entity, Camera) &&
|
|
1010
|
-
NON_RENDERABLE_TRANSFORM_CONSUMERS.some((component) =>
|
|
1011
|
-
world.hasComponent(entity, component),
|
|
1012
|
-
)
|
|
1013
|
-
) {
|
|
1014
|
-
requiresRebuild = true;
|
|
1015
|
-
break;
|
|
1016
|
-
}
|
|
1577
|
+
if (requiresRebuild) break;
|
|
1578
|
+
entry.readVersions[worldId] = read.version;
|
|
1579
|
+
}
|
|
1580
|
+
if (requiresRebuild) {
|
|
1581
|
+
return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
1582
|
+
}
|
|
1583
|
+
const transformSpans: WorldTransformSpan[] = [];
|
|
1584
|
+
for (let worldId = 0; worldId < entry.transformQueries.length; worldId += 1) {
|
|
1585
|
+
const query = entry.transformQueries[worldId];
|
|
1586
|
+
if (query === undefined) continue;
|
|
1587
|
+
for (const span of query.spans().unwrap()) {
|
|
1588
|
+
const changedWorlds = span.get(GlobalTransform).world;
|
|
1589
|
+
if (!(changedWorlds instanceof Float32Array)) {
|
|
1590
|
+
requiresRebuild = true;
|
|
1591
|
+
break;
|
|
1017
1592
|
}
|
|
1593
|
+
transformSpans.push({ worldId, entities: span.entities, worlds: changedWorlds });
|
|
1018
1594
|
}
|
|
1019
1595
|
if (requiresRebuild) break;
|
|
1020
|
-
entry.readCursors[worldId] = read.cursor;
|
|
1021
1596
|
}
|
|
1022
1597
|
if (requiresRebuild) {
|
|
1023
1598
|
return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
1024
1599
|
}
|
|
1025
|
-
if (
|
|
1600
|
+
if (transformSpans.length === 0) {
|
|
1026
1601
|
this.noChangeFrames += 1;
|
|
1027
1602
|
this.syncGpuOwner(entry.token, entry.projection);
|
|
1028
1603
|
return this.deriveFramePlan(entry, buildCandidateFrame);
|
|
1029
1604
|
}
|
|
1030
|
-
const delta = entry.projection.
|
|
1605
|
+
const delta = entry.projection.applyTransformSpans(transformSpans);
|
|
1031
1606
|
entry.topology.apply(delta);
|
|
1032
1607
|
this.syncGpuOwner(entry.token, entry.projection, delta);
|
|
1033
1608
|
this.deltaFrames += 1;
|
|
1034
|
-
this.transformUpdates +=
|
|
1609
|
+
this.transformUpdates += delta.updated;
|
|
1035
1610
|
return this.deriveFramePlan(entry, buildCandidateFrame);
|
|
1036
1611
|
}
|
|
1037
1612
|
|
|
1038
1613
|
invalidate(): void {
|
|
1039
1614
|
this.composition = undefined;
|
|
1040
1615
|
this.temporalCapture = undefined;
|
|
1616
|
+
this.visibilityFacets.clear();
|
|
1617
|
+
this.invalidateActiveEntityRevision();
|
|
1618
|
+
this.visibilityFacetFrame = undefined;
|
|
1619
|
+
this.visibilityProjectionCache = undefined;
|
|
1620
|
+
this.stableSlotByEntity.clear();
|
|
1041
1621
|
this.lastResyncReason = 'explicit-invalidate';
|
|
1042
1622
|
}
|
|
1043
1623
|
|
|
1044
|
-
|
|
1624
|
+
setCompositionGpuSceneDemand(sceneRowsRequired: boolean): void {
|
|
1045
1625
|
const entry = this.composition;
|
|
1046
1626
|
if (entry === undefined) return;
|
|
1047
|
-
entry.
|
|
1048
|
-
entry.gpuDrivenOwnsAll = ownsAll;
|
|
1627
|
+
entry.gpuDrivenSceneRequired = sceneRowsRequired;
|
|
1049
1628
|
}
|
|
1050
1629
|
|
|
1051
1630
|
detach(world: World): void {
|
|
1631
|
+
this.visibilityFacets.detachAttachment(world.identity);
|
|
1052
1632
|
this.materialSnapshotCaches.delete(world);
|
|
1053
1633
|
const detachedComposition = this.composition?.worlds.includes(world)
|
|
1054
1634
|
? this.composition
|
|
1055
1635
|
: undefined;
|
|
1056
1636
|
if (detachedComposition !== undefined) this.composition = undefined;
|
|
1057
1637
|
if (detachedComposition !== undefined) this.temporalCapture = undefined;
|
|
1638
|
+
if (detachedComposition !== undefined) {
|
|
1639
|
+
this.visibilityFacets.clear();
|
|
1640
|
+
this.invalidateActiveEntityRevision();
|
|
1641
|
+
}
|
|
1642
|
+
if (detachedComposition !== undefined) {
|
|
1643
|
+
this.visibilityFacetFrame = undefined;
|
|
1644
|
+
this.visibilityProjectionCache = undefined;
|
|
1645
|
+
this.stableSlotByEntity.clear();
|
|
1646
|
+
}
|
|
1058
1647
|
if (this.gpuOwner !== world && this.gpuOwner !== detachedComposition?.token) return;
|
|
1059
1648
|
this.gpuScene?.dispose();
|
|
1060
1649
|
this.gpuScene = undefined;
|
|
@@ -1065,7 +1654,7 @@ export class PersistentRenderScene {
|
|
|
1065
1654
|
|
|
1066
1655
|
/** Drop only device-owned tables; the CPU projection remains the recovery authority. */
|
|
1067
1656
|
resetGpuForRecover(): void {
|
|
1068
|
-
if (this.composition !== undefined) this.composition.
|
|
1657
|
+
if (this.composition !== undefined) this.composition.gpuDrivenSceneRequired = true;
|
|
1069
1658
|
this.gpuScene = undefined;
|
|
1070
1659
|
this.gpuOwner = undefined;
|
|
1071
1660
|
this.gpuDevice = undefined;
|
|
@@ -1088,6 +1677,11 @@ export class PersistentRenderScene {
|
|
|
1088
1677
|
this.gpuStatus = 'inactive';
|
|
1089
1678
|
this.composition = undefined;
|
|
1090
1679
|
this.temporalCapture = undefined;
|
|
1680
|
+
this.visibilityFacets.clear();
|
|
1681
|
+
this.invalidateActiveEntityRevision();
|
|
1682
|
+
this.visibilityFacetFrame = undefined;
|
|
1683
|
+
this.visibilityProjectionCache = undefined;
|
|
1684
|
+
this.stableSlotByEntity.clear();
|
|
1091
1685
|
this.pointsLinesInspections = [];
|
|
1092
1686
|
}
|
|
1093
1687
|
|
|
@@ -1102,6 +1696,7 @@ export class PersistentRenderScene {
|
|
|
1102
1696
|
transformUpdates: this.transformUpdates,
|
|
1103
1697
|
lastResyncReason: this.lastResyncReason,
|
|
1104
1698
|
projectionRecords: entry?.projection.inspect().records.length ?? 0,
|
|
1699
|
+
...(entry === undefined ? {} : { probeBlend: entry.probeProjection.inspect() }),
|
|
1105
1700
|
topology:
|
|
1106
1701
|
entry?.topology.inspect() ??
|
|
1107
1702
|
({
|
|
@@ -1126,12 +1721,15 @@ export class PersistentRenderScene {
|
|
|
1126
1721
|
scene: this.gpuScene,
|
|
1127
1722
|
plan: entry.topology.plan(),
|
|
1128
1723
|
slots: entry.projection.slotsSnapshot(),
|
|
1724
|
+
worldKeys: entry.worldKeys,
|
|
1129
1725
|
};
|
|
1130
1726
|
}
|
|
1131
1727
|
|
|
1132
1728
|
/** Publish renderer-owned previous transforms after queue submission. */
|
|
1133
1729
|
commitTemporalFrame(): Result<void, RhiError> {
|
|
1134
|
-
|
|
1730
|
+
// Previous transforms are consumed only by TAA and motion blur. Ordinary
|
|
1731
|
+
// frames must not copy and upload the complete GPU transform table.
|
|
1732
|
+
const gpuCommit = this.gpuScene?.commitTemporalFrame(this.temporalCapture !== undefined);
|
|
1135
1733
|
if (gpuCommit !== undefined && !gpuCommit.ok) return gpuCommit;
|
|
1136
1734
|
const temporalCapture = this.temporalCapture;
|
|
1137
1735
|
if (temporalCapture !== undefined) {
|
|
@@ -1183,14 +1781,72 @@ export class PersistentRenderScene {
|
|
|
1183
1781
|
worlds: readonly World[],
|
|
1184
1782
|
owner: { readonly cameraOwner: number; readonly resourceOwner: number },
|
|
1185
1783
|
catalogEpoch: number,
|
|
1186
|
-
buildCandidateFrame: () => ExtractedFrame,
|
|
1784
|
+
buildCandidateFrame: (renderables: 'full' | 'none') => ExtractedFrame,
|
|
1187
1785
|
leases?: readonly RenderReadLease[],
|
|
1786
|
+
preserveVisibilityOnReorder = false,
|
|
1188
1787
|
): ExtractedFrame {
|
|
1189
|
-
const
|
|
1788
|
+
const previousEntry = this.composition;
|
|
1789
|
+
const candidateFrame = buildCandidateFrame('full');
|
|
1190
1790
|
this.temporalCapture = undefined;
|
|
1191
1791
|
this.worldEntitiesScanned = candidateFrame.renderables.length;
|
|
1792
|
+
const worldKeys = this.visibilityWorldKeysFor(worlds);
|
|
1793
|
+
if (!preserveVisibilityOnReorder) {
|
|
1794
|
+
this.visibilityFacets.clear();
|
|
1795
|
+
this.invalidateActiveEntityRevision();
|
|
1796
|
+
} else if (previousEntry !== undefined) {
|
|
1797
|
+
const previousKeys = new Set(
|
|
1798
|
+
previousEntry.projection
|
|
1799
|
+
.slotsSnapshot()
|
|
1800
|
+
.map((slot) =>
|
|
1801
|
+
worldEntityKey(previousEntry.worldKeys[slot.worldId] ?? slot.worldId, slot.entityKey),
|
|
1802
|
+
),
|
|
1803
|
+
);
|
|
1804
|
+
const currentKeys = new Set(
|
|
1805
|
+
candidateFrame.renderables.map((renderable) =>
|
|
1806
|
+
worldEntityKey(worldKeys[renderable.worldId] ?? renderable.worldId, renderable.entityKey),
|
|
1807
|
+
),
|
|
1808
|
+
);
|
|
1809
|
+
if (
|
|
1810
|
+
previousKeys.size !== currentKeys.size ||
|
|
1811
|
+
[...previousKeys].some((key) => !currentKeys.has(key))
|
|
1812
|
+
) {
|
|
1813
|
+
this.visibilityFacets.clear();
|
|
1814
|
+
this.invalidateActiveEntityRevision();
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
// Slot allocation is renderer-owned identity. Sort by stable World identity
|
|
1818
|
+
// before allocating so a worlds[] reorder does not silently change a
|
|
1819
|
+
// primitive's slot/generation. Dispatch is remapped below to the sorted
|
|
1820
|
+
// renderable projection and remains frame-order independent.
|
|
1821
|
+
const sorted = candidateFrame.renderables
|
|
1822
|
+
.map((snapshot, sourceIndex) => ({ snapshot, sourceIndex }))
|
|
1823
|
+
.sort((left, right) => {
|
|
1824
|
+
const leftWorld = worldKeys[left.snapshot.worldId] ?? left.snapshot.worldId;
|
|
1825
|
+
const rightWorld = worldKeys[right.snapshot.worldId] ?? right.snapshot.worldId;
|
|
1826
|
+
return leftWorld - rightWorld || left.snapshot.entityKey - right.snapshot.entityKey;
|
|
1827
|
+
});
|
|
1828
|
+
const sortedRenderables = sorted.map(({ snapshot }) => snapshot);
|
|
1829
|
+
const renderableIndex = new Int32Array(candidateFrame.renderables.length);
|
|
1830
|
+
for (let index = 0; index < sorted.length; index += 1) {
|
|
1831
|
+
const sourceIndex = sorted[index]?.sourceIndex;
|
|
1832
|
+
if (sourceIndex !== undefined) renderableIndex[sourceIndex] = index;
|
|
1833
|
+
}
|
|
1834
|
+
const sortedDispatch = candidateFrame.dispatch.flatMap((entry) => {
|
|
1835
|
+
const index = renderableIndex[entry.renderableIndex];
|
|
1836
|
+
return index === undefined ? [] : [{ ...entry, renderableIndex: index }];
|
|
1837
|
+
});
|
|
1838
|
+
const stableCandidateFrame = {
|
|
1839
|
+
...candidateFrame,
|
|
1840
|
+
renderables: sortedRenderables,
|
|
1841
|
+
dispatch: sortedDispatch,
|
|
1842
|
+
};
|
|
1192
1843
|
const projection = new RenderScene();
|
|
1193
|
-
projection.reset(
|
|
1844
|
+
projection.reset(sortedRenderables);
|
|
1845
|
+
this.stableSlotByEntity.clear();
|
|
1846
|
+
for (const slot of projection.slotsSnapshot()) {
|
|
1847
|
+
const stableWorld = worldKeys[slot.worldId] ?? slot.worldId;
|
|
1848
|
+
this.stableSlotByEntity.set(worldEntityKey(stableWorld, slot.entityKey), slot);
|
|
1849
|
+
}
|
|
1194
1850
|
const transmissionDemand = new PersistentTransmissionDemandProjection();
|
|
1195
1851
|
transmissionDemand.apply(transmissionDemandOperations(candidateFrame.renderables));
|
|
1196
1852
|
const topology = new BatchTopology();
|
|
@@ -1204,27 +1860,69 @@ export class PersistentRenderScene {
|
|
|
1204
1860
|
projection,
|
|
1205
1861
|
topology,
|
|
1206
1862
|
transmissionDemand,
|
|
1207
|
-
|
|
1863
|
+
structure: {
|
|
1864
|
+
dispatch: stableCandidateFrame.dispatch,
|
|
1865
|
+
visibilityStats: stableCandidateFrame.visibilityStats,
|
|
1866
|
+
visibilitySnapshots: stableCandidateFrame.visibilitySnapshots,
|
|
1867
|
+
featureVisibilitySnapshots: stableCandidateFrame.featureVisibilitySnapshots,
|
|
1868
|
+
hiddenEntityReports: stableCandidateFrame.hiddenEntityReports,
|
|
1869
|
+
},
|
|
1870
|
+
worldKeys,
|
|
1871
|
+
readVersions:
|
|
1208
1872
|
leases === undefined || leases.length !== worlds.length
|
|
1209
1873
|
? []
|
|
1210
|
-
: leases.map((lease) => lease.
|
|
1874
|
+
: leases.map((lease) => lease.captureVersion()),
|
|
1211
1875
|
leaseIdentities:
|
|
1212
1876
|
leases === undefined || leases.length !== worlds.length
|
|
1213
1877
|
? []
|
|
1214
1878
|
: leases.map((lease) => lease.worldIdentity),
|
|
1879
|
+
transformQueries:
|
|
1880
|
+
leases === undefined || leases.length !== worlds.length
|
|
1881
|
+
? []
|
|
1882
|
+
: worlds.map(createGlobalTransformChangeQuery),
|
|
1215
1883
|
catalogEpoch,
|
|
1216
1884
|
rigidOnly: candidateFrame.renderables.every((renderable) => renderable.skin === undefined),
|
|
1217
|
-
|
|
1218
|
-
|
|
1885
|
+
probeProjection: new ProbeBlendSceneProjection(),
|
|
1886
|
+
temporalDemanded: frameDemandsTemporal(candidateFrame),
|
|
1887
|
+
gpuDrivenSceneRequired: true,
|
|
1219
1888
|
};
|
|
1220
1889
|
this.composition = entry;
|
|
1890
|
+
projection.setTemporalTracking(entry.temporalDemanded);
|
|
1891
|
+
// The projection must be installed before deriving probe primitives. On
|
|
1892
|
+
// the first composition rebuild `projectReflectionProbes` otherwise sees
|
|
1893
|
+
// no composition and silently drops every per-renderable selection,
|
|
1894
|
+
// leaving the producer on its neutral/no-demand path until an unrelated
|
|
1895
|
+
// later rebuild.
|
|
1896
|
+
this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
|
|
1897
|
+
this.projectProbes(entry, candidateFrame);
|
|
1221
1898
|
this.syncGpuOwner(token, projection, undefined, true);
|
|
1222
1899
|
this.fullRebuilds += 1;
|
|
1223
1900
|
this.lastResyncReason = 'attach';
|
|
1224
|
-
const
|
|
1225
|
-
|
|
1226
|
-
.map((snapshot) => projection.withTemporal(snapshot))
|
|
1227
|
-
|
|
1901
|
+
const materialized = projection.materialize();
|
|
1902
|
+
const candidates = entry.temporalDemanded
|
|
1903
|
+
? materialized.map((snapshot) => projection.withTemporal(snapshot))
|
|
1904
|
+
: materialized;
|
|
1905
|
+
const frameWithProbes = attachProbeRecords(
|
|
1906
|
+
{ ...stableCandidateFrame, renderables: candidates },
|
|
1907
|
+
candidates,
|
|
1908
|
+
projection.slotsSnapshot(),
|
|
1909
|
+
entry.probeProjection,
|
|
1910
|
+
);
|
|
1911
|
+
const slots = projection.slotsSnapshot();
|
|
1912
|
+
const result = cullPersistentFrame(
|
|
1913
|
+
frameWithProbes,
|
|
1914
|
+
frameWithProbes.renderables,
|
|
1915
|
+
(snapshot, index) => {
|
|
1916
|
+
const slot = slots[index];
|
|
1917
|
+
return slot === undefined
|
|
1918
|
+
? projection.cullingWorldBounds(snapshot)
|
|
1919
|
+
: projection.cullingWorldBoundsAt(slot);
|
|
1920
|
+
},
|
|
1921
|
+
);
|
|
1922
|
+
this.frameEpoch += 1;
|
|
1923
|
+
this.visibilityFacetFrame = undefined;
|
|
1924
|
+
this.visibilityProjectionCache = undefined;
|
|
1925
|
+
return result;
|
|
1228
1926
|
}
|
|
1229
1927
|
|
|
1230
1928
|
/** Internal topology fact; the renderer does not expose demand as public API. */
|
|
@@ -1239,17 +1937,89 @@ export class PersistentRenderScene {
|
|
|
1239
1937
|
|
|
1240
1938
|
/**
|
|
1241
1939
|
* Derive an ephemeral frame plan from persistent scene facts. The scene
|
|
1242
|
-
* retains identity/topology/
|
|
1940
|
+
* retains identity/topology/read versions; no prior frame object is an authority.
|
|
1243
1941
|
*/
|
|
1244
1942
|
private deriveFramePlan(
|
|
1245
1943
|
entry: PersistentCompositionEntry,
|
|
1246
|
-
buildCandidateFrame: () => ExtractedFrame,
|
|
1944
|
+
buildCandidateFrame: (renderables: 'full' | 'none') => ExtractedFrame,
|
|
1247
1945
|
): ExtractedFrame {
|
|
1248
|
-
const
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1946
|
+
const resourceFrame = buildCandidateFrame('none');
|
|
1947
|
+
const candidateFrame: ExtractedFrame = { ...resourceFrame, ...entry.structure };
|
|
1948
|
+
entry.temporalDemanded = frameDemandsTemporal(candidateFrame);
|
|
1949
|
+
entry.projection.setTemporalTracking(entry.temporalDemanded);
|
|
1950
|
+
this.projectProbes(entry, candidateFrame);
|
|
1951
|
+
this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
|
|
1952
|
+
const materialized = entry.projection.materialize();
|
|
1953
|
+
const candidates = entry.temporalDemanded
|
|
1954
|
+
? materialized.map((snapshot) => entry.projection.withTemporal(snapshot))
|
|
1955
|
+
: materialized;
|
|
1956
|
+
const frameWithProbes = attachProbeRecords(
|
|
1957
|
+
{ ...candidateFrame, renderables: candidates },
|
|
1958
|
+
candidates,
|
|
1959
|
+
entry.projection.slotsSnapshot(),
|
|
1960
|
+
entry.probeProjection,
|
|
1961
|
+
);
|
|
1962
|
+
const slots = entry.projection.slotsSnapshot();
|
|
1963
|
+
const result = cullPersistentFrame(
|
|
1964
|
+
frameWithProbes,
|
|
1965
|
+
frameWithProbes.renderables,
|
|
1966
|
+
(snapshot, index) => {
|
|
1967
|
+
const slot = slots[index];
|
|
1968
|
+
return slot === undefined
|
|
1969
|
+
? entry.projection.cullingWorldBounds(snapshot)
|
|
1970
|
+
: entry.projection.cullingWorldBoundsAt(slot);
|
|
1971
|
+
},
|
|
1972
|
+
);
|
|
1973
|
+
this.frameEpoch += 1;
|
|
1974
|
+
this.visibilityFacetFrame = undefined;
|
|
1975
|
+
this.visibilityProjectionCache = undefined;
|
|
1976
|
+
return result;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
private projectProbes(entry: PersistentCompositionEntry, frame: ExtractedFrame): void {
|
|
1980
|
+
const sky =
|
|
1981
|
+
frame.skylight === undefined
|
|
1982
|
+
? {
|
|
1983
|
+
available: false,
|
|
1984
|
+
irradiance: [0, 0, 0] as const,
|
|
1985
|
+
fallbackReason: 'no-skylight',
|
|
1986
|
+
}
|
|
1987
|
+
: {
|
|
1988
|
+
available: true,
|
|
1989
|
+
identity: `skylight:${frame.skylight.entityHandle}`,
|
|
1990
|
+
sourceKey:
|
|
1991
|
+
frame.skylight.equirectHandle > 0
|
|
1992
|
+
? `equirect:${frame.skylight.equirectHandle}`
|
|
1993
|
+
: `skylight:${frame.skylight.entityHandle}`,
|
|
1994
|
+
irradiance: [
|
|
1995
|
+
frame.skylight.color[0] * frame.skylight.intensity,
|
|
1996
|
+
frame.skylight.color[1] * frame.skylight.intensity,
|
|
1997
|
+
frame.skylight.color[2] * frame.skylight.intensity,
|
|
1998
|
+
] as [number, number, number],
|
|
1999
|
+
};
|
|
2000
|
+
const probes = frame.lightProbes ?? [];
|
|
2001
|
+
// Sky is evaluated by the global environment path. With no local light
|
|
2002
|
+
// probes there can be no per-object ProbeBlendRecord, regardless of how
|
|
2003
|
+
// many dynamic objects moved this frame. Preserve the projection call so
|
|
2004
|
+
// a transition from probes to no probes still clears retained records.
|
|
2005
|
+
if (probes.length === 0) {
|
|
2006
|
+
entry.probeProjection.apply({ objects: [], probes, sky });
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
const objects: ProbeSceneObjectInput[] = entry.projection.slotsSnapshot().map((slot) => ({
|
|
2010
|
+
objectKey: slot.slot,
|
|
2011
|
+
generation: slot.generation,
|
|
2012
|
+
position: [
|
|
2013
|
+
slot.snapshot.transform.world[12] ?? 0,
|
|
2014
|
+
slot.snapshot.transform.world[13] ?? 0,
|
|
2015
|
+
slot.snapshot.transform.world[14] ?? 0,
|
|
2016
|
+
],
|
|
2017
|
+
}));
|
|
2018
|
+
entry.probeProjection.apply({
|
|
2019
|
+
objects,
|
|
2020
|
+
probes,
|
|
2021
|
+
sky,
|
|
2022
|
+
});
|
|
1253
2023
|
}
|
|
1254
2024
|
|
|
1255
2025
|
private syncGpuOwner(
|
|
@@ -1258,6 +2028,7 @@ export class PersistentRenderScene {
|
|
|
1258
2028
|
delta?: RenderSceneApplyResult,
|
|
1259
2029
|
full = false,
|
|
1260
2030
|
): void {
|
|
2031
|
+
if (this.composition?.token === owner && !this.composition.gpuDrivenSceneRequired) return;
|
|
1261
2032
|
const acquired = this.acquireGpuScene(owner, projection);
|
|
1262
2033
|
if (acquired === undefined || acquired.rebuilt) return;
|
|
1263
2034
|
const result = full
|