@forgeax/engine-render 0.1.28 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +627 -124
- package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
- package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/dynamic-geometry-host.d.ts +21 -0
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
- package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
- package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +3 -1
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/gpu-driven-owner.d.ts +10 -0
- package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
- package/dist/assembly/host-contract.d.ts +8 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +7 -0
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material-shader-policy.d.ts +3 -1
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/recovery/failure-location.d.ts +12 -0
- package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts +6 -4
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
- package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
- package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-helpers.d.ts +3 -0
- package/dist/assembly/renderer-helpers.d.ts.map +1 -1
- package/dist/assembly/renderer-inspection.d.ts +4 -0
- package/dist/assembly/renderer-inspection.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +6 -0
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
- package/dist/assembly/skin-palette-owner.d.ts +8 -0
- package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
- package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
- package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/webgpu-device-loss.d.ts +15 -0
- package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
- package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +7 -3
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
- package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
- package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
- package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
- package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +7 -141
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
- package/dist/authoring.mjs +2 -2
- package/dist/{chunk-JAASCMXA.mjs → chunk-33XZFTQU.mjs} +245 -39
- package/dist/chunk-33XZFTQU.mjs.map +1 -0
- package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
- package/dist/chunk-BLXGONR2.mjs.map +1 -0
- package/dist/{chunk-J52NMQKR.mjs → chunk-BQVQOQJH.mjs} +1086 -838
- package/dist/chunk-BQVQOQJH.mjs.map +1 -0
- package/dist/{chunk-IQMKJLNU.mjs → chunk-BTWH7BP4.mjs} +386 -17
- package/dist/chunk-BTWH7BP4.mjs.map +1 -0
- package/dist/{chunk-MBU4ZVE7.mjs → chunk-FUODTTIA.mjs} +3921 -671
- package/dist/chunk-FUODTTIA.mjs.map +1 -0
- package/dist/{chunk-MYJ5RPJO.mjs → chunk-JQTSEI3Z.mjs} +3 -3
- package/dist/{chunk-MYJ5RPJO.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
- package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
- package/dist/chunk-NICXSHAT.mjs.map +1 -0
- package/dist/{chunk-J6KACNOH.mjs → chunk-NRNXBLIJ.mjs} +577 -7
- package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
- package/dist/{chunk-RBDBZ5R7.mjs → chunk-PA5DRJNI.mjs} +2 -2
- package/dist/chunk-PA5DRJNI.mjs.map +1 -0
- package/dist/{chunk-VSL23LZE.mjs → chunk-S4DNKQYQ.mjs} +1117 -445
- package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
- package/dist/components/camera.d.ts +62 -2
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/instances.d.ts +6 -20
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/components/scene-instance.d.ts +2 -1
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/screen-space-reflection.d.ts +14 -0
- package/dist/components/screen-space-reflection.d.ts.map +1 -0
- package/dist/construct-renderer.mjs +12888 -7025
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +1 -12
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/background.d.ts +6 -0
- package/dist/environment/background.d.ts.map +1 -0
- package/dist/errors/gpu-driven.d.ts +6 -3
- package/dist/errors/gpu-driven.d.ts.map +1 -1
- package/dist/errors/render.d.ts +7 -4
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +33 -3
- package/dist/extract/gpu-driven.d.ts.map +1 -1
- package/dist/features/host.d.ts +23 -1
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/noise-texture.d.ts +17 -0
- package/dist/features/noise-texture.d.ts.map +1 -0
- package/dist/features/plan.d.ts +83 -2
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +37 -4
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/prepared-graphics-store.d.ts.map +1 -1
- package/dist/features/prepared-graphics.d.ts +5 -0
- package/dist/features/prepared-graphics.d.ts.map +1 -1
- package/dist/features/render-graph-compute.d.ts +5 -3
- package/dist/features/render-graph-compute.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts +11 -3
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/features/render-graph-raster.d.ts +19 -3
- package/dist/features/render-graph-raster.d.ts.map +1 -1
- package/dist/features/types.d.ts +11 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +2 -0
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts +34 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/pbr-program.d.ts +10 -0
- package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +144 -23
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/shadow-views.d.ts +126 -0
- package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
- package/dist/gpu-driven/view-gpu.d.ts +18 -2
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +2 -8
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +14 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-texture-usage.d.ts +1 -0
- package/dist/gpu-texture-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +6 -0
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/frame-resources.d.ts +8 -0
- package/dist/ibl/frame-resources.d.ts.map +1 -0
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +8 -8
- package/dist/inspection-types.d.ts +164 -0
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +11 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -1
- package/dist/instances.d.ts +89 -44
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +117 -9
- package/dist/internal.mjs.map +1 -1
- package/dist/lifecycle.d.ts +2 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/material-row.d.ts +5 -0
- package/dist/material-row.d.ts.map +1 -0
- package/dist/materials.d.ts +15 -0
- package/dist/materials.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +21 -5
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
- package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
- package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
- package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
- package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
- package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
- package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
- package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
- package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
- package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
- package/dist/pipeline/standard-output/graph.d.ts +15 -0
- package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
- package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
- package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
- package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
- package/dist/pipeline/standard-output/resources.d.ts +10 -0
- package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
- package/dist/pipeline/standard-output/types.d.ts +39 -0
- package/dist/pipeline/standard-output/types.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +2 -2
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec-types.d.ts +4 -0
- package/dist/pipeline-spec-types.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +1 -9
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/points-lines/standard-owner.d.ts +1 -1
- package/dist/points-lines/standard-owner.d.ts.map +1 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
- package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
- package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +3 -3
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +106 -5
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +39 -4
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +7 -8
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +38 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +2 -10
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +12 -2
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +15 -6
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/prepared-material-bindings.d.ts +19 -0
- package/dist/record/prepared-material-bindings.d.ts.map +1 -0
- package/dist/record/recovery-pipeline.d.ts +2 -9
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +74 -26
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +62 -7
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +35 -7
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +1 -0
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +10 -2
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/recovery/render-system-roots.d.ts +11 -0
- package/dist/recovery/render-system-roots.d.ts.map +1 -0
- package/dist/recovery/types.d.ts +76 -0
- package/dist/recovery/types.d.ts.map +1 -0
- package/dist/reflection/record-owner.d.ts +0 -2
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +173 -30
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +64 -5
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +7 -5
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +101 -41
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system-projections.d.ts +22 -0
- package/dist/render-system-projections.d.ts.map +1 -0
- package/dist/render-system.d.ts +27 -77
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene-types.d.ts +14 -7
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +29 -11
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/shadow-visibility.d.ts +5 -0
- package/dist/scene/shadow-visibility.d.ts.map +1 -0
- package/dist/ssr/admission.d.ts +70 -1
- package/dist/ssr/admission.d.ts.map +1 -1
- package/dist/ssr/compose.d.ts +13 -0
- package/dist/ssr/compose.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +35 -0
- package/dist/ssr/errors.d.ts.map +1 -1
- package/dist/ssr/graph.d.ts +60 -0
- package/dist/ssr/graph.d.ts.map +1 -0
- package/dist/ssr/history.d.ts +97 -0
- package/dist/ssr/history.d.ts.map +1 -0
- package/dist/ssr/hiz.d.ts +37 -0
- package/dist/ssr/hiz.d.ts.map +1 -0
- package/dist/ssr/inspection.d.ts +42 -0
- package/dist/ssr/inspection.d.ts.map +1 -0
- package/dist/ssr/resources.d.ts +58 -0
- package/dist/ssr/resources.d.ts.map +1 -0
- package/dist/ssr/temporal.d.ts +38 -0
- package/dist/ssr/temporal.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +32 -10
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/systems/skin-palette-types.d.ts +15 -0
- package/dist/systems/skin-palette-types.d.ts.map +1 -1
- package/dist/systems/transparent-dispatch.d.ts +9 -0
- package/dist/systems/transparent-dispatch.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +7 -0
- package/dist/targets/material-source.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts +7 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/gpu.d.ts +8 -0
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/history.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -6
- package/dist/temporal/inspection.d.ts.map +1 -1
- package/dist/temporal/standard-scene-data.d.ts +2 -1
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/temporal-view.d.ts +2 -0
- package/dist/temporal/temporal-view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +35 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +3 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/temporal.d.ts +6 -0
- package/dist/volume/temporal.d.ts.map +1 -1
- package/package.json +21 -20
- package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
- package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
- package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
- package/src/__tests__/auto-exposure.browser.test.ts +33 -0
- package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
- package/src/__tests__/detached-inspection.integration.test.ts +5 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
- package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
- package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
- package/src/__tests__/factory-contract.integration.test.ts +67 -8
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
- package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
- package/src/__tests__/feature-noise.unit.test.ts +38 -0
- package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
- package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
- package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
- package/src/__tests__/frame-targets.unit.test.ts +24 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
- package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
- package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
- package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
- package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
- package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
- package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
- package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
- package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
- package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
- package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
- package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
- package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
- package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
- package/src/__tests__/gpu-scene.unit.test.ts +559 -33
- package/src/__tests__/instances-culling.integration.test.ts +36 -10
- package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
- package/src/__tests__/instances-store.unit.test.ts +242 -39
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
- package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
- package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
- package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
- package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
- package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
- package/src/__tests__/material-texture-source.unit.test.ts +87 -0
- package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
- package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
- package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
- package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
- package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
- package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
- package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
- package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
- package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
- package/src/__tests__/public-surface.test-d.ts +3 -0
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
- package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
- package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
- package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
- package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
- package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
- package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
- package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
- package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
- package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
- package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
- package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
- package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
- package/src/__tests__/render-target-material-source.test-d.ts +27 -3
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +60 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
- package/src/__tests__/shader-manifest-fixture.ts +39 -0
- package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
- package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
- package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
- package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
- package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
- package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
- package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
- package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
- package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
- package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
- package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
- package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
- package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
- package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
- package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
- package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
- package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
- package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
- package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
- package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
- package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
- package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
- package/src/__tests__/ssr-reprojection-probe.ts +90 -0
- package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
- package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
- package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
- package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
- package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
- package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
- package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
- package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
- package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
- package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
- package/src/__tests__/standard-output.browser.test.ts +15 -0
- package/src/__tests__/standard-output.dawn.test.ts +15 -0
- package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
- package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
- package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
- package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
- package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
- package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
- package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
- package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
- package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
- package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
- package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
- package/src/__tests__/taa-stability-feedback.ts +341 -0
- package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
- package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
- package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
- package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
- package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
- package/src/__tests__/visibility-extract.perf.test.ts +20 -8
- package/src/__tests__/visibility-instances.integration.test.ts +19 -7
- package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
- package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
- package/src/assembly/dynamic-geometry-host.ts +48 -0
- package/src/assembly/dynamic-geometry-runtime.ts +71 -0
- package/src/assembly/factory.ts +4 -0
- package/src/assembly/gpu-driven-owner.ts +29 -0
- package/src/assembly/host-contract.ts +10 -0
- package/src/assembly/material/assembly.ts +38 -0
- package/src/assembly/material-shader-policy.ts +71 -28
- package/src/assembly/recovery/failure-location.ts +62 -0
- package/src/assembly/recovery/renderer-recover.ts +19 -17
- package/src/assembly/renderer-facade.ts +12 -0
- package/src/assembly/renderer-feature-inspection.ts +13 -0
- package/src/assembly/renderer-frame-transaction.ts +63 -0
- package/src/assembly/renderer-helpers.ts +26 -0
- package/src/assembly/renderer-inspection.ts +6 -0
- package/src/assembly/shader-prewarm-policy.ts +32 -0
- package/src/assembly/skin-palette-owner.ts +21 -0
- package/src/assembly/ssr-shader-prewarm.ts +41 -0
- package/src/assembly/webgpu-device-loss.ts +75 -0
- package/src/assembly/webgpu-pbr-ready.ts +232 -0
- package/src/assembly/webgpu-ready-mipmap.ts +26 -0
- package/src/assembly/webgpu-ready.ts +142 -121
- package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
- package/src/assembly/webgpu-renderer-contract.ts +69 -0
- package/src/assembly/webgpu-renderer-guards.ts +32 -0
- package/src/assembly/webgpu-renderer-observation.ts +43 -0
- package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
- package/src/assembly/webgpu-renderer.ts +666 -652
- package/src/assembly/webgpu-vertex-layouts.ts +233 -87
- package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
- package/src/components/camera.ts +287 -7
- package/src/components/index.ts +1 -0
- package/src/components/instances.ts +45 -18
- package/src/components/scene-instance.ts +2 -1
- package/src/components/screen-space-reflection.ts +21 -0
- package/src/components/sprite-animation.ts +2 -2
- package/src/components/sprite-instances.ts +1 -1
- package/src/device/gpu-residency.ts +34 -31
- package/src/environment/background.ts +263 -0
- package/src/errors/gpu-driven.ts +39 -5
- package/src/errors/render.ts +27 -3
- package/src/extract/camera.ts +26 -1
- package/src/extract/gpu-driven.ts +185 -10
- package/src/features/host.ts +237 -19
- package/src/features/noise-texture.ts +91 -0
- package/src/features/plan.ts +553 -24
- package/src/features/prepared-gpu-work.ts +631 -49
- package/src/features/prepared-graphics-store.ts +31 -0
- package/src/features/prepared-graphics.ts +5 -0
- package/src/features/render-graph-compute.ts +41 -8
- package/src/features/render-graph-contribution.ts +141 -3
- package/src/features/render-graph-raster.ts +143 -30
- package/src/features/types.ts +11 -0
- package/src/fullscreen-post-process-pass.ts +3 -0
- package/src/gpu-driven/batch-topology.ts +424 -102
- package/src/gpu-driven/pbr-program.ts +31 -0
- package/src/gpu-driven/prepared-draw.ts +29 -4
- package/src/gpu-driven/production-raster.ts +2267 -694
- package/src/gpu-driven/shadow-views.ts +539 -0
- package/src/gpu-driven/view-gpu.ts +188 -41
- package/src/gpu-scene-schema.ts +52 -11
- package/src/gpu-scene.ts +186 -99
- package/src/gpu-texture-usage.ts +1 -0
- package/src/hdrp-buffers.ts +72 -36
- package/src/ibl/frame-resources.ts +38 -0
- package/src/ibl/skylight-bind-group.ts +5 -4
- package/src/index.ts +109 -1
- package/src/inspection-types.ts +191 -0
- package/src/instances-derived-bounds.ts +258 -34
- package/src/instances.ts +459 -150
- package/src/internal.ts +22 -0
- package/src/lifecycle.ts +2 -0
- package/src/material-row.ts +145 -0
- package/src/materials.ts +44 -8
- package/src/pbr-pipeline.ts +159 -17
- package/src/pipeline/standard-forward-lane.ts +52 -24
- package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
- package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
- package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
- package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
- package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
- package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
- package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
- package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
- package/src/pipeline/standard-output/color-transform.ts +124 -0
- package/src/pipeline/standard-output/graph.ts +139 -0
- package/src/pipeline/standard-output/lut-admission.ts +121 -0
- package/src/pipeline/standard-output/lut-gpu.ts +399 -0
- package/src/pipeline/standard-output/lut-state.ts +176 -0
- package/src/pipeline/standard-output/resources.ts +121 -0
- package/src/pipeline/standard-output/types.ts +58 -0
- package/src/pipeline/standard-pipeline.ts +192 -59
- package/src/pipeline/standard-post.ts +186 -27
- package/src/pipeline-builder.ts +16 -6
- package/src/pipeline-spec-types.ts +4 -0
- package/src/pipeline-spec.ts +37 -29
- package/src/points-lines/standard-owner.ts +6 -14
- package/src/prepare/prepared-graphics-resolver.ts +9 -2
- package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
- package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
- package/src/record/dynamic-geometry-consumption.ts +59 -0
- package/src/record/frame-lighting.ts +12 -11
- package/src/record/frame-snapshot.ts +133 -23
- package/src/record/frame-targets.ts +9 -0
- package/src/record/frame.ts +725 -133
- package/src/record/main-pass-geometry.ts +149 -94
- package/src/record/main-pass-material.ts +115 -26
- package/src/record/main-pass-sprite-draws.ts +16 -107
- package/src/record/main-pass.ts +78 -8
- package/src/record/material-uniforms.ts +1 -1
- package/src/record/mesh-ssbo.ts +59 -15
- package/src/record/prepared-material-bindings.ts +234 -0
- package/src/record/recovery-pipeline.ts +59 -136
- package/src/record/render-context.ts +90 -22
- package/src/record/shadow-pass.ts +1231 -209
- package/src/record/skybox-post-pass.ts +14 -3
- package/src/record/typed-frame-graph.ts +1004 -44
- package/src/record/view-ubo.ts +11 -0
- package/src/recovery/render-system-candidate.ts +90 -66
- package/src/recovery/render-system-roots.ts +113 -0
- package/src/recovery/types.ts +77 -0
- package/src/reflection/record-owner.ts +26 -54
- package/src/render-contract.ts +195 -25
- package/src/render-graph-primitives.ts +6 -2
- package/src/render-pipeline.ts +70 -1
- package/src/render-system-extract-tail.ts +409 -160
- package/src/render-system-extract.ts +376 -102
- package/src/render-system-projections.ts +241 -0
- package/src/render-system.ts +681 -822
- package/src/scene/render-scene-types.ts +13 -4
- package/src/scene/render-scene.ts +569 -355
- package/src/scene/shadow-visibility.ts +21 -0
- package/src/ssr/admission.ts +380 -14
- package/src/ssr/compose.ts +144 -0
- package/src/ssr/errors.ts +48 -0
- package/src/ssr/graph.ts +777 -0
- package/src/ssr/history.ts +436 -0
- package/src/ssr/hiz.ts +124 -0
- package/src/ssr/inspection.ts +124 -0
- package/src/ssr/resources.ts +202 -0
- package/src/ssr/temporal.ts +164 -0
- package/src/systems/skin-palette-allocator.ts +495 -148
- package/src/systems/skin-palette-types.ts +17 -0
- package/src/systems/transparent-dispatch.ts +90 -0
- package/src/targets/material-source.ts +34 -0
- package/src/temporal/coverage.ts +1 -1
- package/src/temporal/frame-coordinator.ts +23 -2
- package/src/temporal/gpu.ts +74 -12
- package/src/temporal/history.ts +3 -1
- package/src/temporal/inspection.ts +2 -1
- package/src/temporal/standard-scene-data.ts +3 -1
- package/src/temporal/temporal-view.ts +3 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
- package/src/typed-render-graph-primitives.ts +266 -74
- package/src/typed-shadow-passes.ts +122 -12
- package/src/volume/temporal.ts +10 -0
- package/dist/chunk-GE3SDD72.mjs.map +0 -1
- package/dist/chunk-GF523LHF.mjs.map +0 -1
- package/dist/chunk-IQMKJLNU.mjs.map +0 -1
- package/dist/chunk-J52NMQKR.mjs.map +0 -1
- package/dist/chunk-J6KACNOH.mjs.map +0 -1
- package/dist/chunk-JAASCMXA.mjs.map +0 -1
- package/dist/chunk-MBU4ZVE7.mjs.map +0 -1
- package/dist/chunk-RBDBZ5R7.mjs.map +0 -1
- package/dist/chunk-VSL23LZE.mjs.map +0 -1
- package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
package/src/record/frame.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { uploadMaterialUniforms } from './material-uniforms';
|
|
|
5
5
|
import { HANDLE_NINESLICE_QUAD, resolveAssetHandle } from '@forgeax/engine-assets-runtime';
|
|
6
6
|
import type { World } from '@forgeax/engine-ecs';
|
|
7
7
|
import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
|
|
8
|
-
import {
|
|
8
|
+
import { deriveVertexCount } from '@forgeax/engine-geometry';
|
|
9
|
+
import { frustum, mat4, vec3 } from '@forgeax/engine-math';
|
|
9
10
|
import {
|
|
10
11
|
type CurrentFrameObservationLease,
|
|
11
12
|
createCurrentFrameObservationLease,
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
type TextureView,
|
|
22
23
|
} from '@forgeax/engine-rhi';
|
|
23
24
|
import type { MaterialRenderState, MeshAsset } from '@forgeax/engine-types';
|
|
24
|
-
import { handleSlot, toShared } from '@forgeax/engine-types';
|
|
25
|
+
import { BUILTIN_BASE, handleSlot, toShared } from '@forgeax/engine-types';
|
|
25
26
|
import { CUBE_CAMERA_FACE_ORDER } from '../components/cube-camera';
|
|
26
27
|
import type { DirectionalShadowQuality } from '../components/directional-shadow-filter';
|
|
27
28
|
import type { MeshGpuHandles } from '../device/gpu-residency';
|
|
@@ -30,7 +31,9 @@ import {
|
|
|
30
31
|
type ObservationUnavailableReason,
|
|
31
32
|
RendererOperationError,
|
|
32
33
|
} from '../errors/render';
|
|
34
|
+
import { gpuDrivenDrawKey, gpuDrivenSourceDrawItemIndex } from '../extract/gpu-driven';
|
|
33
35
|
import type { GpuDrivenProduction, PreparedGpuDrivenFrame } from '../gpu-driven/production-raster';
|
|
36
|
+
import type { ShadowViewIdentity } from '../gpu-driven/shadow-views';
|
|
34
37
|
import { GpuBuffer } from '../gpu-resource';
|
|
35
38
|
import { GPU_TEXTURE_USAGE_COPY_SRC } from '../gpu-texture-usage';
|
|
36
39
|
import {
|
|
@@ -56,12 +59,22 @@ import type {
|
|
|
56
59
|
ExtractedVolumetricFog,
|
|
57
60
|
MaterialSnapshot,
|
|
58
61
|
RenderableSnapshot,
|
|
62
|
+
ShadowCasterMembership,
|
|
59
63
|
SkyboxSnapshot,
|
|
60
64
|
SkylightSnapshot,
|
|
61
65
|
} from '../render-system-extract';
|
|
62
|
-
import type {
|
|
66
|
+
import type {
|
|
67
|
+
PersistentGpuDrivenState,
|
|
68
|
+
PersistentShadowCasterProjection,
|
|
69
|
+
} from '../scene/render-scene';
|
|
63
70
|
import type { OcclusionFrameProjection } from '../scene/visibility/occlusion-runtime';
|
|
64
71
|
import { SHADOW_ATLAS_DEFAULT_LAYERS } from '../shadow-atlas';
|
|
72
|
+
import {
|
|
73
|
+
admitSsrSpatial,
|
|
74
|
+
type SsrDependenciesInspection,
|
|
75
|
+
type SsrSpatialAdmission,
|
|
76
|
+
} from '../ssr/admission';
|
|
77
|
+
import { createSsrHistoryOwner } from '../ssr/history';
|
|
65
78
|
import {
|
|
66
79
|
getTransparentSortConfig,
|
|
67
80
|
TRANSPARENT_SORT_MODE_LAYER_Y,
|
|
@@ -123,6 +136,66 @@ export type FrameObservationReadback = (
|
|
|
123
136
|
lease: CurrentFrameObservationLease,
|
|
124
137
|
) => Promise<Result<Uint8Array, Error>>;
|
|
125
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Fold World identity into the renderer-owned validation token. The GPU
|
|
141
|
+
* production owner receives only this opaque key; ECS identity and ordering
|
|
142
|
+
* stay in the record owner where the World list is authoritative.
|
|
143
|
+
*/
|
|
144
|
+
function residencyValidationCacheKey(world: World, worlds: readonly World[]): string {
|
|
145
|
+
return [world.identity, ...worlds.map((candidate) => candidate.identity)].join('\u001f');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function renderableIdentity(snapshot: Pick<RenderableSnapshot, 'worldId' | 'entityKey'>): string {
|
|
149
|
+
return `${snapshot.worldId}:${snapshot.entityKey}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* A GPU-owned indirect draw has no CPU submit callback. Keep the same
|
|
154
|
+
* record-stage consumption proof for downstream lifecycle owners by checking
|
|
155
|
+
* the concrete draw-key projection that was actually encoded for this frame.
|
|
156
|
+
*/
|
|
157
|
+
function gpuDrivenRenderableConsumed(
|
|
158
|
+
entry: ValidatedRenderable,
|
|
159
|
+
drawKeys: ReadonlySet<string>,
|
|
160
|
+
): boolean {
|
|
161
|
+
const draws = entry.source.gpuDrivenDraws;
|
|
162
|
+
if (draws === undefined || draws.length === 0) return false;
|
|
163
|
+
const worldEntity = worldEntityKey(entry.source.worldId, entry.source.entityKey);
|
|
164
|
+
return draws.some((draw, compactIndex) => {
|
|
165
|
+
const material = entry.source.materials[draw.materialSlot] ?? entry.source.material;
|
|
166
|
+
return drawKeys.has(
|
|
167
|
+
gpuDrivenDrawKey(
|
|
168
|
+
worldEntity,
|
|
169
|
+
material.materialHandle ?? -1,
|
|
170
|
+
gpuDrivenSourceDrawItemIndex(draw, compactIndex),
|
|
171
|
+
),
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Project one shared validation result into a cull-specific renderable index
|
|
178
|
+
* domain. The retained ShadowCaster projection is the superset; the primary
|
|
179
|
+
* camera submission reuses its resolved mesh rows without a second residency
|
|
180
|
+
* walk or a second validation authority.
|
|
181
|
+
*/
|
|
182
|
+
function projectValidatedRows(
|
|
183
|
+
validated: readonly ValidatedRenderable[],
|
|
184
|
+
target: readonly RenderableSnapshot[],
|
|
185
|
+
): ValidatedRenderable[] {
|
|
186
|
+
const byIdentity = new Map<string, ValidatedRenderable>();
|
|
187
|
+
for (const row of validated) byIdentity.set(renderableIdentity(row.source), row);
|
|
188
|
+
const projected: ValidatedRenderable[] = [];
|
|
189
|
+
for (let index = 0; index < target.length; index += 1) {
|
|
190
|
+
const snapshot = target[index];
|
|
191
|
+
if (snapshot === undefined) continue;
|
|
192
|
+
const row = byIdentity.get(renderableIdentity(snapshot));
|
|
193
|
+
if (row === undefined) continue;
|
|
194
|
+
projected.push({ ...row, source: snapshot, renderableIndex: index });
|
|
195
|
+
}
|
|
196
|
+
return projected;
|
|
197
|
+
}
|
|
198
|
+
|
|
126
199
|
export interface FrameObservationOptions {
|
|
127
200
|
readonly semantic: 'linear-hdr';
|
|
128
201
|
readonly readback: FrameObservationReadback;
|
|
@@ -219,7 +292,8 @@ export async function observeCurrentFrame(
|
|
|
219
292
|
});
|
|
220
293
|
}
|
|
221
294
|
|
|
222
|
-
import {
|
|
295
|
+
import { acknowledgeRenderFeatureSubmission } from '../features/host';
|
|
296
|
+
import { createTemporalView, resolveTemporalReset, type TemporalView } from '../temporal/view';
|
|
223
297
|
import { inspectVolumetricFog } from '../volume/inspection';
|
|
224
298
|
import {
|
|
225
299
|
acquireSwapChainTarget,
|
|
@@ -244,6 +318,7 @@ import {
|
|
|
244
318
|
type CubeCaptureGraphState,
|
|
245
319
|
ensureCompiledFrameGraph,
|
|
246
320
|
executeCompiledFrameGraph,
|
|
321
|
+
getRenderFeatureGraphState,
|
|
247
322
|
type RenderFeatureGraphCandidate,
|
|
248
323
|
settleVolumetricFogGraphCandidate,
|
|
249
324
|
} from './typed-frame-graph';
|
|
@@ -256,35 +331,34 @@ import {
|
|
|
256
331
|
|
|
257
332
|
/**
|
|
258
333
|
* Resolve the GPU mesh view used by the optional production raster before its
|
|
259
|
-
* filtering pass runs.
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
334
|
+
* filtering pass runs. The result is keyed by the retained RenderScene slot,
|
|
335
|
+
* which preserves the World namespace already used by gpuStore. User meshes
|
|
336
|
+
* are pull-resident during record; relying only on the boot-time builtin map
|
|
337
|
+
* would make the first frame classify every imported mesh as a CPU fallback
|
|
338
|
+
* and would hide its LOD ranges from the GPU cull. Builtins are the only
|
|
339
|
+
* process-static entries and may be read from PipelineState.meshes.
|
|
264
340
|
*/
|
|
265
|
-
function gpuDrivenMeshesForFrame(
|
|
341
|
+
export function gpuDrivenMeshesForFrame(
|
|
266
342
|
internals: RenderSystemInternals,
|
|
267
343
|
pipelineState: PipelineState,
|
|
268
344
|
worlds: readonly World[],
|
|
269
345
|
scene: PersistentGpuDrivenState | undefined,
|
|
270
346
|
): ReadonlyMap<number, MeshGpuHandles> {
|
|
271
|
-
const meshes = new Map(
|
|
347
|
+
const meshes = new Map<number, MeshGpuHandles>();
|
|
272
348
|
if (scene === undefined) return meshes;
|
|
273
|
-
const cached = gpuDrivenMeshCache.get(scene.scene);
|
|
274
|
-
if (
|
|
275
|
-
cached !== undefined &&
|
|
276
|
-
cached.meshResidencyEpoch === internals.gpuStore.meshResidencyEpoch &&
|
|
277
|
-
cached.pipelineMeshes === pipelineState.meshes
|
|
278
|
-
) {
|
|
279
|
-
return cached.value;
|
|
280
|
-
}
|
|
281
349
|
for (const slot of scene.slots) {
|
|
282
|
-
const world = worlds[slot.snapshot.worldId];
|
|
283
|
-
if (world === undefined) continue;
|
|
284
350
|
const assetHandle = slot.snapshot.assetHandle;
|
|
285
351
|
const sharedHandle = toShared<'MeshAsset'>(assetHandle);
|
|
352
|
+
const assetSlot = handleSlot(sharedHandle);
|
|
353
|
+
if (assetSlot < BUILTIN_BASE) {
|
|
354
|
+
const gpu = pipelineState.meshes.get(assetSlot);
|
|
355
|
+
if (gpu !== undefined) meshes.set(slot.slot, gpu);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const world = worlds[slot.snapshot.worldId];
|
|
359
|
+
if (world === undefined) continue;
|
|
286
360
|
let gpu = internals.gpuStore.getMeshGpuHandles(sharedHandle, world);
|
|
287
|
-
if (gpu === undefined
|
|
361
|
+
if (gpu === undefined) {
|
|
288
362
|
const asset = resolveAssetHandle<MeshAsset>(world, sharedHandle);
|
|
289
363
|
if (asset.ok) {
|
|
290
364
|
const lodMeshes = slot.snapshot.lods?.map((lod) =>
|
|
@@ -303,26 +377,11 @@ function gpuDrivenMeshesForFrame(
|
|
|
303
377
|
if (resident.ok) gpu = resident.value;
|
|
304
378
|
}
|
|
305
379
|
}
|
|
306
|
-
if (gpu !== undefined) meshes.set(
|
|
380
|
+
if (gpu !== undefined) meshes.set(slot.slot, gpu);
|
|
307
381
|
}
|
|
308
|
-
|
|
309
|
-
gpuDrivenMeshCache.set(scene.scene, {
|
|
310
|
-
meshResidencyEpoch: internals.gpuStore.meshResidencyEpoch,
|
|
311
|
-
pipelineMeshes: pipelineState.meshes,
|
|
312
|
-
value,
|
|
313
|
-
});
|
|
314
|
-
return value;
|
|
382
|
+
return meshes;
|
|
315
383
|
}
|
|
316
384
|
|
|
317
|
-
const gpuDrivenMeshCache = new WeakMap<
|
|
318
|
-
object,
|
|
319
|
-
{
|
|
320
|
-
readonly meshResidencyEpoch: number;
|
|
321
|
-
readonly pipelineMeshes: ReadonlyMap<number, MeshGpuHandles>;
|
|
322
|
-
readonly value: ReadonlyMap<number, MeshGpuHandles>;
|
|
323
|
-
}
|
|
324
|
-
>();
|
|
325
|
-
|
|
326
385
|
/**
|
|
327
386
|
* A frame-local material table. Repeated snapshot objects share one UBO slot;
|
|
328
387
|
* each renderable keeps only the slot index used by each authored material.
|
|
@@ -392,6 +451,34 @@ export function materialSlotCountForPrefix(
|
|
|
392
451
|
return maxSlot + 1;
|
|
393
452
|
}
|
|
394
453
|
|
|
454
|
+
type MeshSsboCapacityResult = ReturnType<typeof ensureMeshSsboCapacity>;
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Resolve the actual mesh-SSBO ceiling after the shared grow contract ran.
|
|
458
|
+
* A missing grow controller is the legacy/test surface with no renderer-owned
|
|
459
|
+
* ceiling; a failed grow explicitly exposes the pre-grow capacity and must
|
|
460
|
+
* constrain every row written by this frame, including retained shadow rows.
|
|
461
|
+
*/
|
|
462
|
+
function meshSsboSlotCapacityAfterEnsure(
|
|
463
|
+
internals: Pick<RenderSystemInternals, 'growMeshSsbo' | 'meshSsboState'>,
|
|
464
|
+
neededSlots: number,
|
|
465
|
+
result: MeshSsboCapacityResult,
|
|
466
|
+
): number {
|
|
467
|
+
if (internals.growMeshSsbo === undefined) return Number.POSITIVE_INFINITY;
|
|
468
|
+
if (!result.ok) return Math.max(0, result.degradedToSlotCount);
|
|
469
|
+
return internals.meshSsboState?.slotCount ?? neededSlots;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Keep a row sequence inside the capacity published by the mesh-SSBO owner.
|
|
474
|
+
* The caller has already reserved earlier rows (the primary view), so this
|
|
475
|
+
* helper only accepts the remaining slots for the retained shadow sequence.
|
|
476
|
+
*/
|
|
477
|
+
function limitRowsToMeshSsboCapacity<T>(rows: readonly T[], availableSlots: number): readonly T[] {
|
|
478
|
+
if (availableSlots === Number.POSITIVE_INFINITY) return rows;
|
|
479
|
+
return rows.slice(0, Math.max(0, Math.floor(availableSlots)));
|
|
480
|
+
}
|
|
481
|
+
|
|
395
482
|
export type RecordProfileRunner = <T>(phase: RenderRecordPhase, action: () => T) => T;
|
|
396
483
|
|
|
397
484
|
export interface CubeCaptureFrameInput {
|
|
@@ -456,6 +543,18 @@ function prepareCubeCapture(
|
|
|
456
543
|
}
|
|
457
544
|
|
|
458
545
|
const EMPTY_ENTITY_KEYS: ReadonlySet<number> = new Set<number>();
|
|
546
|
+
const EMPTY_DRAW_KEYS: ReadonlySet<string> = new Set<string>();
|
|
547
|
+
|
|
548
|
+
function cacheEntityKeysFromDrawKeys(drawKeys: ReadonlySet<string>): ReadonlySet<number> {
|
|
549
|
+
const entityKeys = new Set<number>();
|
|
550
|
+
for (const key of drawKeys) {
|
|
551
|
+
const separator = key.indexOf(':');
|
|
552
|
+
if (separator <= 0) continue;
|
|
553
|
+
const entityKey = Number(key.slice(0, separator));
|
|
554
|
+
if (Number.isSafeInteger(entityKey)) entityKeys.add(entityKey);
|
|
555
|
+
}
|
|
556
|
+
return entityKeys;
|
|
557
|
+
}
|
|
459
558
|
|
|
460
559
|
function runRecordProfilePhase<T>(
|
|
461
560
|
runner: RecordProfileRunner | undefined,
|
|
@@ -465,6 +564,14 @@ function runRecordProfilePhase<T>(
|
|
|
465
564
|
return runner === undefined ? action() : runner(phase, action);
|
|
466
565
|
}
|
|
467
566
|
|
|
567
|
+
function shadowPlanes(matrix: Float32Array): Float32Array {
|
|
568
|
+
return frustum.fromViewProjection(frustum.create(), matrix as never);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function shadowViewToken(matrix: Float32Array): string {
|
|
572
|
+
return Array.from(matrix).join(',');
|
|
573
|
+
}
|
|
574
|
+
|
|
468
575
|
function halfFloatToNumber(bits: number): number {
|
|
469
576
|
const sign = (bits & 0x8000) === 0 ? 1 : -1;
|
|
470
577
|
const exponent = (bits >>> 10) & 0x1f;
|
|
@@ -1048,6 +1155,157 @@ function stageAcceptedVolumetricFogFrame(
|
|
|
1048
1155
|
return { ...accepted, paramsBuffer };
|
|
1049
1156
|
}
|
|
1050
1157
|
|
|
1158
|
+
function ssrRuntimeError(
|
|
1159
|
+
cause: unknown,
|
|
1160
|
+
): RhiError | { code: string; message: string; name?: string } {
|
|
1161
|
+
if (cause instanceof RhiError) return cause;
|
|
1162
|
+
if (cause instanceof Error)
|
|
1163
|
+
return { code: 'ssr-history-failure', message: cause.message, name: cause.name };
|
|
1164
|
+
return { code: 'ssr-history-failure', message: String(cause) };
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function reportSsrHistoryFailure(
|
|
1168
|
+
internals: RenderSystemInternals,
|
|
1169
|
+
stage: 'build' | 'encode' | 'finish' | 'submit',
|
|
1170
|
+
cause: unknown,
|
|
1171
|
+
): void {
|
|
1172
|
+
internals.errorRegistry.fire(
|
|
1173
|
+
new RhiError({
|
|
1174
|
+
code: 'webgpu-runtime-error',
|
|
1175
|
+
expected: `SSR history ${stage} succeeds before queue submission`,
|
|
1176
|
+
hint: 'inspect the SSR history owner failure and retry the next frame',
|
|
1177
|
+
detail: { error: ssrRuntimeError(cause) },
|
|
1178
|
+
}),
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function mapSsrHistoryResetReason(
|
|
1183
|
+
reason: TemporalView['resetReason'],
|
|
1184
|
+
): import('../ssr/history').SsrHistoryResetReason | undefined {
|
|
1185
|
+
switch (reason) {
|
|
1186
|
+
case 'first-frame':
|
|
1187
|
+
return 'first-enable';
|
|
1188
|
+
case 'resize':
|
|
1189
|
+
return 'resize';
|
|
1190
|
+
case 'camera-cut':
|
|
1191
|
+
case 'view-switch':
|
|
1192
|
+
return 'camera-cut';
|
|
1193
|
+
case 'history-version':
|
|
1194
|
+
return 'history-version';
|
|
1195
|
+
case 'environment-change':
|
|
1196
|
+
case 'fog-change':
|
|
1197
|
+
return 'reflection-generation';
|
|
1198
|
+
case 'device-recover':
|
|
1199
|
+
return 'device-recovery';
|
|
1200
|
+
case 'submit-failure':
|
|
1201
|
+
case undefined:
|
|
1202
|
+
return undefined;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function stageSsrHistoryCandidate(
|
|
1207
|
+
internals: RenderSystemInternals,
|
|
1208
|
+
frameState: RenderFrameState,
|
|
1209
|
+
camera: CameraSnapshot,
|
|
1210
|
+
width: number,
|
|
1211
|
+
height: number,
|
|
1212
|
+
admission: SsrSpatialAdmission,
|
|
1213
|
+
temporalView: TemporalView,
|
|
1214
|
+
): boolean {
|
|
1215
|
+
const previousAdmission = frameState.ssrSpatialAdmission;
|
|
1216
|
+
frameState.ssrSpatialAdmission = admission;
|
|
1217
|
+
if (admission.status !== 'admitted') {
|
|
1218
|
+
if (frameState.ssrHistoryOwner !== undefined && previousAdmission?.status === 'admitted') {
|
|
1219
|
+
const owner = frameState.ssrHistoryOwner;
|
|
1220
|
+
owner.reset('disable');
|
|
1221
|
+
owner.retireAfterFence(internals.device.queue, (cause) => {
|
|
1222
|
+
reportSsrHistoryFailure(internals, 'submit', cause);
|
|
1223
|
+
});
|
|
1224
|
+
frameState.ssrHistoryOwner = undefined;
|
|
1225
|
+
}
|
|
1226
|
+
frameState.ssrHistoryCandidate = undefined;
|
|
1227
|
+
frameState.ssrLastCameraEntity = camera.entityKey;
|
|
1228
|
+
frameState.ssrLastHistoryVersion = camera.historyVersion;
|
|
1229
|
+
return true;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
let owner = frameState.ssrHistoryOwner;
|
|
1233
|
+
if (owner === undefined) {
|
|
1234
|
+
const created = createSsrHistoryOwner({
|
|
1235
|
+
device: internals.device,
|
|
1236
|
+
scope: internals.deviceScope,
|
|
1237
|
+
width,
|
|
1238
|
+
height,
|
|
1239
|
+
});
|
|
1240
|
+
if (!created.ok) {
|
|
1241
|
+
reportSsrHistoryFailure(internals, 'build', created.error);
|
|
1242
|
+
return false;
|
|
1243
|
+
}
|
|
1244
|
+
owner = created.value;
|
|
1245
|
+
frameState.ssrHistoryOwner = owner;
|
|
1246
|
+
} else if (owner.resources.width !== width || owner.resources.height !== height) {
|
|
1247
|
+
const resized = owner.resize(width, height);
|
|
1248
|
+
if (!resized.ok) {
|
|
1249
|
+
reportSsrHistoryFailure(internals, 'build', resized.error);
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1252
|
+
owner.retireAfterFence(internals.device.queue, (cause) => {
|
|
1253
|
+
reportSsrHistoryFailure(internals, 'submit', cause);
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
let resetReason: import('../ssr/history').SsrHistoryResetReason | undefined;
|
|
1258
|
+
if (
|
|
1259
|
+
frameState.ssrLastCameraEntity !== undefined &&
|
|
1260
|
+
frameState.ssrLastCameraEntity !== camera.entityKey
|
|
1261
|
+
) {
|
|
1262
|
+
resetReason = 'camera-cut';
|
|
1263
|
+
} else if (
|
|
1264
|
+
frameState.ssrLastHistoryVersion !== undefined &&
|
|
1265
|
+
frameState.ssrLastHistoryVersion !== camera.historyVersion
|
|
1266
|
+
) {
|
|
1267
|
+
resetReason = 'history-version';
|
|
1268
|
+
} else {
|
|
1269
|
+
resetReason = mapSsrHistoryResetReason(temporalView.resetReason);
|
|
1270
|
+
}
|
|
1271
|
+
if (resetReason !== undefined) owner.reset(resetReason);
|
|
1272
|
+
|
|
1273
|
+
const started = owner.beginFrame();
|
|
1274
|
+
if (!started.ok) {
|
|
1275
|
+
reportSsrHistoryFailure(internals, 'build', started.error);
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
const candidate = started.value;
|
|
1279
|
+
const payload =
|
|
1280
|
+
frameState.ssrTemporalParamsPayload?.byteLength === 32
|
|
1281
|
+
? frameState.ssrTemporalParamsPayload
|
|
1282
|
+
: new Uint8Array(32);
|
|
1283
|
+
payload.fill(0);
|
|
1284
|
+
const values = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
1285
|
+
values.setUint32(0, candidate.historyValid ? 1 : 0, true);
|
|
1286
|
+
values.setFloat32(4, 0.9, true);
|
|
1287
|
+
values.setFloat32(8, admission.config.thickness, true);
|
|
1288
|
+
values.setFloat32(12, 0.5, true);
|
|
1289
|
+
// Reuse the shared successful-submit snapshot; SSR owns no jitter sequence
|
|
1290
|
+
// or independently advancing previous-camera state.
|
|
1291
|
+
const previousJitter = frameState.lastSuccessfulTemporalView?.currentJitterUv;
|
|
1292
|
+
values.setFloat32(16, temporalView.currentJitterUv?.[0] ?? 0, true);
|
|
1293
|
+
values.setFloat32(20, temporalView.currentJitterUv?.[1] ?? 0, true);
|
|
1294
|
+
values.setFloat32(24, previousJitter?.[0] ?? 0, true);
|
|
1295
|
+
values.setFloat32(28, previousJitter?.[1] ?? 0, true);
|
|
1296
|
+
const written = internals.device.queue.writeBuffer(owner.resources.paramsBuffer, 0, payload);
|
|
1297
|
+
if (!written.ok) {
|
|
1298
|
+
owner.abortFrame(candidate, 'encode');
|
|
1299
|
+
reportSsrHistoryFailure(internals, 'encode', written.error);
|
|
1300
|
+
return false;
|
|
1301
|
+
}
|
|
1302
|
+
frameState.ssrTemporalParamsPayload = payload;
|
|
1303
|
+
frameState.ssrHistoryCandidate = candidate;
|
|
1304
|
+
frameState.ssrLastCameraEntity = camera.entityKey;
|
|
1305
|
+
frameState.ssrLastHistoryVersion = camera.historyVersion;
|
|
1306
|
+
return true;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1051
1309
|
function updateVolumetricFogInspection(
|
|
1052
1310
|
frameState: RenderFrameState,
|
|
1053
1311
|
fog: ExtractedVolumetricFog | undefined,
|
|
@@ -1303,7 +1561,7 @@ export function recordFrame(
|
|
|
1303
1561
|
world: World,
|
|
1304
1562
|
cameras: CameraSnapshot[],
|
|
1305
1563
|
lights: ExtractedLights,
|
|
1306
|
-
renderables: RenderableSnapshot[],
|
|
1564
|
+
renderables: readonly RenderableSnapshot[],
|
|
1307
1565
|
transparentDispatch: readonly DispatchEntry[],
|
|
1308
1566
|
frameState: RenderFrameState,
|
|
1309
1567
|
dispatchCounts: DispatchCounts,
|
|
@@ -1339,10 +1597,18 @@ export function recordFrame(
|
|
|
1339
1597
|
fogSignature = '',
|
|
1340
1598
|
environmentReady = true,
|
|
1341
1599
|
transmissionDemand?: TransmissionDemand,
|
|
1600
|
+
shadowCasterEntityKeys: ReadonlySet<number> = EMPTY_ENTITY_KEYS,
|
|
1601
|
+
shadowCasterDrawKeys: ReadonlySet<string> | undefined = undefined,
|
|
1342
1602
|
volumetricFog?: ExtractedVolumetricFog,
|
|
1343
1603
|
timingCapture?: GpuTimingCapture,
|
|
1604
|
+
shadowCasterMembership?: readonly ShadowCasterMembership[],
|
|
1605
|
+
shadowCasterProjection?: PersistentShadowCasterProjection,
|
|
1606
|
+
shadowCasterWorldKeys?: readonly number[],
|
|
1607
|
+
preparedLighting?: ReturnType<typeof prepareFrameLighting>,
|
|
1608
|
+
ssrDependencies?: SsrDependenciesInspection,
|
|
1344
1609
|
onRenderableDraw?: (entry: ValidatedRenderable) => void,
|
|
1345
1610
|
): boolean {
|
|
1611
|
+
void shadowCasterEntityKeys;
|
|
1346
1612
|
frameState.reflectionFallbackObservationSource = undefined;
|
|
1347
1613
|
frameState.reflectionFallbackCompletion = undefined;
|
|
1348
1614
|
// The `try / finally` wrapper advances `frameState.frameNumber` exactly
|
|
@@ -1392,6 +1658,7 @@ export function recordFrame(
|
|
|
1392
1658
|
}
|
|
1393
1659
|
const camera = activeCameras[0];
|
|
1394
1660
|
if (!camera) return false;
|
|
1661
|
+
frameState.ssrRequested = camera.screenSpaceReflection !== undefined;
|
|
1395
1662
|
const pipelineState = internals.getPipelineState();
|
|
1396
1663
|
if (pipelineState === null) return false;
|
|
1397
1664
|
if (pipelineState.device !== internals.device) {
|
|
@@ -1433,8 +1700,12 @@ export function recordFrame(
|
|
|
1433
1700
|
// ensure + ExtractedLights three-arm destructure (directional fallback,
|
|
1434
1701
|
// point/spot/Rect arrays, totalLightCount). Extracted to prepareFrameLighting
|
|
1435
1702
|
// (M3/w18) so recordFrame stays a skeleton.
|
|
1436
|
-
const lighting = runRecordProfilePhase(
|
|
1437
|
-
|
|
1703
|
+
const lighting = runRecordProfilePhase(
|
|
1704
|
+
profilePhase,
|
|
1705
|
+
'record/scene-state/lighting-prep',
|
|
1706
|
+
() =>
|
|
1707
|
+
preparedLighting ??
|
|
1708
|
+
prepareFrameLighting(internals, frameState, lights, camera, pipelineState),
|
|
1438
1709
|
);
|
|
1439
1710
|
if (!lighting.ok) return lighting;
|
|
1440
1711
|
const { light, totalLightCount, standard } = lighting.value;
|
|
@@ -1519,6 +1790,10 @@ export function recordFrame(
|
|
|
1519
1790
|
const effectiveShadowMapSize = resolveShadowMapSize(internals, lights);
|
|
1520
1791
|
const effectiveSpotShadowMapSize = resolveSpotShadowMapSize(internals, lights);
|
|
1521
1792
|
const graphShadowMapSize = effectiveShadowMapSize ?? effectiveSpotShadowMapSize;
|
|
1793
|
+
const hasShadowViews =
|
|
1794
|
+
(effectiveShadowMapSize ?? 0) > 0 ||
|
|
1795
|
+
lights.pointShadow.length > 0 ||
|
|
1796
|
+
lights.spot.some((spot) => spot.castShadow);
|
|
1522
1797
|
const volumeCapability = hasVolumetricFogCapability(
|
|
1523
1798
|
internals.device.caps,
|
|
1524
1799
|
internals.volumetricFogShaders,
|
|
@@ -1579,89 +1854,248 @@ export function recordFrame(
|
|
|
1579
1854
|
(volumetricFog?.status === 'degraded' && acceptedFallback !== undefined);
|
|
1580
1855
|
|
|
1581
1856
|
let preparedGpuDriven: PreparedGpuDrivenFrame | undefined;
|
|
1582
|
-
|
|
1857
|
+
const abortPreparedGpuDriven = (): void => {
|
|
1858
|
+
preparedGpuDriven?._abortResourceReplacement?.();
|
|
1859
|
+
};
|
|
1860
|
+
const cubeCaptureWork = prepareCubeCapture(internals, cubeCapture, camera);
|
|
1861
|
+
const reflectionFallbackCandidate = cubeCapture?.reflectionProbes?.fallbackDemand === true;
|
|
1862
|
+
// The Standard PBR GPU raster owns one color target. Reflection fallback
|
|
1863
|
+
// adds an MRT attachment, so keep this frame on the ordinary CPU producer
|
|
1864
|
+
// instead of claiming rows that the one-target GPU pipeline cannot draw.
|
|
1865
|
+
const frameGpuDriven = reflectionFallbackCandidate ? undefined : gpuDriven;
|
|
1866
|
+
|
|
1867
|
+
// Validate first so user meshes are resident in the same frame. The GPU
|
|
1868
|
+
// owner consumes this producer-owned map together with the builtin mesh
|
|
1869
|
+
// entries; it never resolves an asset or creates a fallback residency.
|
|
1870
|
+
// Stable persistent-scene frames reuse this exact producer result. The
|
|
1871
|
+
// cache is fenced by source identities plus mesh/device/pipeline/catalog
|
|
1872
|
+
// generations, so recovery or any scene/resource replacement performs a
|
|
1873
|
+
// fresh owner validation before a capable submission.
|
|
1874
|
+
const shadowValidationActive = hasShadowViews && shadowCasterProjection !== undefined;
|
|
1875
|
+
const validationRenderables = shadowValidationActive
|
|
1876
|
+
? shadowCasterProjection.renderables
|
|
1877
|
+
: renderables;
|
|
1878
|
+
const validationDispatch = shadowValidationActive
|
|
1879
|
+
? shadowCasterProjection.dispatch
|
|
1880
|
+
: transparentDispatch;
|
|
1881
|
+
const validationInput = {
|
|
1882
|
+
cacheKey: residencyValidationCacheKey(world, worlds),
|
|
1883
|
+
renderables: validationRenderables,
|
|
1884
|
+
transparentDispatch: validationDispatch,
|
|
1885
|
+
pipelineState,
|
|
1886
|
+
pipelineHandle: frameState.installedPipelineHandle,
|
|
1887
|
+
meshResidencyEpoch: internals.gpuStore.meshResidencyEpoch,
|
|
1888
|
+
deviceGeneration: internals.deviceScope.generation,
|
|
1889
|
+
assetCatalogEpoch: internals.assets.catalogEpoch,
|
|
1890
|
+
} as const;
|
|
1891
|
+
const cachedValidation = frameGpuDriven?.owner.reuseResidencyValidation(validationInput);
|
|
1892
|
+
const validatedProjection =
|
|
1893
|
+
cachedValidation ??
|
|
1894
|
+
runRecordProfilePhase(profilePhase, 'record/validation', () =>
|
|
1895
|
+
validateRenderables(
|
|
1896
|
+
internals,
|
|
1897
|
+
world,
|
|
1898
|
+
worlds,
|
|
1899
|
+
pipelineState,
|
|
1900
|
+
frameState,
|
|
1901
|
+
validationRenderables,
|
|
1902
|
+
validationDispatch,
|
|
1903
|
+
EMPTY_ENTITY_KEYS,
|
|
1904
|
+
),
|
|
1905
|
+
);
|
|
1906
|
+
if (cachedValidation === undefined) {
|
|
1907
|
+
frameGpuDriven?.owner.rememberResidencyValidation(validationInput, validatedProjection);
|
|
1908
|
+
}
|
|
1909
|
+
// Camera culling only changes the primary index domain. When shadows are
|
|
1910
|
+
// active, the retained projection is the validation superset; project the
|
|
1911
|
+
// same resolved rows into the camera-visible subset without another walk.
|
|
1912
|
+
const validatedShadowForResidency = shadowValidationActive ? validatedProjection : undefined;
|
|
1913
|
+
const validatedForResidency = shadowValidationActive
|
|
1914
|
+
? projectValidatedRows(validatedProjection, renderables)
|
|
1915
|
+
: validatedProjection;
|
|
1916
|
+
|
|
1917
|
+
if (frameGpuDriven !== undefined) {
|
|
1918
|
+
const gpuDrivenMeshes = new Map(
|
|
1919
|
+
gpuDrivenMeshesForFrame(internals, pipelineState, worlds, frameGpuDriven.scene),
|
|
1920
|
+
);
|
|
1921
|
+
const meshSlotByIdentity = new Map(
|
|
1922
|
+
(frameGpuDriven.scene?.slots ?? []).map((slot) => [
|
|
1923
|
+
worldEntityKey(slot.snapshot.worldId, slot.snapshot.entityKey),
|
|
1924
|
+
slot.slot,
|
|
1925
|
+
]),
|
|
1926
|
+
);
|
|
1927
|
+
for (const row of [...validatedForResidency, ...(validatedShadowForResidency ?? [])]) {
|
|
1928
|
+
const slot = meshSlotByIdentity.get(
|
|
1929
|
+
worldEntityKey(row.source.worldId, row.source.entityKey),
|
|
1930
|
+
);
|
|
1931
|
+
if (slot !== undefined) gpuDrivenMeshes.set(slot, row.mesh);
|
|
1932
|
+
}
|
|
1583
1933
|
const standardPbrArtifact = internals.getMaterialShaderArtifact?.(
|
|
1584
1934
|
'forgeax::default-standard-pbr',
|
|
1585
1935
|
);
|
|
1586
|
-
const
|
|
1587
|
-
|
|
1936
|
+
const standardPbrSkinArtifact = internals.getMaterialShaderArtifact?.('forgeax::pbr-skin');
|
|
1937
|
+
const paletteUploadBytes = validatedForResidency.reduce(
|
|
1938
|
+
(total, row) => total + (row.source.skin?.uploadBytes ?? 0),
|
|
1939
|
+
0,
|
|
1940
|
+
);
|
|
1941
|
+
const prepared = frameGpuDriven.owner.prepare({
|
|
1942
|
+
scene: frameGpuDriven.scene,
|
|
1588
1943
|
camera,
|
|
1589
|
-
|
|
1944
|
+
meshBySlot: gpuDrivenMeshes,
|
|
1590
1945
|
viewBindGroupLayout: pipelineState.viewBindGroupLayout,
|
|
1591
1946
|
meshResidencyEpoch: internals.gpuStore.meshResidencyEpoch,
|
|
1592
|
-
//
|
|
1593
|
-
//
|
|
1594
|
-
//
|
|
1595
|
-
//
|
|
1596
|
-
hdrp:
|
|
1597
|
-
|
|
1947
|
+
// GPU-driven Standard PBR currently owns the non-clustered path only.
|
|
1948
|
+
// Cluster bindings also carry screen-space ambient occlusion. Admit
|
|
1949
|
+
// the scene-index variant only when none of those surface inputs are
|
|
1950
|
+
// required; a zero local-light count alone does not establish that.
|
|
1951
|
+
hdrp:
|
|
1952
|
+
standard.kind === 'clustered' &&
|
|
1953
|
+
(standard.prepared.local.length > 0 ||
|
|
1954
|
+
frameState.installedPipelineConfig?.ssao?.enabled === true),
|
|
1955
|
+
...(frameGpuDriven.activeEntityKeys === undefined
|
|
1956
|
+
? {}
|
|
1957
|
+
: { activeEntityKeys: frameGpuDriven.activeEntityKeys }),
|
|
1958
|
+
...(frameGpuDriven.activeEntityRevision === undefined
|
|
1598
1959
|
? {}
|
|
1599
|
-
: {
|
|
1600
|
-
...(
|
|
1960
|
+
: { activeEntityRevision: frameGpuDriven.activeEntityRevision }),
|
|
1961
|
+
...(frameGpuDriven.telemetryCandidateCount === undefined
|
|
1601
1962
|
? {}
|
|
1602
|
-
: {
|
|
1603
|
-
...(
|
|
1963
|
+
: { telemetryCandidateCount: frameGpuDriven.telemetryCandidateCount }),
|
|
1964
|
+
...(frameGpuDriven.occlusion === undefined ? {} : { occlusion: frameGpuDriven.occlusion }),
|
|
1965
|
+
...(frameGpuDriven.scene?.structureMetrics === undefined
|
|
1604
1966
|
? {}
|
|
1605
|
-
: {
|
|
1606
|
-
|
|
1967
|
+
: {
|
|
1968
|
+
structureMetrics: {
|
|
1969
|
+
...frameGpuDriven.scene.structureMetrics,
|
|
1970
|
+
paletteUploadBytes,
|
|
1971
|
+
},
|
|
1972
|
+
}),
|
|
1607
1973
|
...(standardPbrArtifact === undefined ? {} : { standardPbrArtifact }),
|
|
1974
|
+
...(standardPbrSkinArtifact === undefined ? {} : { standardPbrSkinArtifact }),
|
|
1608
1975
|
standardPbrPipelineState: pipelineState,
|
|
1976
|
+
...(shadowCasterDrawKeys === undefined ? {} : { shadowCasterDrawKeys }),
|
|
1977
|
+
...(shadowCasterMembership === undefined ? {} : { shadowCasterMembership }),
|
|
1609
1978
|
});
|
|
1610
|
-
if (prepared.ok)
|
|
1611
|
-
|
|
1979
|
+
if (!prepared.ok) {
|
|
1980
|
+
// A capable GPU-owned candidate with missing producer resources is a
|
|
1981
|
+
// structured stop, not permission to enumerate the same draw through
|
|
1982
|
+
// the CPU semantic path. Preserve the last-known-good graph and retry
|
|
1983
|
+
// after the producer publishes a validated artifact.
|
|
1612
1984
|
internals.errorRegistry.fire(prepared.error);
|
|
1985
|
+
abortPreparedGpuDriven();
|
|
1986
|
+
return false;
|
|
1987
|
+
} else if (prepared.value !== undefined) {
|
|
1988
|
+
preparedGpuDriven = prepared.value;
|
|
1989
|
+
const shadowViews: Array<{
|
|
1990
|
+
readonly identity: ShadowViewIdentity;
|
|
1991
|
+
readonly planes: Float32Array;
|
|
1992
|
+
readonly cacheToken: string;
|
|
1993
|
+
}> = [];
|
|
1994
|
+
for (const [index, matrix] of (lights.lightViewProj ?? []).entries()) {
|
|
1995
|
+
shadowViews.push({
|
|
1996
|
+
identity: { kind: 'directional', index },
|
|
1997
|
+
planes: shadowPlanes(matrix),
|
|
1998
|
+
cacheToken: `${shadowViewToken(matrix)}|target:${graphShadowMapSize}:${frameState.graphGeneration}`,
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
for (const [index, point] of lights.pointShadow.entries()) {
|
|
2002
|
+
for (let face = 0; face < 6; face += 1) {
|
|
2003
|
+
const matrix = point.shadowMatrices.subarray(face * 16, face * 16 + 16);
|
|
2004
|
+
shadowViews.push({
|
|
2005
|
+
identity: { kind: 'point', index, face },
|
|
2006
|
+
planes: shadowPlanes(matrix),
|
|
2007
|
+
cacheToken: `${shadowViewToken(matrix)}|target:${graphShadowMapSize}:${frameState.graphGeneration}`,
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
let shadowSpotIndex = 0;
|
|
2012
|
+
for (const spot of lights.spot) {
|
|
2013
|
+
if (spot.lightViewProj === undefined) continue;
|
|
2014
|
+
shadowViews.push({
|
|
2015
|
+
identity: { kind: 'spot', index: shadowSpotIndex },
|
|
2016
|
+
planes: shadowPlanes(spot.lightViewProj),
|
|
2017
|
+
cacheToken: `${shadowViewToken(spot.lightViewProj)}|target:${graphShadowMapSize}:${frameState.graphGeneration}`,
|
|
2018
|
+
});
|
|
2019
|
+
shadowSpotIndex += 1;
|
|
2020
|
+
}
|
|
2021
|
+
if (prepared.value.updateShadowViews !== undefined) {
|
|
2022
|
+
const shadowUpdate = prepared.value.updateShadowViews(shadowViews);
|
|
2023
|
+
if (!shadowUpdate.ok) {
|
|
2024
|
+
internals.errorRegistry.fire(shadowUpdate.error);
|
|
2025
|
+
abortPreparedGpuDriven();
|
|
2026
|
+
return false;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
1613
2030
|
}
|
|
1614
2031
|
|
|
2032
|
+
const gpuOwnedDrawKeys = preparedGpuDriven?.drawKeys ?? EMPTY_DRAW_KEYS;
|
|
1615
2033
|
// The GPU lane owns only the primary opaque raster today. Directional,
|
|
1616
|
-
// point, and spot shadow views
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
const gpuOwnedEntityKeys = preparedGpuDriven?.entityKeys ?? EMPTY_ENTITY_KEYS;
|
|
1632
|
-
const gpuDrivenWorldKeys = gpuDriven?.scene?.worldKeys;
|
|
1633
|
-
const validated = runRecordProfilePhase(profilePhase, 'record/validation', () =>
|
|
1634
|
-
gpuOwnsAllPrimaryRaster && !cpuShadowCastersActive && cubeCaptureWork.length === 0
|
|
1635
|
-
? []
|
|
1636
|
-
: validateRenderables(
|
|
1637
|
-
internals,
|
|
1638
|
-
world,
|
|
1639
|
-
worlds,
|
|
1640
|
-
pipelineState,
|
|
1641
|
-
frameState,
|
|
1642
|
-
renderables,
|
|
1643
|
-
transparentDispatch,
|
|
1644
|
-
cpuShadowCastersActive || cubeCaptureWork.length > 0
|
|
1645
|
-
? EMPTY_ENTITY_KEYS
|
|
1646
|
-
: gpuOwnedEntityKeys,
|
|
1647
|
-
gpuDrivenWorldKeys,
|
|
1648
|
-
),
|
|
2034
|
+
// point, and spot shadow views consume the same CPU-validated rows; they do
|
|
2035
|
+
// not require a second residency walk because the first validation already
|
|
2036
|
+
// resolved every renderable used by both lanes.
|
|
2037
|
+
// The GPU lane owns the actual geometry, but the main-pass material
|
|
2038
|
+
// producer still needs the validated snapshot rows to build the
|
|
2039
|
+
// frame-global bind-group table consumed by indirect draws. The record
|
|
2040
|
+
// loop skips claimed submeshes through `gpuDrivenDrawKeys`, so retaining
|
|
2041
|
+
// these rows does not issue duplicate CPU geometry. Reusing this one
|
|
2042
|
+
// validation result also keeps active shadow views from repeating the
|
|
2043
|
+
// residency walk.
|
|
2044
|
+
const validated = validatedForResidency;
|
|
2045
|
+
frameGpuDriven?.owner.recordCpuValidation(
|
|
2046
|
+
validated,
|
|
2047
|
+
gpuOwnedDrawKeys,
|
|
2048
|
+
validationInput.cacheKey,
|
|
1649
2049
|
);
|
|
1650
|
-
|
|
1651
|
-
|
|
2050
|
+
const cacheValidated =
|
|
2051
|
+
validatedShadowForResidency === undefined
|
|
2052
|
+
? validated
|
|
2053
|
+
: [...validated, ...validatedShadowForResidency];
|
|
2054
|
+
cleanPerFrameCaches(
|
|
2055
|
+
internals,
|
|
2056
|
+
frameState,
|
|
2057
|
+
cacheValidated,
|
|
2058
|
+
cacheEntityKeysFromDrawKeys(gpuOwnedDrawKeys),
|
|
2059
|
+
);
|
|
2060
|
+
|
|
2061
|
+
// Compile the single frame graph only after GPU preparation has projected
|
|
2062
|
+
// the current frame. This keeps the graph topology and its execution
|
|
2063
|
+
// closure on the same prepared GPU-owned draw set.
|
|
1652
2064
|
|
|
1653
2065
|
// S_fallback is the second output of the built-in Standard PBR BRDF. A
|
|
1654
2066
|
// graph may still carry the detached owner tickets for inspection, but
|
|
1655
2067
|
// the MRT is admitted only when every CPU-rendered primary row in this
|
|
1656
2068
|
// frame uses that exact output contract. Unsupported compositions remain
|
|
1657
2069
|
// fail-closed for SSR instead of submitting a device-invalid command.
|
|
1658
|
-
|
|
1659
|
-
reflectionFallbackCandidate &&
|
|
2070
|
+
const sceneInputs =
|
|
1660
2071
|
validated.length > 0 &&
|
|
1661
2072
|
validated.every(
|
|
1662
2073
|
(entry) => entry.source.material.materialShaderId === 'forgeax::default-standard-pbr',
|
|
1663
2074
|
);
|
|
1664
|
-
|
|
2075
|
+
const ssrAdmission =
|
|
2076
|
+
ssrDependencies === undefined
|
|
2077
|
+
? undefined
|
|
2078
|
+
: admitSsrSpatial({
|
|
2079
|
+
camera,
|
|
2080
|
+
environment: {
|
|
2081
|
+
lane: standard.prepared.renderPath === 'deferred' ? 'deferred' : 'forward',
|
|
2082
|
+
m0: ssrDependencies.admission,
|
|
2083
|
+
sceneInputs,
|
|
2084
|
+
// Standard geometry produces current motion/coverage in this
|
|
2085
|
+
// graph, including its first frame. Prior history is optional.
|
|
2086
|
+
temporal: sceneInputs,
|
|
2087
|
+
reflectionFallback: ssrDependencies.reflectionFallback !== undefined,
|
|
2088
|
+
capabilities: {
|
|
2089
|
+
compute: internals.device.caps.compute,
|
|
2090
|
+
storageTexture: internals.device.caps.storageTexture,
|
|
2091
|
+
rgba16floatRenderable: internals.device.caps.rgba16floatRenderable,
|
|
2092
|
+
r32floatSampledStorage: ssrDependencies.format?.verdict === 'admitted',
|
|
2093
|
+
},
|
|
2094
|
+
recovery: true,
|
|
2095
|
+
},
|
|
2096
|
+
});
|
|
2097
|
+
frameState.reflectionFallbackDemand =
|
|
2098
|
+
(reflectionFallbackCandidate || ssrAdmission?.status === 'admitted') && sceneInputs;
|
|
1665
2099
|
const preflightGraph = runRecordProfilePhase(profilePhase, 'record/render-graph', () =>
|
|
1666
2100
|
ensureCompiledFrameGraph(
|
|
1667
2101
|
internals,
|
|
@@ -1677,13 +2111,18 @@ export function recordFrame(
|
|
|
1677
2111
|
cubeCapture?.state,
|
|
1678
2112
|
cameras.length === 0,
|
|
1679
2113
|
transmissionDemand,
|
|
1680
|
-
|
|
2114
|
+
frameGpuDriven?.occlusion,
|
|
1681
2115
|
graphVolumetricFog,
|
|
1682
2116
|
volumeTopologyCandidate,
|
|
1683
2117
|
standard,
|
|
2118
|
+
undefined,
|
|
2119
|
+
ssrAdmission,
|
|
1684
2120
|
),
|
|
1685
2121
|
);
|
|
1686
|
-
if (preflightGraph === null)
|
|
2122
|
+
if (preflightGraph === null) {
|
|
2123
|
+
abortPreparedGpuDriven();
|
|
2124
|
+
return false;
|
|
2125
|
+
}
|
|
1687
2126
|
|
|
1688
2127
|
// The compiled graph is the acceptance boundary for the prepared
|
|
1689
2128
|
// Standard lighting declaration. Upload the same declaration only after
|
|
@@ -1704,6 +2143,7 @@ export function recordFrame(
|
|
|
1704
2143
|
},
|
|
1705
2144
|
}),
|
|
1706
2145
|
);
|
|
2146
|
+
abortPreparedGpuDriven();
|
|
1707
2147
|
return false;
|
|
1708
2148
|
}
|
|
1709
2149
|
if (standard.kind === 'clustered') {
|
|
@@ -1732,6 +2172,7 @@ export function recordFrame(
|
|
|
1732
2172
|
);
|
|
1733
2173
|
if (!clusterWrite.ok) {
|
|
1734
2174
|
internals.errorRegistry.fire(clusterWrite.error);
|
|
2175
|
+
abortPreparedGpuDriven();
|
|
1735
2176
|
return false;
|
|
1736
2177
|
}
|
|
1737
2178
|
}
|
|
@@ -1744,7 +2185,10 @@ export function recordFrame(
|
|
|
1744
2185
|
const swapTarget = runRecordProfilePhase(profilePhase, 'record/swapchain', () =>
|
|
1745
2186
|
acquireSwapChainTarget(internals, pipelineState),
|
|
1746
2187
|
);
|
|
1747
|
-
if (swapTarget === null)
|
|
2188
|
+
if (swapTarget === null) {
|
|
2189
|
+
abortPreparedGpuDriven();
|
|
2190
|
+
return false;
|
|
2191
|
+
}
|
|
1748
2192
|
const currentTexture = swapTarget.currentTexture;
|
|
1749
2193
|
const view = swapTarget.view;
|
|
1750
2194
|
const targetW = swapTarget.targetW;
|
|
@@ -1802,6 +2246,23 @@ export function recordFrame(
|
|
|
1802
2246
|
: { kind: 'off', view: temporalView };
|
|
1803
2247
|
const recordCamera: CameraSnapshot =
|
|
1804
2248
|
temporalView.mode === 'taa' ? { ...camera, temporal: temporalView } : camera;
|
|
2249
|
+
if (ssrAdmission !== undefined) {
|
|
2250
|
+
const historyWidth = Math.max(1, internals.canvas.width);
|
|
2251
|
+
const historyHeight = Math.max(1, internals.canvas.height);
|
|
2252
|
+
if (
|
|
2253
|
+
!stageSsrHistoryCandidate(
|
|
2254
|
+
internals,
|
|
2255
|
+
frameState,
|
|
2256
|
+
camera,
|
|
2257
|
+
historyWidth,
|
|
2258
|
+
historyHeight,
|
|
2259
|
+
ssrAdmission,
|
|
2260
|
+
temporalView,
|
|
2261
|
+
)
|
|
2262
|
+
) {
|
|
2263
|
+
return false;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
1805
2266
|
let activeVolumetricFogContext: VolumetricFogFrameContext | undefined;
|
|
1806
2267
|
let activeVolumetricFogSignature: VolumeTemporalSignature | undefined;
|
|
1807
2268
|
if (graphVolumetricFog?.status === 'available' && baseVolumetricFogContext !== undefined) {
|
|
@@ -1828,6 +2289,7 @@ export function recordFrame(
|
|
|
1828
2289
|
);
|
|
1829
2290
|
if (!historyFlagWritten.ok) {
|
|
1830
2291
|
internals.errorRegistry.fire(historyFlagWritten.error);
|
|
2292
|
+
abortPreparedGpuDriven();
|
|
1831
2293
|
return false;
|
|
1832
2294
|
}
|
|
1833
2295
|
}
|
|
@@ -1903,9 +2365,18 @@ export function recordFrame(
|
|
|
1903
2365
|
// order, run the mesh-SSBO capacity gate (graceful truncation), and build
|
|
1904
2366
|
// the fold dispatch plan. Extracted to buildDispatchPlan (M3/w18).
|
|
1905
2367
|
const dispatchPlan = runRecordProfilePhase(profilePhase, 'record/dispatch-plan', () =>
|
|
1906
|
-
buildDispatchPlan(
|
|
2368
|
+
buildDispatchPlan(
|
|
2369
|
+
internals,
|
|
2370
|
+
validated,
|
|
2371
|
+
transparentDispatch,
|
|
2372
|
+
foldBuckets,
|
|
2373
|
+
validatedShadowForResidency ?? [],
|
|
2374
|
+
),
|
|
1907
2375
|
);
|
|
1908
2376
|
const validatedOrdered = dispatchPlan.validatedOrdered;
|
|
2377
|
+
const shadowValidatedOrdered = dispatchPlan.shadowValidatedOrdered;
|
|
2378
|
+
const shadowMeshSsboBase = validatedOrdered.length;
|
|
2379
|
+
const shadowDispatch = shadowCasterProjection?.dispatch;
|
|
1909
2380
|
const foldDispatchPlan = dispatchPlan.foldDispatchPlan;
|
|
1910
2381
|
const materialSlotIndices = dispatchPlan.materialSlotIndices;
|
|
1911
2382
|
const materialSlots = dispatchPlan.materialSlots;
|
|
@@ -1937,12 +2408,13 @@ export function recordFrame(
|
|
|
1937
2408
|
const unormViewRes = internals.device.createTextureView(currentTexture, {});
|
|
1938
2409
|
if (!unormViewRes.ok) {
|
|
1939
2410
|
internals.errorRegistry.fire(unormViewRes.error);
|
|
2411
|
+
abortPreparedGpuDriven();
|
|
1940
2412
|
return false;
|
|
1941
2413
|
}
|
|
1942
2414
|
ldrSpritePassView = unormViewRes.value;
|
|
1943
2415
|
}
|
|
1944
2416
|
|
|
1945
|
-
// View / mesh uniform uploads are
|
|
2417
|
+
// View / mesh uniform uploads are needed when geometry or a feature consumes View,
|
|
1946
2418
|
// OR when a skybox is active (skybox pass reads inverseViewProj from
|
|
1947
2419
|
// the View UBO). Skip the writeBuffer round-trips on the Case E
|
|
1948
2420
|
// (clear-pass-only) path only when neither condition is met.
|
|
@@ -1957,7 +2429,10 @@ export function recordFrame(
|
|
|
1957
2429
|
);
|
|
1958
2430
|
if (
|
|
1959
2431
|
validatedOrdered.length > 0 ||
|
|
2432
|
+
shadowValidatedOrdered.length > 0 ||
|
|
2433
|
+
getRenderFeatureGraphState(internals).plans.some(({ plan }) => plan.passes.length > 0) ||
|
|
1960
2434
|
skyboxActive ||
|
|
2435
|
+
frameState.environmentFrame?.source.kind === 'atmosphere' ||
|
|
1961
2436
|
activeVolumetricFogContext !== undefined ||
|
|
1962
2437
|
preparedVolumetricFog !== undefined
|
|
1963
2438
|
) {
|
|
@@ -2000,6 +2475,15 @@ export function recordFrame(
|
|
|
2000
2475
|
validatedOrdered,
|
|
2001
2476
|
foldDispatchPlan,
|
|
2002
2477
|
);
|
|
2478
|
+
if (shadowValidatedOrdered.length > 0) {
|
|
2479
|
+
uploadMeshSsboBatch(
|
|
2480
|
+
internals.device.queue,
|
|
2481
|
+
pipelineState.meshStorageBuffer,
|
|
2482
|
+
shadowValidatedOrdered,
|
|
2483
|
+
null,
|
|
2484
|
+
shadowMeshSsboBase,
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2003
2487
|
});
|
|
2004
2488
|
}
|
|
2005
2489
|
|
|
@@ -2022,6 +2506,7 @@ export function recordFrame(
|
|
|
2022
2506
|
const encoderResult = internals.device.createCommandEncoder({ label: 'render-system-frame' });
|
|
2023
2507
|
if (!encoderResult.ok) {
|
|
2024
2508
|
internals.errorRegistry.fire(encoderResult.error);
|
|
2509
|
+
abortPreparedGpuDriven();
|
|
2025
2510
|
return false;
|
|
2026
2511
|
}
|
|
2027
2512
|
const encoder: RhiCommandEncoder = encoderResult.value;
|
|
@@ -2100,8 +2585,6 @@ export function recordFrame(
|
|
|
2100
2585
|
const passCtx: _InternalRenderPipelineContext = {
|
|
2101
2586
|
assets: internals.assets,
|
|
2102
2587
|
world,
|
|
2103
|
-
gpuDrivenEntityKeys: gpuOwnedEntityKeys,
|
|
2104
|
-
...(gpuDrivenWorldKeys === undefined ? {} : { gpuDrivenWorldKeys }),
|
|
2105
2588
|
store: internals.gpuStore,
|
|
2106
2589
|
pipelineState,
|
|
2107
2590
|
bloomResources: pipelineState.perPassResources.getBloomResources?.() ?? null,
|
|
@@ -2137,6 +2620,10 @@ export function recordFrame(
|
|
|
2137
2620
|
...(internals.volumetricFogShaders === undefined
|
|
2138
2621
|
? {}
|
|
2139
2622
|
: { volumetricFogShaders: internals.volumetricFogShaders }),
|
|
2623
|
+
...(internals.ssrShaders === undefined ? {} : { ssrShaders: internals.ssrShaders }),
|
|
2624
|
+
...(internals.atmosphereShaders === undefined
|
|
2625
|
+
? {}
|
|
2626
|
+
: { atmosphereShaders: internals.atmosphereShaders }),
|
|
2140
2627
|
...(activeVolumetricFogContext === undefined
|
|
2141
2628
|
? {}
|
|
2142
2629
|
: { volumetricFog: activeVolumetricFogContext }),
|
|
@@ -2146,13 +2633,26 @@ export function recordFrame(
|
|
|
2146
2633
|
dispatch: transparentDispatch,
|
|
2147
2634
|
hdrpClusterBindGroup,
|
|
2148
2635
|
hdrpClusterMembershipBindGroup,
|
|
2149
|
-
...(
|
|
2636
|
+
...(frameGpuDriven?.occlusion === undefined ? {} : { occlusion: frameGpuDriven.occlusion }),
|
|
2150
2637
|
standardLighting: standard,
|
|
2151
2638
|
foldDispatchPlan,
|
|
2152
2639
|
materialSlotIndices,
|
|
2153
2640
|
materialSlots,
|
|
2154
2641
|
materialSlotOwners,
|
|
2155
2642
|
materialSlotCount,
|
|
2643
|
+
...(preparedGpuDriven === undefined
|
|
2644
|
+
? {}
|
|
2645
|
+
: {
|
|
2646
|
+
gpuDrivenStandardPbrFrameResources: preparedGpuDriven.standardPbrFrameResources,
|
|
2647
|
+
gpuDrivenDrawKeys: preparedGpuDriven.drawKeys,
|
|
2648
|
+
gpuDrivenShadowViews: preparedGpuDriven.shadowViewPool,
|
|
2649
|
+
gpuDrivenShadowBatchProjections: preparedGpuDriven.shadowBatchProjections,
|
|
2650
|
+
gpuDrivenShadowDrawKeys: preparedGpuDriven.shadowDrawKeys,
|
|
2651
|
+
gpuDrivenShadowDrawKeysByView: preparedGpuDriven.shadowDrawKeysByView,
|
|
2652
|
+
}),
|
|
2653
|
+
...(frameGpuDriven?.scene === undefined
|
|
2654
|
+
? {}
|
|
2655
|
+
: { gpuDrivenSceneMaterialBuffer: frameGpuDriven.scene.scene.materialBuffer }),
|
|
2156
2656
|
...(cubeCapture?.reflectionProbes === undefined
|
|
2157
2657
|
? {}
|
|
2158
2658
|
: { reflectionProbes: cubeCapture.reflectionProbes }),
|
|
@@ -2160,6 +2660,12 @@ export function recordFrame(
|
|
|
2160
2660
|
materialBgAssemblyCache: frameState.materialBgAssemblyCache,
|
|
2161
2661
|
...(onRenderableDraw === undefined ? {} : { onRenderableDraw }),
|
|
2162
2662
|
directionalShadowCacheReuse: directionalShadowCache.reuse,
|
|
2663
|
+
...(shadowCasterMembership === undefined ? {} : { shadowCasterMembership }),
|
|
2664
|
+
...(shadowValidatedOrdered.length === 0
|
|
2665
|
+
? {}
|
|
2666
|
+
: { shadowValidatedOrdered, shadowMeshSsboBase }),
|
|
2667
|
+
...(shadowDispatch === undefined ? {} : { shadowDispatch }),
|
|
2668
|
+
...(shadowCasterWorldKeys === undefined ? {} : { shadowCasterWorldKeys }),
|
|
2163
2669
|
...(profilePhase !== undefined ? { profilePhase } : {}),
|
|
2164
2670
|
};
|
|
2165
2671
|
uploadMaterialUniforms(passCtx);
|
|
@@ -2190,6 +2696,37 @@ export function recordFrame(
|
|
|
2190
2696
|
frameState.temporalFrameInput = stagedTemporalFrame;
|
|
2191
2697
|
frameState.temporalFrameTransaction.stage(stagedTemporalFrame);
|
|
2192
2698
|
volumeSubmissionAttempted = volumeTopologyCandidate || activeVolumetricFogContext !== undefined;
|
|
2699
|
+
const ssrOwner = frameState.ssrHistoryOwner;
|
|
2700
|
+
const ssrCandidate = frameState.ssrHistoryCandidate;
|
|
2701
|
+
const frameHooks =
|
|
2702
|
+
preparedGpuDriven === undefined &&
|
|
2703
|
+
frameGpuDriven?.occlusion === undefined &&
|
|
2704
|
+
ssrCandidate === undefined
|
|
2705
|
+
? undefined
|
|
2706
|
+
: {
|
|
2707
|
+
afterGraphExecute: () =>
|
|
2708
|
+
frameGpuDriven?.occlusion?.recordResolve(encoder) ?? ok(undefined),
|
|
2709
|
+
onSubmitted: () => {
|
|
2710
|
+
if (ssrOwner !== undefined && ssrCandidate !== undefined) {
|
|
2711
|
+
const committed = ssrOwner.commitFrame(ssrCandidate);
|
|
2712
|
+
if (!committed.ok) {
|
|
2713
|
+
ssrOwner.abortFrame(ssrCandidate, 'submit');
|
|
2714
|
+
reportSsrHistoryFailure(internals, 'submit', committed.error);
|
|
2715
|
+
}
|
|
2716
|
+
frameState.ssrHistoryCandidate = undefined;
|
|
2717
|
+
}
|
|
2718
|
+
preparedGpuDriven?._commitResourceReplacement();
|
|
2719
|
+
frameGpuDriven?.occlusion?.commit(true, frameState.frameNumber, profilePhase);
|
|
2720
|
+
},
|
|
2721
|
+
onAborted: () => {
|
|
2722
|
+
if (ssrOwner !== undefined && ssrCandidate !== undefined) {
|
|
2723
|
+
ssrOwner.abortFrame(ssrCandidate, 'submit');
|
|
2724
|
+
frameState.ssrHistoryCandidate = undefined;
|
|
2725
|
+
}
|
|
2726
|
+
preparedGpuDriven?._abortResourceReplacement?.();
|
|
2727
|
+
frameGpuDriven?.occlusion?.commit(false, frameState.frameNumber);
|
|
2728
|
+
},
|
|
2729
|
+
};
|
|
2193
2730
|
submitted = runRecordProfilePhase(profilePhase, 'record/graph-execute', () =>
|
|
2194
2731
|
executeCompiledFrameGraph(
|
|
2195
2732
|
internals,
|
|
@@ -2199,17 +2736,7 @@ export function recordFrame(
|
|
|
2199
2736
|
profilePhase === undefined
|
|
2200
2737
|
? undefined
|
|
2201
2738
|
: (pass, encode) => profilePhase(graphExecutionPhase(pass.name), encode),
|
|
2202
|
-
|
|
2203
|
-
? undefined
|
|
2204
|
-
: {
|
|
2205
|
-
afterGraphExecute: () => gpuDriven.occlusion?.recordResolve(encoder) ?? ok(undefined),
|
|
2206
|
-
onSubmitted: () => {
|
|
2207
|
-
gpuDriven.occlusion?.commit(true, frameState.frameNumber, profilePhase);
|
|
2208
|
-
},
|
|
2209
|
-
onAborted: () => {
|
|
2210
|
-
gpuDriven.occlusion?.commit(false, frameState.frameNumber);
|
|
2211
|
-
},
|
|
2212
|
-
},
|
|
2739
|
+
frameHooks,
|
|
2213
2740
|
cubeCaptureWork.length === 0 ? undefined : cubeCaptureWork.map((work) => work.faceIndex),
|
|
2214
2741
|
(done) => {
|
|
2215
2742
|
completion = done;
|
|
@@ -2231,6 +2758,20 @@ export function recordFrame(
|
|
|
2231
2758
|
timingCapture,
|
|
2232
2759
|
),
|
|
2233
2760
|
);
|
|
2761
|
+
if (submitted && preparedGpuDriven?.drawKeys !== undefined && onRenderableDraw !== undefined) {
|
|
2762
|
+
for (const entry of validated) {
|
|
2763
|
+
if (gpuDrivenRenderableConsumed(entry, preparedGpuDriven.drawKeys)) {
|
|
2764
|
+
onRenderableDraw(entry);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
if (submitted) {
|
|
2769
|
+
// Feature plans are submission-sensitive: VFX keeps queued fixed ticks
|
|
2770
|
+
// until the graph that consumed them is actually submitted. Acknowledge
|
|
2771
|
+
// the exact accepted graph here so successful frames do not replay every
|
|
2772
|
+
// prior tick into the next topology.
|
|
2773
|
+
acknowledgeRenderFeatureSubmission(getRenderFeatureGraphState(internals).plans);
|
|
2774
|
+
}
|
|
2234
2775
|
if (cubeCapture !== undefined && cubeCaptureWork.length > 0) {
|
|
2235
2776
|
const completed = cubeCapture.scheduler.completeSubmission(submitted, completion);
|
|
2236
2777
|
if (!completed.ok) internals.errorRegistry.fire(completed.error);
|
|
@@ -2312,6 +2853,10 @@ export function recordFrame(
|
|
|
2312
2853
|
}
|
|
2313
2854
|
return submitted;
|
|
2314
2855
|
} finally {
|
|
2856
|
+
if (!submitted && frameState.ssrHistoryCandidate !== undefined) {
|
|
2857
|
+
frameState.ssrHistoryOwner?.abortFrame(frameState.ssrHistoryCandidate, 'submit');
|
|
2858
|
+
frameState.ssrHistoryCandidate = undefined;
|
|
2859
|
+
}
|
|
2315
2860
|
// Volume topology replacements are provisional until the queue submission
|
|
2316
2861
|
// succeeds. This runs for every early-return path as well, so a swapchain,
|
|
2317
2862
|
// encoder, or graph failure cannot leave a non-submitted candidate as the
|
|
@@ -2422,6 +2967,15 @@ function prepareDirectionalShadowCache(
|
|
|
2422
2967
|
): DirectionalShadowCacheDecision {
|
|
2423
2968
|
const cascadeCount = lights.cascadeCount;
|
|
2424
2969
|
const lightViewProj = lights.lightViewProj;
|
|
2970
|
+
// GPU feature buffers can change without an ECS mesh mutation. Until a
|
|
2971
|
+
// producer provides a stable content revision, never reuse their shadow atlas.
|
|
2972
|
+
if (
|
|
2973
|
+
getRenderFeatureGraphState(internals).plans.some(({ plan }) =>
|
|
2974
|
+
plan.passes.some((pass) => pass.kind === 'shadow-caster'),
|
|
2975
|
+
)
|
|
2976
|
+
) {
|
|
2977
|
+
return { reuse: false, next: null };
|
|
2978
|
+
}
|
|
2425
2979
|
if (
|
|
2426
2980
|
frameState.compiledFrameGraph === null ||
|
|
2427
2981
|
frameState.compiledFrameGraphTopologyKey === null ||
|
|
@@ -2662,6 +3216,9 @@ export function validateRenderables(
|
|
|
2662
3216
|
return validated;
|
|
2663
3217
|
}
|
|
2664
3218
|
|
|
3219
|
+
// Keep the primary record-stage name explicit at its call site while sharing
|
|
3220
|
+
// the same owner implementation for the complete retained ShadowCaster view.
|
|
3221
|
+
|
|
2665
3222
|
function prepareMorphMesh(
|
|
2666
3223
|
internals: RenderSystemInternals,
|
|
2667
3224
|
frameState: RenderFrameState,
|
|
@@ -2673,9 +3230,23 @@ function prepareMorphMesh(
|
|
|
2673
3230
|
if (morph === undefined || frameState.morphBuffers === undefined) return undefined;
|
|
2674
3231
|
const targets = mesh.morphTargets;
|
|
2675
3232
|
if (targets === undefined || targets.length !== morph.targetCount) return undefined;
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
3233
|
+
const position = base.layoutProjection.attributes.find(
|
|
3234
|
+
(attribute) => attribute.key === 'position',
|
|
3235
|
+
);
|
|
3236
|
+
if (
|
|
3237
|
+
position === undefined ||
|
|
3238
|
+
position.offset !== 0 ||
|
|
3239
|
+
position.shaderLocation !== 0 ||
|
|
3240
|
+
position.format !== 'float32x3' ||
|
|
3241
|
+
position.byteLength !== 12
|
|
3242
|
+
) {
|
|
3243
|
+
return undefined;
|
|
3244
|
+
}
|
|
3245
|
+
const vertexCount = deriveVertexCount(mesh.vertices, base.layoutProjection);
|
|
3246
|
+
if (vertexCount === undefined || vertexCount !== base.vertexCount || vertexCount <= 0) {
|
|
3247
|
+
return undefined;
|
|
3248
|
+
}
|
|
3249
|
+
const stride = base.layoutProjection.arrayStride / Float32Array.BYTES_PER_ELEMENT;
|
|
2679
3250
|
|
|
2680
3251
|
let active = false;
|
|
2681
3252
|
for (const weight of morph.weights) {
|
|
@@ -2753,17 +3324,19 @@ function prepareMorphMesh(
|
|
|
2753
3324
|
*
|
|
2754
3325
|
* @internal
|
|
2755
3326
|
*/
|
|
2756
|
-
function buildDispatchPlan(
|
|
3327
|
+
export function buildDispatchPlan(
|
|
2757
3328
|
internals: RenderSystemInternals,
|
|
2758
3329
|
validated: readonly ValidatedRenderable[],
|
|
2759
3330
|
transparentDispatch: readonly DispatchEntry[],
|
|
2760
3331
|
foldBuckets: readonly FoldBucket[],
|
|
3332
|
+
shadowValidated: readonly ValidatedRenderable[] = [],
|
|
2761
3333
|
): {
|
|
2762
3334
|
validatedOrdered: readonly ValidatedRenderable[];
|
|
3335
|
+
shadowValidatedOrdered: readonly ValidatedRenderable[];
|
|
2763
3336
|
foldDispatchPlan: FoldDispatchPlan | null;
|
|
2764
3337
|
materialSlotIndices: readonly (readonly number[])[];
|
|
2765
3338
|
materialSlots: readonly MaterialSnapshot[];
|
|
2766
|
-
materialSlotOwners: readonly
|
|
3339
|
+
materialSlotOwners: readonly ValidatedRenderable[];
|
|
2767
3340
|
materialSlotCount: number;
|
|
2768
3341
|
} {
|
|
2769
3342
|
// A production GPU-owned frame has no CPU validated rows by design. The
|
|
@@ -2771,9 +3344,16 @@ function buildDispatchPlan(
|
|
|
2771
3344
|
// CPU plan, material table, or fold bucket is consumed in that lane. Return
|
|
2772
3345
|
// the empty plan without walking that list so a steady 100k scene remains
|
|
2773
3346
|
// bounded by the persistent GPU batches rather than per-entity record work.
|
|
2774
|
-
if (validated.length === 0) {
|
|
3347
|
+
if (validated.length === 0 && shadowValidated.length === 0) {
|
|
3348
|
+
const meshSsboCapResult = ensureMeshSsboCapacity(internals, shadowValidated.length);
|
|
3349
|
+
const meshSsboSlotCapacity = meshSsboSlotCapacityAfterEnsure(
|
|
3350
|
+
internals,
|
|
3351
|
+
shadowValidated.length,
|
|
3352
|
+
meshSsboCapResult,
|
|
3353
|
+
);
|
|
2775
3354
|
return {
|
|
2776
3355
|
validatedOrdered: [],
|
|
3356
|
+
shadowValidatedOrdered: limitRowsToMeshSsboCapacity(shadowValidated, meshSsboSlotCapacity),
|
|
2777
3357
|
foldDispatchPlan: null,
|
|
2778
3358
|
materialSlotIndices: [],
|
|
2779
3359
|
materialSlots: [],
|
|
@@ -2821,7 +3401,10 @@ function buildDispatchPlan(
|
|
|
2821
3401
|
validatedOrdered = ordered;
|
|
2822
3402
|
}
|
|
2823
3403
|
|
|
2824
|
-
|
|
3404
|
+
// Shadow-only/off-camera CPU residuals consume the same material UBO as
|
|
3405
|
+
// visible draws. Their preparation must not depend on main-view visibility.
|
|
3406
|
+
const materialRows = [...validatedOrdered, ...shadowValidated];
|
|
3407
|
+
const materialGroups = materialRows.map((entry): readonly MaterialSnapshot[] => {
|
|
2825
3408
|
const shaderId = entry.source.material.materialShaderId;
|
|
2826
3409
|
if (shaderId === 'forgeax::sprite' || shaderId === 'forgeax::sprite-lit') {
|
|
2827
3410
|
return [entry.source.material];
|
|
@@ -2831,8 +3414,13 @@ function buildDispatchPlan(
|
|
|
2831
3414
|
const materialSlotPlan = buildMaterialSlotPlan(materialGroups);
|
|
2832
3415
|
let materialSlotIndices = materialSlotPlan.slotIndices;
|
|
2833
3416
|
let materialSlots = materialSlotPlan.slots;
|
|
2834
|
-
let materialSlotOwners = materialSlotPlan.slotOwners
|
|
3417
|
+
let materialSlotOwners = materialSlotPlan.slotOwners.map((index) => {
|
|
3418
|
+
const owner = materialRows[index];
|
|
3419
|
+
if (owner === undefined) throw new Error('material slot has no validated owner');
|
|
3420
|
+
return owner;
|
|
3421
|
+
});
|
|
2835
3422
|
let materialSlotCount = materialSlots.length;
|
|
3423
|
+
let materialRenderableCount = materialRows.length;
|
|
2836
3424
|
|
|
2837
3425
|
// feat-20260608-mesh-ssbo-dynamic-grow-l1-lift-1024-entity-cap M3 / T-M3-04:
|
|
2838
3426
|
// ensure the mesh-SSBO + material-UBO buffer pair is large enough to hold
|
|
@@ -2857,8 +3445,13 @@ function buildDispatchPlan(
|
|
|
2857
3445
|
//
|
|
2858
3446
|
// feat-20260624 M1' / t7: sprite-lit treated identically to sprite
|
|
2859
3447
|
// for material-slot accounting (paramSchema mirror, t4).
|
|
2860
|
-
const neededSlots = Math.max(validatedOrdered.length, materialSlotCount);
|
|
3448
|
+
const neededSlots = Math.max(validatedOrdered.length + shadowValidated.length, materialSlotCount);
|
|
2861
3449
|
const meshSsboCapResult = ensureMeshSsboCapacity(internals, neededSlots);
|
|
3450
|
+
const meshSsboSlotCapacity = meshSsboSlotCapacityAfterEnsure(
|
|
3451
|
+
internals,
|
|
3452
|
+
neededSlots,
|
|
3453
|
+
meshSsboCapResult,
|
|
3454
|
+
);
|
|
2862
3455
|
if (!meshSsboCapResult.ok) {
|
|
2863
3456
|
// Graceful degradation: the controller reports slots, but this stage
|
|
2864
3457
|
// consumes entities. Find a complete prefix instead of slicing at the
|
|
@@ -2867,12 +3460,17 @@ function buildDispatchPlan(
|
|
|
2867
3460
|
materialSlotIndices,
|
|
2868
3461
|
meshSsboCapResult.degradedToSlotCount,
|
|
2869
3462
|
);
|
|
3463
|
+
materialRenderableCount = degradedRenderableCount;
|
|
2870
3464
|
materialSlotCount = materialSlotCountForPrefix(materialSlotIndices, degradedRenderableCount);
|
|
2871
3465
|
materialSlotIndices = materialSlotIndices.slice(0, degradedRenderableCount);
|
|
2872
3466
|
materialSlots = materialSlots.slice(0, materialSlotCount);
|
|
2873
3467
|
materialSlotOwners = materialSlotOwners.slice(0, materialSlotCount);
|
|
2874
3468
|
validatedOrdered = validatedOrdered.slice(0, degradedRenderableCount);
|
|
2875
3469
|
}
|
|
3470
|
+
const shadowValidatedOrdered = limitRowsToMeshSsboCapacity(
|
|
3471
|
+
shadowValidated,
|
|
3472
|
+
Math.min(meshSsboSlotCapacity, materialRenderableCount) - validatedOrdered.length,
|
|
3473
|
+
);
|
|
2876
3474
|
|
|
2877
3475
|
// feat-20260622-chunk-gpu-instancing-sprite-tilemap M1 / w4-record-swap
|
|
2878
3476
|
// (D-1): build the fold dispatch plan once `validatedOrdered` is final
|
|
@@ -2965,6 +3563,7 @@ function buildDispatchPlan(
|
|
|
2965
3563
|
|
|
2966
3564
|
return {
|
|
2967
3565
|
validatedOrdered,
|
|
3566
|
+
shadowValidatedOrdered,
|
|
2968
3567
|
foldDispatchPlan,
|
|
2969
3568
|
materialSlotIndices,
|
|
2970
3569
|
materialSlots,
|
|
@@ -3042,13 +3641,6 @@ function cleanPerFrameCaches(
|
|
|
3042
3641
|
}
|
|
3043
3642
|
}
|
|
3044
3643
|
|
|
3045
|
-
if (frameState.instanceResidency !== undefined) {
|
|
3046
|
-
const liveCollections = new Set(validated.map((entry) => entry.source.instances?.collectionId));
|
|
3047
|
-
for (const id of frameState.instanceResidency.keys()) {
|
|
3048
|
-
if (!liveCollections.has(id)) frameState.instanceResidency.delete(id);
|
|
3049
|
-
}
|
|
3050
|
-
}
|
|
3051
|
-
|
|
3052
3644
|
// Chunk keys are `${worldEntityKey}:${chunkStart}`. Keep the key opaque to
|
|
3053
3645
|
// the authoring API, but retain the numeric owner prefix for eviction.
|
|
3054
3646
|
if (frameState.instanceBufferChunks !== undefined) {
|