@forgeax/engine-render 0.1.28 → 0.1.30
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 +856 -126
- package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
- package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/device-feature-admission.d.ts +4 -3
- package/dist/assembly/device-feature-admission.d.ts.map +1 -1
- package/dist/assembly/dynamic-geometry-host.d.ts +27 -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 +14 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +31 -1
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/pipeline-helpers.d.ts +24 -0
- package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -0
- 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/device-loss-fanout.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/recovery-attempt.d.ts +1 -6
- package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -1
- package/dist/assembly/recovery/renderer-recover.d.ts +7 -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 +7 -1
- 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/capture/scene-projection.d.ts +41 -0
- package/dist/capture/scene-projection.d.ts.map +1 -0
- package/dist/{chunk-J52NMQKR.mjs → chunk-7MQ3XBSP.mjs} +1098 -844
- package/dist/chunk-7MQ3XBSP.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-VSL23LZE.mjs → chunk-C3DDSXYK.mjs} +1188 -463
- package/dist/chunk-C3DDSXYK.mjs.map +1 -0
- package/dist/{chunk-IQMKJLNU.mjs → chunk-FUUO4UND.mjs} +443 -30
- package/dist/chunk-FUUO4UND.mjs.map +1 -0
- package/dist/{chunk-J6KACNOH.mjs → chunk-JRBDO2KB.mjs} +617 -7
- package/dist/chunk-JRBDO2KB.mjs.map +1 -0
- package/dist/{chunk-GE3SDD72.mjs → chunk-MDQJPY3T.mjs} +20 -40
- package/dist/chunk-MDQJPY3T.mjs.map +1 -0
- package/dist/{chunk-MYJ5RPJO.mjs → chunk-NO4WZCAH.mjs} +3 -3
- package/dist/chunk-NO4WZCAH.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-MBU4ZVE7.mjs → chunk-PSEQ6QPH.mjs} +4655 -892
- package/dist/chunk-PSEQ6QPH.mjs.map +1 -0
- package/dist/{chunk-JAASCMXA.mjs → chunk-S5UE2T5P.mjs} +289 -41
- package/dist/chunk-S5UE2T5P.mjs.map +1 -0
- package/dist/components/camera.d.ts +62 -2
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/directional-light.d.ts +2 -2
- package/dist/components/dynamic-resolution.d.ts +15 -0
- package/dist/components/dynamic-resolution.d.ts.map +1 -0
- package/dist/components/index.d.ts +2 -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/light-helpers.d.ts.map +1 -1
- package/dist/components/reflection-probe.d.ts +1 -0
- package/dist/components/reflection-probe.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/components/skylight.d.ts +4 -1
- package/dist/components/skylight.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +14809 -7570
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/device-scope.d.ts +2 -0
- package/dist/device/device-scope.d.ts.map +1 -1
- package/dist/device/gpu-residency.d.ts +3 -14
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/dynamic-geometry.d.ts.map +1 -1
- package/dist/environment/background.d.ts +7 -0
- package/dist/environment/background.d.ts.map +1 -0
- package/dist/environment/ibl.d.ts +11 -0
- package/dist/environment/ibl.d.ts.map +1 -0
- package/dist/environment/storage.d.ts +14 -0
- package/dist/environment/storage.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 +41 -4
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +7 -0
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +50 -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/material-artifacts.d.ts +24 -0
- package/dist/gpu-driven/material-artifacts.d.ts.map +1 -0
- package/dist/gpu-driven/material-bindings.d.ts +7 -0
- package/dist/gpu-driven/material-bindings.d.ts.map +1 -0
- 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 +2 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +169 -27
- 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-resource.d.ts +5 -2
- package/dist/gpu-resource.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 +9 -0
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +2 -0
- package/dist/ibl/IblPipelineCache.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 +3 -1
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +87 -75
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +10 -8
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +180 -1
- 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 +15 -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 +23 -5
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/render-extent.d.ts +51 -0
- package/dist/pipeline/render-extent.d.ts.map +1 -0
- package/dist/pipeline/standard-build-context.d.ts +7 -0
- package/dist/pipeline/standard-build-context.d.ts.map +1 -0
- package/dist/pipeline/standard-forward-lane.d.ts +3 -2
- 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/standard-profile.d.ts +2 -1
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +5 -2
- 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/plugin.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/post-process-errors.d.ts +9 -2
- package/dist/post-process-errors.d.ts.map +1 -1
- package/dist/prepare/extended-lighting/resources.d.ts +3 -3
- 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 +109 -13
- 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 +41 -5
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +13 -9
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +43 -3
- 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 +14 -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 +1 -0
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +101 -22
- 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 +39 -8
- 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/background.d.ts +14 -0
- package/dist/reflection/background.d.ts.map +1 -0
- package/dist/reflection/filter.d.ts.map +1 -1
- package/dist/reflection/projection.d.ts +1 -0
- package/dist/reflection/projection.d.ts.map +1 -1
- package/dist/reflection/record-owner.d.ts +4 -4
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +211 -53
- 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 +81 -6
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +15 -6
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +128 -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 +31 -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/ssao-config.d.ts +8 -6
- package/dist/ssao-config.d.ts.map +1 -1
- 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 +4 -2
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/target-coverage-attachment.d.ts +15 -0
- package/dist/temporal/target-coverage-attachment.d.ts.map +1 -0
- package/dist/temporal/temporal-view.d.ts +6 -0
- package/dist/temporal/temporal-view.d.ts.map +1 -1
- package/dist/temporal/view.d.ts +6 -0
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +41 -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/component.d.ts +1 -0
- package/dist/volume/component.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__/capture-scene-projection.unit.test.ts +24 -0
- package/src/__tests__/detached-inspection.integration.test.ts +5 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +15 -24
- package/src/__tests__/device-loss-fanout.unit.test.ts +43 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
- package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
- package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
- package/src/__tests__/dynamic-geometry-batch.unit.test.ts +157 -0
- package/src/__tests__/dynamic-geometry.unit.test.ts +36 -0
- package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +81 -0
- package/src/__tests__/factory-contract.integration.test.ts +231 -9
- 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-observation.contract.test.ts +21 -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 +307 -7
- 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 +261 -0
- package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +346 -0
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1422 -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 +1076 -106
- 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 +160 -52
- package/src/__tests__/gpu-pass-timing-browser-runner.ts +15 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +20 -2
- package/src/__tests__/gpu-resource-scope.unit.test.ts +71 -0
- 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 +613 -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 +57 -3
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +8 -0
- package/src/__tests__/morph-neutral-admission.unit.test.ts +19 -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 +42 -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__/post-process-error-policy-owner.unit.test.ts +6 -5
- 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 +251 -9
- 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 +171 -0
- package/src/__tests__/reflection-probe-authoring.unit.test.ts +12 -10
- package/src/__tests__/reflection-probe-box.dawn.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +9 -0
- package/src/__tests__/reflection-probe-orientation.dawn.test.ts +294 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +5 -14
- package/src/__tests__/reflection-probe-updates.unit.test.ts +129 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +7 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +27 -1
- package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
- package/src/__tests__/render-extent.unit.test.ts +63 -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 +915 -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 +233 -9
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
- package/src/__tests__/shader-manifest-fixture.ts +45 -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 +125 -14
- package/src/__tests__/specular-environment-scale.dawn.test.ts +158 -0
- package/src/__tests__/ssao-config.unit.test.ts +7 -0
- package/src/__tests__/ssao-surface.dawn.test.ts +252 -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-cube-silhouette.dawn.test.ts +270 -0
- package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +99 -0
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +116 -0
- package/src/__tests__/ssr-gpu-dispatch.ts +951 -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 +84 -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 +275 -0
- package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
- package/src/__tests__/standard-profile-oracle.unit.test.ts +49 -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 +199 -0
- package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
- package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +782 -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 +342 -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__/temporal-view.unit.test.ts +17 -0
- package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
- package/src/__tests__/transmission-prewarm.unit.test.ts +57 -0
- 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/__tests__/volumetric-fog-stage-readback.dawn.test.ts +62 -13
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +7 -7
- package/src/assembly/atmosphere-shader-prewarm.ts +39 -0
- package/src/assembly/device-feature-admission.ts +9 -4
- package/src/assembly/dynamic-geometry-host.ts +284 -163
- 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 +19 -0
- package/src/assembly/material/assembly.ts +383 -1
- package/src/assembly/material/pipeline-helpers.ts +100 -0
- package/src/assembly/material-shader-policy.ts +110 -46
- package/src/assembly/recovery/device-loss-fanout.ts +5 -0
- package/src/assembly/recovery/failure-location.ts +62 -0
- package/src/assembly/recovery/recovery-attempt.ts +2 -29
- package/src/assembly/recovery/renderer-recover.ts +16 -17
- package/src/assembly/renderer-facade.ts +34 -1
- 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 +46 -1
- 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 +161 -131
- 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 +744 -730
- package/src/assembly/webgpu-vertex-layouts.ts +233 -87
- package/src/capture/scene-projection.ts +37 -0
- package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
- package/src/components/__tests__/dynamic-resolution-schema.unit.test.ts +65 -0
- package/src/components/__tests__/light-radius.integration.test.ts +59 -0
- package/src/components/camera.ts +287 -7
- package/src/components/directional-light.ts +3 -3
- package/src/components/dynamic-resolution.ts +67 -0
- package/src/components/index.ts +2 -0
- package/src/components/instances.ts +45 -18
- package/src/components/light-helpers.ts +6 -1
- package/src/components/reflection-probe.ts +1 -0
- package/src/components/scene-instance.ts +2 -1
- package/src/components/screen-space-reflection.ts +21 -0
- package/src/components/skylight.ts +4 -1
- package/src/components/sprite-animation.ts +2 -2
- package/src/components/sprite-instances.ts +1 -1
- package/src/device/device-scope.ts +13 -7
- package/src/device/gpu-residency.ts +45 -42
- package/src/dynamic-geometry.ts +13 -8
- package/src/environment/background.ts +281 -0
- package/src/environment/ibl.ts +211 -0
- package/src/environment/storage.ts +63 -0
- package/src/errors/gpu-driven.ts +49 -8
- package/src/errors/render.ts +91 -3
- package/src/extract/camera.ts +37 -1
- package/src/extract/contracts.ts +10 -0
- package/src/extract/gpu-driven.ts +240 -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 +452 -108
- package/src/gpu-driven/material-artifacts.ts +179 -0
- package/src/gpu-driven/material-bindings.ts +41 -0
- package/src/gpu-driven/pbr-program.ts +31 -0
- package/src/gpu-driven/prepared-draw.ts +42 -6
- package/src/gpu-driven/production-raster.ts +2806 -716
- package/src/gpu-driven/shadow-views.ts +539 -0
- package/src/gpu-driven/view-gpu.ts +188 -41
- package/src/gpu-resource.ts +21 -2
- package/src/gpu-scene-schema.ts +61 -11
- package/src/gpu-scene.ts +197 -99
- package/src/gpu-texture-usage.ts +1 -0
- package/src/hdrp-buffers.ts +141 -36
- package/src/ibl/IblPipelineCache.ts +5 -0
- package/src/ibl/frame-resources.ts +38 -0
- package/src/ibl/skylight-bind-group.ts +32 -21
- package/src/index.ts +194 -75
- package/src/inspection-types.ts +207 -1
- 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 +194 -0
- package/src/materials.ts +44 -8
- package/src/pbr-pipeline.ts +172 -19
- package/src/pipeline/render-extent.ts +111 -0
- package/src/pipeline/standard-build-context.ts +7 -0
- package/src/pipeline/standard-forward-lane.ts +74 -29
- 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 +232 -70
- package/src/pipeline/standard-post.ts +190 -27
- package/src/pipeline/standard-profile.ts +2 -1
- package/src/pipeline-builder.ts +28 -9
- package/src/pipeline-spec-types.ts +4 -0
- package/src/pipeline-spec.ts +37 -29
- package/src/plugin.ts +4 -0
- package/src/points-lines/standard-owner.ts +6 -14
- package/src/post-process-errors.ts +26 -18
- package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +2 -2
- package/src/prepare/extended-lighting/resources.ts +3 -3
- package/src/prepare/prepared-graphics-resolver.ts +9 -2
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +68 -0
- package/src/record/__tests__/material-bg-cache.unit.test.ts +45 -0
- package/src/record/__tests__/material-slot-plan.unit.test.ts +57 -0
- package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +20 -1
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +41 -6
- package/src/record/__tests__/temporal-payload.unit.test.ts +2 -0
- 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 +15 -12
- package/src/record/frame-snapshot.ts +146 -34
- package/src/record/frame-targets.ts +9 -0
- package/src/record/frame.ts +838 -170
- package/src/record/main-pass-geometry.ts +209 -103
- package/src/record/main-pass-material.ts +157 -40
- package/src/record/main-pass-sprite-draws.ts +16 -107
- package/src/record/main-pass.ts +141 -12
- package/src/record/material-uniforms.ts +1 -1
- package/src/record/mesh-ssbo.ts +59 -15
- package/src/record/prepared-material-bindings.ts +238 -0
- package/src/record/recovery-pipeline.ts +69 -90
- package/src/record/render-context.ts +123 -24
- package/src/record/shadow-pass.ts +1389 -230
- package/src/record/skybox-post-pass.ts +14 -3
- package/src/record/typed-frame-graph.ts +1064 -47
- package/src/record/view-ubo.ts +12 -1
- package/src/recovery/render-system-candidate.ts +135 -68
- package/src/recovery/render-system-roots.ts +113 -0
- package/src/recovery/types.ts +77 -0
- package/src/reflection/background.ts +151 -0
- package/src/reflection/filter.ts +3 -0
- package/src/reflection/projection.ts +16 -6
- package/src/reflection/record-owner.ts +308 -140
- package/src/render-contract.ts +237 -49
- package/src/render-graph-primitives.ts +34 -5
- package/src/render-pipeline.ts +90 -2
- package/src/render-system-extract-tail.ts +431 -162
- package/src/render-system-extract.ts +478 -119
- package/src/render-system-projections.ts +241 -0
- package/src/render-system.ts +715 -828
- package/src/scene/render-scene-types.ts +13 -4
- package/src/scene/render-scene.ts +577 -355
- package/src/scene/shadow-visibility.ts +21 -0
- package/src/ssao-config.ts +25 -8
- 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 +797 -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 +79 -12
- package/src/temporal/history.ts +3 -1
- package/src/temporal/inspection.ts +2 -1
- package/src/temporal/standard-scene-data.ts +10 -2
- package/src/temporal/target-coverage-attachment.ts +72 -0
- package/src/temporal/temporal-view.ts +11 -2
- package/src/temporal/view.ts +12 -2
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +219 -139
- package/src/typed-render-graph-primitives.ts +304 -74
- package/src/typed-shadow-passes.ts +122 -12
- package/src/volume/component.ts +1 -0
- package/src/volume/passes.ts +3 -3
- 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-MYJ5RPJO.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
|
@@ -9,8 +9,21 @@ import {
|
|
|
9
9
|
type RhiRenderPassEncoder,
|
|
10
10
|
} from '@forgeax/engine-rhi';
|
|
11
11
|
import type { MaterialRenderState, PassSelector } from '@forgeax/engine-types';
|
|
12
|
-
import
|
|
12
|
+
import { transmissionBackdropAvailable } from '../assembly/device-feature-admission';
|
|
13
|
+
import { gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
|
|
14
|
+
import type { SubmissionPlan } from '../gpu-driven/batch-topology';
|
|
15
|
+
import type { GpuDrivenShadowBatchProjection } from '../gpu-driven/production-raster';
|
|
16
|
+
import { type ShadowViewIdentity, shadowViewIdentityKey } from '../gpu-driven/shadow-views';
|
|
17
|
+
import type { GpuDrivenView } from '../gpu-driven/view-gpu';
|
|
18
|
+
import { GpuBuffer } from '../gpu-resource';
|
|
19
|
+
import {
|
|
20
|
+
GPU_BUFFER_USAGE_COPY_DST,
|
|
21
|
+
GPU_BUFFER_USAGE_STORAGE,
|
|
22
|
+
GPU_BUFFER_USAGE_UNIFORM,
|
|
23
|
+
} from '../gpu-usage';
|
|
13
24
|
import { assembleMaterialWithSkylightEntries } from '../ibl/skylight-bind-group';
|
|
25
|
+
import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
|
|
26
|
+
import { instanceUploadRangesForResident } from '../instances';
|
|
14
27
|
import {
|
|
15
28
|
buildPbrMaterialUserRegionEntries,
|
|
16
29
|
createPbrSkinMeshBindGroupEntries,
|
|
@@ -19,11 +32,18 @@ import {
|
|
|
19
32
|
shadowCasterVariantSet,
|
|
20
33
|
} from '../pbr-pipeline';
|
|
21
34
|
import { COOKIE_MATRIX_BYTES } from '../prepare/extended-lighting/resources';
|
|
22
|
-
import type {
|
|
35
|
+
import type {
|
|
36
|
+
DispatchEntry,
|
|
37
|
+
ExtractedLights,
|
|
38
|
+
RenderableSnapshot,
|
|
39
|
+
ShadowCasterMembership,
|
|
40
|
+
} from '../render-system-extract';
|
|
23
41
|
import { matchPass } from '../systems/pass-selector';
|
|
42
|
+
import { instanceCollectionCacheKey, worldEntityKey } from './frame-snapshot';
|
|
24
43
|
import {
|
|
25
|
-
|
|
44
|
+
reportInstanceResidency,
|
|
26
45
|
resolveGeometryInstancesBindGroup,
|
|
46
|
+
resolveStorageInstanceChunks,
|
|
27
47
|
} from './main-pass-geometry';
|
|
28
48
|
import {
|
|
29
49
|
buildPerSubmeshMaterialBg,
|
|
@@ -31,10 +51,15 @@ import {
|
|
|
31
51
|
prepareMaterialSkylight,
|
|
32
52
|
} from './main-pass-material';
|
|
33
53
|
import {
|
|
54
|
+
findFromChain,
|
|
34
55
|
getOrCreateFromChain,
|
|
56
|
+
getOrCreateFromChainResult,
|
|
57
|
+
INSTANCE_UBO_FULL_ARRAY_BYTES,
|
|
58
|
+
MAX_UNIFORM_INSTANCES,
|
|
35
59
|
MESH_PER_ENTITY_STRIDE,
|
|
36
60
|
MESH_SSBO_BYTES,
|
|
37
61
|
MESH_UBO_FULL_ARRAY_BYTES,
|
|
62
|
+
packInstanceStorageBuffer,
|
|
38
63
|
} from './mesh-ssbo';
|
|
39
64
|
import type { _InternalRenderPipelineContext } from './render-context';
|
|
40
65
|
import { MATERIAL_PER_ENTITY_STRIDE, STANDARD_PBR_UBO_SIZE } from './render-context';
|
|
@@ -43,6 +68,32 @@ import { POINTS_LINES_VIEW_BYTES, pointShadowViewOffset, VIEW_UNIFORM_BYTES } fr
|
|
|
43
68
|
export const SHADOW_CASTER_SLOT_STRIDE = 256;
|
|
44
69
|
export const SHADOW_CASTER_BUFFER_SIZE = SHADOW_CASTER_SLOT_STRIDE * 8;
|
|
45
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Renderer-owned capable shadow submission. The view/counter/indirect ABI is
|
|
73
|
+
* shared with the main GPU-driven lane; this record only supplies the
|
|
74
|
+
* prepared per-batch raster state that cannot be inferred from a raw scene.
|
|
75
|
+
* When present, the capable lane emits one indirect command per topology batch
|
|
76
|
+
* and never walks `validatedOrdered`.
|
|
77
|
+
*/
|
|
78
|
+
export interface GpuDrivenShadowBatch {
|
|
79
|
+
readonly pipeline: RenderPipeline;
|
|
80
|
+
readonly meshBindGroup: BindGroup;
|
|
81
|
+
readonly visibleBindGroup: BindGroup;
|
|
82
|
+
readonly deformation: 'rigid' | 'skin';
|
|
83
|
+
/** Complete binding for this batch, including opaque batches after custom casters. */
|
|
84
|
+
readonly materialGroup: BindGroup;
|
|
85
|
+
readonly vertexBuffer: Buffer;
|
|
86
|
+
readonly indexBuffer?: Buffer | undefined;
|
|
87
|
+
readonly indexFormat?: 'uint16' | 'uint32' | undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface GpuDrivenShadowSubmission {
|
|
91
|
+
readonly view: GpuDrivenView;
|
|
92
|
+
readonly plan: SubmissionPlan;
|
|
93
|
+
readonly batches: ReadonlyMap<number, GpuDrivenShadowBatch>;
|
|
94
|
+
readonly instancesBindGroup?: BindGroup | undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
46
97
|
export function directionalShadowCasterOffset(cascadeIndex: number): number {
|
|
47
98
|
return SHADOW_CASTER_SLOT_STRIDE * cascadeIndex;
|
|
48
99
|
}
|
|
@@ -253,43 +304,76 @@ function ensureTypedShadowViewBg(
|
|
|
253
304
|
}
|
|
254
305
|
|
|
255
306
|
interface ShadowDispatch {
|
|
307
|
+
readonly passIndex: number;
|
|
308
|
+
readonly materialHandle: number;
|
|
256
309
|
readonly vertexEntry: string | undefined;
|
|
257
310
|
readonly fragmentEntry: string | undefined;
|
|
258
311
|
readonly materialShaderId: string;
|
|
259
312
|
readonly renderState: MaterialRenderState | undefined;
|
|
313
|
+
readonly materialSlot: number | undefined;
|
|
314
|
+
readonly paramSnapshot: DispatchEntry['paramSnapshot'];
|
|
260
315
|
}
|
|
261
316
|
|
|
262
|
-
type ShadowDispatchMap = ReadonlyMap<number, ReadonlyMap<number, ShadowDispatch>>;
|
|
317
|
+
type ShadowDispatchMap = ReadonlyMap<number, ReadonlyMap<number, readonly ShadowDispatch[]>>;
|
|
318
|
+
|
|
319
|
+
function shadowDispatchEntries(c: _InternalRenderPipelineContext): readonly DispatchEntry[] {
|
|
320
|
+
return c.shadowDispatch ?? c.dispatch;
|
|
321
|
+
}
|
|
263
322
|
|
|
264
323
|
export function shadowShaderMap(c: _InternalRenderPipelineContext): ShadowDispatchMap {
|
|
265
|
-
const shaders = new Map<number, Map<number, ShadowDispatch>>();
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
? SHADOW_CASTER_SHADER_ID
|
|
279
|
-
: entry.materialShaderId,
|
|
280
|
-
renderState: entry.renderState,
|
|
281
|
-
});
|
|
324
|
+
const shaders = new Map<number, Map<number, ShadowDispatch[]>>();
|
|
325
|
+
const shadowRows = c.shadowDispatch !== undefined;
|
|
326
|
+
const rows = (shadowRows ? c.shadowValidatedOrdered : c.validatedOrdered) ?? [];
|
|
327
|
+
const base = shadowRows ? c.validatedOrdered.length : 0;
|
|
328
|
+
const slotsByRenderable = new Map(
|
|
329
|
+
rows.map((row, index) => [row.renderableIndex, c.materialSlotIndices?.[base + index] ?? []]),
|
|
330
|
+
);
|
|
331
|
+
for (const entry of shadowDispatchEntries(c)) {
|
|
332
|
+
if (entry.tags.LightMode !== 'ShadowCaster' || entry.materialShaderId === undefined) continue;
|
|
333
|
+
let byMaterial = shaders.get(entry.renderableIndex);
|
|
334
|
+
if (byMaterial === undefined) {
|
|
335
|
+
byMaterial = new Map<number, ShadowDispatch[]>();
|
|
336
|
+
shaders.set(entry.renderableIndex, byMaterial);
|
|
282
337
|
}
|
|
338
|
+
const candidateSlots = slotsByRenderable.get(entry.renderableIndex) ?? [];
|
|
339
|
+
const materialSlot = candidateSlots.find(
|
|
340
|
+
(slot) => c.materialSlots?.[slot]?.materialHandle === entry.materialHandle,
|
|
341
|
+
);
|
|
342
|
+
const dispatches = byMaterial.get(entry.materialHandle) ?? [];
|
|
343
|
+
dispatches.push({
|
|
344
|
+
passIndex: entry.passIndex,
|
|
345
|
+
materialHandle: entry.materialHandle,
|
|
346
|
+
vertexEntry: entry.vertexEntry,
|
|
347
|
+
fragmentEntry: entry.fragmentEntry,
|
|
348
|
+
materialShaderId:
|
|
349
|
+
entry.materialShaderId === 'forgeax::default-standard-pbr'
|
|
350
|
+
? SHADOW_CASTER_SHADER_ID
|
|
351
|
+
: entry.materialShaderId,
|
|
352
|
+
renderState: entry.renderState,
|
|
353
|
+
materialSlot,
|
|
354
|
+
paramSnapshot: entry.paramSnapshot,
|
|
355
|
+
});
|
|
356
|
+
byMaterial.set(entry.materialHandle, dispatches);
|
|
283
357
|
}
|
|
284
358
|
return shaders;
|
|
285
359
|
}
|
|
286
360
|
|
|
287
|
-
function
|
|
361
|
+
function shadowRasterState(
|
|
362
|
+
state: MaterialRenderState | undefined,
|
|
363
|
+
reflected: boolean,
|
|
364
|
+
): MaterialRenderState | undefined {
|
|
365
|
+
return reflected ? { ...state, frontFace: state?.frontFace === 'cw' ? 'ccw' : 'cw' } : state;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function shadowPipeline(
|
|
369
|
+
c: _InternalRenderPipelineContext,
|
|
370
|
+
reflected = false,
|
|
371
|
+
): RenderPipeline | null {
|
|
288
372
|
return (
|
|
289
373
|
c.runtime.getMaterialShaderPipeline?.(
|
|
290
374
|
SHADOW_CASTER_SHADER_ID,
|
|
291
375
|
false,
|
|
292
|
-
undefined,
|
|
376
|
+
shadowRasterState(undefined, reflected),
|
|
293
377
|
'triangle-list',
|
|
294
378
|
undefined,
|
|
295
379
|
shadowCasterVariantSet(c.runtime.device.caps.storageBuffer, false),
|
|
@@ -307,32 +391,6 @@ function shadowPipeline(c: _InternalRenderPipelineContext): RenderPipeline | nul
|
|
|
307
391
|
);
|
|
308
392
|
}
|
|
309
393
|
|
|
310
|
-
function skinnedShadowPipeline(
|
|
311
|
-
c: _InternalRenderPipelineContext,
|
|
312
|
-
entry: _InternalRenderPipelineContext['validatedOrdered'][number],
|
|
313
|
-
): RenderPipeline | null {
|
|
314
|
-
return (
|
|
315
|
-
c.runtime.getMaterialShaderPipeline?.(
|
|
316
|
-
SHADOW_CASTER_SHADER_ID,
|
|
317
|
-
false,
|
|
318
|
-
undefined,
|
|
319
|
-
entry.mesh.submeshes[0]?.topology ?? 'triangle-list',
|
|
320
|
-
entry.mesh.indexFormat,
|
|
321
|
-
shadowCasterVariantSet(c.runtime.device.caps.storageBuffer, true),
|
|
322
|
-
'shadow-caster',
|
|
323
|
-
undefined,
|
|
324
|
-
1,
|
|
325
|
-
undefined,
|
|
326
|
-
undefined,
|
|
327
|
-
undefined,
|
|
328
|
-
undefined,
|
|
329
|
-
entry.mesh.layoutProjection,
|
|
330
|
-
undefined,
|
|
331
|
-
'pbr-skin',
|
|
332
|
-
) ?? null
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
394
|
function skinnedShadowMeshBindGroup(
|
|
337
395
|
c: _InternalRenderPipelineContext,
|
|
338
396
|
entry: _InternalRenderPipelineContext['validatedOrdered'][number],
|
|
@@ -363,13 +421,410 @@ function skinnedShadowMeshBindGroup(
|
|
|
363
421
|
);
|
|
364
422
|
}
|
|
365
423
|
|
|
424
|
+
/**
|
|
425
|
+
* Record the capable shadow lane from the shared view's indirect arguments.
|
|
426
|
+
* The submission is an explicit prepared contract: missing view buffers or a
|
|
427
|
+
* missing batch raster projection are terminal rather than a reason to fall
|
|
428
|
+
* back to a CPU caster walk. The caller may then keep the existing CPU path
|
|
429
|
+
* when no capable submission was published for the current view.
|
|
430
|
+
*/
|
|
431
|
+
export function recordGpuDrivenShadowIndirect(
|
|
432
|
+
pass: RhiRenderPassEncoder,
|
|
433
|
+
submission: GpuDrivenShadowSubmission,
|
|
434
|
+
): void {
|
|
435
|
+
const indirect = submission.view.indirectBuffer;
|
|
436
|
+
if (indirect === undefined) {
|
|
437
|
+
throw new RhiError({
|
|
438
|
+
code: 'internal-error',
|
|
439
|
+
expected: 'GPU-driven shadow view indirect buffer',
|
|
440
|
+
hint: 'publish a prepared view generation before recording a capable shadow pass',
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
const indexed = new Map<number, GpuDrivenShadowBatch>();
|
|
444
|
+
for (const [batchId, batch] of submission.batches) indexed.set(batchId, batch);
|
|
445
|
+
let currentPipeline: RenderPipeline | undefined;
|
|
446
|
+
let currentVertex: Buffer | undefined;
|
|
447
|
+
let currentIndex: Buffer | undefined;
|
|
448
|
+
for (const batch of submission.plan.batches) {
|
|
449
|
+
const raster = indexed.get(batch.batchId);
|
|
450
|
+
if (raster === undefined) {
|
|
451
|
+
throw new RhiError({
|
|
452
|
+
code: 'internal-error',
|
|
453
|
+
expected: `prepared shadow raster batch ${batch.batchId}`,
|
|
454
|
+
hint: 'keep the shadow view plan and prepared batch projection on one generation',
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
if (currentPipeline !== raster.pipeline) {
|
|
458
|
+
pass.setPipeline(raster.pipeline);
|
|
459
|
+
currentPipeline = raster.pipeline;
|
|
460
|
+
}
|
|
461
|
+
if (currentVertex !== raster.vertexBuffer) {
|
|
462
|
+
pass.setVertexBuffer(0, raster.vertexBuffer);
|
|
463
|
+
currentVertex = raster.vertexBuffer;
|
|
464
|
+
}
|
|
465
|
+
pass.setBindGroup(1, raster.materialGroup, [0]);
|
|
466
|
+
if (raster.indexBuffer !== undefined) {
|
|
467
|
+
if (raster.indexFormat === undefined) {
|
|
468
|
+
throw new RhiError({
|
|
469
|
+
code: 'internal-error',
|
|
470
|
+
expected: `index format for prepared shadow raster batch ${batch.batchId}`,
|
|
471
|
+
hint: 'publish the geometry layout projection with the capable shadow batch',
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
if (currentIndex !== raster.indexBuffer) {
|
|
475
|
+
pass.setIndexBuffer(raster.indexBuffer, raster.indexFormat);
|
|
476
|
+
currentIndex = raster.indexBuffer;
|
|
477
|
+
}
|
|
478
|
+
pass.setBindGroup(2, raster.meshBindGroup, raster.deformation === 'skin' ? [0, 0, 0] : [0]);
|
|
479
|
+
pass.setBindGroup(3, raster.visibleBindGroup);
|
|
480
|
+
pass.drawIndexedIndirect(indirect, batch.indirectOffset);
|
|
481
|
+
} else {
|
|
482
|
+
pass.setBindGroup(2, raster.meshBindGroup, raster.deformation === 'skin' ? [0, 0, 0] : [0]);
|
|
483
|
+
pass.setBindGroup(3, raster.visibleBindGroup);
|
|
484
|
+
pass.drawIndirect(indirect, batch.indirectOffset);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function shadowDrawIndicesBySubmesh(
|
|
490
|
+
source: RenderableSnapshot,
|
|
491
|
+
mesh: _InternalRenderPipelineContext['validatedOrdered'][number]['mesh'],
|
|
492
|
+
): ReadonlyMap<number, number> {
|
|
493
|
+
const draws = source.gpuDrivenDraws;
|
|
494
|
+
if (draws === undefined || draws.length === 0) return new Map();
|
|
495
|
+
const indices = new Map<number, number>();
|
|
496
|
+
let hasStableSourceIdentity = false;
|
|
497
|
+
for (const [compactIndex, draw] of draws.entries()) {
|
|
498
|
+
const drawItemIndex = draw.drawItemIndex;
|
|
499
|
+
if (drawItemIndex === undefined) continue;
|
|
500
|
+
hasStableSourceIdentity = true;
|
|
501
|
+
if (mesh.submeshes[drawItemIndex] !== undefined) indices.set(drawItemIndex, compactIndex);
|
|
502
|
+
}
|
|
503
|
+
if (hasStableSourceIdentity) return indices;
|
|
504
|
+
|
|
505
|
+
// Legacy snapshots predating drawItemIndex are matched by their compact
|
|
506
|
+
// geometry facts. New extracted snapshots never take this path.
|
|
507
|
+
let nonIndexedFirst = 0;
|
|
508
|
+
let drawCursor = 0;
|
|
509
|
+
for (const [submeshIndex, submesh] of mesh.submeshes.entries()) {
|
|
510
|
+
const first = mesh.indexed ? submesh.indexOffset : nonIndexedFirst;
|
|
511
|
+
const count = mesh.indexed ? submesh.indexCount : submesh.vertexCount;
|
|
512
|
+
if (!mesh.indexed) nonIndexedFirst += submesh.vertexCount;
|
|
513
|
+
const draw = draws[drawCursor];
|
|
514
|
+
if (
|
|
515
|
+
draw !== undefined &&
|
|
516
|
+
draw.first === first &&
|
|
517
|
+
draw.count === count &&
|
|
518
|
+
draw.materialSlot === submesh.materialSlot &&
|
|
519
|
+
draw.topology === submesh.topology
|
|
520
|
+
) {
|
|
521
|
+
indices.set(submeshIndex, drawCursor);
|
|
522
|
+
drawCursor += 1;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return indices;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function isGpuShadowClaimedSubmesh(
|
|
529
|
+
source: RenderableSnapshot,
|
|
530
|
+
renderableIndex: number,
|
|
531
|
+
submeshIndex: number,
|
|
532
|
+
drawIndices: ReadonlyMap<number, number>,
|
|
533
|
+
gpuDrivenShadowDrawKeys: ReadonlySet<string> | undefined,
|
|
534
|
+
shadowDispatchByRenderableIdx: ShadowDispatchMap,
|
|
535
|
+
shadowCasterMembership: readonly ShadowCasterMembership[] | undefined,
|
|
536
|
+
shadowCasterWorldKeys: readonly number[] | undefined,
|
|
537
|
+
): boolean {
|
|
538
|
+
if (gpuDrivenShadowDrawKeys === undefined) return false;
|
|
539
|
+
const drawItemIndex = drawIndices.get(submeshIndex);
|
|
540
|
+
const draw = drawItemIndex === undefined ? undefined : source.gpuDrivenDraws?.[drawItemIndex];
|
|
541
|
+
if (drawItemIndex === undefined || draw === undefined) return false;
|
|
542
|
+
const material = source.materials[draw.materialSlot] ?? source.material;
|
|
543
|
+
const worldEntity = worldEntityKey(
|
|
544
|
+
shadowCasterWorldKeys?.[source.worldId] ?? source.worldId,
|
|
545
|
+
source.entityKey,
|
|
546
|
+
);
|
|
547
|
+
const sourceDrawItemIndex = draw.drawItemIndex ?? drawItemIndex;
|
|
548
|
+
if (shadowCasterMembership !== undefined) {
|
|
549
|
+
const matching = shadowCasterMembership.filter(
|
|
550
|
+
(membership) =>
|
|
551
|
+
membership.worldEntity === worldEntity &&
|
|
552
|
+
membership.materialHandle === (material.materialHandle ?? -1) &&
|
|
553
|
+
membership.drawItemIndex === sourceDrawItemIndex,
|
|
554
|
+
);
|
|
555
|
+
if (
|
|
556
|
+
matching.length === 0 ||
|
|
557
|
+
matching.some((membership) => membership.cpuReason !== undefined)
|
|
558
|
+
) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
return matching.every((membership) =>
|
|
562
|
+
gpuDrivenShadowDrawKeys.has(
|
|
563
|
+
gpuDrivenShadowDrawKey(
|
|
564
|
+
membership.worldEntity,
|
|
565
|
+
membership.materialHandle,
|
|
566
|
+
membership.drawItemIndex,
|
|
567
|
+
membership.passIndex,
|
|
568
|
+
),
|
|
569
|
+
),
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
const dispatches =
|
|
573
|
+
shadowDispatchByRenderableIdx.get(renderableIndex)?.get(material.materialHandle ?? 0) ?? [];
|
|
574
|
+
if (dispatches.length > 0) {
|
|
575
|
+
return dispatches.every((dispatch) =>
|
|
576
|
+
gpuDrivenShadowDrawKeys.has(
|
|
577
|
+
gpuDrivenShadowDrawKey(
|
|
578
|
+
worldEntity,
|
|
579
|
+
material.materialHandle ?? -1,
|
|
580
|
+
draw.drawItemIndex ?? drawItemIndex,
|
|
581
|
+
dispatch.passIndex,
|
|
582
|
+
),
|
|
583
|
+
),
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
const base = gpuDrivenShadowDrawKey(
|
|
587
|
+
worldEntity,
|
|
588
|
+
material.materialHandle ?? -1,
|
|
589
|
+
sourceDrawItemIndex,
|
|
590
|
+
0,
|
|
591
|
+
).split(':');
|
|
592
|
+
return [...gpuDrivenShadowDrawKeys].some((key) => {
|
|
593
|
+
const parts = key.split(':');
|
|
594
|
+
return (
|
|
595
|
+
parts.length === 4 && parts[0] === base[0] && parts[1] === base[1] && parts[2] === base[2]
|
|
596
|
+
);
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
interface GpuDrivenShadowRecordResult {
|
|
601
|
+
readonly claimedKeys: ReadonlySet<string>;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function shadowCasterKeys(c: _InternalRenderPipelineContext): ReadonlySet<string> {
|
|
605
|
+
if (c.shadowCasterMembership !== undefined) {
|
|
606
|
+
const keys = new Set<string>();
|
|
607
|
+
for (const membership of c.shadowCasterMembership) {
|
|
608
|
+
keys.add(
|
|
609
|
+
gpuDrivenShadowDrawKey(
|
|
610
|
+
membership.worldEntity,
|
|
611
|
+
membership.materialHandle,
|
|
612
|
+
membership.drawItemIndex,
|
|
613
|
+
membership.passIndex,
|
|
614
|
+
),
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
return keys;
|
|
618
|
+
}
|
|
619
|
+
return c.gpuDrivenShadowDrawKeys ?? new Set<string>();
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function validateShadowOwnership(
|
|
623
|
+
c: _InternalRenderPipelineContext,
|
|
624
|
+
identity: ShadowViewIdentity,
|
|
625
|
+
claimedKeys: ReadonlySet<string> | undefined,
|
|
626
|
+
recordedKeys: ReadonlySet<string>,
|
|
627
|
+
): void {
|
|
628
|
+
if (
|
|
629
|
+
c.shadowCasterMembership === undefined &&
|
|
630
|
+
(c.gpuDrivenShadowDrawKeys === undefined || c.gpuDrivenShadowDrawKeys.size === 0)
|
|
631
|
+
)
|
|
632
|
+
return;
|
|
633
|
+
const expectedKeys = shadowCasterKeys(c);
|
|
634
|
+
const claimed = claimedKeys ?? new Set<string>();
|
|
635
|
+
const overlap = [...claimed].filter((key) => recordedKeys.has(key));
|
|
636
|
+
const union = new Set([...claimed, ...recordedKeys]);
|
|
637
|
+
const missing = [...expectedKeys].filter((key) => !union.has(key));
|
|
638
|
+
const unexpectedClaims = [...claimed].filter((key) => !expectedKeys.has(key));
|
|
639
|
+
// `shadowCasterMembership` describes the concrete draws that the GPU
|
|
640
|
+
// producer is eligible to claim. A renderable that stays on the CPU lane
|
|
641
|
+
// (for example a clustered frame without a compatible GPU-driven PBR
|
|
642
|
+
// variant, or a dynamic mesh whose producer receipt is not ready) still
|
|
643
|
+
// records a valid ShadowCaster draw, but it has no membership row. Treating
|
|
644
|
+
// every CPU-only key as an "extra" made the ownership check reject that
|
|
645
|
+
// legitimate residual path and stopped the whole shadow pass. Ownership
|
|
646
|
+
// needs two invariants here: a draw is never both GPU-claimed and CPU-
|
|
647
|
+
// recorded, and every GPU-eligible membership is covered by one of those
|
|
648
|
+
// lanes. CPU-only residuals are already owned by the record path.
|
|
649
|
+
if (overlap.length === 0 && missing.length === 0 && unexpectedClaims.length === 0) return;
|
|
650
|
+
throw new RhiError({
|
|
651
|
+
code: 'internal-error',
|
|
652
|
+
expected: `disjoint ShadowCaster ownership for ${shadowViewIdentityKey(identity)}`,
|
|
653
|
+
hint: `claimed=${[...claimed].sort().join(',')} recorded=${[...recordedKeys].sort().join(',')} missing=${missing.sort().join(',')} unexpectedClaims=${unexpectedClaims.sort().join(',')}`,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function hasUnclaimedShadowCasters(
|
|
658
|
+
c: _InternalRenderPipelineContext,
|
|
659
|
+
claimedKeys: ReadonlySet<string> | undefined,
|
|
660
|
+
): boolean {
|
|
661
|
+
const extractedKeys =
|
|
662
|
+
c.shadowCasterMembership === undefined
|
|
663
|
+
? [...(c.gpuDrivenShadowDrawKeys ?? [])]
|
|
664
|
+
: c.shadowCasterMembership.map((membership) =>
|
|
665
|
+
gpuDrivenShadowDrawKey(
|
|
666
|
+
membership.worldEntity,
|
|
667
|
+
membership.materialHandle,
|
|
668
|
+
membership.drawItemIndex,
|
|
669
|
+
membership.passIndex,
|
|
670
|
+
),
|
|
671
|
+
);
|
|
672
|
+
if (extractedKeys.length === 0) return false;
|
|
673
|
+
return claimedKeys === undefined || extractedKeys.some((key) => !claimedKeys.has(key));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function hasExpectedShadowCasters(c: _InternalRenderPipelineContext): boolean {
|
|
677
|
+
if ((c.shadowCasterMembership?.length ?? 0) > 0) return true;
|
|
678
|
+
if ((c.gpuDrivenShadowDrawKeys?.size ?? 0) > 0) return true;
|
|
679
|
+
return shadowDispatchEntries(c).some((entry) => entry.tags.LightMode === 'ShadowCaster');
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function recordGpuDrivenShadowIfPublished(
|
|
683
|
+
c: _InternalRenderPipelineContext,
|
|
684
|
+
pass: RhiRenderPassEncoder,
|
|
685
|
+
identity: ShadowViewIdentity,
|
|
686
|
+
): GpuDrivenShadowRecordResult | undefined {
|
|
687
|
+
const viewSubmission = c.gpuDrivenShadowViews?.submission(identity);
|
|
688
|
+
const projections = c.gpuDrivenShadowBatchProjections?.get(shadowViewIdentityKey(identity));
|
|
689
|
+
if (viewSubmission === undefined) {
|
|
690
|
+
return undefined;
|
|
691
|
+
}
|
|
692
|
+
if (projections === undefined) {
|
|
693
|
+
throw new RhiError({
|
|
694
|
+
code: 'rhi-not-available',
|
|
695
|
+
expected: `GPU-driven shadow projection for ${shadowViewIdentityKey(identity)}`,
|
|
696
|
+
hint: 'terminate the capable shadow frame instead of falling through to CPU caster enumeration',
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
const batches = new Map<number, GpuDrivenShadowBatch>();
|
|
700
|
+
for (const batch of viewSubmission.plan.batches) {
|
|
701
|
+
const projection: GpuDrivenShadowBatchProjection | undefined = projections.get(batch.batchId);
|
|
702
|
+
if (projection === undefined) {
|
|
703
|
+
throw new RhiError({
|
|
704
|
+
code: 'internal-error',
|
|
705
|
+
expected: `projected mesh binding for GPU-driven shadow batch ${batch.batchId}`,
|
|
706
|
+
hint: 'keep shadow batch scene rows aligned with the shared SubmissionPlan',
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
const shadowArtifact = projection.shadowArtifact;
|
|
710
|
+
const shadowReceipt = shadowArtifact.receipt;
|
|
711
|
+
if (shadowReceipt === undefined) {
|
|
712
|
+
throw new RhiError({
|
|
713
|
+
code: 'rhi-not-available',
|
|
714
|
+
expected: `published shadow MaterialProgramAbi for batch ${batch.batchId}`,
|
|
715
|
+
hint: 'keep the shadow draw out of the capable lane until its producer receipt is ready',
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
const shadowVariantSet =
|
|
719
|
+
shadowArtifact.variantSet ??
|
|
720
|
+
(shadowArtifact.material === SHADOW_CASTER_SHADER_ID
|
|
721
|
+
? shadowCasterVariantSet(
|
|
722
|
+
c.runtime.device.caps.storageBuffer,
|
|
723
|
+
projection.deformation === 'skin',
|
|
724
|
+
true,
|
|
725
|
+
batch.key.admission === 'alpha-mask',
|
|
726
|
+
projection.vertexColorAvailable,
|
|
727
|
+
)
|
|
728
|
+
: undefined);
|
|
729
|
+
const pipeline = c.runtime.getMaterialShaderPipeline?.(
|
|
730
|
+
// A scene-index shadow artifact may retain the logical material id for
|
|
731
|
+
// batching while its specialization key owns the actual WGSL module.
|
|
732
|
+
shadowArtifact.specializationKey ?? shadowArtifact.material,
|
|
733
|
+
false,
|
|
734
|
+
shadowRasterState(undefined, identity.kind === 'point'),
|
|
735
|
+
batch.key.topology,
|
|
736
|
+
batch.key.drawKind === 'indexed' ? projection.mesh.indexFormat : undefined,
|
|
737
|
+
shadowVariantSet,
|
|
738
|
+
'shadow-caster',
|
|
739
|
+
undefined,
|
|
740
|
+
undefined,
|
|
741
|
+
undefined,
|
|
742
|
+
undefined,
|
|
743
|
+
undefined,
|
|
744
|
+
undefined,
|
|
745
|
+
projection.mesh.layoutProjection,
|
|
746
|
+
// The shadow projection carries the producer-validated receipt. Select
|
|
747
|
+
// the immediate module adapter so a newly loaded custom Surface cannot
|
|
748
|
+
// lose its first capable shadow draw to async pre-bake warm-up.
|
|
749
|
+
'immediate',
|
|
750
|
+
projection.deformation === 'skin' ? 'gpu-driven-skin' : 'gpu-driven-pbr',
|
|
751
|
+
shadowReceipt.sceneIndexEntry,
|
|
752
|
+
undefined,
|
|
753
|
+
projection.shadowVertexEntry,
|
|
754
|
+
projection.shadowFragmentEntry,
|
|
755
|
+
);
|
|
756
|
+
if (pipeline === null || pipeline === undefined) {
|
|
757
|
+
throw new RhiError({
|
|
758
|
+
code: 'rhi-not-available',
|
|
759
|
+
expected: `GPU-driven shadow pipeline variant for batch ${batch.batchId}`,
|
|
760
|
+
hint: 'publish the exact scene-index shadow variant before entering the capable lane',
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
if (batch.key.drawKind === 'indexed' && projection.mesh.indexBuffer === null) {
|
|
764
|
+
throw new RhiError({
|
|
765
|
+
code: 'internal-error',
|
|
766
|
+
expected: `indexed mesh buffer for GPU-driven shadow batch ${batch.batchId}`,
|
|
767
|
+
hint: 'keep the geometry residency and topology draw kind aligned before capable recording',
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
const materialGroup =
|
|
771
|
+
shadowArtifact.material !== SHADOW_CASTER_SHADER_ID
|
|
772
|
+
? ensureGpuDrivenCustomShadowMaterialBg(c, projection)
|
|
773
|
+
: batch.key.admission === 'alpha-mask'
|
|
774
|
+
? ensureGpuDrivenAlphaMaskMaterialBg(c, projection)
|
|
775
|
+
: ensureSpotShadowMaterialBg(c);
|
|
776
|
+
if (materialGroup === null) {
|
|
777
|
+
throw new RhiError({
|
|
778
|
+
code: 'rhi-not-available',
|
|
779
|
+
expected: 'material resources for every GPU shadow batch',
|
|
780
|
+
hint: 'publish the shadow material binding before recording the indirect batch',
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
batches.set(batch.batchId, {
|
|
784
|
+
pipeline,
|
|
785
|
+
meshBindGroup: projection.meshBindGroup,
|
|
786
|
+
visibleBindGroup: projection.visibleBindGroup,
|
|
787
|
+
deformation: projection.deformation,
|
|
788
|
+
materialGroup,
|
|
789
|
+
vertexBuffer: projection.mesh.vertexBuffer.handle,
|
|
790
|
+
...(batch.key.drawKind === 'indexed' && projection.mesh.indexBuffer !== null
|
|
791
|
+
? {
|
|
792
|
+
indexBuffer: projection.mesh.indexBuffer.handle,
|
|
793
|
+
indexFormat: projection.mesh.indexFormat,
|
|
794
|
+
}
|
|
795
|
+
: {}),
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
const submission: GpuDrivenShadowSubmission = {
|
|
799
|
+
view: viewSubmission.view,
|
|
800
|
+
plan: viewSubmission.plan,
|
|
801
|
+
batches,
|
|
802
|
+
};
|
|
803
|
+
recordGpuDrivenShadowIndirect(pass, submission);
|
|
804
|
+
const viewKey = shadowViewIdentityKey(identity);
|
|
805
|
+
const viewClaims = c.gpuDrivenShadowDrawKeysByView;
|
|
806
|
+
return {
|
|
807
|
+
claimedKeys:
|
|
808
|
+
viewClaims === undefined
|
|
809
|
+
? (c.gpuDrivenShadowDrawKeys ?? new Set<string>())
|
|
810
|
+
: (viewClaims.get(viewKey) ?? new Set<string>()),
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
366
814
|
export function encodeDirectionalShadowPass(
|
|
367
815
|
c: _InternalRenderPipelineContext,
|
|
368
816
|
pass: RhiRenderPassEncoder,
|
|
369
817
|
cascadeIndex: number,
|
|
370
818
|
viewport: { readonly x: number; readonly y: number; readonly w: number; readonly h: number },
|
|
819
|
+
drawFeatures?: (view: BindGroup) => void,
|
|
371
820
|
): void {
|
|
372
|
-
|
|
821
|
+
const directionalIdentity = { kind: 'directional' as const, index: cascadeIndex };
|
|
822
|
+
if (
|
|
823
|
+
(c.gpuDrivenShadowViews?.cacheHit(directionalIdentity) === true ||
|
|
824
|
+
(c.gpuDrivenShadowViews === undefined && c.directionalShadowCacheReuse)) &&
|
|
825
|
+
drawFeatures === undefined
|
|
826
|
+
)
|
|
827
|
+
return;
|
|
373
828
|
const pipeline = shadowPipeline(c);
|
|
374
829
|
const viewBg = ensureTypedShadowViewBg(
|
|
375
830
|
c,
|
|
@@ -377,25 +832,94 @@ export function encodeDirectionalShadowPass(
|
|
|
377
832
|
directionalShadowCasterOffset(cascadeIndex),
|
|
378
833
|
`view-shadow-directional-${cascadeIndex}`,
|
|
379
834
|
);
|
|
380
|
-
const materialBg = ensureSpotShadowMaterialBg(c);
|
|
381
|
-
|
|
835
|
+
const materialBg = viewBg === null ? null : ensureSpotShadowMaterialBg(c);
|
|
836
|
+
const capable = c.gpuDrivenShadowViews?.submission(directionalIdentity) !== undefined;
|
|
837
|
+
if (viewBg === null || materialBg === null) {
|
|
382
838
|
c.frameState.directionalShadowCacheRecorded = false;
|
|
839
|
+
if (capable || hasExpectedShadowCasters(c)) {
|
|
840
|
+
throw new RhiError({
|
|
841
|
+
code: 'rhi-not-available',
|
|
842
|
+
expected: 'directional ShadowCaster bind groups',
|
|
843
|
+
hint: 'publish the view/material resources before recording the shadow lane',
|
|
844
|
+
});
|
|
845
|
+
}
|
|
383
846
|
return;
|
|
384
847
|
}
|
|
848
|
+
c.frameState.directionalShadowCacheRecorded = true;
|
|
385
849
|
pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
|
|
386
|
-
|
|
850
|
+
drawFeatures?.(viewBg);
|
|
387
851
|
pass.setBindGroup(0, viewBg, [0, 0]);
|
|
388
852
|
pass.setBindGroup(1, materialBg, [0]);
|
|
389
|
-
const
|
|
853
|
+
const gpuRecord = recordGpuDrivenShadowIfPublished(c, pass, directionalIdentity);
|
|
854
|
+
const gpuRecorded = gpuRecord !== undefined;
|
|
855
|
+
const claimedKeys = gpuRecord?.claimedKeys;
|
|
856
|
+
if (capable && !gpuRecorded) {
|
|
857
|
+
throw new RhiError({
|
|
858
|
+
code: 'rhi-not-available',
|
|
859
|
+
expected: 'capable directional GPU-driven shadow projection',
|
|
860
|
+
hint: 'terminate the capable shadow frame instead of enumerating CPU casters',
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
if (!hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
864
|
+
if (gpuRecorded || (!capable && !hasExpectedShadowCasters(c))) {
|
|
865
|
+
validateShadowOwnership(c, directionalIdentity, claimedKeys, new Set());
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (pipeline === null) {
|
|
870
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
871
|
+
validateShadowOwnership(c, directionalIdentity, claimedKeys, new Set());
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
if (gpuRecorded) {
|
|
875
|
+
throw new RhiError({
|
|
876
|
+
code: 'rhi-not-available',
|
|
877
|
+
expected: 'CPU residual directional shadow pipeline',
|
|
878
|
+
hint: 'publish the legacy residual caster pipeline alongside the partial GPU shadow lane',
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
throw new RhiError({
|
|
882
|
+
code: 'rhi-not-available',
|
|
883
|
+
expected: 'directional ShadowCaster pipeline',
|
|
884
|
+
hint: 'publish the CPU ShadowCaster pipeline before recording residual casters',
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
pass.setPipeline(pipeline);
|
|
888
|
+
const legacyMeshBindGroup = c.meshBindGroup;
|
|
889
|
+
if (legacyMeshBindGroup === null) {
|
|
890
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
891
|
+
validateShadowOwnership(c, directionalIdentity, claimedKeys, new Set());
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
if (gpuRecorded) {
|
|
895
|
+
throw new RhiError({
|
|
896
|
+
code: 'rhi-not-available',
|
|
897
|
+
expected: 'CPU residual directional shadow mesh binding',
|
|
898
|
+
hint: 'publish legacy mesh resources for the unclaimed ShadowCaster residual',
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
throw new RhiError({
|
|
902
|
+
code: 'rhi-not-available',
|
|
903
|
+
expected: 'directional ShadowCaster mesh binding',
|
|
904
|
+
hint: 'publish the CPU mesh binding before recording residual casters',
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
if (gpuRecorded) pass.setBindGroup(1, materialBg, [0]);
|
|
908
|
+
const recordedKeys = recordShadowCasterDraws(
|
|
390
909
|
c,
|
|
391
910
|
pass,
|
|
392
911
|
pipeline,
|
|
393
|
-
|
|
394
|
-
buildMatchedRenderableIndices(c
|
|
912
|
+
legacyMeshBindGroup,
|
|
913
|
+
buildMatchedRenderableIndices(shadowDispatchEntries(c), { LightMode: ['ShadowCaster'] }),
|
|
914
|
+
buildMatchedMaterialHandlesByRenderable(shadowDispatchEntries(c), {
|
|
915
|
+
LightMode: ['ShadowCaster'],
|
|
916
|
+
}),
|
|
395
917
|
shadowShaderMap(c),
|
|
918
|
+
claimedKeys,
|
|
396
919
|
);
|
|
920
|
+
validateShadowOwnership(c, directionalIdentity, claimedKeys, recordedKeys);
|
|
397
921
|
c.frameState.directionalShadowCacheRecorded =
|
|
398
|
-
cascadeIndex === 0 ?
|
|
922
|
+
cascadeIndex === 0 ? true : c.frameState.directionalShadowCacheRecorded;
|
|
399
923
|
}
|
|
400
924
|
|
|
401
925
|
export function encodePointShadowPass(
|
|
@@ -403,35 +927,110 @@ export function encodePointShadowPass(
|
|
|
403
927
|
pass: RhiRenderPassEncoder,
|
|
404
928
|
snapshotIndex: number,
|
|
405
929
|
face: number,
|
|
930
|
+
drawFeatures?: (view: BindGroup) => void,
|
|
406
931
|
): void {
|
|
407
932
|
const snapshot = c.frameState.pointShadowSnapshots[snapshotIndex];
|
|
408
|
-
if (snapshot === undefined
|
|
409
|
-
const pipeline = shadowPipeline(c);
|
|
933
|
+
if (snapshot === undefined) return;
|
|
934
|
+
const pipeline = shadowPipeline(c, true);
|
|
410
935
|
const viewBg = ensureTypedShadowViewBg(
|
|
411
936
|
c,
|
|
412
937
|
pointShadowViewOffset(snapshot.shadowAtlasLayer, face),
|
|
413
938
|
0,
|
|
414
939
|
`view-shadow-point-${snapshot.shadowAtlasLayer}-${face}`,
|
|
415
940
|
);
|
|
416
|
-
const materialBg = ensureSpotShadowMaterialBg(c);
|
|
417
|
-
|
|
418
|
-
|
|
941
|
+
const materialBg = viewBg === null ? null : ensureSpotShadowMaterialBg(c);
|
|
942
|
+
const pointIdentity = { kind: 'point' as const, index: snapshotIndex, face };
|
|
943
|
+
const capable = c.gpuDrivenShadowViews?.submission(pointIdentity) !== undefined;
|
|
944
|
+
if (viewBg === null || materialBg === null) {
|
|
945
|
+
if (capable || hasExpectedShadowCasters(c))
|
|
946
|
+
throw new RhiError({
|
|
947
|
+
code: 'rhi-not-available',
|
|
948
|
+
expected: 'point ShadowCaster bind groups',
|
|
949
|
+
hint: 'publish the view/material resources before recording the shadow lane',
|
|
950
|
+
});
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (pipeline === null && !capable && !hasExpectedShadowCasters(c) && drawFeatures === undefined)
|
|
954
|
+
return;
|
|
955
|
+
drawFeatures?.(viewBg);
|
|
419
956
|
pass.setBindGroup(0, viewBg, [0, 0]);
|
|
420
957
|
pass.setBindGroup(1, materialBg, [0]);
|
|
421
|
-
|
|
958
|
+
const gpuRecord = recordGpuDrivenShadowIfPublished(c, pass, pointIdentity);
|
|
959
|
+
const gpuRecorded = gpuRecord !== undefined;
|
|
960
|
+
const claimedKeys = gpuRecord?.claimedKeys;
|
|
961
|
+
if (capable && !gpuRecorded) {
|
|
962
|
+
throw new RhiError({
|
|
963
|
+
code: 'rhi-not-available',
|
|
964
|
+
expected: 'capable point GPU-driven shadow projection',
|
|
965
|
+
hint: 'terminate the capable shadow frame instead of enumerating CPU casters',
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
if (!hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
969
|
+
if (gpuRecorded || (!capable && !hasExpectedShadowCasters(c))) {
|
|
970
|
+
validateShadowOwnership(c, pointIdentity, claimedKeys, new Set());
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (pipeline === null) {
|
|
975
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
976
|
+
validateShadowOwnership(c, pointIdentity, claimedKeys, new Set());
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
if (gpuRecorded) {
|
|
980
|
+
throw new RhiError({
|
|
981
|
+
code: 'rhi-not-available',
|
|
982
|
+
expected: 'CPU residual point shadow pipeline',
|
|
983
|
+
hint: 'publish the legacy residual caster pipeline alongside the partial GPU shadow lane',
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
throw new RhiError({
|
|
987
|
+
code: 'rhi-not-available',
|
|
988
|
+
expected: 'point ShadowCaster pipeline',
|
|
989
|
+
hint: 'publish the CPU ShadowCaster pipeline before recording residual casters',
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
pass.setPipeline(pipeline);
|
|
993
|
+
const legacyMeshBindGroup = c.meshBindGroup;
|
|
994
|
+
if (legacyMeshBindGroup === null) {
|
|
995
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
996
|
+
validateShadowOwnership(c, pointIdentity, claimedKeys, new Set());
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
if (gpuRecorded) {
|
|
1000
|
+
throw new RhiError({
|
|
1001
|
+
code: 'rhi-not-available',
|
|
1002
|
+
expected: 'CPU residual point shadow mesh binding',
|
|
1003
|
+
hint: 'publish legacy mesh resources for the unclaimed ShadowCaster residual',
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
throw new RhiError({
|
|
1007
|
+
code: 'rhi-not-available',
|
|
1008
|
+
expected: 'point ShadowCaster mesh binding',
|
|
1009
|
+
hint: 'publish the CPU mesh binding before recording residual casters',
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
if (gpuRecorded) pass.setBindGroup(1, materialBg, [0]);
|
|
1013
|
+
const recordedKeys = recordShadowCasterDraws(
|
|
422
1014
|
c,
|
|
423
1015
|
pass,
|
|
424
1016
|
pipeline,
|
|
425
|
-
|
|
426
|
-
buildMatchedRenderableIndices(c
|
|
1017
|
+
legacyMeshBindGroup,
|
|
1018
|
+
buildMatchedRenderableIndices(shadowDispatchEntries(c), { LightMode: ['ShadowCaster'] }),
|
|
1019
|
+
buildMatchedMaterialHandlesByRenderable(shadowDispatchEntries(c), {
|
|
1020
|
+
LightMode: ['ShadowCaster'],
|
|
1021
|
+
}),
|
|
427
1022
|
shadowShaderMap(c),
|
|
1023
|
+
claimedKeys,
|
|
1024
|
+
true,
|
|
428
1025
|
);
|
|
1026
|
+
validateShadowOwnership(c, pointIdentity, claimedKeys, recordedKeys);
|
|
429
1027
|
}
|
|
430
1028
|
|
|
431
1029
|
export function encodeSpotShadowPass(
|
|
432
1030
|
c: _InternalRenderPipelineContext,
|
|
433
1031
|
pass: RhiRenderPassEncoder,
|
|
434
1032
|
snapshotIndex: number,
|
|
1033
|
+
drawFeatures?: (view: BindGroup) => void,
|
|
435
1034
|
): void {
|
|
436
1035
|
const snapshot = c.frameState.spotShadowSnapshots.filter(
|
|
437
1036
|
(candidate) => candidate.shadowAtlasTile >= 0 && candidate.lightViewProj !== undefined,
|
|
@@ -439,8 +1038,7 @@ export function encodeSpotShadowPass(
|
|
|
439
1038
|
if (
|
|
440
1039
|
snapshot === undefined ||
|
|
441
1040
|
snapshot.shadowAtlasTile < 0 ||
|
|
442
|
-
snapshot.lightViewProj === undefined
|
|
443
|
-
c.meshBindGroup === null
|
|
1041
|
+
snapshot.lightViewProj === undefined
|
|
444
1042
|
) {
|
|
445
1043
|
return;
|
|
446
1044
|
}
|
|
@@ -451,8 +1049,20 @@ export function encodeSpotShadowPass(
|
|
|
451
1049
|
spotShadowCasterOffset(snapshot.shadowAtlasTile),
|
|
452
1050
|
`view-shadow-spot-${snapshot.shadowAtlasTile}`,
|
|
453
1051
|
);
|
|
454
|
-
const materialBg = ensureSpotShadowMaterialBg(c);
|
|
455
|
-
|
|
1052
|
+
const materialBg = viewBg === null ? null : ensureSpotShadowMaterialBg(c);
|
|
1053
|
+
const spotIdentity = { kind: 'spot' as const, index: snapshotIndex };
|
|
1054
|
+
const capable = c.gpuDrivenShadowViews?.submission(spotIdentity) !== undefined;
|
|
1055
|
+
if (viewBg === null || materialBg === null) {
|
|
1056
|
+
if (capable || hasExpectedShadowCasters(c))
|
|
1057
|
+
throw new RhiError({
|
|
1058
|
+
code: 'rhi-not-available',
|
|
1059
|
+
expected: 'spot ShadowCaster bind groups',
|
|
1060
|
+
hint: 'publish the view/material resources before recording the shadow lane',
|
|
1061
|
+
});
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
if (pipeline === null && !capable && !hasExpectedShadowCasters(c) && drawFeatures === undefined)
|
|
1065
|
+
return;
|
|
456
1066
|
const tileSize = c.pipelineState.perPassResources.shadowMapSize;
|
|
457
1067
|
const tile = snapshot.shadowAtlasTile;
|
|
458
1068
|
pass.setViewport(
|
|
@@ -463,21 +1073,81 @@ export function encodeSpotShadowPass(
|
|
|
463
1073
|
0,
|
|
464
1074
|
1,
|
|
465
1075
|
);
|
|
466
|
-
|
|
1076
|
+
drawFeatures?.(viewBg);
|
|
467
1077
|
pass.setBindGroup(0, viewBg, [0, 0]);
|
|
468
1078
|
pass.setBindGroup(1, materialBg, [0]);
|
|
469
1079
|
// Reuse the shared caster recorder. The compact spot-only loop used to
|
|
470
1080
|
// depend on instance bindings created by a directional pass, so a scene
|
|
471
1081
|
// containing only a SpotLight opened the atlas pass but emitted no caster
|
|
472
1082
|
// draws.
|
|
473
|
-
|
|
1083
|
+
const gpuRecord = recordGpuDrivenShadowIfPublished(c, pass, spotIdentity);
|
|
1084
|
+
const gpuRecorded = gpuRecord !== undefined;
|
|
1085
|
+
const claimedKeys = gpuRecord?.claimedKeys;
|
|
1086
|
+
if (capable && !gpuRecorded) {
|
|
1087
|
+
throw new RhiError({
|
|
1088
|
+
code: 'rhi-not-available',
|
|
1089
|
+
expected: 'capable spot GPU-driven shadow projection',
|
|
1090
|
+
hint: 'terminate the capable shadow frame instead of enumerating CPU casters',
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
if (!hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
1094
|
+
if (gpuRecorded || (!capable && !hasExpectedShadowCasters(c))) {
|
|
1095
|
+
validateShadowOwnership(c, spotIdentity, claimedKeys, new Set());
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (pipeline === null) {
|
|
1100
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
1101
|
+
validateShadowOwnership(c, spotIdentity, claimedKeys, new Set());
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
if (gpuRecorded) {
|
|
1105
|
+
throw new RhiError({
|
|
1106
|
+
code: 'rhi-not-available',
|
|
1107
|
+
expected: 'CPU residual spot shadow pipeline',
|
|
1108
|
+
hint: 'publish the legacy residual caster pipeline alongside the partial GPU shadow lane',
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
throw new RhiError({
|
|
1112
|
+
code: 'rhi-not-available',
|
|
1113
|
+
expected: 'spot ShadowCaster pipeline',
|
|
1114
|
+
hint: 'publish the CPU ShadowCaster pipeline before recording residual casters',
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
pass.setPipeline(pipeline);
|
|
1118
|
+
const legacyMeshBindGroup = c.meshBindGroup;
|
|
1119
|
+
if (legacyMeshBindGroup === null) {
|
|
1120
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
1121
|
+
validateShadowOwnership(c, spotIdentity, claimedKeys, new Set());
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
if (gpuRecorded) {
|
|
1125
|
+
throw new RhiError({
|
|
1126
|
+
code: 'rhi-not-available',
|
|
1127
|
+
expected: 'CPU residual spot shadow mesh binding',
|
|
1128
|
+
hint: 'publish legacy mesh resources for the unclaimed ShadowCaster residual',
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
throw new RhiError({
|
|
1132
|
+
code: 'rhi-not-available',
|
|
1133
|
+
expected: 'spot ShadowCaster mesh binding',
|
|
1134
|
+
hint: 'publish the CPU mesh binding before recording residual casters',
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
if (gpuRecorded) pass.setBindGroup(1, materialBg, [0]);
|
|
1138
|
+
const recordedKeys = recordShadowCasterDraws(
|
|
474
1139
|
c,
|
|
475
1140
|
pass,
|
|
476
1141
|
pipeline,
|
|
477
|
-
|
|
478
|
-
buildMatchedRenderableIndices(c
|
|
1142
|
+
legacyMeshBindGroup,
|
|
1143
|
+
buildMatchedRenderableIndices(shadowDispatchEntries(c), { LightMode: ['ShadowCaster'] }),
|
|
1144
|
+
buildMatchedMaterialHandlesByRenderable(shadowDispatchEntries(c), {
|
|
1145
|
+
LightMode: ['ShadowCaster'],
|
|
1146
|
+
}),
|
|
479
1147
|
shadowShaderMap(c),
|
|
1148
|
+
claimedKeys,
|
|
480
1149
|
);
|
|
1150
|
+
validateShadowOwnership(c, spotIdentity, claimedKeys, recordedKeys);
|
|
481
1151
|
}
|
|
482
1152
|
|
|
483
1153
|
/**
|
|
@@ -541,16 +1211,41 @@ export function buildMatchedRenderableIndices(
|
|
|
541
1211
|
* filtering is insufficient for geometry recording: a Deferred graph pass
|
|
542
1212
|
* must not draw the same renderable's Forward-only material. Keep the
|
|
543
1213
|
* renderable key in the result so mixed-material meshes retain their
|
|
544
|
-
* per-submesh routing.
|
|
1214
|
+
* per-submesh routing. An optional exclusion selector removes a material
|
|
1215
|
+
* handle when that same renderable/material pair also contributes to another
|
|
1216
|
+
* graph lane (for example Deferred Standard geometry after SSR composition).
|
|
1217
|
+
* An inclusion selector intersects membership by the same renderable/material
|
|
1218
|
+
* pair without replacing the selected shader pass or its authored entries.
|
|
545
1219
|
*/
|
|
546
1220
|
export function buildMatchedMaterialHandlesByRenderable(
|
|
547
1221
|
dispatch: readonly DispatchEntry[],
|
|
548
1222
|
selector: PassSelector,
|
|
1223
|
+
excludeSelector?: PassSelector,
|
|
1224
|
+
includeSelector?: PassSelector,
|
|
549
1225
|
): ReadonlyMap<number, ReadonlySet<number>> | null {
|
|
550
1226
|
if (dispatch.length === 0) return null;
|
|
551
1227
|
const matched = filterDispatchBySelector(dispatch, selector);
|
|
1228
|
+
const included =
|
|
1229
|
+
includeSelector === undefined
|
|
1230
|
+
? undefined
|
|
1231
|
+
: new Set(
|
|
1232
|
+
filterDispatchBySelector(dispatch, includeSelector).map(
|
|
1233
|
+
(entry) => `${entry.renderableIndex}:${entry.materialHandle}`,
|
|
1234
|
+
),
|
|
1235
|
+
);
|
|
1236
|
+
const excluded =
|
|
1237
|
+
excludeSelector === undefined
|
|
1238
|
+
? undefined
|
|
1239
|
+
: new Set(
|
|
1240
|
+
filterDispatchBySelector(dispatch, excludeSelector).map(
|
|
1241
|
+
(entry) => `${entry.renderableIndex}:${entry.materialHandle}`,
|
|
1242
|
+
),
|
|
1243
|
+
);
|
|
552
1244
|
const handlesByRenderable = new Map<number, Set<number>>();
|
|
553
1245
|
for (const entry of matched) {
|
|
1246
|
+
if (included !== undefined && !included.has(`${entry.renderableIndex}:${entry.materialHandle}`))
|
|
1247
|
+
continue;
|
|
1248
|
+
if (excluded?.has(`${entry.renderableIndex}:${entry.materialHandle}`)) continue;
|
|
554
1249
|
const handles = handlesByRenderable.get(entry.renderableIndex);
|
|
555
1250
|
if (handles === undefined) {
|
|
556
1251
|
handlesByRenderable.set(entry.renderableIndex, new Set([entry.materialHandle]));
|
|
@@ -569,25 +1264,36 @@ export function buildMatchedMaterialHandlesByRenderable(
|
|
|
569
1264
|
* and issues the per-submesh depth draws. `shadowPass` view/material bind
|
|
570
1265
|
* groups (@group 0/1) are already set by the caller; this loop owns @group
|
|
571
1266
|
* 2/3 + the vertex/index/pipeline de-dup state. Receives the explicit
|
|
572
|
-
* `_InternalRenderPipelineContext` (`c`) plus the caller-resolved
|
|
573
|
-
*
|
|
1267
|
+
* `_InternalRenderPipelineContext` (`c`) plus the caller-resolved
|
|
1268
|
+
* mesh bind group, pass-selector match set, and per-material
|
|
574
1269
|
* ShadowCaster dispatch map so no cross-function mutable state is introduced.
|
|
575
1270
|
*/
|
|
576
|
-
function recordShadowCasterDraws(
|
|
1271
|
+
export function recordShadowCasterDraws(
|
|
577
1272
|
c: _InternalRenderPipelineContext,
|
|
578
1273
|
shadowPass: RhiRenderPassEncoder,
|
|
579
1274
|
shadowPipeline: RenderPipeline,
|
|
580
1275
|
shadowMeshBindGroup: BindGroup,
|
|
581
1276
|
matchedIndices: Set<number> | null,
|
|
1277
|
+
matchedMaterialHandles: ReadonlyMap<number, ReadonlySet<number>> | null,
|
|
582
1278
|
shadowDispatchByRenderableIdx: ShadowDispatchMap,
|
|
583
|
-
|
|
584
|
-
|
|
1279
|
+
gpuDrivenShadowDrawKeys: ReadonlySet<string> | undefined,
|
|
1280
|
+
reflected = false,
|
|
1281
|
+
): ReadonlySet<string> {
|
|
1282
|
+
const {
|
|
1283
|
+
runtime,
|
|
1284
|
+
pipelineState,
|
|
1285
|
+
validatedOrdered: mainValidatedOrdered,
|
|
1286
|
+
shadowValidatedOrdered,
|
|
1287
|
+
} = c;
|
|
1288
|
+
const validatedOrdered = shadowValidatedOrdered ?? mainValidatedOrdered;
|
|
1289
|
+
const meshSsboBase = c.shadowMeshSsboBase ?? 0;
|
|
585
1290
|
// M-3 / w12: vertexBuffer/indexBuffer state locals migrate to GpuBuffer
|
|
586
1291
|
// (the wrapper) -- the de-dup compare uses wrapper identity (one wrapper
|
|
587
1292
|
// per RHI handle from gpuStore), and `.handle` is passed to the RHI
|
|
588
1293
|
// setVertexBuffer / setIndexBuffer call.
|
|
589
1294
|
let shadowLastVertexBuffer: GpuBuffer | null = null;
|
|
590
1295
|
let shadowLastIndexBuffer: GpuBuffer | null = null;
|
|
1296
|
+
const recordedKeys = new Set<string>();
|
|
591
1297
|
// bug-20260619-csm RC-3 (D-3): track the currently-bound shadow PSO so
|
|
592
1298
|
// per-entity setPipeline only fires on change (same de-dup discipline as
|
|
593
1299
|
// vertex/index buffers above). The default-shadow-caster PSO is already
|
|
@@ -595,7 +1301,6 @@ function recordShadowCasterDraws(
|
|
|
595
1301
|
// ShadowCaster PSO when a material supplies one.
|
|
596
1302
|
let shadowLastPipeline: RenderPipeline = shadowPipeline;
|
|
597
1303
|
let materialDeps: PerSubmeshMaterialBgDeps | undefined;
|
|
598
|
-
let complete = true;
|
|
599
1304
|
|
|
600
1305
|
for (let i = 0; i < validatedOrdered.length; i++) {
|
|
601
1306
|
const entry = validatedOrdered[i];
|
|
@@ -604,17 +1309,30 @@ function recordShadowCasterDraws(
|
|
|
604
1309
|
// feat-20260609 M2: skip entities that don't match the pass selector.
|
|
605
1310
|
if (matchedIndices !== null && !matchedIndices.has(entry.renderableIndex)) continue;
|
|
606
1311
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if (
|
|
610
|
-
|
|
611
|
-
continue;
|
|
1312
|
+
const drawIndices = shadowDrawIndicesBySubmesh(entry.source, entry.mesh);
|
|
1313
|
+
const shadowSubmeshes = entry.mesh.submeshes.flatMap((submesh, submeshIndex) => {
|
|
1314
|
+
if (submesh.topology !== 'triangle-list' && submesh.topology !== 'triangle-strip') {
|
|
1315
|
+
return [];
|
|
612
1316
|
}
|
|
613
|
-
if (
|
|
614
|
-
|
|
615
|
-
|
|
1317
|
+
if (matchedMaterialHandles !== null) {
|
|
1318
|
+
const handles = matchedMaterialHandles.get(entry.renderableIndex);
|
|
1319
|
+
const material = entry.source.materials[submesh.materialSlot] ?? entry.source.material;
|
|
1320
|
+
if (handles === undefined || !handles.has(material.materialHandle ?? -1)) return [];
|
|
616
1321
|
}
|
|
617
|
-
|
|
1322
|
+
return isGpuShadowClaimedSubmesh(
|
|
1323
|
+
entry.source,
|
|
1324
|
+
entry.renderableIndex,
|
|
1325
|
+
submeshIndex,
|
|
1326
|
+
drawIndices,
|
|
1327
|
+
gpuDrivenShadowDrawKeys,
|
|
1328
|
+
shadowDispatchByRenderableIdx,
|
|
1329
|
+
c.shadowCasterMembership,
|
|
1330
|
+
c.shadowCasterWorldKeys,
|
|
1331
|
+
)
|
|
1332
|
+
? []
|
|
1333
|
+
: [{ submesh, submeshIndex }];
|
|
1334
|
+
});
|
|
1335
|
+
if (shadowSubmeshes.length === 0) continue;
|
|
618
1336
|
// feat-20260604-mesh-topology-debug-draw M5 / w14 (AC-09, D-A6): the
|
|
619
1337
|
// shadow caster PSO is triangle-list; it only projects triangle faces.
|
|
620
1338
|
// line-list / line-strip / point-list meshes have no surface to cast a
|
|
@@ -624,14 +1342,6 @@ function recordShadowCasterDraws(
|
|
|
624
1342
|
//
|
|
625
1343
|
// feat-20260608 M4 / w16: per-submesh shadow draw — iterate submeshes
|
|
626
1344
|
// and skip non-triangle submeshes individually (each submesh may differ).
|
|
627
|
-
const shadowSubmeshes = entry.mesh.submeshes;
|
|
628
|
-
const hasAnyShadowSubmesh = shadowSubmeshes.some(
|
|
629
|
-
(sm) => sm.topology === 'triangle-list' || sm.topology === 'triangle-strip',
|
|
630
|
-
);
|
|
631
|
-
if (!hasAnyShadowSubmesh) {
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
1345
|
if (entry.mesh.vertexBuffer !== shadowLastVertexBuffer) {
|
|
636
1346
|
shadowPass.setVertexBuffer(0, entry.mesh.vertexBuffer.handle);
|
|
637
1347
|
shadowLastVertexBuffer = entry.mesh.vertexBuffer;
|
|
@@ -646,39 +1356,250 @@ function recordShadowCasterDraws(
|
|
|
646
1356
|
|
|
647
1357
|
if (entry.source.skin !== undefined) {
|
|
648
1358
|
const skinMeshBindGroup = skinnedShadowMeshBindGroup(c, entry);
|
|
649
|
-
if (skinMeshBindGroup === null)
|
|
1359
|
+
if (skinMeshBindGroup === null) {
|
|
1360
|
+
throw new RhiError({
|
|
1361
|
+
code: 'rhi-not-available',
|
|
1362
|
+
expected: 'CPU skinned ShadowCaster mesh binding',
|
|
1363
|
+
hint: 'publish the skin mesh/palette bind group before recording the residual',
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
650
1366
|
shadowPass.setBindGroup(
|
|
651
1367
|
2,
|
|
652
1368
|
skinMeshBindGroup,
|
|
653
|
-
pbrSkinMeshDynamicOffsets(
|
|
1369
|
+
pbrSkinMeshDynamicOffsets(
|
|
1370
|
+
(meshSsboBase + i) * MESH_PER_ENTITY_STRIDE,
|
|
1371
|
+
entry.source.skin.byteOffset,
|
|
1372
|
+
),
|
|
654
1373
|
);
|
|
655
1374
|
} else {
|
|
656
|
-
shadowPass.setBindGroup(2, shadowMeshBindGroup, [
|
|
1375
|
+
shadowPass.setBindGroup(2, shadowMeshBindGroup, [
|
|
1376
|
+
(meshSsboBase + i) * MESH_PER_ENTITY_STRIDE,
|
|
1377
|
+
]);
|
|
657
1378
|
}
|
|
658
1379
|
|
|
659
|
-
//
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
1380
|
+
// C1 + C2 (w12): per-entity instance buffer + instanceCount
|
|
1381
|
+
let shadowInstanceDraws: readonly { readonly buffer: Buffer; readonly count: number }[] = [
|
|
1382
|
+
{ buffer: pipelineState.identityInstanceBuffer, count: 1 },
|
|
1383
|
+
];
|
|
1384
|
+
const shadowInst = entry.source.instances;
|
|
1385
|
+
if (shadowInst?.instanceCount === 0) continue;
|
|
1386
|
+
if (shadowInst !== undefined) {
|
|
1387
|
+
const uniformFallback = runtime.device.caps.storageBuffer === false;
|
|
1388
|
+
// Uniform fallback uses resident chunks because the shader's uniform
|
|
1389
|
+
// array is capped at MAX_UNIFORM_INSTANCES entries.
|
|
1390
|
+
if (uniformFallback && shadowInst.instanceCount > MAX_UNIFORM_INSTANCES) {
|
|
1391
|
+
const ownerKey = instanceCollectionCacheKey(entry.source.worldId, shadowInst);
|
|
1392
|
+
const chunks = c.frameState.instanceBufferChunks;
|
|
1393
|
+
const activeKeys = new Set<string>();
|
|
1394
|
+
const draws: { buffer: Buffer; count: number }[] = [];
|
|
1395
|
+
const strideBytes = 16 * Float32Array.BYTES_PER_ELEMENT;
|
|
1396
|
+
const dirtyRanges = shadowInst.dirtyRanges ?? [{ start: 0, end: shadowInst.instanceCount }];
|
|
1397
|
+
const uploadedRanges: { start: number; end: number }[] = [];
|
|
1398
|
+
let uploadedBytes = 0;
|
|
1399
|
+
let failed = false;
|
|
1400
|
+
for (let start = 0; start < shadowInst.instanceCount; start += MAX_UNIFORM_INSTANCES) {
|
|
1401
|
+
const count = Math.min(MAX_UNIFORM_INSTANCES, shadowInst.instanceCount - start);
|
|
1402
|
+
const chunkKey = `${ownerKey}:${start}`;
|
|
1403
|
+
activeKeys.add(chunkKey);
|
|
1404
|
+
const payloadBytes = count * strideBytes;
|
|
1405
|
+
const previous = chunks?.get(chunkKey);
|
|
1406
|
+
let active =
|
|
1407
|
+
previous !== undefined &&
|
|
1408
|
+
previous.uploadedArchVersion === shadowInst.archVersion &&
|
|
1409
|
+
previous.uploadedByteLength === payloadBytes
|
|
1410
|
+
? previous
|
|
1411
|
+
: undefined;
|
|
1412
|
+
let activeIsNew = false;
|
|
1413
|
+
if (active === undefined) {
|
|
1414
|
+
const created = runtime.device.createBuffer({
|
|
1415
|
+
size: INSTANCE_UBO_FULL_ARRAY_BYTES,
|
|
1416
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1417
|
+
mappedAtCreation: false,
|
|
1418
|
+
});
|
|
1419
|
+
if (!created.ok) {
|
|
1420
|
+
runtime.errorRegistry.fire(created.error);
|
|
1421
|
+
failed = true;
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
if (previous !== undefined && !previous.buffer.isDestroyed) {
|
|
1425
|
+
const destroyed = previous.buffer.destroy();
|
|
1426
|
+
if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
|
|
1427
|
+
}
|
|
1428
|
+
active = {
|
|
1429
|
+
buffer: new GpuBuffer(runtime.device, created.value),
|
|
1430
|
+
uploadedArchVersion: shadowInst.archVersion,
|
|
1431
|
+
uploadedByteLength: payloadBytes,
|
|
1432
|
+
};
|
|
1433
|
+
activeIsNew = true;
|
|
1434
|
+
chunks?.set(chunkKey, active);
|
|
1435
|
+
if (chunks === undefined) c.frameState.transientInstanceBuffers.push(active);
|
|
1436
|
+
}
|
|
1437
|
+
const localRanges = dirtyRanges
|
|
1438
|
+
.map((range) => ({
|
|
1439
|
+
start: Math.max(start, range.start) - start,
|
|
1440
|
+
end: Math.min(start + count, range.end) - start,
|
|
1441
|
+
}))
|
|
1442
|
+
.filter((range) => range.end > range.start);
|
|
1443
|
+
const rangesToUpload = instanceUploadRangesForResident(activeIsNew, localRanges, {
|
|
1444
|
+
start: 0,
|
|
1445
|
+
end: count,
|
|
1446
|
+
});
|
|
1447
|
+
const needsUpload =
|
|
1448
|
+
activeIsNew ||
|
|
1449
|
+
(shadowInst.revision !== undefined && active.uploadedRevision !== shadowInst.revision);
|
|
1450
|
+
if (needsUpload && rangesToUpload.length > 0) {
|
|
1451
|
+
for (const range of rangesToUpload) {
|
|
1452
|
+
const source = shadowInst.transforms.subarray(
|
|
1453
|
+
(start + range.start) * 16,
|
|
1454
|
+
(start + range.end) * 16,
|
|
1455
|
+
);
|
|
1456
|
+
const written = runtime.device.queue.writeBuffer(
|
|
1457
|
+
active.buffer.handle,
|
|
1458
|
+
range.start * strideBytes,
|
|
1459
|
+
source,
|
|
1460
|
+
);
|
|
1461
|
+
if (!written.ok) {
|
|
1462
|
+
runtime.errorRegistry.fire(written.error);
|
|
1463
|
+
failed = true;
|
|
1464
|
+
break;
|
|
1465
|
+
}
|
|
1466
|
+
uploadedRanges.push({ start: start + range.start, end: start + range.end });
|
|
1467
|
+
uploadedBytes += source.byteLength;
|
|
1468
|
+
}
|
|
1469
|
+
if (failed) break;
|
|
1470
|
+
}
|
|
1471
|
+
if (needsUpload && shadowInst.revision !== undefined) {
|
|
1472
|
+
active = { ...active, uploadedRevision: shadowInst.revision };
|
|
1473
|
+
chunks?.set(chunkKey, active);
|
|
1474
|
+
}
|
|
1475
|
+
draws.push({ buffer: active.buffer.handle, count });
|
|
1476
|
+
}
|
|
1477
|
+
shadowInstanceDraws = failed
|
|
1478
|
+
? [{ buffer: pipelineState.identityInstanceBuffer, count: 1 }]
|
|
1479
|
+
: draws;
|
|
1480
|
+
if (chunks !== undefined) {
|
|
1481
|
+
const prefix = `${ownerKey}:`;
|
|
1482
|
+
for (const [key, stale] of chunks.entries()) {
|
|
1483
|
+
if (!key.startsWith(prefix) || activeKeys.has(key)) continue;
|
|
1484
|
+
if (!stale.buffer.isDestroyed) {
|
|
1485
|
+
const destroyed = stale.buffer.destroy();
|
|
1486
|
+
if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
|
|
1487
|
+
}
|
|
1488
|
+
chunks.delete(key);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
if (!failed) {
|
|
1492
|
+
reportInstanceResidency(c, shadowInst, {
|
|
1493
|
+
lane: 'chunked-uniform',
|
|
1494
|
+
requestedBytes: shadowInst.instanceCount * strideBytes,
|
|
1495
|
+
supportedBytes: MAX_UNIFORM_INSTANCES * strideBytes,
|
|
1496
|
+
uploadRanges: uploadedRanges,
|
|
1497
|
+
uploadedBytes,
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
} else {
|
|
1501
|
+
const bufUsage = uniformFallback
|
|
1502
|
+
? GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST
|
|
1503
|
+
: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST;
|
|
1504
|
+
const instancePayload = uniformFallback
|
|
1505
|
+
? shadowInst.transforms
|
|
1506
|
+
: packInstanceStorageBuffer(shadowInst.transforms);
|
|
1507
|
+
const requestedBytes = instancePayload.byteLength;
|
|
1508
|
+
const cap = runtime.device.limits.maxStorageBufferBindingSize;
|
|
1509
|
+
if (!uniformFallback && typeof cap === 'number' && cap > 0 && requestedBytes > cap) {
|
|
1510
|
+
const chunked = resolveStorageInstanceChunks(c, entry, shadowInst, cap, undefined, -1);
|
|
1511
|
+
if (chunked !== null) {
|
|
1512
|
+
shadowInstanceDraws = chunked.map((draw) => ({
|
|
1513
|
+
buffer: draw.instanceBuffer,
|
|
1514
|
+
count: draw.instanceCount,
|
|
1515
|
+
}));
|
|
1516
|
+
}
|
|
1517
|
+
} else {
|
|
1518
|
+
const instanceCacheKey = instanceCollectionCacheKey(entry.source.worldId, shadowInst);
|
|
1519
|
+
const cached = c.frameState.instanceBuffers.get(instanceCacheKey);
|
|
1520
|
+
let active: InstanceBufferCacheEntry | null = null;
|
|
1521
|
+
let activeIsNew = false;
|
|
1522
|
+
if (
|
|
1523
|
+
cached !== undefined &&
|
|
1524
|
+
cached.uploadedArchVersion === shadowInst.archVersion &&
|
|
1525
|
+
cached.uploadedByteLength === requestedBytes &&
|
|
1526
|
+
(shadowInst.revision === undefined
|
|
1527
|
+
? cached.uploadedRevision === undefined
|
|
1528
|
+
: cached.uploadedRevision === shadowInst.revision)
|
|
1529
|
+
) {
|
|
1530
|
+
active = cached;
|
|
1531
|
+
} else if (requestedBytes > 0) {
|
|
1532
|
+
const bufRes = runtime.device.createBuffer({
|
|
1533
|
+
size: requestedBytes,
|
|
1534
|
+
usage: bufUsage,
|
|
1535
|
+
mappedAtCreation: false,
|
|
1536
|
+
});
|
|
1537
|
+
if (!bufRes.ok) {
|
|
1538
|
+
runtime.errorRegistry.fire(bufRes.error);
|
|
1539
|
+
} else {
|
|
1540
|
+
// feat-20260619 M4 / F12: destroy the old cached buffer
|
|
1541
|
+
// before replacing it with the new one (D-6).
|
|
1542
|
+
if (cached !== undefined && !cached.buffer.isDestroyed) {
|
|
1543
|
+
const r = cached.buffer.destroy();
|
|
1544
|
+
if (!r.ok) runtime.errorRegistry.fire(r.error);
|
|
1545
|
+
}
|
|
1546
|
+
const newBuffer = new GpuBuffer(runtime.device, bufRes.value);
|
|
1547
|
+
active = {
|
|
1548
|
+
buffer: newBuffer,
|
|
1549
|
+
uploadedArchVersion: shadowInst.archVersion,
|
|
1550
|
+
uploadedByteLength: requestedBytes,
|
|
1551
|
+
};
|
|
1552
|
+
activeIsNew = true;
|
|
1553
|
+
if (active !== null) {
|
|
1554
|
+
// Keep the collection identity in the worldEntityKey namespace.
|
|
1555
|
+
c.frameState.instanceBuffers.set(instanceCacheKey, active);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
if (active !== null) {
|
|
1560
|
+
const needsUpload =
|
|
1561
|
+
activeIsNew ||
|
|
1562
|
+
(shadowInst.revision !== undefined &&
|
|
1563
|
+
active.uploadedRevision !== shadowInst.revision);
|
|
1564
|
+
if (needsUpload) {
|
|
1565
|
+
const writeRes = runtime.device.queue.writeBuffer(
|
|
1566
|
+
active.buffer.handle,
|
|
1567
|
+
0,
|
|
1568
|
+
instancePayload,
|
|
1569
|
+
);
|
|
1570
|
+
if (!writeRes.ok) {
|
|
1571
|
+
runtime.errorRegistry.fire(writeRes.error);
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1574
|
+
if (shadowInst.revision !== undefined) {
|
|
1575
|
+
const published = { ...active, uploadedRevision: shadowInst.revision };
|
|
1576
|
+
c.frameState.instanceBuffers.set(instanceCacheKey, published);
|
|
1577
|
+
active = published;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
reportInstanceResidency(c, shadowInst, {
|
|
1581
|
+
lane: uniformFallback ? 'direct-uniform' : 'direct-storage',
|
|
1582
|
+
requestedBytes,
|
|
1583
|
+
supportedBytes: uniformFallback
|
|
1584
|
+
? MAX_UNIFORM_INSTANCES * Float32Array.BYTES_PER_ELEMENT * 16
|
|
1585
|
+
: typeof cap === 'number'
|
|
1586
|
+
? cap
|
|
1587
|
+
: undefined,
|
|
1588
|
+
uploadRanges: needsUpload ? [{ start: 0, end: shadowInst.instanceCount }] : [],
|
|
1589
|
+
uploadedBytes: needsUpload ? instancePayload.byteLength : 0,
|
|
1590
|
+
});
|
|
1591
|
+
shadowInstanceDraws = [
|
|
1592
|
+
{ buffer: active.buffer.handle, count: Math.max(1, shadowInst.instanceCount) },
|
|
1593
|
+
];
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
677
1598
|
|
|
678
1599
|
// feat-20260608 M4 / w16: per-submesh shadow draw loop.
|
|
679
1600
|
// Only draw submeshes whose topology is triangle-list or triangle-strip
|
|
680
1601
|
// (line-list / point-list submeshes cast no shadow and are skipped).
|
|
681
|
-
for (const sm of shadowSubmeshes) {
|
|
1602
|
+
for (const { submesh: sm, submeshIndex } of shadowSubmeshes) {
|
|
682
1603
|
if (sm.topology !== 'triangle-list' && sm.topology !== 'triangle-strip') {
|
|
683
1604
|
continue;
|
|
684
1605
|
}
|
|
@@ -689,115 +1610,199 @@ function recordShadowCasterDraws(
|
|
|
689
1610
|
// two-sided casters (such as the Three.js teapot) populate the atlas
|
|
690
1611
|
// from both faces. Custom ShadowCaster shaders retain their own path.
|
|
691
1612
|
const submeshMaterial = entry.source.materials[sm.materialSlot] ?? entry.source.material;
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
)
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
complete = false;
|
|
733
|
-
continue;
|
|
734
|
-
}
|
|
735
|
-
if (entryShadowPipeline !== shadowLastPipeline && entryShadowPipeline !== null) {
|
|
736
|
-
shadowPass.setPipeline(entryShadowPipeline);
|
|
737
|
-
shadowLastPipeline = entryShadowPipeline;
|
|
738
|
-
}
|
|
739
|
-
if (entryShadowShaderId !== undefined && entryShadowShaderId !== SHADOW_CASTER_SHADER_ID) {
|
|
740
|
-
materialDeps ??= {
|
|
741
|
-
runtime,
|
|
742
|
-
pipelineState,
|
|
743
|
-
world: c.world,
|
|
744
|
-
store: c.store,
|
|
745
|
-
materialSlice: STANDARD_PBR_UBO_SIZE,
|
|
746
|
-
videoHighPerfAvailable: probeVideoHighPerfUpload(runtime.device),
|
|
747
|
-
skylightResources: prepareMaterialSkylight(c).skylightResources,
|
|
748
|
-
resolveRenderTargetTextureSource: runtime.resolveRenderTargetTextureSource,
|
|
749
|
-
materialBgShared: c.frameState.materialBgShared,
|
|
750
|
-
materialBgAssemblyCache: c.materialBgAssemblyCache,
|
|
751
|
-
frameState: c.frameState,
|
|
752
|
-
bindGroupCounts: c.bindGroupCounts,
|
|
753
|
-
};
|
|
754
|
-
const materialSlot =
|
|
755
|
-
c.materialSlotIndices[i]?.[sm.materialSlot] ?? c.materialSlotIndices[i]?.[0];
|
|
756
|
-
if (materialSlot === undefined) {
|
|
1613
|
+
const shadowDispatches =
|
|
1614
|
+
shadowDispatchByRenderableIdx
|
|
1615
|
+
.get(entry.renderableIndex)
|
|
1616
|
+
?.get(submeshMaterial.materialHandle ?? 0) ?? [];
|
|
1617
|
+
const compactDrawIndex = drawIndices.get(submeshIndex);
|
|
1618
|
+
const draw =
|
|
1619
|
+
compactDrawIndex === undefined
|
|
1620
|
+
? undefined
|
|
1621
|
+
: entry.source.gpuDrivenDraws?.[compactDrawIndex];
|
|
1622
|
+
const drawItemIndex = draw?.drawItemIndex ?? compactDrawIndex ?? submeshIndex;
|
|
1623
|
+
const worldEntity = worldEntityKey(
|
|
1624
|
+
c.shadowCasterWorldKeys?.[entry.source.worldId] ?? entry.source.worldId,
|
|
1625
|
+
entry.source.entityKey,
|
|
1626
|
+
);
|
|
1627
|
+
const residualDispatches =
|
|
1628
|
+
gpuDrivenShadowDrawKeys === undefined
|
|
1629
|
+
? shadowDispatches
|
|
1630
|
+
: shadowDispatches.filter(
|
|
1631
|
+
(dispatch) =>
|
|
1632
|
+
!gpuDrivenShadowDrawKeys.has(
|
|
1633
|
+
gpuDrivenShadowDrawKey(
|
|
1634
|
+
worldEntity,
|
|
1635
|
+
submeshMaterial.materialHandle ?? -1,
|
|
1636
|
+
drawItemIndex,
|
|
1637
|
+
dispatch.passIndex,
|
|
1638
|
+
),
|
|
1639
|
+
),
|
|
1640
|
+
);
|
|
1641
|
+
const passes = residualDispatches.length === 0 ? [undefined] : residualDispatches;
|
|
1642
|
+
if (gpuDrivenShadowDrawKeys !== undefined && residualDispatches.length === 0) continue;
|
|
1643
|
+
// Deformation selects the mesh/palette layout, never the material program.
|
|
1644
|
+
// Each residual keeps its authored shader, entries, state and bindings.
|
|
1645
|
+
for (const shadowDispatch of passes) {
|
|
1646
|
+
const entryShadowShaderId = shadowDispatch?.materialShaderId;
|
|
1647
|
+
const entryShadowRenderState = shadowRasterState(shadowDispatch?.renderState, reflected);
|
|
1648
|
+
const skinned = entry.source.skin !== undefined;
|
|
1649
|
+
let entryShadowPipeline: RenderPipeline | null = shadowPipeline;
|
|
1650
|
+
const needsDedicatedPipeline =
|
|
1651
|
+
shadowDispatch !== undefined || skinned || sm.topology !== 'triangle-list';
|
|
1652
|
+
if (shadowDispatch !== undefined && entryShadowShaderId === undefined) {
|
|
757
1653
|
throw new RhiError({
|
|
758
|
-
code: 'rhi-
|
|
759
|
-
expected: '
|
|
760
|
-
hint: '
|
|
1654
|
+
code: 'rhi-not-available',
|
|
1655
|
+
expected: 'material shader identity for CPU ShadowCaster residual',
|
|
1656
|
+
hint: 'publish a producer-owned ShadowCaster shader identity before recording the residual',
|
|
761
1657
|
});
|
|
762
1658
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1659
|
+
if (needsDedicatedPipeline) {
|
|
1660
|
+
entryShadowPipeline =
|
|
1661
|
+
runtime.getMaterialShaderPipeline?.(
|
|
1662
|
+
entryShadowShaderId ?? SHADOW_CASTER_SHADER_ID,
|
|
1663
|
+
false,
|
|
1664
|
+
entryShadowRenderState,
|
|
1665
|
+
sm.topology,
|
|
1666
|
+
entry.mesh.indexFormat,
|
|
1667
|
+
shadowCasterVariantSet(
|
|
1668
|
+
runtime.device.caps.storageBuffer,
|
|
1669
|
+
skinned,
|
|
1670
|
+
false,
|
|
1671
|
+
Number(shadowDispatch?.paramSnapshot?.alphaCutoff ?? 0) > 0,
|
|
1672
|
+
entry.mesh.layoutProjection.attributes.some(
|
|
1673
|
+
(attribute) => attribute.key === 'color',
|
|
1674
|
+
),
|
|
1675
|
+
),
|
|
1676
|
+
'shadow-caster',
|
|
1677
|
+
undefined, // meshAttributes
|
|
1678
|
+
1, // sampleCount
|
|
1679
|
+
undefined,
|
|
1680
|
+
undefined,
|
|
1681
|
+
undefined,
|
|
1682
|
+
undefined,
|
|
1683
|
+
entry.mesh.layoutProjection,
|
|
1684
|
+
// Built-in variants reuse validated prewarm modules, including
|
|
1685
|
+
// adapters with no synchronous shader factory. Authored residual
|
|
1686
|
+
// programs use their synchronous manifest producer at first draw.
|
|
1687
|
+
entryShadowShaderId === undefined || entryShadowShaderId === SHADOW_CASTER_SHADER_ID
|
|
1688
|
+
? 'validated'
|
|
1689
|
+
: 'immediate',
|
|
1690
|
+
skinned ? 'pbr-skin' : 'pbr',
|
|
1691
|
+
undefined, // vertexEntryPoint
|
|
1692
|
+
undefined, // additionalColorFormats
|
|
1693
|
+
shadowDispatch?.vertexEntry,
|
|
1694
|
+
shadowDispatch?.fragmentEntry,
|
|
1695
|
+
) ?? null;
|
|
1696
|
+
}
|
|
1697
|
+
if (entryShadowPipeline === null) {
|
|
1698
|
+
throw new RhiError({
|
|
1699
|
+
code: 'rhi-not-available',
|
|
1700
|
+
expected: `CPU ShadowCaster pipeline ${entryShadowShaderId ?? '<missing>'}`,
|
|
1701
|
+
hint: 'publish the exact residual ShadowCaster pipeline; do not substitute the default shader',
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
if (entryShadowPipeline !== shadowLastPipeline && entryShadowPipeline !== null) {
|
|
1705
|
+
shadowPass.setPipeline(entryShadowPipeline);
|
|
1706
|
+
shadowLastPipeline = entryShadowPipeline;
|
|
1707
|
+
}
|
|
1708
|
+
if (shadowDispatch !== undefined && entryShadowShaderId !== undefined) {
|
|
1709
|
+
materialDeps ??= {
|
|
1710
|
+
runtime,
|
|
1711
|
+
pipelineState,
|
|
1712
|
+
world: c.world,
|
|
1713
|
+
store: c.store,
|
|
1714
|
+
materialSlice: STANDARD_PBR_UBO_SIZE,
|
|
1715
|
+
videoHighPerfAvailable: probeVideoHighPerfUpload(runtime.device),
|
|
1716
|
+
skylightResources: prepareMaterialSkylight(c).skylightResources,
|
|
1717
|
+
resolveRenderTargetTextureSource: runtime.resolveRenderTargetTextureSource,
|
|
1718
|
+
materialBgShared: c.frameState.materialBgShared,
|
|
1719
|
+
materialBgAssemblyCache: c.materialBgAssemblyCache,
|
|
1720
|
+
frameState: c.frameState,
|
|
1721
|
+
bindGroupCounts: c.bindGroupCounts,
|
|
1722
|
+
};
|
|
1723
|
+
const materialSlot =
|
|
1724
|
+
shadowDispatch?.materialSlot ??
|
|
1725
|
+
c.materialSlotIndices[i]?.[sm.materialSlot] ??
|
|
1726
|
+
c.materialSlotIndices[i]?.[0];
|
|
1727
|
+
if (materialSlot === undefined) {
|
|
1728
|
+
throw new RhiError({
|
|
1729
|
+
code: 'rhi-descriptor-invalid',
|
|
1730
|
+
expected: 'a prepared material slot for each shadow submesh',
|
|
1731
|
+
hint: 'repair material slot preparation before recording shadows',
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
const materialGroup = buildPerSubmeshMaterialBg(
|
|
1735
|
+
materialDeps,
|
|
1736
|
+
submeshMaterial,
|
|
1737
|
+
entry.source.entityKey,
|
|
1738
|
+
entry.world ?? c.world,
|
|
1739
|
+
entryShadowShaderId,
|
|
1740
|
+
);
|
|
1741
|
+
shadowPass.setBindGroup(1, materialGroup, [materialSlot * MATERIAL_PER_ENTITY_STRIDE]);
|
|
780
1742
|
} else {
|
|
781
|
-
|
|
1743
|
+
const materialGroup = ensureSpotShadowMaterialBg(c);
|
|
1744
|
+
if (materialGroup === null) {
|
|
1745
|
+
throw new RhiError({
|
|
1746
|
+
code: 'rhi-not-available',
|
|
1747
|
+
expected: 'CPU residual shadow material bind group',
|
|
1748
|
+
hint: 'publish the residual material resources before recording shadows',
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
shadowPass.setBindGroup(1, materialGroup, [0]);
|
|
1752
|
+
}
|
|
1753
|
+
for (const instanceDraw of shadowInstanceDraws) {
|
|
1754
|
+
const shadowInstancesBg = resolveGeometryInstancesBindGroup(c, instanceDraw.buffer);
|
|
1755
|
+
shadowPass.setBindGroup(3, shadowInstancesBg);
|
|
1756
|
+
if (entry.mesh.indexed) {
|
|
1757
|
+
shadowPass.drawIndexed(sm.indexCount, instanceDraw.count, sm.indexOffset, 0, 0);
|
|
1758
|
+
} else {
|
|
1759
|
+
shadowPass.draw(sm.vertexCount, instanceDraw.count, 0, 0);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
if (shadowDispatch !== undefined) {
|
|
1763
|
+
recordedKeys.add(
|
|
1764
|
+
gpuDrivenShadowDrawKey(
|
|
1765
|
+
worldEntity,
|
|
1766
|
+
submeshMaterial.materialHandle ?? -1,
|
|
1767
|
+
drawItemIndex,
|
|
1768
|
+
shadowDispatch.passIndex,
|
|
1769
|
+
),
|
|
1770
|
+
);
|
|
782
1771
|
}
|
|
783
1772
|
}
|
|
784
1773
|
}
|
|
785
1774
|
}
|
|
786
|
-
return
|
|
1775
|
+
return recordedKeys;
|
|
787
1776
|
}
|
|
788
1777
|
|
|
789
1778
|
/**
|
|
790
|
-
* Build (or reuse) the
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
1779
|
+
* Build (or reuse) the neutral `shadow-material-singleton` @group(1) BG for
|
|
1780
|
+
* default casters without an authored material dispatch. Authored opaque and
|
|
1781
|
+
* masked residuals bind their own prepared material slots instead. Directional,
|
|
1782
|
+
* point, and spot passes share bindings only while every resource identity
|
|
1783
|
+
* matches; scene-table growth or recovery must create a fresh bind group.
|
|
795
1784
|
*/
|
|
796
1785
|
function ensureSpotShadowMaterialBg(c: _InternalRenderPipelineContext): BindGroup | null {
|
|
797
1786
|
const { runtime, frameState, pipelineState } = c;
|
|
798
|
-
const cached = frameState.singletonMaterialCache.get('shadow-material-singleton');
|
|
799
|
-
if (cached !== undefined) return cached;
|
|
800
1787
|
const fb = pipelineState.skylightFallback;
|
|
1788
|
+
const sceneMaterialBuffer =
|
|
1789
|
+
c.gpuDrivenStandardPbrFrameResources?.sceneMaterialBuffer ??
|
|
1790
|
+
pipelineState.meshStorageBuffer.buffer;
|
|
1791
|
+
const handles = [
|
|
1792
|
+
pipelineState.materialBindGroupLayout,
|
|
1793
|
+
pipelineState.materialUniformBuffer.buffer,
|
|
1794
|
+
pipelineState.defaultSampler,
|
|
1795
|
+
pipelineState.defaultNormalTextureView,
|
|
1796
|
+
pipelineState.fallbackTextureView,
|
|
1797
|
+
sceneMaterialBuffer,
|
|
1798
|
+
...(fb === null ? [] : [fb]),
|
|
1799
|
+
];
|
|
1800
|
+
const cached = findFromChain(
|
|
1801
|
+
frameState.shadowMaterialBindGroups,
|
|
1802
|
+
handles,
|
|
1803
|
+
'shadow-material-singleton',
|
|
1804
|
+
);
|
|
1805
|
+
if (cached !== undefined) return cached;
|
|
801
1806
|
const fallbackEntries: BindGroupEntry[] = buildPbrMaterialUserRegionEntries().map((entry) => {
|
|
802
1807
|
if (entry.buffer !== undefined) {
|
|
803
1808
|
return {
|
|
@@ -831,27 +1836,181 @@ function ensureSpotShadowMaterialBg(c: _InternalRenderPipelineContext): BindGrou
|
|
|
831
1836
|
});
|
|
832
1837
|
const merged =
|
|
833
1838
|
fb !== null
|
|
834
|
-
? assembleMaterialWithSkylightEntries(
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1839
|
+
? assembleMaterialWithSkylightEntries(
|
|
1840
|
+
fallbackEntries,
|
|
1841
|
+
{
|
|
1842
|
+
irradianceView: fb.irradianceView,
|
|
1843
|
+
irradianceSampler: fb.sampler,
|
|
1844
|
+
prefilterView: fb.prefilterView,
|
|
1845
|
+
prefilterSampler: fb.sampler,
|
|
1846
|
+
brdfLutView: fb.brdfLutView,
|
|
1847
|
+
brdfLutSampler: fb.sampler,
|
|
1848
|
+
intensityBuffer: fb.intensityBuffer,
|
|
1849
|
+
},
|
|
1850
|
+
transmissionBackdropAvailable(runtime.device.limits.maxSampledTexturesPerShaderStage)
|
|
1851
|
+
? undefined
|
|
1852
|
+
: null,
|
|
1853
|
+
)
|
|
843
1854
|
: fallbackEntries;
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1855
|
+
// Binding(46) is the optional scene-material storage table. The material
|
|
1856
|
+
// BGL intentionally omits it on the uniform-only WebGL2 lane, so emitting
|
|
1857
|
+
// an entry unconditionally creates a deferred-invalid bind group in wgpu's
|
|
1858
|
+
// downlevel backend and only surfaces at Queue::submit. GPU-driven alpha
|
|
1859
|
+
// batches build their own receipt-backed group and are storage-capable by
|
|
1860
|
+
// admission; this singleton only needs the optional slot when that same
|
|
1861
|
+
// capability is present.
|
|
1862
|
+
if (runtime.device.caps.storageBuffer) {
|
|
1863
|
+
// Scene-index shadow variants read MaterialParameters by the projected
|
|
1864
|
+
// row selected in visibleItems. The legacy singleton used the mesh SSBO
|
|
1865
|
+
// here because the old shadow vertex path never consumed binding(46); on
|
|
1866
|
+
// the capable scene-index lane that buffer is a different struct and can
|
|
1867
|
+
// make the alpha/depth fragment discard every caster. Reuse the frame's
|
|
1868
|
+
// producer-owned scene material table, with the mesh buffer only for
|
|
1869
|
+
// direct/legacy callers that do not publish one.
|
|
1870
|
+
merged.push({
|
|
1871
|
+
binding: 46,
|
|
1872
|
+
resource: {
|
|
1873
|
+
kind: 'buffer',
|
|
1874
|
+
value: { buffer: sceneMaterialBuffer },
|
|
1875
|
+
},
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
const r = getOrCreateFromChainResult(
|
|
1879
|
+
frameState.shadowMaterialBindGroups,
|
|
1880
|
+
handles,
|
|
1881
|
+
'shadow-material-singleton',
|
|
1882
|
+
() =>
|
|
1883
|
+
runtime.device.createBindGroup({
|
|
1884
|
+
label: 'shadow-material-bg',
|
|
1885
|
+
layout: pipelineState.materialBindGroupLayout,
|
|
1886
|
+
entries: merged,
|
|
1887
|
+
}),
|
|
1888
|
+
c.bindGroupCounts,
|
|
1889
|
+
);
|
|
849
1890
|
if (!r.ok) {
|
|
850
1891
|
runtime.errorRegistry.fire(r.error);
|
|
851
1892
|
return null;
|
|
852
1893
|
}
|
|
853
|
-
c.bindGroupCounts.createBindGroup += 1;
|
|
854
|
-
c.bindGroupCounts.keys.push('shadow-material-singleton');
|
|
855
|
-
frameState.singletonMaterialCache.set('shadow-material-singleton', r.value);
|
|
856
1894
|
return r.value;
|
|
857
1895
|
}
|
|
1896
|
+
|
|
1897
|
+
/**
|
|
1898
|
+
* Assemble the material group for a GPU-driven alpha-mask caster from the
|
|
1899
|
+
* same material snapshot/residency owner as the main PBR pass. The shadow
|
|
1900
|
+
* shader only consumes the base-color pair and the scene-material table, but
|
|
1901
|
+
* the complete merged layout is still required by the shared pipeline.
|
|
1902
|
+
*/
|
|
1903
|
+
function ensureGpuDrivenAlphaMaskMaterialBg(
|
|
1904
|
+
c: _InternalRenderPipelineContext,
|
|
1905
|
+
projection: GpuDrivenShadowBatchProjection,
|
|
1906
|
+
): BindGroup {
|
|
1907
|
+
const sceneMaterialBuffer = c.gpuDrivenStandardPbrFrameResources?.sceneMaterialBuffer;
|
|
1908
|
+
if (sceneMaterialBuffer === undefined) {
|
|
1909
|
+
throw new RhiError({
|
|
1910
|
+
code: 'rhi-not-available',
|
|
1911
|
+
expected: 'GPU Scene material buffer for alpha-mask shadow recording',
|
|
1912
|
+
hint: 'publish the receipt-derived scene material table before the capable shadow pass',
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
const fallback = c.pipelineState.skylightFallback;
|
|
1916
|
+
if (fallback === null) {
|
|
1917
|
+
throw new RhiError({
|
|
1918
|
+
code: 'webgpu-runtime-error',
|
|
1919
|
+
expected: 'pipelineState.skylightFallback for alpha-mask shadow material layout',
|
|
1920
|
+
hint: 'create the merged Standard PBR material resources before recording shadows',
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
const skylightResources = {
|
|
1924
|
+
irradianceView: fallback.irradianceView,
|
|
1925
|
+
irradianceSampler: fallback.sampler,
|
|
1926
|
+
prefilterView: fallback.prefilterView,
|
|
1927
|
+
prefilterSampler: fallback.sampler,
|
|
1928
|
+
brdfLutView: fallback.brdfLutView,
|
|
1929
|
+
brdfLutSampler: fallback.sampler,
|
|
1930
|
+
intensityBuffer: fallback.intensityBuffer,
|
|
1931
|
+
} satisfies import('../ibl/skylight-bind-group').SkylightBindGroupResources;
|
|
1932
|
+
const deps: PerSubmeshMaterialBgDeps = {
|
|
1933
|
+
runtime: c.runtime,
|
|
1934
|
+
pipelineState: c.pipelineState,
|
|
1935
|
+
world: c.world,
|
|
1936
|
+
store: c.store,
|
|
1937
|
+
materialSlice: STANDARD_PBR_UBO_SIZE,
|
|
1938
|
+
videoHighPerfAvailable: false,
|
|
1939
|
+
skylightResources,
|
|
1940
|
+
materialBgShared: c.frameState.materialBgShared,
|
|
1941
|
+
materialBgAssemblyCache: c.materialBgAssemblyCache,
|
|
1942
|
+
sceneMaterialBuffer,
|
|
1943
|
+
bindGroupCounts: c.bindGroupCounts,
|
|
1944
|
+
frameState: c.frameState,
|
|
1945
|
+
};
|
|
1946
|
+
const bindGroup = buildPerSubmeshMaterialBg(
|
|
1947
|
+
deps,
|
|
1948
|
+
projection.material,
|
|
1949
|
+
projection.materialEntityKey,
|
|
1950
|
+
c.world,
|
|
1951
|
+
);
|
|
1952
|
+
return bindGroup;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
/**
|
|
1956
|
+
* Build the group(1) binding for a receipt-backed custom shadow program. The
|
|
1957
|
+
* ordinary shadow singleton is Standard-only; binding it for a custom layout
|
|
1958
|
+
* would make the pipeline appear executable while deferring a BGL mismatch to
|
|
1959
|
+
* submit. Custom resources therefore use the same material producer as the
|
|
1960
|
+
* main pass, using the selected scene-index row when the published custom ABI
|
|
1961
|
+
* declares that resource. No Standard-only alpha or shader rows are
|
|
1962
|
+
* synthesized for the custom artifact.
|
|
1963
|
+
*/
|
|
1964
|
+
function ensureGpuDrivenCustomShadowMaterialBg(
|
|
1965
|
+
c: _InternalRenderPipelineContext,
|
|
1966
|
+
projection: GpuDrivenShadowBatchProjection,
|
|
1967
|
+
): BindGroup {
|
|
1968
|
+
const shaderId = projection.shadowArtifact.material;
|
|
1969
|
+
const fallback = c.pipelineState.skylightFallback;
|
|
1970
|
+
if (fallback === null) {
|
|
1971
|
+
throw new RhiError({
|
|
1972
|
+
code: 'rhi-not-available',
|
|
1973
|
+
expected: 'skylight resources for custom shadow material binding',
|
|
1974
|
+
hint: 'publish the custom material resource owner before recording the shadow lane',
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
const sceneMaterialBuffer = c.gpuDrivenStandardPbrFrameResources?.sceneMaterialBuffer;
|
|
1978
|
+
if (sceneMaterialBuffer === undefined) {
|
|
1979
|
+
throw new RhiError({
|
|
1980
|
+
code: 'rhi-not-available',
|
|
1981
|
+
expected: 'producer-owned scene material buffer for custom shadow ABI',
|
|
1982
|
+
hint: 'publish the selected shadow MaterialProgramAbi scene-index resource before recording',
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
const skylightResources = {
|
|
1986
|
+
irradianceView: fallback.irradianceView,
|
|
1987
|
+
irradianceSampler: fallback.sampler,
|
|
1988
|
+
prefilterView: fallback.prefilterView,
|
|
1989
|
+
prefilterSampler: fallback.sampler,
|
|
1990
|
+
brdfLutView: fallback.brdfLutView,
|
|
1991
|
+
brdfLutSampler: fallback.sampler,
|
|
1992
|
+
intensityBuffer: fallback.intensityBuffer,
|
|
1993
|
+
} satisfies import('../ibl/skylight-bind-group').SkylightBindGroupResources;
|
|
1994
|
+
const deps: PerSubmeshMaterialBgDeps = {
|
|
1995
|
+
runtime: c.runtime,
|
|
1996
|
+
pipelineState: c.pipelineState,
|
|
1997
|
+
world: c.world,
|
|
1998
|
+
store: c.store,
|
|
1999
|
+
materialSlice: STANDARD_PBR_UBO_SIZE,
|
|
2000
|
+
videoHighPerfAvailable: false,
|
|
2001
|
+
skylightResources,
|
|
2002
|
+
materialBgShared: c.frameState.materialBgShared,
|
|
2003
|
+
materialBgAssemblyCache: c.materialBgAssemblyCache,
|
|
2004
|
+
bindGroupCounts: c.bindGroupCounts,
|
|
2005
|
+
frameState: c.frameState,
|
|
2006
|
+
sceneMaterialBuffer,
|
|
2007
|
+
};
|
|
2008
|
+
const bindGroup = buildPerSubmeshMaterialBg(
|
|
2009
|
+
deps,
|
|
2010
|
+
projection.material,
|
|
2011
|
+
projection.materialEntityKey,
|
|
2012
|
+
c.world,
|
|
2013
|
+
shaderId,
|
|
2014
|
+
);
|
|
2015
|
+
return bindGroup;
|
|
2016
|
+
}
|