@forgeax/engine-render 0.1.20 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +365 -29
- package/dist/assembly/backend-contract.d.ts +13 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -1
- package/dist/assembly/device-feature-admission.d.ts +18 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -0
- package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
- package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +30 -19
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +19 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/render-target-host.d.ts +46 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -0
- package/dist/assets/render-asset-port.d.ts.map +1 -1
- package/dist/authoring.mjs +4 -1
- package/dist/authoring.mjs.map +1 -1
- package/dist/capture/cube-views.d.ts +21 -0
- package/dist/capture/cube-views.d.ts.map +1 -0
- package/dist/capture/scheduler.d.ts +54 -0
- package/dist/capture/scheduler.d.ts.map +1 -0
- package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
- package/dist/chunk-4L3P6DT5.mjs.map +1 -0
- package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
- package/dist/chunk-4NPUASZR.mjs.map +1 -0
- package/dist/chunk-DIYW3M5D.mjs +5085 -0
- package/dist/chunk-DIYW3M5D.mjs.map +1 -0
- package/dist/chunk-FBC4R44J.mjs +1003 -0
- package/dist/chunk-FBC4R44J.mjs.map +1 -0
- package/dist/{chunk-CDTWTHZR.mjs → chunk-FF7K3FSO.mjs} +474 -168
- package/dist/chunk-FF7K3FSO.mjs.map +1 -0
- package/dist/chunk-HZUPSYW7.mjs +1104 -0
- package/dist/chunk-HZUPSYW7.mjs.map +1 -0
- package/dist/{chunk-TBQJOK2L.mjs → chunk-QODA5UKU.mjs} +321 -269
- package/dist/chunk-QODA5UKU.mjs.map +1 -0
- package/dist/chunk-YALPS44A.mjs +296 -0
- package/dist/chunk-YALPS44A.mjs.map +1 -0
- package/dist/{chunk-4XINDLAY.mjs → chunk-ZUWKAZ2I.mjs} +1963 -2178
- package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
- package/dist/cluster-binner.d.ts +5 -0
- package/dist/cluster-binner.d.ts.map +1 -1
- package/dist/components/camera.d.ts +1 -0
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/cube-camera.d.ts +20 -0
- package/dist/components/cube-camera.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +11 -2
- package/dist/components/directional-light.d.ts.map +1 -1
- package/dist/components/directional-shadow-filter.d.ts +25 -0
- package/dist/components/directional-shadow-filter.d.ts.map +1 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts +4 -0
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/light-probe.d.ts +9 -0
- package/dist/components/light-probe.d.ts.map +1 -0
- package/dist/components/rect-area-light.d.ts +12 -0
- package/dist/components/rect-area-light.d.ts.map +1 -0
- package/dist/components/reflection-probe.d.ts +17 -0
- package/dist/components/reflection-probe.d.ts.map +1 -0
- package/dist/components/scene-instance.d.ts +4 -0
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/spot-light.d.ts +3 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +14836 -9620
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +8 -1
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/lifecycle-transaction.d.ts +2 -0
- package/dist/device/lifecycle-transaction.d.ts.map +1 -0
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +5 -0
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/gpu-driven.d.ts +26 -0
- package/dist/errors/gpu-driven.d.ts.map +1 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +9 -1
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +166 -93
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/directional-shadow-projection.d.ts +23 -0
- package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
- package/dist/extract/environment.d.ts +14 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +50 -0
- package/dist/extract/gpu-driven.d.ts.map +1 -0
- package/dist/extract/visibility.d.ts.map +1 -1
- package/dist/features/fullscreen.d.ts.map +1 -1
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +10 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +13 -2
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts +34 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +63 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +97 -2
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +64 -1
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +14 -0
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +8 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +0 -1
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +21 -19
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +4 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/kernel-cache.d.ts +36 -0
- package/dist/ibl/kernel-cache.d.ts.map +1 -0
- package/dist/index.d.ts +39 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +91 -6
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +214 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +50 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -0
- package/dist/internal.d.ts +9 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +23 -2
- package/dist/internal.mjs.map +1 -1
- package/dist/light-buffer-layout.d.ts +58 -140
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +54 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +38 -14
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts +2 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
- package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
- package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
- package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts +4 -13
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +5 -26
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -0
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +40 -7
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/point-shadow-inspection.d.ts +21 -0
- package/dist/point-shadow-inspection.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
- package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/resources.d.ts +72 -0
- package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/state.d.ts +24 -0
- package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +33 -36
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +78 -24
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts +5 -0
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +16 -6
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
- package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
- package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/index.d.ts +8 -0
- package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
- package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/session.d.ts +43 -0
- package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
- package/dist/record/gpu-timing.d.ts +20 -3
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/helpers.d.ts +0 -20
- package/dist/record/helpers.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +6 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +25 -2
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +2 -0
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +98 -6
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +119 -4
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +7 -5
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/reflection/filter.d.ts +22 -0
- package/dist/reflection/filter.d.ts.map +1 -0
- package/dist/reflection/gpu-table.d.ts +25 -0
- package/dist/reflection/gpu-table.d.ts.map +1 -0
- package/dist/reflection/inspection.d.ts +10 -0
- package/dist/reflection/inspection.d.ts.map +1 -0
- package/dist/reflection/projection.d.ts +140 -0
- package/dist/reflection/projection.d.ts.map +1 -0
- package/dist/reflection/record-owner.d.ts +60 -0
- package/dist/reflection/record-owner.d.ts.map +1 -0
- package/dist/render-contract.d.ts +62 -14
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +49 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +160 -31
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +28 -2
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer.d.ts +43 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/scene/probe-blend-record.d.ts +22 -0
- package/dist/scene/probe-blend-record.d.ts.map +1 -0
- package/dist/scene/probe-blend.d.ts +142 -0
- package/dist/scene/probe-blend.d.ts.map +1 -0
- package/dist/scene/render-scene-types.d.ts +0 -6
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +117 -9
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/visibility/bounds.d.ts +20 -0
- package/dist/scene/visibility/bounds.d.ts.map +1 -0
- package/dist/scene/visibility/budget.d.ts +16 -0
- package/dist/scene/visibility/budget.d.ts.map +1 -0
- package/dist/scene/visibility/facet.d.ts +73 -0
- package/dist/scene/visibility/facet.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-lod.d.ts +46 -0
- package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
- package/dist/scene/visibility/inspection.d.ts +132 -0
- package/dist/scene/visibility/inspection.d.ts.map +1 -0
- package/dist/scene/visibility/lod-selector.d.ts +25 -0
- package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
- package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
- package/dist/scene/visibility/types.d.ts +34 -0
- package/dist/scene/visibility/types.d.ts.map +1 -0
- package/dist/shadow-atlas.d.ts +3 -3
- package/dist/shadow-atlas.d.ts.map +1 -1
- package/dist/ssao-buffers.d.ts +3 -0
- package/dist/ssao-buffers.d.ts.map +1 -1
- package/dist/ssr/admission.d.ts +122 -0
- package/dist/ssr/admission.d.ts.map +1 -0
- package/dist/ssr/composition.d.ts +32 -0
- package/dist/ssr/composition.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +39 -0
- package/dist/ssr/errors.d.ts.map +1 -0
- package/dist/ssr/identity.d.ts +13 -0
- package/dist/ssr/identity.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +1 -1
- package/dist/targets/contracts.d.ts +66 -0
- package/dist/targets/contracts.d.ts.map +1 -0
- package/dist/targets/graph-projection.d.ts +29 -0
- package/dist/targets/graph-projection.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +34 -0
- package/dist/targets/material-source.d.ts.map +1 -0
- package/dist/targets/owner.d.ts +32 -0
- package/dist/targets/owner.d.ts.map +1 -0
- package/dist/targets/physical.d.ts +18 -0
- package/dist/targets/physical.d.ts.map +1 -0
- package/dist/targets/readback.d.ts +39 -0
- package/dist/targets/readback.d.ts.map +1 -0
- package/dist/temporal/frame-coordinator.d.ts +13 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +2 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -5
- package/dist/temporal/view.d.ts +7 -0
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/tilemap-chunk-extract-system.d.ts +1 -1
- package/dist/typed-render-graph-primitives.d.ts +5 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +1 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +9 -5
- package/dist/volume/passes.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
- package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
- package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
- package/src/__tests__/cluster-binner.unit.test.ts +35 -0
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +12 -2
- package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
- package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
- package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
- package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
- package/src/__tests__/cube-camera.integration.test.ts +42 -0
- package/src/__tests__/detached-inspection.integration.test.ts +6 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +85 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +62 -6
- package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
- package/src/__tests__/environment-generation.integration.test.ts +16 -0
- package/src/__tests__/evidence-identity.ts +13 -0
- package/src/__tests__/evidence-identity.unit.test.ts +24 -0
- package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
- package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
- package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
- package/src/__tests__/factory-contract.integration.test.ts +288 -29
- package/src/__tests__/fog-component.unit.test.ts +67 -0
- package/src/__tests__/fog.integration.test.ts +86 -0
- package/src/__tests__/frame-observation.contract.test.ts +15 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -8
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +18 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +579 -17
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +101 -6
- package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
- package/src/__tests__/gpu-lod-evidence.ts +188 -0
- package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
- package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
- package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
- package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
- package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
- package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
- package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
- package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
- package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
- package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
- package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
- package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
- package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
- package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
- package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
- package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
- package/src/__tests__/gpu-scene.unit.test.ts +49 -3
- package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
- package/src/__tests__/instances-culling.integration.test.ts +94 -0
- package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
- package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
- package/src/__tests__/light-inspection.unit.test.ts +55 -0
- package/src/__tests__/light-resource-error.unit.test.ts +43 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
- package/src/__tests__/lod-bounds.unit.test.ts +42 -0
- package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
- package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
- package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
- package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
- package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
- package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
- package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
- package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
- package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
- package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
- package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
- package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
- package/src/__tests__/lod-selector.profile.test.ts +35 -0
- package/src/__tests__/lod-selector.unit.test.ts +68 -0
- package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
- package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
- package/src/__tests__/ltc-resources.unit.test.ts +32 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
- package/src/__tests__/material-observation.integration.test.ts +101 -0
- package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
- package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
- package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
- package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
- package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
- package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
- package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
- package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
- package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
- package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
- package/src/__tests__/occlusion-query.evidence.ts +178 -0
- package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
- package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
- package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
- package/src/__tests__/probe-bindings.unit.test.ts +200 -0
- package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
- package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
- package/src/__tests__/probe-capacity.integration.test.ts +97 -0
- package/src/__tests__/probe-recovery.integration.test.ts +104 -0
- package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
- package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
- package/src/__tests__/profiler-phase-token.test.ts +1 -0
- package/src/__tests__/public-surface.test-d.ts +10 -0
- package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
- package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
- package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
- package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
- package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
- package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
- package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
- package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
- package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
- package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
- package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
- package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
- package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +19 -3
- package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
- package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
- package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
- package/src/__tests__/render-target-admission.unit.test.ts +80 -0
- package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
- package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
- package/src/__tests__/render-target-material-source.test-d.ts +30 -0
- package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
- package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
- package/src/__tests__/render-target-owner.unit.test.ts +122 -0
- package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
- package/src/__tests__/render-target-public-schema.json +107 -0
- package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
- package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
- package/src/__tests__/render-target-readback.integration.test.ts +90 -0
- package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +124 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
- package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
- package/src/__tests__/skin-motion-regression.unit.test.ts +154 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +13 -4
- package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
- package/src/__tests__/ssr-composition.unit.test.ts +46 -0
- package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
- package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
- package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
- package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +299 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +15 -5
- package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
- package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +441 -0
- package/src/__tests__/standard-lighting.integration.test.ts +67 -0
- package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
- package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
- package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
- package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
- package/src/__tests__/temporal-view.unit.test.ts +3 -3
- package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
- package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
- package/src/__tests__/visibility-budget.integration.test.ts +140 -0
- package/src/__tests__/visibility-budget.unit.test.ts +70 -0
- package/src/__tests__/visibility-extract.integration.test.ts +11 -6
- package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
- package/src/assembly/backend-contract.ts +13 -0
- package/src/assembly/device-feature-admission.ts +80 -0
- package/src/assembly/directional-shadow-inspection.ts +122 -0
- package/src/assembly/factory.ts +1411 -366
- package/src/assembly/host-contract.ts +35 -0
- package/src/assembly/render-target-host.ts +847 -0
- package/src/assets/asset-decoders.ts +1 -1
- package/src/assets/render-asset-port.ts +7 -1
- package/src/capture/cube-views.ts +69 -0
- package/src/capture/scheduler.ts +265 -0
- package/src/cluster-binner.ts +31 -0
- package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
- package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
- package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
- package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
- package/src/components/camera.ts +4 -0
- package/src/components/cube-camera.ts +52 -0
- package/src/components/directional-light.ts +22 -2
- package/src/components/directional-shadow-filter.ts +57 -0
- package/src/components/index.ts +5 -0
- package/src/components/light-helpers.ts +133 -8
- package/src/components/light-probe.ts +13 -0
- package/src/components/rect-area-light.ts +15 -0
- package/src/components/reflection-probe.ts +49 -0
- package/src/components/scene-instance.ts +6 -0
- package/src/components/spot-light.ts +3 -0
- package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
- package/src/device/gpu-residency.ts +140 -9
- package/src/device/lifecycle-transaction.ts +12 -0
- package/src/environment/frame.ts +6 -24
- package/src/environment/lifecycle.ts +20 -1
- package/src/errors/gpu-driven.ts +92 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/recover.ts +32 -0
- package/src/errors/render.ts +341 -143
- package/src/extract/camera.ts +3 -3
- package/src/extract/directional-shadow-projection.ts +61 -0
- package/src/extract/environment.ts +57 -0
- package/src/extract/gpu-driven.ts +108 -0
- package/src/extract/visibility.ts +2 -1
- package/src/features/fullscreen.ts +3 -0
- package/src/features/targets.ts +4 -1
- package/src/features/types.ts +10 -0
- package/src/fullscreen-post-process-pass.ts +43 -2
- package/src/gpu-driven/batch-topology.ts +207 -28
- package/src/gpu-driven/prepared-draw.ts +208 -0
- package/src/gpu-driven/production-raster.ts +953 -43
- package/src/gpu-driven/view-gpu.ts +411 -29
- package/src/gpu-scene-schema.ts +13 -0
- package/src/gpu-scene.ts +140 -64
- package/src/gpu-usage.ts +0 -1
- package/src/hdrp-buffers.ts +169 -63
- package/src/ibl/IblPipelineCache.ts +11 -0
- package/src/ibl/kernel-cache.ts +88 -0
- package/src/index.ts +222 -0
- package/src/inspection-types.ts +276 -1
- package/src/instances-derived-bounds.ts +171 -0
- package/src/internal.ts +38 -0
- package/src/light-buffer-layout.ts +155 -310
- package/src/materials.ts +16 -2
- package/src/mesh-material-bindings.ts +132 -0
- package/src/pbr-pipeline.ts +255 -60
- package/src/pipeline/standard-forward-lane.ts +93 -16
- package/src/pipeline/standard-lighting/errors.ts +6 -0
- package/src/pipeline/standard-lighting/graph.ts +158 -0
- package/src/pipeline/standard-lighting/inspection.ts +88 -0
- package/src/pipeline/standard-lighting/layout.ts +31 -0
- package/src/pipeline/standard-lighting/light-frame.ts +35 -0
- package/src/pipeline/standard-lighting/membership.ts +43 -0
- package/src/pipeline/standard-lighting/prepare.ts +37 -0
- package/src/pipeline/standard-lighting/topology.ts +120 -0
- package/src/pipeline/standard-lighting/transport.ts +78 -0
- package/src/pipeline/standard-pipeline.ts +124 -193
- package/src/pipeline/standard-post.ts +26 -7
- package/src/pipeline/standard-profile.ts +17 -47
- package/src/pipeline-builder.ts +5 -2
- package/src/pipeline-spec.ts +95 -21
- package/src/plugin.ts +4 -0
- package/src/point-shadow-inspection.ts +55 -0
- package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
- package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
- package/src/prepare/extended-lighting/resources.ts +389 -0
- package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
- package/src/prepare/extended-lighting/state.ts +92 -0
- package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +64 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
- package/src/record/frame-lighting.ts +591 -452
- package/src/record/frame-snapshot.ts +92 -39
- package/src/record/frame-targets.ts +17 -0
- package/src/record/frame.ts +662 -158
- package/src/record/gpu-pass-timing/contract.ts +180 -0
- package/src/record/gpu-pass-timing/errors.ts +34 -0
- package/src/record/gpu-pass-timing/index.ts +37 -0
- package/src/record/gpu-pass-timing/parser.ts +97 -0
- package/src/record/gpu-pass-timing/session.ts +938 -0
- package/src/record/gpu-timing.ts +112 -39
- package/src/record/helpers.ts +0 -58
- package/src/record/main-pass-geometry.ts +324 -119
- package/src/record/main-pass-material.ts +103 -10
- package/src/record/main-pass-sprite-draws.ts +99 -103
- package/src/record/main-pass.ts +71 -15
- package/src/record/mesh-ssbo.ts +47 -36
- package/src/record/render-context.ts +136 -3
- package/src/record/shadow-pass.ts +83 -68
- package/src/record/skybox-post-pass.ts +20 -4
- package/src/record/typed-frame-graph.ts +1034 -43
- package/src/record/view-ubo.ts +72 -47
- package/src/reflection/filter.ts +81 -0
- package/src/reflection/gpu-table.ts +44 -0
- package/src/reflection/inspection.ts +67 -0
- package/src/reflection/projection.ts +476 -0
- package/src/reflection/record-owner.ts +1283 -0
- package/src/render-contract.ts +93 -10
- package/src/render-graph-primitives.ts +9 -4
- package/src/render-pipeline.ts +122 -2
- package/src/render-system-extract.ts +1041 -225
- package/src/render-system.ts +1310 -85
- package/src/renderer.ts +222 -0
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
- package/src/scene/probe-blend-record.ts +54 -0
- package/src/scene/probe-blend.ts +813 -0
- package/src/scene/render-scene-types.ts +0 -7
- package/src/scene/render-scene.ts +942 -171
- package/src/scene/visibility/bounds.ts +57 -0
- package/src/scene/visibility/budget.ts +40 -0
- package/src/scene/visibility/facet.ts +400 -0
- package/src/scene/visibility/gpu-frame-timing.ts +101 -0
- package/src/scene/visibility/gpu-lod.ts +119 -0
- package/src/scene/visibility/inspection.ts +313 -0
- package/src/scene/visibility/lod-selector.ts +84 -0
- package/src/scene/visibility/occlusion-confidence.ts +478 -0
- package/src/scene/visibility/occlusion-pass.ts +165 -0
- package/src/scene/visibility/occlusion-query-pool.ts +178 -0
- package/src/scene/visibility/occlusion-runtime.ts +672 -0
- package/src/scene/visibility/types.ts +50 -0
- package/src/shadow-atlas.ts +5 -5
- package/src/ssao-buffers.ts +9 -2
- package/src/ssr/admission.ts +451 -0
- package/src/ssr/composition.ts +73 -0
- package/src/ssr/errors.ts +76 -0
- package/src/ssr/identity.ts +12 -0
- package/src/systems/skin-palette-allocator.ts +1 -1
- package/src/targets/contracts.ts +182 -0
- package/src/targets/graph-projection.ts +169 -0
- package/src/targets/material-source.ts +143 -0
- package/src/targets/owner.ts +214 -0
- package/src/targets/physical.ts +18 -0
- package/src/targets/readback.ts +153 -0
- package/src/temporal/frame-coordinator.ts +26 -0
- package/src/temporal/frame.ts +14 -1
- package/src/temporal/view.ts +11 -0
- package/src/tilemap-chunk-extract-system.ts +4 -4
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
- package/src/typed-render-graph-primitives.ts +53 -3
- package/src/typed-shadow-passes.ts +46 -40
- package/src/volume/passes.ts +61 -58
- package/dist/.tsbuildinfo +0 -1
- package/dist/chunk-4XINDLAY.mjs.map +0 -1
- package/dist/chunk-CDTWTHZR.mjs.map +0 -1
- package/dist/chunk-M2MP4ZOU.mjs +0 -73
- package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
- package/dist/chunk-PCHBPYML.mjs.map +0 -1
- package/dist/chunk-TBQJOK2L.mjs.map +0 -1
- package/dist/chunk-VFPLTKCU.mjs.map +0 -1
package/src/render-system.ts
CHANGED
|
@@ -19,12 +19,14 @@
|
|
|
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
25
|
import type { BindGroupEntry, BindGroupLayout, Buffer, RenderPipeline } from '@forgeax/engine-rhi';
|
|
25
26
|
import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
|
|
26
27
|
import {
|
|
27
28
|
derive,
|
|
29
|
+
deriveTextureLayout,
|
|
28
30
|
type Handle,
|
|
29
31
|
type MaterialAsset,
|
|
30
32
|
type MaterialTextureValue,
|
|
@@ -32,8 +34,14 @@ import {
|
|
|
32
34
|
type RenderPipelineAsset,
|
|
33
35
|
RenderQueue,
|
|
34
36
|
type SamplerAsset,
|
|
37
|
+
type TextureAsset,
|
|
35
38
|
toShared,
|
|
36
39
|
} from '@forgeax/engine-types';
|
|
40
|
+
import {
|
|
41
|
+
type DirectionalShadowInspectionSource,
|
|
42
|
+
directionalShadowProfileFromQuality,
|
|
43
|
+
projectDirectionalShadowInspectionSource,
|
|
44
|
+
} from './assembly/directional-shadow-inspection';
|
|
37
45
|
import { createClusterBinScratch } from './cluster-binner';
|
|
38
46
|
import { EnvironmentLifecycle } from './environment/lifecycle';
|
|
39
47
|
import {
|
|
@@ -57,12 +65,22 @@ import {
|
|
|
57
65
|
DEPTH_MIN_PARAMS_BYTE_SIZE,
|
|
58
66
|
entryHasDepthRead,
|
|
59
67
|
type PostProcessShaderEntry,
|
|
68
|
+
postProcessShaderEntrySignature,
|
|
69
|
+
postProcessShaderModuleLabel,
|
|
70
|
+
postProcessShaderPipelineLabel,
|
|
60
71
|
} from './fullscreen-post-process-pass';
|
|
61
72
|
import { GpuDrivenProduction } from './gpu-driven/production-raster';
|
|
62
73
|
import type {
|
|
63
74
|
BloomInspection,
|
|
75
|
+
DirectionalShadowInspection,
|
|
76
|
+
GpuDrivenProductionInspection,
|
|
77
|
+
LodOcclusionInspection,
|
|
78
|
+
LodOcclusionInspectionRow,
|
|
79
|
+
LodOcclusionWorldInspection,
|
|
64
80
|
MotionBlurInspection,
|
|
81
|
+
ReflectionProbeInspection,
|
|
65
82
|
RenderSceneInspection,
|
|
83
|
+
SsrDependenciesInspection,
|
|
66
84
|
TemporalTargetInspection,
|
|
67
85
|
TransmissionInspection,
|
|
68
86
|
} from './inspection-types';
|
|
@@ -80,11 +98,18 @@ import {
|
|
|
80
98
|
import { createPointsLinesLaneAdapter, type PointsLinesBackend } from './points-lines/record';
|
|
81
99
|
import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
|
|
82
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';
|
|
83
107
|
import { inspectVolumetricFog } from './volume/inspection';
|
|
84
108
|
|
|
85
109
|
export type { RenderSceneInspection } from './inspection-types';
|
|
86
110
|
|
|
87
111
|
import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
|
|
112
|
+
import { createCubeCaptureScheduler } from './capture/scheduler';
|
|
88
113
|
import type { EnvironmentInspection } from './environment/inspection';
|
|
89
114
|
import {
|
|
90
115
|
GPU_BUFFER_USAGE_COPY_DST,
|
|
@@ -92,22 +117,27 @@ import {
|
|
|
92
117
|
GPU_BUFFER_USAGE_UNIFORM,
|
|
93
118
|
GPU_BUFFER_USAGE_VERTEX,
|
|
94
119
|
} from './gpu-usage';
|
|
120
|
+
import { resetHdrpBuffers } from './hdrp-buffers';
|
|
95
121
|
import { assembleMaterialWithSkylightEntries } from './ibl/skylight-bind-group';
|
|
96
122
|
import { disposeInstanceBuffers, disposeTransientInstanceBuffers } from './instance-buffer-cache';
|
|
97
|
-
import type { MeshMaterialBindingObservation } from './mesh-material-bindings';
|
|
98
|
-
import { validateClusterGrid } from './pipeline/standard-pipeline';
|
|
99
123
|
import {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
124
|
+
type MeshMaterialBindingObservation,
|
|
125
|
+
type MeshMaterialBindingPreparationFailure,
|
|
126
|
+
type MeshMaterialBindingResidency,
|
|
127
|
+
projectMeshMaterialBindingObservation,
|
|
128
|
+
} from './mesh-material-bindings';
|
|
129
|
+
import type { StandardLightingInspection } from './pipeline/standard-lighting/inspection';
|
|
130
|
+
import { validateClusterGrid } from './pipeline/standard-pipeline';
|
|
131
|
+
import { DEFAULT_CLUSTER_GRID } from './pipeline/standard-profile';
|
|
104
132
|
import { PipelineSpecError } from './pipeline-spec';
|
|
133
|
+
import type { PointShadowInspection } from './point-shadow-inspection';
|
|
105
134
|
import { PostProcessError } from './post-process-errors';
|
|
106
135
|
import {
|
|
107
136
|
createPreparedGraphicsResolver,
|
|
108
137
|
type PreparedGraphicsResolver,
|
|
109
138
|
} from './prepare/prepared-graphics-resolver';
|
|
110
139
|
import {
|
|
140
|
+
type CubeCaptureFrameInput,
|
|
111
141
|
type FrameObservation,
|
|
112
142
|
type FrameObservationOptions,
|
|
113
143
|
observeCurrentFrame,
|
|
@@ -120,10 +150,18 @@ import type {
|
|
|
120
150
|
GraphTargetCaptureRequest,
|
|
121
151
|
RenderFrameState,
|
|
122
152
|
} from './record/frame-snapshot';
|
|
123
|
-
import { getTextureIdentity } from './record/frame-snapshot';
|
|
153
|
+
import { getTextureIdentity, worldEntityKey } from './record/frame-snapshot';
|
|
154
|
+
import type { GpuPassTimingReason } from './record/gpu-pass-timing/errors';
|
|
155
|
+
import type {
|
|
156
|
+
GpuPassTimingCapture,
|
|
157
|
+
GpuPassTimingFrameIdentity,
|
|
158
|
+
GpuPassTimingSession,
|
|
159
|
+
} from './record/gpu-pass-timing/session';
|
|
124
160
|
import type { GpuTimingCapture } from './record/gpu-timing';
|
|
161
|
+
import { computeProjectionMatrix, computeViewMatrix } from './record/helpers';
|
|
125
162
|
import { applyParamSnapshotToUbo, residentTextureView } from './record/main-pass-material';
|
|
126
163
|
import type { RenderSystemInternals } from './record/render-context';
|
|
164
|
+
import type { CubeCaptureGraphState } from './record/typed-frame-graph';
|
|
127
165
|
import {
|
|
128
166
|
type RenderFeatureGraphCandidate,
|
|
129
167
|
resetRenderFeatureGraphState,
|
|
@@ -137,10 +175,18 @@ import {
|
|
|
137
175
|
type RenderPhaseSkipReason,
|
|
138
176
|
type RenderRecordPhase,
|
|
139
177
|
} from './render-contract';
|
|
140
|
-
import type {
|
|
178
|
+
import type {
|
|
179
|
+
DispatchEntry,
|
|
180
|
+
ExtractedFrame,
|
|
181
|
+
ExtractedLights,
|
|
182
|
+
MaterialSnapshot,
|
|
183
|
+
RenderableSnapshot,
|
|
184
|
+
} from './render-system-extract';
|
|
141
185
|
import { extractFrames } from './render-system-extract';
|
|
142
186
|
import { PersistentRenderScene } from './scene/render-scene';
|
|
187
|
+
import { inspectLodOcclusion } from './scene/visibility/inspection';
|
|
143
188
|
import { resolveSsaoParameters } from './ssao-config';
|
|
189
|
+
import { projectSsrDependencies, resolveSsrAdmissionGeneration } from './ssr/admission';
|
|
144
190
|
import {
|
|
145
191
|
getTransparentSortConfig,
|
|
146
192
|
TRANSPARENT_SORT_MODE_DISTANCE,
|
|
@@ -148,6 +194,7 @@ import {
|
|
|
148
194
|
TRANSPARENT_SORT_MODE_LAYER_YZ,
|
|
149
195
|
TRANSPARENT_SORT_MODE_LAYER_Z,
|
|
150
196
|
} from './systems/transparent-sort-config';
|
|
197
|
+
import type { RenderTarget } from './targets/contracts';
|
|
151
198
|
import { createTemporalFrameTransaction, type TemporalFrame } from './temporal/frame';
|
|
152
199
|
import { retireTemporalGpuState } from './temporal/gpu';
|
|
153
200
|
import { inspectTemporal, type TemporalInspection } from './temporal/inspection';
|
|
@@ -163,6 +210,136 @@ import {
|
|
|
163
210
|
inspectTransmissionFromAdmission,
|
|
164
211
|
} from './transmission/inspection';
|
|
165
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
|
+
|
|
328
|
+
type TimingSessionHost = RenderSystemInternals & {
|
|
329
|
+
gpuPassTimingSession?: GpuPassTimingSession | undefined;
|
|
330
|
+
gpuPassTimingCapture?: GpuPassTimingCapture | undefined;
|
|
331
|
+
gpuPassTimingFrameIdentity?: GpuPassTimingFrameIdentity | undefined;
|
|
332
|
+
gpuPassTimingBeginReason?: GpuPassTimingReason | undefined;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
/** Attach the Render-owned timing session without widening the public renderer surface. */
|
|
336
|
+
export function attachGpuPassTimingSession(
|
|
337
|
+
internals: object,
|
|
338
|
+
session: GpuPassTimingSession | undefined,
|
|
339
|
+
): void {
|
|
340
|
+
(internals as TimingSessionHost).gpuPassTimingSession = session;
|
|
341
|
+
}
|
|
342
|
+
|
|
166
343
|
export type {
|
|
167
344
|
_InternalRenderPipelineContext,
|
|
168
345
|
_StandardForwardSceneView,
|
|
@@ -232,7 +409,7 @@ function sortTransparentDispatch(
|
|
|
232
409
|
const camPos = camera.position;
|
|
233
410
|
|
|
234
411
|
// Squared camera distance per entry (negated → ascending = back-to-front).
|
|
235
|
-
// D-3: world position = translation column of
|
|
412
|
+
// D-3: world position = translation column of GlobalTransform.world (m[12,13,14]).
|
|
236
413
|
const negDistSq = (entry: DispatchEntry): number => {
|
|
237
414
|
const tx = renderables[entry.renderableIndex]?.transform;
|
|
238
415
|
if (tx === undefined) return 0;
|
|
@@ -338,6 +515,8 @@ export interface RenderSystem {
|
|
|
338
515
|
observeCurrentFrame(
|
|
339
516
|
options: FrameObservationOptions,
|
|
340
517
|
): Promise<Result<FrameObservation, ObservationUnavailableError>>;
|
|
518
|
+
/** Resolve the renderer-owned GPU LOD counters for the last accepted submit. */
|
|
519
|
+
observeLodOcclusion(): Promise<void>;
|
|
341
520
|
/** Internal same-frame graph target access for backend diagnostics. */
|
|
342
521
|
getCurrentGraphTarget(name: string): CurrentGraphTarget | undefined;
|
|
343
522
|
/** Internal test capture copied into the draw's command submission. */
|
|
@@ -354,13 +533,29 @@ export interface RenderSystem {
|
|
|
354
533
|
readonly visibilityStats: { explicitlyHidden: number };
|
|
355
534
|
/** Persistent scene maintenance evidence from the ordinary single-World path. */
|
|
356
535
|
readonly renderScene: RenderSceneInspection;
|
|
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;
|
|
541
|
+
/** True while a progressive CubeCamera candidate still owns unfinished faces. */
|
|
542
|
+
isCubeCapturePending(target: RenderTarget): boolean;
|
|
357
543
|
readonly environment: EnvironmentInspection;
|
|
358
544
|
readonly temporal: TemporalInspection;
|
|
359
545
|
readonly bloom: BloomInspection;
|
|
360
546
|
readonly motionBlurInspection: MotionBlurInspection | undefined;
|
|
361
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;
|
|
362
551
|
/** Last completed transmission candidate facts, detached from GPU handles. */
|
|
363
552
|
readonly transmission: TransmissionInspection | undefined;
|
|
553
|
+
/** Single production projection of Directional author/extract/record facts. */
|
|
554
|
+
readonly directionalShadow: DirectionalShadowInspection;
|
|
555
|
+
/** Last prepared Standard transport facts, detached from GPU handles. */
|
|
556
|
+
readonly standardLightingInspection: StandardLightingInspection | undefined;
|
|
557
|
+
/** Last submitted point-shadow atlas budget facts, detached from GPU handles. */
|
|
558
|
+
readonly pointShadowInspection: PointShadowInspection | undefined;
|
|
364
559
|
/** Retained Points/Lines authoring facts from the single scene projection. */
|
|
365
560
|
readonly pointsLinesSnapshots: readonly PointsLinesRetainedSnapshot[];
|
|
366
561
|
/** Current mesh-slot provenance and active diagnostics from the last frame. */
|
|
@@ -492,6 +687,175 @@ type PointsLinesGpuResources = {
|
|
|
492
687
|
};
|
|
493
688
|
type PointsLinesPreparationAdapter = PointsLinesLanePreparationAdapter<PointsLinesGpuResources>;
|
|
494
689
|
|
|
690
|
+
function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationFailure | undefined {
|
|
691
|
+
if (typeof error !== 'object' || error === null) return undefined;
|
|
692
|
+
const value = error as {
|
|
693
|
+
readonly code?: unknown;
|
|
694
|
+
readonly expected?: unknown;
|
|
695
|
+
readonly hint?: unknown;
|
|
696
|
+
readonly detail?: unknown;
|
|
697
|
+
};
|
|
698
|
+
if (
|
|
699
|
+
typeof value.code !== 'string' ||
|
|
700
|
+
typeof value.expected !== 'string' ||
|
|
701
|
+
typeof value.hint !== 'string'
|
|
702
|
+
) {
|
|
703
|
+
return undefined;
|
|
704
|
+
}
|
|
705
|
+
const detail =
|
|
706
|
+
typeof value.detail === 'object' && value.detail !== null
|
|
707
|
+
? { ...(value.detail as Record<string, unknown>) }
|
|
708
|
+
: undefined;
|
|
709
|
+
return {
|
|
710
|
+
code: value.code,
|
|
711
|
+
expected: value.expected,
|
|
712
|
+
hint: value.hint,
|
|
713
|
+
...(detail === undefined ? {} : { detail }),
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function materialTextureHandles(
|
|
718
|
+
material: MaterialSnapshot,
|
|
719
|
+
): readonly Handle<'TextureAsset', 'shared'>[] {
|
|
720
|
+
const handles = new Map<number, Handle<'TextureAsset', 'shared'>>();
|
|
721
|
+
const add = (handle: Handle<'TextureAsset', 'shared'> | undefined): void => {
|
|
722
|
+
if (handle !== undefined) handles.set(Number(handle), handle);
|
|
723
|
+
};
|
|
724
|
+
for (const handle of material.textureHandles?.values() ?? []) add(handle);
|
|
725
|
+
add(material.baseColorTexture);
|
|
726
|
+
add(material.metallicRoughnessTexture);
|
|
727
|
+
add(material.normalTexture);
|
|
728
|
+
add(material.emissiveTexture);
|
|
729
|
+
add(material.occlusionTexture);
|
|
730
|
+
return [...handles.values()];
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function materialSamplerHandles(
|
|
734
|
+
material: MaterialSnapshot,
|
|
735
|
+
): readonly Handle<'SamplerAsset', 'shared'>[] {
|
|
736
|
+
const handles = new Map<number, Handle<'SamplerAsset', 'shared'>>();
|
|
737
|
+
for (const handle of material.samplerHandles?.values() ?? []) {
|
|
738
|
+
handles.set(Number(handle), handle);
|
|
739
|
+
}
|
|
740
|
+
return [...handles.values()];
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Project the producer-owned material and GPU store state into the one
|
|
745
|
+
* renderer observation surface. This is intentionally recomputed from the
|
|
746
|
+
* current frame's detached material snapshots; no readiness map is retained
|
|
747
|
+
* by RenderSystem. A producer failure with an already resident resource is
|
|
748
|
+
* reported as last-known-good, preserving recovery context without copying
|
|
749
|
+
* the producer's lifecycle state.
|
|
750
|
+
*/
|
|
751
|
+
function observeMaterialResidency(
|
|
752
|
+
world: World | undefined,
|
|
753
|
+
material: MaterialSnapshot,
|
|
754
|
+
internals: RenderSystemInternals,
|
|
755
|
+
): MeshMaterialBindingResidency {
|
|
756
|
+
const textures: Array<{ readonly handle: number; readonly mipLevelCount: number }> = [];
|
|
757
|
+
const samplers: Array<{ readonly handle: number; readonly resident: boolean }> = [];
|
|
758
|
+
let pending = false;
|
|
759
|
+
let resident = false;
|
|
760
|
+
let failure: MeshMaterialBindingPreparationFailure | undefined;
|
|
761
|
+
const keepFailure = (error: unknown): void => {
|
|
762
|
+
if (failure === undefined) failure = preparationFailureFrom(error);
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
if (world === undefined) {
|
|
766
|
+
pending = true;
|
|
767
|
+
} else {
|
|
768
|
+
const materialHandle = material.materialHandle;
|
|
769
|
+
if (materialHandle !== undefined && materialHandle !== 0) {
|
|
770
|
+
const materialResult = resolveAssetHandle<MaterialAsset>(
|
|
771
|
+
world,
|
|
772
|
+
toShared<'MaterialAsset'>(materialHandle),
|
|
773
|
+
);
|
|
774
|
+
if (!materialResult.ok) {
|
|
775
|
+
keepFailure(materialResult.error);
|
|
776
|
+
} else {
|
|
777
|
+
const guid = internals.assets.guidOf(materialResult.value);
|
|
778
|
+
if (guid !== undefined) {
|
|
779
|
+
const readiness = internals.assets.getMaterialReadiness(guid);
|
|
780
|
+
if (readiness?.status === 'Error') keepFailure(readiness.error);
|
|
781
|
+
const load = internals.assets.loadState.get(guid);
|
|
782
|
+
if (load?.status === 'provisional') pending = true;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
for (const handle of materialTextureHandles(material)) {
|
|
788
|
+
const existing = internals.gpuStore.getTextureGpuView(handle, world);
|
|
789
|
+
const pod = resolveAssetHandle<TextureAsset>(world, handle);
|
|
790
|
+
if (!pod.ok) {
|
|
791
|
+
keepFailure(pod.error);
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
let view = existing;
|
|
795
|
+
if (view === undefined) {
|
|
796
|
+
const prepared = internals.gpuStore.ensureResident(handle, pod.value, world);
|
|
797
|
+
if (!prepared.ok) keepFailure(prepared.error);
|
|
798
|
+
view = internals.gpuStore.getTextureGpuView(handle, world);
|
|
799
|
+
}
|
|
800
|
+
if (view === undefined) {
|
|
801
|
+
pending = failure === undefined;
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const textureLayout = deriveTextureLayout({
|
|
805
|
+
shape: pod.value.shape,
|
|
806
|
+
format: pod.value.format,
|
|
807
|
+
mips: pod.value.mips,
|
|
808
|
+
});
|
|
809
|
+
if (!textureLayout.ok) {
|
|
810
|
+
keepFailure(textureLayout.error);
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
resident = true;
|
|
814
|
+
textures.push({
|
|
815
|
+
handle: Number(handle),
|
|
816
|
+
mipLevelCount: Math.max(1, textureLayout.value.levels.length),
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
for (const handle of materialSamplerHandles(material)) {
|
|
821
|
+
const pod = resolveAssetHandle<SamplerAsset>(world, handle);
|
|
822
|
+
if (!pod.ok) {
|
|
823
|
+
keepFailure(pod.error);
|
|
824
|
+
samplers.push({ handle: Number(handle), resident: false });
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
const prepared = internals.gpuStore.ensureSamplerResident(handle, pod.value, world);
|
|
828
|
+
if (!prepared.ok) {
|
|
829
|
+
keepFailure(prepared.error);
|
|
830
|
+
samplers.push({ handle: Number(handle), resident: false });
|
|
831
|
+
} else {
|
|
832
|
+
resident = true;
|
|
833
|
+
samplers.push({ handle: Number(handle), resident: true });
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
// Video views are supplied by the dynamic host store at record time. They
|
|
837
|
+
// are not static TextureAsset residents, so this bounded projection stays
|
|
838
|
+
// pending until a dynamic observation contract exists rather than guessing
|
|
839
|
+
// from the VideoAsset GUID.
|
|
840
|
+
if ((material.videoTextureFields?.size ?? 0) > 0) pending = true;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const readiness =
|
|
844
|
+
failure === undefined
|
|
845
|
+
? pending
|
|
846
|
+
? 'pending'
|
|
847
|
+
: 'ready'
|
|
848
|
+
: resident
|
|
849
|
+
? 'last-known-good'
|
|
850
|
+
: 'failed';
|
|
851
|
+
return {
|
|
852
|
+
readiness,
|
|
853
|
+
samplers,
|
|
854
|
+
textures,
|
|
855
|
+
...(failure === undefined ? {} : { preparationFailure: failure }),
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
495
859
|
/**
|
|
496
860
|
* The Standard renderer's single Points/Lines prepare owner.
|
|
497
861
|
*
|
|
@@ -763,6 +1127,41 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
763
1127
|
let preparedWorlds: readonly World[] = [];
|
|
764
1128
|
let latestCamera: CameraSnapshot | undefined;
|
|
765
1129
|
const pointsLinesOwner = new StandardPointsLinesOwner(internals);
|
|
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
|
+
};
|
|
1163
|
+
const cubeCaptureScheduler = createCubeCaptureScheduler({ maxFacesPerFrame: 1 });
|
|
1164
|
+
const cubeCaptureState: CubeCaptureGraphState = { work: [] };
|
|
766
1165
|
const persistentRenderScene = new PersistentRenderScene({
|
|
767
1166
|
getDevice: () => internals.device,
|
|
768
1167
|
onGpuError: (error) => internals.errorRegistry.fire(error),
|
|
@@ -908,6 +1307,23 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
908
1307
|
),
|
|
909
1308
|
} satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory);
|
|
910
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
|
+
};
|
|
911
1327
|
const featureGpuWork: RenderFeatureGpuWorkOwner = createRenderFeatureGpuWorkOwner({
|
|
912
1328
|
getDevice: () => internals.device,
|
|
913
1329
|
getShaderModuleFactory: () =>
|
|
@@ -955,15 +1371,18 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
955
1371
|
// raw (material-handle-based, cross-world collision semantically correct).
|
|
956
1372
|
const frameState: RenderFrameState = {
|
|
957
1373
|
frameNumber: 0,
|
|
1374
|
+
reflectionFallbackDemand: false,
|
|
958
1375
|
graphGeneration: 0,
|
|
959
1376
|
successfulTemporalFrameIndex: 0,
|
|
960
1377
|
lastSuccessfulBloom: 'off',
|
|
961
1378
|
temporalFrameTransaction: createTemporalFrameTransaction({ deviceEpoch: 0 }),
|
|
962
1379
|
temporalFrameInput: undefined,
|
|
1380
|
+
reflectionFallbackReadback: undefined,
|
|
963
1381
|
directionalShadowCache: null,
|
|
964
1382
|
directionalShadowCacheRecorded: false,
|
|
965
1383
|
compiledFrameGraph: null,
|
|
966
1384
|
compiledFrameGraphTopologyKey: null,
|
|
1385
|
+
compiledFrameGraphGeneration: 0,
|
|
967
1386
|
volumetricFogPreviousGraph: null,
|
|
968
1387
|
volumetricFogPreviousGraphKey: null,
|
|
969
1388
|
volumetricFogCandidateGraph: null,
|
|
@@ -980,6 +1399,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
980
1399
|
volumetricFogHistorySignature: null,
|
|
981
1400
|
retiredCompiledFrameGraphs: new Set(),
|
|
982
1401
|
currentFrameObservationSource: undefined,
|
|
1402
|
+
reflectionFallbackObservationSource: undefined,
|
|
983
1403
|
lastSuccessfulCameraAntialias: undefined,
|
|
984
1404
|
temporalGpuState: undefined,
|
|
985
1405
|
activeTemporalGpuState: undefined,
|
|
@@ -991,16 +1411,19 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
991
1411
|
currentDirectionalShadowView: null,
|
|
992
1412
|
currentSpotShadowView: null,
|
|
993
1413
|
instanceBuffers: new Map(),
|
|
1414
|
+
probeBlendBuffers: new Map(),
|
|
1415
|
+
probeBlendRecordBufferCapacity: 0,
|
|
994
1416
|
morphBuffers: new Map(),
|
|
995
1417
|
hdrpClusterBinScratch: createClusterBinScratch(),
|
|
996
1418
|
hdrpClusterGridScratch: null,
|
|
997
1419
|
hdrpLightIndexListScratch: null,
|
|
998
|
-
|
|
1420
|
+
hdrpClusterMembership: null,
|
|
1421
|
+
standardLightingGraphSignature: '',
|
|
1422
|
+
standardLightingInspection: undefined,
|
|
1423
|
+
pointShadowInspection: undefined,
|
|
999
1424
|
transientInstanceBuffers: [],
|
|
1000
1425
|
warnedZeroLightStandard: false,
|
|
1001
1426
|
warnedMultiLightDirectional: false,
|
|
1002
|
-
warnedMultiLightPoint: false,
|
|
1003
|
-
warnedMultiLightSpot: false,
|
|
1004
1427
|
// feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M3 / w19:
|
|
1005
1428
|
// once-warn latches for >1 Skylight / >1 SkyboxBackground (names winner).
|
|
1006
1429
|
warnedMultiSkylight: false,
|
|
@@ -1051,10 +1474,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1051
1474
|
// (its topology is frame-invariant). Standard configuration overwrites this with the
|
|
1052
1475
|
// resolved asset config on every swap so the active graph reads its current config.
|
|
1053
1476
|
installedPipelineConfig: undefined,
|
|
1054
|
-
|
|
1055
|
-
// warn dedup set for hdrp-light-budget-exceeded / hdrp-index-list-overflow.
|
|
1056
|
-
isHdrpActive: false,
|
|
1057
|
-
hdrpOncePerFrameFired: new Set(),
|
|
1477
|
+
standardOncePerFrameFired: new Set(),
|
|
1058
1478
|
// feat-20260612-point-light-shadows-urp-hdrp M3 / T-M3-2 (plan-strategy §D-1):
|
|
1059
1479
|
// cube_array shadow atlas + per-frame snapshot list. Atlas is null until the
|
|
1060
1480
|
// first frame whose extracted lights.pointShadow is non-empty (zero-shadow
|
|
@@ -1073,6 +1493,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1073
1493
|
spotShadowSnapshots: [],
|
|
1074
1494
|
};
|
|
1075
1495
|
let directFrameId = 0;
|
|
1496
|
+
let lastDirectionalShadowLights: ExtractedLights | undefined;
|
|
1497
|
+
let lastDirectionalShadowCandidate: 'accepted' | 'failed' = 'failed';
|
|
1498
|
+
let lastDirectionalShadowError: DirectionalShadowInspectionSource['error'];
|
|
1076
1499
|
|
|
1077
1500
|
function beginProfilePhase(session: RecorderSession | undefined, phase: RenderPhase): boolean {
|
|
1078
1501
|
if (session === undefined) return false;
|
|
@@ -1133,66 +1556,124 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1133
1556
|
let activeFeaturePostProcessEntries: ReadonlyMap<string, PostProcessShaderEntry> = new Map();
|
|
1134
1557
|
const lookupPostProcess = (id: string): PostProcessShaderEntry | undefined =>
|
|
1135
1558
|
builtinPostProcessEntries.get(id) ?? activeFeaturePostProcessEntries.get(id);
|
|
1559
|
+
const invalidatePostProcessModule = (id: string): void => {
|
|
1560
|
+
const entry = lookupPostProcess(id);
|
|
1561
|
+
if (entry !== undefined) {
|
|
1562
|
+
internals.invalidateShaderModule?.(postProcessShaderModuleLabel(id, entry.source));
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1136
1565
|
// feat-20260621 M-A2 / w8: expose the eager-created per-id params UBO through
|
|
1137
1566
|
// the narrow runtime surface so dispatchFullscreenPass can writeBuffer the
|
|
1138
1567
|
// per-frame snapshot + bind it at group(1) binding(2).
|
|
1139
1568
|
const getPostProcessParamsBuffer = (id: string): Buffer | undefined =>
|
|
1140
1569
|
postProcessParamsBuffers.get(id);
|
|
1141
|
-
|
|
1570
|
+
interface PostProcessParamsCandidate {
|
|
1571
|
+
readonly buffers: ReadonlyMap<string, Buffer>;
|
|
1572
|
+
readonly created: readonly Buffer[];
|
|
1573
|
+
}
|
|
1574
|
+
const postProcessParamsByteSize = (entry: PostProcessShaderEntry): number =>
|
|
1575
|
+
entry.params?.byteSize ?? (entryHasDepthRead(entry) ? DEPTH_MIN_PARAMS_BYTE_SIZE : 0);
|
|
1576
|
+
const destroyPostProcessParams = (buffers: readonly Buffer[]): void => {
|
|
1577
|
+
for (const buffer of buffers) {
|
|
1578
|
+
const destroyed = internals.device.destroyBuffer(buffer);
|
|
1579
|
+
if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
const preparePostProcessParamsResources = (
|
|
1142
1583
|
featureEntries: ReadonlyMap<string, PostProcessShaderEntry>,
|
|
1143
|
-
):
|
|
1584
|
+
): Result<PostProcessParamsCandidate, RhiError> => {
|
|
1144
1585
|
const declarations = new Map<string, PostProcessShaderEntry>([
|
|
1145
1586
|
...builtinPostProcessEntries.entries(),
|
|
1146
1587
|
...featureEntries.entries(),
|
|
1147
1588
|
]);
|
|
1589
|
+
const next = new Map<string, Buffer>();
|
|
1590
|
+
const allocated: Buffer[] = [];
|
|
1591
|
+
const discardCreated = (): void => destroyPostProcessParams(allocated);
|
|
1148
1592
|
for (const [id, entry] of declarations) {
|
|
1149
1593
|
if (id === 'forgeax.taa-resolve') continue;
|
|
1150
|
-
const byteSize =
|
|
1151
|
-
|
|
1152
|
-
|
|
1594
|
+
const byteSize = postProcessParamsByteSize(entry);
|
|
1595
|
+
if (byteSize === 0) continue;
|
|
1596
|
+
const existing = postProcessParamsBuffers.get(id);
|
|
1597
|
+
const accepted = lookupPostProcess(id);
|
|
1598
|
+
if (
|
|
1599
|
+
existing !== undefined &&
|
|
1600
|
+
accepted !== undefined &&
|
|
1601
|
+
postProcessShaderEntrySignature(accepted) === postProcessShaderEntrySignature(entry)
|
|
1602
|
+
) {
|
|
1603
|
+
next.set(id, existing);
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1153
1606
|
const created = internals.device.createBuffer({
|
|
1154
1607
|
label: `post-process-params-${id}`,
|
|
1155
1608
|
size: byteSize,
|
|
1156
1609
|
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1157
1610
|
mappedAtCreation: false,
|
|
1158
1611
|
});
|
|
1159
|
-
if (!created.ok)
|
|
1612
|
+
if (!created.ok) {
|
|
1613
|
+
discardCreated();
|
|
1614
|
+
return err(created.error);
|
|
1615
|
+
}
|
|
1160
1616
|
const buffer = created.value;
|
|
1617
|
+
allocated.push(buffer);
|
|
1161
1618
|
if (entry.params !== undefined) {
|
|
1162
1619
|
const written = internals.device.queue.writeBuffer(buffer, 0, entry.params.defaultValue);
|
|
1163
1620
|
if (!written.ok) {
|
|
1164
|
-
|
|
1165
|
-
|
|
1621
|
+
discardCreated();
|
|
1622
|
+
return err(written.error);
|
|
1166
1623
|
}
|
|
1167
1624
|
}
|
|
1168
|
-
|
|
1625
|
+
next.set(id, buffer);
|
|
1169
1626
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1627
|
+
return ok({ buffers: next, created: Object.freeze([...allocated]) });
|
|
1628
|
+
};
|
|
1629
|
+
const discardPostProcessParamsCandidate = (candidate: PostProcessParamsCandidate): void => {
|
|
1630
|
+
destroyPostProcessParams(candidate.created);
|
|
1631
|
+
};
|
|
1632
|
+
const retirePostProcessParams = (buffers: readonly Buffer[]): void => {
|
|
1633
|
+
if (buffers.length === 0) return;
|
|
1634
|
+
const release = (): void => destroyPostProcessParams(buffers);
|
|
1635
|
+
try {
|
|
1636
|
+
void internals.device.queue.onSubmittedWorkDone().then(release, release);
|
|
1637
|
+
} catch {
|
|
1638
|
+
release();
|
|
1174
1639
|
}
|
|
1175
1640
|
};
|
|
1176
|
-
|
|
1641
|
+
const acceptPostProcessParamsCandidate = (candidate: PostProcessParamsCandidate): void => {
|
|
1642
|
+
const previous = [...postProcessParamsBuffers.values()];
|
|
1643
|
+
const next = new Set(candidate.buffers.values());
|
|
1644
|
+
postProcessParamsBuffers.clear();
|
|
1645
|
+
for (const [id, buffer] of candidate.buffers) {
|
|
1646
|
+
postProcessParamsBuffers.set(id, buffer);
|
|
1647
|
+
}
|
|
1648
|
+
retirePostProcessParams(previous.filter((buffer) => !next.has(buffer)));
|
|
1649
|
+
};
|
|
1650
|
+
// feat-20260609 M4 / T-10-a: post-process pipeline cache (declaration|colorFormat -> RhiRenderPipeline).
|
|
1177
1651
|
// Solves CONCERN-1: dispatcher previously passed `pipeline=null` to
|
|
1178
1652
|
// built.createHandle because per-frame execute closures cannot await async
|
|
1179
1653
|
// shader compile. The cache here delegates the actual build to
|
|
1180
1654
|
// `internals.buildPostProcessPipeline` (sync wrapper over the shared shader
|
|
1181
|
-
// adapter; 1-frame warmup), then memoizes by
|
|
1655
|
+
// adapter; 1-frame warmup), then memoizes by declaration identity + format.
|
|
1182
1656
|
const postProcessPipelineCache = new Map<string, RenderPipeline>();
|
|
1183
1657
|
const getPostProcessPipeline = (
|
|
1184
1658
|
id: string,
|
|
1185
1659
|
bgl: BindGroupLayout,
|
|
1186
1660
|
colorFormat: GPUTextureFormat,
|
|
1661
|
+
entryOverride?: PostProcessShaderEntry,
|
|
1187
1662
|
): RenderPipeline | null => {
|
|
1188
|
-
const
|
|
1663
|
+
const entry = entryOverride ?? lookupPostProcess(id);
|
|
1664
|
+
if (entry === undefined) return null;
|
|
1665
|
+
const signature = postProcessShaderEntrySignature(entry);
|
|
1666
|
+
const key = `${id}|${colorFormat}|${signature}`;
|
|
1189
1667
|
const cached = postProcessPipelineCache.get(key);
|
|
1190
1668
|
if (cached !== undefined) return cached;
|
|
1191
|
-
const entry = lookupPostProcess(id);
|
|
1192
|
-
if (entry === undefined) return null;
|
|
1193
1669
|
const factory = internals.buildPostProcessPipeline;
|
|
1194
1670
|
if (factory === undefined) return null;
|
|
1195
|
-
const built = factory(
|
|
1671
|
+
const built = factory(
|
|
1672
|
+
entry,
|
|
1673
|
+
bgl,
|
|
1674
|
+
colorFormat,
|
|
1675
|
+
postProcessShaderPipelineLabel(id, entry.source),
|
|
1676
|
+
);
|
|
1196
1677
|
if (built === null) return null;
|
|
1197
1678
|
postProcessPipelineCache.set(key, built);
|
|
1198
1679
|
return built;
|
|
@@ -1202,6 +1683,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1202
1683
|
if (key.startsWith(`${id}|`)) postProcessPipelineCache.delete(key);
|
|
1203
1684
|
}
|
|
1204
1685
|
};
|
|
1686
|
+
const clearPostProcessPipelineEntry = (id: string, entry: PostProcessShaderEntry): void => {
|
|
1687
|
+
const suffix = `|${postProcessShaderEntrySignature(entry)}`;
|
|
1688
|
+
for (const key of postProcessPipelineCache.keys()) {
|
|
1689
|
+
if (key.startsWith(`${id}|`) && key.endsWith(suffix)) {
|
|
1690
|
+
postProcessPipelineCache.delete(key);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1205
1694
|
Object.assign(internals, {
|
|
1206
1695
|
lookupPostProcess,
|
|
1207
1696
|
getPostProcessParamsBuffer,
|
|
@@ -1217,6 +1706,134 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1217
1706
|
const dispatchCounts: { unlit: number } = {
|
|
1218
1707
|
unlit: 0,
|
|
1219
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
|
+
};
|
|
1220
1837
|
// feat-20260531-per-frame-bind-group-cache M1 / w4: per-frame
|
|
1221
1838
|
// createBindGroup counter scaffolding. Reset on every draw([world], { cameraOwner: 0, resourceOwner: 0 }) entry,
|
|
1222
1839
|
// bumped on cache-miss in render-system-record.ts (M2-M4 bump points).
|
|
@@ -1376,7 +1993,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1376
1993
|
if (buffer === undefined) return group;
|
|
1377
1994
|
return ok({
|
|
1378
1995
|
handle: group.value,
|
|
1379
|
-
dynamicOffsets: [0],
|
|
1996
|
+
dynamicOffsets: [0, 0],
|
|
1380
1997
|
release: () => internals.device.destroyBuffer(buffer),
|
|
1381
1998
|
});
|
|
1382
1999
|
};
|
|
@@ -1387,8 +2004,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1387
2004
|
const preparedResolverFactory = (
|
|
1388
2005
|
input: RenderFeaturePreparedGraphicsResolverInput,
|
|
1389
2006
|
): PreparedGraphicsResolver => {
|
|
1390
|
-
activeFeaturePostProcessEntries = input.fullscreenEffects;
|
|
1391
|
-
ensurePostProcessParamsResources(input.fullscreenEffects);
|
|
1392
2007
|
return createPreparedGraphicsResolver({
|
|
1393
2008
|
device: internals.device,
|
|
1394
2009
|
featureIdentity: input.featureIdentity,
|
|
@@ -1412,6 +2027,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1412
2027
|
descriptor.shader,
|
|
1413
2028
|
fullscreen.bindGroupLayout,
|
|
1414
2029
|
descriptor.colorFormats[0] as GPUTextureFormat,
|
|
2030
|
+
postProcessEntry,
|
|
1415
2031
|
);
|
|
1416
2032
|
if (pipeline !== null && pipeline !== undefined) {
|
|
1417
2033
|
preparedPipelineIds.set(pipeline as object, descriptor.shader);
|
|
@@ -1580,6 +2196,47 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1580
2196
|
gpuDriven: gpuDrivenProduction.inspect(),
|
|
1581
2197
|
};
|
|
1582
2198
|
},
|
|
2199
|
+
get gpuDrivenInspection(): GpuDrivenProductionInspection {
|
|
2200
|
+
return gpuDrivenProduction.inspect();
|
|
2201
|
+
},
|
|
2202
|
+
get reflectionProbes(): ReflectionProbeInspection {
|
|
2203
|
+
return reflectionProbeOwner.inspect();
|
|
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
|
+
},
|
|
2237
|
+
isCubeCapturePending(target: RenderTarget): boolean {
|
|
2238
|
+
return cubeCaptureScheduler.inspect(target).candidateGeneration !== undefined;
|
|
2239
|
+
},
|
|
1583
2240
|
get environment(): EnvironmentInspection {
|
|
1584
2241
|
return environmentLifecycle.inspect();
|
|
1585
2242
|
},
|
|
@@ -1646,6 +2303,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1646
2303
|
}),
|
|
1647
2304
|
});
|
|
1648
2305
|
},
|
|
2306
|
+
get lodOcclusionInspection(): LodOcclusionInspection | undefined {
|
|
2307
|
+
return lastLodOcclusionInspection;
|
|
2308
|
+
},
|
|
1649
2309
|
get bloom(): BloomInspection {
|
|
1650
2310
|
const pipelineState = internals.getPipelineState();
|
|
1651
2311
|
const resources = pipelineState?.perPassResources;
|
|
@@ -1672,6 +2332,49 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1672
2332
|
get transmission(): TransmissionInspection | undefined {
|
|
1673
2333
|
return lastTransmissionInspection;
|
|
1674
2334
|
},
|
|
2335
|
+
get directionalShadow(): DirectionalShadowInspection {
|
|
2336
|
+
const lights = lastDirectionalShadowLights;
|
|
2337
|
+
const pipelineState = internals.getPipelineState();
|
|
2338
|
+
const graph = frameState.compiledFrameGraph?.inspect();
|
|
2339
|
+
const requested = directionalShadowProfileFromQuality(lights?.directionalShadowQuality);
|
|
2340
|
+
const mapSize = pipelineState?.perPassResources.shadowMapSize ?? 0;
|
|
2341
|
+
const cascadeCount = lights?.cascadeCount ?? 0;
|
|
2342
|
+
const shadowReady = frameState.currentDirectionalShadowView !== null;
|
|
2343
|
+
const atlasBytes =
|
|
2344
|
+
shadowReady && mapSize > 0 && cascadeCount > 0
|
|
2345
|
+
? mapSize * mapSize * Math.ceil(Math.sqrt(cascadeCount)) ** 2 * 4
|
|
2346
|
+
: 0;
|
|
2347
|
+
return projectDirectionalShadowInspectionSource({
|
|
2348
|
+
requested,
|
|
2349
|
+
candidate: lastDirectionalShadowCandidate,
|
|
2350
|
+
...(frameState.directionalShadowCache === null
|
|
2351
|
+
? {}
|
|
2352
|
+
: {
|
|
2353
|
+
lastKnownGood: directionalShadowProfileFromQuality(
|
|
2354
|
+
frameState.directionalShadowCache.directionalShadowQuality,
|
|
2355
|
+
),
|
|
2356
|
+
}),
|
|
2357
|
+
cascadeCount,
|
|
2358
|
+
mapSize,
|
|
2359
|
+
atlasBytes,
|
|
2360
|
+
writerPasses:
|
|
2361
|
+
graph?.passes.filter((pass) => pass.name.startsWith('shadowCascade')).length ?? 0,
|
|
2362
|
+
shadowAngularRadius: lights?.directionalCsmConfig?.shadowAngularRadius,
|
|
2363
|
+
maxPenumbraTexels: lights?.directionalCsmConfig?.maxPenumbraTexels,
|
|
2364
|
+
deviceGeneration: internals.deviceScope.generation,
|
|
2365
|
+
graphGeneration: frameState.perFrameGraph?.graphGeneration ?? frameState.graphGeneration,
|
|
2366
|
+
shadowReady,
|
|
2367
|
+
...(lastDirectionalShadowError === undefined ? {} : { error: lastDirectionalShadowError }),
|
|
2368
|
+
});
|
|
2369
|
+
},
|
|
2370
|
+
get standardLightingInspection(): StandardLightingInspection | undefined {
|
|
2371
|
+
const inspection = frameState.standardLightingInspection;
|
|
2372
|
+
return inspection === undefined ? undefined : Object.freeze({ ...inspection });
|
|
2373
|
+
},
|
|
2374
|
+
get pointShadowInspection(): PointShadowInspection | undefined {
|
|
2375
|
+
const inspection = frameState.pointShadowInspection;
|
|
2376
|
+
return inspection === undefined ? undefined : Object.freeze({ ...inspection });
|
|
2377
|
+
},
|
|
1675
2378
|
get pointsLinesSnapshots(): readonly PointsLinesRetainedSnapshot[] {
|
|
1676
2379
|
return persistentRenderScene.pointsLinesSnapshots();
|
|
1677
2380
|
},
|
|
@@ -1741,20 +2444,27 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1741
2444
|
worlds,
|
|
1742
2445
|
{ cameraOwner, resourceOwner },
|
|
1743
2446
|
internals.assets.catalogEpoch,
|
|
1744
|
-
() =>
|
|
2447
|
+
(renderables) =>
|
|
1745
2448
|
extractFrames(
|
|
1746
2449
|
worlds,
|
|
1747
2450
|
{ cameraOwner, resourceOwner },
|
|
1748
2451
|
internals.assets,
|
|
1749
2452
|
internals.getPipelineState(),
|
|
1750
2453
|
persistentRenderScene.materialSnapshotCacheStore(),
|
|
1751
|
-
{
|
|
2454
|
+
{
|
|
2455
|
+
cull: 'none',
|
|
2456
|
+
renderables,
|
|
2457
|
+
...(internals.getMaterialShaderArtifact === undefined
|
|
2458
|
+
? {}
|
|
2459
|
+
: { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
|
|
2460
|
+
},
|
|
1752
2461
|
),
|
|
1753
2462
|
renderReadLeases,
|
|
1754
2463
|
);
|
|
1755
2464
|
});
|
|
1756
2465
|
const {
|
|
1757
2466
|
cameras,
|
|
2467
|
+
cubeCameras,
|
|
1758
2468
|
lights,
|
|
1759
2469
|
environment,
|
|
1760
2470
|
environmentReady,
|
|
@@ -1765,18 +2475,296 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1765
2475
|
skylightCount,
|
|
1766
2476
|
skybox,
|
|
1767
2477
|
skyboxCount,
|
|
1768
|
-
|
|
2478
|
+
fogFailure,
|
|
1769
2479
|
frustumStats,
|
|
1770
2480
|
visibilityStats,
|
|
1771
2481
|
postProcessParams,
|
|
1772
2482
|
} = frame;
|
|
2483
|
+
lastDirectionalShadowLights = lights;
|
|
2484
|
+
lastDirectionalShadowCandidate = 'failed';
|
|
2485
|
+
lastDirectionalShadowError = lights.directionalShadowError;
|
|
2486
|
+
const reflectionProbeRecord = reflectionProbeOwner.prepare(
|
|
2487
|
+
frame.reflectionProbes ?? [],
|
|
2488
|
+
persistentRenderScene.reflectionProbeProjection().selected,
|
|
2489
|
+
cameras[0],
|
|
2490
|
+
frameState.frameNumber,
|
|
2491
|
+
skylight,
|
|
2492
|
+
);
|
|
1773
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
|
+
}
|
|
1774
2759
|
const temporalDemanded =
|
|
1775
2760
|
cameras[0]?.antialias === 'taa' || (cameras[0]?.motionBlur?.shutterAngle ?? 0) > 0;
|
|
1776
2761
|
if (temporalDemanded) {
|
|
1777
|
-
persistentRenderScene.prepareTemporalFrame(
|
|
2762
|
+
persistentRenderScene.prepareTemporalFrame(submissionRenderables);
|
|
1778
2763
|
}
|
|
1779
2764
|
environmentLifecycle.bindScope(internals.deviceScope);
|
|
2765
|
+
if (fogFailure !== undefined) {
|
|
2766
|
+
environmentLifecycle.recordSelectionFailure(fogFailure);
|
|
2767
|
+
}
|
|
1780
2768
|
if (!environmentReady || environment === undefined) {
|
|
1781
2769
|
frameState.pendingTemporalCommit = { kind: 'none' };
|
|
1782
2770
|
// A world with no camera still has a valid clear-only render path.
|
|
@@ -1805,15 +2793,21 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1805
2793
|
}
|
|
1806
2794
|
if (lastMeshMaterialBindingFrame !== frame) {
|
|
1807
2795
|
lastMeshMaterialBindingFrame = frame;
|
|
1808
|
-
lastMeshMaterialBindings = renderables.map((renderable) =>
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2796
|
+
lastMeshMaterialBindings = renderables.map((renderable) => {
|
|
2797
|
+
const materialWorld = preparedWorlds[renderable.worldId];
|
|
2798
|
+
return projectMeshMaterialBindingObservation({
|
|
2799
|
+
worldId: renderable.worldId,
|
|
2800
|
+
entityKey: renderable.entityKey,
|
|
2801
|
+
bindings: renderable.materials.map((material, slotIndex) => ({
|
|
2802
|
+
handle: material.materialHandle ?? 0,
|
|
2803
|
+
source: renderable.materialBindingSources[slotIndex] ?? 'engine-default',
|
|
2804
|
+
})),
|
|
2805
|
+
diagnostics: renderable.materialBindingDiagnostics ?? [],
|
|
2806
|
+
residency: renderable.materials.map((material) =>
|
|
2807
|
+
observeMaterialResidency(materialWorld, material, internals),
|
|
2808
|
+
),
|
|
2809
|
+
});
|
|
2810
|
+
});
|
|
1817
2811
|
}
|
|
1818
2812
|
|
|
1819
2813
|
bindGroupCounts.createBindGroup = 0;
|
|
@@ -1879,14 +2873,23 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1879
2873
|
createPreparedGraphicsResolver: preparedResolverFactory,
|
|
1880
2874
|
gpuWork: featureGpuWork,
|
|
1881
2875
|
});
|
|
1882
|
-
activeFeaturePostProcessEntries = featureFrame.fullscreenEffects;
|
|
1883
|
-
ensurePostProcessParamsResources(featureFrame.fullscreenEffects);
|
|
1884
2876
|
lastVisibilityStats.explicitlyHidden = featureFrame.hiddenEntityReports.length;
|
|
1885
2877
|
for (const featureError of featureFrame.errors) {
|
|
1886
2878
|
if (!isPendingRenderFeaturePreparation(featureError)) {
|
|
1887
2879
|
internals.errorRegistry.fire(featureError);
|
|
1888
2880
|
}
|
|
1889
2881
|
}
|
|
2882
|
+
const postProcessParamsCandidate = preparePostProcessParamsResources(
|
|
2883
|
+
featureFrame.fullscreenEffects,
|
|
2884
|
+
);
|
|
2885
|
+
if (!postProcessParamsCandidate.ok) {
|
|
2886
|
+
internals.errorRegistry.fire(postProcessParamsCandidate.error);
|
|
2887
|
+
for (const batch of featureFrame.preparedResourceBatches) {
|
|
2888
|
+
const released = batch.release();
|
|
2889
|
+
if (!released.ok) internals.errorRegistry.fire(released.error);
|
|
2890
|
+
}
|
|
2891
|
+
return [];
|
|
2892
|
+
}
|
|
1890
2893
|
featureGraphCandidate = {
|
|
1891
2894
|
plans: featureFrame.plans,
|
|
1892
2895
|
fullscreenEffects: featureFrame.fullscreenEffects,
|
|
@@ -1900,6 +2903,29 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1900
2903
|
internals.errorRegistry.fire(released.error);
|
|
1901
2904
|
}
|
|
1902
2905
|
}
|
|
2906
|
+
discardPostProcessParamsCandidate(postProcessParamsCandidate.value);
|
|
2907
|
+
},
|
|
2908
|
+
onAbandoned: () => {
|
|
2909
|
+
discardPostProcessParamsCandidate(postProcessParamsCandidate.value);
|
|
2910
|
+
},
|
|
2911
|
+
onAccepted: () => {
|
|
2912
|
+
for (const [id, previous] of activeFeaturePostProcessEntries) {
|
|
2913
|
+
const next = featureFrame.fullscreenEffects.get(id);
|
|
2914
|
+
if (next === undefined) {
|
|
2915
|
+
clearPostProcessPipelineCache(id);
|
|
2916
|
+
invalidatePostProcessModule(id);
|
|
2917
|
+
} else if (
|
|
2918
|
+
postProcessShaderEntrySignature(previous) !==
|
|
2919
|
+
postProcessShaderEntrySignature(next)
|
|
2920
|
+
) {
|
|
2921
|
+
clearPostProcessPipelineEntry(id, previous);
|
|
2922
|
+
if (previous.source !== next.source) {
|
|
2923
|
+
invalidatePostProcessModule(id);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
acceptPostProcessParamsCandidate(postProcessParamsCandidate.value);
|
|
2928
|
+
activeFeaturePostProcessEntries = featureFrame.fullscreenEffects;
|
|
1903
2929
|
},
|
|
1904
2930
|
};
|
|
1905
2931
|
return featureFrame.preparedResourceBatches;
|
|
@@ -1911,9 +2937,31 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1911
2937
|
// world that owns skylight / skybox / singleton render state, w5 / D-3).
|
|
1912
2938
|
// Only the Transparent segment is reordered; queue ordering between
|
|
1913
2939
|
// segments (sortDispatchByQueue, stable) is preserved.
|
|
1914
|
-
const orderedDispatch = runProfiledRenderPhase(profileSession, 'sort', () =>
|
|
1915
|
-
|
|
1916
|
-
|
|
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
|
+
});
|
|
1917
2965
|
|
|
1918
2966
|
// M3 / w26: single dispatch list replaces old three-bucket model.
|
|
1919
2967
|
// Pass dispatch to recordFrame — the record stage iterates dispatch
|
|
@@ -1941,13 +2989,16 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1941
2989
|
// The same runner is also called by geometry/material helpers;
|
|
1942
2990
|
// invoking those wrappers would turn a pass probe into the
|
|
1943
2991
|
// high-overhead per-draw `nested` probe.
|
|
1944
|
-
if (
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
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();
|
|
1951
3002
|
}
|
|
1952
3003
|
return runProfiledRenderPhase(profileSession, phase, action);
|
|
1953
3004
|
};
|
|
@@ -1960,7 +3011,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1960
3011
|
resourceWorld,
|
|
1961
3012
|
cameras,
|
|
1962
3013
|
lights,
|
|
1963
|
-
|
|
3014
|
+
[...submissionRenderables],
|
|
1964
3015
|
orderedDispatch,
|
|
1965
3016
|
frameState,
|
|
1966
3017
|
dispatchCounts,
|
|
@@ -1970,20 +3021,28 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1970
3021
|
skybox,
|
|
1971
3022
|
skyboxCount,
|
|
1972
3023
|
postProcessParams,
|
|
1973
|
-
fog,
|
|
1974
3024
|
worlds,
|
|
1975
3025
|
recordProfilePhase,
|
|
1976
3026
|
{
|
|
1977
3027
|
owner: gpuDrivenProduction,
|
|
1978
3028
|
scene: persistentRenderScene.compositionGpuDrivenState(),
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
persistentRenderScene.setCompositionGpuDrivenCoverage(key, ownsAll);
|
|
3029
|
+
onSceneDemand: (sceneRowsRequired) => {
|
|
3030
|
+
persistentRenderScene.setCompositionGpuSceneDemand(sceneRowsRequired);
|
|
1982
3031
|
},
|
|
3032
|
+
activeEntityKeys: visibilityProjection.activeEntityKeys,
|
|
3033
|
+
activeEntityRevision: visibilityProjection.activeEntityRevision,
|
|
3034
|
+
telemetryCandidateCount: lodCandidateCountForFrame,
|
|
3035
|
+
...(occlusionProjection === undefined ? {} : { occlusion: occlusionProjection }),
|
|
1983
3036
|
},
|
|
1984
3037
|
renderReadLeases,
|
|
1985
3038
|
featureGraphCandidate,
|
|
1986
3039
|
pointsLinesOwner,
|
|
3040
|
+
{
|
|
3041
|
+
snapshots: cubeCameras,
|
|
3042
|
+
scheduler: cubeCaptureScheduler,
|
|
3043
|
+
state: cubeCaptureState,
|
|
3044
|
+
reflectionProbes: reflectionProbeRecord,
|
|
3045
|
+
} satisfies CubeCaptureFrameInput,
|
|
1987
3046
|
environment?.environmentSignature ?? '',
|
|
1988
3047
|
environment?.fogSignature ?? '',
|
|
1989
3048
|
environmentReady && environment !== undefined,
|
|
@@ -1992,7 +3051,50 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1992
3051
|
timingCapture,
|
|
1993
3052
|
),
|
|
1994
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
|
+
}
|
|
3070
|
+
const directionalQuality = lights.directionalShadowQuality;
|
|
3071
|
+
const directionalShadowReady =
|
|
3072
|
+
directionalQuality === undefined ||
|
|
3073
|
+
(frameState.currentDirectionalShadowView !== null &&
|
|
3074
|
+
(lights.lightViewProj?.length ?? 0) > 0 &&
|
|
3075
|
+
(internals.getPipelineState()?.perPassResources.shadowMapSize ?? 0) > 0);
|
|
3076
|
+
if (submitted && directionalShadowReady) {
|
|
3077
|
+
lastDirectionalShadowCandidate = 'accepted';
|
|
3078
|
+
} else if (directionalQuality !== undefined) {
|
|
3079
|
+
lastDirectionalShadowError = {
|
|
3080
|
+
code: 'directional-shadow-candidate-failed',
|
|
3081
|
+
expected:
|
|
3082
|
+
'the extracted Directional CSM candidate has a valid graph target and fitted matrices',
|
|
3083
|
+
hint: 'inspect the retained LKG and retry after the graph or resource owner recovers',
|
|
3084
|
+
detail: {
|
|
3085
|
+
submitted,
|
|
3086
|
+
hasShadowView: frameState.currentDirectionalShadowView !== null,
|
|
3087
|
+
matrixCount: lights.lightViewProj?.length ?? 0,
|
|
3088
|
+
shadowMapSize: internals.getPipelineState()?.perPassResources.shadowMapSize ?? 0,
|
|
3089
|
+
},
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
1995
3092
|
if (submitted) {
|
|
3093
|
+
if (occlusionProjection === undefined) {
|
|
3094
|
+
runProfiledRenderPhase(profileSession, 'record/occlusion-global-advance', () =>
|
|
3095
|
+
ensureOcclusionRuntime().advanceSuccessfulSubmit(frameState.frameNumber),
|
|
3096
|
+
);
|
|
3097
|
+
}
|
|
1996
3098
|
const temporalCommit = persistentRenderScene.commitTemporalFrame();
|
|
1997
3099
|
if (!temporalCommit.ok) {
|
|
1998
3100
|
internals.errorRegistry.fire(temporalCommit.error);
|
|
@@ -2071,6 +3173,21 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2071
3173
|
const currentFrameId = frameState.frameNumber - 1;
|
|
2072
3174
|
return observeCurrentFrame(options, frameState.currentFrameObservationSource, currentFrameId);
|
|
2073
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
|
+
},
|
|
2074
3191
|
getCurrentGraphTarget(name: string) {
|
|
2075
3192
|
const graph = frameState.perFrameGraph;
|
|
2076
3193
|
if (graph === undefined || graph === null) return undefined;
|
|
@@ -2127,9 +3244,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2127
3244
|
profileConfig === undefined
|
|
2128
3245
|
? config
|
|
2129
3246
|
: {
|
|
2130
|
-
|
|
2131
|
-
? { clusterGrid: DEFAULT_CLUSTER_GRID }
|
|
2132
|
-
: {}),
|
|
3247
|
+
clusterGrid: DEFAULT_CLUSTER_GRID,
|
|
2133
3248
|
...(profileConfig.ssao ? { ssao: { enabled: true } } : {}),
|
|
2134
3249
|
...config,
|
|
2135
3250
|
};
|
|
@@ -2154,16 +3269,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2154
3269
|
// is a rare boot/swap event, never a per-frame cost.
|
|
2155
3270
|
installEpoch += 1;
|
|
2156
3271
|
frameState.installedPipelineHandle = installEpoch;
|
|
2157
|
-
// The installed profile may request clustered lighting, but the actual
|
|
2158
|
-
// graph lane is capability-gated. On WebGL2 (including WebKit's wgpu
|
|
2159
|
-
// fallback) the forward lane is selected when compute/storage buffers
|
|
2160
|
-
// are unavailable; keep the frame bindings on that same lane instead of
|
|
2161
|
-
// advertising HDRP's clustered bind-group layout to URP pipelines.
|
|
2162
|
-
frameState.isHdrpActive =
|
|
2163
|
-
resolveStandardLane(internals.standardProfile ?? DEFAULT_STANDARD_PROFILE, {
|
|
2164
|
-
compute: internals.device.caps.compute,
|
|
2165
|
-
storageBuffer: internals.device.caps.storageBuffer,
|
|
2166
|
-
}) === 'clustered';
|
|
2167
3272
|
frameState.installedPipelineConfig = resolvedConfig;
|
|
2168
3273
|
},
|
|
2169
3274
|
registerBuiltinPostProcess(id: string, entry: PostProcessShaderEntry): () => void {
|
|
@@ -2207,6 +3312,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2207
3312
|
}
|
|
2208
3313
|
builtinPostProcessEntries.set(id, entry);
|
|
2209
3314
|
if (paramsBuffer !== undefined) postProcessParamsBuffers.set(id, paramsBuffer);
|
|
3315
|
+
const moduleLabel = postProcessShaderModuleLabel(id, entry.source);
|
|
2210
3316
|
return () => {
|
|
2211
3317
|
if (builtinPostProcessEntries.get(id) === entry) {
|
|
2212
3318
|
builtinPostProcessEntries.delete(id);
|
|
@@ -2216,9 +3322,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2216
3322
|
internals.device.destroyBuffer(current);
|
|
2217
3323
|
postProcessParamsBuffers.delete(id);
|
|
2218
3324
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
}
|
|
3325
|
+
clearPostProcessPipelineCache(id);
|
|
3326
|
+
internals.invalidateShaderModule?.(moduleLabel);
|
|
2222
3327
|
};
|
|
2223
3328
|
} catch (cause) {
|
|
2224
3329
|
if (paramsBuffer !== undefined) internals.device.destroyBuffer(paramsBuffer);
|
|
@@ -2226,16 +3331,25 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2226
3331
|
}
|
|
2227
3332
|
},
|
|
2228
3333
|
disposeFrameState(): void {
|
|
3334
|
+
(internals as TimingSessionHost).gpuPassTimingSession?.dispose();
|
|
3335
|
+
(internals as TimingSessionHost).gpuPassTimingSession = undefined;
|
|
3336
|
+
(internals as TimingSessionHost).gpuPassTimingCapture = undefined;
|
|
3337
|
+
(internals as TimingSessionHost).gpuPassTimingSubmittedWork = undefined;
|
|
3338
|
+
(internals as TimingSessionHost).gpuPassTimingFrameIdentity = undefined;
|
|
3339
|
+
(internals as TimingSessionHost).gpuPassTimingBeginReason = undefined;
|
|
2229
3340
|
persistentRenderScene.dispose();
|
|
2230
3341
|
gpuDrivenProduction.dispose();
|
|
3342
|
+
occlusionRuntime?.dispose();
|
|
2231
3343
|
disposeFeatureGpuWork();
|
|
3344
|
+
reflectionProbeOwner.dispose();
|
|
2232
3345
|
// Bloom generations are renderer-owned rather than graph-owned. Drain
|
|
2233
3346
|
// candidate, active, and fence-retiring bundles before the rest of the
|
|
2234
3347
|
// frame state is torn down; the owner callback is idempotent so the
|
|
2235
3348
|
// outer Renderer.dispose cascade can safely call this once only.
|
|
2236
3349
|
internals.getPipelineState()?.perPassResources.drainBloomResources?.();
|
|
2237
3350
|
internals.clearPostProcessPipelineCache?.('forgeax.taa-resolve');
|
|
2238
|
-
|
|
3351
|
+
invalidatePostProcessModule('forgeax.taa-resolve');
|
|
3352
|
+
resetHdrpBuffers(internals);
|
|
2239
3353
|
// Retire graph-owned resources and dispose instance-buffer caches.
|
|
2240
3354
|
// Both calls are idempotent + tolerate per-handle errors silently;
|
|
2241
3355
|
// the Renderer.dispose() cascade owns the surrounding try/catch
|
|
@@ -2276,9 +3390,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2276
3390
|
frameState.volumetricFogHistoryGraph = null;
|
|
2277
3391
|
frameState.volumetricFogHistorySlot = null;
|
|
2278
3392
|
frameState.volumetricFogHistorySignature = null;
|
|
3393
|
+
frameState.hdrpClusterMembership = null;
|
|
2279
3394
|
const compiled = frameState.compiledFrameGraph;
|
|
2280
3395
|
frameState.compiledFrameGraph = null;
|
|
2281
3396
|
frameState.compiledFrameGraphTopologyKey = null;
|
|
3397
|
+
frameState.perFrameGraph = null;
|
|
3398
|
+
frameState.standardLightingGraphSignature = '';
|
|
3399
|
+
frameState.standardLightingInspection = undefined;
|
|
3400
|
+
frameState.pointShadowInspection = undefined;
|
|
2282
3401
|
if (compiled !== null) compiled.retire().catch(() => undefined);
|
|
2283
3402
|
for (const retired of frameState.retiredCompiledFrameGraphs) {
|
|
2284
3403
|
retired.retire().catch(() => undefined);
|
|
@@ -2289,6 +3408,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2289
3408
|
// dispose error strategy).
|
|
2290
3409
|
disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
|
|
2291
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();
|
|
2292
3420
|
if (frameState.morphBuffers !== undefined) {
|
|
2293
3421
|
for (const entry of frameState.morphBuffers.values()) {
|
|
2294
3422
|
if (!entry.buffer.isDestroyed) {
|
|
@@ -2309,10 +3437,19 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2309
3437
|
}
|
|
2310
3438
|
},
|
|
2311
3439
|
resetForRecover(): void {
|
|
3440
|
+
ssrFormatReceipt = undefined;
|
|
3441
|
+
ssrFormatProbeGeneration = -1;
|
|
3442
|
+
ssrFormatProbe = undefined;
|
|
3443
|
+
ssrFallbackGeneration = undefined;
|
|
2312
3444
|
transmissionAdmission.markDeviceLost();
|
|
2313
3445
|
lastTransmissionKey = '';
|
|
2314
3446
|
transmissionCapabilityGeneration = -1;
|
|
2315
3447
|
transmissionCapability = undefined;
|
|
3448
|
+
// The replacement device cannot consume buffers minted by the lost
|
|
3449
|
+
// device. Drop the shared Standard Cluster bundle before the next
|
|
3450
|
+
// frame asks for a new generation; resetHdrpBuffers fences the bundle
|
|
3451
|
+
// using its own device/queue instead of the replacement runtime device.
|
|
3452
|
+
resetHdrpBuffers(internals);
|
|
2316
3453
|
// Keep the persistent material demand observable across device loss.
|
|
2317
3454
|
// The physical resource has already been fenced by markDeviceLost(); the
|
|
2318
3455
|
// detached inspection now exposes the rebuild-required lifecycle until a
|
|
@@ -2321,7 +3458,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2321
3458
|
persistentRenderScene.resetGpuForRecover();
|
|
2322
3459
|
gpuDrivenProduction.dispose();
|
|
2323
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;
|
|
2324
3466
|
disposeFeatureGpuWork();
|
|
3467
|
+
reflectionProbeOwner.dispose();
|
|
2325
3468
|
// A recovered device cannot retain handles from the lost device. The
|
|
2326
3469
|
// Bloom owner abandons candidates and retires every committed generation
|
|
2327
3470
|
// before the new PipelineState is rebuilt.
|
|
@@ -2351,10 +3494,23 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2351
3494
|
frameState.volumetricFogParamsAcceptedSlot = null;
|
|
2352
3495
|
frameState.volumetricFogAcceptedParams = undefined;
|
|
2353
3496
|
frameState.volumetricFogPendingParams = undefined;
|
|
3497
|
+
// Do not expose graph-owned texture/descriptor accessors from the lost
|
|
3498
|
+
// device while recovery is between generations. Inspection and capture
|
|
3499
|
+
// must remain detached until the replacement graph is accepted.
|
|
3500
|
+
frameState.perFrameGraph = null;
|
|
3501
|
+
frameState.standardLightingGraphSignature = '';
|
|
3502
|
+
frameState.standardLightingInspection = undefined;
|
|
3503
|
+
frameState.pointShadowInspection = undefined;
|
|
2354
3504
|
frameState.directionalShadowCache = null;
|
|
2355
3505
|
frameState.directionalShadowCacheRecorded = false;
|
|
2356
3506
|
frameState.currentFrameObservationSource = undefined;
|
|
2357
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' };
|
|
2358
3514
|
frameState.currentDirectionalShadowView = null;
|
|
2359
3515
|
frameState.currentSpotShadowView = null;
|
|
2360
3516
|
if (frameState.temporalGpuState !== undefined) {
|
|
@@ -2369,11 +3525,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2369
3525
|
retireTemporalGpuState(retiring);
|
|
2370
3526
|
}
|
|
2371
3527
|
frameState.retiringTemporalGpuStates.clear();
|
|
3528
|
+
frameState.hdrpClusterMembership = null;
|
|
2372
3529
|
for (const retired of frameState.retiredCompiledFrameGraphs) {
|
|
2373
3530
|
retired.retire().catch(() => undefined);
|
|
2374
3531
|
}
|
|
2375
3532
|
frameState.retiredCompiledFrameGraphs.clear();
|
|
2376
3533
|
frameState.instanceBuffers.clear();
|
|
3534
|
+
delete frameState.probeBlendRecordBuffer;
|
|
3535
|
+
frameState.probeBlendRecordBufferCapacity = 0;
|
|
3536
|
+
frameState.probeBlendBuffers.clear();
|
|
2377
3537
|
frameState.morphBuffers?.clear();
|
|
2378
3538
|
frameState.transientInstanceBuffers = [];
|
|
2379
3539
|
frameState.pointShadowAtlas = null;
|
|
@@ -2441,6 +3601,71 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2441
3601
|
};
|
|
2442
3602
|
}
|
|
2443
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
|
+
|
|
2444
3669
|
// @forgeax/engine-render - built-in record context extension.
|
|
2445
3670
|
//
|
|
2446
3671
|
// The public RenderPipelineContext is canonical in render-contract.ts. This
|