@forgeax/engine-render 0.1.27 → 0.1.29
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 +842 -124
- package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
- package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/dynamic-geometry-host.d.ts +52 -0
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -0
- package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
- package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +3 -1
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/gpu-driven-owner.d.ts +10 -0
- package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
- package/dist/assembly/host-contract.d.ts +19 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +7 -0
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material-shader-policy.d.ts +3 -1
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/recovery/failure-location.d.ts +12 -0
- package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts +6 -4
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
- package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
- package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-helpers.d.ts +3 -0
- package/dist/assembly/renderer-helpers.d.ts.map +1 -1
- package/dist/assembly/renderer-inspection.d.ts +4 -0
- package/dist/assembly/renderer-inspection.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +6 -0
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
- package/dist/assembly/skin-palette-owner.d.ts +8 -0
- package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
- package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
- package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/webgpu-device-loss.d.ts +15 -0
- package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
- package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +7 -3
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
- package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
- package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
- package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
- package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +7 -141
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
- package/dist/authoring.mjs +3 -3
- package/dist/{chunk-ADHHVYLW.mjs → chunk-33XZFTQU.mjs} +245 -39
- package/dist/chunk-33XZFTQU.mjs.map +1 -0
- package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
- package/dist/chunk-BLXGONR2.mjs.map +1 -0
- package/dist/{chunk-KM2NOX2I.mjs → chunk-BQVQOQJH.mjs} +958 -254
- package/dist/chunk-BQVQOQJH.mjs.map +1 -0
- package/dist/{chunk-HKXTW355.mjs → chunk-BTWH7BP4.mjs} +386 -17
- package/dist/chunk-BTWH7BP4.mjs.map +1 -0
- package/dist/{chunk-TMIASV2N.mjs → chunk-FUODTTIA.mjs} +3934 -676
- package/dist/chunk-FUODTTIA.mjs.map +1 -0
- package/dist/{chunk-FJ6P52EE.mjs → chunk-ILQ5MEZF.mjs} +3 -17
- package/dist/chunk-ILQ5MEZF.mjs.map +1 -0
- package/dist/{chunk-E34VL5VI.mjs → chunk-JQTSEI3Z.mjs} +3 -3
- package/dist/{chunk-E34VL5VI.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
- package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
- package/dist/chunk-NICXSHAT.mjs.map +1 -0
- package/dist/{chunk-ZZ4YQ474.mjs → chunk-NRNXBLIJ.mjs} +577 -7
- package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
- package/dist/{chunk-JDWARUOI.mjs → chunk-PA5DRJNI.mjs} +17 -3
- package/dist/chunk-PA5DRJNI.mjs.map +1 -0
- package/dist/{chunk-XDP6XE5V.mjs → chunk-S4DNKQYQ.mjs} +1118 -446
- package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
- package/dist/components/camera.d.ts +62 -2
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/instances.d.ts +6 -20
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/components/scene-instance.d.ts +2 -1
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/screen-space-reflection.d.ts +14 -0
- package/dist/components/screen-space-reflection.d.ts.map +1 -0
- package/dist/construct-renderer.mjs +13805 -7186
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +11 -17
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/mesh-residency-lifetime.d.ts +21 -0
- package/dist/device/mesh-residency-lifetime.d.ts.map +1 -0
- package/dist/dynamic-geometry.d.ts +125 -0
- package/dist/dynamic-geometry.d.ts.map +1 -0
- package/dist/environment/background.d.ts +6 -0
- package/dist/environment/background.d.ts.map +1 -0
- package/dist/errors/gpu-driven.d.ts +6 -3
- package/dist/errors/gpu-driven.d.ts.map +1 -1
- package/dist/errors/render.d.ts +7 -4
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +33 -3
- package/dist/extract/gpu-driven.d.ts.map +1 -1
- package/dist/features/host.d.ts +23 -1
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/noise-texture.d.ts +17 -0
- package/dist/features/noise-texture.d.ts.map +1 -0
- package/dist/features/plan.d.ts +83 -2
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +37 -4
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/prepared-graphics-store.d.ts.map +1 -1
- package/dist/features/prepared-graphics.d.ts +5 -0
- package/dist/features/prepared-graphics.d.ts.map +1 -1
- package/dist/features/render-graph-compute.d.ts +5 -3
- package/dist/features/render-graph-compute.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts +11 -3
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/features/render-graph-raster.d.ts +19 -3
- package/dist/features/render-graph-raster.d.ts.map +1 -1
- package/dist/features/types.d.ts +11 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +2 -0
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts +34 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/pbr-program.d.ts +10 -0
- package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +144 -23
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/shadow-views.d.ts +126 -0
- package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
- package/dist/gpu-driven/view-gpu.d.ts +18 -2
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +2 -8
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +14 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-texture-usage.d.ts +1 -0
- package/dist/gpu-texture-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +6 -0
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/frame-resources.d.ts +8 -0
- package/dist/ibl/frame-resources.d.ts.map +1 -0
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +21 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +8 -7
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +164 -0
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +11 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -1
- package/dist/instances.d.ts +89 -44
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +118 -10
- package/dist/internal.mjs.map +1 -1
- package/dist/lifecycle.d.ts +2 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/material-row.d.ts +5 -0
- package/dist/material-row.d.ts.map +1 -0
- package/dist/materials.d.ts +15 -0
- package/dist/materials.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +21 -5
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
- package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
- package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
- package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
- package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
- package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
- package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
- package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
- package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
- package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
- package/dist/pipeline/standard-output/graph.d.ts +15 -0
- package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
- package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
- package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
- package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
- package/dist/pipeline/standard-output/resources.d.ts +10 -0
- package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
- package/dist/pipeline/standard-output/types.d.ts +39 -0
- package/dist/pipeline/standard-output/types.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +2 -2
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec-types.d.ts +4 -0
- package/dist/pipeline-spec-types.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +1 -9
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/points-lines/standard-owner.d.ts +1 -1
- package/dist/points-lines/standard-owner.d.ts.map +1 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
- package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
- package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +3 -3
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +106 -5
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +39 -4
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +7 -8
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +38 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +2 -10
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +12 -2
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +15 -6
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/prepared-material-bindings.d.ts +19 -0
- package/dist/record/prepared-material-bindings.d.ts.map +1 -0
- package/dist/record/recovery-pipeline.d.ts +2 -9
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +76 -26
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +62 -7
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +35 -7
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +1 -0
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +10 -2
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/recovery/render-system-roots.d.ts +11 -0
- package/dist/recovery/render-system-roots.d.ts.map +1 -0
- package/dist/recovery/types.d.ts +76 -0
- package/dist/recovery/types.d.ts.map +1 -0
- package/dist/reflection/record-owner.d.ts +0 -2
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +185 -30
- 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 +64 -5
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +7 -5
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +101 -41
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system-projections.d.ts +22 -0
- package/dist/render-system-projections.d.ts.map +1 -0
- package/dist/render-system.d.ts +34 -77
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene-types.d.ts +14 -7
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +29 -11
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/shadow-visibility.d.ts +5 -0
- package/dist/scene/shadow-visibility.d.ts.map +1 -0
- package/dist/ssr/admission.d.ts +70 -1
- package/dist/ssr/admission.d.ts.map +1 -1
- package/dist/ssr/compose.d.ts +13 -0
- package/dist/ssr/compose.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +35 -0
- package/dist/ssr/errors.d.ts.map +1 -1
- package/dist/ssr/graph.d.ts +60 -0
- package/dist/ssr/graph.d.ts.map +1 -0
- package/dist/ssr/history.d.ts +97 -0
- package/dist/ssr/history.d.ts.map +1 -0
- package/dist/ssr/hiz.d.ts +37 -0
- package/dist/ssr/hiz.d.ts.map +1 -0
- package/dist/ssr/inspection.d.ts +42 -0
- package/dist/ssr/inspection.d.ts.map +1 -0
- package/dist/ssr/resources.d.ts +58 -0
- package/dist/ssr/resources.d.ts.map +1 -0
- package/dist/ssr/temporal.d.ts +38 -0
- package/dist/ssr/temporal.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +32 -10
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/systems/skin-palette-types.d.ts +15 -0
- package/dist/systems/skin-palette-types.d.ts.map +1 -1
- package/dist/systems/transparent-dispatch.d.ts +9 -0
- package/dist/systems/transparent-dispatch.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +7 -0
- package/dist/targets/material-source.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts +7 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/gpu.d.ts +8 -0
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/history.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -6
- package/dist/temporal/inspection.d.ts.map +1 -1
- package/dist/temporal/standard-scene-data.d.ts +2 -1
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/temporal-view.d.ts +2 -0
- package/dist/temporal/temporal-view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +35 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +3 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/temporal.d.ts +6 -0
- package/dist/volume/temporal.d.ts.map +1 -1
- package/package.json +21 -20
- package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
- package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
- package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
- package/src/__tests__/auto-exposure.browser.test.ts +33 -0
- package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
- package/src/__tests__/detached-inspection.integration.test.ts +5 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
- package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
- package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
- package/src/__tests__/dynamic-geometry-host.unit.test.ts +1123 -0
- package/src/__tests__/dynamic-geometry.unit.test.ts +233 -0
- package/src/__tests__/factory-contract.integration.test.ts +73 -22
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
- package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
- package/src/__tests__/feature-noise.unit.test.ts +38 -0
- package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
- package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
- package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
- package/src/__tests__/frame-targets.unit.test.ts +24 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
- package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
- package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
- package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
- package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
- package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
- package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
- package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
- package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
- package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
- package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
- package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
- package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
- package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
- package/src/__tests__/gpu-scene.unit.test.ts +559 -33
- package/src/__tests__/instances-culling.integration.test.ts +36 -10
- package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
- package/src/__tests__/instances-store.unit.test.ts +242 -39
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
- package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
- package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
- package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
- package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
- package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
- package/src/__tests__/material-texture-source.unit.test.ts +87 -0
- package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
- package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +53 -0
- package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
- package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
- package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
- package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
- package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
- package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
- package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
- package/src/__tests__/public-surface.test-d.ts +3 -0
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
- package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
- package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
- package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
- package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
- package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
- package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
- package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
- package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
- package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
- package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
- package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
- package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
- package/src/__tests__/render-target-material-source.test-d.ts +27 -3
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +61 -1
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
- package/src/__tests__/shader-manifest-fixture.ts +39 -0
- package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
- package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
- package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
- package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
- package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
- package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
- package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
- package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
- package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
- package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
- package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
- package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
- package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
- package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
- package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
- package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
- package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
- package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
- package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
- package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
- package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
- package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
- package/src/__tests__/ssr-reprojection-probe.ts +90 -0
- package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
- package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
- package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
- package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
- package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
- package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
- package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
- package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
- package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
- package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
- package/src/__tests__/standard-output.browser.test.ts +15 -0
- package/src/__tests__/standard-output.dawn.test.ts +15 -0
- package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
- package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
- package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
- package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
- package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
- package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
- package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
- package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
- package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
- package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
- package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
- package/src/__tests__/taa-stability-feedback.ts +341 -0
- package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
- package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
- package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
- package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
- package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
- package/src/__tests__/visibility-extract.perf.test.ts +20 -8
- package/src/__tests__/visibility-instances.integration.test.ts +19 -7
- package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
- package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
- package/src/assembly/dynamic-geometry-host.ts +887 -0
- package/src/assembly/dynamic-geometry-runtime.ts +71 -0
- package/src/assembly/factory.ts +4 -0
- package/src/assembly/gpu-driven-owner.ts +29 -0
- package/src/assembly/host-contract.ts +36 -0
- package/src/assembly/material/assembly.ts +38 -0
- package/src/assembly/material-shader-policy.ts +71 -28
- package/src/assembly/recovery/failure-location.ts +62 -0
- package/src/assembly/recovery/renderer-recover.ts +19 -17
- package/src/assembly/renderer-facade.ts +29 -0
- package/src/assembly/renderer-feature-inspection.ts +13 -0
- package/src/assembly/renderer-frame-transaction.ts +63 -0
- package/src/assembly/renderer-helpers.ts +26 -0
- package/src/assembly/renderer-inspection.ts +6 -0
- package/src/assembly/shader-prewarm-policy.ts +32 -0
- package/src/assembly/skin-palette-owner.ts +21 -0
- package/src/assembly/ssr-shader-prewarm.ts +41 -0
- package/src/assembly/webgpu-device-loss.ts +75 -0
- package/src/assembly/webgpu-pbr-ready.ts +232 -0
- package/src/assembly/webgpu-ready-mipmap.ts +26 -0
- package/src/assembly/webgpu-ready.ts +142 -121
- package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
- package/src/assembly/webgpu-renderer-contract.ts +69 -0
- package/src/assembly/webgpu-renderer-guards.ts +32 -0
- package/src/assembly/webgpu-renderer-observation.ts +43 -0
- package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
- package/src/assembly/webgpu-renderer.ts +675 -614
- package/src/assembly/webgpu-vertex-layouts.ts +233 -87
- package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
- package/src/components/camera.ts +287 -7
- package/src/components/index.ts +1 -0
- package/src/components/instances.ts +45 -18
- package/src/components/scene-instance.ts +2 -1
- package/src/components/screen-space-reflection.ts +21 -0
- package/src/components/sprite-animation.ts +3 -3
- package/src/components/sprite-instances.ts +1 -1
- package/src/device/gpu-residency.ts +96 -93
- package/src/device/mesh-residency-lifetime.ts +73 -0
- package/src/dynamic-geometry.ts +736 -0
- package/src/environment/background.ts +263 -0
- package/src/errors/gpu-driven.ts +39 -5
- package/src/errors/render.ts +27 -3
- package/src/extract/camera.ts +26 -1
- package/src/extract/gpu-driven.ts +185 -10
- package/src/features/host.ts +237 -19
- package/src/features/noise-texture.ts +91 -0
- package/src/features/plan.ts +553 -24
- package/src/features/prepared-gpu-work.ts +631 -49
- package/src/features/prepared-graphics-store.ts +31 -0
- package/src/features/prepared-graphics.ts +5 -0
- package/src/features/render-graph-compute.ts +41 -8
- package/src/features/render-graph-contribution.ts +141 -3
- package/src/features/render-graph-raster.ts +143 -30
- package/src/features/types.ts +11 -0
- package/src/fullscreen-post-process-pass.ts +3 -0
- package/src/gpu-driven/batch-topology.ts +424 -102
- package/src/gpu-driven/pbr-program.ts +31 -0
- package/src/gpu-driven/prepared-draw.ts +29 -4
- package/src/gpu-driven/production-raster.ts +2267 -694
- package/src/gpu-driven/shadow-views.ts +539 -0
- package/src/gpu-driven/view-gpu.ts +188 -41
- package/src/gpu-scene-schema.ts +52 -11
- package/src/gpu-scene.ts +186 -99
- package/src/gpu-texture-usage.ts +1 -0
- package/src/hdrp-buffers.ts +72 -36
- package/src/ibl/frame-resources.ts +38 -0
- package/src/ibl/skylight-bind-group.ts +5 -4
- package/src/index.ts +125 -1
- package/src/inspection-types.ts +191 -0
- package/src/instances-derived-bounds.ts +258 -34
- package/src/instances.ts +459 -150
- package/src/internal.ts +22 -0
- package/src/lifecycle.ts +2 -0
- package/src/material-row.ts +145 -0
- package/src/materials.ts +44 -8
- package/src/pbr-pipeline.ts +159 -17
- package/src/pipeline/standard-forward-lane.ts +52 -24
- package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
- package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
- package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
- package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
- package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
- package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
- package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
- package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
- package/src/pipeline/standard-output/color-transform.ts +124 -0
- package/src/pipeline/standard-output/graph.ts +139 -0
- package/src/pipeline/standard-output/lut-admission.ts +121 -0
- package/src/pipeline/standard-output/lut-gpu.ts +399 -0
- package/src/pipeline/standard-output/lut-state.ts +176 -0
- package/src/pipeline/standard-output/resources.ts +121 -0
- package/src/pipeline/standard-output/types.ts +58 -0
- package/src/pipeline/standard-pipeline.ts +192 -59
- package/src/pipeline/standard-post.ts +186 -27
- package/src/pipeline-builder.ts +16 -6
- package/src/pipeline-spec-types.ts +4 -0
- package/src/pipeline-spec.ts +37 -29
- package/src/points-lines/standard-owner.ts +6 -14
- package/src/prepare/prepared-graphics-resolver.ts +9 -2
- package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
- package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
- package/src/record/dynamic-geometry-consumption.ts +59 -0
- package/src/record/frame-lighting.ts +12 -11
- package/src/record/frame-snapshot.ts +133 -23
- package/src/record/frame-targets.ts +9 -0
- package/src/record/frame.ts +727 -133
- package/src/record/main-pass-geometry.ts +162 -98
- package/src/record/main-pass-material.ts +115 -26
- package/src/record/main-pass-sprite-draws.ts +18 -107
- package/src/record/main-pass.ts +78 -8
- package/src/record/material-uniforms.ts +1 -1
- package/src/record/mesh-ssbo.ts +59 -15
- package/src/record/prepared-material-bindings.ts +234 -0
- package/src/record/recovery-pipeline.ts +59 -136
- package/src/record/render-context.ts +92 -22
- package/src/record/shadow-pass.ts +1231 -209
- package/src/record/skybox-post-pass.ts +14 -3
- package/src/record/typed-frame-graph.ts +1006 -53
- package/src/record/view-ubo.ts +11 -0
- package/src/recovery/render-system-candidate.ts +90 -66
- package/src/recovery/render-system-roots.ts +113 -0
- package/src/recovery/types.ts +77 -0
- package/src/reflection/record-owner.ts +26 -54
- package/src/render-contract.ts +219 -25
- package/src/render-graph-primitives.ts +6 -2
- package/src/render-pipeline.ts +70 -1
- package/src/render-system-extract-tail.ts +409 -160
- package/src/render-system-extract.ts +376 -102
- package/src/render-system-projections.ts +241 -0
- package/src/render-system.ts +692 -791
- package/src/scene/render-scene-types.ts +13 -4
- package/src/scene/render-scene.ts +569 -355
- package/src/scene/shadow-visibility.ts +21 -0
- package/src/ssr/admission.ts +380 -14
- package/src/ssr/compose.ts +144 -0
- package/src/ssr/errors.ts +48 -0
- package/src/ssr/graph.ts +777 -0
- package/src/ssr/history.ts +436 -0
- package/src/ssr/hiz.ts +124 -0
- package/src/ssr/inspection.ts +124 -0
- package/src/ssr/resources.ts +202 -0
- package/src/ssr/temporal.ts +164 -0
- package/src/systems/skin-palette-allocator.ts +495 -148
- package/src/systems/skin-palette-types.ts +17 -0
- package/src/systems/transparent-dispatch.ts +90 -0
- package/src/targets/material-source.ts +34 -0
- package/src/temporal/coverage.ts +1 -1
- package/src/temporal/frame-coordinator.ts +23 -2
- package/src/temporal/gpu.ts +74 -12
- package/src/temporal/history.ts +3 -1
- package/src/temporal/inspection.ts +2 -1
- package/src/temporal/standard-scene-data.ts +3 -1
- package/src/temporal/temporal-view.ts +3 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
- package/src/typed-render-graph-primitives.ts +266 -74
- package/src/typed-shadow-passes.ts +122 -12
- package/src/volume/temporal.ts +10 -0
- package/dist/chunk-ADHHVYLW.mjs.map +0 -1
- package/dist/chunk-FJ6P52EE.mjs.map +0 -1
- package/dist/chunk-GE3SDD72.mjs.map +0 -1
- package/dist/chunk-GF523LHF.mjs.map +0 -1
- package/dist/chunk-HKXTW355.mjs.map +0 -1
- package/dist/chunk-JDWARUOI.mjs.map +0 -1
- package/dist/chunk-KM2NOX2I.mjs.map +0 -1
- package/dist/chunk-TMIASV2N.mjs.map +0 -1
- package/dist/chunk-XDP6XE5V.mjs.map +0 -1
- package/dist/chunk-ZZ4YQ474.mjs.map +0 -1
- package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
|
@@ -3,41 +3,40 @@ import { componentId } from '@forgeax/engine-ecs/internal';
|
|
|
3
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 {
|
|
6
|
+
import {
|
|
7
|
+
ChildOf,
|
|
8
|
+
collectSubtree,
|
|
9
|
+
GlobalTransform,
|
|
10
|
+
MorphWeights,
|
|
11
|
+
Transform,
|
|
12
|
+
} from '@forgeax/engine-scene';
|
|
7
13
|
import { Skin } from '@forgeax/engine-skinning';
|
|
8
14
|
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
9
15
|
import {
|
|
10
|
-
Camera,
|
|
11
|
-
DirectionalLight,
|
|
12
16
|
Instances,
|
|
13
17
|
Layer,
|
|
14
|
-
LightProbe,
|
|
15
18
|
Lines,
|
|
16
19
|
MeshFilter,
|
|
17
20
|
MeshRenderer,
|
|
18
|
-
MotionBlur,
|
|
19
|
-
PointLight,
|
|
20
|
-
PointLightShadow,
|
|
21
21
|
Points,
|
|
22
|
-
PostProcessParams,
|
|
23
|
-
ReflectionProbe,
|
|
24
|
-
SkyboxBackground,
|
|
25
|
-
Skylight,
|
|
26
22
|
SortKey,
|
|
27
|
-
SpotLight,
|
|
28
23
|
SpriteInstances,
|
|
29
24
|
SpriteRegionOverride,
|
|
30
25
|
Visibility,
|
|
31
26
|
} from '../components';
|
|
32
27
|
import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
|
|
28
|
+
import { gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
|
|
33
29
|
import {
|
|
34
30
|
BatchTopology,
|
|
35
31
|
type BatchTopologyInspection,
|
|
36
32
|
type SubmissionPlan,
|
|
37
33
|
} from '../gpu-driven/batch-topology';
|
|
38
34
|
import { GpuScene } from '../gpu-scene';
|
|
39
|
-
import type {
|
|
40
|
-
|
|
35
|
+
import type {
|
|
36
|
+
GpuDrivenStructureMetrics,
|
|
37
|
+
PersistentRenderSceneInspection,
|
|
38
|
+
} from '../inspection-types';
|
|
39
|
+
import type { InstanceCollectionStore } from '../instances';
|
|
41
40
|
import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
|
|
42
41
|
import type { PointsLinesInspection } from '../points-lines/inspection';
|
|
43
42
|
import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
|
|
@@ -57,13 +56,11 @@ import type {
|
|
|
57
56
|
RenderableSnapshot,
|
|
58
57
|
RenderableTemporalSnapshot,
|
|
59
58
|
} from '../render-system-extract';
|
|
60
|
-
import type { SkinPaletteAllocator } from '../systems/skin-palette-allocator';
|
|
61
59
|
import {
|
|
62
60
|
PersistentTransmissionDemandProjection,
|
|
63
61
|
type TransmissionDemand,
|
|
64
62
|
type TransmissionDemandOperation,
|
|
65
63
|
} from '../transmission/projection';
|
|
66
|
-
import { VolumetricFog } from '../volume/component';
|
|
67
64
|
import { ProbeBlendSceneProjection, type ProbeSceneObjectInput } from './probe-blend';
|
|
68
65
|
import type {
|
|
69
66
|
RenderSceneApplyResult,
|
|
@@ -74,6 +71,7 @@ import type {
|
|
|
74
71
|
RenderSceneResyncReason,
|
|
75
72
|
RenderSceneSlot,
|
|
76
73
|
} from './render-scene-types';
|
|
74
|
+
import { buildShadowFrusta } from './shadow-visibility';
|
|
77
75
|
import { createVisibilityBudget, type VisibilityBudget } from './visibility/budget';
|
|
78
76
|
import {
|
|
79
77
|
primitiveKey,
|
|
@@ -103,6 +101,10 @@ interface PendingIdentity {
|
|
|
103
101
|
current: RenderableSnapshot | undefined;
|
|
104
102
|
removed: boolean;
|
|
105
103
|
contentChanged: boolean;
|
|
104
|
+
/** An Instances row changed during this apply batch. */
|
|
105
|
+
instanceChanged: boolean;
|
|
106
|
+
/** A retained world transform changed during this apply batch. */
|
|
107
|
+
transformChanged: boolean;
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
interface WorldTransformSpan {
|
|
@@ -137,6 +139,16 @@ export interface RenderSceneSubmissionDelta {
|
|
|
137
139
|
readonly slots: readonly number[];
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Complete ShadowCaster producer projection retained beside the camera-cull
|
|
144
|
+
* submission frame. The persistent scene owns this sequence; record consumes
|
|
145
|
+
* it for the CPU residual lane without rebuilding a second scene projection.
|
|
146
|
+
*/
|
|
147
|
+
export interface PersistentShadowCasterProjection {
|
|
148
|
+
readonly renderables: readonly RenderableSnapshot[];
|
|
149
|
+
readonly dispatch: readonly DispatchEntry[];
|
|
150
|
+
}
|
|
151
|
+
|
|
140
152
|
function identityKey(worldId: number, entityKey: number): string {
|
|
141
153
|
return `${worldId}:${entityKey}`;
|
|
142
154
|
}
|
|
@@ -222,13 +234,6 @@ function ownPointsLinesSnapshot(
|
|
|
222
234
|
};
|
|
223
235
|
}
|
|
224
236
|
|
|
225
|
-
function withWorld(snapshot: RenderableSnapshot, world: Float32Array): RenderableSnapshot {
|
|
226
|
-
return {
|
|
227
|
-
...snapshot,
|
|
228
|
-
transform: { ...snapshot.transform, world: new Float32Array(world) },
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
237
|
function intersects(left: RenderSceneBounds, right: RenderSceneBounds): boolean {
|
|
233
238
|
return (
|
|
234
239
|
left.min[0] <= right.max[0] &&
|
|
@@ -322,8 +327,8 @@ export class RenderScene {
|
|
|
322
327
|
private readonly freeSlots: number[] = [];
|
|
323
328
|
private readonly slotsByWorld = new Map<number, Map<number, number>>();
|
|
324
329
|
private readonly slotsByMaterial = new Map<number, Set<number>>();
|
|
330
|
+
private readonly slotsByAsset = new Map<number, Set<number>>();
|
|
325
331
|
private readonly dirtySinceSubmitted = new Set<number>();
|
|
326
|
-
private readonly transformUpdatedSlots: RenderSceneSlot[] = [];
|
|
327
332
|
private temporalTracking = false;
|
|
328
333
|
private lastSubmittedVisible = new Set<string>();
|
|
329
334
|
private submittedEpoch = 0;
|
|
@@ -348,13 +353,26 @@ export class RenderScene {
|
|
|
348
353
|
return this.orderedSlots.length;
|
|
349
354
|
}
|
|
350
355
|
|
|
351
|
-
apply(
|
|
352
|
-
|
|
356
|
+
apply(
|
|
357
|
+
operations: readonly RenderSceneOperation[],
|
|
358
|
+
spans: readonly WorldTransformSpan[] = [],
|
|
359
|
+
): RenderSceneApplyResult {
|
|
360
|
+
if (operations.length === 0 && spans.length === 0) {
|
|
353
361
|
this.noChangeFrames += 1;
|
|
354
362
|
this.renderableScans = 0;
|
|
355
363
|
return this.emptyResult();
|
|
356
364
|
}
|
|
357
365
|
|
|
366
|
+
for (const operation of operations) {
|
|
367
|
+
if (
|
|
368
|
+
operation.kind === 'update' &&
|
|
369
|
+
operation.snapshot !== undefined &&
|
|
370
|
+
(operation.worldId !== operation.snapshot.worldId ||
|
|
371
|
+
operation.entityKey !== operation.snapshot.entityKey)
|
|
372
|
+
) {
|
|
373
|
+
throw new RangeError('RenderScene update identity must match its source snapshot');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
358
376
|
this.deltaFrames += 1;
|
|
359
377
|
this.renderableScans = operations.length;
|
|
360
378
|
const pendingByWorld = new Map<number, Map<number, PendingIdentity>>();
|
|
@@ -377,31 +395,75 @@ export class RenderScene {
|
|
|
377
395
|
current: initial?.snapshot,
|
|
378
396
|
removed: false,
|
|
379
397
|
contentChanged: false,
|
|
398
|
+
instanceChanged: false,
|
|
399
|
+
transformChanged: false,
|
|
380
400
|
};
|
|
381
401
|
entities.set(entityKey, pending);
|
|
382
402
|
pendingOrder.push(pending);
|
|
383
403
|
return pending;
|
|
384
404
|
};
|
|
385
405
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
406
|
+
const updateWorld = (pending: PendingIdentity, source: Float32Array, offset: number): void => {
|
|
407
|
+
const target = pending.current?.transform.world;
|
|
408
|
+
if (target === undefined) return;
|
|
409
|
+
for (let lane = 0; lane < 16; lane += 1) {
|
|
410
|
+
const value = source[offset + lane] ?? 0;
|
|
411
|
+
if (target[lane] !== value) {
|
|
412
|
+
target[lane] = value;
|
|
413
|
+
pending.transformChanged = true;
|
|
414
|
+
}
|
|
393
415
|
}
|
|
416
|
+
};
|
|
417
|
+
for (const operation of operations) {
|
|
394
418
|
const pending = pendingFor(operation.worldId, operation.entityKey);
|
|
395
419
|
if (operation.kind === 'remove') {
|
|
396
420
|
pending.current = undefined;
|
|
397
421
|
pending.removed = true;
|
|
398
422
|
continue;
|
|
399
423
|
}
|
|
424
|
+
if (operation.snapshot !== undefined) {
|
|
425
|
+
const previousMaterials = pending.current?.materials;
|
|
426
|
+
const next = ownSnapshot(operation.snapshot);
|
|
427
|
+
pending.current =
|
|
428
|
+
previousMaterials !== undefined &&
|
|
429
|
+
previousMaterials.length === next.materials.length &&
|
|
430
|
+
previousMaterials.every((material, index) => material === next.materials[index])
|
|
431
|
+
? { ...next, materials: previousMaterials }
|
|
432
|
+
: next;
|
|
433
|
+
pending.contentChanged = true;
|
|
434
|
+
}
|
|
400
435
|
if (pending.current === undefined) {
|
|
401
436
|
ignoredLateUpdates += 1;
|
|
402
437
|
continue;
|
|
403
438
|
}
|
|
404
|
-
|
|
439
|
+
if (operation.instances !== undefined) {
|
|
440
|
+
pending.current = {
|
|
441
|
+
...pending.current,
|
|
442
|
+
instances: {
|
|
443
|
+
...operation.instances,
|
|
444
|
+
transforms: new Float32Array(operation.instances.transforms),
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
pending.instanceChanged = true;
|
|
448
|
+
}
|
|
449
|
+
if (operation.world !== undefined) {
|
|
450
|
+
updateWorld(pending, operation.world, 0);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// The same pending identity merges column updates with all other source
|
|
454
|
+
// edits. There is one publication and one consumer delta per entity.
|
|
455
|
+
for (const span of spans) {
|
|
456
|
+
this.renderableScans += span.entities.length;
|
|
457
|
+
for (let row = 0; row < span.entities.length; row += 1) {
|
|
458
|
+
const entity = span.entities[row];
|
|
459
|
+
if (entity === undefined) continue;
|
|
460
|
+
const pending = pendingFor(span.worldId, entity);
|
|
461
|
+
if (pending.current === undefined) {
|
|
462
|
+
ignoredLateUpdates += 1;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
updateWorld(pending, span.worlds, row * 16);
|
|
466
|
+
}
|
|
405
467
|
}
|
|
406
468
|
|
|
407
469
|
let created = 0;
|
|
@@ -411,6 +473,7 @@ export class RenderScene {
|
|
|
411
473
|
const createdSlots: RenderSceneSlot[] = [];
|
|
412
474
|
const updatedSlots: RenderSceneSlot[] = [];
|
|
413
475
|
const contentUpdatedSlots: RenderSceneSlot[] = [];
|
|
476
|
+
const instanceUpdatedSlots: RenderSceneSlot[] = [];
|
|
414
477
|
const removedSlots: RenderSceneRecord[] = [];
|
|
415
478
|
const recreatedSlots: RenderSceneSlot[] = [];
|
|
416
479
|
for (const pending of pendingOrder) {
|
|
@@ -438,24 +501,38 @@ export class RenderScene {
|
|
|
438
501
|
recreated += 1;
|
|
439
502
|
continue;
|
|
440
503
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
504
|
+
if (!pending.contentChanged && !pending.instanceChanged && !pending.transformChanged)
|
|
505
|
+
continue;
|
|
506
|
+
const updatedSlot: RenderSceneSlot =
|
|
507
|
+
pending.current === pending.initial.snapshot
|
|
508
|
+
? pending.initial
|
|
509
|
+
: { ...pending.initial, snapshot: pending.current };
|
|
510
|
+
if (pending.contentChanged) this.unindexMaterials(pending.initial);
|
|
446
511
|
this.slots[pending.initial.slot] = updatedSlot;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
512
|
+
if (pending.current.instances === undefined && !pending.contentChanged) {
|
|
513
|
+
const cached = this.worldBoundsBySlot[updatedSlot.slot];
|
|
514
|
+
this.worldBoundsBySlot[updatedSlot.slot] =
|
|
515
|
+
worldBounds(pending.current, cached ?? undefined) ?? null;
|
|
516
|
+
} else {
|
|
517
|
+
this.worldBoundsBySlot[updatedSlot.slot] = undefined;
|
|
518
|
+
}
|
|
519
|
+
if (pending.contentChanged) this.indexMaterials(updatedSlot);
|
|
520
|
+
if (this.temporalTracking) this.dirtySinceSubmitted.add(updatedSlot.slot);
|
|
450
521
|
updatedSlots.push(updatedSlot);
|
|
451
522
|
if (pending.contentChanged) contentUpdatedSlots.push(updatedSlot);
|
|
523
|
+
else if (pending.instanceChanged) instanceUpdatedSlots.push(updatedSlot);
|
|
452
524
|
updated += 1;
|
|
453
525
|
}
|
|
454
526
|
|
|
455
527
|
const changed = created + updated + removed + recreated > 0;
|
|
456
528
|
if (changed) {
|
|
457
529
|
this.revision += 1;
|
|
458
|
-
|
|
530
|
+
if (
|
|
531
|
+
created + removed + recreated > 0 ||
|
|
532
|
+
pendingOrder.some((pending) => pending.current !== pending.initial?.snapshot)
|
|
533
|
+
) {
|
|
534
|
+
this.invalidateSnapshots();
|
|
535
|
+
}
|
|
459
536
|
}
|
|
460
537
|
return {
|
|
461
538
|
created,
|
|
@@ -466,61 +543,31 @@ export class RenderScene {
|
|
|
466
543
|
createdSlots,
|
|
467
544
|
updatedSlots,
|
|
468
545
|
contentUpdatedSlots,
|
|
546
|
+
instanceUpdatedSlots,
|
|
469
547
|
removedSlots,
|
|
470
548
|
recreatedSlots,
|
|
471
549
|
resynced: 0,
|
|
472
550
|
};
|
|
473
551
|
}
|
|
474
552
|
|
|
475
|
-
/**
|
|
476
|
-
|
|
477
|
-
this.
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if (slot === undefined) {
|
|
490
|
-
ignoredLateUpdates += 1;
|
|
491
|
-
continue;
|
|
492
|
-
}
|
|
493
|
-
const target = slot.snapshot.transform.world;
|
|
494
|
-
const sourceStart = row * 16;
|
|
495
|
-
for (let index = 0; index < 16; index += 1) {
|
|
496
|
-
target[index] = span.worlds[sourceStart + index] ?? target[index] ?? 0;
|
|
497
|
-
}
|
|
498
|
-
if (slot.snapshot.instances === undefined) {
|
|
499
|
-
const cached = this.worldBoundsBySlot[slot.slot];
|
|
500
|
-
const bounds = worldBounds(slot.snapshot, cached ?? undefined);
|
|
501
|
-
this.worldBoundsBySlot[slot.slot] = bounds ?? null;
|
|
502
|
-
} else {
|
|
503
|
-
this.worldBoundsBySlot[slot.slot] = undefined;
|
|
504
|
-
}
|
|
505
|
-
if (this.temporalTracking) this.dirtySinceSubmitted.add(slot.slot);
|
|
506
|
-
updatedSlots.push(slot);
|
|
507
|
-
updated += 1;
|
|
553
|
+
/** Rebind positional World indices while retaining stable slot and submission history. */
|
|
554
|
+
remapWorlds(indices: readonly number[]): void {
|
|
555
|
+
this.slotsByWorld.clear();
|
|
556
|
+
for (const slot of this.slots) {
|
|
557
|
+
if (slot === undefined) continue;
|
|
558
|
+
this.instanceBoundsCache.invalidate(slot.entityKey, slot.worldId);
|
|
559
|
+
const mapped = indices[slot.worldId];
|
|
560
|
+
const worldId = mapped === undefined || mapped < 0 ? -1 - slot.worldId : mapped;
|
|
561
|
+
const remapped = { ...slot, worldId, snapshot: { ...slot.snapshot, worldId } };
|
|
562
|
+
this.slots[slot.slot] = remapped;
|
|
563
|
+
let entities = this.slotsByWorld.get(worldId);
|
|
564
|
+
if (entities === undefined) {
|
|
565
|
+
entities = new Map<number, number>();
|
|
566
|
+
this.slotsByWorld.set(worldId, entities);
|
|
508
567
|
}
|
|
568
|
+
entities.set(slot.entityKey, slot.slot);
|
|
509
569
|
}
|
|
510
|
-
this.
|
|
511
|
-
if (updated > 0) this.revision += 1;
|
|
512
|
-
return {
|
|
513
|
-
created: 0,
|
|
514
|
-
updated,
|
|
515
|
-
removed: 0,
|
|
516
|
-
recreated: 0,
|
|
517
|
-
ignoredLateUpdates,
|
|
518
|
-
createdSlots: [],
|
|
519
|
-
updatedSlots,
|
|
520
|
-
removedSlots: [],
|
|
521
|
-
recreatedSlots: [],
|
|
522
|
-
resynced: 0,
|
|
523
|
-
};
|
|
570
|
+
this.invalidateSnapshots();
|
|
524
571
|
}
|
|
525
572
|
|
|
526
573
|
/** Reconcile the current identity set while retaining slots for survivors. */
|
|
@@ -545,7 +592,7 @@ export class RenderScene {
|
|
|
545
592
|
for (const snapshot of desired.values()) {
|
|
546
593
|
const existing = this.lookup(snapshot.worldId, snapshot.entityKey);
|
|
547
594
|
if (existing === undefined) {
|
|
548
|
-
const created = this.allocate(snapshot);
|
|
595
|
+
const created = this.allocate(ownSnapshot(snapshot));
|
|
549
596
|
this.dirtySinceSubmitted.add(created.slot);
|
|
550
597
|
continue;
|
|
551
598
|
}
|
|
@@ -647,7 +694,6 @@ export class RenderScene {
|
|
|
647
694
|
}
|
|
648
695
|
this.lastSubmittedVisible = new Set(capture.visible);
|
|
649
696
|
this.submittedEpoch += 1;
|
|
650
|
-
this.invalidateSnapshots();
|
|
651
697
|
return {
|
|
652
698
|
epoch: this.submittedEpoch,
|
|
653
699
|
slots: capture.slots.map((update) => update.slot),
|
|
@@ -729,6 +775,7 @@ export class RenderScene {
|
|
|
729
775
|
fullRebuilds: this.fullRebuilds,
|
|
730
776
|
resyncs: this.resyncs,
|
|
731
777
|
lastResyncReason: this.lastResyncReason,
|
|
778
|
+
instanceBoundsCache: this.instanceBoundsCache.inspect(),
|
|
732
779
|
};
|
|
733
780
|
}
|
|
734
781
|
|
|
@@ -741,6 +788,8 @@ export class RenderScene {
|
|
|
741
788
|
ignoredLateUpdates: 0,
|
|
742
789
|
createdSlots: [],
|
|
743
790
|
updatedSlots: [],
|
|
791
|
+
contentUpdatedSlots: [],
|
|
792
|
+
instanceUpdatedSlots: [],
|
|
744
793
|
removedSlots: [],
|
|
745
794
|
recreatedSlots: [],
|
|
746
795
|
resynced: 0,
|
|
@@ -761,7 +810,7 @@ export class RenderScene {
|
|
|
761
810
|
generation,
|
|
762
811
|
worldId: snapshot.worldId,
|
|
763
812
|
entityKey: snapshot.entityKey,
|
|
764
|
-
snapshot
|
|
813
|
+
snapshot,
|
|
765
814
|
};
|
|
766
815
|
let previousWorld = this.previousWorldBySlot[slot];
|
|
767
816
|
if (previousWorld === undefined) {
|
|
@@ -796,7 +845,23 @@ export class RenderScene {
|
|
|
796
845
|
this.freeSlots.push(record.slot);
|
|
797
846
|
}
|
|
798
847
|
|
|
848
|
+
addSharedRefConsumers(worldId: number, handle: number, target: Set<number>): void {
|
|
849
|
+
for (const index of [this.slotsByAsset, this.slotsByMaterial]) {
|
|
850
|
+
for (const slot of index.get(handle) ?? []) {
|
|
851
|
+
const record = this.slots[slot];
|
|
852
|
+
if (record?.worldId === worldId) target.add(record.entityKey);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
799
857
|
private indexMaterials(record: RenderSceneSlot): void {
|
|
858
|
+
const asset = record.snapshot.assetHandle;
|
|
859
|
+
let assetSlots = this.slotsByAsset.get(asset);
|
|
860
|
+
if (assetSlots === undefined) {
|
|
861
|
+
assetSlots = new Set<number>();
|
|
862
|
+
this.slotsByAsset.set(asset, assetSlots);
|
|
863
|
+
}
|
|
864
|
+
assetSlots.add(record.slot);
|
|
800
865
|
for (const material of record.snapshot.materials) {
|
|
801
866
|
const handle = material.materialHandle ?? 0;
|
|
802
867
|
let slots = this.slotsByMaterial.get(handle);
|
|
@@ -809,6 +874,10 @@ export class RenderScene {
|
|
|
809
874
|
}
|
|
810
875
|
|
|
811
876
|
private unindexMaterials(record: RenderSceneSlot): void {
|
|
877
|
+
const asset = record.snapshot.assetHandle;
|
|
878
|
+
const assetSlots = this.slotsByAsset.get(asset);
|
|
879
|
+
assetSlots?.delete(record.slot);
|
|
880
|
+
if (assetSlots?.size === 0) this.slotsByAsset.delete(asset);
|
|
812
881
|
for (const material of record.snapshot.materials) {
|
|
813
882
|
const handle = material.materialHandle ?? 0;
|
|
814
883
|
const slots = this.slotsByMaterial.get(handle);
|
|
@@ -914,9 +983,8 @@ export interface PersistentRenderSceneOptions {
|
|
|
914
983
|
readonly onGpuError?: ((error: RhiError) => void) | undefined;
|
|
915
984
|
readonly onSharedRefMutation?: ((worldId: number, handle: number) => void) | undefined;
|
|
916
985
|
readonly configuredQueryBudget?: number | undefined;
|
|
917
|
-
/**
|
|
918
|
-
readonly instanceCollections?:
|
|
919
|
-
readonly getSkinPaletteAllocator?: (() => SkinPaletteAllocator | null) | undefined;
|
|
986
|
+
/** Producer-owned instance collections consumed by the same source update flow. */
|
|
987
|
+
readonly instanceCollections?: InstanceCollectionStore | undefined;
|
|
920
988
|
}
|
|
921
989
|
|
|
922
990
|
export interface PersistentGpuDrivenState {
|
|
@@ -925,6 +993,7 @@ export interface PersistentGpuDrivenState {
|
|
|
925
993
|
readonly slots: readonly RenderSceneSlot[];
|
|
926
994
|
/** Stable renderer-local World keys aligned with RenderSceneSlot.worldId. */
|
|
927
995
|
readonly worldKeys?: readonly number[];
|
|
996
|
+
readonly structureMetrics?: GpuDrivenStructureMetrics;
|
|
928
997
|
}
|
|
929
998
|
|
|
930
999
|
export interface CpuDirectViewInput {
|
|
@@ -993,19 +1062,10 @@ type PersistentGpuSceneInspection = PersistentRenderSceneInspection['gpu'];
|
|
|
993
1062
|
interface PersistentCompositionEntry {
|
|
994
1063
|
readonly token: object;
|
|
995
1064
|
readonly worlds: readonly World[];
|
|
996
|
-
readonly cameraOwner: number;
|
|
997
|
-
readonly resourceOwner: number;
|
|
998
1065
|
readonly projection: RenderScene;
|
|
999
1066
|
readonly topology: BatchTopology;
|
|
1000
1067
|
readonly transmissionDemand: PersistentTransmissionDemandProjection;
|
|
1001
|
-
|
|
1002
|
-
ExtractedFrame,
|
|
1003
|
-
| 'dispatch'
|
|
1004
|
-
| 'visibilityStats'
|
|
1005
|
-
| 'visibilitySnapshots'
|
|
1006
|
-
| 'featureVisibilitySnapshots'
|
|
1007
|
-
| 'hiddenEntityReports'
|
|
1008
|
-
>;
|
|
1068
|
+
hiddenEntityReports: ExtractedFrame['hiddenEntityReports'];
|
|
1009
1069
|
/** Stable renderer-local World keys; unlike RenderableSnapshot.worldId they survive reorder. */
|
|
1010
1070
|
readonly worldKeys: readonly number[];
|
|
1011
1071
|
readonly readVersions: RenderReadVersion[];
|
|
@@ -1014,53 +1074,22 @@ interface PersistentCompositionEntry {
|
|
|
1014
1074
|
readonly renderChangeQueries: readonly ReadonlyMap<number, Query>[];
|
|
1015
1075
|
readonly dispatchBySlot: Map<number, readonly DispatchEntry[]>;
|
|
1016
1076
|
dispatchRevision: number;
|
|
1017
|
-
dispatchCache:
|
|
1018
|
-
| { readonly revision: number; readonly value: readonly DispatchEntry[] }
|
|
1019
|
-
| undefined;
|
|
1077
|
+
dispatchCache: { readonly revision: number; readonly value: DispatchEntry[] } | undefined;
|
|
1020
1078
|
readonly skinConsumersByJoint: Map<string, Set<number>>;
|
|
1021
1079
|
/** Renderable identities whose partial producer still needs a successful publish. */
|
|
1022
1080
|
readonly pendingRenderableEntitiesByWorld: Set<number>[];
|
|
1023
1081
|
/** Slots retained for recovery but excluded from direct and indirect submission. */
|
|
1024
1082
|
readonly unavailableSlots: Set<number>;
|
|
1025
1083
|
catalogEpoch: number;
|
|
1084
|
+
instanceEpoch: number;
|
|
1026
1085
|
readonly probeProjection: ProbeBlendSceneProjection;
|
|
1027
1086
|
temporalDemanded: boolean;
|
|
1028
1087
|
gpuDrivenSceneRequired: boolean;
|
|
1029
1088
|
}
|
|
1030
1089
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
componentId(MeshFilter),
|
|
1035
|
-
componentId(MeshRenderer),
|
|
1036
|
-
componentId(Instances),
|
|
1037
|
-
componentId(SpriteInstances),
|
|
1038
|
-
componentId(SpriteRegionOverride),
|
|
1039
|
-
componentId(Skin),
|
|
1040
|
-
componentId(Layer),
|
|
1041
|
-
componentId(Visibility),
|
|
1042
|
-
componentId(MorphWeights),
|
|
1043
|
-
componentId(Points),
|
|
1044
|
-
componentId(Lines),
|
|
1045
|
-
componentId(SortKey),
|
|
1046
|
-
componentId(Camera),
|
|
1047
|
-
componentId(MotionBlur),
|
|
1048
|
-
componentId(DirectionalLight),
|
|
1049
|
-
componentId(LightProbe),
|
|
1050
|
-
componentId(PointLight),
|
|
1051
|
-
componentId(PointLightShadow),
|
|
1052
|
-
componentId(SpotLight),
|
|
1053
|
-
componentId(Skylight),
|
|
1054
|
-
componentId(SkyboxBackground),
|
|
1055
|
-
componentId(PostProcessParams),
|
|
1056
|
-
componentId(ReflectionProbe),
|
|
1057
|
-
componentId(VolumetricFog),
|
|
1058
|
-
]);
|
|
1059
|
-
|
|
1060
|
-
// These components can change the retained renderable source. Other relevant
|
|
1061
|
-
// components affect frame resources and still require a composition rebuild;
|
|
1062
|
-
// they must not cause a renderable-only partial extraction to silently retain
|
|
1063
|
-
// stale lights, cameras, or environment facts.
|
|
1090
|
+
// Retained geometry consumes these component changes. Camera, lighting and
|
|
1091
|
+
// environment facts are freshly extracted by deriveFramePlan on every draw;
|
|
1092
|
+
// their edits must not recreate unrelated geometry or its submission history.
|
|
1064
1093
|
const RENDERABLE_SOURCE_COMPONENTS = [
|
|
1065
1094
|
GlobalTransform,
|
|
1066
1095
|
ChildOf,
|
|
@@ -1105,6 +1134,7 @@ function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQu
|
|
|
1105
1134
|
function createRenderChangeQueries(world: World): ReadonlyMap<number, Query> {
|
|
1106
1135
|
const queries = new Map<number, Query>();
|
|
1107
1136
|
for (const component of RENDERABLE_SOURCE_COMPONENTS) {
|
|
1137
|
+
if (component === GlobalTransform) continue;
|
|
1108
1138
|
const result = world.query({ read: [component], changed: [component] });
|
|
1109
1139
|
if (!result.ok) throw result.error;
|
|
1110
1140
|
const query = result.value as Query;
|
|
@@ -1190,7 +1220,7 @@ function dispatchForProjection(
|
|
|
1190
1220
|
dispatchBySlot: ReadonlyMap<number, readonly DispatchEntry[]>,
|
|
1191
1221
|
projection: RenderScene,
|
|
1192
1222
|
unavailableSlots: ReadonlySet<number>,
|
|
1193
|
-
):
|
|
1223
|
+
): DispatchEntry[] {
|
|
1194
1224
|
const dispatch: DispatchEntry[] = [];
|
|
1195
1225
|
const renderables = projection.materialize();
|
|
1196
1226
|
for (let renderableIndex = 0; renderableIndex < renderables.length; renderableIndex += 1) {
|
|
@@ -1200,7 +1230,9 @@ function dispatchForProjection(
|
|
|
1200
1230
|
if (slot === undefined) continue;
|
|
1201
1231
|
if (unavailableSlots.has(slot.slot)) continue;
|
|
1202
1232
|
for (const entry of dispatchBySlot.get(slot.slot) ?? []) {
|
|
1203
|
-
dispatch.push(
|
|
1233
|
+
dispatch.push(
|
|
1234
|
+
renderableIndex === entry.renderableIndex ? entry : { ...entry, renderableIndex },
|
|
1235
|
+
);
|
|
1204
1236
|
}
|
|
1205
1237
|
}
|
|
1206
1238
|
return dispatch;
|
|
@@ -1212,39 +1244,29 @@ function topologyDeltaForAvailable(
|
|
|
1212
1244
|
newlyUnavailable: readonly RenderSceneSlot[],
|
|
1213
1245
|
): RenderSceneApplyResult {
|
|
1214
1246
|
const updatedSlots = delta.updatedSlots.filter((slot) => !unavailableSlots.has(slot.slot));
|
|
1247
|
+
const unavailableUpdates = newlyUnavailable.map((slot) => ({
|
|
1248
|
+
...slot,
|
|
1249
|
+
snapshot: { ...slot.snapshot, gpuDrivenDraws: [] },
|
|
1250
|
+
}));
|
|
1215
1251
|
const contentUpdatedSlots = delta.contentUpdatedSlots?.filter(
|
|
1216
1252
|
(slot) => !unavailableSlots.has(slot.slot),
|
|
1217
1253
|
);
|
|
1254
|
+
const instanceUpdatedSlots = delta.instanceUpdatedSlots?.filter(
|
|
1255
|
+
(slot) => !unavailableSlots.has(slot.slot),
|
|
1256
|
+
);
|
|
1257
|
+
const contentBySlot =
|
|
1258
|
+
contentUpdatedSlots === undefined
|
|
1259
|
+
? undefined
|
|
1260
|
+
: new Map(contentUpdatedSlots.map((slot) => [slot.slot, slot]));
|
|
1261
|
+
for (const slot of unavailableUpdates) contentBySlot?.set(slot.slot, slot);
|
|
1218
1262
|
return {
|
|
1219
1263
|
...delta,
|
|
1220
|
-
updatedSlots: [
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
...slot,
|
|
1224
|
-
snapshot: { ...slot.snapshot, gpuDrivenDraws: [] },
|
|
1225
|
-
})),
|
|
1226
|
-
],
|
|
1227
|
-
...(contentUpdatedSlots === undefined ? {} : { contentUpdatedSlots }),
|
|
1264
|
+
updatedSlots: [...updatedSlots, ...unavailableUpdates],
|
|
1265
|
+
...(contentBySlot === undefined ? {} : { contentUpdatedSlots: [...contentBySlot.values()] }),
|
|
1266
|
+
...(instanceUpdatedSlots === undefined ? {} : { instanceUpdatedSlots }),
|
|
1228
1267
|
};
|
|
1229
1268
|
}
|
|
1230
1269
|
|
|
1231
|
-
function addSharedRefConsumers(
|
|
1232
|
-
projection: RenderScene,
|
|
1233
|
-
worldId: number,
|
|
1234
|
-
handle: number,
|
|
1235
|
-
target: Set<number>,
|
|
1236
|
-
): void {
|
|
1237
|
-
for (const slot of projection.slotsSnapshot()) {
|
|
1238
|
-
if (
|
|
1239
|
-
slot.worldId === worldId &&
|
|
1240
|
-
(slot.snapshot.assetHandle === handle ||
|
|
1241
|
-
slot.snapshot.materials.some((material) => material.materialHandle === handle))
|
|
1242
|
-
) {
|
|
1243
|
-
target.add(slot.entityKey);
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
1270
|
function frameDemandsTemporal(frame: ExtractedFrame): boolean {
|
|
1249
1271
|
const camera = frame.cameras[0];
|
|
1250
1272
|
return camera?.antialias === 'taa' || (camera?.motionBlur?.shutterAngle ?? 0) > 0;
|
|
@@ -1295,6 +1317,18 @@ function cullPersistentFrame(
|
|
|
1295
1317
|
) => worldBounds(snapshot),
|
|
1296
1318
|
): ExtractedFrame {
|
|
1297
1319
|
const planes = cameraFrusta(frame.cameras);
|
|
1320
|
+
// Shadow casters are not constrained by the display camera. Keep the
|
|
1321
|
+
// primary camera cull intact, but admit a caster that is outside every
|
|
1322
|
+
// camera frustum when its bounds intersect one of the active light-space
|
|
1323
|
+
// frusta. This is especially important for point-light cube faces: the
|
|
1324
|
+
// caster can be behind the camera while still occluding the receiver.
|
|
1325
|
+
const casterIndices = new Set(
|
|
1326
|
+
frame.dispatch
|
|
1327
|
+
.filter((entry) => entry.tags.LightMode === 'ShadowCaster')
|
|
1328
|
+
.map((entry) => entry.renderableIndex),
|
|
1329
|
+
);
|
|
1330
|
+
const shadowFrusta = casterIndices.size === 0 ? [] : buildShadowFrusta(frame.lights);
|
|
1331
|
+
const shadowOnly = new Set<number>();
|
|
1298
1332
|
const visible: RenderableSnapshot[] = [];
|
|
1299
1333
|
if (cullProjectedIndexScratch.length < candidates.length) {
|
|
1300
1334
|
cullProjectedIndexScratch = new Int32Array(candidates.length);
|
|
@@ -1313,6 +1347,7 @@ function cullPersistentFrame(
|
|
|
1313
1347
|
continue;
|
|
1314
1348
|
}
|
|
1315
1349
|
let isVisible = true;
|
|
1350
|
+
let isShadowVisible = false;
|
|
1316
1351
|
const candidateBounds = boundsOf(candidate, index);
|
|
1317
1352
|
if (candidateBounds !== undefined) {
|
|
1318
1353
|
total += 1;
|
|
@@ -1332,10 +1367,16 @@ function cullPersistentFrame(
|
|
|
1332
1367
|
break;
|
|
1333
1368
|
}
|
|
1334
1369
|
}
|
|
1370
|
+
if (!isVisible && casterIndices.has(index)) {
|
|
1371
|
+
isShadowVisible = shadowFrusta.some((lightPlanes) =>
|
|
1372
|
+
frustum.intersectsBox(lightPlanes, worldAabb as box3.Box3Like),
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1335
1375
|
}
|
|
1336
1376
|
if (!isVisible) {
|
|
1337
1377
|
culled += 1;
|
|
1338
|
-
continue;
|
|
1378
|
+
if (!isShadowVisible) continue;
|
|
1379
|
+
shadowOnly.add(index);
|
|
1339
1380
|
}
|
|
1340
1381
|
projectedIndex[index] = visible.length;
|
|
1341
1382
|
// The culling candidate is the persistent projection snapshot used for
|
|
@@ -1349,9 +1390,15 @@ function cullPersistentFrame(
|
|
|
1349
1390
|
const dispatch: DispatchEntry[] = [];
|
|
1350
1391
|
for (const entry of frame.dispatch) {
|
|
1351
1392
|
const candidateIndex = entry.renderableIndex;
|
|
1393
|
+
// Shadow-only candidates must never enter the main forward/transparent
|
|
1394
|
+
// dispatch. Their ShadowCaster entries remain available to the depth
|
|
1395
|
+
// passes, which consume the same validated ordered list.
|
|
1396
|
+
if (shadowOnly.has(candidateIndex) && entry.tags.LightMode !== 'ShadowCaster') continue;
|
|
1352
1397
|
const renderableIndex = projectedIndex[candidateIndex];
|
|
1353
1398
|
if (renderableIndex === undefined || renderableIndex < 0) continue;
|
|
1354
|
-
dispatch.push(
|
|
1399
|
+
dispatch.push(
|
|
1400
|
+
renderableIndex === entry.renderableIndex ? entry : { ...entry, renderableIndex },
|
|
1401
|
+
);
|
|
1355
1402
|
}
|
|
1356
1403
|
return {
|
|
1357
1404
|
...frame,
|
|
@@ -1391,12 +1438,15 @@ export class PersistentRenderScene {
|
|
|
1391
1438
|
private readonly materialSnapshotCaches: MaterialSnapshotCachesByWorld = new WeakMap();
|
|
1392
1439
|
private fullRebuilds = 0;
|
|
1393
1440
|
private worldEntitiesScanned = 0;
|
|
1441
|
+
private sceneTableUploadBytes = 0;
|
|
1394
1442
|
private noChangeFrames = 0;
|
|
1395
1443
|
private deltaFrames = 0;
|
|
1396
1444
|
private transformUpdates = 0;
|
|
1397
1445
|
private lastResyncReason: RenderSceneResyncReason | undefined;
|
|
1398
1446
|
private composition: PersistentCompositionEntry | undefined;
|
|
1399
1447
|
private gpuScene: GpuScene | undefined;
|
|
1448
|
+
private readonly retiredGpuScenes = new Set<GpuScene>();
|
|
1449
|
+
private shadowProjection: PersistentShadowCasterProjection | undefined;
|
|
1400
1450
|
private gpuOwner: object | undefined;
|
|
1401
1451
|
private gpuDevice: RhiDevice | undefined;
|
|
1402
1452
|
private gpuStatus: 'inactive' | 'unsupported' | 'resident' | 'rebuild-pending' | 'error' =
|
|
@@ -1424,6 +1474,7 @@ export class PersistentRenderScene {
|
|
|
1424
1474
|
}
|
|
1425
1475
|
| undefined;
|
|
1426
1476
|
private visibilityProjectionRevision = 0;
|
|
1477
|
+
private projectedIndexScratch = new Int32Array(0);
|
|
1427
1478
|
private lastActiveEntityKeys: ReadonlySet<number> | undefined;
|
|
1428
1479
|
/** Stable renderer-world/entity lookup; rebuilt only with composition topology. */
|
|
1429
1480
|
private readonly stableSlotByEntity = new Map<number, RenderSceneSlot>();
|
|
@@ -1473,6 +1524,10 @@ export class PersistentRenderScene {
|
|
|
1473
1524
|
return this.composition?.projection.slotsSnapshot() ?? [];
|
|
1474
1525
|
}
|
|
1475
1526
|
|
|
1527
|
+
shadowCasterProjection(): PersistentShadowCasterProjection | undefined {
|
|
1528
|
+
return this.shadowProjection;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1476
1531
|
/** Stable renderer-world/entity lookup shared by query and final projection paths. */
|
|
1477
1532
|
compositionSlotByStableEntity(): ReadonlyMap<number, RenderSceneSlot> {
|
|
1478
1533
|
return this.stableSlotByEntity;
|
|
@@ -1517,7 +1572,7 @@ export class PersistentRenderScene {
|
|
|
1517
1572
|
renderable.lods.length === 0 ||
|
|
1518
1573
|
bounds === undefined ||
|
|
1519
1574
|
bounds.length < 6 ||
|
|
1520
|
-
|
|
1575
|
+
!bounds.every(Number.isFinite)
|
|
1521
1576
|
) {
|
|
1522
1577
|
continue;
|
|
1523
1578
|
}
|
|
@@ -1594,15 +1649,19 @@ export class PersistentRenderScene {
|
|
|
1594
1649
|
) {
|
|
1595
1650
|
return cached.projection;
|
|
1596
1651
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1652
|
+
if (this.projectedIndexScratch.length < renderables.length) {
|
|
1653
|
+
this.projectedIndexScratch = new Int32Array(renderables.length);
|
|
1654
|
+
}
|
|
1655
|
+
const projectedIndex = this.projectedIndexScratch;
|
|
1656
|
+
projectedIndex.fill(-1, 0, renderables.length);
|
|
1599
1657
|
const projected: RenderableSnapshot[] = [];
|
|
1600
1658
|
const activeEntityKeys = new Set<number>();
|
|
1601
1659
|
let suppressed = 0;
|
|
1602
1660
|
for (let index = 0; index < renderables.length; index += 1) {
|
|
1603
1661
|
const renderable = renderables[index];
|
|
1604
1662
|
if (renderable === undefined) continue;
|
|
1605
|
-
const
|
|
1663
|
+
const stableEntity = this.stableEntityKey(worlds, renderable);
|
|
1664
|
+
const slot = this.stableSlotByEntity.get(stableEntity);
|
|
1606
1665
|
let draw = true;
|
|
1607
1666
|
if (slot !== undefined && (renderable.lods?.length ?? 0) > 0) {
|
|
1608
1667
|
const primitive = primitiveKey({
|
|
@@ -1613,9 +1672,7 @@ export class PersistentRenderScene {
|
|
|
1613
1672
|
});
|
|
1614
1673
|
const bounds = renderable.localAabb;
|
|
1615
1674
|
const validBounds =
|
|
1616
|
-
bounds !== undefined &&
|
|
1617
|
-
bounds.length >= 6 &&
|
|
1618
|
-
[...bounds].every((value) => Number.isFinite(value));
|
|
1675
|
+
bounds !== undefined && bounds.length >= 6 && bounds.every(Number.isFinite);
|
|
1619
1676
|
draw = decideVisibility({
|
|
1620
1677
|
authorVisible: renderable.authorVisible !== false,
|
|
1621
1678
|
validBounds,
|
|
@@ -1631,13 +1688,15 @@ export class PersistentRenderScene {
|
|
|
1631
1688
|
}
|
|
1632
1689
|
projectedIndex[index] = projected.length;
|
|
1633
1690
|
projected.push(renderable);
|
|
1634
|
-
activeEntityKeys.add(
|
|
1691
|
+
activeEntityKeys.add(stableEntity);
|
|
1635
1692
|
}
|
|
1636
1693
|
const projectedDispatch: DispatchEntry[] = [];
|
|
1637
1694
|
for (const entry of dispatch) {
|
|
1638
1695
|
const renderableIndex = projectedIndex[entry.renderableIndex];
|
|
1639
1696
|
if (renderableIndex === undefined || renderableIndex < 0) continue;
|
|
1640
|
-
projectedDispatch.push(
|
|
1697
|
+
projectedDispatch.push(
|
|
1698
|
+
renderableIndex === entry.renderableIndex ? entry : { ...entry, renderableIndex },
|
|
1699
|
+
);
|
|
1641
1700
|
}
|
|
1642
1701
|
const projection = {
|
|
1643
1702
|
renderables: Object.freeze(projected),
|
|
@@ -1725,6 +1784,7 @@ export class PersistentRenderScene {
|
|
|
1725
1784
|
leases?: readonly RenderReadLease[],
|
|
1726
1785
|
): ExtractedFrame {
|
|
1727
1786
|
this.worldEntitiesScanned = 0;
|
|
1787
|
+
this.sceneTableUploadBytes = 0;
|
|
1728
1788
|
const entry = this.composition;
|
|
1729
1789
|
const sameWorldSet =
|
|
1730
1790
|
entry !== undefined &&
|
|
@@ -1749,15 +1809,12 @@ export class PersistentRenderScene {
|
|
|
1749
1809
|
leaseSetPreserved;
|
|
1750
1810
|
if (
|
|
1751
1811
|
entry === undefined ||
|
|
1752
|
-
entry.catalogEpoch !== catalogEpoch ||
|
|
1753
|
-
entry.cameraOwner !== owner.cameraOwner ||
|
|
1754
|
-
entry.resourceOwner !== owner.resourceOwner ||
|
|
1755
1812
|
entry.worlds.length !== worlds.length ||
|
|
1756
1813
|
entry.worlds.some((world, index) => world !== worlds[index]) ||
|
|
1757
1814
|
leases === undefined ||
|
|
1758
1815
|
leases.length !== worlds.length
|
|
1759
1816
|
) {
|
|
1760
|
-
return this.
|
|
1817
|
+
return this.reconcileComposition(
|
|
1761
1818
|
worlds,
|
|
1762
1819
|
owner,
|
|
1763
1820
|
catalogEpoch,
|
|
@@ -1766,14 +1823,33 @@ export class PersistentRenderScene {
|
|
|
1766
1823
|
preserveVisibilityOnReorder,
|
|
1767
1824
|
);
|
|
1768
1825
|
}
|
|
1769
|
-
let
|
|
1826
|
+
let requiresSourceRefresh = false;
|
|
1770
1827
|
const changedEntitiesByWorld = worlds.map(
|
|
1771
1828
|
(_, worldId) => new Set(entry.pendingRenderableEntitiesByWorld[worldId] ?? []),
|
|
1772
1829
|
);
|
|
1830
|
+
const contentChangedByWorld = worlds.map(
|
|
1831
|
+
(_, worldId) => new Set(entry.pendingRenderableEntitiesByWorld[worldId] ?? []),
|
|
1832
|
+
);
|
|
1833
|
+
if (entry.catalogEpoch !== catalogEpoch) {
|
|
1834
|
+
for (const slot of entry.projection.slotsSnapshot()) {
|
|
1835
|
+
changedEntitiesByWorld[slot.worldId]?.add(slot.entityKey);
|
|
1836
|
+
contentChangedByWorld[slot.worldId]?.add(slot.entityKey);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
const instanceEpoch = this.options.instanceCollections?.epoch ?? 0;
|
|
1840
|
+
if (entry.instanceEpoch !== instanceEpoch) {
|
|
1841
|
+
for (const slot of entry.projection.slotsSnapshot()) {
|
|
1842
|
+
const id = slot.snapshot.instances?.collectionId;
|
|
1843
|
+
if (id === undefined) continue;
|
|
1844
|
+
const current = this.options.instanceCollections?.inspect(id);
|
|
1845
|
+
if (!current?.ok || current.value.revision !== slot.snapshot.instances?.revision) {
|
|
1846
|
+
changedEntitiesByWorld[slot.worldId]?.add(slot.entityKey);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1773
1850
|
const nextReadVersions = entry.readVersions.slice();
|
|
1774
1851
|
const structureChangedWorlds = new Set<number>();
|
|
1775
1852
|
const structuralComponentIdsByWorld = worlds.map(() => new Set<number>());
|
|
1776
|
-
let refreshStructureFacts = false;
|
|
1777
1853
|
let currentRenderableEntitiesByWorld: readonly (ReadonlySet<number> | undefined)[] | undefined;
|
|
1778
1854
|
const removedSlots: RenderSceneSlot[] = [];
|
|
1779
1855
|
for (let worldId = 0; worldId < worlds.length; worldId += 1) {
|
|
@@ -1781,24 +1857,23 @@ export class PersistentRenderScene {
|
|
|
1781
1857
|
if (world === undefined) continue;
|
|
1782
1858
|
const lease = leases?.[worldId];
|
|
1783
1859
|
if (lease === undefined || entry.leaseIdentities[worldId] !== lease.worldIdentity) {
|
|
1784
|
-
|
|
1860
|
+
requiresSourceRefresh = true;
|
|
1785
1861
|
break;
|
|
1786
1862
|
}
|
|
1787
1863
|
const version = entry.readVersions[worldId];
|
|
1788
1864
|
if (version === undefined) {
|
|
1789
|
-
|
|
1865
|
+
requiresSourceRefresh = true;
|
|
1790
1866
|
break;
|
|
1791
1867
|
}
|
|
1792
1868
|
let read: ReturnType<RenderReadLease['readChanges']>;
|
|
1793
1869
|
try {
|
|
1794
1870
|
read = lease.readChanges(version);
|
|
1795
1871
|
} catch {
|
|
1796
|
-
|
|
1872
|
+
requiresSourceRefresh = true;
|
|
1797
1873
|
break;
|
|
1798
1874
|
}
|
|
1799
1875
|
if (read.status === 'rebuild') {
|
|
1800
1876
|
structureChangedWorlds.add(worldId);
|
|
1801
|
-
refreshStructureFacts = true;
|
|
1802
1877
|
}
|
|
1803
1878
|
const changes = read.world;
|
|
1804
1879
|
const shared = read.sharedRefs;
|
|
@@ -1806,56 +1881,80 @@ export class PersistentRenderScene {
|
|
|
1806
1881
|
for (const record of shared.records) {
|
|
1807
1882
|
this.options.onSharedRefMutation?.(worldId, record.handle);
|
|
1808
1883
|
const changed = changedEntitiesByWorld[worldId];
|
|
1809
|
-
|
|
1810
|
-
|
|
1884
|
+
const content = contentChangedByWorld[worldId];
|
|
1885
|
+
if (changed !== undefined && content !== undefined) {
|
|
1886
|
+
entry.projection.addSharedRefConsumers(worldId, record.handle, content);
|
|
1887
|
+
for (const entity of content) changed.add(entity);
|
|
1888
|
+
}
|
|
1811
1889
|
}
|
|
1812
1890
|
}
|
|
1813
1891
|
for (const changedComponentId of changes.changedComponentIds) {
|
|
1814
1892
|
if (read.status === 'rebuild') {
|
|
1815
1893
|
structuralComponentIdsByWorld[worldId]?.add(changedComponentId);
|
|
1816
1894
|
}
|
|
1817
|
-
if (!
|
|
1818
|
-
if (!RENDERABLE_SOURCE_COMPONENT_IDS.has(changedComponentId)) {
|
|
1819
|
-
requiresRebuild = true;
|
|
1820
|
-
break;
|
|
1821
|
-
}
|
|
1895
|
+
if (!RENDERABLE_SOURCE_COMPONENT_IDS.has(changedComponentId)) continue;
|
|
1822
1896
|
const changed = changedEntitiesByWorld[worldId];
|
|
1823
1897
|
if (changed === undefined) continue;
|
|
1824
|
-
|
|
1825
|
-
refreshStructureFacts = true;
|
|
1826
|
-
// Visibility and parent changes affect the effective state of a
|
|
1827
|
-
// subtree. The normal update path evaluates the current rows; the
|
|
1828
|
-
// set is deliberately allowed to be broad, while empty work still
|
|
1829
|
-
// exits naturally without a candidate loop.
|
|
1830
|
-
for (const entity of collectRenderableEntities(world)) changed.add(entity);
|
|
1831
|
-
continue;
|
|
1832
|
-
}
|
|
1833
|
-
if (changedComponentId === componentId(GlobalTransform)) {
|
|
1834
|
-
const query = entry.renderChangeQueries[worldId]?.get(changedComponentId);
|
|
1835
|
-
for (const row of query ?? []) {
|
|
1836
|
-
const consumers = entry.skinConsumersByJoint.get(
|
|
1837
|
-
skinJointKey(worldId, row.entity as number),
|
|
1838
|
-
);
|
|
1839
|
-
for (const consumer of consumers ?? []) changed.add(consumer);
|
|
1840
|
-
}
|
|
1841
|
-
continue;
|
|
1842
|
-
}
|
|
1898
|
+
const otherChanged = contentChangedByWorld[worldId];
|
|
1843
1899
|
const query = entry.renderChangeQueries[worldId]?.get(changedComponentId);
|
|
1844
1900
|
if (query !== undefined) {
|
|
1845
|
-
for (const row of query)
|
|
1901
|
+
for (const row of query) {
|
|
1902
|
+
const entity = row.entity as number;
|
|
1903
|
+
if (VISIBILITY_DEPENDENCY_COMPONENT_IDS.has(changedComponentId)) {
|
|
1904
|
+
const affected = collectSubtree(world, row.entity);
|
|
1905
|
+
for (const dependent of affected) {
|
|
1906
|
+
if (
|
|
1907
|
+
!entry.projection.has(worldId, dependent) &&
|
|
1908
|
+
!world.hasComponent(dependent as EntityHandle, MeshRenderer)
|
|
1909
|
+
)
|
|
1910
|
+
continue;
|
|
1911
|
+
changed.add(dependent);
|
|
1912
|
+
otherChanged?.add(dependent);
|
|
1913
|
+
}
|
|
1914
|
+
} else {
|
|
1915
|
+
changed.add(entity);
|
|
1916
|
+
if (changedComponentId !== componentId(Instances)) otherChanged?.add(entity);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1846
1919
|
}
|
|
1847
1920
|
}
|
|
1848
|
-
if (
|
|
1921
|
+
if (requiresSourceRefresh) break;
|
|
1849
1922
|
nextReadVersions[worldId] = read.version;
|
|
1850
1923
|
}
|
|
1851
|
-
if (
|
|
1852
|
-
return this.
|
|
1924
|
+
if (requiresSourceRefresh) {
|
|
1925
|
+
return this.reconcileComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
1853
1926
|
}
|
|
1854
1927
|
|
|
1928
|
+
const transformSpans: WorldTransformSpan[] = [];
|
|
1929
|
+
for (let worldId = 0; worldId < entry.transformQueries.length; worldId += 1) {
|
|
1930
|
+
const query = entry.transformQueries[worldId];
|
|
1931
|
+
if (query === undefined) continue;
|
|
1932
|
+
for (const span of query.spans().unwrap()) {
|
|
1933
|
+
const changedWorlds = span.get(GlobalTransform).world;
|
|
1934
|
+
if (!(changedWorlds instanceof Float32Array)) {
|
|
1935
|
+
requiresSourceRefresh = true;
|
|
1936
|
+
break;
|
|
1937
|
+
}
|
|
1938
|
+
transformSpans.push({ worldId, entities: span.entities, worlds: changedWorlds });
|
|
1939
|
+
for (const entity of span.entities) {
|
|
1940
|
+
for (const consumer of entry.skinConsumersByJoint.get(skinJointKey(worldId, entity)) ??
|
|
1941
|
+
[]) {
|
|
1942
|
+
changedEntitiesByWorld[worldId]?.add(consumer);
|
|
1943
|
+
contentChangedByWorld[worldId]?.add(consumer);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
if (requiresSourceRefresh) break;
|
|
1948
|
+
}
|
|
1949
|
+
if (requiresSourceRefresh) {
|
|
1950
|
+
return this.reconcileComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
1951
|
+
}
|
|
1855
1952
|
if (structureChangedWorlds.size > 0) {
|
|
1856
1953
|
this.visibilityFacets.clear();
|
|
1857
1954
|
this.invalidateActiveEntityRevision();
|
|
1858
|
-
const currentByWorld = worlds.map((world) =>
|
|
1955
|
+
const currentByWorld = worlds.map((world, worldId) =>
|
|
1956
|
+
structureChangedWorlds.has(worldId) ? collectRenderableEntities(world) : undefined,
|
|
1957
|
+
);
|
|
1859
1958
|
currentRenderableEntitiesByWorld = currentByWorld;
|
|
1860
1959
|
for (let worldId = 0; worldId < currentByWorld.length; worldId += 1) {
|
|
1861
1960
|
const world = worlds[worldId];
|
|
@@ -1873,7 +1972,10 @@ export class PersistentRenderScene {
|
|
|
1873
1972
|
if (indexedWorldId !== worldId) continue;
|
|
1874
1973
|
const jointEntity = Number(jointKey.slice(separator + 1));
|
|
1875
1974
|
if (world.hasComponent(jointEntity as EntityHandle, GlobalTransform)) continue;
|
|
1876
|
-
for (const consumer of consumers)
|
|
1975
|
+
for (const consumer of consumers) {
|
|
1976
|
+
changed.add(consumer);
|
|
1977
|
+
contentChangedByWorld[worldId]?.add(consumer);
|
|
1978
|
+
}
|
|
1877
1979
|
}
|
|
1878
1980
|
for (const entity of current) {
|
|
1879
1981
|
if (entry.projection.slot(worldId, entity) === undefined) changed.add(entity);
|
|
@@ -1889,26 +1991,14 @@ export class PersistentRenderScene {
|
|
|
1889
1991
|
const membershipChanged = sourceMembershipChanged(world, slot);
|
|
1890
1992
|
if (scanAllMembership || membershipChanged) {
|
|
1891
1993
|
changed.add(slot.entityKey);
|
|
1892
|
-
|
|
1893
|
-
slot.snapshot.instances !== undefined &&
|
|
1894
|
-
!world.hasComponent(slot.entityKey as EntityHandle, Instances)
|
|
1895
|
-
) {
|
|
1896
|
-
this.options.instanceCollections?.release(world, slot.entityKey);
|
|
1897
|
-
}
|
|
1898
|
-
if (
|
|
1899
|
-
slot.snapshot.skin !== undefined &&
|
|
1900
|
-
!world.hasComponent(slot.entityKey as EntityHandle, Skin)
|
|
1901
|
-
) {
|
|
1902
|
-
this.options
|
|
1903
|
-
.getSkinPaletteAllocator?.()
|
|
1904
|
-
?.releaseSliceFor(`${world.identity}:${slot.entityKey}`);
|
|
1905
|
-
}
|
|
1994
|
+
contentChangedByWorld[worldId]?.add(slot.entityKey);
|
|
1906
1995
|
}
|
|
1907
1996
|
}
|
|
1908
1997
|
}
|
|
1909
1998
|
for (const slot of entry.projection.slotsSnapshot()) {
|
|
1910
1999
|
if (slot.worldId !== worldId || current.has(slot.entityKey)) continue;
|
|
1911
2000
|
removedSlots.push(slot);
|
|
2001
|
+
changedEntitiesByWorld[worldId]?.add(slot.entityKey);
|
|
1912
2002
|
}
|
|
1913
2003
|
this.worldEntitiesScanned += current.size;
|
|
1914
2004
|
}
|
|
@@ -1925,7 +2015,7 @@ export class PersistentRenderScene {
|
|
|
1925
2015
|
|
|
1926
2016
|
let changedDelta: RenderSceneApplyResult | undefined;
|
|
1927
2017
|
const hasChangedEntities = changedEntitiesByWorld.some((entities) => entities.size > 0);
|
|
1928
|
-
if (hasChangedEntities || removedSlots.length > 0) {
|
|
2018
|
+
if (hasChangedEntities || removedSlots.length > 0 || transformSpans.length > 0) {
|
|
1929
2019
|
const operations: RenderSceneOperation[] = [];
|
|
1930
2020
|
const removedDemand: TransmissionDemandOperation[] = [];
|
|
1931
2021
|
const replacedSkinSnapshots: RenderableSnapshot[] = [];
|
|
@@ -1940,11 +2030,6 @@ export class PersistentRenderScene {
|
|
|
1940
2030
|
if (slot.snapshot.skinJointEntities !== undefined) {
|
|
1941
2031
|
removeSkinConsumer(entry.skinConsumersByJoint, slot.snapshot);
|
|
1942
2032
|
}
|
|
1943
|
-
const world = worlds[slot.worldId];
|
|
1944
|
-
if (world !== undefined) this.options.instanceCollections?.release(world, slot.entityKey);
|
|
1945
|
-
this.options
|
|
1946
|
-
.getSkinPaletteAllocator?.()
|
|
1947
|
-
?.releaseSliceFor(`${world?.identity ?? slot.worldId}:${slot.entityKey}`);
|
|
1948
2033
|
for (
|
|
1949
2034
|
let materialIndex = 0;
|
|
1950
2035
|
materialIndex < slot.snapshot.materials.length;
|
|
@@ -1969,15 +2054,39 @@ export class PersistentRenderScene {
|
|
|
1969
2054
|
}
|
|
1970
2055
|
}
|
|
1971
2056
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
2057
|
+
let partial: ExtractedFrame | undefined;
|
|
2058
|
+
try {
|
|
2059
|
+
partial = hasChangedEntities
|
|
2060
|
+
? buildCandidateFrame({ kind: 'partial', entitiesByWorld: changedEntitiesByWorld })
|
|
2061
|
+
: undefined;
|
|
2062
|
+
} catch (cause) {
|
|
2063
|
+
// Change queries have been observed, but this publication never ran.
|
|
2064
|
+
// Retain every consumed transform identity for the producer retry.
|
|
2065
|
+
for (const span of transformSpans) {
|
|
2066
|
+
const pending = entry.pendingRenderableEntitiesByWorld[span.worldId];
|
|
2067
|
+
for (const entity of span.entities) {
|
|
2068
|
+
if (entry.projection.has(span.worldId, entity)) pending?.add(entity);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
throw cause;
|
|
2072
|
+
}
|
|
1975
2073
|
const publishedEntitiesByWorld = worlds.map(() => new Set<number>());
|
|
1976
2074
|
for (const snapshot of partial?.renderables ?? []) {
|
|
1977
|
-
|
|
2075
|
+
const existing = entry.projection.slot(snapshot.worldId, snapshot.entityKey);
|
|
2076
|
+
const contentChanged =
|
|
2077
|
+
existing === undefined ||
|
|
2078
|
+
contentChangedByWorld[snapshot.worldId]?.has(snapshot.entityKey);
|
|
2079
|
+
operations.push({
|
|
2080
|
+
kind: 'update',
|
|
2081
|
+
worldId: snapshot.worldId,
|
|
2082
|
+
entityKey: snapshot.entityKey,
|
|
2083
|
+
...(contentChanged || snapshot.instances === undefined
|
|
2084
|
+
? { snapshot }
|
|
2085
|
+
: { instances: snapshot.instances }),
|
|
2086
|
+
});
|
|
1978
2087
|
publishedEntitiesByWorld[snapshot.worldId]?.add(snapshot.entityKey);
|
|
1979
2088
|
}
|
|
1980
|
-
changedDelta = entry.projection.apply(operations);
|
|
2089
|
+
changedDelta = entry.projection.apply(operations, transformSpans);
|
|
1981
2090
|
const publishedSkinKeys = new Set<string>();
|
|
1982
2091
|
for (const snapshot of partial?.renderables ?? []) {
|
|
1983
2092
|
publishedSkinKeys.add(`${snapshot.worldId}:${snapshot.entityKey}`);
|
|
@@ -2005,17 +2114,21 @@ export class PersistentRenderScene {
|
|
|
2005
2114
|
for (const removed of changedDelta.removedSlots) entry.dispatchBySlot.delete(removed.slot);
|
|
2006
2115
|
for (const recreated of changedDelta.recreatedSlots)
|
|
2007
2116
|
entry.dispatchBySlot.delete(recreated.slot);
|
|
2117
|
+
if (partial !== undefined || removedSlots.length > 0) {
|
|
2118
|
+
const worldIndices = new Map(worlds.map((world, index) => [world, index]));
|
|
2119
|
+
entry.hiddenEntityReports = [
|
|
2120
|
+
...entry.hiddenEntityReports.filter((report) => {
|
|
2121
|
+
const worldId = worldIndices.get(report.world);
|
|
2122
|
+
return (
|
|
2123
|
+
worldId !== undefined &&
|
|
2124
|
+
!changedEntitiesByWorld[worldId]?.has(report.entity) &&
|
|
2125
|
+
currentRenderableEntitiesByWorld?.[worldId]?.has(report.entity) !== false
|
|
2126
|
+
);
|
|
2127
|
+
}),
|
|
2128
|
+
...(partial?.hiddenEntityReports ?? []),
|
|
2129
|
+
];
|
|
2130
|
+
}
|
|
2008
2131
|
if (partial !== undefined) {
|
|
2009
|
-
if (refreshStructureFacts) {
|
|
2010
|
-
entry.structure = {
|
|
2011
|
-
...entry.structure,
|
|
2012
|
-
dispatch: partial.dispatch,
|
|
2013
|
-
visibilityStats: partial.visibilityStats,
|
|
2014
|
-
visibilitySnapshots: partial.visibilitySnapshots,
|
|
2015
|
-
featureVisibilitySnapshots: partial.featureVisibilitySnapshots,
|
|
2016
|
-
hiddenEntityReports: partial.hiddenEntityReports,
|
|
2017
|
-
};
|
|
2018
|
-
}
|
|
2019
2132
|
const partialDispatch = dispatchEntriesBySlot(partial, entry.projection);
|
|
2020
2133
|
for (const [slot, dispatch] of partialDispatch) entry.dispatchBySlot.set(slot, dispatch);
|
|
2021
2134
|
// A changed renderable which now fails producer validation still owns
|
|
@@ -2067,8 +2180,10 @@ export class PersistentRenderScene {
|
|
|
2067
2180
|
for (const removed of changedDelta.removedSlots) entry.unavailableSlots.delete(removed.slot);
|
|
2068
2181
|
for (const recreated of changedDelta.recreatedSlots)
|
|
2069
2182
|
entry.unavailableSlots.delete(recreated.slot);
|
|
2070
|
-
|
|
2071
|
-
|
|
2183
|
+
if (partial !== undefined || removedSlots.length > 0) {
|
|
2184
|
+
entry.dispatchRevision += 1;
|
|
2185
|
+
entry.dispatchCache = undefined;
|
|
2186
|
+
}
|
|
2072
2187
|
entry.topology.apply(
|
|
2073
2188
|
topologyDeltaForAvailable(changedDelta, entry.unavailableSlots, newlyUnavailable),
|
|
2074
2189
|
);
|
|
@@ -2084,41 +2199,13 @@ export class PersistentRenderScene {
|
|
|
2084
2199
|
this.transformUpdates += changedDelta.updated;
|
|
2085
2200
|
}
|
|
2086
2201
|
|
|
2087
|
-
|
|
2088
|
-
for (let worldId = 0; worldId < entry.transformQueries.length; worldId += 1) {
|
|
2089
|
-
const query = entry.transformQueries[worldId];
|
|
2090
|
-
if (query === undefined) continue;
|
|
2091
|
-
for (const span of query.spans().unwrap()) {
|
|
2092
|
-
const changedWorlds = span.get(GlobalTransform).world;
|
|
2093
|
-
if (!(changedWorlds instanceof Float32Array)) {
|
|
2094
|
-
requiresRebuild = true;
|
|
2095
|
-
break;
|
|
2096
|
-
}
|
|
2097
|
-
transformSpans.push({ worldId, entities: span.entities, worlds: changedWorlds });
|
|
2098
|
-
}
|
|
2099
|
-
if (requiresRebuild) break;
|
|
2100
|
-
}
|
|
2101
|
-
if (requiresRebuild) {
|
|
2102
|
-
return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
2103
|
-
}
|
|
2104
|
-
if (transformSpans.length === 0 && changedDelta === undefined) {
|
|
2202
|
+
if (changedDelta === undefined) {
|
|
2105
2203
|
this.noChangeFrames += 1;
|
|
2106
2204
|
this.syncGpuOwner(entry.token, entry.projection);
|
|
2107
|
-
const result = this.deriveFramePlan(entry, buildCandidateFrame);
|
|
2108
|
-
for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
|
|
2109
|
-
const version = nextReadVersions[worldId];
|
|
2110
|
-
if (version !== undefined) entry.readVersions[worldId] = version;
|
|
2111
|
-
}
|
|
2112
|
-
return result;
|
|
2113
|
-
}
|
|
2114
|
-
if (transformSpans.length > 0) {
|
|
2115
|
-
const delta = entry.projection.applyTransformSpans(transformSpans);
|
|
2116
|
-
entry.topology.apply(topologyDeltaForAvailable(delta, entry.unavailableSlots, []));
|
|
2117
|
-
this.syncGpuOwner(entry.token, entry.projection, delta);
|
|
2118
|
-
this.deltaFrames += 1;
|
|
2119
|
-
this.transformUpdates += delta.updated;
|
|
2120
2205
|
}
|
|
2121
2206
|
const result = this.deriveFramePlan(entry, buildCandidateFrame);
|
|
2207
|
+
entry.catalogEpoch = catalogEpoch;
|
|
2208
|
+
entry.instanceEpoch = instanceEpoch;
|
|
2122
2209
|
for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
|
|
2123
2210
|
const version = nextReadVersions[worldId];
|
|
2124
2211
|
if (version !== undefined) entry.readVersions[worldId] = version;
|
|
@@ -2128,6 +2215,7 @@ export class PersistentRenderScene {
|
|
|
2128
2215
|
|
|
2129
2216
|
invalidate(): void {
|
|
2130
2217
|
this.composition = undefined;
|
|
2218
|
+
this.shadowProjection = undefined;
|
|
2131
2219
|
this.temporalCapture = undefined;
|
|
2132
2220
|
this.visibilityFacets.clear();
|
|
2133
2221
|
this.invalidateActiveEntityRevision();
|
|
@@ -2150,16 +2238,9 @@ export class PersistentRenderScene {
|
|
|
2150
2238
|
? this.composition
|
|
2151
2239
|
: undefined;
|
|
2152
2240
|
if (detachedComposition !== undefined) {
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
this.options.instanceCollections?.release(world, slot.entityKey);
|
|
2156
|
-
if (slot.snapshot.skin === undefined) continue;
|
|
2157
|
-
this.options
|
|
2158
|
-
.getSkinPaletteAllocator?.()
|
|
2159
|
-
?.releaseSliceFor(`${world.identity}:${slot.entityKey}`);
|
|
2160
|
-
}
|
|
2241
|
+
this.composition = undefined;
|
|
2242
|
+
this.shadowProjection = undefined;
|
|
2161
2243
|
}
|
|
2162
|
-
if (detachedComposition !== undefined) this.composition = undefined;
|
|
2163
2244
|
if (detachedComposition !== undefined) this.temporalCapture = undefined;
|
|
2164
2245
|
if (detachedComposition !== undefined) {
|
|
2165
2246
|
this.visibilityFacets.clear();
|
|
@@ -2171,7 +2252,7 @@ export class PersistentRenderScene {
|
|
|
2171
2252
|
this.stableSlotByEntity.clear();
|
|
2172
2253
|
}
|
|
2173
2254
|
if (this.gpuOwner !== world && this.gpuOwner !== detachedComposition?.token) return;
|
|
2174
|
-
this.gpuScene
|
|
2255
|
+
this.retireGpuScene(this.gpuScene);
|
|
2175
2256
|
this.gpuScene = undefined;
|
|
2176
2257
|
this.gpuOwner = undefined;
|
|
2177
2258
|
this.gpuDevice = undefined;
|
|
@@ -2181,6 +2262,7 @@ export class PersistentRenderScene {
|
|
|
2181
2262
|
/** Drop only device-owned tables; the CPU projection remains the recovery authority. */
|
|
2182
2263
|
resetGpuForRecover(): void {
|
|
2183
2264
|
if (this.composition !== undefined) this.composition.gpuDrivenSceneRequired = true;
|
|
2265
|
+
this.retireGpuScene(this.gpuScene);
|
|
2184
2266
|
this.gpuScene = undefined;
|
|
2185
2267
|
this.gpuOwner = undefined;
|
|
2186
2268
|
this.gpuDevice = undefined;
|
|
@@ -2256,6 +2338,7 @@ export class PersistentRenderScene {
|
|
|
2256
2338
|
publish: () => {
|
|
2257
2339
|
if (published || released) return;
|
|
2258
2340
|
published = true;
|
|
2341
|
+
this.retireGpuScene(this.gpuScene);
|
|
2259
2342
|
this.gpuScene = scene;
|
|
2260
2343
|
this.gpuOwner = composition.token;
|
|
2261
2344
|
this.gpuDevice = device;
|
|
@@ -2301,12 +2384,13 @@ export class PersistentRenderScene {
|
|
|
2301
2384
|
}
|
|
2302
2385
|
|
|
2303
2386
|
dispose(): void {
|
|
2304
|
-
this.gpuScene
|
|
2387
|
+
this.retireGpuScene(this.gpuScene);
|
|
2305
2388
|
this.gpuScene = undefined;
|
|
2306
2389
|
this.gpuOwner = undefined;
|
|
2307
2390
|
this.gpuDevice = undefined;
|
|
2308
2391
|
this.gpuStatus = 'inactive';
|
|
2309
2392
|
this.composition = undefined;
|
|
2393
|
+
this.shadowProjection = undefined;
|
|
2310
2394
|
this.temporalCapture = undefined;
|
|
2311
2395
|
this.visibilityFacets.clear();
|
|
2312
2396
|
this.invalidateActiveEntityRevision();
|
|
@@ -2316,6 +2400,55 @@ export class PersistentRenderScene {
|
|
|
2316
2400
|
this.pointsLinesInspections = [];
|
|
2317
2401
|
}
|
|
2318
2402
|
|
|
2403
|
+
private retireGpuScene(scene: GpuScene | undefined, device = this.gpuDevice): void {
|
|
2404
|
+
if (scene === undefined) return;
|
|
2405
|
+
if (device === undefined) {
|
|
2406
|
+
scene.dispose();
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
this.retiredGpuScenes.add(scene);
|
|
2410
|
+
const release = (): void => {
|
|
2411
|
+
if (!this.retiredGpuScenes.delete(scene)) return;
|
|
2412
|
+
scene.dispose();
|
|
2413
|
+
};
|
|
2414
|
+
// Retirement can happen while the current frame is still being encoded
|
|
2415
|
+
// (for example when a new composition token is published). Calling
|
|
2416
|
+
// onSubmittedWorkDone() synchronously at that point observes only work
|
|
2417
|
+
// already queued and may resolve before the frame that still references
|
|
2418
|
+
// this scene is submitted. Defer fence acquisition to the next
|
|
2419
|
+
// microtask, after the synchronous record/submit transaction has had a
|
|
2420
|
+
// chance to enqueue its command buffer.
|
|
2421
|
+
void Promise.resolve()
|
|
2422
|
+
.then(() => {
|
|
2423
|
+
try {
|
|
2424
|
+
return device.queue.onSubmittedWorkDone();
|
|
2425
|
+
} catch {
|
|
2426
|
+
return undefined;
|
|
2427
|
+
}
|
|
2428
|
+
})
|
|
2429
|
+
.then(release, release);
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
/** Read existing culling bounds; never expose cached mutable arrays. */
|
|
2433
|
+
bounds(world: World, entity: number): RenderSceneBounds | undefined {
|
|
2434
|
+
const entry = this.composition;
|
|
2435
|
+
const worldId = entry?.worlds.indexOf(world) ?? -1;
|
|
2436
|
+
if (entry === undefined || worldId < 0) return undefined;
|
|
2437
|
+
const slot = entry.projection.slot(worldId, entity);
|
|
2438
|
+
if (slot === undefined) return undefined;
|
|
2439
|
+
const bounds = entry.projection.cullingWorldBoundsAt(slot);
|
|
2440
|
+
if (bounds === undefined) return undefined;
|
|
2441
|
+
for (const axis of [0, 1, 2] as const) {
|
|
2442
|
+
if (
|
|
2443
|
+
!Number.isFinite(bounds.min[axis]) ||
|
|
2444
|
+
!Number.isFinite(bounds.max[axis]) ||
|
|
2445
|
+
bounds.min[axis] > bounds.max[axis]
|
|
2446
|
+
)
|
|
2447
|
+
return undefined;
|
|
2448
|
+
}
|
|
2449
|
+
return { min: [...bounds.min], max: [...bounds.max] };
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2319
2452
|
inspect(): PersistentRenderSceneInspection {
|
|
2320
2453
|
const entry = this.composition;
|
|
2321
2454
|
const pointsLines = this.pointsLinesInspections;
|
|
@@ -2327,6 +2460,9 @@ export class PersistentRenderScene {
|
|
|
2327
2460
|
transformUpdates: this.transformUpdates,
|
|
2328
2461
|
lastResyncReason: this.lastResyncReason,
|
|
2329
2462
|
projectionRecords: entry?.projection.inspect().records.length ?? 0,
|
|
2463
|
+
...(entry === undefined
|
|
2464
|
+
? {}
|
|
2465
|
+
: { instanceBoundsCache: entry.projection.inspect().instanceBoundsCache }),
|
|
2330
2466
|
...(entry === undefined ? {} : { probeBlend: entry.probeProjection.inspect() }),
|
|
2331
2467
|
topology:
|
|
2332
2468
|
entry?.topology.inspect() ??
|
|
@@ -2353,6 +2489,11 @@ export class PersistentRenderScene {
|
|
|
2353
2489
|
plan: entry.topology.plan(),
|
|
2354
2490
|
slots: entry.projection.slotsSnapshot(),
|
|
2355
2491
|
worldKeys: entry.worldKeys,
|
|
2492
|
+
structureMetrics: {
|
|
2493
|
+
worldEntitiesScanned: this.worldEntitiesScanned,
|
|
2494
|
+
sceneTableUploadBytes: this.sceneTableUploadBytes,
|
|
2495
|
+
paletteUploadBytes: 0,
|
|
2496
|
+
},
|
|
2356
2497
|
};
|
|
2357
2498
|
}
|
|
2358
2499
|
|
|
@@ -2408,9 +2549,9 @@ export class PersistentRenderScene {
|
|
|
2408
2549
|
return { status: 'inactive' };
|
|
2409
2550
|
}
|
|
2410
2551
|
|
|
2411
|
-
private
|
|
2552
|
+
private reconcileComposition(
|
|
2412
2553
|
worlds: readonly World[],
|
|
2413
|
-
|
|
2554
|
+
_owner: { readonly cameraOwner: number; readonly resourceOwner: number },
|
|
2414
2555
|
catalogEpoch: number,
|
|
2415
2556
|
buildCandidateFrame: (request: PersistentRenderCandidateRequest) => ExtractedFrame,
|
|
2416
2557
|
leases?: readonly RenderReadLease[],
|
|
@@ -2471,8 +2612,28 @@ export class PersistentRenderScene {
|
|
|
2471
2612
|
renderables: sortedRenderables,
|
|
2472
2613
|
dispatch: sortedDispatch,
|
|
2473
2614
|
};
|
|
2474
|
-
const projection = new RenderScene();
|
|
2475
|
-
|
|
2615
|
+
const projection = previousEntry?.projection ?? new RenderScene();
|
|
2616
|
+
if (previousEntry !== undefined) {
|
|
2617
|
+
projection.remapWorlds(previousEntry.worlds.map((world) => worlds.indexOf(world)));
|
|
2618
|
+
}
|
|
2619
|
+
const desired = new Set(
|
|
2620
|
+
sortedRenderables.map((snapshot) => identityKey(snapshot.worldId, snapshot.entityKey)),
|
|
2621
|
+
);
|
|
2622
|
+
const operations: RenderSceneOperation[] = [];
|
|
2623
|
+
for (const slot of projection.slotsSnapshot()) {
|
|
2624
|
+
if (!desired.has(identityKey(slot.worldId, slot.entityKey))) {
|
|
2625
|
+
operations.push({ kind: 'remove', worldId: slot.worldId, entityKey: slot.entityKey });
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
for (const snapshot of sortedRenderables) {
|
|
2629
|
+
operations.push({
|
|
2630
|
+
kind: 'update',
|
|
2631
|
+
worldId: snapshot.worldId,
|
|
2632
|
+
entityKey: snapshot.entityKey,
|
|
2633
|
+
snapshot,
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
const delta = projection.apply(operations);
|
|
2476
2637
|
this.stableSlotByEntity.clear();
|
|
2477
2638
|
for (const slot of projection.slotsSnapshot()) {
|
|
2478
2639
|
const stableWorld = worldKeys[slot.worldId] ?? slot.worldId;
|
|
@@ -2480,28 +2641,20 @@ export class PersistentRenderScene {
|
|
|
2480
2641
|
}
|
|
2481
2642
|
const transmissionDemand = new PersistentTransmissionDemandProjection();
|
|
2482
2643
|
transmissionDemand.apply(transmissionDemandOperations(candidateFrame.renderables));
|
|
2483
|
-
const topology = new BatchTopology();
|
|
2484
|
-
topology.
|
|
2644
|
+
const topology = previousEntry?.topology ?? new BatchTopology();
|
|
2645
|
+
topology.apply(delta);
|
|
2485
2646
|
const skinConsumersByJoint = new Map<string, Set<number>>();
|
|
2486
2647
|
for (const snapshot of stableCandidateFrame.renderables) {
|
|
2487
2648
|
if (snapshot.skinJointEntities !== undefined) addSkinConsumer(skinConsumersByJoint, snapshot);
|
|
2488
2649
|
}
|
|
2489
|
-
const token = {};
|
|
2650
|
+
const token = previousEntry?.token ?? {};
|
|
2490
2651
|
const entry: PersistentCompositionEntry = {
|
|
2491
2652
|
token,
|
|
2492
2653
|
worlds: [...worlds],
|
|
2493
|
-
cameraOwner: owner.cameraOwner,
|
|
2494
|
-
resourceOwner: owner.resourceOwner,
|
|
2495
2654
|
projection,
|
|
2496
2655
|
topology,
|
|
2497
2656
|
transmissionDemand,
|
|
2498
|
-
|
|
2499
|
-
dispatch: stableCandidateFrame.dispatch,
|
|
2500
|
-
visibilityStats: stableCandidateFrame.visibilityStats,
|
|
2501
|
-
visibilitySnapshots: stableCandidateFrame.visibilitySnapshots,
|
|
2502
|
-
featureVisibilitySnapshots: stableCandidateFrame.featureVisibilitySnapshots,
|
|
2503
|
-
hiddenEntityReports: stableCandidateFrame.hiddenEntityReports,
|
|
2504
|
-
},
|
|
2657
|
+
hiddenEntityReports: stableCandidateFrame.hiddenEntityReports,
|
|
2505
2658
|
worldKeys,
|
|
2506
2659
|
readVersions:
|
|
2507
2660
|
leases === undefined || leases.length !== worlds.length
|
|
@@ -2526,6 +2679,7 @@ export class PersistentRenderScene {
|
|
|
2526
2679
|
pendingRenderableEntitiesByWorld: worlds.map(() => new Set<number>()),
|
|
2527
2680
|
unavailableSlots: new Set<number>(),
|
|
2528
2681
|
catalogEpoch,
|
|
2682
|
+
instanceEpoch: this.options.instanceCollections?.epoch ?? 0,
|
|
2529
2683
|
probeProjection: new ProbeBlendSceneProjection(),
|
|
2530
2684
|
temporalDemanded: frameDemandsTemporal(candidateFrame),
|
|
2531
2685
|
gpuDrivenSceneRequired: true,
|
|
@@ -2539,8 +2693,8 @@ export class PersistentRenderScene {
|
|
|
2539
2693
|
// later rebuild.
|
|
2540
2694
|
this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
|
|
2541
2695
|
this.projectProbes(entry, candidateFrame);
|
|
2542
|
-
this.syncGpuOwner(token, projection,
|
|
2543
|
-
this.fullRebuilds += 1;
|
|
2696
|
+
this.syncGpuOwner(token, projection, delta);
|
|
2697
|
+
if (previousEntry === undefined) this.fullRebuilds += 1;
|
|
2544
2698
|
this.lastResyncReason = 'attach';
|
|
2545
2699
|
const materialized = projection.materialize();
|
|
2546
2700
|
const candidates = entry.temporalDemanded
|
|
@@ -2553,16 +2707,14 @@ export class PersistentRenderScene {
|
|
|
2553
2707
|
entry.probeProjection,
|
|
2554
2708
|
);
|
|
2555
2709
|
const slots = projection.slotsSnapshot();
|
|
2556
|
-
const
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
},
|
|
2565
|
-
);
|
|
2710
|
+
const ownedFrame = this.projectShadowOwnership(frameWithProbes);
|
|
2711
|
+
this.shadowProjection = { renderables: ownedFrame.renderables, dispatch: ownedFrame.dispatch };
|
|
2712
|
+
const result = cullPersistentFrame(ownedFrame, ownedFrame.renderables, (snapshot, index) => {
|
|
2713
|
+
const slot = slots[index];
|
|
2714
|
+
return slot === undefined
|
|
2715
|
+
? projection.cullingWorldBounds(snapshot)
|
|
2716
|
+
: projection.cullingWorldBoundsAt(slot);
|
|
2717
|
+
});
|
|
2566
2718
|
this.frameEpoch += 1;
|
|
2567
2719
|
this.visibilityFacetFrame = undefined;
|
|
2568
2720
|
this.visibilityProjectionCache = undefined;
|
|
@@ -2589,17 +2741,17 @@ export class PersistentRenderScene {
|
|
|
2589
2741
|
): ExtractedFrame {
|
|
2590
2742
|
const resourceFrame = buildCandidateFrame('none');
|
|
2591
2743
|
const cachedDispatch = entry.dispatchCache;
|
|
2592
|
-
const dispatch
|
|
2593
|
-
|
|
2744
|
+
const dispatch =
|
|
2745
|
+
cachedDispatch?.revision === entry.dispatchRevision
|
|
2594
2746
|
? cachedDispatch.value
|
|
2595
|
-
: dispatchForProjection(entry.dispatchBySlot, entry.projection, entry.unavailableSlots)
|
|
2596
|
-
];
|
|
2747
|
+
: dispatchForProjection(entry.dispatchBySlot, entry.projection, entry.unavailableSlots);
|
|
2597
2748
|
if (cachedDispatch?.revision !== entry.dispatchRevision) {
|
|
2598
2749
|
entry.dispatchCache = { revision: entry.dispatchRevision, value: dispatch };
|
|
2599
2750
|
}
|
|
2600
2751
|
const candidateFrame: ExtractedFrame = {
|
|
2601
2752
|
...resourceFrame,
|
|
2602
|
-
|
|
2753
|
+
hiddenEntityReports: entry.hiddenEntityReports,
|
|
2754
|
+
visibilityStats: { explicitlyHidden: entry.hiddenEntityReports.length },
|
|
2603
2755
|
dispatch,
|
|
2604
2756
|
};
|
|
2605
2757
|
entry.temporalDemanded = frameDemandsTemporal(candidateFrame);
|
|
@@ -2626,22 +2778,55 @@ export class PersistentRenderScene {
|
|
|
2626
2778
|
entry.probeProjection,
|
|
2627
2779
|
);
|
|
2628
2780
|
const slots = entry.projection.slotsSnapshot();
|
|
2629
|
-
const
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
},
|
|
2638
|
-
);
|
|
2781
|
+
const ownedFrame = this.projectShadowOwnership(frameWithProbes);
|
|
2782
|
+
this.shadowProjection = { renderables: ownedFrame.renderables, dispatch: ownedFrame.dispatch };
|
|
2783
|
+
const result = cullPersistentFrame(ownedFrame, ownedFrame.renderables, (snapshot, index) => {
|
|
2784
|
+
const slot = slots[index];
|
|
2785
|
+
return slot === undefined
|
|
2786
|
+
? entry.projection.cullingWorldBounds(snapshot)
|
|
2787
|
+
: entry.projection.cullingWorldBoundsAt(slot);
|
|
2788
|
+
});
|
|
2639
2789
|
this.frameEpoch += 1;
|
|
2640
2790
|
this.visibilityFacetFrame = undefined;
|
|
2641
2791
|
this.visibilityProjectionCache = undefined;
|
|
2642
2792
|
return result;
|
|
2643
2793
|
}
|
|
2644
2794
|
|
|
2795
|
+
private projectShadowOwnership(frame: ExtractedFrame): ExtractedFrame {
|
|
2796
|
+
const shadowCasterEntityKeys = new Set<number>();
|
|
2797
|
+
const shadowCasterDrawKeys = new Set<string>();
|
|
2798
|
+
const shadowCasterMembership: NonNullable<ExtractedFrame['shadowCasterMembership']>[number][] =
|
|
2799
|
+
[];
|
|
2800
|
+
for (
|
|
2801
|
+
let renderableIndex = 0;
|
|
2802
|
+
renderableIndex < frame.renderables.length;
|
|
2803
|
+
renderableIndex += 1
|
|
2804
|
+
) {
|
|
2805
|
+
const snapshot = frame.renderables[renderableIndex];
|
|
2806
|
+
if (snapshot === undefined || snapshot.authorVisible === false) continue;
|
|
2807
|
+
const worldEntity = worldEntityKey(snapshot.worldId, snapshot.entityKey);
|
|
2808
|
+
for (const pass of snapshot.shadowCasterPasses ?? []) {
|
|
2809
|
+
shadowCasterMembership.push({ ...pass, worldEntity, renderableIndex });
|
|
2810
|
+
shadowCasterEntityKeys.add(worldEntity);
|
|
2811
|
+
shadowCasterDrawKeys.add(
|
|
2812
|
+
gpuDrivenShadowDrawKey(
|
|
2813
|
+
worldEntity,
|
|
2814
|
+
pass.materialHandle,
|
|
2815
|
+
pass.drawItemIndex,
|
|
2816
|
+
pass.passIndex,
|
|
2817
|
+
),
|
|
2818
|
+
);
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
for (const draw of frame.dispatch) {
|
|
2822
|
+
if (draw.tags.LightMode !== 'ShadowCaster') continue;
|
|
2823
|
+
const snapshot = frame.renderables[draw.renderableIndex];
|
|
2824
|
+
if (snapshot !== undefined)
|
|
2825
|
+
shadowCasterEntityKeys.add(worldEntityKey(snapshot.worldId, snapshot.entityKey));
|
|
2826
|
+
}
|
|
2827
|
+
return { ...frame, shadowCasterEntityKeys, shadowCasterDrawKeys, shadowCasterMembership };
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2645
2830
|
private projectProbes(entry: PersistentCompositionEntry, frame: ExtractedFrame): void {
|
|
2646
2831
|
const sky =
|
|
2647
2832
|
frame.skylight === undefined
|
|
@@ -2692,14 +2877,19 @@ export class PersistentRenderScene {
|
|
|
2692
2877
|
owner: object,
|
|
2693
2878
|
projection: RenderScene,
|
|
2694
2879
|
delta?: RenderSceneApplyResult,
|
|
2695
|
-
full = false,
|
|
2696
2880
|
): void {
|
|
2697
2881
|
if (this.composition?.token === owner && !this.composition.gpuDrivenSceneRequired) return;
|
|
2698
2882
|
const acquired = this.acquireGpuScene(owner, projection);
|
|
2699
|
-
if (acquired === undefined
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2883
|
+
if (acquired === undefined) {
|
|
2884
|
+
this.sceneTableUploadBytes = 0;
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
if (acquired.rebuilt) {
|
|
2888
|
+
this.sceneTableUploadBytes = acquired.uploadBytes;
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
const result =
|
|
2892
|
+
delta === undefined
|
|
2703
2893
|
? acquired.scene.sync({
|
|
2704
2894
|
created: 0,
|
|
2705
2895
|
updated: 0,
|
|
@@ -2708,22 +2898,32 @@ export class PersistentRenderScene {
|
|
|
2708
2898
|
ignoredLateUpdates: 0,
|
|
2709
2899
|
createdSlots: [],
|
|
2710
2900
|
updatedSlots: [],
|
|
2901
|
+
contentUpdatedSlots: [],
|
|
2902
|
+
instanceUpdatedSlots: [],
|
|
2711
2903
|
removedSlots: [],
|
|
2712
2904
|
recreatedSlots: [],
|
|
2713
2905
|
resynced: 0,
|
|
2714
2906
|
})
|
|
2715
2907
|
: acquired.scene.sync(delta);
|
|
2716
|
-
if (!result.ok)
|
|
2908
|
+
if (!result.ok) {
|
|
2909
|
+
this.sceneTableUploadBytes = 0;
|
|
2910
|
+
this.failGpu(result.error);
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
this.sceneTableUploadBytes = result.value.bytes;
|
|
2717
2914
|
}
|
|
2718
2915
|
|
|
2719
2916
|
private acquireGpuScene(
|
|
2720
2917
|
owner: object,
|
|
2721
2918
|
projection: RenderScene,
|
|
2722
|
-
):
|
|
2919
|
+
):
|
|
2920
|
+
| { readonly scene: GpuScene; readonly rebuilt: boolean; readonly uploadBytes: number }
|
|
2921
|
+
| undefined {
|
|
2723
2922
|
const getDevice = this.options.getDevice;
|
|
2724
2923
|
if (getDevice === undefined) return undefined;
|
|
2725
2924
|
const device = getDevice();
|
|
2726
2925
|
if (this.gpuDevice !== undefined && this.gpuDevice !== device) {
|
|
2926
|
+
this.retireGpuScene(this.gpuScene, this.gpuDevice);
|
|
2727
2927
|
this.gpuScene = undefined;
|
|
2728
2928
|
this.gpuOwner = undefined;
|
|
2729
2929
|
this.gpuStatus = 'rebuild-pending';
|
|
@@ -2731,9 +2931,23 @@ export class PersistentRenderScene {
|
|
|
2731
2931
|
if (this.gpuStatus === 'error' && this.gpuDevice === device) return undefined;
|
|
2732
2932
|
if (this.gpuStatus === 'unsupported' && this.gpuDevice === device) return undefined;
|
|
2733
2933
|
if (this.gpuScene !== undefined && this.gpuOwner === owner) {
|
|
2734
|
-
return { scene: this.gpuScene, rebuilt: false };
|
|
2934
|
+
return { scene: this.gpuScene, rebuilt: false, uploadBytes: 0 };
|
|
2935
|
+
}
|
|
2936
|
+
if (this.gpuScene !== undefined && this.gpuDevice === device) {
|
|
2937
|
+
// Composition rebuilds replace the CPU projection token, but the GPU
|
|
2938
|
+
// table resource can be rebuilt in place. Keeping one scene identity
|
|
2939
|
+
// avoids destroying buffers that cached frame graphs still reference
|
|
2940
|
+
// and lets queue ordering preserve writes across the next submission.
|
|
2941
|
+
const rebuilt = this.gpuScene.rebuild(projection.slotsSnapshot());
|
|
2942
|
+
if (!rebuilt.ok) {
|
|
2943
|
+
this.failGpu(rebuilt.error);
|
|
2944
|
+
return undefined;
|
|
2945
|
+
}
|
|
2946
|
+
this.gpuOwner = owner;
|
|
2947
|
+
this.gpuStatus = 'resident';
|
|
2948
|
+
return { scene: this.gpuScene, rebuilt: true, uploadBytes: rebuilt.value.bytes };
|
|
2735
2949
|
}
|
|
2736
|
-
|
|
2950
|
+
this.retireGpuScene(this.gpuScene);
|
|
2737
2951
|
this.gpuScene = undefined;
|
|
2738
2952
|
this.gpuOwner = undefined;
|
|
2739
2953
|
this.gpuDevice = device;
|
|
@@ -2756,11 +2970,11 @@ export class PersistentRenderScene {
|
|
|
2756
2970
|
this.gpuScene = scene;
|
|
2757
2971
|
this.gpuOwner = owner;
|
|
2758
2972
|
this.gpuStatus = 'resident';
|
|
2759
|
-
return { scene, rebuilt: true };
|
|
2973
|
+
return { scene, rebuilt: true, uploadBytes: rebuilt.value.bytes };
|
|
2760
2974
|
}
|
|
2761
2975
|
|
|
2762
2976
|
private failGpu(error: RhiError): void {
|
|
2763
|
-
this.gpuScene
|
|
2977
|
+
this.retireGpuScene(this.gpuScene);
|
|
2764
2978
|
this.gpuScene = undefined;
|
|
2765
2979
|
this.gpuOwner = undefined;
|
|
2766
2980
|
this.gpuStatus = 'error';
|