@forgeax/engine-render 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +842 -124
- package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
- package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/dynamic-geometry-host.d.ts +52 -0
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -0
- package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
- package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +3 -1
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/gpu-driven-owner.d.ts +10 -0
- package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
- package/dist/assembly/host-contract.d.ts +19 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +7 -0
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material-shader-policy.d.ts +3 -1
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/recovery/failure-location.d.ts +12 -0
- package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts +6 -4
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
- package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
- package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-helpers.d.ts +3 -0
- package/dist/assembly/renderer-helpers.d.ts.map +1 -1
- package/dist/assembly/renderer-inspection.d.ts +4 -0
- package/dist/assembly/renderer-inspection.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +6 -0
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
- package/dist/assembly/skin-palette-owner.d.ts +8 -0
- package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
- package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
- package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/webgpu-device-loss.d.ts +15 -0
- package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
- package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +7 -3
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
- package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
- package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
- package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
- package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +7 -141
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
- package/dist/authoring.mjs +3 -3
- package/dist/{chunk-ADHHVYLW.mjs → chunk-33XZFTQU.mjs} +245 -39
- package/dist/chunk-33XZFTQU.mjs.map +1 -0
- package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
- package/dist/chunk-BLXGONR2.mjs.map +1 -0
- package/dist/{chunk-KM2NOX2I.mjs → chunk-BQVQOQJH.mjs} +958 -254
- package/dist/chunk-BQVQOQJH.mjs.map +1 -0
- package/dist/{chunk-HKXTW355.mjs → chunk-BTWH7BP4.mjs} +386 -17
- package/dist/chunk-BTWH7BP4.mjs.map +1 -0
- package/dist/{chunk-TMIASV2N.mjs → chunk-FUODTTIA.mjs} +3934 -676
- package/dist/chunk-FUODTTIA.mjs.map +1 -0
- package/dist/{chunk-FJ6P52EE.mjs → chunk-ILQ5MEZF.mjs} +3 -17
- package/dist/chunk-ILQ5MEZF.mjs.map +1 -0
- package/dist/{chunk-E34VL5VI.mjs → chunk-JQTSEI3Z.mjs} +3 -3
- package/dist/{chunk-E34VL5VI.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
- package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
- package/dist/chunk-NICXSHAT.mjs.map +1 -0
- package/dist/{chunk-ZZ4YQ474.mjs → chunk-NRNXBLIJ.mjs} +577 -7
- package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
- package/dist/{chunk-JDWARUOI.mjs → chunk-PA5DRJNI.mjs} +17 -3
- package/dist/chunk-PA5DRJNI.mjs.map +1 -0
- package/dist/{chunk-XDP6XE5V.mjs → chunk-S4DNKQYQ.mjs} +1118 -446
- package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
- package/dist/components/camera.d.ts +62 -2
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/instances.d.ts +6 -20
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/components/scene-instance.d.ts +2 -1
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/screen-space-reflection.d.ts +14 -0
- package/dist/components/screen-space-reflection.d.ts.map +1 -0
- package/dist/construct-renderer.mjs +13805 -7186
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +11 -17
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/mesh-residency-lifetime.d.ts +21 -0
- package/dist/device/mesh-residency-lifetime.d.ts.map +1 -0
- package/dist/dynamic-geometry.d.ts +125 -0
- package/dist/dynamic-geometry.d.ts.map +1 -0
- package/dist/environment/background.d.ts +6 -0
- package/dist/environment/background.d.ts.map +1 -0
- package/dist/errors/gpu-driven.d.ts +6 -3
- package/dist/errors/gpu-driven.d.ts.map +1 -1
- package/dist/errors/render.d.ts +7 -4
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +33 -3
- package/dist/extract/gpu-driven.d.ts.map +1 -1
- package/dist/features/host.d.ts +23 -1
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/noise-texture.d.ts +17 -0
- package/dist/features/noise-texture.d.ts.map +1 -0
- package/dist/features/plan.d.ts +83 -2
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +37 -4
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/prepared-graphics-store.d.ts.map +1 -1
- package/dist/features/prepared-graphics.d.ts +5 -0
- package/dist/features/prepared-graphics.d.ts.map +1 -1
- package/dist/features/render-graph-compute.d.ts +5 -3
- package/dist/features/render-graph-compute.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts +11 -3
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/features/render-graph-raster.d.ts +19 -3
- package/dist/features/render-graph-raster.d.ts.map +1 -1
- package/dist/features/types.d.ts +11 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +2 -0
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts +34 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/pbr-program.d.ts +10 -0
- package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +144 -23
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/shadow-views.d.ts +126 -0
- package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
- package/dist/gpu-driven/view-gpu.d.ts +18 -2
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +2 -8
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +14 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-texture-usage.d.ts +1 -0
- package/dist/gpu-texture-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +6 -0
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/frame-resources.d.ts +8 -0
- package/dist/ibl/frame-resources.d.ts.map +1 -0
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +21 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +8 -7
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +164 -0
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +11 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -1
- package/dist/instances.d.ts +89 -44
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +118 -10
- package/dist/internal.mjs.map +1 -1
- package/dist/lifecycle.d.ts +2 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/material-row.d.ts +5 -0
- package/dist/material-row.d.ts.map +1 -0
- package/dist/materials.d.ts +15 -0
- package/dist/materials.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +21 -5
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
- package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
- package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
- package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
- package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
- package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
- package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
- package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
- package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
- package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
- package/dist/pipeline/standard-output/graph.d.ts +15 -0
- package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
- package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
- package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
- package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
- package/dist/pipeline/standard-output/resources.d.ts +10 -0
- package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
- package/dist/pipeline/standard-output/types.d.ts +39 -0
- package/dist/pipeline/standard-output/types.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +2 -2
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec-types.d.ts +4 -0
- package/dist/pipeline-spec-types.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +1 -9
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/points-lines/standard-owner.d.ts +1 -1
- package/dist/points-lines/standard-owner.d.ts.map +1 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
- package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
- package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +3 -3
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +106 -5
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +39 -4
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +7 -8
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +38 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +2 -10
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +12 -2
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +15 -6
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/prepared-material-bindings.d.ts +19 -0
- package/dist/record/prepared-material-bindings.d.ts.map +1 -0
- package/dist/record/recovery-pipeline.d.ts +2 -9
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +76 -26
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +62 -7
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +35 -7
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +1 -0
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +10 -2
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/recovery/render-system-roots.d.ts +11 -0
- package/dist/recovery/render-system-roots.d.ts.map +1 -0
- package/dist/recovery/types.d.ts +76 -0
- package/dist/recovery/types.d.ts.map +1 -0
- package/dist/reflection/record-owner.d.ts +0 -2
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +185 -30
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +64 -5
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +7 -5
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +101 -41
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system-projections.d.ts +22 -0
- package/dist/render-system-projections.d.ts.map +1 -0
- package/dist/render-system.d.ts +34 -77
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene-types.d.ts +14 -7
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +29 -11
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/shadow-visibility.d.ts +5 -0
- package/dist/scene/shadow-visibility.d.ts.map +1 -0
- package/dist/ssr/admission.d.ts +70 -1
- package/dist/ssr/admission.d.ts.map +1 -1
- package/dist/ssr/compose.d.ts +13 -0
- package/dist/ssr/compose.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +35 -0
- package/dist/ssr/errors.d.ts.map +1 -1
- package/dist/ssr/graph.d.ts +60 -0
- package/dist/ssr/graph.d.ts.map +1 -0
- package/dist/ssr/history.d.ts +97 -0
- package/dist/ssr/history.d.ts.map +1 -0
- package/dist/ssr/hiz.d.ts +37 -0
- package/dist/ssr/hiz.d.ts.map +1 -0
- package/dist/ssr/inspection.d.ts +42 -0
- package/dist/ssr/inspection.d.ts.map +1 -0
- package/dist/ssr/resources.d.ts +58 -0
- package/dist/ssr/resources.d.ts.map +1 -0
- package/dist/ssr/temporal.d.ts +38 -0
- package/dist/ssr/temporal.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +32 -10
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/systems/skin-palette-types.d.ts +15 -0
- package/dist/systems/skin-palette-types.d.ts.map +1 -1
- package/dist/systems/transparent-dispatch.d.ts +9 -0
- package/dist/systems/transparent-dispatch.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +7 -0
- package/dist/targets/material-source.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts +7 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/gpu.d.ts +8 -0
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/history.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -6
- package/dist/temporal/inspection.d.ts.map +1 -1
- package/dist/temporal/standard-scene-data.d.ts +2 -1
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/temporal-view.d.ts +2 -0
- package/dist/temporal/temporal-view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +35 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +3 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/temporal.d.ts +6 -0
- package/dist/volume/temporal.d.ts.map +1 -1
- package/package.json +21 -20
- package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
- package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
- package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
- package/src/__tests__/auto-exposure.browser.test.ts +33 -0
- package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
- package/src/__tests__/detached-inspection.integration.test.ts +5 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
- package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
- package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
- package/src/__tests__/dynamic-geometry-host.unit.test.ts +1123 -0
- package/src/__tests__/dynamic-geometry.unit.test.ts +233 -0
- package/src/__tests__/factory-contract.integration.test.ts +73 -22
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
- package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
- package/src/__tests__/feature-noise.unit.test.ts +38 -0
- package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
- package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
- package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
- package/src/__tests__/frame-targets.unit.test.ts +24 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
- package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
- package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
- package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
- package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
- package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
- package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
- package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
- package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
- package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
- package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
- package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
- package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
- package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
- package/src/__tests__/gpu-scene.unit.test.ts +559 -33
- package/src/__tests__/instances-culling.integration.test.ts +36 -10
- package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
- package/src/__tests__/instances-store.unit.test.ts +242 -39
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
- package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
- package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
- package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
- package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
- package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
- package/src/__tests__/material-texture-source.unit.test.ts +87 -0
- package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
- package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +53 -0
- package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
- package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
- package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
- package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
- package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
- package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
- package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
- package/src/__tests__/public-surface.test-d.ts +3 -0
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
- package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
- package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
- package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
- package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
- package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
- package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
- package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
- package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
- package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
- package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
- package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
- package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
- package/src/__tests__/render-target-material-source.test-d.ts +27 -3
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +61 -1
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
- package/src/__tests__/shader-manifest-fixture.ts +39 -0
- package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
- package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
- package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
- package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
- package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
- package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
- package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
- package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
- package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
- package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
- package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
- package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
- package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
- package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
- package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
- package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
- package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
- package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
- package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
- package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
- package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
- package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
- package/src/__tests__/ssr-reprojection-probe.ts +90 -0
- package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
- package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
- package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
- package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
- package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
- package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
- package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
- package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
- package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
- package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
- package/src/__tests__/standard-output.browser.test.ts +15 -0
- package/src/__tests__/standard-output.dawn.test.ts +15 -0
- package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
- package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
- package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
- package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
- package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
- package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
- package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
- package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
- package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
- package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
- package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
- package/src/__tests__/taa-stability-feedback.ts +341 -0
- package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
- package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
- package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
- package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
- package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
- package/src/__tests__/visibility-extract.perf.test.ts +20 -8
- package/src/__tests__/visibility-instances.integration.test.ts +19 -7
- package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
- package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
- package/src/assembly/dynamic-geometry-host.ts +887 -0
- package/src/assembly/dynamic-geometry-runtime.ts +71 -0
- package/src/assembly/factory.ts +4 -0
- package/src/assembly/gpu-driven-owner.ts +29 -0
- package/src/assembly/host-contract.ts +36 -0
- package/src/assembly/material/assembly.ts +38 -0
- package/src/assembly/material-shader-policy.ts +71 -28
- package/src/assembly/recovery/failure-location.ts +62 -0
- package/src/assembly/recovery/renderer-recover.ts +19 -17
- package/src/assembly/renderer-facade.ts +29 -0
- package/src/assembly/renderer-feature-inspection.ts +13 -0
- package/src/assembly/renderer-frame-transaction.ts +63 -0
- package/src/assembly/renderer-helpers.ts +26 -0
- package/src/assembly/renderer-inspection.ts +6 -0
- package/src/assembly/shader-prewarm-policy.ts +32 -0
- package/src/assembly/skin-palette-owner.ts +21 -0
- package/src/assembly/ssr-shader-prewarm.ts +41 -0
- package/src/assembly/webgpu-device-loss.ts +75 -0
- package/src/assembly/webgpu-pbr-ready.ts +232 -0
- package/src/assembly/webgpu-ready-mipmap.ts +26 -0
- package/src/assembly/webgpu-ready.ts +142 -121
- package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
- package/src/assembly/webgpu-renderer-contract.ts +69 -0
- package/src/assembly/webgpu-renderer-guards.ts +32 -0
- package/src/assembly/webgpu-renderer-observation.ts +43 -0
- package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
- package/src/assembly/webgpu-renderer.ts +675 -614
- package/src/assembly/webgpu-vertex-layouts.ts +233 -87
- package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
- package/src/components/camera.ts +287 -7
- package/src/components/index.ts +1 -0
- package/src/components/instances.ts +45 -18
- package/src/components/scene-instance.ts +2 -1
- package/src/components/screen-space-reflection.ts +21 -0
- package/src/components/sprite-animation.ts +3 -3
- package/src/components/sprite-instances.ts +1 -1
- package/src/device/gpu-residency.ts +96 -93
- package/src/device/mesh-residency-lifetime.ts +73 -0
- package/src/dynamic-geometry.ts +736 -0
- package/src/environment/background.ts +263 -0
- package/src/errors/gpu-driven.ts +39 -5
- package/src/errors/render.ts +27 -3
- package/src/extract/camera.ts +26 -1
- package/src/extract/gpu-driven.ts +185 -10
- package/src/features/host.ts +237 -19
- package/src/features/noise-texture.ts +91 -0
- package/src/features/plan.ts +553 -24
- package/src/features/prepared-gpu-work.ts +631 -49
- package/src/features/prepared-graphics-store.ts +31 -0
- package/src/features/prepared-graphics.ts +5 -0
- package/src/features/render-graph-compute.ts +41 -8
- package/src/features/render-graph-contribution.ts +141 -3
- package/src/features/render-graph-raster.ts +143 -30
- package/src/features/types.ts +11 -0
- package/src/fullscreen-post-process-pass.ts +3 -0
- package/src/gpu-driven/batch-topology.ts +424 -102
- package/src/gpu-driven/pbr-program.ts +31 -0
- package/src/gpu-driven/prepared-draw.ts +29 -4
- package/src/gpu-driven/production-raster.ts +2267 -694
- package/src/gpu-driven/shadow-views.ts +539 -0
- package/src/gpu-driven/view-gpu.ts +188 -41
- package/src/gpu-scene-schema.ts +52 -11
- package/src/gpu-scene.ts +186 -99
- package/src/gpu-texture-usage.ts +1 -0
- package/src/hdrp-buffers.ts +72 -36
- package/src/ibl/frame-resources.ts +38 -0
- package/src/ibl/skylight-bind-group.ts +5 -4
- package/src/index.ts +125 -1
- package/src/inspection-types.ts +191 -0
- package/src/instances-derived-bounds.ts +258 -34
- package/src/instances.ts +459 -150
- package/src/internal.ts +22 -0
- package/src/lifecycle.ts +2 -0
- package/src/material-row.ts +145 -0
- package/src/materials.ts +44 -8
- package/src/pbr-pipeline.ts +159 -17
- package/src/pipeline/standard-forward-lane.ts +52 -24
- package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
- package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
- package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
- package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
- package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
- package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
- package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
- package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
- package/src/pipeline/standard-output/color-transform.ts +124 -0
- package/src/pipeline/standard-output/graph.ts +139 -0
- package/src/pipeline/standard-output/lut-admission.ts +121 -0
- package/src/pipeline/standard-output/lut-gpu.ts +399 -0
- package/src/pipeline/standard-output/lut-state.ts +176 -0
- package/src/pipeline/standard-output/resources.ts +121 -0
- package/src/pipeline/standard-output/types.ts +58 -0
- package/src/pipeline/standard-pipeline.ts +192 -59
- package/src/pipeline/standard-post.ts +186 -27
- package/src/pipeline-builder.ts +16 -6
- package/src/pipeline-spec-types.ts +4 -0
- package/src/pipeline-spec.ts +37 -29
- package/src/points-lines/standard-owner.ts +6 -14
- package/src/prepare/prepared-graphics-resolver.ts +9 -2
- package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
- package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
- package/src/record/dynamic-geometry-consumption.ts +59 -0
- package/src/record/frame-lighting.ts +12 -11
- package/src/record/frame-snapshot.ts +133 -23
- package/src/record/frame-targets.ts +9 -0
- package/src/record/frame.ts +727 -133
- package/src/record/main-pass-geometry.ts +162 -98
- package/src/record/main-pass-material.ts +115 -26
- package/src/record/main-pass-sprite-draws.ts +18 -107
- package/src/record/main-pass.ts +78 -8
- package/src/record/material-uniforms.ts +1 -1
- package/src/record/mesh-ssbo.ts +59 -15
- package/src/record/prepared-material-bindings.ts +234 -0
- package/src/record/recovery-pipeline.ts +59 -136
- package/src/record/render-context.ts +92 -22
- package/src/record/shadow-pass.ts +1231 -209
- package/src/record/skybox-post-pass.ts +14 -3
- package/src/record/typed-frame-graph.ts +1006 -53
- package/src/record/view-ubo.ts +11 -0
- package/src/recovery/render-system-candidate.ts +90 -66
- package/src/recovery/render-system-roots.ts +113 -0
- package/src/recovery/types.ts +77 -0
- package/src/reflection/record-owner.ts +26 -54
- package/src/render-contract.ts +219 -25
- package/src/render-graph-primitives.ts +6 -2
- package/src/render-pipeline.ts +70 -1
- package/src/render-system-extract-tail.ts +409 -160
- package/src/render-system-extract.ts +376 -102
- package/src/render-system-projections.ts +241 -0
- package/src/render-system.ts +692 -791
- package/src/scene/render-scene-types.ts +13 -4
- package/src/scene/render-scene.ts +569 -355
- package/src/scene/shadow-visibility.ts +21 -0
- package/src/ssr/admission.ts +380 -14
- package/src/ssr/compose.ts +144 -0
- package/src/ssr/errors.ts +48 -0
- package/src/ssr/graph.ts +777 -0
- package/src/ssr/history.ts +436 -0
- package/src/ssr/hiz.ts +124 -0
- package/src/ssr/inspection.ts +124 -0
- package/src/ssr/resources.ts +202 -0
- package/src/ssr/temporal.ts +164 -0
- package/src/systems/skin-palette-allocator.ts +495 -148
- package/src/systems/skin-palette-types.ts +17 -0
- package/src/systems/transparent-dispatch.ts +90 -0
- package/src/targets/material-source.ts +34 -0
- package/src/temporal/coverage.ts +1 -1
- package/src/temporal/frame-coordinator.ts +23 -2
- package/src/temporal/gpu.ts +74 -12
- package/src/temporal/history.ts +3 -1
- package/src/temporal/inspection.ts +2 -1
- package/src/temporal/standard-scene-data.ts +3 -1
- package/src/temporal/temporal-view.ts +3 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
- package/src/typed-render-graph-primitives.ts +266 -74
- package/src/typed-shadow-passes.ts +122 -12
- package/src/volume/temporal.ts +10 -0
- package/dist/chunk-ADHHVYLW.mjs.map +0 -1
- package/dist/chunk-FJ6P52EE.mjs.map +0 -1
- package/dist/chunk-GE3SDD72.mjs.map +0 -1
- package/dist/chunk-GF523LHF.mjs.map +0 -1
- package/dist/chunk-HKXTW355.mjs.map +0 -1
- package/dist/chunk-JDWARUOI.mjs.map +0 -1
- package/dist/chunk-KM2NOX2I.mjs.map +0 -1
- package/dist/chunk-TMIASV2N.mjs.map +0 -1
- package/dist/chunk-XDP6XE5V.mjs.map +0 -1
- package/dist/chunk-ZZ4YQ474.mjs.map +0 -1
- package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
|
@@ -9,8 +9,20 @@ 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 { gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
|
|
13
|
+
import type { SubmissionPlan } from '../gpu-driven/batch-topology';
|
|
14
|
+
import type { GpuDrivenShadowBatchProjection } from '../gpu-driven/production-raster';
|
|
15
|
+
import { type ShadowViewIdentity, shadowViewIdentityKey } from '../gpu-driven/shadow-views';
|
|
16
|
+
import type { GpuDrivenView } from '../gpu-driven/view-gpu';
|
|
17
|
+
import { GpuBuffer } from '../gpu-resource';
|
|
18
|
+
import {
|
|
19
|
+
GPU_BUFFER_USAGE_COPY_DST,
|
|
20
|
+
GPU_BUFFER_USAGE_STORAGE,
|
|
21
|
+
GPU_BUFFER_USAGE_UNIFORM,
|
|
22
|
+
} from '../gpu-usage';
|
|
13
23
|
import { assembleMaterialWithSkylightEntries } from '../ibl/skylight-bind-group';
|
|
24
|
+
import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
|
|
25
|
+
import { instanceUploadRangesForResident } from '../instances';
|
|
14
26
|
import {
|
|
15
27
|
buildPbrMaterialUserRegionEntries,
|
|
16
28
|
createPbrSkinMeshBindGroupEntries,
|
|
@@ -19,11 +31,18 @@ import {
|
|
|
19
31
|
shadowCasterVariantSet,
|
|
20
32
|
} from '../pbr-pipeline';
|
|
21
33
|
import { COOKIE_MATRIX_BYTES } from '../prepare/extended-lighting/resources';
|
|
22
|
-
import type {
|
|
34
|
+
import type {
|
|
35
|
+
DispatchEntry,
|
|
36
|
+
ExtractedLights,
|
|
37
|
+
RenderableSnapshot,
|
|
38
|
+
ShadowCasterMembership,
|
|
39
|
+
} from '../render-system-extract';
|
|
23
40
|
import { matchPass } from '../systems/pass-selector';
|
|
41
|
+
import { instanceCollectionCacheKey, worldEntityKey } from './frame-snapshot';
|
|
24
42
|
import {
|
|
25
|
-
|
|
43
|
+
reportInstanceResidency,
|
|
26
44
|
resolveGeometryInstancesBindGroup,
|
|
45
|
+
resolveStorageInstanceChunks,
|
|
27
46
|
} from './main-pass-geometry';
|
|
28
47
|
import {
|
|
29
48
|
buildPerSubmeshMaterialBg,
|
|
@@ -32,9 +51,12 @@ import {
|
|
|
32
51
|
} from './main-pass-material';
|
|
33
52
|
import {
|
|
34
53
|
getOrCreateFromChain,
|
|
54
|
+
INSTANCE_UBO_FULL_ARRAY_BYTES,
|
|
55
|
+
MAX_UNIFORM_INSTANCES,
|
|
35
56
|
MESH_PER_ENTITY_STRIDE,
|
|
36
57
|
MESH_SSBO_BYTES,
|
|
37
58
|
MESH_UBO_FULL_ARRAY_BYTES,
|
|
59
|
+
packInstanceStorageBuffer,
|
|
38
60
|
} from './mesh-ssbo';
|
|
39
61
|
import type { _InternalRenderPipelineContext } from './render-context';
|
|
40
62
|
import { MATERIAL_PER_ENTITY_STRIDE, STANDARD_PBR_UBO_SIZE } from './render-context';
|
|
@@ -43,6 +65,32 @@ import { POINTS_LINES_VIEW_BYTES, pointShadowViewOffset, VIEW_UNIFORM_BYTES } fr
|
|
|
43
65
|
export const SHADOW_CASTER_SLOT_STRIDE = 256;
|
|
44
66
|
export const SHADOW_CASTER_BUFFER_SIZE = SHADOW_CASTER_SLOT_STRIDE * 8;
|
|
45
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Renderer-owned capable shadow submission. The view/counter/indirect ABI is
|
|
70
|
+
* shared with the main GPU-driven lane; this record only supplies the
|
|
71
|
+
* prepared per-batch raster state that cannot be inferred from a raw scene.
|
|
72
|
+
* When present, the capable lane emits one indirect command per topology batch
|
|
73
|
+
* and never walks `validatedOrdered`.
|
|
74
|
+
*/
|
|
75
|
+
export interface GpuDrivenShadowBatch {
|
|
76
|
+
readonly pipeline: RenderPipeline;
|
|
77
|
+
readonly meshBindGroup: BindGroup;
|
|
78
|
+
readonly visibleBindGroup: BindGroup;
|
|
79
|
+
readonly deformation: 'rigid' | 'skin';
|
|
80
|
+
/** Receipt-derived material BG for alpha-mask batches; opaque uses the pass singleton. */
|
|
81
|
+
readonly alphaMaskMaterialGroup?: BindGroup | undefined;
|
|
82
|
+
readonly vertexBuffer: Buffer;
|
|
83
|
+
readonly indexBuffer?: Buffer | undefined;
|
|
84
|
+
readonly indexFormat?: 'uint16' | 'uint32' | undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface GpuDrivenShadowSubmission {
|
|
88
|
+
readonly view: GpuDrivenView;
|
|
89
|
+
readonly plan: SubmissionPlan;
|
|
90
|
+
readonly batches: ReadonlyMap<number, GpuDrivenShadowBatch>;
|
|
91
|
+
readonly instancesBindGroup?: BindGroup | undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
46
94
|
export function directionalShadowCasterOffset(cascadeIndex: number): number {
|
|
47
95
|
return SHADOW_CASTER_SLOT_STRIDE * cascadeIndex;
|
|
48
96
|
}
|
|
@@ -253,43 +301,76 @@ function ensureTypedShadowViewBg(
|
|
|
253
301
|
}
|
|
254
302
|
|
|
255
303
|
interface ShadowDispatch {
|
|
304
|
+
readonly passIndex: number;
|
|
305
|
+
readonly materialHandle: number;
|
|
256
306
|
readonly vertexEntry: string | undefined;
|
|
257
307
|
readonly fragmentEntry: string | undefined;
|
|
258
308
|
readonly materialShaderId: string;
|
|
259
309
|
readonly renderState: MaterialRenderState | undefined;
|
|
310
|
+
readonly materialSlot: number | undefined;
|
|
311
|
+
readonly paramSnapshot: DispatchEntry['paramSnapshot'];
|
|
260
312
|
}
|
|
261
313
|
|
|
262
|
-
type ShadowDispatchMap = ReadonlyMap<number, ReadonlyMap<number, ShadowDispatch>>;
|
|
314
|
+
type ShadowDispatchMap = ReadonlyMap<number, ReadonlyMap<number, readonly ShadowDispatch[]>>;
|
|
315
|
+
|
|
316
|
+
function shadowDispatchEntries(c: _InternalRenderPipelineContext): readonly DispatchEntry[] {
|
|
317
|
+
return c.shadowDispatch ?? c.dispatch;
|
|
318
|
+
}
|
|
263
319
|
|
|
264
320
|
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
|
-
});
|
|
321
|
+
const shaders = new Map<number, Map<number, ShadowDispatch[]>>();
|
|
322
|
+
const shadowRows = c.shadowDispatch !== undefined;
|
|
323
|
+
const rows = (shadowRows ? c.shadowValidatedOrdered : c.validatedOrdered) ?? [];
|
|
324
|
+
const base = shadowRows ? c.validatedOrdered.length : 0;
|
|
325
|
+
const slotsByRenderable = new Map(
|
|
326
|
+
rows.map((row, index) => [row.renderableIndex, c.materialSlotIndices?.[base + index] ?? []]),
|
|
327
|
+
);
|
|
328
|
+
for (const entry of shadowDispatchEntries(c)) {
|
|
329
|
+
if (entry.tags.LightMode !== 'ShadowCaster' || entry.materialShaderId === undefined) continue;
|
|
330
|
+
let byMaterial = shaders.get(entry.renderableIndex);
|
|
331
|
+
if (byMaterial === undefined) {
|
|
332
|
+
byMaterial = new Map<number, ShadowDispatch[]>();
|
|
333
|
+
shaders.set(entry.renderableIndex, byMaterial);
|
|
282
334
|
}
|
|
335
|
+
const candidateSlots = slotsByRenderable.get(entry.renderableIndex) ?? [];
|
|
336
|
+
const materialSlot = candidateSlots.find(
|
|
337
|
+
(slot) => c.materialSlots?.[slot]?.materialHandle === entry.materialHandle,
|
|
338
|
+
);
|
|
339
|
+
const dispatches = byMaterial.get(entry.materialHandle) ?? [];
|
|
340
|
+
dispatches.push({
|
|
341
|
+
passIndex: entry.passIndex,
|
|
342
|
+
materialHandle: entry.materialHandle,
|
|
343
|
+
vertexEntry: entry.vertexEntry,
|
|
344
|
+
fragmentEntry: entry.fragmentEntry,
|
|
345
|
+
materialShaderId:
|
|
346
|
+
entry.materialShaderId === 'forgeax::default-standard-pbr'
|
|
347
|
+
? SHADOW_CASTER_SHADER_ID
|
|
348
|
+
: entry.materialShaderId,
|
|
349
|
+
renderState: entry.renderState,
|
|
350
|
+
materialSlot,
|
|
351
|
+
paramSnapshot: entry.paramSnapshot,
|
|
352
|
+
});
|
|
353
|
+
byMaterial.set(entry.materialHandle, dispatches);
|
|
283
354
|
}
|
|
284
355
|
return shaders;
|
|
285
356
|
}
|
|
286
357
|
|
|
287
|
-
function
|
|
358
|
+
function shadowRasterState(
|
|
359
|
+
state: MaterialRenderState | undefined,
|
|
360
|
+
reflected: boolean,
|
|
361
|
+
): MaterialRenderState | undefined {
|
|
362
|
+
return reflected ? { ...state, frontFace: state?.frontFace === 'cw' ? 'ccw' : 'cw' } : state;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function shadowPipeline(
|
|
366
|
+
c: _InternalRenderPipelineContext,
|
|
367
|
+
reflected = false,
|
|
368
|
+
): RenderPipeline | null {
|
|
288
369
|
return (
|
|
289
370
|
c.runtime.getMaterialShaderPipeline?.(
|
|
290
371
|
SHADOW_CASTER_SHADER_ID,
|
|
291
372
|
false,
|
|
292
|
-
undefined,
|
|
373
|
+
shadowRasterState(undefined, reflected),
|
|
293
374
|
'triangle-list',
|
|
294
375
|
undefined,
|
|
295
376
|
shadowCasterVariantSet(c.runtime.device.caps.storageBuffer, false),
|
|
@@ -307,32 +388,6 @@ function shadowPipeline(c: _InternalRenderPipelineContext): RenderPipeline | nul
|
|
|
307
388
|
);
|
|
308
389
|
}
|
|
309
390
|
|
|
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
391
|
function skinnedShadowMeshBindGroup(
|
|
337
392
|
c: _InternalRenderPipelineContext,
|
|
338
393
|
entry: _InternalRenderPipelineContext['validatedOrdered'][number],
|
|
@@ -363,13 +418,371 @@ function skinnedShadowMeshBindGroup(
|
|
|
363
418
|
);
|
|
364
419
|
}
|
|
365
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Record the capable shadow lane from the shared view's indirect arguments.
|
|
423
|
+
* The submission is an explicit prepared contract: missing view buffers or a
|
|
424
|
+
* missing batch raster projection are terminal rather than a reason to fall
|
|
425
|
+
* back to a CPU caster walk. The caller may then keep the existing CPU path
|
|
426
|
+
* when no capable submission was published for the current view.
|
|
427
|
+
*/
|
|
428
|
+
export function recordGpuDrivenShadowIndirect(
|
|
429
|
+
pass: RhiRenderPassEncoder,
|
|
430
|
+
submission: GpuDrivenShadowSubmission,
|
|
431
|
+
): void {
|
|
432
|
+
const indirect = submission.view.indirectBuffer;
|
|
433
|
+
if (indirect === undefined) {
|
|
434
|
+
throw new RhiError({
|
|
435
|
+
code: 'internal-error',
|
|
436
|
+
expected: 'GPU-driven shadow view indirect buffer',
|
|
437
|
+
hint: 'publish a prepared view generation before recording a capable shadow pass',
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
const indexed = new Map<number, GpuDrivenShadowBatch>();
|
|
441
|
+
for (const [batchId, batch] of submission.batches) indexed.set(batchId, batch);
|
|
442
|
+
let currentPipeline: RenderPipeline | undefined;
|
|
443
|
+
let currentVertex: Buffer | undefined;
|
|
444
|
+
let currentIndex: Buffer | undefined;
|
|
445
|
+
for (const batch of submission.plan.batches) {
|
|
446
|
+
const raster = indexed.get(batch.batchId);
|
|
447
|
+
if (raster === undefined) {
|
|
448
|
+
throw new RhiError({
|
|
449
|
+
code: 'internal-error',
|
|
450
|
+
expected: `prepared shadow raster batch ${batch.batchId}`,
|
|
451
|
+
hint: 'keep the shadow view plan and prepared batch projection on one generation',
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
if (currentPipeline !== raster.pipeline) {
|
|
455
|
+
pass.setPipeline(raster.pipeline);
|
|
456
|
+
currentPipeline = raster.pipeline;
|
|
457
|
+
}
|
|
458
|
+
if (currentVertex !== raster.vertexBuffer) {
|
|
459
|
+
pass.setVertexBuffer(0, raster.vertexBuffer);
|
|
460
|
+
currentVertex = raster.vertexBuffer;
|
|
461
|
+
}
|
|
462
|
+
if (raster.alphaMaskMaterialGroup !== undefined) {
|
|
463
|
+
// Alpha-mask shadow variants sample the receipt-owned base-color
|
|
464
|
+
// texture. Opaque batches keep the pass-level singleton bound by the
|
|
465
|
+
// caller, so group(1) remains one stable layout for every draw.
|
|
466
|
+
pass.setBindGroup(1, raster.alphaMaskMaterialGroup, [0]);
|
|
467
|
+
}
|
|
468
|
+
if (raster.indexBuffer !== undefined) {
|
|
469
|
+
if (raster.indexFormat === undefined) {
|
|
470
|
+
throw new RhiError({
|
|
471
|
+
code: 'internal-error',
|
|
472
|
+
expected: `index format for prepared shadow raster batch ${batch.batchId}`,
|
|
473
|
+
hint: 'publish the geometry layout projection with the capable shadow batch',
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
if (currentIndex !== raster.indexBuffer) {
|
|
477
|
+
pass.setIndexBuffer(raster.indexBuffer, raster.indexFormat);
|
|
478
|
+
currentIndex = raster.indexBuffer;
|
|
479
|
+
}
|
|
480
|
+
pass.setBindGroup(2, raster.meshBindGroup, raster.deformation === 'skin' ? [0, 0, 0] : [0]);
|
|
481
|
+
pass.setBindGroup(3, raster.visibleBindGroup);
|
|
482
|
+
pass.drawIndexedIndirect(indirect, batch.indirectOffset);
|
|
483
|
+
} else {
|
|
484
|
+
pass.setBindGroup(2, raster.meshBindGroup, raster.deformation === 'skin' ? [0, 0, 0] : [0]);
|
|
485
|
+
pass.setBindGroup(3, raster.visibleBindGroup);
|
|
486
|
+
pass.drawIndirect(indirect, batch.indirectOffset);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function shadowDrawIndicesBySubmesh(
|
|
492
|
+
source: RenderableSnapshot,
|
|
493
|
+
mesh: _InternalRenderPipelineContext['validatedOrdered'][number]['mesh'],
|
|
494
|
+
): ReadonlyMap<number, number> {
|
|
495
|
+
const draws = source.gpuDrivenDraws;
|
|
496
|
+
if (draws === undefined || draws.length === 0) return new Map();
|
|
497
|
+
const indices = new Map<number, number>();
|
|
498
|
+
let hasStableSourceIdentity = false;
|
|
499
|
+
for (const [compactIndex, draw] of draws.entries()) {
|
|
500
|
+
const drawItemIndex = draw.drawItemIndex;
|
|
501
|
+
if (drawItemIndex === undefined) continue;
|
|
502
|
+
hasStableSourceIdentity = true;
|
|
503
|
+
if (mesh.submeshes[drawItemIndex] !== undefined) indices.set(drawItemIndex, compactIndex);
|
|
504
|
+
}
|
|
505
|
+
if (hasStableSourceIdentity) return indices;
|
|
506
|
+
|
|
507
|
+
// Legacy snapshots predating drawItemIndex are matched by their compact
|
|
508
|
+
// geometry facts. New extracted snapshots never take this path.
|
|
509
|
+
let nonIndexedFirst = 0;
|
|
510
|
+
let drawCursor = 0;
|
|
511
|
+
for (const [submeshIndex, submesh] of mesh.submeshes.entries()) {
|
|
512
|
+
const first = mesh.indexed ? submesh.indexOffset : nonIndexedFirst;
|
|
513
|
+
const count = mesh.indexed ? submesh.indexCount : submesh.vertexCount;
|
|
514
|
+
if (!mesh.indexed) nonIndexedFirst += submesh.vertexCount;
|
|
515
|
+
const draw = draws[drawCursor];
|
|
516
|
+
if (
|
|
517
|
+
draw !== undefined &&
|
|
518
|
+
draw.first === first &&
|
|
519
|
+
draw.count === count &&
|
|
520
|
+
draw.materialSlot === submesh.materialSlot &&
|
|
521
|
+
draw.topology === submesh.topology
|
|
522
|
+
) {
|
|
523
|
+
indices.set(submeshIndex, drawCursor);
|
|
524
|
+
drawCursor += 1;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return indices;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function isGpuShadowClaimedSubmesh(
|
|
531
|
+
source: RenderableSnapshot,
|
|
532
|
+
renderableIndex: number,
|
|
533
|
+
submeshIndex: number,
|
|
534
|
+
drawIndices: ReadonlyMap<number, number>,
|
|
535
|
+
gpuDrivenShadowDrawKeys: ReadonlySet<string> | undefined,
|
|
536
|
+
shadowDispatchByRenderableIdx: ShadowDispatchMap,
|
|
537
|
+
shadowCasterMembership: readonly ShadowCasterMembership[] | undefined,
|
|
538
|
+
shadowCasterWorldKeys: readonly number[] | undefined,
|
|
539
|
+
): boolean {
|
|
540
|
+
if (gpuDrivenShadowDrawKeys === undefined) return false;
|
|
541
|
+
const drawItemIndex = drawIndices.get(submeshIndex);
|
|
542
|
+
const draw = drawItemIndex === undefined ? undefined : source.gpuDrivenDraws?.[drawItemIndex];
|
|
543
|
+
if (drawItemIndex === undefined || draw === undefined) return false;
|
|
544
|
+
const material = source.materials[draw.materialSlot] ?? source.material;
|
|
545
|
+
const worldEntity = worldEntityKey(
|
|
546
|
+
shadowCasterWorldKeys?.[source.worldId] ?? source.worldId,
|
|
547
|
+
source.entityKey,
|
|
548
|
+
);
|
|
549
|
+
const sourceDrawItemIndex = draw.drawItemIndex ?? drawItemIndex;
|
|
550
|
+
if (shadowCasterMembership !== undefined) {
|
|
551
|
+
const matching = shadowCasterMembership.filter(
|
|
552
|
+
(membership) =>
|
|
553
|
+
membership.worldEntity === worldEntity &&
|
|
554
|
+
membership.materialHandle === (material.materialHandle ?? -1) &&
|
|
555
|
+
membership.drawItemIndex === sourceDrawItemIndex,
|
|
556
|
+
);
|
|
557
|
+
if (
|
|
558
|
+
matching.length === 0 ||
|
|
559
|
+
matching.some((membership) => membership.cpuReason !== undefined)
|
|
560
|
+
) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
return matching.every((membership) =>
|
|
564
|
+
gpuDrivenShadowDrawKeys.has(
|
|
565
|
+
gpuDrivenShadowDrawKey(
|
|
566
|
+
membership.worldEntity,
|
|
567
|
+
membership.materialHandle,
|
|
568
|
+
membership.drawItemIndex,
|
|
569
|
+
membership.passIndex,
|
|
570
|
+
),
|
|
571
|
+
),
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
const dispatches =
|
|
575
|
+
shadowDispatchByRenderableIdx.get(renderableIndex)?.get(material.materialHandle ?? 0) ?? [];
|
|
576
|
+
if (dispatches.length > 0) {
|
|
577
|
+
return dispatches.every((dispatch) =>
|
|
578
|
+
gpuDrivenShadowDrawKeys.has(
|
|
579
|
+
gpuDrivenShadowDrawKey(
|
|
580
|
+
worldEntity,
|
|
581
|
+
material.materialHandle ?? -1,
|
|
582
|
+
draw.drawItemIndex ?? drawItemIndex,
|
|
583
|
+
dispatch.passIndex,
|
|
584
|
+
),
|
|
585
|
+
),
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
const base = gpuDrivenShadowDrawKey(
|
|
589
|
+
worldEntity,
|
|
590
|
+
material.materialHandle ?? -1,
|
|
591
|
+
sourceDrawItemIndex,
|
|
592
|
+
0,
|
|
593
|
+
).split(':');
|
|
594
|
+
return [...gpuDrivenShadowDrawKeys].some((key) => {
|
|
595
|
+
const parts = key.split(':');
|
|
596
|
+
return (
|
|
597
|
+
parts.length === 4 && parts[0] === base[0] && parts[1] === base[1] && parts[2] === base[2]
|
|
598
|
+
);
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
interface GpuDrivenShadowRecordResult {
|
|
603
|
+
readonly claimedKeys: ReadonlySet<string>;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function shadowCasterKeys(c: _InternalRenderPipelineContext): ReadonlySet<string> {
|
|
607
|
+
if (c.shadowCasterMembership !== undefined) {
|
|
608
|
+
const keys = new Set<string>();
|
|
609
|
+
for (const membership of c.shadowCasterMembership) {
|
|
610
|
+
keys.add(
|
|
611
|
+
gpuDrivenShadowDrawKey(
|
|
612
|
+
membership.worldEntity,
|
|
613
|
+
membership.materialHandle,
|
|
614
|
+
membership.drawItemIndex,
|
|
615
|
+
membership.passIndex,
|
|
616
|
+
),
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
return keys;
|
|
620
|
+
}
|
|
621
|
+
return c.gpuDrivenShadowDrawKeys ?? new Set<string>();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function validateShadowOwnership(
|
|
625
|
+
c: _InternalRenderPipelineContext,
|
|
626
|
+
identity: ShadowViewIdentity,
|
|
627
|
+
claimedKeys: ReadonlySet<string> | undefined,
|
|
628
|
+
recordedKeys: ReadonlySet<string>,
|
|
629
|
+
): void {
|
|
630
|
+
if (
|
|
631
|
+
c.shadowCasterMembership === undefined &&
|
|
632
|
+
(c.gpuDrivenShadowDrawKeys === undefined || c.gpuDrivenShadowDrawKeys.size === 0)
|
|
633
|
+
)
|
|
634
|
+
return;
|
|
635
|
+
const expectedKeys = shadowCasterKeys(c);
|
|
636
|
+
const claimed = claimedKeys ?? new Set<string>();
|
|
637
|
+
const overlap = [...claimed].filter((key) => recordedKeys.has(key));
|
|
638
|
+
const union = new Set([...claimed, ...recordedKeys]);
|
|
639
|
+
const missing = [...expectedKeys].filter((key) => !union.has(key));
|
|
640
|
+
const extra = [...union].filter((key) => !expectedKeys.has(key));
|
|
641
|
+
if (overlap.length === 0 && missing.length === 0 && extra.length === 0) return;
|
|
642
|
+
throw new RhiError({
|
|
643
|
+
code: 'internal-error',
|
|
644
|
+
expected: `disjoint ShadowCaster ownership for ${shadowViewIdentityKey(identity)}`,
|
|
645
|
+
hint: `claimed=${[...claimed].sort().join(',')} recorded=${[...recordedKeys].sort().join(',')} missing=${missing.sort().join(',')} extra=${extra.sort().join(',')}`,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function hasUnclaimedShadowCasters(
|
|
650
|
+
c: _InternalRenderPipelineContext,
|
|
651
|
+
claimedKeys: ReadonlySet<string> | undefined,
|
|
652
|
+
): boolean {
|
|
653
|
+
const extractedKeys =
|
|
654
|
+
c.shadowCasterMembership === undefined
|
|
655
|
+
? [...(c.gpuDrivenShadowDrawKeys ?? [])]
|
|
656
|
+
: c.shadowCasterMembership.map((membership) =>
|
|
657
|
+
gpuDrivenShadowDrawKey(
|
|
658
|
+
membership.worldEntity,
|
|
659
|
+
membership.materialHandle,
|
|
660
|
+
membership.drawItemIndex,
|
|
661
|
+
membership.passIndex,
|
|
662
|
+
),
|
|
663
|
+
);
|
|
664
|
+
if (extractedKeys.length === 0) return false;
|
|
665
|
+
return claimedKeys === undefined || extractedKeys.some((key) => !claimedKeys.has(key));
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function hasExpectedShadowCasters(c: _InternalRenderPipelineContext): boolean {
|
|
669
|
+
if ((c.shadowCasterMembership?.length ?? 0) > 0) return true;
|
|
670
|
+
if ((c.gpuDrivenShadowDrawKeys?.size ?? 0) > 0) return true;
|
|
671
|
+
return shadowDispatchEntries(c).some((entry) => entry.tags.LightMode === 'ShadowCaster');
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function recordGpuDrivenShadowIfPublished(
|
|
675
|
+
c: _InternalRenderPipelineContext,
|
|
676
|
+
pass: RhiRenderPassEncoder,
|
|
677
|
+
identity: ShadowViewIdentity,
|
|
678
|
+
): GpuDrivenShadowRecordResult | undefined {
|
|
679
|
+
const viewSubmission = c.gpuDrivenShadowViews?.submission(identity);
|
|
680
|
+
const projections = c.gpuDrivenShadowBatchProjections?.get(shadowViewIdentityKey(identity));
|
|
681
|
+
if (viewSubmission === undefined) {
|
|
682
|
+
return undefined;
|
|
683
|
+
}
|
|
684
|
+
if (projections === undefined) {
|
|
685
|
+
throw new RhiError({
|
|
686
|
+
code: 'rhi-not-available',
|
|
687
|
+
expected: `GPU-driven shadow projection for ${shadowViewIdentityKey(identity)}`,
|
|
688
|
+
hint: 'terminate the capable shadow frame instead of falling through to CPU caster enumeration',
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
const batches = new Map<number, GpuDrivenShadowBatch>();
|
|
692
|
+
for (const batch of viewSubmission.plan.batches) {
|
|
693
|
+
const projection: GpuDrivenShadowBatchProjection | undefined = projections.get(batch.batchId);
|
|
694
|
+
if (projection === undefined) {
|
|
695
|
+
throw new RhiError({
|
|
696
|
+
code: 'internal-error',
|
|
697
|
+
expected: `projected mesh binding for GPU-driven shadow batch ${batch.batchId}`,
|
|
698
|
+
hint: 'keep shadow batch scene rows aligned with the shared SubmissionPlan',
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
const pipeline = c.runtime.getMaterialShaderPipeline?.(
|
|
702
|
+
SHADOW_CASTER_SHADER_ID,
|
|
703
|
+
false,
|
|
704
|
+
shadowRasterState(undefined, identity.kind === 'point'),
|
|
705
|
+
batch.key.topology,
|
|
706
|
+
batch.key.drawKind === 'indexed' ? projection.mesh.indexFormat : undefined,
|
|
707
|
+
shadowCasterVariantSet(
|
|
708
|
+
c.runtime.device.caps.storageBuffer,
|
|
709
|
+
projection.deformation === 'skin',
|
|
710
|
+
true,
|
|
711
|
+
batch.key.admission === 'alpha-mask',
|
|
712
|
+
),
|
|
713
|
+
'shadow-caster',
|
|
714
|
+
undefined,
|
|
715
|
+
undefined,
|
|
716
|
+
undefined,
|
|
717
|
+
undefined,
|
|
718
|
+
undefined,
|
|
719
|
+
undefined,
|
|
720
|
+
projection.mesh.layoutProjection,
|
|
721
|
+
undefined,
|
|
722
|
+
projection.deformation === 'skin' ? 'gpu-driven-skin' : 'gpu-driven-pbr',
|
|
723
|
+
'vs_scene_index',
|
|
724
|
+
);
|
|
725
|
+
if (pipeline === null || pipeline === undefined) {
|
|
726
|
+
throw new RhiError({
|
|
727
|
+
code: 'rhi-not-available',
|
|
728
|
+
expected: `GPU-driven shadow pipeline variant for batch ${batch.batchId}`,
|
|
729
|
+
hint: 'publish the exact scene-index shadow variant before entering the capable lane',
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
if (batch.key.drawKind === 'indexed' && projection.mesh.indexBuffer === null) {
|
|
733
|
+
throw new RhiError({
|
|
734
|
+
code: 'internal-error',
|
|
735
|
+
expected: `indexed mesh buffer for GPU-driven shadow batch ${batch.batchId}`,
|
|
736
|
+
hint: 'keep the geometry residency and topology draw kind aligned before capable recording',
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
batches.set(batch.batchId, {
|
|
740
|
+
pipeline,
|
|
741
|
+
meshBindGroup: projection.meshBindGroup,
|
|
742
|
+
visibleBindGroup: projection.visibleBindGroup,
|
|
743
|
+
deformation: projection.deformation,
|
|
744
|
+
...(batch.key.admission === 'alpha-mask'
|
|
745
|
+
? { alphaMaskMaterialGroup: ensureGpuDrivenAlphaMaskMaterialBg(c, projection) }
|
|
746
|
+
: {}),
|
|
747
|
+
vertexBuffer: projection.mesh.vertexBuffer.handle,
|
|
748
|
+
...(batch.key.drawKind === 'indexed' && projection.mesh.indexBuffer !== null
|
|
749
|
+
? {
|
|
750
|
+
indexBuffer: projection.mesh.indexBuffer.handle,
|
|
751
|
+
indexFormat: projection.mesh.indexFormat,
|
|
752
|
+
}
|
|
753
|
+
: {}),
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
const submission: GpuDrivenShadowSubmission = {
|
|
757
|
+
view: viewSubmission.view,
|
|
758
|
+
plan: viewSubmission.plan,
|
|
759
|
+
batches,
|
|
760
|
+
};
|
|
761
|
+
recordGpuDrivenShadowIndirect(pass, submission);
|
|
762
|
+
const viewKey = shadowViewIdentityKey(identity);
|
|
763
|
+
const viewClaims = c.gpuDrivenShadowDrawKeysByView;
|
|
764
|
+
return {
|
|
765
|
+
claimedKeys:
|
|
766
|
+
viewClaims === undefined
|
|
767
|
+
? (c.gpuDrivenShadowDrawKeys ?? new Set<string>())
|
|
768
|
+
: (viewClaims.get(viewKey) ?? new Set<string>()),
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
366
772
|
export function encodeDirectionalShadowPass(
|
|
367
773
|
c: _InternalRenderPipelineContext,
|
|
368
774
|
pass: RhiRenderPassEncoder,
|
|
369
775
|
cascadeIndex: number,
|
|
370
776
|
viewport: { readonly x: number; readonly y: number; readonly w: number; readonly h: number },
|
|
777
|
+
drawFeatures?: (view: BindGroup) => void,
|
|
371
778
|
): void {
|
|
372
|
-
|
|
779
|
+
const directionalIdentity = { kind: 'directional' as const, index: cascadeIndex };
|
|
780
|
+
if (
|
|
781
|
+
(c.gpuDrivenShadowViews?.cacheHit(directionalIdentity) === true ||
|
|
782
|
+
(c.gpuDrivenShadowViews === undefined && c.directionalShadowCacheReuse)) &&
|
|
783
|
+
drawFeatures === undefined
|
|
784
|
+
)
|
|
785
|
+
return;
|
|
373
786
|
const pipeline = shadowPipeline(c);
|
|
374
787
|
const viewBg = ensureTypedShadowViewBg(
|
|
375
788
|
c,
|
|
@@ -377,25 +790,94 @@ export function encodeDirectionalShadowPass(
|
|
|
377
790
|
directionalShadowCasterOffset(cascadeIndex),
|
|
378
791
|
`view-shadow-directional-${cascadeIndex}`,
|
|
379
792
|
);
|
|
380
|
-
const materialBg = ensureSpotShadowMaterialBg(c);
|
|
381
|
-
|
|
793
|
+
const materialBg = viewBg === null ? null : ensureSpotShadowMaterialBg(c);
|
|
794
|
+
const capable = c.gpuDrivenShadowViews?.submission(directionalIdentity) !== undefined;
|
|
795
|
+
if (viewBg === null || materialBg === null) {
|
|
382
796
|
c.frameState.directionalShadowCacheRecorded = false;
|
|
797
|
+
if (capable || hasExpectedShadowCasters(c)) {
|
|
798
|
+
throw new RhiError({
|
|
799
|
+
code: 'rhi-not-available',
|
|
800
|
+
expected: 'directional ShadowCaster bind groups',
|
|
801
|
+
hint: 'publish the view/material resources before recording the shadow lane',
|
|
802
|
+
});
|
|
803
|
+
}
|
|
383
804
|
return;
|
|
384
805
|
}
|
|
806
|
+
c.frameState.directionalShadowCacheRecorded = true;
|
|
385
807
|
pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
|
|
386
|
-
|
|
808
|
+
drawFeatures?.(viewBg);
|
|
387
809
|
pass.setBindGroup(0, viewBg, [0, 0]);
|
|
388
810
|
pass.setBindGroup(1, materialBg, [0]);
|
|
389
|
-
const
|
|
811
|
+
const gpuRecord = recordGpuDrivenShadowIfPublished(c, pass, directionalIdentity);
|
|
812
|
+
const gpuRecorded = gpuRecord !== undefined;
|
|
813
|
+
const claimedKeys = gpuRecord?.claimedKeys;
|
|
814
|
+
if (capable && !gpuRecorded) {
|
|
815
|
+
throw new RhiError({
|
|
816
|
+
code: 'rhi-not-available',
|
|
817
|
+
expected: 'capable directional GPU-driven shadow projection',
|
|
818
|
+
hint: 'terminate the capable shadow frame instead of enumerating CPU casters',
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
if (!hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
822
|
+
if (gpuRecorded || (!capable && !hasExpectedShadowCasters(c))) {
|
|
823
|
+
validateShadowOwnership(c, directionalIdentity, claimedKeys, new Set());
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (pipeline === null) {
|
|
828
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
829
|
+
validateShadowOwnership(c, directionalIdentity, claimedKeys, new Set());
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
if (gpuRecorded) {
|
|
833
|
+
throw new RhiError({
|
|
834
|
+
code: 'rhi-not-available',
|
|
835
|
+
expected: 'CPU residual directional shadow pipeline',
|
|
836
|
+
hint: 'publish the legacy residual caster pipeline alongside the partial GPU shadow lane',
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
throw new RhiError({
|
|
840
|
+
code: 'rhi-not-available',
|
|
841
|
+
expected: 'directional ShadowCaster pipeline',
|
|
842
|
+
hint: 'publish the CPU ShadowCaster pipeline before recording residual casters',
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
pass.setPipeline(pipeline);
|
|
846
|
+
const legacyMeshBindGroup = c.meshBindGroup;
|
|
847
|
+
if (legacyMeshBindGroup === null) {
|
|
848
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
849
|
+
validateShadowOwnership(c, directionalIdentity, claimedKeys, new Set());
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
if (gpuRecorded) {
|
|
853
|
+
throw new RhiError({
|
|
854
|
+
code: 'rhi-not-available',
|
|
855
|
+
expected: 'CPU residual directional shadow mesh binding',
|
|
856
|
+
hint: 'publish legacy mesh resources for the unclaimed ShadowCaster residual',
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
throw new RhiError({
|
|
860
|
+
code: 'rhi-not-available',
|
|
861
|
+
expected: 'directional ShadowCaster mesh binding',
|
|
862
|
+
hint: 'publish the CPU mesh binding before recording residual casters',
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
if (gpuRecorded) pass.setBindGroup(1, materialBg, [0]);
|
|
866
|
+
const recordedKeys = recordShadowCasterDraws(
|
|
390
867
|
c,
|
|
391
868
|
pass,
|
|
392
869
|
pipeline,
|
|
393
|
-
|
|
394
|
-
buildMatchedRenderableIndices(c
|
|
870
|
+
legacyMeshBindGroup,
|
|
871
|
+
buildMatchedRenderableIndices(shadowDispatchEntries(c), { LightMode: ['ShadowCaster'] }),
|
|
872
|
+
buildMatchedMaterialHandlesByRenderable(shadowDispatchEntries(c), {
|
|
873
|
+
LightMode: ['ShadowCaster'],
|
|
874
|
+
}),
|
|
395
875
|
shadowShaderMap(c),
|
|
876
|
+
claimedKeys,
|
|
396
877
|
);
|
|
878
|
+
validateShadowOwnership(c, directionalIdentity, claimedKeys, recordedKeys);
|
|
397
879
|
c.frameState.directionalShadowCacheRecorded =
|
|
398
|
-
cascadeIndex === 0 ?
|
|
880
|
+
cascadeIndex === 0 ? true : c.frameState.directionalShadowCacheRecorded;
|
|
399
881
|
}
|
|
400
882
|
|
|
401
883
|
export function encodePointShadowPass(
|
|
@@ -403,35 +885,110 @@ export function encodePointShadowPass(
|
|
|
403
885
|
pass: RhiRenderPassEncoder,
|
|
404
886
|
snapshotIndex: number,
|
|
405
887
|
face: number,
|
|
888
|
+
drawFeatures?: (view: BindGroup) => void,
|
|
406
889
|
): void {
|
|
407
890
|
const snapshot = c.frameState.pointShadowSnapshots[snapshotIndex];
|
|
408
|
-
if (snapshot === undefined
|
|
409
|
-
const pipeline = shadowPipeline(c);
|
|
891
|
+
if (snapshot === undefined) return;
|
|
892
|
+
const pipeline = shadowPipeline(c, true);
|
|
410
893
|
const viewBg = ensureTypedShadowViewBg(
|
|
411
894
|
c,
|
|
412
895
|
pointShadowViewOffset(snapshot.shadowAtlasLayer, face),
|
|
413
896
|
0,
|
|
414
897
|
`view-shadow-point-${snapshot.shadowAtlasLayer}-${face}`,
|
|
415
898
|
);
|
|
416
|
-
const materialBg = ensureSpotShadowMaterialBg(c);
|
|
417
|
-
|
|
418
|
-
|
|
899
|
+
const materialBg = viewBg === null ? null : ensureSpotShadowMaterialBg(c);
|
|
900
|
+
const pointIdentity = { kind: 'point' as const, index: snapshotIndex, face };
|
|
901
|
+
const capable = c.gpuDrivenShadowViews?.submission(pointIdentity) !== undefined;
|
|
902
|
+
if (viewBg === null || materialBg === null) {
|
|
903
|
+
if (capable || hasExpectedShadowCasters(c))
|
|
904
|
+
throw new RhiError({
|
|
905
|
+
code: 'rhi-not-available',
|
|
906
|
+
expected: 'point ShadowCaster bind groups',
|
|
907
|
+
hint: 'publish the view/material resources before recording the shadow lane',
|
|
908
|
+
});
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
if (pipeline === null && !capable && !hasExpectedShadowCasters(c) && drawFeatures === undefined)
|
|
912
|
+
return;
|
|
913
|
+
drawFeatures?.(viewBg);
|
|
419
914
|
pass.setBindGroup(0, viewBg, [0, 0]);
|
|
420
915
|
pass.setBindGroup(1, materialBg, [0]);
|
|
421
|
-
|
|
916
|
+
const gpuRecord = recordGpuDrivenShadowIfPublished(c, pass, pointIdentity);
|
|
917
|
+
const gpuRecorded = gpuRecord !== undefined;
|
|
918
|
+
const claimedKeys = gpuRecord?.claimedKeys;
|
|
919
|
+
if (capable && !gpuRecorded) {
|
|
920
|
+
throw new RhiError({
|
|
921
|
+
code: 'rhi-not-available',
|
|
922
|
+
expected: 'capable point GPU-driven shadow projection',
|
|
923
|
+
hint: 'terminate the capable shadow frame instead of enumerating CPU casters',
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
if (!hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
927
|
+
if (gpuRecorded || (!capable && !hasExpectedShadowCasters(c))) {
|
|
928
|
+
validateShadowOwnership(c, pointIdentity, claimedKeys, new Set());
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (pipeline === null) {
|
|
933
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
934
|
+
validateShadowOwnership(c, pointIdentity, claimedKeys, new Set());
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
if (gpuRecorded) {
|
|
938
|
+
throw new RhiError({
|
|
939
|
+
code: 'rhi-not-available',
|
|
940
|
+
expected: 'CPU residual point shadow pipeline',
|
|
941
|
+
hint: 'publish the legacy residual caster pipeline alongside the partial GPU shadow lane',
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
throw new RhiError({
|
|
945
|
+
code: 'rhi-not-available',
|
|
946
|
+
expected: 'point ShadowCaster pipeline',
|
|
947
|
+
hint: 'publish the CPU ShadowCaster pipeline before recording residual casters',
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
pass.setPipeline(pipeline);
|
|
951
|
+
const legacyMeshBindGroup = c.meshBindGroup;
|
|
952
|
+
if (legacyMeshBindGroup === null) {
|
|
953
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
954
|
+
validateShadowOwnership(c, pointIdentity, claimedKeys, new Set());
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (gpuRecorded) {
|
|
958
|
+
throw new RhiError({
|
|
959
|
+
code: 'rhi-not-available',
|
|
960
|
+
expected: 'CPU residual point shadow mesh binding',
|
|
961
|
+
hint: 'publish legacy mesh resources for the unclaimed ShadowCaster residual',
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
throw new RhiError({
|
|
965
|
+
code: 'rhi-not-available',
|
|
966
|
+
expected: 'point ShadowCaster mesh binding',
|
|
967
|
+
hint: 'publish the CPU mesh binding before recording residual casters',
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
if (gpuRecorded) pass.setBindGroup(1, materialBg, [0]);
|
|
971
|
+
const recordedKeys = recordShadowCasterDraws(
|
|
422
972
|
c,
|
|
423
973
|
pass,
|
|
424
974
|
pipeline,
|
|
425
|
-
|
|
426
|
-
buildMatchedRenderableIndices(c
|
|
975
|
+
legacyMeshBindGroup,
|
|
976
|
+
buildMatchedRenderableIndices(shadowDispatchEntries(c), { LightMode: ['ShadowCaster'] }),
|
|
977
|
+
buildMatchedMaterialHandlesByRenderable(shadowDispatchEntries(c), {
|
|
978
|
+
LightMode: ['ShadowCaster'],
|
|
979
|
+
}),
|
|
427
980
|
shadowShaderMap(c),
|
|
981
|
+
claimedKeys,
|
|
982
|
+
true,
|
|
428
983
|
);
|
|
984
|
+
validateShadowOwnership(c, pointIdentity, claimedKeys, recordedKeys);
|
|
429
985
|
}
|
|
430
986
|
|
|
431
987
|
export function encodeSpotShadowPass(
|
|
432
988
|
c: _InternalRenderPipelineContext,
|
|
433
989
|
pass: RhiRenderPassEncoder,
|
|
434
990
|
snapshotIndex: number,
|
|
991
|
+
drawFeatures?: (view: BindGroup) => void,
|
|
435
992
|
): void {
|
|
436
993
|
const snapshot = c.frameState.spotShadowSnapshots.filter(
|
|
437
994
|
(candidate) => candidate.shadowAtlasTile >= 0 && candidate.lightViewProj !== undefined,
|
|
@@ -439,8 +996,7 @@ export function encodeSpotShadowPass(
|
|
|
439
996
|
if (
|
|
440
997
|
snapshot === undefined ||
|
|
441
998
|
snapshot.shadowAtlasTile < 0 ||
|
|
442
|
-
snapshot.lightViewProj === undefined
|
|
443
|
-
c.meshBindGroup === null
|
|
999
|
+
snapshot.lightViewProj === undefined
|
|
444
1000
|
) {
|
|
445
1001
|
return;
|
|
446
1002
|
}
|
|
@@ -451,8 +1007,20 @@ export function encodeSpotShadowPass(
|
|
|
451
1007
|
spotShadowCasterOffset(snapshot.shadowAtlasTile),
|
|
452
1008
|
`view-shadow-spot-${snapshot.shadowAtlasTile}`,
|
|
453
1009
|
);
|
|
454
|
-
const materialBg = ensureSpotShadowMaterialBg(c);
|
|
455
|
-
|
|
1010
|
+
const materialBg = viewBg === null ? null : ensureSpotShadowMaterialBg(c);
|
|
1011
|
+
const spotIdentity = { kind: 'spot' as const, index: snapshotIndex };
|
|
1012
|
+
const capable = c.gpuDrivenShadowViews?.submission(spotIdentity) !== undefined;
|
|
1013
|
+
if (viewBg === null || materialBg === null) {
|
|
1014
|
+
if (capable || hasExpectedShadowCasters(c))
|
|
1015
|
+
throw new RhiError({
|
|
1016
|
+
code: 'rhi-not-available',
|
|
1017
|
+
expected: 'spot ShadowCaster bind groups',
|
|
1018
|
+
hint: 'publish the view/material resources before recording the shadow lane',
|
|
1019
|
+
});
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
if (pipeline === null && !capable && !hasExpectedShadowCasters(c) && drawFeatures === undefined)
|
|
1023
|
+
return;
|
|
456
1024
|
const tileSize = c.pipelineState.perPassResources.shadowMapSize;
|
|
457
1025
|
const tile = snapshot.shadowAtlasTile;
|
|
458
1026
|
pass.setViewport(
|
|
@@ -463,21 +1031,81 @@ export function encodeSpotShadowPass(
|
|
|
463
1031
|
0,
|
|
464
1032
|
1,
|
|
465
1033
|
);
|
|
466
|
-
|
|
1034
|
+
drawFeatures?.(viewBg);
|
|
467
1035
|
pass.setBindGroup(0, viewBg, [0, 0]);
|
|
468
1036
|
pass.setBindGroup(1, materialBg, [0]);
|
|
469
1037
|
// Reuse the shared caster recorder. The compact spot-only loop used to
|
|
470
1038
|
// depend on instance bindings created by a directional pass, so a scene
|
|
471
1039
|
// containing only a SpotLight opened the atlas pass but emitted no caster
|
|
472
1040
|
// draws.
|
|
473
|
-
|
|
1041
|
+
const gpuRecord = recordGpuDrivenShadowIfPublished(c, pass, spotIdentity);
|
|
1042
|
+
const gpuRecorded = gpuRecord !== undefined;
|
|
1043
|
+
const claimedKeys = gpuRecord?.claimedKeys;
|
|
1044
|
+
if (capable && !gpuRecorded) {
|
|
1045
|
+
throw new RhiError({
|
|
1046
|
+
code: 'rhi-not-available',
|
|
1047
|
+
expected: 'capable spot GPU-driven shadow projection',
|
|
1048
|
+
hint: 'terminate the capable shadow frame instead of enumerating CPU casters',
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
if (!hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
1052
|
+
if (gpuRecorded || (!capable && !hasExpectedShadowCasters(c))) {
|
|
1053
|
+
validateShadowOwnership(c, spotIdentity, claimedKeys, new Set());
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
if (pipeline === null) {
|
|
1058
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
1059
|
+
validateShadowOwnership(c, spotIdentity, claimedKeys, new Set());
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
if (gpuRecorded) {
|
|
1063
|
+
throw new RhiError({
|
|
1064
|
+
code: 'rhi-not-available',
|
|
1065
|
+
expected: 'CPU residual spot shadow pipeline',
|
|
1066
|
+
hint: 'publish the legacy residual caster pipeline alongside the partial GPU shadow lane',
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
throw new RhiError({
|
|
1070
|
+
code: 'rhi-not-available',
|
|
1071
|
+
expected: 'spot ShadowCaster pipeline',
|
|
1072
|
+
hint: 'publish the CPU ShadowCaster pipeline before recording residual casters',
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
pass.setPipeline(pipeline);
|
|
1076
|
+
const legacyMeshBindGroup = c.meshBindGroup;
|
|
1077
|
+
if (legacyMeshBindGroup === null) {
|
|
1078
|
+
if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
|
|
1079
|
+
validateShadowOwnership(c, spotIdentity, claimedKeys, new Set());
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
if (gpuRecorded) {
|
|
1083
|
+
throw new RhiError({
|
|
1084
|
+
code: 'rhi-not-available',
|
|
1085
|
+
expected: 'CPU residual spot shadow mesh binding',
|
|
1086
|
+
hint: 'publish legacy mesh resources for the unclaimed ShadowCaster residual',
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
throw new RhiError({
|
|
1090
|
+
code: 'rhi-not-available',
|
|
1091
|
+
expected: 'spot ShadowCaster mesh binding',
|
|
1092
|
+
hint: 'publish the CPU mesh binding before recording residual casters',
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
if (gpuRecorded) pass.setBindGroup(1, materialBg, [0]);
|
|
1096
|
+
const recordedKeys = recordShadowCasterDraws(
|
|
474
1097
|
c,
|
|
475
1098
|
pass,
|
|
476
1099
|
pipeline,
|
|
477
|
-
|
|
478
|
-
buildMatchedRenderableIndices(c
|
|
1100
|
+
legacyMeshBindGroup,
|
|
1101
|
+
buildMatchedRenderableIndices(shadowDispatchEntries(c), { LightMode: ['ShadowCaster'] }),
|
|
1102
|
+
buildMatchedMaterialHandlesByRenderable(shadowDispatchEntries(c), {
|
|
1103
|
+
LightMode: ['ShadowCaster'],
|
|
1104
|
+
}),
|
|
479
1105
|
shadowShaderMap(c),
|
|
1106
|
+
claimedKeys,
|
|
480
1107
|
);
|
|
1108
|
+
validateShadowOwnership(c, spotIdentity, claimedKeys, recordedKeys);
|
|
481
1109
|
}
|
|
482
1110
|
|
|
483
1111
|
/**
|
|
@@ -541,16 +1169,41 @@ export function buildMatchedRenderableIndices(
|
|
|
541
1169
|
* filtering is insufficient for geometry recording: a Deferred graph pass
|
|
542
1170
|
* must not draw the same renderable's Forward-only material. Keep the
|
|
543
1171
|
* renderable key in the result so mixed-material meshes retain their
|
|
544
|
-
* per-submesh routing.
|
|
1172
|
+
* per-submesh routing. An optional exclusion selector removes a material
|
|
1173
|
+
* handle when that same renderable/material pair also contributes to another
|
|
1174
|
+
* graph lane (for example Deferred Standard geometry after SSR composition).
|
|
1175
|
+
* An inclusion selector intersects membership by the same renderable/material
|
|
1176
|
+
* pair without replacing the selected shader pass or its authored entries.
|
|
545
1177
|
*/
|
|
546
1178
|
export function buildMatchedMaterialHandlesByRenderable(
|
|
547
1179
|
dispatch: readonly DispatchEntry[],
|
|
548
1180
|
selector: PassSelector,
|
|
1181
|
+
excludeSelector?: PassSelector,
|
|
1182
|
+
includeSelector?: PassSelector,
|
|
549
1183
|
): ReadonlyMap<number, ReadonlySet<number>> | null {
|
|
550
1184
|
if (dispatch.length === 0) return null;
|
|
551
1185
|
const matched = filterDispatchBySelector(dispatch, selector);
|
|
1186
|
+
const included =
|
|
1187
|
+
includeSelector === undefined
|
|
1188
|
+
? undefined
|
|
1189
|
+
: new Set(
|
|
1190
|
+
filterDispatchBySelector(dispatch, includeSelector).map(
|
|
1191
|
+
(entry) => `${entry.renderableIndex}:${entry.materialHandle}`,
|
|
1192
|
+
),
|
|
1193
|
+
);
|
|
1194
|
+
const excluded =
|
|
1195
|
+
excludeSelector === undefined
|
|
1196
|
+
? undefined
|
|
1197
|
+
: new Set(
|
|
1198
|
+
filterDispatchBySelector(dispatch, excludeSelector).map(
|
|
1199
|
+
(entry) => `${entry.renderableIndex}:${entry.materialHandle}`,
|
|
1200
|
+
),
|
|
1201
|
+
);
|
|
552
1202
|
const handlesByRenderable = new Map<number, Set<number>>();
|
|
553
1203
|
for (const entry of matched) {
|
|
1204
|
+
if (included !== undefined && !included.has(`${entry.renderableIndex}:${entry.materialHandle}`))
|
|
1205
|
+
continue;
|
|
1206
|
+
if (excluded?.has(`${entry.renderableIndex}:${entry.materialHandle}`)) continue;
|
|
554
1207
|
const handles = handlesByRenderable.get(entry.renderableIndex);
|
|
555
1208
|
if (handles === undefined) {
|
|
556
1209
|
handlesByRenderable.set(entry.renderableIndex, new Set([entry.materialHandle]));
|
|
@@ -569,25 +1222,36 @@ export function buildMatchedMaterialHandlesByRenderable(
|
|
|
569
1222
|
* and issues the per-submesh depth draws. `shadowPass` view/material bind
|
|
570
1223
|
* groups (@group 0/1) are already set by the caller; this loop owns @group
|
|
571
1224
|
* 2/3 + the vertex/index/pipeline de-dup state. Receives the explicit
|
|
572
|
-
* `_InternalRenderPipelineContext` (`c`) plus the caller-resolved
|
|
573
|
-
*
|
|
1225
|
+
* `_InternalRenderPipelineContext` (`c`) plus the caller-resolved
|
|
1226
|
+
* mesh bind group, pass-selector match set, and per-material
|
|
574
1227
|
* ShadowCaster dispatch map so no cross-function mutable state is introduced.
|
|
575
1228
|
*/
|
|
576
|
-
function recordShadowCasterDraws(
|
|
1229
|
+
export function recordShadowCasterDraws(
|
|
577
1230
|
c: _InternalRenderPipelineContext,
|
|
578
1231
|
shadowPass: RhiRenderPassEncoder,
|
|
579
1232
|
shadowPipeline: RenderPipeline,
|
|
580
1233
|
shadowMeshBindGroup: BindGroup,
|
|
581
1234
|
matchedIndices: Set<number> | null,
|
|
1235
|
+
matchedMaterialHandles: ReadonlyMap<number, ReadonlySet<number>> | null,
|
|
582
1236
|
shadowDispatchByRenderableIdx: ShadowDispatchMap,
|
|
583
|
-
|
|
584
|
-
|
|
1237
|
+
gpuDrivenShadowDrawKeys: ReadonlySet<string> | undefined,
|
|
1238
|
+
reflected = false,
|
|
1239
|
+
): ReadonlySet<string> {
|
|
1240
|
+
const {
|
|
1241
|
+
runtime,
|
|
1242
|
+
pipelineState,
|
|
1243
|
+
validatedOrdered: mainValidatedOrdered,
|
|
1244
|
+
shadowValidatedOrdered,
|
|
1245
|
+
} = c;
|
|
1246
|
+
const validatedOrdered = shadowValidatedOrdered ?? mainValidatedOrdered;
|
|
1247
|
+
const meshSsboBase = c.shadowMeshSsboBase ?? 0;
|
|
585
1248
|
// M-3 / w12: vertexBuffer/indexBuffer state locals migrate to GpuBuffer
|
|
586
1249
|
// (the wrapper) -- the de-dup compare uses wrapper identity (one wrapper
|
|
587
1250
|
// per RHI handle from gpuStore), and `.handle` is passed to the RHI
|
|
588
1251
|
// setVertexBuffer / setIndexBuffer call.
|
|
589
1252
|
let shadowLastVertexBuffer: GpuBuffer | null = null;
|
|
590
1253
|
let shadowLastIndexBuffer: GpuBuffer | null = null;
|
|
1254
|
+
const recordedKeys = new Set<string>();
|
|
591
1255
|
// bug-20260619-csm RC-3 (D-3): track the currently-bound shadow PSO so
|
|
592
1256
|
// per-entity setPipeline only fires on change (same de-dup discipline as
|
|
593
1257
|
// vertex/index buffers above). The default-shadow-caster PSO is already
|
|
@@ -595,7 +1259,6 @@ function recordShadowCasterDraws(
|
|
|
595
1259
|
// ShadowCaster PSO when a material supplies one.
|
|
596
1260
|
let shadowLastPipeline: RenderPipeline = shadowPipeline;
|
|
597
1261
|
let materialDeps: PerSubmeshMaterialBgDeps | undefined;
|
|
598
|
-
let complete = true;
|
|
599
1262
|
|
|
600
1263
|
for (let i = 0; i < validatedOrdered.length; i++) {
|
|
601
1264
|
const entry = validatedOrdered[i];
|
|
@@ -604,17 +1267,30 @@ function recordShadowCasterDraws(
|
|
|
604
1267
|
// feat-20260609 M2: skip entities that don't match the pass selector.
|
|
605
1268
|
if (matchedIndices !== null && !matchedIndices.has(entry.renderableIndex)) continue;
|
|
606
1269
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if (
|
|
610
|
-
|
|
611
|
-
continue;
|
|
1270
|
+
const drawIndices = shadowDrawIndicesBySubmesh(entry.source, entry.mesh);
|
|
1271
|
+
const shadowSubmeshes = entry.mesh.submeshes.flatMap((submesh, submeshIndex) => {
|
|
1272
|
+
if (submesh.topology !== 'triangle-list' && submesh.topology !== 'triangle-strip') {
|
|
1273
|
+
return [];
|
|
612
1274
|
}
|
|
613
|
-
if (
|
|
614
|
-
|
|
615
|
-
|
|
1275
|
+
if (matchedMaterialHandles !== null) {
|
|
1276
|
+
const handles = matchedMaterialHandles.get(entry.renderableIndex);
|
|
1277
|
+
const material = entry.source.materials[submesh.materialSlot] ?? entry.source.material;
|
|
1278
|
+
if (handles === undefined || !handles.has(material.materialHandle ?? -1)) return [];
|
|
616
1279
|
}
|
|
617
|
-
|
|
1280
|
+
return isGpuShadowClaimedSubmesh(
|
|
1281
|
+
entry.source,
|
|
1282
|
+
entry.renderableIndex,
|
|
1283
|
+
submeshIndex,
|
|
1284
|
+
drawIndices,
|
|
1285
|
+
gpuDrivenShadowDrawKeys,
|
|
1286
|
+
shadowDispatchByRenderableIdx,
|
|
1287
|
+
c.shadowCasterMembership,
|
|
1288
|
+
c.shadowCasterWorldKeys,
|
|
1289
|
+
)
|
|
1290
|
+
? []
|
|
1291
|
+
: [{ submesh, submeshIndex }];
|
|
1292
|
+
});
|
|
1293
|
+
if (shadowSubmeshes.length === 0) continue;
|
|
618
1294
|
// feat-20260604-mesh-topology-debug-draw M5 / w14 (AC-09, D-A6): the
|
|
619
1295
|
// shadow caster PSO is triangle-list; it only projects triangle faces.
|
|
620
1296
|
// line-list / line-strip / point-list meshes have no surface to cast a
|
|
@@ -624,14 +1300,6 @@ function recordShadowCasterDraws(
|
|
|
624
1300
|
//
|
|
625
1301
|
// feat-20260608 M4 / w16: per-submesh shadow draw — iterate submeshes
|
|
626
1302
|
// 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
1303
|
if (entry.mesh.vertexBuffer !== shadowLastVertexBuffer) {
|
|
636
1304
|
shadowPass.setVertexBuffer(0, entry.mesh.vertexBuffer.handle);
|
|
637
1305
|
shadowLastVertexBuffer = entry.mesh.vertexBuffer;
|
|
@@ -646,39 +1314,250 @@ function recordShadowCasterDraws(
|
|
|
646
1314
|
|
|
647
1315
|
if (entry.source.skin !== undefined) {
|
|
648
1316
|
const skinMeshBindGroup = skinnedShadowMeshBindGroup(c, entry);
|
|
649
|
-
if (skinMeshBindGroup === null)
|
|
1317
|
+
if (skinMeshBindGroup === null) {
|
|
1318
|
+
throw new RhiError({
|
|
1319
|
+
code: 'rhi-not-available',
|
|
1320
|
+
expected: 'CPU skinned ShadowCaster mesh binding',
|
|
1321
|
+
hint: 'publish the skin mesh/palette bind group before recording the residual',
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
650
1324
|
shadowPass.setBindGroup(
|
|
651
1325
|
2,
|
|
652
1326
|
skinMeshBindGroup,
|
|
653
|
-
pbrSkinMeshDynamicOffsets(
|
|
1327
|
+
pbrSkinMeshDynamicOffsets(
|
|
1328
|
+
(meshSsboBase + i) * MESH_PER_ENTITY_STRIDE,
|
|
1329
|
+
entry.source.skin.byteOffset,
|
|
1330
|
+
),
|
|
654
1331
|
);
|
|
655
1332
|
} else {
|
|
656
|
-
shadowPass.setBindGroup(2, shadowMeshBindGroup, [
|
|
1333
|
+
shadowPass.setBindGroup(2, shadowMeshBindGroup, [
|
|
1334
|
+
(meshSsboBase + i) * MESH_PER_ENTITY_STRIDE,
|
|
1335
|
+
]);
|
|
657
1336
|
}
|
|
658
1337
|
|
|
659
|
-
//
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
1338
|
+
// C1 + C2 (w12): per-entity instance buffer + instanceCount
|
|
1339
|
+
let shadowInstanceDraws: readonly { readonly buffer: Buffer; readonly count: number }[] = [
|
|
1340
|
+
{ buffer: pipelineState.identityInstanceBuffer, count: 1 },
|
|
1341
|
+
];
|
|
1342
|
+
const shadowInst = entry.source.instances;
|
|
1343
|
+
if (shadowInst?.instanceCount === 0) continue;
|
|
1344
|
+
if (shadowInst !== undefined) {
|
|
1345
|
+
const uniformFallback = runtime.device.caps.storageBuffer === false;
|
|
1346
|
+
// Uniform fallback uses resident chunks because the shader's uniform
|
|
1347
|
+
// array is capped at MAX_UNIFORM_INSTANCES entries.
|
|
1348
|
+
if (uniformFallback && shadowInst.instanceCount > MAX_UNIFORM_INSTANCES) {
|
|
1349
|
+
const ownerKey = instanceCollectionCacheKey(entry.source.worldId, shadowInst);
|
|
1350
|
+
const chunks = c.frameState.instanceBufferChunks;
|
|
1351
|
+
const activeKeys = new Set<string>();
|
|
1352
|
+
const draws: { buffer: Buffer; count: number }[] = [];
|
|
1353
|
+
const strideBytes = 16 * Float32Array.BYTES_PER_ELEMENT;
|
|
1354
|
+
const dirtyRanges = shadowInst.dirtyRanges ?? [{ start: 0, end: shadowInst.instanceCount }];
|
|
1355
|
+
const uploadedRanges: { start: number; end: number }[] = [];
|
|
1356
|
+
let uploadedBytes = 0;
|
|
1357
|
+
let failed = false;
|
|
1358
|
+
for (let start = 0; start < shadowInst.instanceCount; start += MAX_UNIFORM_INSTANCES) {
|
|
1359
|
+
const count = Math.min(MAX_UNIFORM_INSTANCES, shadowInst.instanceCount - start);
|
|
1360
|
+
const chunkKey = `${ownerKey}:${start}`;
|
|
1361
|
+
activeKeys.add(chunkKey);
|
|
1362
|
+
const payloadBytes = count * strideBytes;
|
|
1363
|
+
const previous = chunks?.get(chunkKey);
|
|
1364
|
+
let active =
|
|
1365
|
+
previous !== undefined &&
|
|
1366
|
+
previous.uploadedArchVersion === shadowInst.archVersion &&
|
|
1367
|
+
previous.uploadedByteLength === payloadBytes
|
|
1368
|
+
? previous
|
|
1369
|
+
: undefined;
|
|
1370
|
+
let activeIsNew = false;
|
|
1371
|
+
if (active === undefined) {
|
|
1372
|
+
const created = runtime.device.createBuffer({
|
|
1373
|
+
size: INSTANCE_UBO_FULL_ARRAY_BYTES,
|
|
1374
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1375
|
+
mappedAtCreation: false,
|
|
1376
|
+
});
|
|
1377
|
+
if (!created.ok) {
|
|
1378
|
+
runtime.errorRegistry.fire(created.error);
|
|
1379
|
+
failed = true;
|
|
1380
|
+
break;
|
|
1381
|
+
}
|
|
1382
|
+
if (previous !== undefined && !previous.buffer.isDestroyed) {
|
|
1383
|
+
const destroyed = previous.buffer.destroy();
|
|
1384
|
+
if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
|
|
1385
|
+
}
|
|
1386
|
+
active = {
|
|
1387
|
+
buffer: new GpuBuffer(runtime.device, created.value),
|
|
1388
|
+
uploadedArchVersion: shadowInst.archVersion,
|
|
1389
|
+
uploadedByteLength: payloadBytes,
|
|
1390
|
+
};
|
|
1391
|
+
activeIsNew = true;
|
|
1392
|
+
chunks?.set(chunkKey, active);
|
|
1393
|
+
if (chunks === undefined) c.frameState.transientInstanceBuffers.push(active);
|
|
1394
|
+
}
|
|
1395
|
+
const localRanges = dirtyRanges
|
|
1396
|
+
.map((range) => ({
|
|
1397
|
+
start: Math.max(start, range.start) - start,
|
|
1398
|
+
end: Math.min(start + count, range.end) - start,
|
|
1399
|
+
}))
|
|
1400
|
+
.filter((range) => range.end > range.start);
|
|
1401
|
+
const rangesToUpload = instanceUploadRangesForResident(activeIsNew, localRanges, {
|
|
1402
|
+
start: 0,
|
|
1403
|
+
end: count,
|
|
1404
|
+
});
|
|
1405
|
+
const needsUpload =
|
|
1406
|
+
activeIsNew ||
|
|
1407
|
+
(shadowInst.revision !== undefined && active.uploadedRevision !== shadowInst.revision);
|
|
1408
|
+
if (needsUpload && rangesToUpload.length > 0) {
|
|
1409
|
+
for (const range of rangesToUpload) {
|
|
1410
|
+
const source = shadowInst.transforms.subarray(
|
|
1411
|
+
(start + range.start) * 16,
|
|
1412
|
+
(start + range.end) * 16,
|
|
1413
|
+
);
|
|
1414
|
+
const written = runtime.device.queue.writeBuffer(
|
|
1415
|
+
active.buffer.handle,
|
|
1416
|
+
range.start * strideBytes,
|
|
1417
|
+
source,
|
|
1418
|
+
);
|
|
1419
|
+
if (!written.ok) {
|
|
1420
|
+
runtime.errorRegistry.fire(written.error);
|
|
1421
|
+
failed = true;
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
uploadedRanges.push({ start: start + range.start, end: start + range.end });
|
|
1425
|
+
uploadedBytes += source.byteLength;
|
|
1426
|
+
}
|
|
1427
|
+
if (failed) break;
|
|
1428
|
+
}
|
|
1429
|
+
if (needsUpload && shadowInst.revision !== undefined) {
|
|
1430
|
+
active = { ...active, uploadedRevision: shadowInst.revision };
|
|
1431
|
+
chunks?.set(chunkKey, active);
|
|
1432
|
+
}
|
|
1433
|
+
draws.push({ buffer: active.buffer.handle, count });
|
|
1434
|
+
}
|
|
1435
|
+
shadowInstanceDraws = failed
|
|
1436
|
+
? [{ buffer: pipelineState.identityInstanceBuffer, count: 1 }]
|
|
1437
|
+
: draws;
|
|
1438
|
+
if (chunks !== undefined) {
|
|
1439
|
+
const prefix = `${ownerKey}:`;
|
|
1440
|
+
for (const [key, stale] of chunks.entries()) {
|
|
1441
|
+
if (!key.startsWith(prefix) || activeKeys.has(key)) continue;
|
|
1442
|
+
if (!stale.buffer.isDestroyed) {
|
|
1443
|
+
const destroyed = stale.buffer.destroy();
|
|
1444
|
+
if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
|
|
1445
|
+
}
|
|
1446
|
+
chunks.delete(key);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
if (!failed) {
|
|
1450
|
+
reportInstanceResidency(c, shadowInst, {
|
|
1451
|
+
lane: 'chunked-uniform',
|
|
1452
|
+
requestedBytes: shadowInst.instanceCount * strideBytes,
|
|
1453
|
+
supportedBytes: MAX_UNIFORM_INSTANCES * strideBytes,
|
|
1454
|
+
uploadRanges: uploadedRanges,
|
|
1455
|
+
uploadedBytes,
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
} else {
|
|
1459
|
+
const bufUsage = uniformFallback
|
|
1460
|
+
? GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST
|
|
1461
|
+
: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST;
|
|
1462
|
+
const instancePayload = uniformFallback
|
|
1463
|
+
? shadowInst.transforms
|
|
1464
|
+
: packInstanceStorageBuffer(shadowInst.transforms);
|
|
1465
|
+
const requestedBytes = instancePayload.byteLength;
|
|
1466
|
+
const cap = runtime.device.limits.maxStorageBufferBindingSize;
|
|
1467
|
+
if (!uniformFallback && typeof cap === 'number' && cap > 0 && requestedBytes > cap) {
|
|
1468
|
+
const chunked = resolveStorageInstanceChunks(c, entry, shadowInst, cap, undefined, -1);
|
|
1469
|
+
if (chunked !== null) {
|
|
1470
|
+
shadowInstanceDraws = chunked.map((draw) => ({
|
|
1471
|
+
buffer: draw.instanceBuffer,
|
|
1472
|
+
count: draw.instanceCount,
|
|
1473
|
+
}));
|
|
1474
|
+
}
|
|
1475
|
+
} else {
|
|
1476
|
+
const instanceCacheKey = instanceCollectionCacheKey(entry.source.worldId, shadowInst);
|
|
1477
|
+
const cached = c.frameState.instanceBuffers.get(instanceCacheKey);
|
|
1478
|
+
let active: InstanceBufferCacheEntry | null = null;
|
|
1479
|
+
let activeIsNew = false;
|
|
1480
|
+
if (
|
|
1481
|
+
cached !== undefined &&
|
|
1482
|
+
cached.uploadedArchVersion === shadowInst.archVersion &&
|
|
1483
|
+
cached.uploadedByteLength === requestedBytes &&
|
|
1484
|
+
(shadowInst.revision === undefined
|
|
1485
|
+
? cached.uploadedRevision === undefined
|
|
1486
|
+
: cached.uploadedRevision === shadowInst.revision)
|
|
1487
|
+
) {
|
|
1488
|
+
active = cached;
|
|
1489
|
+
} else if (requestedBytes > 0) {
|
|
1490
|
+
const bufRes = runtime.device.createBuffer({
|
|
1491
|
+
size: requestedBytes,
|
|
1492
|
+
usage: bufUsage,
|
|
1493
|
+
mappedAtCreation: false,
|
|
1494
|
+
});
|
|
1495
|
+
if (!bufRes.ok) {
|
|
1496
|
+
runtime.errorRegistry.fire(bufRes.error);
|
|
1497
|
+
} else {
|
|
1498
|
+
// feat-20260619 M4 / F12: destroy the old cached buffer
|
|
1499
|
+
// before replacing it with the new one (D-6).
|
|
1500
|
+
if (cached !== undefined && !cached.buffer.isDestroyed) {
|
|
1501
|
+
const r = cached.buffer.destroy();
|
|
1502
|
+
if (!r.ok) runtime.errorRegistry.fire(r.error);
|
|
1503
|
+
}
|
|
1504
|
+
const newBuffer = new GpuBuffer(runtime.device, bufRes.value);
|
|
1505
|
+
active = {
|
|
1506
|
+
buffer: newBuffer,
|
|
1507
|
+
uploadedArchVersion: shadowInst.archVersion,
|
|
1508
|
+
uploadedByteLength: requestedBytes,
|
|
1509
|
+
};
|
|
1510
|
+
activeIsNew = true;
|
|
1511
|
+
if (active !== null) {
|
|
1512
|
+
// Keep the collection identity in the worldEntityKey namespace.
|
|
1513
|
+
c.frameState.instanceBuffers.set(instanceCacheKey, active);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
if (active !== null) {
|
|
1518
|
+
const needsUpload =
|
|
1519
|
+
activeIsNew ||
|
|
1520
|
+
(shadowInst.revision !== undefined &&
|
|
1521
|
+
active.uploadedRevision !== shadowInst.revision);
|
|
1522
|
+
if (needsUpload) {
|
|
1523
|
+
const writeRes = runtime.device.queue.writeBuffer(
|
|
1524
|
+
active.buffer.handle,
|
|
1525
|
+
0,
|
|
1526
|
+
instancePayload,
|
|
1527
|
+
);
|
|
1528
|
+
if (!writeRes.ok) {
|
|
1529
|
+
runtime.errorRegistry.fire(writeRes.error);
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
if (shadowInst.revision !== undefined) {
|
|
1533
|
+
const published = { ...active, uploadedRevision: shadowInst.revision };
|
|
1534
|
+
c.frameState.instanceBuffers.set(instanceCacheKey, published);
|
|
1535
|
+
active = published;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
reportInstanceResidency(c, shadowInst, {
|
|
1539
|
+
lane: uniformFallback ? 'direct-uniform' : 'direct-storage',
|
|
1540
|
+
requestedBytes,
|
|
1541
|
+
supportedBytes: uniformFallback
|
|
1542
|
+
? MAX_UNIFORM_INSTANCES * Float32Array.BYTES_PER_ELEMENT * 16
|
|
1543
|
+
: typeof cap === 'number'
|
|
1544
|
+
? cap
|
|
1545
|
+
: undefined,
|
|
1546
|
+
uploadRanges: needsUpload ? [{ start: 0, end: shadowInst.instanceCount }] : [],
|
|
1547
|
+
uploadedBytes: needsUpload ? instancePayload.byteLength : 0,
|
|
1548
|
+
});
|
|
1549
|
+
shadowInstanceDraws = [
|
|
1550
|
+
{ buffer: active.buffer.handle, count: Math.max(1, shadowInst.instanceCount) },
|
|
1551
|
+
];
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
677
1556
|
|
|
678
1557
|
// feat-20260608 M4 / w16: per-submesh shadow draw loop.
|
|
679
1558
|
// Only draw submeshes whose topology is triangle-list or triangle-strip
|
|
680
1559
|
// (line-list / point-list submeshes cast no shadow and are skipped).
|
|
681
|
-
for (const sm of shadowSubmeshes) {
|
|
1560
|
+
for (const { submesh: sm, submeshIndex } of shadowSubmeshes) {
|
|
682
1561
|
if (sm.topology !== 'triangle-list' && sm.topology !== 'triangle-strip') {
|
|
683
1562
|
continue;
|
|
684
1563
|
}
|
|
@@ -689,107 +1568,172 @@ function recordShadowCasterDraws(
|
|
|
689
1568
|
// two-sided casters (such as the Three.js teapot) populate the atlas
|
|
690
1569
|
// from both faces. Custom ShadowCaster shaders retain their own path.
|
|
691
1570
|
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) {
|
|
1571
|
+
const shadowDispatches =
|
|
1572
|
+
shadowDispatchByRenderableIdx
|
|
1573
|
+
.get(entry.renderableIndex)
|
|
1574
|
+
?.get(submeshMaterial.materialHandle ?? 0) ?? [];
|
|
1575
|
+
const compactDrawIndex = drawIndices.get(submeshIndex);
|
|
1576
|
+
const draw =
|
|
1577
|
+
compactDrawIndex === undefined
|
|
1578
|
+
? undefined
|
|
1579
|
+
: entry.source.gpuDrivenDraws?.[compactDrawIndex];
|
|
1580
|
+
const drawItemIndex = draw?.drawItemIndex ?? compactDrawIndex ?? submeshIndex;
|
|
1581
|
+
const worldEntity = worldEntityKey(
|
|
1582
|
+
c.shadowCasterWorldKeys?.[entry.source.worldId] ?? entry.source.worldId,
|
|
1583
|
+
entry.source.entityKey,
|
|
1584
|
+
);
|
|
1585
|
+
const residualDispatches =
|
|
1586
|
+
gpuDrivenShadowDrawKeys === undefined
|
|
1587
|
+
? shadowDispatches
|
|
1588
|
+
: shadowDispatches.filter(
|
|
1589
|
+
(dispatch) =>
|
|
1590
|
+
!gpuDrivenShadowDrawKeys.has(
|
|
1591
|
+
gpuDrivenShadowDrawKey(
|
|
1592
|
+
worldEntity,
|
|
1593
|
+
submeshMaterial.materialHandle ?? -1,
|
|
1594
|
+
drawItemIndex,
|
|
1595
|
+
dispatch.passIndex,
|
|
1596
|
+
),
|
|
1597
|
+
),
|
|
1598
|
+
);
|
|
1599
|
+
const passes = residualDispatches.length === 0 ? [undefined] : residualDispatches;
|
|
1600
|
+
if (gpuDrivenShadowDrawKeys !== undefined && residualDispatches.length === 0) continue;
|
|
1601
|
+
// Deformation selects the mesh/palette layout, never the material program.
|
|
1602
|
+
// Each residual keeps its authored shader, entries, state and bindings.
|
|
1603
|
+
for (const shadowDispatch of passes) {
|
|
1604
|
+
const entryShadowShaderId = shadowDispatch?.materialShaderId;
|
|
1605
|
+
const entryShadowRenderState = shadowRasterState(shadowDispatch?.renderState, reflected);
|
|
1606
|
+
const skinned = entry.source.skin !== undefined;
|
|
1607
|
+
let entryShadowPipeline: RenderPipeline | null = shadowPipeline;
|
|
1608
|
+
const needsDedicatedPipeline =
|
|
1609
|
+
shadowDispatch !== undefined || skinned || sm.topology !== 'triangle-list';
|
|
1610
|
+
if (shadowDispatch !== undefined && entryShadowShaderId === undefined) {
|
|
757
1611
|
throw new RhiError({
|
|
758
|
-
code: 'rhi-
|
|
759
|
-
expected: '
|
|
760
|
-
hint: '
|
|
1612
|
+
code: 'rhi-not-available',
|
|
1613
|
+
expected: 'material shader identity for CPU ShadowCaster residual',
|
|
1614
|
+
hint: 'publish a producer-owned ShadowCaster shader identity before recording the residual',
|
|
761
1615
|
});
|
|
762
1616
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1617
|
+
if (needsDedicatedPipeline) {
|
|
1618
|
+
entryShadowPipeline =
|
|
1619
|
+
runtime.getMaterialShaderPipeline?.(
|
|
1620
|
+
entryShadowShaderId ?? SHADOW_CASTER_SHADER_ID,
|
|
1621
|
+
false,
|
|
1622
|
+
entryShadowRenderState,
|
|
1623
|
+
sm.topology,
|
|
1624
|
+
entry.mesh.indexFormat,
|
|
1625
|
+
shadowCasterVariantSet(
|
|
1626
|
+
runtime.device.caps.storageBuffer,
|
|
1627
|
+
skinned,
|
|
1628
|
+
false,
|
|
1629
|
+
Number(shadowDispatch?.paramSnapshot?.alphaCutoff ?? 0) > 0,
|
|
1630
|
+
),
|
|
1631
|
+
'shadow-caster',
|
|
1632
|
+
undefined, // meshAttributes
|
|
1633
|
+
1, // sampleCount
|
|
1634
|
+
undefined,
|
|
1635
|
+
undefined,
|
|
1636
|
+
undefined,
|
|
1637
|
+
undefined,
|
|
1638
|
+
entry.mesh.layoutProjection,
|
|
1639
|
+
// Built-in variants reuse validated prewarm modules, including
|
|
1640
|
+
// adapters with no synchronous shader factory. Authored residual
|
|
1641
|
+
// programs use their synchronous manifest producer at first draw.
|
|
1642
|
+
entryShadowShaderId === undefined || entryShadowShaderId === SHADOW_CASTER_SHADER_ID
|
|
1643
|
+
? 'validated'
|
|
1644
|
+
: 'immediate',
|
|
1645
|
+
skinned ? 'pbr-skin' : 'pbr',
|
|
1646
|
+
undefined, // vertexEntryPoint
|
|
1647
|
+
undefined, // additionalColorFormats
|
|
1648
|
+
shadowDispatch?.vertexEntry,
|
|
1649
|
+
shadowDispatch?.fragmentEntry,
|
|
1650
|
+
) ?? null;
|
|
1651
|
+
}
|
|
1652
|
+
if (entryShadowPipeline === null) {
|
|
1653
|
+
throw new RhiError({
|
|
1654
|
+
code: 'rhi-not-available',
|
|
1655
|
+
expected: `CPU ShadowCaster pipeline ${entryShadowShaderId ?? '<missing>'}`,
|
|
1656
|
+
hint: 'publish the exact residual ShadowCaster pipeline; do not substitute the default shader',
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
if (entryShadowPipeline !== shadowLastPipeline && entryShadowPipeline !== null) {
|
|
1660
|
+
shadowPass.setPipeline(entryShadowPipeline);
|
|
1661
|
+
shadowLastPipeline = entryShadowPipeline;
|
|
1662
|
+
}
|
|
1663
|
+
if (shadowDispatch !== undefined && entryShadowShaderId !== undefined) {
|
|
1664
|
+
materialDeps ??= {
|
|
1665
|
+
runtime,
|
|
1666
|
+
pipelineState,
|
|
1667
|
+
world: c.world,
|
|
1668
|
+
store: c.store,
|
|
1669
|
+
materialSlice: STANDARD_PBR_UBO_SIZE,
|
|
1670
|
+
videoHighPerfAvailable: probeVideoHighPerfUpload(runtime.device),
|
|
1671
|
+
skylightResources: prepareMaterialSkylight(c).skylightResources,
|
|
1672
|
+
resolveRenderTargetTextureSource: runtime.resolveRenderTargetTextureSource,
|
|
1673
|
+
materialBgShared: c.frameState.materialBgShared,
|
|
1674
|
+
materialBgAssemblyCache: c.materialBgAssemblyCache,
|
|
1675
|
+
frameState: c.frameState,
|
|
1676
|
+
bindGroupCounts: c.bindGroupCounts,
|
|
1677
|
+
};
|
|
1678
|
+
const materialSlot =
|
|
1679
|
+
shadowDispatch?.materialSlot ??
|
|
1680
|
+
c.materialSlotIndices[i]?.[sm.materialSlot] ??
|
|
1681
|
+
c.materialSlotIndices[i]?.[0];
|
|
1682
|
+
if (materialSlot === undefined) {
|
|
1683
|
+
throw new RhiError({
|
|
1684
|
+
code: 'rhi-descriptor-invalid',
|
|
1685
|
+
expected: 'a prepared material slot for each shadow submesh',
|
|
1686
|
+
hint: 'repair material slot preparation before recording shadows',
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
const materialGroup = buildPerSubmeshMaterialBg(
|
|
1690
|
+
materialDeps,
|
|
1691
|
+
submeshMaterial,
|
|
1692
|
+
entry.source.entityKey,
|
|
1693
|
+
entry.world ?? c.world,
|
|
1694
|
+
entryShadowShaderId,
|
|
1695
|
+
);
|
|
1696
|
+
shadowPass.setBindGroup(1, materialGroup, [materialSlot * MATERIAL_PER_ENTITY_STRIDE]);
|
|
780
1697
|
} else {
|
|
781
|
-
|
|
1698
|
+
const materialGroup = ensureSpotShadowMaterialBg(c);
|
|
1699
|
+
if (materialGroup === null) {
|
|
1700
|
+
throw new RhiError({
|
|
1701
|
+
code: 'rhi-not-available',
|
|
1702
|
+
expected: 'CPU residual shadow material bind group',
|
|
1703
|
+
hint: 'publish the residual material resources before recording shadows',
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
shadowPass.setBindGroup(1, materialGroup, [0]);
|
|
1707
|
+
}
|
|
1708
|
+
for (const instanceDraw of shadowInstanceDraws) {
|
|
1709
|
+
const shadowInstancesBg = resolveGeometryInstancesBindGroup(c, instanceDraw.buffer);
|
|
1710
|
+
shadowPass.setBindGroup(3, shadowInstancesBg);
|
|
1711
|
+
if (entry.mesh.indexed) {
|
|
1712
|
+
shadowPass.drawIndexed(sm.indexCount, instanceDraw.count, sm.indexOffset, 0, 0);
|
|
1713
|
+
} else {
|
|
1714
|
+
shadowPass.draw(sm.vertexCount, instanceDraw.count, 0, 0);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
if (shadowDispatch !== undefined) {
|
|
1718
|
+
recordedKeys.add(
|
|
1719
|
+
gpuDrivenShadowDrawKey(
|
|
1720
|
+
worldEntity,
|
|
1721
|
+
submeshMaterial.materialHandle ?? -1,
|
|
1722
|
+
drawItemIndex,
|
|
1723
|
+
shadowDispatch.passIndex,
|
|
1724
|
+
),
|
|
1725
|
+
);
|
|
782
1726
|
}
|
|
783
1727
|
}
|
|
784
1728
|
}
|
|
785
1729
|
}
|
|
786
|
-
return
|
|
1730
|
+
return recordedKeys;
|
|
787
1731
|
}
|
|
788
1732
|
|
|
789
1733
|
/**
|
|
790
|
-
* Build (or reuse) the
|
|
791
|
-
*
|
|
792
|
-
*
|
|
1734
|
+
* Build (or reuse) the neutral `shadow-material-singleton` @group(1) BG for
|
|
1735
|
+
* default casters without an authored material dispatch. Authored opaque and
|
|
1736
|
+
* masked residuals bind their own prepared material slots instead. Reuses the same singleton Map
|
|
793
1737
|
* entry encodeDirectionalShadowPass / encodePointShadowPass write so the three paths share
|
|
794
1738
|
* one allocation per frame (D-6).
|
|
795
1739
|
*/
|
|
@@ -841,6 +1785,22 @@ function ensureSpotShadowMaterialBg(c: _InternalRenderPipelineContext): BindGrou
|
|
|
841
1785
|
intensityBuffer: fb.intensityBuffer,
|
|
842
1786
|
})
|
|
843
1787
|
: fallbackEntries;
|
|
1788
|
+
// Binding(46) is the optional scene-material storage table. The material
|
|
1789
|
+
// BGL intentionally omits it on the uniform-only WebGL2 lane, so emitting
|
|
1790
|
+
// an entry unconditionally creates a deferred-invalid bind group in wgpu's
|
|
1791
|
+
// downlevel backend and only surfaces at Queue::submit. GPU-driven alpha
|
|
1792
|
+
// batches build their own receipt-backed group and are storage-capable by
|
|
1793
|
+
// admission; this singleton only needs the optional slot when that same
|
|
1794
|
+
// capability is present.
|
|
1795
|
+
if (runtime.device.caps.storageBuffer) {
|
|
1796
|
+
merged.push({
|
|
1797
|
+
binding: 46,
|
|
1798
|
+
resource: {
|
|
1799
|
+
kind: 'buffer',
|
|
1800
|
+
value: { buffer: pipelineState.meshStorageBuffer.buffer },
|
|
1801
|
+
},
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
844
1804
|
const r = runtime.device.createBindGroup({
|
|
845
1805
|
label: 'shadow-material-bg',
|
|
846
1806
|
layout: pipelineState.materialBindGroupLayout,
|
|
@@ -855,3 +1815,65 @@ function ensureSpotShadowMaterialBg(c: _InternalRenderPipelineContext): BindGrou
|
|
|
855
1815
|
frameState.singletonMaterialCache.set('shadow-material-singleton', r.value);
|
|
856
1816
|
return r.value;
|
|
857
1817
|
}
|
|
1818
|
+
|
|
1819
|
+
/**
|
|
1820
|
+
* Assemble the material group for a GPU-driven alpha-mask caster from the
|
|
1821
|
+
* same material snapshot/residency owner as the main PBR pass. The shadow
|
|
1822
|
+
* shader only consumes the base-color pair and the scene-material table, but
|
|
1823
|
+
* the complete merged layout is still required by the shared pipeline.
|
|
1824
|
+
*/
|
|
1825
|
+
function ensureGpuDrivenAlphaMaskMaterialBg(
|
|
1826
|
+
c: _InternalRenderPipelineContext,
|
|
1827
|
+
projection: GpuDrivenShadowBatchProjection,
|
|
1828
|
+
): BindGroup {
|
|
1829
|
+
const sceneMaterialBuffer = c.gpuDrivenStandardPbrFrameResources?.sceneMaterialBuffer;
|
|
1830
|
+
if (sceneMaterialBuffer === undefined) {
|
|
1831
|
+
throw new RhiError({
|
|
1832
|
+
code: 'rhi-not-available',
|
|
1833
|
+
expected: 'GPU Scene material buffer for alpha-mask shadow recording',
|
|
1834
|
+
hint: 'publish the receipt-derived scene material table before the capable shadow pass',
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
const materialKey = `shadow-material-alpha:${projection.materialEntityKey}:${projection.material.materialHandle ?? -1}:${c.store.materialResourceEpoch}`;
|
|
1838
|
+
const cached = c.frameState.singletonMaterialCache.get(materialKey);
|
|
1839
|
+
if (cached !== undefined) return cached;
|
|
1840
|
+
const fallback = c.pipelineState.skylightFallback;
|
|
1841
|
+
if (fallback === null) {
|
|
1842
|
+
throw new RhiError({
|
|
1843
|
+
code: 'webgpu-runtime-error',
|
|
1844
|
+
expected: 'pipelineState.skylightFallback for alpha-mask shadow material layout',
|
|
1845
|
+
hint: 'create the merged Standard PBR material resources before recording shadows',
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
const skylightResources = {
|
|
1849
|
+
irradianceView: fallback.irradianceView,
|
|
1850
|
+
irradianceSampler: fallback.sampler,
|
|
1851
|
+
prefilterView: fallback.prefilterView,
|
|
1852
|
+
prefilterSampler: fallback.sampler,
|
|
1853
|
+
brdfLutView: fallback.brdfLutView,
|
|
1854
|
+
brdfLutSampler: fallback.sampler,
|
|
1855
|
+
intensityBuffer: fallback.intensityBuffer,
|
|
1856
|
+
} satisfies import('../ibl/skylight-bind-group').SkylightBindGroupResources;
|
|
1857
|
+
const deps: PerSubmeshMaterialBgDeps = {
|
|
1858
|
+
runtime: c.runtime,
|
|
1859
|
+
pipelineState: c.pipelineState,
|
|
1860
|
+
world: c.world,
|
|
1861
|
+
store: c.store,
|
|
1862
|
+
materialSlice: STANDARD_PBR_UBO_SIZE,
|
|
1863
|
+
videoHighPerfAvailable: false,
|
|
1864
|
+
skylightResources,
|
|
1865
|
+
materialBgShared: c.frameState.materialBgShared,
|
|
1866
|
+
materialBgAssemblyCache: c.materialBgAssemblyCache,
|
|
1867
|
+
sceneMaterialBuffer,
|
|
1868
|
+
bindGroupCounts: c.bindGroupCounts,
|
|
1869
|
+
frameState: c.frameState,
|
|
1870
|
+
};
|
|
1871
|
+
const bindGroup = buildPerSubmeshMaterialBg(
|
|
1872
|
+
deps,
|
|
1873
|
+
projection.material,
|
|
1874
|
+
projection.materialEntityKey,
|
|
1875
|
+
c.world,
|
|
1876
|
+
);
|
|
1877
|
+
c.frameState.singletonMaterialCache.set(materialKey, bindGroup);
|
|
1878
|
+
return bindGroup;
|
|
1879
|
+
}
|