@forgeax/engine-render 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -29
- package/dist/assembly/backend-contract.d.ts +13 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -1
- package/dist/assembly/device-feature-admission.d.ts.map +1 -1
- package/dist/assembly/factory.d.ts +14 -6
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +10 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
- package/dist/chunk-4L3P6DT5.mjs.map +1 -0
- package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
- package/dist/chunk-4NPUASZR.mjs.map +1 -0
- package/dist/chunk-DIYW3M5D.mjs +5085 -0
- package/dist/chunk-DIYW3M5D.mjs.map +1 -0
- package/dist/chunk-FBC4R44J.mjs +1003 -0
- package/dist/chunk-FBC4R44J.mjs.map +1 -0
- package/dist/{chunk-FS2KNUQH.mjs → chunk-FF7K3FSO.mjs} +253 -489
- package/dist/chunk-FF7K3FSO.mjs.map +1 -0
- package/dist/chunk-HZUPSYW7.mjs +1104 -0
- package/dist/chunk-HZUPSYW7.mjs.map +1 -0
- package/dist/{chunk-6OPXDQ2E.mjs → chunk-QODA5UKU.mjs} +33 -294
- package/dist/chunk-QODA5UKU.mjs.map +1 -0
- package/dist/chunk-YALPS44A.mjs +296 -0
- package/dist/chunk-YALPS44A.mjs.map +1 -0
- package/dist/{chunk-5YADUCA7.mjs → chunk-ZUWKAZ2I.mjs} +585 -991
- package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
- package/dist/cluster-binner.d.ts +5 -0
- package/dist/cluster-binner.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts +4 -0
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/light-probe.d.ts +9 -0
- package/dist/components/light-probe.d.ts.map +1 -0
- package/dist/components/rect-area-light.d.ts +12 -0
- package/dist/components/rect-area-light.d.ts.map +1 -0
- package/dist/components/scene-instance.d.ts +4 -0
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/spot-light.d.ts +3 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +10437 -10044
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +8 -1
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/lifecycle-transaction.d.ts +2 -0
- package/dist/device/lifecycle-transaction.d.ts.map +1 -0
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/gpu-driven.d.ts +26 -0
- package/dist/errors/gpu-driven.d.ts.map +1 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +9 -1
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +26 -1
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +50 -0
- package/dist/extract/gpu-driven.d.ts.map +1 -0
- package/dist/extract/visibility.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts +34 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +63 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +98 -2
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +64 -1
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +14 -0
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +8 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +0 -1
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +3 -3
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +90 -5
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +163 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/internal.d.ts +9 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +23 -2
- package/dist/internal.mjs.map +1 -1
- package/dist/light-buffer-layout.d.ts +58 -149
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +26 -12
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -0
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +1 -1
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
- package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/resources.d.ts +72 -0
- package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/state.d.ts +24 -0
- package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +8 -6
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +50 -10
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts +7 -3
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-timing.d.ts +19 -7
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/helpers.d.ts +0 -20
- package/dist/record/helpers.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +6 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +3 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +57 -6
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +49 -2
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +1 -2
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/reflection/inspection.d.ts +10 -0
- package/dist/reflection/inspection.d.ts.map +1 -0
- package/dist/reflection/projection.d.ts +49 -0
- package/dist/reflection/projection.d.ts.map +1 -1
- package/dist/reflection/record-owner.d.ts +60 -0
- package/dist/reflection/record-owner.d.ts.map +1 -0
- package/dist/render-contract.d.ts +14 -8
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +7 -0
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +104 -16
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +12 -1
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/probe-blend-record.d.ts +22 -0
- package/dist/scene/probe-blend-record.d.ts.map +1 -0
- package/dist/scene/probe-blend.d.ts +142 -0
- package/dist/scene/probe-blend.d.ts.map +1 -0
- package/dist/scene/render-scene-types.d.ts +0 -6
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +103 -9
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/visibility/bounds.d.ts +20 -0
- package/dist/scene/visibility/bounds.d.ts.map +1 -0
- package/dist/scene/visibility/budget.d.ts +16 -0
- package/dist/scene/visibility/budget.d.ts.map +1 -0
- package/dist/scene/visibility/facet.d.ts +73 -0
- package/dist/scene/visibility/facet.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-lod.d.ts +46 -0
- package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
- package/dist/scene/visibility/inspection.d.ts +132 -0
- package/dist/scene/visibility/inspection.d.ts.map +1 -0
- package/dist/scene/visibility/lod-selector.d.ts +25 -0
- package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
- package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
- package/dist/scene/visibility/types.d.ts +34 -0
- package/dist/scene/visibility/types.d.ts.map +1 -0
- package/dist/ssr/admission.d.ts +122 -0
- package/dist/ssr/admission.d.ts.map +1 -0
- package/dist/ssr/composition.d.ts +32 -0
- package/dist/ssr/composition.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +39 -0
- package/dist/ssr/errors.d.ts.map +1 -0
- package/dist/ssr/identity.d.ts +13 -0
- package/dist/ssr/identity.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +1 -1
- package/dist/temporal/frame-coordinator.d.ts +13 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +2 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -5
- package/dist/temporal/view.d.ts +7 -0
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/tilemap-chunk-extract-system.d.ts +1 -1
- package/dist/typed-render-graph-primitives.d.ts +4 -0
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +8 -4
- package/dist/volume/passes.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
- package/src/__tests__/detached-inspection.integration.test.ts +6 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +13 -1
- package/src/__tests__/direct-light-snapshot.unit.test.ts +59 -3
- package/src/__tests__/environment-generation.integration.test.ts +16 -0
- package/src/__tests__/evidence-identity.ts +13 -0
- package/src/__tests__/evidence-identity.unit.test.ts +24 -0
- package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
- package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
- package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
- package/src/__tests__/factory-contract.integration.test.ts +33 -0
- package/src/__tests__/frame-observation.contract.test.ts +15 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
- package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
- package/src/__tests__/gpu-lod-evidence.ts +188 -0
- package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
- package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
- package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
- package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
- package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
- package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
- package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
- package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
- package/src/__tests__/gpu-scene.unit.test.ts +49 -3
- package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
- package/src/__tests__/light-inspection.unit.test.ts +55 -0
- package/src/__tests__/light-resource-error.unit.test.ts +43 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
- package/src/__tests__/lod-bounds.unit.test.ts +42 -0
- package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
- package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
- package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
- package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
- package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
- package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
- package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
- package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
- package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
- package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
- package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
- package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
- package/src/__tests__/lod-selector.profile.test.ts +35 -0
- package/src/__tests__/lod-selector.unit.test.ts +68 -0
- package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
- package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
- package/src/__tests__/ltc-resources.unit.test.ts +32 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
- package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
- package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
- package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
- package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
- package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
- package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
- package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
- package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
- package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
- package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
- package/src/__tests__/occlusion-query.evidence.ts +178 -0
- package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
- package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
- package/src/__tests__/probe-bindings.unit.test.ts +200 -0
- package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
- package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
- package/src/__tests__/probe-capacity.integration.test.ts +97 -0
- package/src/__tests__/probe-recovery.integration.test.ts +104 -0
- package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
- package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
- package/src/__tests__/profiler-phase-token.test.ts +1 -0
- package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
- package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
- package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
- package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
- package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
- package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
- package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
- package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
- package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
- package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
- package/src/__tests__/skin-motion-regression.unit.test.ts +22 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
- package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
- package/src/__tests__/ssr-composition.unit.test.ts +46 -0
- package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
- package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
- package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
- package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
- package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
- package/src/__tests__/temporal-view.unit.test.ts +3 -3
- package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
- package/src/__tests__/visibility-budget.integration.test.ts +140 -0
- package/src/__tests__/visibility-budget.unit.test.ts +70 -0
- package/src/__tests__/visibility-extract.integration.test.ts +11 -6
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
- package/src/assembly/backend-contract.ts +13 -0
- package/src/assembly/device-feature-admission.ts +16 -9
- package/src/assembly/factory.ts +547 -93
- package/src/assembly/host-contract.ts +10 -0
- package/src/cluster-binner.ts +31 -0
- package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
- package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
- package/src/components/index.ts +3 -0
- package/src/components/light-helpers.ts +52 -1
- package/src/components/light-probe.ts +13 -0
- package/src/components/rect-area-light.ts +15 -0
- package/src/components/scene-instance.ts +6 -0
- package/src/components/spot-light.ts +3 -0
- package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
- package/src/device/gpu-residency.ts +140 -9
- package/src/device/lifecycle-transaction.ts +12 -0
- package/src/environment/lifecycle.ts +8 -1
- package/src/errors/gpu-driven.ts +92 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/recover.ts +32 -0
- package/src/errors/render.ts +55 -1
- package/src/extract/camera.ts +3 -3
- package/src/extract/gpu-driven.ts +108 -0
- package/src/extract/visibility.ts +0 -23
- package/src/gpu-driven/batch-topology.ts +206 -28
- package/src/gpu-driven/prepared-draw.ts +208 -0
- package/src/gpu-driven/production-raster.ts +953 -41
- package/src/gpu-driven/view-gpu.ts +411 -29
- package/src/gpu-scene-schema.ts +13 -0
- package/src/gpu-scene.ts +134 -65
- package/src/gpu-usage.ts +0 -1
- package/src/hdrp-buffers.ts +6 -6
- package/src/index.ts +74 -0
- package/src/inspection-types.ts +220 -1
- package/src/internal.ts +38 -0
- package/src/light-buffer-layout.ts +154 -324
- package/src/materials.ts +16 -2
- package/src/pbr-pipeline.ts +151 -45
- package/src/pipeline/standard-forward-lane.ts +47 -0
- package/src/pipeline/standard-lighting/graph.ts +2 -1
- package/src/pipeline/standard-lighting/light-frame.ts +3 -2
- package/src/pipeline/standard-lighting/topology.ts +1 -1
- package/src/pipeline/standard-lighting/transport.ts +2 -1
- package/src/pipeline/standard-pipeline.ts +2 -0
- package/src/pipeline-builder.ts +5 -2
- package/src/pipeline-spec.ts +12 -1
- package/src/plugin.ts +4 -0
- package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
- package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
- package/src/prepare/extended-lighting/resources.ts +389 -0
- package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
- package/src/prepare/extended-lighting/state.ts +92 -0
- package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +23 -1
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
- package/src/record/frame-lighting.ts +252 -53
- package/src/record/frame-snapshot.ts +57 -12
- package/src/record/frame.ts +410 -93
- package/src/record/gpu-timing.ts +108 -44
- package/src/record/helpers.ts +0 -58
- package/src/record/main-pass-geometry.ts +204 -39
- package/src/record/main-pass-material.ts +8 -1
- package/src/record/main-pass-sprite-draws.ts +52 -85
- package/src/record/mesh-ssbo.ts +47 -36
- package/src/record/render-context.ts +65 -4
- package/src/record/shadow-pass.ts +66 -40
- package/src/record/typed-frame-graph.ts +225 -83
- package/src/record/view-ubo.ts +14 -24
- package/src/reflection/inspection.ts +67 -0
- package/src/reflection/projection.ts +196 -1
- package/src/reflection/record-owner.ts +1283 -0
- package/src/render-contract.ts +17 -5
- package/src/render-pipeline.ts +5 -0
- package/src/render-system-extract.ts +493 -134
- package/src/render-system.ts +811 -615
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
- package/src/scene/probe-blend-record.ts +54 -0
- package/src/scene/probe-blend.ts +813 -0
- package/src/scene/render-scene-types.ts +0 -7
- package/src/scene/render-scene.ts +848 -183
- package/src/scene/visibility/bounds.ts +57 -0
- package/src/scene/visibility/budget.ts +40 -0
- package/src/scene/visibility/facet.ts +400 -0
- package/src/scene/visibility/gpu-frame-timing.ts +101 -0
- package/src/scene/visibility/gpu-lod.ts +119 -0
- package/src/scene/visibility/inspection.ts +313 -0
- package/src/scene/visibility/lod-selector.ts +84 -0
- package/src/scene/visibility/occlusion-confidence.ts +478 -0
- package/src/scene/visibility/occlusion-pass.ts +165 -0
- package/src/scene/visibility/occlusion-query-pool.ts +178 -0
- package/src/scene/visibility/occlusion-runtime.ts +672 -0
- package/src/scene/visibility/types.ts +50 -0
- package/src/ssr/admission.ts +451 -0
- package/src/ssr/composition.ts +73 -0
- package/src/ssr/errors.ts +76 -0
- package/src/ssr/identity.ts +12 -0
- package/src/systems/skin-palette-allocator.ts +1 -1
- package/src/temporal/frame-coordinator.ts +26 -0
- package/src/temporal/frame.ts +14 -1
- package/src/temporal/view.ts +11 -0
- package/src/tilemap-chunk-extract-system.ts +4 -4
- package/src/typed-render-graph-primitives.ts +48 -0
- package/src/volume/passes.ts +46 -49
- package/dist/.tsbuildinfo +0 -1
- package/dist/chunk-5YADUCA7.mjs.map +0 -1
- package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
- package/dist/chunk-BXBQQKFQ.mjs +0 -67
- package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
- package/dist/chunk-FS2KNUQH.mjs.map +0 -1
- package/dist/chunk-PCHBPYML.mjs.map +0 -1
- package/dist/chunk-VFPLTKCU.mjs.map +0 -1
package/src/render-system.ts
CHANGED
|
@@ -19,19 +19,10 @@
|
|
|
19
19
|
import { numMipLevels, resolveAssetHandle } from '@forgeax/engine-assets-runtime';
|
|
20
20
|
import type { World } from '@forgeax/engine-ecs';
|
|
21
21
|
import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
|
|
22
|
+
import { mat4 } from '@forgeax/engine-math';
|
|
22
23
|
import type { RecorderSession } from '@forgeax/engine-profiler';
|
|
23
24
|
import type { CompiledRenderGraphInfo } from '@forgeax/engine-render-graph';
|
|
24
|
-
import type {
|
|
25
|
-
BindGroup,
|
|
26
|
-
BindGroupEntry,
|
|
27
|
-
BindGroupLayout,
|
|
28
|
-
Buffer,
|
|
29
|
-
RenderPipeline,
|
|
30
|
-
Sampler,
|
|
31
|
-
Texture,
|
|
32
|
-
TextureFormat,
|
|
33
|
-
TextureView,
|
|
34
|
-
} from '@forgeax/engine-rhi';
|
|
25
|
+
import type { BindGroupEntry, BindGroupLayout, Buffer, RenderPipeline } from '@forgeax/engine-rhi';
|
|
35
26
|
import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
|
|
36
27
|
import {
|
|
37
28
|
derive,
|
|
@@ -82,10 +73,14 @@ import { GpuDrivenProduction } from './gpu-driven/production-raster';
|
|
|
82
73
|
import type {
|
|
83
74
|
BloomInspection,
|
|
84
75
|
DirectionalShadowInspection,
|
|
76
|
+
GpuDrivenProductionInspection,
|
|
77
|
+
LodOcclusionInspection,
|
|
78
|
+
LodOcclusionInspectionRow,
|
|
79
|
+
LodOcclusionWorldInspection,
|
|
85
80
|
MotionBlurInspection,
|
|
86
81
|
ReflectionProbeInspection,
|
|
87
|
-
ReflectionProbeSelectionInspection,
|
|
88
82
|
RenderSceneInspection,
|
|
83
|
+
SsrDependenciesInspection,
|
|
89
84
|
TemporalTargetInspection,
|
|
90
85
|
TransmissionInspection,
|
|
91
86
|
} from './inspection-types';
|
|
@@ -103,25 +98,19 @@ import {
|
|
|
103
98
|
import { createPointsLinesLaneAdapter, type PointsLinesBackend } from './points-lines/record';
|
|
104
99
|
import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
|
|
105
100
|
import type { PointsLinesRecordOwner, PointsLinesRecordSubmission } from './record/render-context';
|
|
101
|
+
import { ReflectionProbeRecordOwner } from './reflection/record-owner';
|
|
102
|
+
import {
|
|
103
|
+
OcclusionRenderRuntime,
|
|
104
|
+
type OcclusionRuntimeCandidate,
|
|
105
|
+
} from './scene/visibility/occlusion-runtime';
|
|
106
|
+
import { primitiveKey, viewKey } from './scene/visibility/types';
|
|
106
107
|
import { inspectVolumetricFog } from './volume/inspection';
|
|
107
108
|
|
|
108
109
|
export type { RenderSceneInspection } from './inspection-types';
|
|
109
110
|
|
|
110
111
|
import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
|
|
111
|
-
import { mat4, vec3 } from '@forgeax/engine-math';
|
|
112
|
-
import { buildCubeCameraFaceViews } from './capture/cube-views';
|
|
113
112
|
import { createCubeCaptureScheduler } from './capture/scheduler';
|
|
114
|
-
import {
|
|
115
|
-
createFaceUniformsBuffer,
|
|
116
|
-
createPrefilterUniformsBuffer,
|
|
117
|
-
writeAllFaceUniforms,
|
|
118
|
-
writeAllPrefilterUniforms,
|
|
119
|
-
} from './device/gpu-residency';
|
|
120
113
|
import type { EnvironmentInspection } from './environment/inspection';
|
|
121
|
-
import {
|
|
122
|
-
GPU_TEXTURE_USAGE_COPY_SRC,
|
|
123
|
-
GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING,
|
|
124
|
-
} from './gpu-texture-usage';
|
|
125
114
|
import {
|
|
126
115
|
GPU_BUFFER_USAGE_COPY_DST,
|
|
127
116
|
GPU_BUFFER_USAGE_INDEX,
|
|
@@ -129,13 +118,6 @@ import {
|
|
|
129
118
|
GPU_BUFFER_USAGE_VERTEX,
|
|
130
119
|
} from './gpu-usage';
|
|
131
120
|
import { resetHdrpBuffers } from './hdrp-buffers';
|
|
132
|
-
import {
|
|
133
|
-
CUBEMAP_FACE_VERTICES,
|
|
134
|
-
createIblPipelines,
|
|
135
|
-
getOrCreateIblCache,
|
|
136
|
-
PREFILTER_MIP_LEVELS,
|
|
137
|
-
PREFILTER_SIZE,
|
|
138
|
-
} from './ibl/IblPipelineCache';
|
|
139
121
|
import { assembleMaterialWithSkylightEntries } from './ibl/skylight-bind-group';
|
|
140
122
|
import { disposeInstanceBuffers, disposeTransientInstanceBuffers } from './instance-buffer-cache';
|
|
141
123
|
import {
|
|
@@ -168,7 +150,7 @@ import type {
|
|
|
168
150
|
GraphTargetCaptureRequest,
|
|
169
151
|
RenderFrameState,
|
|
170
152
|
} from './record/frame-snapshot';
|
|
171
|
-
import { getTextureIdentity } from './record/frame-snapshot';
|
|
153
|
+
import { getTextureIdentity, worldEntityKey } from './record/frame-snapshot';
|
|
172
154
|
import type { GpuPassTimingReason } from './record/gpu-pass-timing/errors';
|
|
173
155
|
import type {
|
|
174
156
|
GpuPassTimingCapture,
|
|
@@ -176,28 +158,15 @@ import type {
|
|
|
176
158
|
GpuPassTimingSession,
|
|
177
159
|
} from './record/gpu-pass-timing/session';
|
|
178
160
|
import type { GpuTimingCapture } from './record/gpu-timing';
|
|
161
|
+
import { computeProjectionMatrix, computeViewMatrix } from './record/helpers';
|
|
179
162
|
import { applyParamSnapshotToUbo, residentTextureView } from './record/main-pass-material';
|
|
180
|
-
import type {
|
|
181
|
-
import type { CubeCaptureGraphState
|
|
163
|
+
import type { RenderSystemInternals } from './record/render-context';
|
|
164
|
+
import type { CubeCaptureGraphState } from './record/typed-frame-graph';
|
|
182
165
|
import {
|
|
183
166
|
type RenderFeatureGraphCandidate,
|
|
184
167
|
resetRenderFeatureGraphState,
|
|
185
168
|
settleVolumetricFogGraphCandidate,
|
|
186
169
|
} from './record/typed-frame-graph';
|
|
187
|
-
import { REFLECTION_PROBE_VIEW_SLOT_BASE, VIEW_UNIFORM_SLOT_STRIDE } from './record/view-ubo';
|
|
188
|
-
import {
|
|
189
|
-
advanceProbeFilter,
|
|
190
|
-
commitProbeFilterStep,
|
|
191
|
-
createProbeFilterState,
|
|
192
|
-
type ProbeFilterState,
|
|
193
|
-
} from './reflection/filter';
|
|
194
|
-
import { buildReflectionProbeTable } from './reflection/gpu-table';
|
|
195
|
-
import {
|
|
196
|
-
admitReflectionProbe,
|
|
197
|
-
DEFAULT_REFLECTION_PROBE_LIMITS,
|
|
198
|
-
type ReflectionProbeFact,
|
|
199
|
-
type ReflectionProbeSelectionResult,
|
|
200
|
-
} from './reflection/projection';
|
|
201
170
|
import {
|
|
202
171
|
type CameraSnapshot,
|
|
203
172
|
type DrawOwnerOptions,
|
|
@@ -215,7 +184,9 @@ import type {
|
|
|
215
184
|
} from './render-system-extract';
|
|
216
185
|
import { extractFrames } from './render-system-extract';
|
|
217
186
|
import { PersistentRenderScene } from './scene/render-scene';
|
|
187
|
+
import { inspectLodOcclusion } from './scene/visibility/inspection';
|
|
218
188
|
import { resolveSsaoParameters } from './ssao-config';
|
|
189
|
+
import { projectSsrDependencies, resolveSsrAdmissionGeneration } from './ssr/admission';
|
|
219
190
|
import {
|
|
220
191
|
getTransparentSortConfig,
|
|
221
192
|
TRANSPARENT_SORT_MODE_DISTANCE,
|
|
@@ -239,6 +210,121 @@ import {
|
|
|
239
210
|
inspectTransmissionFromAdmission,
|
|
240
211
|
} from './transmission/inspection';
|
|
241
212
|
|
|
213
|
+
interface LodInspectionQuerySummary {
|
|
214
|
+
readonly used: number;
|
|
215
|
+
readonly capacity: number;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function createLodWorldInspections(
|
|
219
|
+
worlds: readonly World[],
|
|
220
|
+
renderables: readonly RenderableSnapshot[],
|
|
221
|
+
submittedRenderables: readonly RenderableSnapshot[],
|
|
222
|
+
camera: CameraSnapshot,
|
|
223
|
+
frameId: number,
|
|
224
|
+
worldKeys: readonly number[],
|
|
225
|
+
slots: ReadonlyMap<number, { readonly slot: number; readonly generation: number }>,
|
|
226
|
+
query: LodInspectionQuerySummary,
|
|
227
|
+
fallback: LodOcclusionInspection['fallback'],
|
|
228
|
+
degradation: LodOcclusionInspection['degradation'],
|
|
229
|
+
): readonly LodOcclusionWorldInspection[] {
|
|
230
|
+
// The CPU projection knows which renderables belong to each World, but it
|
|
231
|
+
// does not receive the GPU selector/query counters split by World from the
|
|
232
|
+
// same submit. Keep these rows available for diagnostics while explicitly
|
|
233
|
+
// marking their attribution unavailable; producers must not promote them
|
|
234
|
+
// to World-reorder evidence by inferring facts from array order.
|
|
235
|
+
const attribution = Object.freeze({
|
|
236
|
+
status: 'unavailable' as const,
|
|
237
|
+
reason: 'projection-only' as const,
|
|
238
|
+
});
|
|
239
|
+
const submitted = new Set(submittedRenderables);
|
|
240
|
+
const sampleLimit = 64;
|
|
241
|
+
const accumulators = worlds.map(() => ({
|
|
242
|
+
allCount: 0,
|
|
243
|
+
allVisible: 0,
|
|
244
|
+
allSamples: [] as RenderableSnapshot[],
|
|
245
|
+
lodCount: 0,
|
|
246
|
+
lodVisible: 0,
|
|
247
|
+
lodSamples: [] as RenderableSnapshot[],
|
|
248
|
+
}));
|
|
249
|
+
// Keep this projection bounded: the old implementation filtered the full
|
|
250
|
+
// 100k renderable list twice per World on every frame and retained another
|
|
251
|
+
// Set-backed traversal just to derive the six counters below. One pass is
|
|
252
|
+
// enough because the inspection only needs counts, the first row identity,
|
|
253
|
+
// and at most 64 detached samples.
|
|
254
|
+
for (const renderable of renderables) {
|
|
255
|
+
const accumulator = accumulators[renderable.worldId];
|
|
256
|
+
if (accumulator === undefined) continue;
|
|
257
|
+
const visible = submitted.has(renderable);
|
|
258
|
+
accumulator.allCount += 1;
|
|
259
|
+
if (visible) accumulator.allVisible += 1;
|
|
260
|
+
if (accumulator.allSamples.length < sampleLimit) accumulator.allSamples.push(renderable);
|
|
261
|
+
if ((renderable.lods?.length ?? 0) > 0) {
|
|
262
|
+
accumulator.lodCount += 1;
|
|
263
|
+
if (visible) accumulator.lodVisible += 1;
|
|
264
|
+
if (accumulator.lodSamples.length < sampleLimit) accumulator.lodSamples.push(renderable);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const result: LodOcclusionWorldInspection[] = [];
|
|
268
|
+
for (let worldId = 0; worldId < worlds.length; worldId += 1) {
|
|
269
|
+
const world = worlds[worldId];
|
|
270
|
+
if (world === undefined) continue;
|
|
271
|
+
const accumulator = accumulators[worldId];
|
|
272
|
+
if (accumulator === undefined || accumulator.allCount === 0) {
|
|
273
|
+
result.push(
|
|
274
|
+
Object.freeze({ attachmentId: world.identity, rows: Object.freeze([]), attribution }),
|
|
275
|
+
);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const usesLod = accumulator.lodCount > 0;
|
|
279
|
+
const candidateCount = usesLod ? accumulator.lodCount : accumulator.allCount;
|
|
280
|
+
const visible = usesLod ? accumulator.lodVisible : accumulator.allVisible;
|
|
281
|
+
const samples = usesLod ? accumulator.lodSamples : accumulator.allSamples;
|
|
282
|
+
const first = samples[0];
|
|
283
|
+
const slot =
|
|
284
|
+
first === undefined
|
|
285
|
+
? undefined
|
|
286
|
+
: slots.get(worldEntityKey(worldKeys[first.worldId] ?? first.worldId, first.entityKey));
|
|
287
|
+
const row: LodOcclusionInspectionRow = {
|
|
288
|
+
root: {
|
|
289
|
+
guid: first === undefined ? 'none' : `asset-handle:${first.assetHandle}`,
|
|
290
|
+
sourceKey: `render-world:${world.identity}`,
|
|
291
|
+
},
|
|
292
|
+
view: {
|
|
293
|
+
attachmentId: world.identity,
|
|
294
|
+
cameraEntity: world === worlds[camera.worldId ?? 0] ? (camera.entityKey ?? 0) : 0,
|
|
295
|
+
viewRole: 'main',
|
|
296
|
+
viewGeneration: camera.historyVersion ?? 0,
|
|
297
|
+
},
|
|
298
|
+
slot: { primitiveSlot: slot?.slot ?? 0, slotGeneration: slot?.generation ?? 0 },
|
|
299
|
+
generation: frameId,
|
|
300
|
+
count: { candidates: candidateCount, visible, occluded: candidateCount - visible },
|
|
301
|
+
lodHistogram: [{ level: 0, count: candidateCount }],
|
|
302
|
+
queryLatencyUs: { median: 0, p95: 0, last: 0 },
|
|
303
|
+
pagePressure: query,
|
|
304
|
+
fallback,
|
|
305
|
+
degradation,
|
|
306
|
+
samples: samples.map((candidate, index) => ({
|
|
307
|
+
primitiveSlot:
|
|
308
|
+
slots.get(
|
|
309
|
+
worldEntityKey(worldKeys[candidate.worldId] ?? candidate.worldId, candidate.entityKey),
|
|
310
|
+
)?.slot ?? index,
|
|
311
|
+
level: 0,
|
|
312
|
+
visible: submitted.has(candidate),
|
|
313
|
+
})),
|
|
314
|
+
};
|
|
315
|
+
result.push(
|
|
316
|
+
Object.freeze({
|
|
317
|
+
attachmentId: world.identity,
|
|
318
|
+
rows: Object.freeze([row]),
|
|
319
|
+
attribution,
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
return Object.freeze(
|
|
324
|
+
result.sort((left, right) => left.attachmentId.localeCompare(right.attachmentId)),
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
242
328
|
type TimingSessionHost = RenderSystemInternals & {
|
|
243
329
|
gpuPassTimingSession?: GpuPassTimingSession | undefined;
|
|
244
330
|
gpuPassTimingCapture?: GpuPassTimingCapture | undefined;
|
|
@@ -323,7 +409,7 @@ function sortTransparentDispatch(
|
|
|
323
409
|
const camPos = camera.position;
|
|
324
410
|
|
|
325
411
|
// Squared camera distance per entry (negated → ascending = back-to-front).
|
|
326
|
-
// D-3: world position = translation column of
|
|
412
|
+
// D-3: world position = translation column of GlobalTransform.world (m[12,13,14]).
|
|
327
413
|
const negDistSq = (entry: DispatchEntry): number => {
|
|
328
414
|
const tx = renderables[entry.renderableIndex]?.transform;
|
|
329
415
|
if (tx === undefined) return 0;
|
|
@@ -429,6 +515,8 @@ export interface RenderSystem {
|
|
|
429
515
|
observeCurrentFrame(
|
|
430
516
|
options: FrameObservationOptions,
|
|
431
517
|
): Promise<Result<FrameObservation, ObservationUnavailableError>>;
|
|
518
|
+
/** Resolve the renderer-owned GPU LOD counters for the last accepted submit. */
|
|
519
|
+
observeLodOcclusion(): Promise<void>;
|
|
432
520
|
/** Internal same-frame graph target access for backend diagnostics. */
|
|
433
521
|
getCurrentGraphTarget(name: string): CurrentGraphTarget | undefined;
|
|
434
522
|
/** Internal test capture copied into the draw's command submission. */
|
|
@@ -446,6 +534,10 @@ export interface RenderSystem {
|
|
|
446
534
|
/** Persistent scene maintenance evidence from the ordinary single-World path. */
|
|
447
535
|
readonly renderScene: RenderSceneInspection;
|
|
448
536
|
readonly reflectionProbes: ReflectionProbeInspection;
|
|
537
|
+
/** Live producer/RHI/temporal receipts projected through SSR admission. */
|
|
538
|
+
readonly ssrDependencies: SsrDependenciesInspection;
|
|
539
|
+
/** Producer completion fence included in the public FrameReceipt. */
|
|
540
|
+
readonly reflectionFallbackCompletion: Promise<void> | undefined;
|
|
449
541
|
/** True while a progressive CubeCamera candidate still owns unfinished faces. */
|
|
450
542
|
isCubeCapturePending(target: RenderTarget): boolean;
|
|
451
543
|
readonly environment: EnvironmentInspection;
|
|
@@ -453,6 +545,9 @@ export interface RenderSystem {
|
|
|
453
545
|
readonly bloom: BloomInspection;
|
|
454
546
|
readonly motionBlurInspection: MotionBlurInspection | undefined;
|
|
455
547
|
readonly temporalTargetInspection: TemporalTargetInspection | undefined;
|
|
548
|
+
readonly lodOcclusionInspection: LodOcclusionInspection | undefined;
|
|
549
|
+
/** Bounded GPU-driven counters without materializing the persistent scene table. */
|
|
550
|
+
readonly gpuDrivenInspection: GpuDrivenProductionInspection;
|
|
456
551
|
/** Last completed transmission candidate facts, detached from GPU handles. */
|
|
457
552
|
readonly transmission: TransmissionInspection | undefined;
|
|
458
553
|
/** Single production projection of Directional author/extract/record facts. */
|
|
@@ -1022,551 +1117,6 @@ class StandardPointsLinesOwner implements PointsLinesRecordOwner {
|
|
|
1022
1117
|
}
|
|
1023
1118
|
}
|
|
1024
1119
|
|
|
1025
|
-
type ProbeCubeSet = {
|
|
1026
|
-
readonly texture: Texture;
|
|
1027
|
-
readonly cubeView: TextureView;
|
|
1028
|
-
readonly faceViews: readonly TextureView[];
|
|
1029
|
-
readonly mipViews: readonly (readonly TextureView[])[];
|
|
1030
|
-
readonly depthTexture: Texture;
|
|
1031
|
-
readonly depthView: TextureView;
|
|
1032
|
-
readonly size: number;
|
|
1033
|
-
};
|
|
1034
|
-
|
|
1035
|
-
type ProbeGpuResource = {
|
|
1036
|
-
fact: ReflectionProbeFact;
|
|
1037
|
-
index: number;
|
|
1038
|
-
filter: ProbeFilterState;
|
|
1039
|
-
rawFaceCursor: number;
|
|
1040
|
-
readonly raw: ProbeCubeSet;
|
|
1041
|
-
active: ProbeCubeSet | undefined;
|
|
1042
|
-
candidate: ProbeCubeSet;
|
|
1043
|
-
spare: ProbeCubeSet | undefined;
|
|
1044
|
-
readonly sampler: Sampler;
|
|
1045
|
-
readonly uniformBuffer: Buffer;
|
|
1046
|
-
readonly filterGroup1: BindGroup;
|
|
1047
|
-
pending?: {
|
|
1048
|
-
readonly rawCaptureFace: number | undefined;
|
|
1049
|
-
readonly step: ReturnType<typeof advanceProbeFilter>;
|
|
1050
|
-
};
|
|
1051
|
-
};
|
|
1052
|
-
|
|
1053
|
-
type ProbeSharedResources = {
|
|
1054
|
-
readonly faceUniforms: Buffer;
|
|
1055
|
-
readonly prefilterUniforms: Buffer;
|
|
1056
|
-
readonly vertexBuffer: Buffer;
|
|
1057
|
-
readonly sampler: Sampler;
|
|
1058
|
-
readonly filterPipeline: RenderPipeline;
|
|
1059
|
-
readonly cubeGroup1Bgl: BindGroupLayout;
|
|
1060
|
-
readonly prefilterGroup0Bgl: BindGroupLayout;
|
|
1061
|
-
};
|
|
1062
|
-
|
|
1063
|
-
/** Renderer-owned raw capture and incremental PMREM/LKG promotion owner. */
|
|
1064
|
-
class ReflectionProbeRecordOwner {
|
|
1065
|
-
private readonly resources = new Map<string, ProbeGpuResource>();
|
|
1066
|
-
private shared: ProbeSharedResources | undefined;
|
|
1067
|
-
private pipelineWarmup: Promise<void> | undefined;
|
|
1068
|
-
private pipelineWarmupFailure: string | undefined;
|
|
1069
|
-
private pipelineWarmupAttempts = 0;
|
|
1070
|
-
private lastFactCount = 0;
|
|
1071
|
-
private lastAcceptedCount = 0;
|
|
1072
|
-
private lastScheduledRawFaces = 0;
|
|
1073
|
-
private lastScheduledFilteredSteps = 0;
|
|
1074
|
-
private lastSelection: ReflectionProbeSelectionInspection = { kind: 'skylight' };
|
|
1075
|
-
private readonly outputFormat: TextureFormat;
|
|
1076
|
-
|
|
1077
|
-
constructor(private readonly internals: RenderSystemInternals) {
|
|
1078
|
-
this.outputFormat = internals.device.caps.rgba16floatRenderable ? 'rgba16float' : 'rgba8unorm';
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
inspect(): ReflectionProbeInspection {
|
|
1082
|
-
let rawFacesCaptured = 0;
|
|
1083
|
-
let filteredStepsCompleted = 0;
|
|
1084
|
-
const filteredMipLevels = new Set<number>();
|
|
1085
|
-
let activeCount = 0;
|
|
1086
|
-
for (const resource of this.resources.values()) {
|
|
1087
|
-
rawFacesCaptured += resource.rawFaceCursor;
|
|
1088
|
-
filteredStepsCompleted += resource.filter.cursor;
|
|
1089
|
-
const completedMipCount = Math.min(
|
|
1090
|
-
PREFILTER_MIP_LEVELS,
|
|
1091
|
-
Math.floor(resource.filter.cursor / resource.filter.faceCount),
|
|
1092
|
-
);
|
|
1093
|
-
for (let mip = 0; mip < completedMipCount; mip += 1) filteredMipLevels.add(mip);
|
|
1094
|
-
if (resource.active !== undefined) activeCount += 1;
|
|
1095
|
-
}
|
|
1096
|
-
return {
|
|
1097
|
-
selection: this.lastSelection,
|
|
1098
|
-
factCount: this.lastFactCount,
|
|
1099
|
-
acceptedCount: this.lastAcceptedCount,
|
|
1100
|
-
activeCount,
|
|
1101
|
-
rawFacesCaptured,
|
|
1102
|
-
filteredStepsCompleted,
|
|
1103
|
-
filteredMipLevels: [...filteredMipLevels].sort((a, b) => a - b),
|
|
1104
|
-
scheduledRawFaces: this.lastScheduledRawFaces,
|
|
1105
|
-
scheduledFilteredSteps: this.lastScheduledFilteredSteps,
|
|
1106
|
-
pipelineReady: this.shared !== undefined,
|
|
1107
|
-
pipelineWarmupAttempts: this.pipelineWarmupAttempts,
|
|
1108
|
-
...(this.pipelineWarmupFailure === undefined
|
|
1109
|
-
? {}
|
|
1110
|
-
: { pipelineWarmupFailure: this.pipelineWarmupFailure }),
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
private ensurePipelineWarmup(): void {
|
|
1115
|
-
if (this.shared !== undefined || this.pipelineWarmup !== undefined) return;
|
|
1116
|
-
const factory = this.internals.shaderModuleFactory;
|
|
1117
|
-
if (factory === undefined) return;
|
|
1118
|
-
const attempt = this.pipelineWarmupAttempts + 1;
|
|
1119
|
-
this.pipelineWarmupAttempts = attempt;
|
|
1120
|
-
this.pipelineWarmup = createIblPipelines(
|
|
1121
|
-
this.internals.deviceScope,
|
|
1122
|
-
this.internals.device,
|
|
1123
|
-
(device, descriptor) => {
|
|
1124
|
-
const labeled = {
|
|
1125
|
-
...descriptor,
|
|
1126
|
-
label: `${descriptor.label ?? 'ibl'}-reflection-probe-${attempt}`,
|
|
1127
|
-
};
|
|
1128
|
-
return this.internals.createShaderModule === undefined
|
|
1129
|
-
? Promise.resolve(factory.createShaderModule(labeled))
|
|
1130
|
-
: this.internals.createShaderModule(device, labeled);
|
|
1131
|
-
},
|
|
1132
|
-
this.outputFormat,
|
|
1133
|
-
).then((result) => {
|
|
1134
|
-
if (!result.ok) {
|
|
1135
|
-
this.pipelineWarmupFailure = `${result.error.code}: ${result.error.hint}`;
|
|
1136
|
-
console.warn('[forgeax] ReflectionProbe IBL warmup retry', this.pipelineWarmupFailure);
|
|
1137
|
-
this.pipelineWarmup = undefined;
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
this.shared = this.createSharedResources(result.value.prefilterPipeline);
|
|
1141
|
-
if (this.shared === undefined) {
|
|
1142
|
-
this.pipelineWarmupFailure = 'renderer-owned IBL resources could not be allocated';
|
|
1143
|
-
console.warn('[forgeax] ReflectionProbe IBL resource allocation retry');
|
|
1144
|
-
this.pipelineWarmup = undefined;
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
prepare(
|
|
1150
|
-
facts: readonly ReflectionProbeFact[],
|
|
1151
|
-
selections: ReadonlyMap<string, ReflectionProbeSelectionResult>,
|
|
1152
|
-
displayCamera: CameraSnapshot | undefined,
|
|
1153
|
-
): ReflectionProbeRecordState {
|
|
1154
|
-
this.lastFactCount = facts.length;
|
|
1155
|
-
this.lastScheduledRawFaces = 0;
|
|
1156
|
-
this.lastScheduledFilteredSteps = 0;
|
|
1157
|
-
const selectedProbe = [...selections.values()]
|
|
1158
|
-
.filter(
|
|
1159
|
-
(selection): selection is Extract<ReflectionProbeSelectionResult, { kind: 'probe' }> =>
|
|
1160
|
-
selection.kind === 'probe',
|
|
1161
|
-
)
|
|
1162
|
-
.sort((a, b) => a.worldId - b.worldId || a.entityKey - b.entityKey)[0];
|
|
1163
|
-
this.lastSelection =
|
|
1164
|
-
selectedProbe === undefined
|
|
1165
|
-
? { kind: 'skylight' }
|
|
1166
|
-
: { kind: 'probe', worldId: selectedProbe.worldId, entityKey: selectedProbe.entityKey };
|
|
1167
|
-
if (facts.length > 0) this.ensurePipelineWarmup();
|
|
1168
|
-
void this.pipelineWarmup;
|
|
1169
|
-
const ordered = facts
|
|
1170
|
-
.slice()
|
|
1171
|
-
.sort((a, b) => a.worldId - b.worldId || a.entityKey - b.entityKey);
|
|
1172
|
-
let acceptedCount = 0;
|
|
1173
|
-
let acceptedBytes = 0;
|
|
1174
|
-
const accepted = new Map<string, ProbeGpuResource>();
|
|
1175
|
-
for (const fact of ordered) {
|
|
1176
|
-
const admission = admitReflectionProbe(
|
|
1177
|
-
fact,
|
|
1178
|
-
{ acceptedCount, acceptedBytes },
|
|
1179
|
-
DEFAULT_REFLECTION_PROBE_LIMITS,
|
|
1180
|
-
);
|
|
1181
|
-
if (!admission.ok) continue;
|
|
1182
|
-
const key = `${fact.worldId}:${fact.entityKey}`;
|
|
1183
|
-
const resource = this.resourceFor(key, fact, acceptedCount);
|
|
1184
|
-
if (resource === undefined) continue;
|
|
1185
|
-
acceptedCount += 1;
|
|
1186
|
-
acceptedBytes = admission.acceptedBytes;
|
|
1187
|
-
accepted.set(key, resource);
|
|
1188
|
-
}
|
|
1189
|
-
for (const [key, resource] of this.resources) {
|
|
1190
|
-
if (!accepted.has(key)) {
|
|
1191
|
-
this.destroyProbeResource(resource);
|
|
1192
|
-
this.resources.delete(key);
|
|
1193
|
-
} else resource.index = [...accepted.keys()].indexOf(key);
|
|
1194
|
-
}
|
|
1195
|
-
const rows = [...accepted.values()]
|
|
1196
|
-
.map((resource) => {
|
|
1197
|
-
const active = resource.active;
|
|
1198
|
-
if (active === undefined) return undefined;
|
|
1199
|
-
return {
|
|
1200
|
-
primitiveKey: `${resource.fact.worldId}:${resource.fact.entityKey}`,
|
|
1201
|
-
index: resource.index,
|
|
1202
|
-
worldId: resource.fact.worldId,
|
|
1203
|
-
entityKey: resource.fact.entityKey,
|
|
1204
|
-
center: resource.fact.center,
|
|
1205
|
-
halfExtents: resource.fact.halfExtents,
|
|
1206
|
-
intensity: resource.fact.intensity,
|
|
1207
|
-
generation: resource.filter.activeGeneration,
|
|
1208
|
-
filteredView: active.cubeView,
|
|
1209
|
-
sampler: resource.sampler,
|
|
1210
|
-
uniformBuffer: resource.uniformBuffer,
|
|
1211
|
-
};
|
|
1212
|
-
})
|
|
1213
|
-
.filter((row): row is NonNullable<typeof row> => row !== undefined);
|
|
1214
|
-
const table = buildReflectionProbeTable(rows);
|
|
1215
|
-
const work: ReflectionProbeGraphWork[] = [];
|
|
1216
|
-
for (const resource of accepted.values()) {
|
|
1217
|
-
const rawCaptureFace =
|
|
1218
|
-
resource.rawFaceCursor < resource.raw.faceViews.length ? resource.rawFaceCursor : undefined;
|
|
1219
|
-
const step =
|
|
1220
|
-
resource.rawFaceCursor >= resource.raw.faceViews.length
|
|
1221
|
-
? advanceProbeFilter(resource.filter)
|
|
1222
|
-
: undefined;
|
|
1223
|
-
if (this.shared === undefined) continue;
|
|
1224
|
-
if (rawCaptureFace === undefined && step === undefined) continue;
|
|
1225
|
-
const filterGroup0 =
|
|
1226
|
-
step === undefined
|
|
1227
|
-
? undefined
|
|
1228
|
-
: this.internals.device.createBindGroup({
|
|
1229
|
-
label: `reflection-probe-${resource.index}-pmrem-${step.mipLevel}-${step.faceIndex}`,
|
|
1230
|
-
layout: this.shared.prefilterGroup0Bgl,
|
|
1231
|
-
entries: [
|
|
1232
|
-
{
|
|
1233
|
-
binding: 0,
|
|
1234
|
-
resource: {
|
|
1235
|
-
kind: 'buffer',
|
|
1236
|
-
value: {
|
|
1237
|
-
buffer: this.shared.faceUniforms,
|
|
1238
|
-
offset: step.faceIndex * 256,
|
|
1239
|
-
size: 64,
|
|
1240
|
-
},
|
|
1241
|
-
},
|
|
1242
|
-
},
|
|
1243
|
-
{
|
|
1244
|
-
binding: 1,
|
|
1245
|
-
resource: {
|
|
1246
|
-
kind: 'buffer',
|
|
1247
|
-
value: {
|
|
1248
|
-
buffer: this.shared.prefilterUniforms,
|
|
1249
|
-
offset: (step.mipLevel * 6 + step.faceIndex) * 256,
|
|
1250
|
-
size: 16,
|
|
1251
|
-
},
|
|
1252
|
-
},
|
|
1253
|
-
},
|
|
1254
|
-
],
|
|
1255
|
-
});
|
|
1256
|
-
if (filterGroup0 !== undefined && !filterGroup0.ok) continue;
|
|
1257
|
-
if (resource.pending !== undefined) continue;
|
|
1258
|
-
resource.pending = { rawCaptureFace, step };
|
|
1259
|
-
if (rawCaptureFace !== undefined) this.lastScheduledRawFaces += 1;
|
|
1260
|
-
if (step !== undefined) this.lastScheduledFilteredSteps += 1;
|
|
1261
|
-
const face =
|
|
1262
|
-
rawCaptureFace === undefined
|
|
1263
|
-
? undefined
|
|
1264
|
-
: buildCubeCameraFaceViews({
|
|
1265
|
-
position: resource.fact.center,
|
|
1266
|
-
near: 0.1,
|
|
1267
|
-
far: 100,
|
|
1268
|
-
})[rawCaptureFace];
|
|
1269
|
-
const faceCamera =
|
|
1270
|
-
face === undefined || displayCamera === undefined
|
|
1271
|
-
? undefined
|
|
1272
|
-
: {
|
|
1273
|
-
...displayCamera,
|
|
1274
|
-
position: vec3.create(
|
|
1275
|
-
resource.fact.center[0] ?? 0,
|
|
1276
|
-
resource.fact.center[1] ?? 0,
|
|
1277
|
-
resource.fact.center[2] ?? 0,
|
|
1278
|
-
),
|
|
1279
|
-
world: mat4.invert(mat4.create(), face.view),
|
|
1280
|
-
fov: Math.PI / 2,
|
|
1281
|
-
aspect: 1,
|
|
1282
|
-
near: face.near,
|
|
1283
|
-
far: face.far,
|
|
1284
|
-
projection: 'perspective' as const,
|
|
1285
|
-
};
|
|
1286
|
-
work.push({
|
|
1287
|
-
probeIndex: resource.index,
|
|
1288
|
-
rawTexture: resource.raw.texture,
|
|
1289
|
-
rawCubeView: resource.raw.cubeView,
|
|
1290
|
-
rawFaceViews: resource.raw.faceViews,
|
|
1291
|
-
rawDepthTexture: resource.raw.depthTexture,
|
|
1292
|
-
filteredTexture: resource.candidate.texture,
|
|
1293
|
-
filteredCubeView: resource.candidate.cubeView,
|
|
1294
|
-
filteredFaceViewsByMip: resource.candidate.mipViews,
|
|
1295
|
-
outputFormat: this.outputFormat,
|
|
1296
|
-
sampler: resource.sampler,
|
|
1297
|
-
filterPipeline: this.shared.filterPipeline,
|
|
1298
|
-
filterGroup0: filterGroup0?.value,
|
|
1299
|
-
filterGroup1: resource.filterGroup1,
|
|
1300
|
-
cubeVertexBuffer: this.shared.vertexBuffer,
|
|
1301
|
-
rawCaptureFace,
|
|
1302
|
-
rawDepthView: resource.raw.depthView,
|
|
1303
|
-
rawSize: resource.raw.size,
|
|
1304
|
-
...(faceCamera === undefined ? {} : { faceCamera }),
|
|
1305
|
-
...(rawCaptureFace === undefined
|
|
1306
|
-
? {}
|
|
1307
|
-
: {
|
|
1308
|
-
viewBindGroupDynamicOffset:
|
|
1309
|
-
(REFLECTION_PROBE_VIEW_SLOT_BASE + resource.index) * VIEW_UNIFORM_SLOT_STRIDE,
|
|
1310
|
-
}),
|
|
1311
|
-
filteredSize: resource.candidate.size,
|
|
1312
|
-
step,
|
|
1313
|
-
});
|
|
1314
|
-
}
|
|
1315
|
-
return {
|
|
1316
|
-
table,
|
|
1317
|
-
selections,
|
|
1318
|
-
graph: { work },
|
|
1319
|
-
completeSubmission: (submitted, completed) => this.completeSubmission(submitted, completed),
|
|
1320
|
-
};
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
private completeSubmission(submitted: boolean, completed?: Promise<unknown>): void {
|
|
1324
|
-
if (completed !== undefined) {
|
|
1325
|
-
void completed.then(
|
|
1326
|
-
() => this.completeSubmission(submitted),
|
|
1327
|
-
() => this.completeSubmission(false),
|
|
1328
|
-
);
|
|
1329
|
-
return;
|
|
1330
|
-
}
|
|
1331
|
-
for (const resource of this.resources.values()) {
|
|
1332
|
-
const pending = resource.pending;
|
|
1333
|
-
delete resource.pending;
|
|
1334
|
-
if (!submitted || pending === undefined) continue;
|
|
1335
|
-
if (pending.rawCaptureFace !== undefined) {
|
|
1336
|
-
resource.rawFaceCursor = Math.min(
|
|
1337
|
-
resource.rawFaceCursor + 1,
|
|
1338
|
-
resource.raw.faceViews.length,
|
|
1339
|
-
);
|
|
1340
|
-
continue;
|
|
1341
|
-
}
|
|
1342
|
-
if (pending.step === undefined) continue;
|
|
1343
|
-
resource.filter = commitProbeFilterStep(resource.filter, true);
|
|
1344
|
-
if (resource.filter.cursor >= resource.filter.faceCount * resource.filter.mipCount) {
|
|
1345
|
-
const previous = resource.active;
|
|
1346
|
-
resource.active = resource.candidate;
|
|
1347
|
-
resource.candidate = resource.spare ?? previous ?? resource.candidate;
|
|
1348
|
-
resource.spare = undefined;
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
dispose(): void {
|
|
1354
|
-
for (const resource of this.resources.values()) {
|
|
1355
|
-
this.destroyProbeResource(resource);
|
|
1356
|
-
}
|
|
1357
|
-
this.resources.clear();
|
|
1358
|
-
if (this.shared !== undefined) {
|
|
1359
|
-
this.internals.device.destroyBuffer(this.shared.faceUniforms);
|
|
1360
|
-
this.internals.device.destroyBuffer(this.shared.prefilterUniforms);
|
|
1361
|
-
this.internals.device.destroyBuffer(this.shared.vertexBuffer);
|
|
1362
|
-
}
|
|
1363
|
-
this.shared = undefined;
|
|
1364
|
-
this.pipelineWarmup = undefined;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
private destroyProbeResource(resource: ProbeGpuResource): void {
|
|
1368
|
-
const sets = new Set<ProbeCubeSet>([resource.raw, resource.candidate]);
|
|
1369
|
-
if (resource.active !== undefined) sets.add(resource.active);
|
|
1370
|
-
if (resource.spare !== undefined) sets.add(resource.spare);
|
|
1371
|
-
for (const set of sets) this.destroyCubeSet(set);
|
|
1372
|
-
this.internals.device.destroyBuffer(resource.uniformBuffer);
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
private destroyCubeSet(set: ProbeCubeSet): void {
|
|
1376
|
-
this.internals.device.destroyTexture(set.texture);
|
|
1377
|
-
this.internals.device.destroyTexture(set.depthTexture);
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
private resourceFor(
|
|
1381
|
-
key: string,
|
|
1382
|
-
fact: ReflectionProbeFact,
|
|
1383
|
-
index: number,
|
|
1384
|
-
): ProbeGpuResource | undefined {
|
|
1385
|
-
const existing = this.resources.get(key);
|
|
1386
|
-
if (existing !== undefined) {
|
|
1387
|
-
if (existing.fact.revision !== fact.revision) {
|
|
1388
|
-
existing.fact = fact;
|
|
1389
|
-
existing.filter = createProbeFilterState({
|
|
1390
|
-
probeIndex: index,
|
|
1391
|
-
mipCount: PREFILTER_MIP_LEVELS,
|
|
1392
|
-
activeGeneration: existing.filter.activeGeneration,
|
|
1393
|
-
});
|
|
1394
|
-
existing.rawFaceCursor = 0;
|
|
1395
|
-
} else {
|
|
1396
|
-
existing.fact = fact;
|
|
1397
|
-
existing.index = index;
|
|
1398
|
-
}
|
|
1399
|
-
return existing;
|
|
1400
|
-
}
|
|
1401
|
-
if (this.shared === undefined) return undefined;
|
|
1402
|
-
const raw = this.createCubeSet(`reflection-probe-${index}-raw`, fact.resolution);
|
|
1403
|
-
const candidate = this.createCubeSet(`reflection-probe-${index}-candidate`, fact.resolution);
|
|
1404
|
-
const spare = this.createCubeSet(`reflection-probe-${index}-spare`, fact.resolution);
|
|
1405
|
-
if (raw === undefined || candidate === undefined || spare === undefined) return undefined;
|
|
1406
|
-
const uniformBuffer = this.internals.device.createBuffer({
|
|
1407
|
-
label: `reflection-probe-${index}-uniforms`,
|
|
1408
|
-
size: 32,
|
|
1409
|
-
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1410
|
-
mappedAtCreation: false,
|
|
1411
|
-
});
|
|
1412
|
-
if (!uniformBuffer.ok) return undefined;
|
|
1413
|
-
const payload = new Float32Array([
|
|
1414
|
-
-Math.max(0, fact.intensity),
|
|
1415
|
-
fact.center[0],
|
|
1416
|
-
fact.center[1],
|
|
1417
|
-
fact.center[2],
|
|
1418
|
-
fact.halfExtents[0],
|
|
1419
|
-
fact.halfExtents[1],
|
|
1420
|
-
fact.halfExtents[2],
|
|
1421
|
-
0,
|
|
1422
|
-
]);
|
|
1423
|
-
const written = this.internals.device.queue.writeBuffer(uniformBuffer.value, 0, payload);
|
|
1424
|
-
if (!written.ok) return undefined;
|
|
1425
|
-
const filterGroup1 = this.internals.device.createBindGroup({
|
|
1426
|
-
label: `reflection-probe-${index}-filter-input`,
|
|
1427
|
-
layout: this.shared.cubeGroup1Bgl,
|
|
1428
|
-
entries: [
|
|
1429
|
-
{ binding: 0, resource: { kind: 'textureView', value: raw.cubeView } },
|
|
1430
|
-
{ binding: 1, resource: { kind: 'sampler', value: this.shared.sampler } },
|
|
1431
|
-
],
|
|
1432
|
-
});
|
|
1433
|
-
if (!filterGroup1.ok) return undefined;
|
|
1434
|
-
const resource: ProbeGpuResource = {
|
|
1435
|
-
fact,
|
|
1436
|
-
index,
|
|
1437
|
-
filter: createProbeFilterState({ probeIndex: index, mipCount: PREFILTER_MIP_LEVELS }),
|
|
1438
|
-
rawFaceCursor: 0,
|
|
1439
|
-
raw,
|
|
1440
|
-
active: undefined,
|
|
1441
|
-
candidate,
|
|
1442
|
-
spare,
|
|
1443
|
-
sampler: this.shared.sampler,
|
|
1444
|
-
uniformBuffer: uniformBuffer.value,
|
|
1445
|
-
filterGroup1: filterGroup1.value,
|
|
1446
|
-
};
|
|
1447
|
-
this.resources.set(key, resource);
|
|
1448
|
-
return resource;
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
private createSharedResources(filterPipeline: RenderPipeline): ProbeSharedResources | undefined {
|
|
1452
|
-
const faceUniforms = createFaceUniformsBuffer(this.internals.device);
|
|
1453
|
-
const prefilterUniforms = createPrefilterUniformsBuffer(this.internals.device);
|
|
1454
|
-
if (!faceUniforms.ok || !prefilterUniforms.ok) return undefined;
|
|
1455
|
-
if (!writeAllFaceUniforms(this.internals.device, faceUniforms.value).ok) return undefined;
|
|
1456
|
-
if (!writeAllPrefilterUniforms(this.internals.device, prefilterUniforms.value).ok)
|
|
1457
|
-
return undefined;
|
|
1458
|
-
const vertexBuffer = this.internals.device.createBuffer({
|
|
1459
|
-
label: 'reflection-probe-capture-cube-vertices',
|
|
1460
|
-
size: CUBEMAP_FACE_VERTICES.byteLength,
|
|
1461
|
-
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
1462
|
-
mappedAtCreation: false,
|
|
1463
|
-
});
|
|
1464
|
-
if (!vertexBuffer.ok) return undefined;
|
|
1465
|
-
const vertexWrite = this.internals.device.queue.writeBuffer(
|
|
1466
|
-
vertexBuffer.value,
|
|
1467
|
-
0,
|
|
1468
|
-
CUBEMAP_FACE_VERTICES,
|
|
1469
|
-
);
|
|
1470
|
-
if (!vertexWrite.ok) return undefined;
|
|
1471
|
-
const sampler = this.internals.device.createSampler({
|
|
1472
|
-
label: 'reflection-probe-pmrem-sampler',
|
|
1473
|
-
magFilter: 'linear',
|
|
1474
|
-
minFilter: 'linear',
|
|
1475
|
-
mipmapFilter: 'linear',
|
|
1476
|
-
addressModeU: 'clamp-to-edge',
|
|
1477
|
-
addressModeV: 'clamp-to-edge',
|
|
1478
|
-
addressModeW: 'clamp-to-edge',
|
|
1479
|
-
});
|
|
1480
|
-
const cache = getOrCreateIblCache(this.internals.deviceScope);
|
|
1481
|
-
if (
|
|
1482
|
-
!sampler.ok ||
|
|
1483
|
-
cache.cubeGroup1Bgl === undefined ||
|
|
1484
|
-
cache.prefilterGroup0Bgl === undefined
|
|
1485
|
-
) {
|
|
1486
|
-
return undefined;
|
|
1487
|
-
}
|
|
1488
|
-
return {
|
|
1489
|
-
faceUniforms: faceUniforms.value,
|
|
1490
|
-
prefilterUniforms: prefilterUniforms.value,
|
|
1491
|
-
vertexBuffer: vertexBuffer.value,
|
|
1492
|
-
sampler: sampler.value,
|
|
1493
|
-
filterPipeline,
|
|
1494
|
-
cubeGroup1Bgl: cache.cubeGroup1Bgl,
|
|
1495
|
-
prefilterGroup0Bgl: cache.prefilterGroup0Bgl,
|
|
1496
|
-
};
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
private createCubeSet(label: string, requestedSize: number): ProbeCubeSet | undefined {
|
|
1500
|
-
const size = Math.min(Math.max(1, Math.floor(requestedSize)), PREFILTER_SIZE);
|
|
1501
|
-
const mipCount = Math.min(PREFILTER_MIP_LEVELS, Math.floor(Math.log2(size)) + 1);
|
|
1502
|
-
const texture = this.internals.device.createTexture({
|
|
1503
|
-
label,
|
|
1504
|
-
size: { width: size, height: size, depthOrArrayLayers: 6 },
|
|
1505
|
-
mipLevelCount: mipCount,
|
|
1506
|
-
sampleCount: 1,
|
|
1507
|
-
dimension: '2d',
|
|
1508
|
-
format: this.outputFormat,
|
|
1509
|
-
usage: GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_SRC,
|
|
1510
|
-
viewFormats: [],
|
|
1511
|
-
textureBindingViewDimension: undefined,
|
|
1512
|
-
});
|
|
1513
|
-
if (!texture.ok) return undefined;
|
|
1514
|
-
const cubeView = this.internals.device.createTextureView(texture.value, {
|
|
1515
|
-
label: `${label}-cube`,
|
|
1516
|
-
dimension: 'cube',
|
|
1517
|
-
baseMipLevel: 0,
|
|
1518
|
-
mipLevelCount: mipCount,
|
|
1519
|
-
arrayLayerCount: 6,
|
|
1520
|
-
});
|
|
1521
|
-
if (!cubeView.ok) return undefined;
|
|
1522
|
-
const faceViews: TextureView[] = [];
|
|
1523
|
-
const mipViews: TextureView[][] = [];
|
|
1524
|
-
for (let mip = 0; mip < mipCount; mip += 1) {
|
|
1525
|
-
const faces: TextureView[] = [];
|
|
1526
|
-
for (let face = 0; face < 6; face += 1) {
|
|
1527
|
-
const view = this.internals.device.createTextureView(texture.value, {
|
|
1528
|
-
label: `${label}-mip${mip}-face${face}`,
|
|
1529
|
-
dimension: '2d',
|
|
1530
|
-
baseMipLevel: mip,
|
|
1531
|
-
mipLevelCount: 1,
|
|
1532
|
-
baseArrayLayer: face,
|
|
1533
|
-
arrayLayerCount: 1,
|
|
1534
|
-
});
|
|
1535
|
-
if (!view.ok) return undefined;
|
|
1536
|
-
if (mip === 0) faceViews.push(view.value);
|
|
1537
|
-
faces.push(view.value);
|
|
1538
|
-
}
|
|
1539
|
-
mipViews.push(faces);
|
|
1540
|
-
}
|
|
1541
|
-
const depthTexture = this.internals.device.createTexture({
|
|
1542
|
-
label: `${label}-depth`,
|
|
1543
|
-
size: { width: size, height: size, depthOrArrayLayers: 1 },
|
|
1544
|
-
mipLevelCount: 1,
|
|
1545
|
-
sampleCount: 1,
|
|
1546
|
-
dimension: '2d',
|
|
1547
|
-
format: 'depth24plus-stencil8',
|
|
1548
|
-
usage: 0x10,
|
|
1549
|
-
viewFormats: [],
|
|
1550
|
-
textureBindingViewDimension: undefined,
|
|
1551
|
-
});
|
|
1552
|
-
if (!depthTexture.ok) return undefined;
|
|
1553
|
-
const depthView = this.internals.device.createTextureView(depthTexture.value, {
|
|
1554
|
-
label: `${label}-depth-view`,
|
|
1555
|
-
dimension: '2d',
|
|
1556
|
-
});
|
|
1557
|
-
if (!depthView.ok) return undefined;
|
|
1558
|
-
return {
|
|
1559
|
-
texture: texture.value,
|
|
1560
|
-
cubeView: cubeView.value,
|
|
1561
|
-
faceViews,
|
|
1562
|
-
mipViews,
|
|
1563
|
-
depthTexture: depthTexture.value,
|
|
1564
|
-
depthView: depthView.value,
|
|
1565
|
-
size,
|
|
1566
|
-
};
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
1120
|
export function createRenderSystem(internals: RenderSystemInternals): RenderSystem {
|
|
1571
1121
|
const phaseCatalogRegistration = internals.profiler?.registerPhaseCatalog(
|
|
1572
1122
|
'render',
|
|
@@ -1578,6 +1128,38 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1578
1128
|
let latestCamera: CameraSnapshot | undefined;
|
|
1579
1129
|
const pointsLinesOwner = new StandardPointsLinesOwner(internals);
|
|
1580
1130
|
const reflectionProbeOwner = new ReflectionProbeRecordOwner(internals);
|
|
1131
|
+
let ssrFormatReceipt: import('@forgeax/engine-rhi').RhiTextureFormatCapabilityReceipt | undefined;
|
|
1132
|
+
let ssrFormatProbeGeneration = -1;
|
|
1133
|
+
let ssrFormatProbe: Promise<void> | undefined;
|
|
1134
|
+
let ssrFallbackGeneration: number | undefined;
|
|
1135
|
+
const ensureSsrFormatProbe = (): void => {
|
|
1136
|
+
if (internals.ssrIdentity === undefined) return;
|
|
1137
|
+
const generation = internals.deviceScope.generation;
|
|
1138
|
+
if (
|
|
1139
|
+
ssrFormatReceipt?.deviceGeneration === generation ||
|
|
1140
|
+
ssrFormatProbeGeneration === generation ||
|
|
1141
|
+
ssrFormatProbe !== undefined ||
|
|
1142
|
+
typeof internals.device.probeTextureFormatCapability !== 'function'
|
|
1143
|
+
) {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
ssrFormatProbeGeneration = generation;
|
|
1147
|
+
const device = internals.device;
|
|
1148
|
+
ssrFormatProbe = device
|
|
1149
|
+
.probeTextureFormatCapability()
|
|
1150
|
+
.then((result) => {
|
|
1151
|
+
if (internals.device !== device || internals.deviceScope.generation !== generation) return;
|
|
1152
|
+
ssrFormatReceipt = result.ok ? result.value : undefined;
|
|
1153
|
+
})
|
|
1154
|
+
.catch(() => {
|
|
1155
|
+
if (internals.device === device && internals.deviceScope.generation === generation) {
|
|
1156
|
+
ssrFormatReceipt = undefined;
|
|
1157
|
+
}
|
|
1158
|
+
})
|
|
1159
|
+
.finally(() => {
|
|
1160
|
+
ssrFormatProbe = undefined;
|
|
1161
|
+
});
|
|
1162
|
+
};
|
|
1581
1163
|
const cubeCaptureScheduler = createCubeCaptureScheduler({ maxFacesPerFrame: 1 });
|
|
1582
1164
|
const cubeCaptureState: CubeCaptureGraphState = { work: [] };
|
|
1583
1165
|
const persistentRenderScene = new PersistentRenderScene({
|
|
@@ -1725,6 +1307,23 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1725
1307
|
),
|
|
1726
1308
|
} satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory);
|
|
1727
1309
|
let gpuDrivenProduction = new GpuDrivenProduction(internals.device, gpuDrivenShaderFactory);
|
|
1310
|
+
let occlusionRuntime: OcclusionRenderRuntime | undefined = new OcclusionRenderRuntime(
|
|
1311
|
+
internals.device,
|
|
1312
|
+
persistentRenderScene.visibilityFacetStore(),
|
|
1313
|
+
gpuDrivenShaderFactory,
|
|
1314
|
+
persistentRenderScene.visibilityBudgetValue(),
|
|
1315
|
+
);
|
|
1316
|
+
const ensureOcclusionRuntime = (): OcclusionRenderRuntime => {
|
|
1317
|
+
if (occlusionRuntime === undefined) {
|
|
1318
|
+
occlusionRuntime = new OcclusionRenderRuntime(
|
|
1319
|
+
internals.device,
|
|
1320
|
+
persistentRenderScene.visibilityFacetStore(),
|
|
1321
|
+
gpuDrivenShaderFactory,
|
|
1322
|
+
persistentRenderScene.visibilityBudgetValue(),
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
return occlusionRuntime;
|
|
1326
|
+
};
|
|
1728
1327
|
const featureGpuWork: RenderFeatureGpuWorkOwner = createRenderFeatureGpuWorkOwner({
|
|
1729
1328
|
getDevice: () => internals.device,
|
|
1730
1329
|
getShaderModuleFactory: () =>
|
|
@@ -1772,11 +1371,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1772
1371
|
// raw (material-handle-based, cross-world collision semantically correct).
|
|
1773
1372
|
const frameState: RenderFrameState = {
|
|
1774
1373
|
frameNumber: 0,
|
|
1374
|
+
reflectionFallbackDemand: false,
|
|
1775
1375
|
graphGeneration: 0,
|
|
1776
1376
|
successfulTemporalFrameIndex: 0,
|
|
1777
1377
|
lastSuccessfulBloom: 'off',
|
|
1778
1378
|
temporalFrameTransaction: createTemporalFrameTransaction({ deviceEpoch: 0 }),
|
|
1779
1379
|
temporalFrameInput: undefined,
|
|
1380
|
+
reflectionFallbackReadback: undefined,
|
|
1780
1381
|
directionalShadowCache: null,
|
|
1781
1382
|
directionalShadowCacheRecorded: false,
|
|
1782
1383
|
compiledFrameGraph: null,
|
|
@@ -1798,6 +1399,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1798
1399
|
volumetricFogHistorySignature: null,
|
|
1799
1400
|
retiredCompiledFrameGraphs: new Set(),
|
|
1800
1401
|
currentFrameObservationSource: undefined,
|
|
1402
|
+
reflectionFallbackObservationSource: undefined,
|
|
1801
1403
|
lastSuccessfulCameraAntialias: undefined,
|
|
1802
1404
|
temporalGpuState: undefined,
|
|
1803
1405
|
activeTemporalGpuState: undefined,
|
|
@@ -1809,6 +1411,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1809
1411
|
currentDirectionalShadowView: null,
|
|
1810
1412
|
currentSpotShadowView: null,
|
|
1811
1413
|
instanceBuffers: new Map(),
|
|
1414
|
+
probeBlendBuffers: new Map(),
|
|
1415
|
+
probeBlendRecordBufferCapacity: 0,
|
|
1812
1416
|
morphBuffers: new Map(),
|
|
1813
1417
|
hdrpClusterBinScratch: createClusterBinScratch(),
|
|
1814
1418
|
hdrpClusterGridScratch: null,
|
|
@@ -1820,8 +1424,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1820
1424
|
transientInstanceBuffers: [],
|
|
1821
1425
|
warnedZeroLightStandard: false,
|
|
1822
1426
|
warnedMultiLightDirectional: false,
|
|
1823
|
-
warnedMultiLightPoint: false,
|
|
1824
|
-
warnedMultiLightSpot: false,
|
|
1825
1427
|
// feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M3 / w19:
|
|
1826
1428
|
// once-warn latches for >1 Skylight / >1 SkyboxBackground (names winner).
|
|
1827
1429
|
warnedMultiSkylight: false,
|
|
@@ -2104,6 +1706,134 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2104
1706
|
const dispatchCounts: { unlit: number } = {
|
|
2105
1707
|
unlit: 0,
|
|
2106
1708
|
};
|
|
1709
|
+
let lastLodOcclusionInspection: LodOcclusionInspection | undefined;
|
|
1710
|
+
// The GPU selector returns stable numeric World keys with its same-submit
|
|
1711
|
+
// counters. Keep the attachment join from that submit, rather than using
|
|
1712
|
+
// the current worlds[] position when the asynchronous readback completes.
|
|
1713
|
+
let lastLodWorldAttachments: ReadonlyMap<number, string> = new Map();
|
|
1714
|
+
let occlusionCandidateCache:
|
|
1715
|
+
| {
|
|
1716
|
+
readonly renderables: readonly RenderableSnapshot[];
|
|
1717
|
+
readonly camera: CameraSnapshot;
|
|
1718
|
+
readonly cameraWorldIdentity: string;
|
|
1719
|
+
readonly candidates: readonly OcclusionRuntimeCandidate[];
|
|
1720
|
+
}
|
|
1721
|
+
| undefined;
|
|
1722
|
+
let lodCandidateCountCache:
|
|
1723
|
+
| { readonly renderables: readonly RenderableSnapshot[]; readonly count: number }
|
|
1724
|
+
| undefined;
|
|
1725
|
+
let lodInspectionRenderablesCache:
|
|
1726
|
+
| {
|
|
1727
|
+
readonly submissionRenderables: readonly RenderableSnapshot[];
|
|
1728
|
+
readonly lodCandidateCount: number;
|
|
1729
|
+
readonly values: readonly RenderableSnapshot[];
|
|
1730
|
+
}
|
|
1731
|
+
| undefined;
|
|
1732
|
+
let orderedDispatchCache:
|
|
1733
|
+
| {
|
|
1734
|
+
readonly source: readonly DispatchEntry[];
|
|
1735
|
+
readonly world: World;
|
|
1736
|
+
readonly camera: readonly CameraSnapshot[];
|
|
1737
|
+
readonly renderables: readonly RenderableSnapshot[];
|
|
1738
|
+
readonly value: readonly DispatchEntry[];
|
|
1739
|
+
}
|
|
1740
|
+
| undefined;
|
|
1741
|
+
const applyLodSelectionTelemetry = (
|
|
1742
|
+
selection:
|
|
1743
|
+
| {
|
|
1744
|
+
readonly candidateCount: number;
|
|
1745
|
+
readonly visible: number;
|
|
1746
|
+
readonly occluded: number;
|
|
1747
|
+
readonly lodHistogram: readonly { readonly level: number; readonly count: number }[];
|
|
1748
|
+
readonly worldSelections?: readonly {
|
|
1749
|
+
readonly worldKey: number;
|
|
1750
|
+
readonly primitiveSlot: number;
|
|
1751
|
+
readonly slotGeneration: number;
|
|
1752
|
+
readonly candidateCount: number;
|
|
1753
|
+
readonly visible: number;
|
|
1754
|
+
readonly occluded: number;
|
|
1755
|
+
readonly lodHistogram: readonly { readonly level: number; readonly count: number }[];
|
|
1756
|
+
}[];
|
|
1757
|
+
}
|
|
1758
|
+
| undefined,
|
|
1759
|
+
): void => {
|
|
1760
|
+
if (selection === undefined || lastLodOcclusionInspection === undefined) return;
|
|
1761
|
+
const inspection = lastLodOcclusionInspection;
|
|
1762
|
+
// The GPU readback histogram is the selector fact, while final submitted
|
|
1763
|
+
// counts are owned by the persistent visibility projection. Keeping the
|
|
1764
|
+
// projection count here excludes ordinary non-LOD geometry (for example a
|
|
1765
|
+
// benchmark occluder) from the LOD workload without inventing a second
|
|
1766
|
+
// visibility owner or mixing CPU/GPU admission decisions.
|
|
1767
|
+
let worlds = inspection.worlds;
|
|
1768
|
+
const worldSelections = selection.worldSelections;
|
|
1769
|
+
if (worldSelections !== undefined && worldSelections.length > 0) {
|
|
1770
|
+
const selectionsByAttachment = new Map<string, (typeof worldSelections)[number][]>();
|
|
1771
|
+
let attributionValid = true;
|
|
1772
|
+
for (const selected of worldSelections) {
|
|
1773
|
+
const attachmentId = lastLodWorldAttachments.get(selected.worldKey);
|
|
1774
|
+
if (attachmentId === undefined) {
|
|
1775
|
+
attributionValid = false;
|
|
1776
|
+
break;
|
|
1777
|
+
}
|
|
1778
|
+
const rows = selectionsByAttachment.get(attachmentId);
|
|
1779
|
+
if (rows === undefined) selectionsByAttachment.set(attachmentId, [selected]);
|
|
1780
|
+
else rows.push(selected);
|
|
1781
|
+
}
|
|
1782
|
+
if (attributionValid) {
|
|
1783
|
+
const nextWorlds = worlds.map((world) => {
|
|
1784
|
+
const selected = selectionsByAttachment.get(world.attachmentId);
|
|
1785
|
+
const baseRow = world.rows[0];
|
|
1786
|
+
if (selected === undefined || baseRow === undefined) {
|
|
1787
|
+
if (world.rows.length > 0) attributionValid = false;
|
|
1788
|
+
return world;
|
|
1789
|
+
}
|
|
1790
|
+
const candidateCount = selected.reduce((sum, row) => sum + row.candidateCount, 0);
|
|
1791
|
+
if (candidateCount !== baseRow.count.candidates) {
|
|
1792
|
+
attributionValid = false;
|
|
1793
|
+
return world;
|
|
1794
|
+
}
|
|
1795
|
+
return Object.freeze({
|
|
1796
|
+
...world,
|
|
1797
|
+
attribution: Object.freeze({
|
|
1798
|
+
status: 'same-submit' as const,
|
|
1799
|
+
submit: inspection.submit,
|
|
1800
|
+
}),
|
|
1801
|
+
rows: Object.freeze(
|
|
1802
|
+
selected.map((row) => ({
|
|
1803
|
+
...baseRow,
|
|
1804
|
+
slot: {
|
|
1805
|
+
primitiveSlot: row.primitiveSlot,
|
|
1806
|
+
slotGeneration: row.slotGeneration,
|
|
1807
|
+
},
|
|
1808
|
+
count: {
|
|
1809
|
+
candidates: row.candidateCount,
|
|
1810
|
+
visible: row.visible,
|
|
1811
|
+
occluded: row.occluded,
|
|
1812
|
+
},
|
|
1813
|
+
lodHistogram: row.lodHistogram,
|
|
1814
|
+
})),
|
|
1815
|
+
),
|
|
1816
|
+
});
|
|
1817
|
+
});
|
|
1818
|
+
if (attributionValid) worlds = Object.freeze(nextWorlds);
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
lastLodOcclusionInspection = inspectLodOcclusion({
|
|
1822
|
+
...inspection,
|
|
1823
|
+
count: {
|
|
1824
|
+
...lastLodOcclusionInspection.count,
|
|
1825
|
+
candidates: selection.candidateCount,
|
|
1826
|
+
visible: selection.visible,
|
|
1827
|
+
occluded: selection.occluded,
|
|
1828
|
+
},
|
|
1829
|
+
lodHistogram: selection.lodHistogram,
|
|
1830
|
+
// Per-primitive samples are intentionally omitted until the GPU readback
|
|
1831
|
+
// carries stable primitive identities; the histogram is the authoritative
|
|
1832
|
+
// producer fact and is copied from the same GPU cull counters.
|
|
1833
|
+
samples: [],
|
|
1834
|
+
worlds,
|
|
1835
|
+
});
|
|
1836
|
+
};
|
|
2107
1837
|
// feat-20260531-per-frame-bind-group-cache M1 / w4: per-frame
|
|
2108
1838
|
// createBindGroup counter scaffolding. Reset on every draw([world], { cameraOwner: 0, resourceOwner: 0 }) entry,
|
|
2109
1839
|
// bumped on cache-miss in render-system-record.ts (M2-M4 bump points).
|
|
@@ -2466,9 +2196,44 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2466
2196
|
gpuDriven: gpuDrivenProduction.inspect(),
|
|
2467
2197
|
};
|
|
2468
2198
|
},
|
|
2199
|
+
get gpuDrivenInspection(): GpuDrivenProductionInspection {
|
|
2200
|
+
return gpuDrivenProduction.inspect();
|
|
2201
|
+
},
|
|
2469
2202
|
get reflectionProbes(): ReflectionProbeInspection {
|
|
2470
2203
|
return reflectionProbeOwner.inspect();
|
|
2471
2204
|
},
|
|
2205
|
+
get ssrDependencies(): SsrDependenciesInspection {
|
|
2206
|
+
ensureSsrFormatProbe();
|
|
2207
|
+
const owner = reflectionProbeOwner.inspect();
|
|
2208
|
+
const nextGeneration = owner.reflectionFallback.projectionGeneration;
|
|
2209
|
+
const reset = resolveSsrAdmissionGeneration(ssrFallbackGeneration, nextGeneration);
|
|
2210
|
+
if (reset.changed) {
|
|
2211
|
+
frameState.temporalFrameTransaction.reset('signature-change');
|
|
2212
|
+
frameState.temporalFrame = undefined;
|
|
2213
|
+
frameState.temporalFrameInput = undefined;
|
|
2214
|
+
frameState.lastSuccessfulTemporalView = undefined;
|
|
2215
|
+
frameState.successfulTemporalFrameIndex = 0;
|
|
2216
|
+
frameState.pendingTemporalCommit = { kind: 'none' };
|
|
2217
|
+
}
|
|
2218
|
+
ssrFallbackGeneration = reset.generation;
|
|
2219
|
+
const temporal =
|
|
2220
|
+
frameState.lastSuccessfulTemporalView === undefined
|
|
2221
|
+
? undefined
|
|
2222
|
+
: ({
|
|
2223
|
+
successfulSubmit: true,
|
|
2224
|
+
generation: frameState.lastSuccessfulTemporalView.input.deviceGeneration,
|
|
2225
|
+
} as const);
|
|
2226
|
+
return projectSsrDependencies({
|
|
2227
|
+
requested: frameState.reflectionFallbackDemand === true,
|
|
2228
|
+
identity: internals.ssrIdentity,
|
|
2229
|
+
reflectionFallback: owner.reflectionFallback,
|
|
2230
|
+
format: ssrFormatReceipt,
|
|
2231
|
+
temporal,
|
|
2232
|
+
});
|
|
2233
|
+
},
|
|
2234
|
+
get reflectionFallbackCompletion(): Promise<void> | undefined {
|
|
2235
|
+
return frameState.reflectionFallbackCompletion;
|
|
2236
|
+
},
|
|
2472
2237
|
isCubeCapturePending(target: RenderTarget): boolean {
|
|
2473
2238
|
return cubeCaptureScheduler.inspect(target).candidateGeneration !== undefined;
|
|
2474
2239
|
},
|
|
@@ -2538,6 +2303,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2538
2303
|
}),
|
|
2539
2304
|
});
|
|
2540
2305
|
},
|
|
2306
|
+
get lodOcclusionInspection(): LodOcclusionInspection | undefined {
|
|
2307
|
+
return lastLodOcclusionInspection;
|
|
2308
|
+
},
|
|
2541
2309
|
get bloom(): BloomInspection {
|
|
2542
2310
|
const pipelineState = internals.getPipelineState();
|
|
2543
2311
|
const resources = pipelineState?.perPassResources;
|
|
@@ -2676,14 +2444,20 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2676
2444
|
worlds,
|
|
2677
2445
|
{ cameraOwner, resourceOwner },
|
|
2678
2446
|
internals.assets.catalogEpoch,
|
|
2679
|
-
() =>
|
|
2447
|
+
(renderables) =>
|
|
2680
2448
|
extractFrames(
|
|
2681
2449
|
worlds,
|
|
2682
2450
|
{ cameraOwner, resourceOwner },
|
|
2683
2451
|
internals.assets,
|
|
2684
2452
|
internals.getPipelineState(),
|
|
2685
2453
|
persistentRenderScene.materialSnapshotCacheStore(),
|
|
2686
|
-
{
|
|
2454
|
+
{
|
|
2455
|
+
cull: 'none',
|
|
2456
|
+
renderables,
|
|
2457
|
+
...(internals.getMaterialShaderArtifact === undefined
|
|
2458
|
+
? {}
|
|
2459
|
+
: { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
|
|
2460
|
+
},
|
|
2687
2461
|
),
|
|
2688
2462
|
renderReadLeases,
|
|
2689
2463
|
);
|
|
@@ -2701,7 +2475,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2701
2475
|
skylightCount,
|
|
2702
2476
|
skybox,
|
|
2703
2477
|
skyboxCount,
|
|
2704
|
-
fog,
|
|
2705
2478
|
fogFailure,
|
|
2706
2479
|
frustumStats,
|
|
2707
2480
|
visibilityStats,
|
|
@@ -2714,12 +2487,279 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2714
2487
|
frame.reflectionProbes ?? [],
|
|
2715
2488
|
persistentRenderScene.reflectionProbeProjection().selected,
|
|
2716
2489
|
cameras[0],
|
|
2490
|
+
frameState.frameNumber,
|
|
2491
|
+
skylight,
|
|
2717
2492
|
);
|
|
2718
2493
|
latestCamera = cameras[0];
|
|
2494
|
+
persistentRenderScene.updateVisibilityFacet(worlds, latestCamera, renderables);
|
|
2495
|
+
let occlusionProjection:
|
|
2496
|
+
| import('./scene/visibility/occlusion-runtime').OcclusionFrameProjection
|
|
2497
|
+
| undefined;
|
|
2498
|
+
let occlusionFallback: LodOcclusionInspection['fallback'] = { active: false };
|
|
2499
|
+
const worldKeys = persistentRenderScene.visibilityWorldKeysFor(worlds);
|
|
2500
|
+
const slotByEntity = persistentRenderScene.compositionSlotByStableEntity();
|
|
2501
|
+
if (latestCamera !== undefined) {
|
|
2502
|
+
const camera = latestCamera;
|
|
2503
|
+
const cameraWorld = worlds[camera.worldId ?? cameraOwner];
|
|
2504
|
+
const view = viewKey({
|
|
2505
|
+
attachmentId: cameraWorld?.identity ?? 'missing-camera-world',
|
|
2506
|
+
cameraEntity: camera.entityKey ?? 0,
|
|
2507
|
+
viewRole: 'main',
|
|
2508
|
+
viewGeneration: camera.historyVersion ?? 0,
|
|
2509
|
+
});
|
|
2510
|
+
const cachedCandidates = occlusionCandidateCache;
|
|
2511
|
+
const candidates =
|
|
2512
|
+
cachedCandidates?.renderables === renderables &&
|
|
2513
|
+
cachedCandidates.camera === camera &&
|
|
2514
|
+
cachedCandidates.cameraWorldIdentity === (cameraWorld?.identity ?? '')
|
|
2515
|
+
? cachedCandidates.candidates
|
|
2516
|
+
: renderables.flatMap((renderable) => {
|
|
2517
|
+
const world = worlds[renderable.worldId];
|
|
2518
|
+
const aabb = renderable.localAabb;
|
|
2519
|
+
// Occlusion is a LOD transport concern. A plain mesh has no
|
|
2520
|
+
// lower-detail range to select, so keep it on the ordinary CPU
|
|
2521
|
+
// visibility path and do not allocate query resources for it.
|
|
2522
|
+
if (
|
|
2523
|
+
world === undefined ||
|
|
2524
|
+
cameraWorld === undefined ||
|
|
2525
|
+
aabb === undefined ||
|
|
2526
|
+
aabb.length < 6 ||
|
|
2527
|
+
renderable.lods === undefined ||
|
|
2528
|
+
renderable.lods.length === 0
|
|
2529
|
+
)
|
|
2530
|
+
return [];
|
|
2531
|
+
const slot = slotByEntity.get(
|
|
2532
|
+
worldEntityKey(
|
|
2533
|
+
worldKeys[renderable.worldId] ?? renderable.worldId,
|
|
2534
|
+
renderable.entityKey,
|
|
2535
|
+
),
|
|
2536
|
+
);
|
|
2537
|
+
if (slot === undefined) return [];
|
|
2538
|
+
const primitive = primitiveKey({
|
|
2539
|
+
attachmentId: cameraWorld.identity,
|
|
2540
|
+
worldGeneration: worldKeys[renderable.worldId] ?? renderable.worldId,
|
|
2541
|
+
primitiveSlot: slot.slot,
|
|
2542
|
+
slotGeneration: slot.generation,
|
|
2543
|
+
});
|
|
2544
|
+
return [
|
|
2545
|
+
{
|
|
2546
|
+
view,
|
|
2547
|
+
primitive,
|
|
2548
|
+
epoch: 0,
|
|
2549
|
+
bounds: {
|
|
2550
|
+
min: [aabb[0] ?? 0, aabb[1] ?? 0, aabb[2] ?? 0] as [number, number, number],
|
|
2551
|
+
max: [aabb[3] ?? 0, aabb[4] ?? 0, aabb[5] ?? 0] as [number, number, number],
|
|
2552
|
+
},
|
|
2553
|
+
candidate: { level: 0, confidence: 1 },
|
|
2554
|
+
deviceGeneration: internals.deviceScope.generation,
|
|
2555
|
+
proxyVertices: buildOcclusionProxyVertices(
|
|
2556
|
+
aabb,
|
|
2557
|
+
renderable.transform.world,
|
|
2558
|
+
camera,
|
|
2559
|
+
),
|
|
2560
|
+
},
|
|
2561
|
+
];
|
|
2562
|
+
});
|
|
2563
|
+
occlusionCandidateCache = {
|
|
2564
|
+
renderables,
|
|
2565
|
+
camera,
|
|
2566
|
+
cameraWorldIdentity: cameraWorld?.identity ?? '',
|
|
2567
|
+
candidates,
|
|
2568
|
+
};
|
|
2569
|
+
occlusionProjection = runProfiledRenderPhase(profileSession, 'occlusion-prepare', () =>
|
|
2570
|
+
ensureOcclusionRuntime().prepareBatch(candidates, camera.antialias === 'msaa' ? 4 : 1),
|
|
2571
|
+
);
|
|
2572
|
+
const queryPool = ensureOcclusionRuntime().inspect();
|
|
2573
|
+
const queryUnavailable = ensureOcclusionRuntime().prepareUnavailable;
|
|
2574
|
+
const queryCapacityFallback =
|
|
2575
|
+
candidates.length > 0 && queryUnavailable && queryPool.availablePages === 0;
|
|
2576
|
+
occlusionFallback = queryCapacityFallback
|
|
2577
|
+
? {
|
|
2578
|
+
active: true as const,
|
|
2579
|
+
reason: 'page-exhausted' as const,
|
|
2580
|
+
error: {
|
|
2581
|
+
code: 'visibility-query-capacity-exhausted',
|
|
2582
|
+
expected: 'one available occlusion-query page for the LOD candidate batch',
|
|
2583
|
+
hint: 'retry the LOD query after an in-flight page completes',
|
|
2584
|
+
detail: {
|
|
2585
|
+
availablePages: queryPool.availablePages,
|
|
2586
|
+
pageCount: queryPool.pageCount,
|
|
2587
|
+
pageIndexLimit: queryPool.pageIndexLimit,
|
|
2588
|
+
},
|
|
2589
|
+
},
|
|
2590
|
+
}
|
|
2591
|
+
: ({ active: false } as const);
|
|
2592
|
+
}
|
|
2593
|
+
// Query transport is best-effort and must fail open. A candidate that
|
|
2594
|
+
// was hidden by an earlier completion becomes visible again when this
|
|
2595
|
+
// device/frame cannot reserve a fresh query page; the next successful
|
|
2596
|
+
// submission can rebuild confidence from a real result.
|
|
2597
|
+
if (
|
|
2598
|
+
occlusionProjection === undefined &&
|
|
2599
|
+
latestCamera !== undefined &&
|
|
2600
|
+
ensureOcclusionRuntime().prepareUnavailable
|
|
2601
|
+
) {
|
|
2602
|
+
const cameraWorld = worlds[latestCamera.worldId ?? cameraOwner];
|
|
2603
|
+
if (cameraWorld !== undefined) {
|
|
2604
|
+
const fallbackView = viewKey({
|
|
2605
|
+
attachmentId: cameraWorld.identity,
|
|
2606
|
+
cameraEntity: latestCamera.entityKey ?? 0,
|
|
2607
|
+
viewRole: 'main',
|
|
2608
|
+
viewGeneration: latestCamera.historyVersion ?? 0,
|
|
2609
|
+
});
|
|
2610
|
+
for (const renderable of renderables) {
|
|
2611
|
+
if ((renderable.lods?.length ?? 0) === 0) continue;
|
|
2612
|
+
const world = worlds[renderable.worldId];
|
|
2613
|
+
if (world === undefined) continue;
|
|
2614
|
+
const slot = slotByEntity.get(
|
|
2615
|
+
worldEntityKey(
|
|
2616
|
+
worldKeys[renderable.worldId] ?? renderable.worldId,
|
|
2617
|
+
renderable.entityKey,
|
|
2618
|
+
),
|
|
2619
|
+
);
|
|
2620
|
+
if (slot === undefined) continue;
|
|
2621
|
+
persistentRenderScene.visibilityFacetStore().applyConfidence(
|
|
2622
|
+
fallbackView,
|
|
2623
|
+
primitiveKey({
|
|
2624
|
+
attachmentId: cameraWorld.identity,
|
|
2625
|
+
worldGeneration: worldKeys[renderable.worldId] ?? renderable.worldId,
|
|
2626
|
+
primitiveSlot: slot.slot,
|
|
2627
|
+
slotGeneration: slot.generation,
|
|
2628
|
+
}),
|
|
2629
|
+
{ type: 'failure', submissionGeneration: frameState.frameNumber },
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
const visibilityProjection = persistentRenderScene.projectVisibility(
|
|
2635
|
+
worlds,
|
|
2636
|
+
latestCamera,
|
|
2637
|
+
renderables,
|
|
2638
|
+
dispatch,
|
|
2639
|
+
);
|
|
2640
|
+
const submissionRenderables = visibilityProjection.renderables;
|
|
2641
|
+
const submissionDispatch = visibilityProjection.dispatch;
|
|
2642
|
+
const inspectionCamera = latestCamera;
|
|
2643
|
+
let lodCandidateCountForFrame = 0;
|
|
2644
|
+
if (inspectionCamera !== undefined) {
|
|
2645
|
+
const firstRenderable = renderables[0];
|
|
2646
|
+
const lodCandidateCount =
|
|
2647
|
+
lodCandidateCountCache?.renderables === renderables
|
|
2648
|
+
? lodCandidateCountCache.count
|
|
2649
|
+
: (() => {
|
|
2650
|
+
const count = renderables.reduce(
|
|
2651
|
+
(total, renderable) => total + ((renderable.lods?.length ?? 0) > 0 ? 1 : 0),
|
|
2652
|
+
0,
|
|
2653
|
+
);
|
|
2654
|
+
lodCandidateCountCache = { renderables, count };
|
|
2655
|
+
return count;
|
|
2656
|
+
})();
|
|
2657
|
+
lodCandidateCountForFrame = lodCandidateCount;
|
|
2658
|
+
const inspectionRenderables =
|
|
2659
|
+
lodInspectionRenderablesCache?.submissionRenderables === submissionRenderables &&
|
|
2660
|
+
lodInspectionRenderablesCache.lodCandidateCount === lodCandidateCount
|
|
2661
|
+
? lodInspectionRenderablesCache.values
|
|
2662
|
+
: (() => {
|
|
2663
|
+
const values =
|
|
2664
|
+
lodCandidateCount > 0
|
|
2665
|
+
? submissionRenderables.filter(
|
|
2666
|
+
(renderable) => (renderable.lods?.length ?? 0) > 0,
|
|
2667
|
+
)
|
|
2668
|
+
: submissionRenderables;
|
|
2669
|
+
lodInspectionRenderablesCache = {
|
|
2670
|
+
submissionRenderables,
|
|
2671
|
+
lodCandidateCount,
|
|
2672
|
+
values,
|
|
2673
|
+
};
|
|
2674
|
+
return values;
|
|
2675
|
+
})();
|
|
2676
|
+
const candidateCount =
|
|
2677
|
+
lodCandidateCount > 0 ? lodCandidateCount : renderables.length + frustumStats.culled;
|
|
2678
|
+
const inspectionQuery = ensureOcclusionRuntime().inspect();
|
|
2679
|
+
const inspectionBudget = persistentRenderScene.visibilityBudgetValue();
|
|
2680
|
+
const inspectionWorlds = createLodWorldInspections(
|
|
2681
|
+
worlds,
|
|
2682
|
+
renderables,
|
|
2683
|
+
submissionRenderables,
|
|
2684
|
+
inspectionCamera,
|
|
2685
|
+
frameState.frameNumber,
|
|
2686
|
+
worldKeys,
|
|
2687
|
+
slotByEntity,
|
|
2688
|
+
{
|
|
2689
|
+
used:
|
|
2690
|
+
inspectionQuery.pageCount * inspectionQuery.pageIndexLimit -
|
|
2691
|
+
inspectionQuery.availablePages * inspectionQuery.pageIndexLimit,
|
|
2692
|
+
capacity: inspectionQuery.pageCount * inspectionQuery.pageIndexLimit,
|
|
2693
|
+
},
|
|
2694
|
+
occlusionFallback,
|
|
2695
|
+
ensureOcclusionRuntime().prepareUnavailable
|
|
2696
|
+
? { active: true, reason: 'query-unavailable' as const }
|
|
2697
|
+
: { active: false },
|
|
2698
|
+
);
|
|
2699
|
+
const attachmentByWorldKey = new Map<number, string>();
|
|
2700
|
+
for (let worldId = 0; worldId < worlds.length; worldId += 1) {
|
|
2701
|
+
const world = worlds[worldId];
|
|
2702
|
+
if (world === undefined) continue;
|
|
2703
|
+
const worldKey = worldKeys[worldId] ?? worldId;
|
|
2704
|
+
if (attachmentByWorldKey.has(worldKey)) {
|
|
2705
|
+
attachmentByWorldKey.clear();
|
|
2706
|
+
break;
|
|
2707
|
+
}
|
|
2708
|
+
attachmentByWorldKey.set(worldKey, world.identity);
|
|
2709
|
+
}
|
|
2710
|
+
lastLodWorldAttachments = attachmentByWorldKey;
|
|
2711
|
+
lastLodOcclusionInspection = inspectLodOcclusion({
|
|
2712
|
+
root: {
|
|
2713
|
+
guid:
|
|
2714
|
+
firstRenderable === undefined
|
|
2715
|
+
? 'none'
|
|
2716
|
+
: `asset-handle:${firstRenderable.assetHandle}`,
|
|
2717
|
+
sourceKey: `render-frame:${frameState.frameNumber}`,
|
|
2718
|
+
},
|
|
2719
|
+
view: {
|
|
2720
|
+
attachmentId: worlds[inspectionCamera.worldId ?? cameraOwner]?.identity ?? 'none',
|
|
2721
|
+
cameraEntity: inspectionCamera.entityKey ?? 0,
|
|
2722
|
+
viewRole: 'main',
|
|
2723
|
+
viewGeneration: inspectionCamera.historyVersion ?? 0,
|
|
2724
|
+
},
|
|
2725
|
+
slot: {
|
|
2726
|
+
primitiveSlot: 0,
|
|
2727
|
+
slotGeneration: persistentRenderScene.inspect().topology.revision,
|
|
2728
|
+
},
|
|
2729
|
+
generation: frameState.frameNumber,
|
|
2730
|
+
count: {
|
|
2731
|
+
candidates: candidateCount,
|
|
2732
|
+
visible: inspectionRenderables.length,
|
|
2733
|
+
occluded: visibilityProjection.suppressed,
|
|
2734
|
+
},
|
|
2735
|
+
lodHistogram: [{ level: 0, count: inspectionRenderables.length }],
|
|
2736
|
+
queryLatencyUs: { median: 0, p95: 0, last: 0 },
|
|
2737
|
+
pagePressure: { used: 0, capacity: 3 * 4096 },
|
|
2738
|
+
fallback: occlusionFallback,
|
|
2739
|
+
degradation: ensureOcclusionRuntime().prepareUnavailable
|
|
2740
|
+
? { active: true, reason: 'query-unavailable' as const }
|
|
2741
|
+
: { active: false },
|
|
2742
|
+
samples: inspectionRenderables.slice(0, 64).map((_, index) => ({
|
|
2743
|
+
primitiveSlot: index,
|
|
2744
|
+
level: 0,
|
|
2745
|
+
visible: true,
|
|
2746
|
+
})),
|
|
2747
|
+
submit: {
|
|
2748
|
+
frameId: frameState.frameNumber,
|
|
2749
|
+
build: resolveInspectionBuild(internals),
|
|
2750
|
+
deviceGeneration: internals.deviceScope.generation,
|
|
2751
|
+
},
|
|
2752
|
+
budget: inspectionBudget,
|
|
2753
|
+
worlds: inspectionWorlds,
|
|
2754
|
+
});
|
|
2755
|
+
} else {
|
|
2756
|
+
lastLodOcclusionInspection = undefined;
|
|
2757
|
+
lastLodWorldAttachments = new Map();
|
|
2758
|
+
}
|
|
2719
2759
|
const temporalDemanded =
|
|
2720
2760
|
cameras[0]?.antialias === 'taa' || (cameras[0]?.motionBlur?.shutterAngle ?? 0) > 0;
|
|
2721
2761
|
if (temporalDemanded) {
|
|
2722
|
-
persistentRenderScene.prepareTemporalFrame(
|
|
2762
|
+
persistentRenderScene.prepareTemporalFrame(submissionRenderables);
|
|
2723
2763
|
}
|
|
2724
2764
|
environmentLifecycle.bindScope(internals.deviceScope);
|
|
2725
2765
|
if (fogFailure !== undefined) {
|
|
@@ -2897,9 +2937,31 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2897
2937
|
// world that owns skylight / skybox / singleton render state, w5 / D-3).
|
|
2898
2938
|
// Only the Transparent segment is reordered; queue ordering between
|
|
2899
2939
|
// segments (sortDispatchByQueue, stable) is preserved.
|
|
2900
|
-
const orderedDispatch = runProfiledRenderPhase(profileSession, 'sort', () =>
|
|
2901
|
-
|
|
2902
|
-
|
|
2940
|
+
const orderedDispatch = runProfiledRenderPhase(profileSession, 'sort', () => {
|
|
2941
|
+
const cached = orderedDispatchCache;
|
|
2942
|
+
if (
|
|
2943
|
+
cached?.source === submissionDispatch &&
|
|
2944
|
+
cached.world === resourceWorld &&
|
|
2945
|
+
cached.camera === cameras &&
|
|
2946
|
+
cached.renderables === submissionRenderables
|
|
2947
|
+
) {
|
|
2948
|
+
return cached.value;
|
|
2949
|
+
}
|
|
2950
|
+
const value = sortTransparentDispatch(
|
|
2951
|
+
[...submissionDispatch],
|
|
2952
|
+
resourceWorld,
|
|
2953
|
+
cameras,
|
|
2954
|
+
submissionRenderables,
|
|
2955
|
+
);
|
|
2956
|
+
orderedDispatchCache = {
|
|
2957
|
+
source: submissionDispatch,
|
|
2958
|
+
world: resourceWorld,
|
|
2959
|
+
camera: cameras,
|
|
2960
|
+
renderables: submissionRenderables,
|
|
2961
|
+
value,
|
|
2962
|
+
};
|
|
2963
|
+
return value;
|
|
2964
|
+
});
|
|
2903
2965
|
|
|
2904
2966
|
// M3 / w26: single dispatch list replaces old three-bucket model.
|
|
2905
2967
|
// Pass dispatch to recordFrame — the record stage iterates dispatch
|
|
@@ -2927,13 +2989,16 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2927
2989
|
// The same runner is also called by geometry/material helpers;
|
|
2928
2990
|
// invoking those wrappers would turn a pass probe into the
|
|
2929
2991
|
// high-overhead per-draw `nested` probe.
|
|
2930
|
-
if (
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2992
|
+
if (profileSession.detail === 'passes') {
|
|
2993
|
+
const isOcclusionOwner =
|
|
2994
|
+
phase === 'record/occlusion-query-submit' ||
|
|
2995
|
+
phase === 'record/occlusion-global-advance';
|
|
2996
|
+
const isGraphOwner =
|
|
2997
|
+
phase === 'record/graph-execute' ||
|
|
2998
|
+
(phase.startsWith('record/graph-execute/') &&
|
|
2999
|
+
(!phase.slice('record/graph-execute/'.length).includes('/') ||
|
|
3000
|
+
phase.endsWith('/geometry-loop')));
|
|
3001
|
+
if (!isOcclusionOwner && !isGraphOwner) return action();
|
|
2937
3002
|
}
|
|
2938
3003
|
return runProfiledRenderPhase(profileSession, phase, action);
|
|
2939
3004
|
};
|
|
@@ -2946,7 +3011,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2946
3011
|
resourceWorld,
|
|
2947
3012
|
cameras,
|
|
2948
3013
|
lights,
|
|
2949
|
-
|
|
3014
|
+
[...submissionRenderables],
|
|
2950
3015
|
orderedDispatch,
|
|
2951
3016
|
frameState,
|
|
2952
3017
|
dispatchCounts,
|
|
@@ -2956,16 +3021,18 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2956
3021
|
skybox,
|
|
2957
3022
|
skyboxCount,
|
|
2958
3023
|
postProcessParams,
|
|
2959
|
-
fog,
|
|
2960
3024
|
worlds,
|
|
2961
3025
|
recordProfilePhase,
|
|
2962
3026
|
{
|
|
2963
3027
|
owner: gpuDrivenProduction,
|
|
2964
3028
|
scene: persistentRenderScene.compositionGpuDrivenState(),
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
persistentRenderScene.setCompositionGpuDrivenCoverage(key, ownsAll);
|
|
3029
|
+
onSceneDemand: (sceneRowsRequired) => {
|
|
3030
|
+
persistentRenderScene.setCompositionGpuSceneDemand(sceneRowsRequired);
|
|
2968
3031
|
},
|
|
3032
|
+
activeEntityKeys: visibilityProjection.activeEntityKeys,
|
|
3033
|
+
activeEntityRevision: visibilityProjection.activeEntityRevision,
|
|
3034
|
+
telemetryCandidateCount: lodCandidateCountForFrame,
|
|
3035
|
+
...(occlusionProjection === undefined ? {} : { occlusion: occlusionProjection }),
|
|
2969
3036
|
},
|
|
2970
3037
|
renderReadLeases,
|
|
2971
3038
|
featureGraphCandidate,
|
|
@@ -2984,6 +3051,22 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2984
3051
|
timingCapture,
|
|
2985
3052
|
),
|
|
2986
3053
|
);
|
|
3054
|
+
if (submitted && lastLodOcclusionInspection !== undefined) {
|
|
3055
|
+
const query = ensureOcclusionRuntime().inspect();
|
|
3056
|
+
lastLodOcclusionInspection = {
|
|
3057
|
+
...lastLodOcclusionInspection,
|
|
3058
|
+
pagePressure: {
|
|
3059
|
+
used:
|
|
3060
|
+
query.pageCount * query.pageIndexLimit -
|
|
3061
|
+
query.availablePages * query.pageIndexLimit,
|
|
3062
|
+
capacity: query.pageCount * query.pageIndexLimit,
|
|
3063
|
+
},
|
|
3064
|
+
fallback: occlusionFallback,
|
|
3065
|
+
degradation: ensureOcclusionRuntime().prepareUnavailable
|
|
3066
|
+
? { active: true, reason: 'query-unavailable' as const }
|
|
3067
|
+
: { active: false },
|
|
3068
|
+
};
|
|
3069
|
+
}
|
|
2987
3070
|
const directionalQuality = lights.directionalShadowQuality;
|
|
2988
3071
|
const directionalShadowReady =
|
|
2989
3072
|
directionalQuality === undefined ||
|
|
@@ -3007,6 +3090,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3007
3090
|
};
|
|
3008
3091
|
}
|
|
3009
3092
|
if (submitted) {
|
|
3093
|
+
if (occlusionProjection === undefined) {
|
|
3094
|
+
runProfiledRenderPhase(profileSession, 'record/occlusion-global-advance', () =>
|
|
3095
|
+
ensureOcclusionRuntime().advanceSuccessfulSubmit(frameState.frameNumber),
|
|
3096
|
+
);
|
|
3097
|
+
}
|
|
3010
3098
|
const temporalCommit = persistentRenderScene.commitTemporalFrame();
|
|
3011
3099
|
if (!temporalCommit.ok) {
|
|
3012
3100
|
internals.errorRegistry.fire(temporalCommit.error);
|
|
@@ -3085,6 +3173,21 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3085
3173
|
const currentFrameId = frameState.frameNumber - 1;
|
|
3086
3174
|
return observeCurrentFrame(options, frameState.currentFrameObservationSource, currentFrameId);
|
|
3087
3175
|
},
|
|
3176
|
+
async observeLodOcclusion(): Promise<void> {
|
|
3177
|
+
await ensureOcclusionRuntime().waitForCompletions();
|
|
3178
|
+
applyLodSelectionTelemetry(await gpuDrivenProduction.readLodSelection());
|
|
3179
|
+
if (lastLodOcclusionInspection !== undefined) {
|
|
3180
|
+
const queryLatency = ensureOcclusionRuntime().inspectQueryLatency();
|
|
3181
|
+
lastLodOcclusionInspection = inspectLodOcclusion({
|
|
3182
|
+
...lastLodOcclusionInspection,
|
|
3183
|
+
queryLatencyUs: {
|
|
3184
|
+
median: queryLatency.median,
|
|
3185
|
+
p95: queryLatency.p95,
|
|
3186
|
+
last: queryLatency.last,
|
|
3187
|
+
},
|
|
3188
|
+
});
|
|
3189
|
+
}
|
|
3190
|
+
},
|
|
3088
3191
|
getCurrentGraphTarget(name: string) {
|
|
3089
3192
|
const graph = frameState.perFrameGraph;
|
|
3090
3193
|
if (graph === undefined || graph === null) return undefined;
|
|
@@ -3236,6 +3339,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3236
3339
|
(internals as TimingSessionHost).gpuPassTimingBeginReason = undefined;
|
|
3237
3340
|
persistentRenderScene.dispose();
|
|
3238
3341
|
gpuDrivenProduction.dispose();
|
|
3342
|
+
occlusionRuntime?.dispose();
|
|
3239
3343
|
disposeFeatureGpuWork();
|
|
3240
3344
|
reflectionProbeOwner.dispose();
|
|
3241
3345
|
// Bloom generations are renderer-owned rather than graph-owned. Drain
|
|
@@ -3304,6 +3408,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3304
3408
|
// dispose error strategy).
|
|
3305
3409
|
disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
|
|
3306
3410
|
disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
|
|
3411
|
+
if (frameState.probeBlendRecordBuffer !== undefined) {
|
|
3412
|
+
if (!frameState.probeBlendRecordBuffer.isDestroyed) {
|
|
3413
|
+
const result = frameState.probeBlendRecordBuffer.destroy();
|
|
3414
|
+
if (!result.ok) internals.errorRegistry.fire(result.error);
|
|
3415
|
+
}
|
|
3416
|
+
delete frameState.probeBlendRecordBuffer;
|
|
3417
|
+
}
|
|
3418
|
+
frameState.probeBlendRecordBufferCapacity = 0;
|
|
3419
|
+
frameState.probeBlendBuffers.clear();
|
|
3307
3420
|
if (frameState.morphBuffers !== undefined) {
|
|
3308
3421
|
for (const entry of frameState.morphBuffers.values()) {
|
|
3309
3422
|
if (!entry.buffer.isDestroyed) {
|
|
@@ -3324,6 +3437,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3324
3437
|
}
|
|
3325
3438
|
},
|
|
3326
3439
|
resetForRecover(): void {
|
|
3440
|
+
ssrFormatReceipt = undefined;
|
|
3441
|
+
ssrFormatProbeGeneration = -1;
|
|
3442
|
+
ssrFormatProbe = undefined;
|
|
3443
|
+
ssrFallbackGeneration = undefined;
|
|
3327
3444
|
transmissionAdmission.markDeviceLost();
|
|
3328
3445
|
lastTransmissionKey = '';
|
|
3329
3446
|
transmissionCapabilityGeneration = -1;
|
|
@@ -3341,6 +3458,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3341
3458
|
persistentRenderScene.resetGpuForRecover();
|
|
3342
3459
|
gpuDrivenProduction.dispose();
|
|
3343
3460
|
gpuDrivenProduction = new GpuDrivenProduction(internals.device, gpuDrivenShaderFactory);
|
|
3461
|
+
occlusionRuntime?.dispose();
|
|
3462
|
+
// The replacement device is installed by the host after this hook. Do
|
|
3463
|
+
// not recreate device-bound query resources against the lost device;
|
|
3464
|
+
// the first post-recovery frame lazily binds the current device.
|
|
3465
|
+
occlusionRuntime = undefined;
|
|
3344
3466
|
disposeFeatureGpuWork();
|
|
3345
3467
|
reflectionProbeOwner.dispose();
|
|
3346
3468
|
// A recovered device cannot retain handles from the lost device. The
|
|
@@ -3383,6 +3505,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3383
3505
|
frameState.directionalShadowCacheRecorded = false;
|
|
3384
3506
|
frameState.currentFrameObservationSource = undefined;
|
|
3385
3507
|
frameState.lastSuccessfulCameraAntialias = undefined;
|
|
3508
|
+
frameState.temporalFrameTransaction.reset('device-recovery');
|
|
3509
|
+
frameState.temporalFrame = undefined;
|
|
3510
|
+
frameState.temporalFrameInput = undefined;
|
|
3511
|
+
frameState.lastSuccessfulTemporalView = undefined;
|
|
3512
|
+
frameState.successfulTemporalFrameIndex = 0;
|
|
3513
|
+
frameState.pendingTemporalCommit = { kind: 'none' };
|
|
3386
3514
|
frameState.currentDirectionalShadowView = null;
|
|
3387
3515
|
frameState.currentSpotShadowView = null;
|
|
3388
3516
|
if (frameState.temporalGpuState !== undefined) {
|
|
@@ -3403,6 +3531,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3403
3531
|
}
|
|
3404
3532
|
frameState.retiredCompiledFrameGraphs.clear();
|
|
3405
3533
|
frameState.instanceBuffers.clear();
|
|
3534
|
+
delete frameState.probeBlendRecordBuffer;
|
|
3535
|
+
frameState.probeBlendRecordBufferCapacity = 0;
|
|
3536
|
+
frameState.probeBlendBuffers.clear();
|
|
3406
3537
|
frameState.morphBuffers?.clear();
|
|
3407
3538
|
frameState.transientInstanceBuffers = [];
|
|
3408
3539
|
frameState.pointShadowAtlas = null;
|
|
@@ -3470,6 +3601,71 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3470
3601
|
};
|
|
3471
3602
|
}
|
|
3472
3603
|
|
|
3604
|
+
function buildOcclusionProxyVertices(
|
|
3605
|
+
aabb: ArrayLike<number>,
|
|
3606
|
+
world: ArrayLike<number>,
|
|
3607
|
+
camera: import('./render-contract').CameraSnapshot,
|
|
3608
|
+
): Float32Array {
|
|
3609
|
+
const view = computeViewMatrix(camera);
|
|
3610
|
+
const projection = computeProjectionMatrix(camera);
|
|
3611
|
+
const viewProjection = mat4.create();
|
|
3612
|
+
mat4.multiply(viewProjection, projection, view);
|
|
3613
|
+
const corners = [
|
|
3614
|
+
[aabb[0] ?? 0, aabb[1] ?? 0, aabb[2] ?? 0],
|
|
3615
|
+
[aabb[3] ?? 0, aabb[1] ?? 0, aabb[2] ?? 0],
|
|
3616
|
+
[aabb[3] ?? 0, aabb[4] ?? 0, aabb[2] ?? 0],
|
|
3617
|
+
[aabb[0] ?? 0, aabb[4] ?? 0, aabb[2] ?? 0],
|
|
3618
|
+
[aabb[0] ?? 0, aabb[1] ?? 0, aabb[5] ?? 0],
|
|
3619
|
+
[aabb[3] ?? 0, aabb[1] ?? 0, aabb[5] ?? 0],
|
|
3620
|
+
[aabb[3] ?? 0, aabb[4] ?? 0, aabb[5] ?? 0],
|
|
3621
|
+
[aabb[0] ?? 0, aabb[4] ?? 0, aabb[5] ?? 0],
|
|
3622
|
+
];
|
|
3623
|
+
const triangles = [
|
|
3624
|
+
0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 4, 5, 0, 5, 1, 3, 2, 6, 3, 6, 7, 0, 3, 7, 0, 7, 4, 1, 5,
|
|
3625
|
+
6, 1, 6, 2,
|
|
3626
|
+
];
|
|
3627
|
+
const output = new Float32Array(triangles.length * 4);
|
|
3628
|
+
for (let index = 0; index < triangles.length; index += 1) {
|
|
3629
|
+
const corner = corners[triangles[index] ?? 0] ?? [0, 0, 0];
|
|
3630
|
+
const cx = corner[0] ?? 0;
|
|
3631
|
+
const cy = corner[1] ?? 0;
|
|
3632
|
+
const cz = corner[2] ?? 0;
|
|
3633
|
+
const x = (world[0] ?? 0) * cx + (world[4] ?? 0) * cy + (world[8] ?? 0) * cz + (world[12] ?? 0);
|
|
3634
|
+
const y = (world[1] ?? 0) * cx + (world[5] ?? 0) * cy + (world[9] ?? 0) * cz + (world[13] ?? 0);
|
|
3635
|
+
const z =
|
|
3636
|
+
(world[2] ?? 0) * cx + (world[6] ?? 0) * cy + (world[10] ?? 0) * cz + (world[14] ?? 0);
|
|
3637
|
+
const w =
|
|
3638
|
+
(world[3] ?? 0) * cx + (world[7] ?? 0) * cy + (world[11] ?? 0) * cz + (world[15] ?? 1);
|
|
3639
|
+
const base = index * 4;
|
|
3640
|
+
output[base] =
|
|
3641
|
+
(viewProjection[0] ?? 0) * x +
|
|
3642
|
+
(viewProjection[4] ?? 0) * y +
|
|
3643
|
+
(viewProjection[8] ?? 0) * z +
|
|
3644
|
+
(viewProjection[12] ?? 0) * w;
|
|
3645
|
+
output[base + 1] =
|
|
3646
|
+
(viewProjection[1] ?? 0) * x +
|
|
3647
|
+
(viewProjection[5] ?? 0) * y +
|
|
3648
|
+
(viewProjection[9] ?? 0) * z +
|
|
3649
|
+
(viewProjection[13] ?? 0) * w;
|
|
3650
|
+
output[base + 2] =
|
|
3651
|
+
(viewProjection[2] ?? 0) * x +
|
|
3652
|
+
(viewProjection[6] ?? 0) * y +
|
|
3653
|
+
(viewProjection[10] ?? 0) * z +
|
|
3654
|
+
(viewProjection[14] ?? 0) * w;
|
|
3655
|
+
output[base + 3] =
|
|
3656
|
+
(viewProjection[3] ?? 0) * x +
|
|
3657
|
+
(viewProjection[7] ?? 0) * y +
|
|
3658
|
+
(viewProjection[11] ?? 0) * z +
|
|
3659
|
+
(viewProjection[15] ?? 0) * w;
|
|
3660
|
+
}
|
|
3661
|
+
return output;
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
/** Resolve the build identity used by every inspection submit. */
|
|
3665
|
+
export function resolveInspectionBuild(internals: Pick<RenderSystemInternals, 'build'>): string {
|
|
3666
|
+
return internals.build ?? 'render-system';
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3473
3669
|
// @forgeax/engine-render - built-in record context extension.
|
|
3474
3670
|
//
|
|
3475
3671
|
// The public RenderPipelineContext is canonical in render-contract.ts. This
|