@forgeax/engine-render 0.1.2
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/LICENSE +202 -0
- package/README.md +1019 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/analytic-sky-contract.test.d.ts +2 -0
- package/dist/__tests__/analytic-sky-contract.test.d.ts.map +1 -0
- package/dist/__tests__/analytic-sky-falsify.test.d.ts +2 -0
- package/dist/__tests__/analytic-sky-falsify.test.d.ts.map +1 -0
- package/dist/__tests__/assets-runtime-handoff.integration.test.d.ts +2 -0
- package/dist/__tests__/assets-runtime-handoff.integration.test.d.ts.map +1 -0
- package/dist/__tests__/authoring-path.unit.test.d.ts +2 -0
- package/dist/__tests__/authoring-path.unit.test.d.ts.map +1 -0
- package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts +2 -0
- package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts.map +1 -0
- package/dist/__tests__/backend-selection.integration.test.d.ts +2 -0
- package/dist/__tests__/backend-selection.integration.test.d.ts.map +1 -0
- package/dist/__tests__/callback-inference.test-d.d.ts +2 -0
- package/dist/__tests__/callback-inference.test-d.d.ts.map +1 -0
- package/dist/__tests__/camera-temporal.test-d.d.ts +2 -0
- package/dist/__tests__/camera-temporal.test-d.d.ts.map +1 -0
- package/dist/__tests__/camera-temporal.test.d.ts +2 -0
- package/dist/__tests__/camera-temporal.test.d.ts.map +1 -0
- package/dist/__tests__/cluster-binner.test.d.ts +2 -0
- package/dist/__tests__/cluster-binner.test.d.ts.map +1 -0
- package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/construct-renderer-identity.unit.test.d.ts +2 -0
- package/dist/__tests__/construct-renderer-identity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts +2 -0
- package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts.map +1 -0
- package/dist/__tests__/direct-light-snapshot.unit.test.d.ts +2 -0
- package/dist/__tests__/direct-light-snapshot.unit.test.d.ts.map +1 -0
- package/dist/__tests__/directional-shadow-probe-atlas-layout.unit.test.d.ts +2 -0
- package/dist/__tests__/directional-shadow-probe-atlas-layout.unit.test.d.ts.map +1 -0
- package/dist/__tests__/environment-inspection.test.d.ts +2 -0
- package/dist/__tests__/environment-inspection.test.d.ts.map +1 -0
- package/dist/__tests__/factory-contract.integration.test.d.ts +2 -0
- package/dist/__tests__/factory-contract.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fog-ai-user.fixture.d.ts +2 -0
- package/dist/__tests__/fog-ai-user.fixture.d.ts.map +1 -0
- package/dist/__tests__/fog-custom-render-feature.fixture.d.ts +16 -0
- package/dist/__tests__/fog-custom-render-feature.fixture.d.ts.map +1 -0
- package/dist/__tests__/fog-custom-render-feature.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-custom-render-feature.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fog-inspection.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-inspection.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fog-owner-state.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-owner-state.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fog-pipeline-topology.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-pipeline-topology.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fog-public-surface.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-public-surface.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fog-ray-contract.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-ray-contract.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fog-source-absence.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-source-absence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fog-temporal.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-temporal.integration.test.d.ts.map +1 -0
- package/dist/__tests__/frame-chain.unit.test.d.ts +2 -0
- package/dist/__tests__/frame-chain.unit.test.d.ts.map +1 -0
- package/dist/__tests__/frame-observation.contract.test.d.ts +2 -0
- package/dist/__tests__/frame-observation.contract.test.d.ts.map +1 -0
- package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts +2 -0
- package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts +6 -0
- package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-production.integration.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-production.integration.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts +12 -0
- package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-view.browser.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-view.browser.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-view.dawn.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-view.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-view.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-view.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene-field-type-owner.test-d.d.ts +2 -0
- package/dist/__tests__/gpu-scene-field-type-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts +7 -0
- package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts +2 -0
- package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts +2 -0
- package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-scene-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene.dawn.test.d.ts +2 -0
- package/dist/__tests__/gpu-scene.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-scene.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-pipeline.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-pipeline.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ibl/ibl-fallback.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl/ibl-fallback.unit.test.d.ts.map +1 -0
- package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts +2 -0
- package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts.map +1 -0
- package/dist/__tests__/injection-kind-owner.test-d.d.ts +2 -0
- package/dist/__tests__/injection-kind-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts +2 -0
- package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/light-buffer-layout.unit.test.d.ts +2 -0
- package/dist/__tests__/light-buffer-layout.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-contract-inventory.unit.test.d.ts +2 -0
- package/dist/__tests__/material-contract-inventory.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cooked-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cooked-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-derived-error-stop-draw.integration.test.d.ts +2 -0
- package/dist/__tests__/material-derived-error-stop-draw.integration.test.d.ts.map +1 -0
- package/dist/__tests__/material-derived-payload.integration.test.d.ts +2 -0
- package/dist/__tests__/material-derived-payload.integration.test.d.ts.map +1 -0
- package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts +2 -0
- package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts.map +1 -0
- package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts +2 -0
- package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-stride-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/material-stride-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/materials.unit.test.d.ts +2 -0
- package/dist/__tests__/materials.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-material-bindings.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-material-bindings.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-stride-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-stride-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts +2 -0
- package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts +2 -0
- package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts.map +1 -0
- package/dist/__tests__/morph-visual-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/morph-visual-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/param-schema-projection-census.unit.test.d.ts +2 -0
- package/dist/__tests__/param-schema-projection-census.unit.test.d.ts.map +1 -0
- package/dist/__tests__/persistent-render-scene.unit.test.d.ts +2 -0
- package/dist/__tests__/persistent-render-scene.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-error-policy-owner.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline-error-policy-owner.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-pass-trace.test.d.ts +2 -0
- package/dist/__tests__/pipeline-pass-trace.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-stage-owner.test-d.d.ts +2 -0
- package/dist/__tests__/pipeline-stage-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/plugin.test.d.ts +2 -0
- package/dist/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts +2 -0
- package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts +2 -0
- package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-backend-matrix.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-backend-matrix.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-resolver-recording.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-resolver-recording.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-store.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-default-off.test.d.ts +2 -0
- package/dist/__tests__/profiler-default-off.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-phase-catalog.test.d.ts +2 -0
- package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-phase-integration.test.d.ts +2 -0
- package/dist/__tests__/profiler-phase-integration.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-phase-token.test.d.ts +2 -0
- package/dist/__tests__/profiler-phase-token.test.d.ts.map +1 -0
- package/dist/__tests__/public-pipeline-id.test-d.d.ts +2 -0
- package/dist/__tests__/public-pipeline-id.test-d.d.ts.map +1 -0
- package/dist/__tests__/public-surface.test-d.d.ts +2 -0
- package/dist/__tests__/public-surface.test-d.d.ts.map +1 -0
- package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts +2 -0
- package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-dispatch-membership.unit.test.d.ts +2 -0
- package/dist/__tests__/render-dispatch-membership.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-error-code-owner.test-d.d.ts +2 -0
- package/dist/__tests__/render-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-error-detail-ssot.test-d.d.ts +2 -0
- package/dist/__tests__/render-error-detail-ssot.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts +2 -0
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-assembly.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-assembly.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-construction.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-construction.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-context.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-context.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-contract.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-contract.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-docs.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-docs.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-error-code-owner.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-errors.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-gpu-buffer-usage-owner.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-gpu-buffer-usage-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts +2 -0
- package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-graph.integration.test.d.ts +2 -0
- package/dist/__tests__/render-feature-graph.integration.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-graphics-staging.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-graphics-staging.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-order.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-order.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics-recording.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics-recording.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-public-surface.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-public-surface.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-retry.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-retry.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-scheduling.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-scheduling.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-staging.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-staging.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-visibility-context.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-visibility-context.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-zero-work.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-zero-work.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-compute-raster-gpu.d.ts +7 -0
- package/dist/__tests__/render-graph-compute-raster-gpu.d.ts.map +1 -0
- package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts +2 -0
- package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts +2 -0
- package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-hzb-gpu.d.ts +7 -0
- package/dist/__tests__/render-graph-hzb-gpu.d.ts.map +1 -0
- package/dist/__tests__/render-graph-hzb.browser.test.d.ts +2 -0
- package/dist/__tests__/render-graph-hzb.browser.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-hzb.dawn.test.d.ts +2 -0
- package/dist/__tests__/render-graph-hzb.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-primitives.unit.test.d.ts +2 -0
- package/dist/__tests__/render-graph-primitives.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts +12 -0
- package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts.map +1 -0
- package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts +2 -0
- package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts +2 -0
- package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/render-scene-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/render-scene-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-lifecycle.integration.test.d.ts +2 -0
- package/dist/__tests__/renderer-lifecycle.integration.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-render-scene-public.test-d.d.ts +2 -0
- package/dist/__tests__/renderer-render-scene-public.test-d.d.ts.map +1 -0
- package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts +2 -0
- package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-temporal-coverage.unit.test.d.ts +2 -0
- package/dist/__tests__/scene-temporal-coverage.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts.map +1 -0
- package/dist/__tests__/shader-stage-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/shader-stage-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sun-snapshot-consumers.test.d.ts +2 -0
- package/dist/__tests__/sun-snapshot-consumers.test.d.ts.map +1 -0
- package/dist/__tests__/surface-backend-kind-owner.test-d.d.ts +2 -0
- package/dist/__tests__/surface-backend-kind-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/surface-configuration.unit.test.d.ts +2 -0
- package/dist/__tests__/surface-configuration.unit.test.d.ts.map +1 -0
- package/dist/__tests__/taa-history-store.unit.test.d.ts +2 -0
- package/dist/__tests__/taa-history-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/temporal-frame-transaction.integration.test.d.ts +2 -0
- package/dist/__tests__/temporal-frame-transaction.integration.test.d.ts.map +1 -0
- package/dist/__tests__/temporal-inspection.unit.test.d.ts +2 -0
- package/dist/__tests__/temporal-inspection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/texture-usage-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/texture-usage-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts +2 -0
- package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts.map +1 -0
- package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts +2 -0
- package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts.map +1 -0
- package/dist/__tests__/urp-pipeline.unit.test.d.ts +2 -0
- package/dist/__tests__/urp-pipeline.unit.test.d.ts.map +1 -0
- package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts +2 -0
- package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts.map +1 -0
- package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts +2 -0
- package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-diagnostics.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-diagnostics.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-extract.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-extract.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-extract.perf.test.d.ts +2 -0
- package/dist/__tests__/visibility-extract.perf.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-hidden-report.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-hidden-report.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-instances.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-instances.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-multi-world.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-multi-world.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-public-surface.test-d.d.ts +2 -0
- package/dist/__tests__/visibility-public-surface.test-d.d.ts.map +1 -0
- package/dist/__tests__/visibility-resolver.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-resolver.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/vocabulary.unit.test.d.ts +2 -0
- package/dist/__tests__/vocabulary.unit.test.d.ts.map +1 -0
- package/dist/assets/__tests__/asset-decoders.unit.test.d.ts +2 -0
- package/dist/assets/__tests__/asset-decoders.unit.test.d.ts.map +1 -0
- package/dist/assets/__tests__/mipmap-generator.unit.test.d.ts +2 -0
- package/dist/assets/__tests__/mipmap-generator.unit.test.d.ts.map +1 -0
- package/dist/assets/__tests__/render-asset-port.unit.test.d.ts +2 -0
- package/dist/assets/__tests__/render-asset-port.unit.test.d.ts.map +1 -0
- package/dist/assets/__tests__/resolve-render-asset.unit.test.d.ts +2 -0
- package/dist/assets/__tests__/resolve-render-asset.unit.test.d.ts.map +1 -0
- package/dist/assets/asset-decoders.d.ts +5 -0
- package/dist/assets/asset-decoders.d.ts.map +1 -0
- package/dist/assets/render-asset-port.d.ts +17 -0
- package/dist/assets/render-asset-port.d.ts.map +1 -0
- package/dist/assets/resolve-render-asset.d.ts +6 -0
- package/dist/assets/resolve-render-asset.d.ts.map +1 -0
- package/dist/authoring.d.ts +12 -0
- package/dist/authoring.d.ts.map +1 -0
- package/dist/authoring.mjs +5 -0
- package/dist/authoring.mjs.map +1 -0
- package/dist/chunk-33UF5SAO.mjs +7397 -0
- package/dist/chunk-33UF5SAO.mjs.map +1 -0
- package/dist/chunk-A5QCJSNL.mjs +29913 -0
- package/dist/chunk-A5QCJSNL.mjs.map +1 -0
- package/dist/chunk-JMSB7AR4.mjs +3 -0
- package/dist/chunk-JMSB7AR4.mjs.map +1 -0
- package/dist/chunk-LXNGO6AO.mjs +569 -0
- package/dist/chunk-LXNGO6AO.mjs.map +1 -0
- package/dist/chunk-QRXPSX4X.mjs +659 -0
- package/dist/chunk-QRXPSX4X.mjs.map +1 -0
- package/dist/chunk-YAKEQPWO.mjs +1439 -0
- package/dist/chunk-YAKEQPWO.mjs.map +1 -0
- package/dist/cluster-binner.d.ts +152 -0
- package/dist/cluster-binner.d.ts.map +1 -0
- package/dist/components/__tests__/atmosphere-red.test.d.ts +2 -0
- package/dist/components/__tests__/atmosphere-red.test.d.ts.map +1 -0
- package/dist/components/__tests__/atmosphere.test.d.ts +2 -0
- package/dist/components/__tests__/atmosphere.test.d.ts.map +1 -0
- package/dist/components/__tests__/fog.test-d.d.ts +2 -0
- package/dist/components/__tests__/fog.test-d.d.ts.map +1 -0
- package/dist/components/__tests__/fog.test.d.ts +2 -0
- package/dist/components/__tests__/fog.test.d.ts.map +1 -0
- package/dist/components/__tests__/schema-parity.unit.test.d.ts +2 -0
- package/dist/components/__tests__/schema-parity.unit.test.d.ts.map +1 -0
- package/dist/components/__tests__/visibility-schema.unit.test.d.ts +2 -0
- package/dist/components/__tests__/visibility-schema.unit.test.d.ts.map +1 -0
- package/dist/components/atmosphere.d.ts +18 -0
- package/dist/components/atmosphere.d.ts.map +1 -0
- package/dist/components/camera.d.ts +311 -0
- package/dist/components/camera.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +76 -0
- package/dist/components/directional-light.d.ts.map +1 -0
- package/dist/components/environment.d.ts +116 -0
- package/dist/components/environment.d.ts.map +1 -0
- package/dist/components/glyph-text.d.ts +26 -0
- package/dist/components/glyph-text.d.ts.map +1 -0
- package/dist/components/index.d.ts +22 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/instances.d.ts +41 -0
- package/dist/components/instances.d.ts.map +1 -0
- package/dist/components/layer.d.ts +34 -0
- package/dist/components/layer.d.ts.map +1 -0
- package/dist/components/light-helpers.d.ts +53 -0
- package/dist/components/light-helpers.d.ts.map +1 -0
- package/dist/components/mesh-filter.d.ts +24 -0
- package/dist/components/mesh-filter.d.ts.map +1 -0
- package/dist/components/mesh-renderer.d.ts +33 -0
- package/dist/components/mesh-renderer.d.ts.map +1 -0
- package/dist/components/point-light-shadow.d.ts +27 -0
- package/dist/components/point-light-shadow.d.ts.map +1 -0
- package/dist/components/point-light.d.ts +35 -0
- package/dist/components/point-light.d.ts.map +1 -0
- package/dist/components/post-process-params.d.ts +5 -0
- package/dist/components/post-process-params.d.ts.map +1 -0
- package/dist/components/scene-instance.d.ts +140 -0
- package/dist/components/scene-instance.d.ts.map +1 -0
- package/dist/components/sort-key.d.ts +44 -0
- package/dist/components/sort-key.d.ts.map +1 -0
- package/dist/components/spot-light.d.ts +81 -0
- package/dist/components/spot-light.d.ts.map +1 -0
- package/dist/components/sprite-animation.d.ts +75 -0
- package/dist/components/sprite-animation.d.ts.map +1 -0
- package/dist/components/sprite-instances.d.ts +51 -0
- package/dist/components/sprite-instances.d.ts.map +1 -0
- package/dist/components/sprite-playback-mode.d.ts +37 -0
- package/dist/components/sprite-playback-mode.d.ts.map +1 -0
- package/dist/components/sprite-region-override.d.ts +47 -0
- package/dist/components/sprite-region-override.d.ts.map +1 -0
- package/dist/components/tile-layer.d.ts +101 -0
- package/dist/components/tile-layer.d.ts.map +1 -0
- package/dist/components/tilemap.d.ts +25 -0
- package/dist/components/tilemap.d.ts.map +1 -0
- package/dist/components/visibility.d.ts +22 -0
- package/dist/components/visibility.d.ts.map +1 -0
- package/dist/construct-renderer.d.ts +2 -0
- package/dist/construct-renderer.d.ts.map +1 -0
- package/dist/construct-renderer.mjs +6 -0
- package/dist/construct-renderer.mjs.map +1 -0
- package/dist/debug-draw-glue.d.ts +56 -0
- package/dist/debug-draw-glue.d.ts.map +1 -0
- package/dist/deferred-lighting.d.ts +13 -0
- package/dist/deferred-lighting.d.ts.map +1 -0
- package/dist/dynamic-texture-store.d.ts +42 -0
- package/dist/dynamic-texture-store.d.ts.map +1 -0
- package/dist/engine-metrics.d.ts +23 -0
- package/dist/engine-metrics.d.ts.map +1 -0
- package/dist/environment/__tests__/failure-injection.test.d.ts +2 -0
- package/dist/environment/__tests__/failure-injection.test.d.ts.map +1 -0
- package/dist/environment/__tests__/generation-red.test.d.ts +2 -0
- package/dist/environment/__tests__/generation-red.test.d.ts.map +1 -0
- package/dist/environment/__tests__/generation.test.d.ts +2 -0
- package/dist/environment/__tests__/generation.test.d.ts.map +1 -0
- package/dist/environment/__tests__/graph-transaction.test.d.ts +2 -0
- package/dist/environment/__tests__/graph-transaction.test.d.ts.map +1 -0
- package/dist/environment/__tests__/inspection-red.test.d.ts +2 -0
- package/dist/environment/__tests__/inspection-red.test.d.ts.map +1 -0
- package/dist/environment/__tests__/lifecycle-evidence.test.d.ts +2 -0
- package/dist/environment/__tests__/lifecycle-evidence.test.d.ts.map +1 -0
- package/dist/environment/__tests__/pipeline-topology.test.d.ts +2 -0
- package/dist/environment/__tests__/pipeline-topology.test.d.ts.map +1 -0
- package/dist/environment/__tests__/readiness.test.d.ts +2 -0
- package/dist/environment/__tests__/readiness.test.d.ts.map +1 -0
- package/dist/environment/__tests__/recovery.test.d.ts +2 -0
- package/dist/environment/__tests__/recovery.test.d.ts.map +1 -0
- package/dist/environment/__tests__/resize-dispose.test.d.ts +2 -0
- package/dist/environment/__tests__/resize-dispose.test.d.ts.map +1 -0
- package/dist/environment/__tests__/signature-red.test.d.ts +2 -0
- package/dist/environment/__tests__/signature-red.test.d.ts.map +1 -0
- package/dist/environment/__tests__/signature.test.d.ts +2 -0
- package/dist/environment/__tests__/signature.test.d.ts.map +1 -0
- package/dist/environment/descriptors.d.ts +2 -0
- package/dist/environment/descriptors.d.ts.map +1 -0
- package/dist/environment/extracted-environment.d.ts +53 -0
- package/dist/environment/extracted-environment.d.ts.map +1 -0
- package/dist/environment/generation.d.ts +62 -0
- package/dist/environment/generation.d.ts.map +1 -0
- package/dist/environment/gpu-bundle.d.ts +23 -0
- package/dist/environment/gpu-bundle.d.ts.map +1 -0
- package/dist/environment/graph.d.ts +108 -0
- package/dist/environment/graph.d.ts.map +1 -0
- package/dist/environment/inspection.d.ts +48 -0
- package/dist/environment/inspection.d.ts.map +1 -0
- package/dist/environment/lifecycle.d.ts +22 -0
- package/dist/environment/lifecycle.d.ts.map +1 -0
- package/dist/environment/readiness.d.ts +29 -0
- package/dist/environment/readiness.d.ts.map +1 -0
- package/dist/environment/signature.d.ts +70 -0
- package/dist/environment/signature.d.ts.map +1 -0
- package/dist/errors/ecs-validation.d.ts +3 -0
- package/dist/errors/ecs-validation.d.ts.map +1 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/recover.d.ts +78 -0
- package/dist/errors/recover.d.ts.map +1 -0
- package/dist/errors/render.d.ts +633 -0
- package/dist/errors/render.d.ts.map +1 -0
- package/dist/extract/__tests__/directional-csm-domain.unit.test.d.ts +2 -0
- package/dist/extract/__tests__/directional-csm-domain.unit.test.d.ts.map +1 -0
- package/dist/extract/__tests__/environment-source.test.d.ts +2 -0
- package/dist/extract/__tests__/environment-source.test.d.ts.map +1 -0
- package/dist/extract/__tests__/schema-readers.unit.test.d.ts +2 -0
- package/dist/extract/__tests__/schema-readers.unit.test.d.ts.map +1 -0
- package/dist/extract/directional-csm.d.ts +129 -0
- package/dist/extract/directional-csm.d.ts.map +1 -0
- package/dist/extract/index.d.ts +5 -0
- package/dist/extract/index.d.ts.map +1 -0
- package/dist/extract/material-interface.d.ts +56 -0
- package/dist/extract/material-interface.d.ts.map +1 -0
- package/dist/extract/schema-readers.d.ts +20 -0
- package/dist/extract/schema-readers.d.ts.map +1 -0
- package/dist/extract/visibility.d.ts +26 -0
- package/dist/extract/visibility.d.ts.map +1 -0
- package/dist/features/graph-contribution.d.ts +64 -0
- package/dist/features/graph-contribution.d.ts.map +1 -0
- package/dist/features/host.d.ts +100 -0
- package/dist/features/host.d.ts.map +1 -0
- package/dist/features/index.d.ts +21 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/morph/index.d.ts +3 -0
- package/dist/features/morph/index.d.ts.map +1 -0
- package/dist/features/morph/morph-bounds.d.ts +42 -0
- package/dist/features/morph/morph-bounds.d.ts.map +1 -0
- package/dist/features/morph/morph-feature.d.ts +45 -0
- package/dist/features/morph/morph-feature.d.ts.map +1 -0
- package/dist/features/prepared-gpu-work.d.ts +113 -0
- package/dist/features/prepared-gpu-work.d.ts.map +1 -0
- package/dist/features/prepared-graphics-store.d.ts +65 -0
- package/dist/features/prepared-graphics-store.d.ts.map +1 -0
- package/dist/features/prepared-graphics.d.ts +225 -0
- package/dist/features/prepared-graphics.d.ts.map +1 -0
- package/dist/features/render-graph-compute.d.ts +13 -0
- package/dist/features/render-graph-compute.d.ts.map +1 -0
- package/dist/features/render-graph-contribution.d.ts +15 -0
- package/dist/features/render-graph-contribution.d.ts.map +1 -0
- package/dist/features/render-graph-raster.d.ts +34 -0
- package/dist/features/render-graph-raster.d.ts.map +1 -0
- package/dist/features/render-graph-resources.d.ts +11 -0
- package/dist/features/render-graph-resources.d.ts.map +1 -0
- package/dist/features/scene-temporal.d.ts +45 -0
- package/dist/features/scene-temporal.d.ts.map +1 -0
- package/dist/features/taa-resolve.d.ts +21 -0
- package/dist/features/taa-resolve.d.ts.map +1 -0
- package/dist/features/targets.d.ts +32 -0
- package/dist/features/targets.d.ts.map +1 -0
- package/dist/features/types.d.ts +247 -0
- package/dist/features/types.d.ts.map +1 -0
- package/dist/frame-chain.d.ts +14 -0
- package/dist/frame-chain.d.ts.map +1 -0
- package/dist/fullscreen-post-process-pass.d.ts +178 -0
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -0
- package/dist/glyph-text-layout-system.d.ts +18 -0
- package/dist/glyph-text-layout-system.d.ts.map +1 -0
- package/dist/gpu-driven/batch-topology.d.ts +124 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +150 -0
- package/dist/gpu-driven/production-raster.d.ts.map +1 -0
- package/dist/gpu-driven/view-gpu.d.ts +141 -0
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -0
- package/dist/gpu-driven/view-reference.d.ts +15 -0
- package/dist/gpu-driven/view-reference.d.ts.map +1 -0
- package/dist/gpu-resource-store.d.ts +294 -0
- package/dist/gpu-resource-store.d.ts.map +1 -0
- package/dist/gpu-resource.d.ts +73 -0
- package/dist/gpu-resource.d.ts.map +1 -0
- package/dist/gpu-scene-schema.d.ts +171 -0
- package/dist/gpu-scene-schema.d.ts.map +1 -0
- package/dist/gpu-scene.d.ts +81 -0
- package/dist/gpu-scene.d.ts.map +1 -0
- package/dist/gpu-stage.d.ts +4 -0
- package/dist/gpu-stage.d.ts.map +1 -0
- package/dist/gpu-texture-usage.d.ts +6 -0
- package/dist/gpu-texture-usage.d.ts.map +1 -0
- package/dist/gpu-usage.d.ts +9 -0
- package/dist/gpu-usage.d.ts.map +1 -0
- package/dist/hdrp-buffers.d.ts +213 -0
- package/dist/hdrp-buffers.d.ts.map +1 -0
- package/dist/hdrp-cluster-membership.d.ts +2 -0
- package/dist/hdrp-cluster-membership.d.ts.map +1 -0
- package/dist/hdrp-pipeline.d.ts +36 -0
- package/dist/hdrp-pipeline.d.ts.map +1 -0
- package/dist/ibl/IblPipelineCache.d.ts +274 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -0
- package/dist/ibl/cubemap-projection.d.ts +23 -0
- package/dist/ibl/cubemap-projection.d.ts.map +1 -0
- package/dist/ibl/face-uniforms.d.ts +65 -0
- package/dist/ibl/face-uniforms.d.ts.map +1 -0
- package/dist/ibl/skylight-bind-group.d.ts +130 -0
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +5 -0
- package/dist/index.mjs.map +1 -0
- package/dist/instance-buffer-cache.d.ts +59 -0
- package/dist/instance-buffer-cache.d.ts.map +1 -0
- package/dist/internal.d.ts +74 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.mjs +45 -0
- package/dist/internal.mjs.map +1 -0
- package/dist/lifecycle.d.ts +28 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/light-buffer-layout.d.ts +164 -0
- package/dist/light-buffer-layout.d.ts.map +1 -0
- package/dist/materials.d.ts +50 -0
- package/dist/materials.d.ts.map +1 -0
- package/dist/mesh-material-bindings.d.ts +44 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -0
- package/dist/mesh-update-errors.d.ts +71 -0
- package/dist/mesh-update-errors.d.ts.map +1 -0
- package/dist/mipmap-generator.d.ts +39 -0
- package/dist/mipmap-generator.d.ts.map +1 -0
- package/dist/param-schema-projection-census.d.ts +18 -0
- package/dist/param-schema-projection-census.d.ts.map +1 -0
- package/dist/pbr-pipeline.d.ts +253 -0
- package/dist/pbr-pipeline.d.ts.map +1 -0
- package/dist/persistent-render-scene.d.ts +158 -0
- package/dist/persistent-render-scene.d.ts.map +1 -0
- package/dist/pipeline/index.d.ts +11 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline-builder.d.ts +122 -0
- package/dist/pipeline-builder.d.ts.map +1 -0
- package/dist/pipeline-errors.d.ts +110 -0
- package/dist/pipeline-errors.d.ts.map +1 -0
- package/dist/pipeline-spec.d.ts +506 -0
- package/dist/pipeline-spec.d.ts.map +1 -0
- package/dist/plugin.d.ts +17 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/post-process-errors.d.ts +182 -0
- package/dist/post-process-errors.d.ts.map +1 -0
- package/dist/prepare/__tests__/schema-readers.unit.test.d.ts +2 -0
- package/dist/prepare/__tests__/schema-readers.unit.test.d.ts.map +1 -0
- package/dist/prepare/index.d.ts +4 -0
- package/dist/prepare/index.d.ts.map +1 -0
- package/dist/prepare/prepared-graphics-resolver.d.ts +62 -0
- package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -0
- package/dist/prepare/schema-readers.d.ts +11 -0
- package/dist/prepare/schema-readers.d.ts.map +1 -0
- package/dist/record/__tests__/environment-record.test.d.ts +2 -0
- package/dist/record/__tests__/environment-record.test.d.ts.map +1 -0
- package/dist/record/__tests__/frame-targets.unit.test.d.ts +2 -0
- package/dist/record/__tests__/frame-targets.unit.test.d.ts.map +1 -0
- package/dist/record/__tests__/material-bg-cache.unit.test.d.ts +2 -0
- package/dist/record/__tests__/material-bg-cache.unit.test.d.ts.map +1 -0
- package/dist/record/__tests__/material-slot-plan.unit.test.d.ts +2 -0
- package/dist/record/__tests__/material-slot-plan.unit.test.d.ts.map +1 -0
- package/dist/record/__tests__/membership-timing.unit.test.d.ts +2 -0
- package/dist/record/__tests__/membership-timing.unit.test.d.ts.map +1 -0
- package/dist/record/__tests__/render-submission.integration.test.d.ts +2 -0
- package/dist/record/__tests__/render-submission.integration.test.d.ts.map +1 -0
- package/dist/record/__tests__/render-submission.perf.test.d.ts +2 -0
- package/dist/record/__tests__/render-submission.perf.test.d.ts.map +1 -0
- package/dist/record/__tests__/shadow-submit-mode.unit.test.d.ts +2 -0
- package/dist/record/__tests__/shadow-submit-mode.unit.test.d.ts.map +1 -0
- package/dist/record/__tests__/video-texture-view.unit.test.d.ts +2 -0
- package/dist/record/__tests__/video-texture-view.unit.test.d.ts.map +1 -0
- package/dist/record/__tests__/view-ubo-layout.unit.test.d.ts +2 -0
- package/dist/record/__tests__/view-ubo-layout.unit.test.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +187 -0
- package/dist/record/frame-lighting.d.ts.map +1 -0
- package/dist/record/frame-observation.d.ts +38 -0
- package/dist/record/frame-observation.d.ts.map +1 -0
- package/dist/record/frame-snapshot.d.ts +429 -0
- package/dist/record/frame-snapshot.d.ts.map +1 -0
- package/dist/record/frame-targets.d.ts +53 -0
- package/dist/record/frame-targets.d.ts.map +1 -0
- package/dist/record/frame.d.ts +59 -0
- package/dist/record/frame.d.ts.map +1 -0
- package/dist/record/helpers.d.ts +101 -0
- package/dist/record/helpers.d.ts.map +1 -0
- package/dist/record/index.d.ts +15 -0
- package/dist/record/index.d.ts.map +1 -0
- package/dist/record/main-pass-geometry.d.ts +20 -0
- package/dist/record/main-pass-geometry.d.ts.map +1 -0
- package/dist/record/main-pass-material.d.ts +173 -0
- package/dist/record/main-pass-material.d.ts.map +1 -0
- package/dist/record/main-pass-skin.d.ts +46 -0
- package/dist/record/main-pass-skin.d.ts.map +1 -0
- package/dist/record/main-pass-sprite-draws.d.ts +18 -0
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -0
- package/dist/record/main-pass-sprite.d.ts +69 -0
- package/dist/record/main-pass-sprite.d.ts.map +1 -0
- package/dist/record/main-pass.d.ts +20 -0
- package/dist/record/main-pass.d.ts.map +1 -0
- package/dist/record/material-payload.d.ts +43 -0
- package/dist/record/material-payload.d.ts.map +1 -0
- package/dist/record/material-slot-plan.d.ts +26 -0
- package/dist/record/material-slot-plan.d.ts.map +1 -0
- package/dist/record/membership-timing.d.ts +232 -0
- package/dist/record/membership-timing.d.ts.map +1 -0
- package/dist/record/mesh-ssbo.d.ts +173 -0
- package/dist/record/mesh-ssbo.d.ts.map +1 -0
- package/dist/record/shadow-caster-uniform.d.ts +8 -0
- package/dist/record/shadow-caster-uniform.d.ts.map +1 -0
- package/dist/record/shadow-pass.d.ts +125 -0
- package/dist/record/shadow-pass.d.ts.map +1 -0
- package/dist/record/shadow-submit-mode.d.ts +66 -0
- package/dist/record/shadow-submit-mode.d.ts.map +1 -0
- package/dist/record/skybox-post-pass.d.ts +33 -0
- package/dist/record/skybox-post-pass.d.ts.map +1 -0
- package/dist/record/typed-frame-graph.d.ts +13 -0
- package/dist/record/typed-frame-graph.d.ts.map +1 -0
- package/dist/record/view-ubo.d.ts +39 -0
- package/dist/record/view-ubo.d.ts.map +1 -0
- package/dist/render-data.d.ts +143 -0
- package/dist/render-data.d.ts.map +1 -0
- package/dist/render-dispatch-membership.d.ts +47 -0
- package/dist/render-dispatch-membership.d.ts.map +1 -0
- package/dist/render-graph-primitives.d.ts +424 -0
- package/dist/render-graph-primitives.d.ts.map +1 -0
- package/dist/render-pipeline-context.d.ts +289 -0
- package/dist/render-pipeline-context.d.ts.map +1 -0
- package/dist/render-pipeline-target.d.ts +16 -0
- package/dist/render-pipeline-target.d.ts.map +1 -0
- package/dist/render-pipeline.d.ts +149 -0
- package/dist/render-pipeline.d.ts.map +1 -0
- package/dist/render-scene-projection.d.ts +174 -0
- package/dist/render-scene-projection.d.ts.map +1 -0
- package/dist/render-system-extract.d.ts +1096 -0
- package/dist/render-system-extract.d.ts.map +1 -0
- package/dist/render-system-fold.d.ts +266 -0
- package/dist/render-system-fold.d.ts.map +1 -0
- package/dist/render-system.d.ts +814 -0
- package/dist/render-system.d.ts.map +1 -0
- package/dist/renderer/asset-service.d.ts +10 -0
- package/dist/renderer/asset-service.d.ts.map +1 -0
- package/dist/renderer/backend-selection.d.ts +26 -0
- package/dist/renderer/backend-selection.d.ts.map +1 -0
- package/dist/renderer/environment-classify.d.ts +6 -0
- package/dist/renderer/environment-classify.d.ts.map +1 -0
- package/dist/renderer/environment-error.d.ts +13 -0
- package/dist/renderer/environment-error.d.ts.map +1 -0
- package/dist/renderer/material/assembly.d.ts +22 -0
- package/dist/renderer/material/assembly.d.ts.map +1 -0
- package/dist/renderer/material/pipeline-projection.d.ts +91 -0
- package/dist/renderer/material/pipeline-projection.d.ts.map +1 -0
- package/dist/renderer/renderer-factory.d.ts +283 -0
- package/dist/renderer/renderer-factory.d.ts.map +1 -0
- package/dist/renderer.d.ts +771 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/scene-instances/post-spawn-resolve-joints.d.ts +33 -0
- package/dist/scene-instances/post-spawn-resolve-joints.d.ts.map +1 -0
- package/dist/scene-utils/collect-subtree.d.ts +11 -0
- package/dist/scene-utils/collect-subtree.d.ts.map +1 -0
- package/dist/shadow-atlas.d.ts +117 -0
- package/dist/shadow-atlas.d.ts.map +1 -0
- package/dist/ssao-buffers.d.ts +31 -0
- package/dist/ssao-buffers.d.ts.map +1 -0
- package/dist/ssao-config.d.ts +28 -0
- package/dist/ssao-config.d.ts.map +1 -0
- package/dist/ssao-data.d.ts +34 -0
- package/dist/ssao-data.d.ts.map +1 -0
- package/dist/surface-format.d.ts +10 -0
- package/dist/surface-format.d.ts.map +1 -0
- package/dist/systems/active-camera.d.ts +53 -0
- package/dist/systems/active-camera.d.ts.map +1 -0
- package/dist/systems/pass-selector.d.ts +24 -0
- package/dist/systems/pass-selector.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +51 -0
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -0
- package/dist/systems/transparent-sort-config.d.ts +91 -0
- package/dist/systems/transparent-sort-config.d.ts.map +1 -0
- package/dist/taa-history-store.d.ts +88 -0
- package/dist/taa-history-store.d.ts.map +1 -0
- package/dist/temporal-coverage.d.ts +35 -0
- package/dist/temporal-coverage.d.ts.map +1 -0
- package/dist/temporal-frame-transaction.d.ts +92 -0
- package/dist/temporal-frame-transaction.d.ts.map +1 -0
- package/dist/temporal-inspection.d.ts +57 -0
- package/dist/temporal-inspection.d.ts.map +1 -0
- package/dist/temporal-view.d.ts +43 -0
- package/dist/temporal-view.d.ts.map +1 -0
- package/dist/tilemap/resolve-tileset-runtime.d.ts +23 -0
- package/dist/tilemap/resolve-tileset-runtime.d.ts.map +1 -0
- package/dist/tilemap-chunk-extract-system.d.ts +142 -0
- package/dist/tilemap-chunk-extract-system.d.ts.map +1 -0
- package/dist/typed-render-graph-primitives.d.ts +59 -0
- package/dist/typed-render-graph-primitives.d.ts.map +1 -0
- package/dist/typed-shadow-passes.d.ts +11 -0
- package/dist/typed-shadow-passes.d.ts.map +1 -0
- package/dist/urp-pipeline.d.ts +4 -0
- package/dist/urp-pipeline.d.ts.map +1 -0
- package/package.json +94 -0
- package/src/__tests__/analytic-sky-contract.test.ts +27 -0
- package/src/__tests__/analytic-sky-falsify-carriers.mjs +43 -0
- package/src/__tests__/analytic-sky-falsify.test.ts +94 -0
- package/src/__tests__/assets-runtime-handoff.integration.test.ts +36 -0
- package/src/__tests__/authoring-path.unit.test.ts +14 -0
- package/src/__tests__/backend-selection-rhi-capture.test-d.ts +21 -0
- package/src/__tests__/backend-selection.integration.test.ts +11 -0
- package/src/__tests__/callback-inference.test-d.ts +5 -0
- package/src/__tests__/camera-temporal.test-d.ts +8 -0
- package/src/__tests__/camera-temporal.test.ts +188 -0
- package/src/__tests__/cluster-binner.test.ts +272 -0
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +17 -0
- package/src/__tests__/construct-renderer-identity.unit.test.ts +10 -0
- package/src/__tests__/direct-extract-consumer-contract.test-d.ts +34 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +71 -0
- package/src/__tests__/directional-shadow-probe-atlas-layout.unit.test.ts +18 -0
- package/src/__tests__/environment-inspection.test.ts +45 -0
- package/src/__tests__/factory-contract.integration.test.ts +331 -0
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +18 -0
- package/src/__tests__/fog-ai-user.fixture.ts +350 -0
- package/src/__tests__/fog-custom-render-feature.fixture.ts +33 -0
- package/src/__tests__/fog-custom-render-feature.integration.test.ts +76 -0
- package/src/__tests__/fog-inspection.integration.test.ts +127 -0
- package/src/__tests__/fog-owner-state.integration.test.ts +112 -0
- package/src/__tests__/fog-pipeline-topology.integration.test.ts +93 -0
- package/src/__tests__/fog-public-surface.integration.test.ts +60 -0
- package/src/__tests__/fog-ray-contract.integration.test.ts +73 -0
- package/src/__tests__/fog-source-absence.integration.test.ts +51 -0
- package/src/__tests__/fog-temporal.integration.test.ts +115 -0
- package/src/__tests__/frame-chain.unit.test.ts +26 -0
- package/src/__tests__/frame-observation.contract.test.ts +103 -0
- package/src/__tests__/glyph-text-layout-recovery.unit.test.ts +172 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +347 -0
- package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +12 -0
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +16 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +609 -0
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +90 -0
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +192 -0
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +128 -0
- package/src/__tests__/gpu-driven-view.browser.test.ts +19 -0
- package/src/__tests__/gpu-driven-view.dawn.test.ts +17 -0
- package/src/__tests__/gpu-driven-view.unit.test.ts +455 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +59 -0
- package/src/__tests__/gpu-scene-field-type-owner.test-d.ts +27 -0
- package/src/__tests__/gpu-scene-render-graph-gpu.ts +309 -0
- package/src/__tests__/gpu-scene-render-graph.browser.test.ts +19 -0
- package/src/__tests__/gpu-scene-render-graph.dawn.test.ts +19 -0
- package/src/__tests__/gpu-scene-schema.unit.test.ts +16 -0
- package/src/__tests__/gpu-scene.dawn.test.ts +209 -0
- package/src/__tests__/gpu-scene.unit.test.ts +161 -0
- package/src/__tests__/hdrp-pipeline.unit.test.ts +100 -0
- package/src/__tests__/ibl/ibl-fallback.unit.test.ts +23 -0
- package/src/__tests__/infinite-grid-no-vertex-probe.unit.test.ts +124 -0
- package/src/__tests__/injection-kind-owner.test-d.ts +14 -0
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +719 -0
- package/src/__tests__/light-buffer-layout.unit.test.ts +85 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +24 -0
- package/src/__tests__/material-cooked-projection.unit.test.ts +177 -0
- package/src/__tests__/material-derived-error-stop-draw.integration.test.ts +69 -0
- package/src/__tests__/material-derived-payload.integration.test.ts +164 -0
- package/src/__tests__/material-snapshot-mutation.integration.test.ts +58 -0
- package/src/__tests__/material-static-dynamic-route.unit.test.ts +113 -0
- package/src/__tests__/material-stride-surface.unit.test.ts +32 -0
- package/src/__tests__/materials.unit.test.ts +25 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +107 -0
- package/src/__tests__/mesh-material-bindings.unit.test.ts +89 -0
- package/src/__tests__/mesh-stride-surface.unit.test.ts +16 -0
- package/src/__tests__/morph-culling-reentry.dawn.test.ts +166 -0
- package/src/__tests__/morph-feature-gpu-work.integration.test.ts +78 -0
- package/src/__tests__/morph-visual-evidence.integration.test.ts +66 -0
- package/src/__tests__/nineslice-handle-surface.unit.test.ts +14 -0
- package/src/__tests__/param-schema-projection-census.unit.test.ts +40 -0
- package/src/__tests__/persistent-render-scene.unit.test.ts +1311 -0
- package/src/__tests__/pipeline-error-policy-owner.unit.test.ts +167 -0
- package/src/__tests__/pipeline-pass-trace.test.ts +92 -0
- package/src/__tests__/pipeline-stage-owner.test-d.ts +13 -0
- package/src/__tests__/plugin.test.ts +25 -0
- package/src/__tests__/post-process-error-policy-owner.unit.test.ts +208 -0
- package/src/__tests__/prepared-buffer-kind-owner.test-d.ts +29 -0
- package/src/__tests__/prepared-graphics-backend-matrix.unit.test.ts +129 -0
- package/src/__tests__/prepared-graphics-capability.unit.test.ts +140 -0
- package/src/__tests__/prepared-graphics-generation.unit.test.ts +175 -0
- package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +206 -0
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +29 -0
- package/src/__tests__/prepared-graphics-resolver-recording.unit.test.ts +180 -0
- package/src/__tests__/prepared-graphics-resolver.unit.test.ts +287 -0
- package/src/__tests__/prepared-graphics-snapshot.unit.test.ts +105 -0
- package/src/__tests__/prepared-graphics-store.unit.test.ts +154 -0
- package/src/__tests__/prepared-graphics-validation.unit.test.ts +176 -0
- package/src/__tests__/profiler-default-off.test.ts +22 -0
- package/src/__tests__/profiler-phase-catalog.test.ts +66 -0
- package/src/__tests__/profiler-phase-integration.test.ts +70 -0
- package/src/__tests__/profiler-phase-token.test.ts +84 -0
- package/src/__tests__/public-pipeline-id.test-d.ts +6 -0
- package/src/__tests__/public-surface.test-d.ts +32 -0
- package/src/__tests__/recover-error-policy-owner.unit.test.ts +127 -0
- package/src/__tests__/render-dispatch-membership.unit.test.ts +117 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +115 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +234 -0
- package/src/__tests__/render-error-exhaustive.test-d.ts +84 -0
- package/src/__tests__/render-feature-assembly.test-d.ts +51 -0
- package/src/__tests__/render-feature-construction.unit.test.ts +124 -0
- package/src/__tests__/render-feature-context.test-d.ts +81 -0
- package/src/__tests__/render-feature-contract.test-d.ts +58 -0
- package/src/__tests__/render-feature-diagnostics.unit.test.ts +151 -0
- package/src/__tests__/render-feature-docs.unit.test.ts +88 -0
- package/src/__tests__/render-feature-error-code-owner.test-d.ts +27 -0
- package/src/__tests__/render-feature-errors.unit.test.ts +47 -0
- package/src/__tests__/render-feature-gpu-buffer-usage-owner.test-d.ts +46 -0
- package/src/__tests__/render-feature-gpu-work.integration.test.ts +377 -0
- package/src/__tests__/render-feature-graph.integration.test.ts +205 -0
- package/src/__tests__/render-feature-graphics-staging.unit.test.ts +200 -0
- package/src/__tests__/render-feature-order.unit.test.ts +66 -0
- package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +101 -0
- package/src/__tests__/render-feature-prepared-errors.test-d.ts +82 -0
- package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +141 -0
- package/src/__tests__/render-feature-prepared-graphics-isolation.unit.test.ts +122 -0
- package/src/__tests__/render-feature-prepared-graphics-negative.test-d.ts +59 -0
- package/src/__tests__/render-feature-prepared-graphics-recording.unit.test.ts +107 -0
- package/src/__tests__/render-feature-prepared-graphics.test-d.ts +96 -0
- package/src/__tests__/render-feature-prepared-graphics.unit.test.ts +121 -0
- package/src/__tests__/render-feature-public-surface.test-d.ts +75 -0
- package/src/__tests__/render-feature-retry.unit.test.ts +128 -0
- package/src/__tests__/render-feature-scheduling.unit.test.ts +122 -0
- package/src/__tests__/render-feature-stage-failed-policy-owner.unit.test.ts +146 -0
- package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +236 -0
- package/src/__tests__/render-feature-staging.unit.test.ts +94 -0
- package/src/__tests__/render-feature-visibility-context.test-d.ts +38 -0
- package/src/__tests__/render-feature-visibility-context.unit.test.ts +38 -0
- package/src/__tests__/render-feature-zero-work.unit.test.ts +82 -0
- package/src/__tests__/render-graph-compute-raster-gpu.ts +342 -0
- package/src/__tests__/render-graph-compute-raster.browser.test.ts +16 -0
- package/src/__tests__/render-graph-compute-raster.dawn.test.ts +22 -0
- package/src/__tests__/render-graph-hzb-gpu.ts +239 -0
- package/src/__tests__/render-graph-hzb.browser.test.ts +16 -0
- package/src/__tests__/render-graph-hzb.dawn.test.ts +23 -0
- package/src/__tests__/render-graph-primitives.unit.test.ts +22 -0
- package/src/__tests__/render-graph-storage-indirect-gpu.ts +162 -0
- package/src/__tests__/render-graph-storage-indirect.browser.test.ts +21 -0
- package/src/__tests__/render-graph-storage-indirect.dawn.test.ts +28 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +206 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +125 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +10 -0
- package/src/__tests__/renderer-render-scene-public.test-d.ts +27 -0
- package/src/__tests__/rhi-backend-package-boundary.unit.test.ts +11 -0
- package/src/__tests__/scene-temporal-coverage.unit.test.ts +259 -0
- package/src/__tests__/scriptable-pack-consumer.test-d.ts +14 -0
- package/src/__tests__/shader-stage-surface.unit.test.ts +38 -0
- package/src/__tests__/ssao-kernel-surface.unit.test.ts +15 -0
- package/src/__tests__/sun-snapshot-consumers.test.ts +35 -0
- package/src/__tests__/surface-backend-kind-owner.test-d.ts +42 -0
- package/src/__tests__/surface-configuration.unit.test.ts +92 -0
- package/src/__tests__/taa-history-store.unit.test.ts +196 -0
- package/src/__tests__/temporal-frame-transaction.integration.test.ts +537 -0
- package/src/__tests__/temporal-inspection.unit.test.ts +138 -0
- package/src/__tests__/texture-usage-surface.unit.test.ts +61 -0
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +389 -0
- package/src/__tests__/urp-pipeline-targets.unit.test.ts +52 -0
- package/src/__tests__/urp-pipeline.unit.test.ts +14 -0
- package/src/__tests__/vertex-color-pipeline.unit.test.ts +124 -0
- package/src/__tests__/vertex-layout-consumer.unit.test.ts +25 -0
- package/src/__tests__/visibility-diagnostics.integration.test.ts +66 -0
- package/src/__tests__/visibility-extract.integration.test.ts +89 -0
- package/src/__tests__/visibility-extract.perf.test.ts +96 -0
- package/src/__tests__/visibility-feature-dedup.integration.test.ts +57 -0
- package/src/__tests__/visibility-hidden-report.unit.test.ts +37 -0
- package/src/__tests__/visibility-instances.integration.test.ts +50 -0
- package/src/__tests__/visibility-multi-world.integration.test.ts +54 -0
- package/src/__tests__/visibility-producer-matrix.integration.test.ts +191 -0
- package/src/__tests__/visibility-producer-matrix.unit.test.ts +96 -0
- package/src/__tests__/visibility-public-surface.test-d.ts +26 -0
- package/src/__tests__/visibility-resolver.unit.test.ts +84 -0
- package/src/__tests__/visibility-snapshot-contract.unit.test.ts +59 -0
- package/src/__tests__/vocabulary.unit.test.ts +21 -0
- package/src/assets/__tests__/asset-decoders.unit.test.ts +92 -0
- package/src/assets/__tests__/mipmap-generator.unit.test.ts +52 -0
- package/src/assets/__tests__/render-asset-port.unit.test.ts +54 -0
- package/src/assets/__tests__/resolve-render-asset.unit.test.ts +47 -0
- package/src/assets/asset-decoders.ts +158 -0
- package/src/assets/render-asset-port.ts +44 -0
- package/src/assets/resolve-render-asset.ts +13 -0
- package/src/authoring.ts +24 -0
- package/src/cluster-binner.ts +700 -0
- package/src/components/__tests__/atmosphere-red.test.ts +51 -0
- package/src/components/__tests__/atmosphere.test.ts +79 -0
- package/src/components/__tests__/fog.test-d.ts +49 -0
- package/src/components/__tests__/fog.test.ts +60 -0
- package/src/components/__tests__/schema-parity.unit.test.ts +25 -0
- package/src/components/__tests__/visibility-schema.unit.test.ts +35 -0
- package/src/components/atmosphere.ts +64 -0
- package/src/components/camera.ts +512 -0
- package/src/components/directional-light.ts +120 -0
- package/src/components/environment.ts +206 -0
- package/src/components/glyph-text.ts +63 -0
- package/src/components/index.ts +49 -0
- package/src/components/instances.ts +207 -0
- package/src/components/layer.ts +69 -0
- package/src/components/light-helpers.ts +188 -0
- package/src/components/mesh-filter.ts +59 -0
- package/src/components/mesh-renderer.ts +61 -0
- package/src/components/point-light-shadow.ts +43 -0
- package/src/components/point-light.ts +65 -0
- package/src/components/post-process-params.ts +35 -0
- package/src/components/scene-instance.ts +193 -0
- package/src/components/sort-key.ts +75 -0
- package/src/components/spot-light.ts +124 -0
- package/src/components/sprite-animation.ts +169 -0
- package/src/components/sprite-instances.ts +179 -0
- package/src/components/sprite-playback-mode.ts +73 -0
- package/src/components/sprite-region-override.ts +106 -0
- package/src/components/tile-layer.ts +161 -0
- package/src/components/tilemap.ts +48 -0
- package/src/components/visibility.ts +53 -0
- package/src/construct-renderer.ts +4 -0
- package/src/debug-draw-glue.ts +169 -0
- package/src/deferred-lighting.ts +92 -0
- package/src/dynamic-texture-store.ts +111 -0
- package/src/engine-metrics.ts +89 -0
- package/src/environment/README.md +76 -0
- package/src/environment/__tests__/failure-injection.test.ts +50 -0
- package/src/environment/__tests__/generation-red.test.ts +64 -0
- package/src/environment/__tests__/generation.test.ts +37 -0
- package/src/environment/__tests__/graph-transaction.test.ts +85 -0
- package/src/environment/__tests__/inspection-red.test.ts +53 -0
- package/src/environment/__tests__/lifecycle-evidence.test.ts +40 -0
- package/src/environment/__tests__/pipeline-topology.test.ts +33 -0
- package/src/environment/__tests__/readiness.test.ts +42 -0
- package/src/environment/__tests__/recovery.test.ts +62 -0
- package/src/environment/__tests__/resize-dispose.test.ts +30 -0
- package/src/environment/__tests__/signature-red.test.ts +45 -0
- package/src/environment/__tests__/signature.test.ts +60 -0
- package/src/environment/descriptors.ts +7 -0
- package/src/environment/extracted-environment.ts +239 -0
- package/src/environment/generation.ts +158 -0
- package/src/environment/gpu-bundle.ts +144 -0
- package/src/environment/graph.ts +588 -0
- package/src/environment/inspection.ts +107 -0
- package/src/environment/lifecycle.ts +124 -0
- package/src/environment/readiness.ts +76 -0
- package/src/environment/signature.ts +185 -0
- package/src/errors/ecs-validation.ts +16 -0
- package/src/errors/index.ts +3 -0
- package/src/errors/recover.ts +119 -0
- package/src/errors/render.ts +1244 -0
- package/src/extract/__tests__/directional-csm-domain.unit.test.ts +391 -0
- package/src/extract/__tests__/environment-source.test.ts +105 -0
- package/src/extract/__tests__/schema-readers.unit.test.ts +16 -0
- package/src/extract/directional-csm.ts +959 -0
- package/src/extract/index.ts +10 -0
- package/src/extract/material-interface.ts +604 -0
- package/src/extract/schema-readers.ts +27 -0
- package/src/extract/visibility.ts +120 -0
- package/src/features/graph-contribution.ts +577 -0
- package/src/features/host.ts +1097 -0
- package/src/features/index.ts +100 -0
- package/src/features/morph/index.ts +24 -0
- package/src/features/morph/morph-bounds.ts +123 -0
- package/src/features/morph/morph-feature.ts +287 -0
- package/src/features/prepared-gpu-work.ts +902 -0
- package/src/features/prepared-graphics-store.ts +435 -0
- package/src/features/prepared-graphics.ts +479 -0
- package/src/features/render-graph-compute.ts +60 -0
- package/src/features/render-graph-contribution.ts +95 -0
- package/src/features/render-graph-raster.ts +308 -0
- package/src/features/render-graph-resources.ts +36 -0
- package/src/features/scene-temporal.ts +274 -0
- package/src/features/taa-resolve.ts +244 -0
- package/src/features/targets.ts +83 -0
- package/src/features/types.ts +291 -0
- package/src/frame-chain.ts +21 -0
- package/src/fullscreen-post-process-pass.ts +406 -0
- package/src/glyph-text-layout-system.ts +447 -0
- package/src/gpu-driven/batch-topology.ts +423 -0
- package/src/gpu-driven/production-raster.ts +1705 -0
- package/src/gpu-driven/view-gpu.ts +849 -0
- package/src/gpu-driven/view-reference.ts +110 -0
- package/src/gpu-resource-store.ts +1921 -0
- package/src/gpu-resource.ts +159 -0
- package/src/gpu-scene-schema.ts +224 -0
- package/src/gpu-scene.ts +855 -0
- package/src/gpu-stage.ts +9 -0
- package/src/gpu-texture-usage.ts +12 -0
- package/src/gpu-usage.ts +14 -0
- package/src/hdrp-buffers.ts +689 -0
- package/src/hdrp-cluster-membership.ts +70 -0
- package/src/hdrp-pipeline.ts +525 -0
- package/src/ibl/IblPipelineCache.ts +1246 -0
- package/src/ibl/cubemap-projection.ts +60 -0
- package/src/ibl/face-uniforms.ts +150 -0
- package/src/ibl/skylight-bind-group.ts +513 -0
- package/src/index.ts +271 -0
- package/src/instance-buffer-cache.ts +123 -0
- package/src/internal.ts +88 -0
- package/src/lifecycle.ts +123 -0
- package/src/light-buffer-layout.ts +346 -0
- package/src/materials.ts +236 -0
- package/src/mesh-material-bindings.ts +104 -0
- package/src/mesh-update-errors.ts +113 -0
- package/src/mipmap-generator.ts +244 -0
- package/src/param-schema-projection-census.ts +73 -0
- package/src/pbr-pipeline.ts +749 -0
- package/src/persistent-render-scene.ts +1752 -0
- package/src/pipeline/index.ts +40 -0
- package/src/pipeline-builder.ts +386 -0
- package/src/pipeline-errors.ts +172 -0
- package/src/pipeline-spec.ts +1768 -0
- package/src/plugin.ts +137 -0
- package/src/post-process-errors.ts +304 -0
- package/src/prepare/__tests__/schema-readers.unit.test.ts +16 -0
- package/src/prepare/index.ts +8 -0
- package/src/prepare/prepared-graphics-resolver.ts +455 -0
- package/src/prepare/schema-readers.ts +20 -0
- package/src/record/__tests__/environment-record.test.ts +35 -0
- package/src/record/__tests__/frame-targets.unit.test.ts +54 -0
- package/src/record/__tests__/material-bg-cache.unit.test.ts +41 -0
- package/src/record/__tests__/material-slot-plan.unit.test.ts +76 -0
- package/src/record/__tests__/membership-timing.unit.test.ts +524 -0
- package/src/record/__tests__/render-submission.integration.test.ts +76 -0
- package/src/record/__tests__/render-submission.perf.test.ts +141 -0
- package/src/record/__tests__/shadow-submit-mode.unit.test.ts +88 -0
- package/src/record/__tests__/video-texture-view.unit.test.ts +90 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +101 -0
- package/src/record/frame-lighting.ts +1183 -0
- package/src/record/frame-observation.ts +122 -0
- package/src/record/frame-snapshot.ts +515 -0
- package/src/record/frame-targets.ts +372 -0
- package/src/record/frame.ts +1874 -0
- package/src/record/helpers.ts +289 -0
- package/src/record/index.ts +15 -0
- package/src/record/main-pass-geometry.ts +1014 -0
- package/src/record/main-pass-material.ts +716 -0
- package/src/record/main-pass-skin.ts +51 -0
- package/src/record/main-pass-sprite-draws.ts +1056 -0
- package/src/record/main-pass-sprite.ts +166 -0
- package/src/record/main-pass.ts +588 -0
- package/src/record/material-payload.ts +292 -0
- package/src/record/material-slot-plan.ts +68 -0
- package/src/record/membership-timing.ts +1012 -0
- package/src/record/mesh-ssbo.ts +406 -0
- package/src/record/shadow-caster-uniform.ts +37 -0
- package/src/record/shadow-pass.ts +2387 -0
- package/src/record/shadow-submit-mode.ts +277 -0
- package/src/record/skybox-post-pass.ts +594 -0
- package/src/record/typed-frame-graph.ts +499 -0
- package/src/record/view-ubo.ts +206 -0
- package/src/render-data.ts +385 -0
- package/src/render-dispatch-membership.ts +155 -0
- package/src/render-graph-primitives.ts +1586 -0
- package/src/render-pipeline-context.ts +332 -0
- package/src/render-pipeline-target.ts +96 -0
- package/src/render-pipeline.ts +343 -0
- package/src/render-scene-projection.ts +1738 -0
- package/src/render-system-extract.ts +5203 -0
- package/src/render-system-fold.ts +531 -0
- package/src/render-system.ts +3258 -0
- package/src/renderer/asset-service.ts +32 -0
- package/src/renderer/backend-selection.ts +121 -0
- package/src/renderer/environment-classify.ts +30 -0
- package/src/renderer/environment-error.ts +19 -0
- package/src/renderer/material/assembly.ts +53 -0
- package/src/renderer/material/pipeline-projection.ts +140 -0
- package/src/renderer/renderer-factory.ts +9062 -0
- package/src/renderer.ts +940 -0
- package/src/scene-instances/post-spawn-resolve-joints.ts +168 -0
- package/src/scene-utils/collect-subtree.ts +40 -0
- package/src/shadow-atlas.ts +244 -0
- package/src/ssao-buffers.ts +175 -0
- package/src/ssao-config.ts +61 -0
- package/src/ssao-data.ts +106 -0
- package/src/surface-format.ts +37 -0
- package/src/systems/active-camera.ts +116 -0
- package/src/systems/pass-selector.ts +49 -0
- package/src/systems/skin-palette-allocator.ts +294 -0
- package/src/systems/transparent-sort-config.ts +211 -0
- package/src/taa-history-store.ts +429 -0
- package/src/temporal-coverage.ts +106 -0
- package/src/temporal-frame-transaction.ts +301 -0
- package/src/temporal-inspection.ts +194 -0
- package/src/temporal-view.ts +174 -0
- package/src/tilemap/resolve-tileset-runtime.ts +72 -0
- package/src/tilemap-chunk-extract-system.ts +1307 -0
- package/src/typed-render-graph-primitives.ts +530 -0
- package/src/typed-shadow-passes.ts +195 -0
- package/src/urp-pipeline.ts +340 -0
|
@@ -0,0 +1,2387 @@
|
|
|
1
|
+
import type { GraphResourceResolver } from '@forgeax/engine-render-graph';
|
|
2
|
+
import {
|
|
3
|
+
type BindGroup,
|
|
4
|
+
type Buffer,
|
|
5
|
+
type RenderPipeline,
|
|
6
|
+
RhiError,
|
|
7
|
+
type RhiRenderPassEncoder,
|
|
8
|
+
type TextureView,
|
|
9
|
+
} from '@forgeax/engine-rhi';
|
|
10
|
+
import type { PassSelector } from '@forgeax/engine-types';
|
|
11
|
+
import {
|
|
12
|
+
PointShadowAtlasBoundsViolationError,
|
|
13
|
+
PointShadowAtlasUninitializedError,
|
|
14
|
+
} from '../errors/render';
|
|
15
|
+
import type { DirectionalCsmCascadePlan } from '../extract/directional-csm';
|
|
16
|
+
import type { MultiViewProductionSubmission } from '../gpu-driven/production-raster';
|
|
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';
|
|
23
|
+
import { assembleMaterialWithSkylightEntries } from '../ibl/skylight-bind-group';
|
|
24
|
+
import {
|
|
25
|
+
type InstanceBufferCacheEntry,
|
|
26
|
+
packTemporalInstanceTransforms,
|
|
27
|
+
temporalInstanceUploadByteLength,
|
|
28
|
+
} from '../instance-buffer-cache';
|
|
29
|
+
import { buildBeginRenderPassDescriptor } from '../pipeline-spec';
|
|
30
|
+
import type { RenderPipelineGpuDrivenShadowProjection } from '../render-pipeline';
|
|
31
|
+
import type { _InternalRenderPipelineContext } from '../render-pipeline-context';
|
|
32
|
+
import { MATERIAL_PER_ENTITY_STRIDE } from '../render-system';
|
|
33
|
+
import type { DispatchEntry, SpotLightSnapshot } from '../render-system-extract';
|
|
34
|
+
import { matchPass } from '../systems/pass-selector';
|
|
35
|
+
import type { ValidatedRenderable } from './frame-snapshot';
|
|
36
|
+
import { worldEntityKey } from './frame-snapshot';
|
|
37
|
+
import {
|
|
38
|
+
getOrCreateFromChain,
|
|
39
|
+
getOrCreatePerEntity,
|
|
40
|
+
MAX_UNIFORM_INSTANCES,
|
|
41
|
+
MESH_PER_ENTITY_STRIDE,
|
|
42
|
+
} from './mesh-ssbo';
|
|
43
|
+
import { directionalShadowCasterOffset, spotShadowCasterOffset } from './shadow-caster-uniform';
|
|
44
|
+
import { pointShadowViewOffset, VIEW_UNIFORM_BYTES } from './view-ubo';
|
|
45
|
+
|
|
46
|
+
function ensureTypedShadowViewBg(
|
|
47
|
+
c: _InternalRenderPipelineContext,
|
|
48
|
+
viewOffset: number,
|
|
49
|
+
cascadeOffset: number,
|
|
50
|
+
variant: string,
|
|
51
|
+
): BindGroup | null {
|
|
52
|
+
const { runtime, frameState, pipelineState } = c;
|
|
53
|
+
try {
|
|
54
|
+
return getOrCreateFromChain(
|
|
55
|
+
frameState.viewBindGroupCache,
|
|
56
|
+
[
|
|
57
|
+
pipelineState.viewUniformBuffer,
|
|
58
|
+
pipelineState.pointLightsBuffer,
|
|
59
|
+
pipelineState.spotLightsBuffer,
|
|
60
|
+
pipelineState.shadowFallbackTextureView,
|
|
61
|
+
pipelineState.perPassResources.shadowSampler,
|
|
62
|
+
pipelineState.shadowAtlasFallbackTextureView,
|
|
63
|
+
pipelineState.shadowParamsBuffer,
|
|
64
|
+
pipelineState.shadowCasterCascadeBuffer,
|
|
65
|
+
pipelineState.shadowFallbackTextureView,
|
|
66
|
+
],
|
|
67
|
+
variant,
|
|
68
|
+
() => {
|
|
69
|
+
const created = runtime.device.createBindGroup({
|
|
70
|
+
label: variant,
|
|
71
|
+
layout: pipelineState.viewBindGroupLayout,
|
|
72
|
+
entries: [
|
|
73
|
+
{
|
|
74
|
+
binding: 0,
|
|
75
|
+
resource: {
|
|
76
|
+
kind: 'buffer',
|
|
77
|
+
value: {
|
|
78
|
+
buffer: pipelineState.viewUniformBuffer,
|
|
79
|
+
offset: viewOffset,
|
|
80
|
+
size: VIEW_UNIFORM_BYTES,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
binding: 1,
|
|
86
|
+
resource: { kind: 'buffer', value: { buffer: pipelineState.pointLightsBuffer } },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
binding: 2,
|
|
90
|
+
resource: { kind: 'buffer', value: { buffer: pipelineState.spotLightsBuffer } },
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
binding: 3,
|
|
94
|
+
resource: { kind: 'textureView', value: pipelineState.shadowFallbackTextureView },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
binding: 4,
|
|
98
|
+
resource: { kind: 'sampler', value: pipelineState.perPassResources.shadowSampler },
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
binding: 5,
|
|
102
|
+
resource: {
|
|
103
|
+
kind: 'textureView',
|
|
104
|
+
value: pipelineState.shadowAtlasFallbackTextureView,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
binding: 6,
|
|
109
|
+
resource: { kind: 'buffer', value: { buffer: pipelineState.shadowParamsBuffer } },
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
binding: 7,
|
|
113
|
+
resource: {
|
|
114
|
+
kind: 'buffer',
|
|
115
|
+
value: {
|
|
116
|
+
buffer: pipelineState.shadowCasterCascadeBuffer,
|
|
117
|
+
offset: cascadeOffset,
|
|
118
|
+
size: 80,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
binding: 8,
|
|
124
|
+
resource: { kind: 'textureView', value: pipelineState.shadowFallbackTextureView },
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
if (!created.ok) throw created.error;
|
|
129
|
+
return created.value;
|
|
130
|
+
},
|
|
131
|
+
c.bindGroupCounts,
|
|
132
|
+
);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error instanceof RhiError) {
|
|
135
|
+
runtime.errorRegistry.fire(error);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function shadowShaderMap(
|
|
143
|
+
c: _InternalRenderPipelineContext,
|
|
144
|
+
dispatch: readonly DispatchEntry[] = c.shadowDispatch,
|
|
145
|
+
): ReadonlyMap<number, string> {
|
|
146
|
+
const shaders = new Map<number, string>();
|
|
147
|
+
for (const entry of dispatch) {
|
|
148
|
+
if (entry.tags.LightMode === 'ShadowCaster' && entry.materialShaderId !== undefined) {
|
|
149
|
+
shaders.set(entry.renderableIndex, entry.materialShaderId);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return shaders;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function multiViewSubmission(
|
|
156
|
+
c: _InternalRenderPipelineContext,
|
|
157
|
+
kind: 'main' | 'cascade',
|
|
158
|
+
cascadeIndex?: number,
|
|
159
|
+
): MultiViewProductionSubmission | undefined {
|
|
160
|
+
return c.multiViewSubmissions.find(
|
|
161
|
+
(submission) =>
|
|
162
|
+
submission.kind === kind && (kind === 'main' || submission.cascadeIndex === cascadeIndex),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** One-way adapter from the batch-aligned production plan to direct RHI draws. */
|
|
167
|
+
function submissionCandidateIndices(
|
|
168
|
+
submission: MultiViewProductionSubmission | undefined,
|
|
169
|
+
): ReadonlySet<number> | undefined {
|
|
170
|
+
if (submission === undefined) return undefined;
|
|
171
|
+
// A truncated compact list is never a safe membership owner. Returning
|
|
172
|
+
// undefined deliberately selects the existing complete pass-selector
|
|
173
|
+
// domain, which is a conservative direct CPU fallback and cannot silently
|
|
174
|
+
// omit a caster. The overflow bit remains available to diagnostics.
|
|
175
|
+
if (submission.overflow) return undefined;
|
|
176
|
+
return submission.candidateIndices;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function directionalCascadeCacheHit(
|
|
180
|
+
c: _InternalRenderPipelineContext,
|
|
181
|
+
cascadeIndex: number,
|
|
182
|
+
): boolean {
|
|
183
|
+
return ((c.directionalShadowCacheHitMask ?? 0) & (1 << cascadeIndex)) !== 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function setOptionalScissorRect(
|
|
187
|
+
pass: RhiRenderPassEncoder,
|
|
188
|
+
x: number,
|
|
189
|
+
y: number,
|
|
190
|
+
width: number,
|
|
191
|
+
height: number,
|
|
192
|
+
): void {
|
|
193
|
+
// The production WebGPU encoder implements scissor clipping, while the
|
|
194
|
+
// lightweight test/fallback encoders intentionally expose only the common
|
|
195
|
+
// raster subset. Keep the atlas tile contract when the capability exists;
|
|
196
|
+
// an absent optional method must not turn a valid depth pass into a graph
|
|
197
|
+
// encode failure.
|
|
198
|
+
const setScissorRect = (
|
|
199
|
+
pass as unknown as {
|
|
200
|
+
readonly setScissorRect?: (x: number, y: number, width: number, height: number) => void;
|
|
201
|
+
}
|
|
202
|
+
).setScissorRect;
|
|
203
|
+
setScissorRect?.call(pass, x, y, width, height);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function clearDirectionalCascadeTile(
|
|
207
|
+
c: _InternalRenderPipelineContext,
|
|
208
|
+
pass: RhiRenderPassEncoder,
|
|
209
|
+
viewport: { readonly x: number; readonly y: number; readonly w: number; readonly h: number },
|
|
210
|
+
): boolean {
|
|
211
|
+
if ((c.directionalShadowCacheHitMask ?? 0) === 0) return true;
|
|
212
|
+
const pipeline = c.runtime.getDirectionalShadowTileClearPipeline?.() ?? null;
|
|
213
|
+
if (pipeline === null) return false;
|
|
214
|
+
pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
|
|
215
|
+
setOptionalScissorRect(pass, viewport.x, viewport.y, viewport.w, viewport.h);
|
|
216
|
+
pass.setPipeline(pipeline);
|
|
217
|
+
pass.draw(3);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Point/spot compatibility recording consumes the shared main-view result. */
|
|
222
|
+
function mainShadowMembership(c: _InternalRenderPipelineContext): ReadonlySet<number> | undefined {
|
|
223
|
+
return submissionCandidateIndices(multiViewSubmission(c, 'main'));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isMainShadowRenderable(
|
|
227
|
+
entry: ValidatedRenderable,
|
|
228
|
+
membership: ReadonlySet<number> | undefined,
|
|
229
|
+
): boolean {
|
|
230
|
+
if (membership === undefined) return true;
|
|
231
|
+
const candidateIndex = entry.source.candidateIndex ?? entry.renderableIndex;
|
|
232
|
+
return membership.has(candidateIndex);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
interface ShadowCascadeObservation {
|
|
236
|
+
readonly frame: number;
|
|
237
|
+
readonly cascade: number;
|
|
238
|
+
readonly viewport: {
|
|
239
|
+
readonly x: number;
|
|
240
|
+
readonly y: number;
|
|
241
|
+
readonly w: number;
|
|
242
|
+
readonly h: number;
|
|
243
|
+
};
|
|
244
|
+
readonly plan: DirectionalCsmCascadePlan;
|
|
245
|
+
readonly submission: MultiViewProductionSubmission | undefined;
|
|
246
|
+
readonly stableIdByCandidateIndex: ReadonlyMap<number, number>;
|
|
247
|
+
readonly cacheToken: string | undefined;
|
|
248
|
+
readonly cacheHit: boolean;
|
|
249
|
+
readonly budget: {
|
|
250
|
+
readonly limit: number;
|
|
251
|
+
emitted: number;
|
|
252
|
+
dropped: number;
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const SHADOW_CANDIDATE_OBSERVATION_BUDGET = 64;
|
|
257
|
+
|
|
258
|
+
interface ProjectedCasterRect {
|
|
259
|
+
readonly x: number;
|
|
260
|
+
readonly y: number;
|
|
261
|
+
readonly w: number;
|
|
262
|
+
readonly h: number;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function projectLightSpaceCasterRect(
|
|
266
|
+
plan: DirectionalCsmCascadePlan,
|
|
267
|
+
viewport: ShadowCascadeObservation['viewport'],
|
|
268
|
+
lightSpaceBounds: ArrayLike<number> | undefined,
|
|
269
|
+
): ProjectedCasterRect | undefined {
|
|
270
|
+
if (lightSpaceBounds === undefined || lightSpaceBounds.length < 4) return undefined;
|
|
271
|
+
const clipMinX = plan.clipLightBounds[0] ?? Number.NaN;
|
|
272
|
+
const clipMaxX = plan.clipLightBounds[1] ?? Number.NaN;
|
|
273
|
+
const clipMinY = plan.clipLightBounds[2] ?? Number.NaN;
|
|
274
|
+
const clipMaxY = plan.clipLightBounds[3] ?? Number.NaN;
|
|
275
|
+
const casterMinX = lightSpaceBounds[0] ?? Number.NaN;
|
|
276
|
+
const casterMaxX = lightSpaceBounds[1] ?? Number.NaN;
|
|
277
|
+
const casterMinY = lightSpaceBounds[2] ?? Number.NaN;
|
|
278
|
+
const casterMaxY = lightSpaceBounds[3] ?? Number.NaN;
|
|
279
|
+
if (
|
|
280
|
+
!Number.isFinite(clipMinX) ||
|
|
281
|
+
!Number.isFinite(clipMaxX) ||
|
|
282
|
+
!Number.isFinite(clipMinY) ||
|
|
283
|
+
!Number.isFinite(clipMaxY) ||
|
|
284
|
+
!Number.isFinite(casterMinX) ||
|
|
285
|
+
!Number.isFinite(casterMaxX) ||
|
|
286
|
+
!Number.isFinite(casterMinY) ||
|
|
287
|
+
!Number.isFinite(casterMaxY) ||
|
|
288
|
+
!(clipMaxX > clipMinX) ||
|
|
289
|
+
!(clipMaxY > clipMinY)
|
|
290
|
+
) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
const toPixelX = (value: number) =>
|
|
294
|
+
viewport.x + ((value - clipMinX) / (clipMaxX - clipMinX)) * viewport.w;
|
|
295
|
+
// Light-space Y is projected with the WebGPU clip-space Y flip used by the
|
|
296
|
+
// orthographic matrix; convert it to the top-left texture origin here.
|
|
297
|
+
const toPixelY = (value: number) =>
|
|
298
|
+
viewport.y + ((clipMaxY - value) / (clipMaxY - clipMinY)) * viewport.h;
|
|
299
|
+
const x0 = toPixelX(casterMinX);
|
|
300
|
+
const x1 = toPixelX(casterMaxX);
|
|
301
|
+
const y0 = toPixelY(casterMinY);
|
|
302
|
+
const y1 = toPixelY(casterMaxY);
|
|
303
|
+
return {
|
|
304
|
+
x: Math.min(x0, x1),
|
|
305
|
+
y: Math.min(y0, y1),
|
|
306
|
+
w: Math.abs(x1 - x0),
|
|
307
|
+
h: Math.abs(y1 - y0),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function shadowMarker(payload: Record<string, unknown>): string {
|
|
312
|
+
return `shadowCascade:${JSON.stringify(payload)}`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function spotShadowMarker(payload: Record<string, unknown>): string {
|
|
316
|
+
return `shadowSpot:${JSON.stringify(payload)}`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function emitSpotShadowPassBeginMarker(
|
|
320
|
+
c: _InternalRenderPipelineContext,
|
|
321
|
+
pass: RhiRenderPassEncoder,
|
|
322
|
+
snapshot: SpotLightSnapshot,
|
|
323
|
+
spotIndex: number,
|
|
324
|
+
spotCount: number,
|
|
325
|
+
): void {
|
|
326
|
+
if (c.shadowDebugObservation !== true) return;
|
|
327
|
+
const payload = {
|
|
328
|
+
kind: 'spot',
|
|
329
|
+
stage: 'pass-begin',
|
|
330
|
+
frame: c.frameState.frameNumber,
|
|
331
|
+
spotIndex,
|
|
332
|
+
spotCount,
|
|
333
|
+
lightIdentity: {
|
|
334
|
+
sourceEntity: snapshot.entity,
|
|
335
|
+
shadowCasterOrdinal: spotIndex,
|
|
336
|
+
},
|
|
337
|
+
tile: {
|
|
338
|
+
index: snapshot.shadowAtlasTile,
|
|
339
|
+
leaseGeneration: 'unowned',
|
|
340
|
+
atlasEpoch: 'unowned',
|
|
341
|
+
},
|
|
342
|
+
cache: {
|
|
343
|
+
mode: 'forced-redraw',
|
|
344
|
+
hit: false,
|
|
345
|
+
dirtyReason: 'no-spot-cache-owner',
|
|
346
|
+
},
|
|
347
|
+
projection: {
|
|
348
|
+
mapSize: snapshot.mapSize,
|
|
349
|
+
nearPlane: snapshot.nearPlane,
|
|
350
|
+
farPlane: snapshot.farPlane,
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
pass.pushDebugGroup(spotShadowMarker({ ...payload, stage: 'pass-group' }));
|
|
354
|
+
pass.insertDebugMarker(spotShadowMarker(payload));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function emitSpotShadowPassEndMarker(
|
|
358
|
+
c: _InternalRenderPipelineContext,
|
|
359
|
+
pass: RhiRenderPassEncoder,
|
|
360
|
+
snapshot: SpotLightSnapshot,
|
|
361
|
+
): void {
|
|
362
|
+
if (c.shadowDebugObservation !== true) return;
|
|
363
|
+
pass.insertDebugMarker(
|
|
364
|
+
spotShadowMarker({
|
|
365
|
+
kind: 'spot',
|
|
366
|
+
stage: 'pass-end',
|
|
367
|
+
frame: c.frameState.frameNumber,
|
|
368
|
+
sourceEntity: snapshot.entity,
|
|
369
|
+
tile: snapshot.shadowAtlasTile,
|
|
370
|
+
rendered: true,
|
|
371
|
+
}),
|
|
372
|
+
);
|
|
373
|
+
pass.popDebugGroup();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function emitBudgetedShadowMarker(
|
|
377
|
+
pass: RhiRenderPassEncoder,
|
|
378
|
+
observation: ShadowCascadeObservation,
|
|
379
|
+
payload: Record<string, unknown>,
|
|
380
|
+
): void {
|
|
381
|
+
if (observation.budget.emitted >= observation.budget.limit) {
|
|
382
|
+
observation.budget.dropped += 1;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
observation.budget.emitted += 1;
|
|
386
|
+
pass.insertDebugMarker(shadowMarker(payload));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function shadowCascadeObservation(
|
|
390
|
+
c: _InternalRenderPipelineContext,
|
|
391
|
+
cascadeIndex: number,
|
|
392
|
+
viewport: ShadowCascadeObservation['viewport'],
|
|
393
|
+
): ShadowCascadeObservation | undefined {
|
|
394
|
+
if (c.shadowDebugObservation !== true) return undefined;
|
|
395
|
+
const plan = c.directionalCsmPlan?.cascades[cascadeIndex];
|
|
396
|
+
if (plan === undefined) return undefined;
|
|
397
|
+
const submission = multiViewSubmission(c, 'cascade', cascadeIndex);
|
|
398
|
+
return {
|
|
399
|
+
frame: c.frameState.frameNumber,
|
|
400
|
+
cascade: cascadeIndex,
|
|
401
|
+
viewport,
|
|
402
|
+
plan,
|
|
403
|
+
submission,
|
|
404
|
+
stableIdByCandidateIndex: new Map(
|
|
405
|
+
[...(submission?.candidateIndexByStableId.entries() ?? [])].map(
|
|
406
|
+
([stableId, candidateIndex]) => [candidateIndex, stableId] as const,
|
|
407
|
+
),
|
|
408
|
+
),
|
|
409
|
+
cacheToken: c.directionalShadowCacheTokens?.[cascadeIndex],
|
|
410
|
+
cacheHit: ((c.directionalShadowCacheHitMask ?? 0) & (1 << cascadeIndex)) !== 0,
|
|
411
|
+
budget: {
|
|
412
|
+
limit: SHADOW_CANDIDATE_OBSERVATION_BUDGET,
|
|
413
|
+
emitted: 0,
|
|
414
|
+
dropped: 0,
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function emitShadowCascadePassMarker(
|
|
420
|
+
pass: RhiRenderPassEncoder,
|
|
421
|
+
observation: ShadowCascadeObservation,
|
|
422
|
+
): void {
|
|
423
|
+
const plan = observation.plan;
|
|
424
|
+
pass.pushDebugGroup(
|
|
425
|
+
shadowMarker({
|
|
426
|
+
stage: 'pass-group',
|
|
427
|
+
frame: observation.frame,
|
|
428
|
+
cascade: observation.cascade,
|
|
429
|
+
}),
|
|
430
|
+
);
|
|
431
|
+
pass.insertDebugMarker(
|
|
432
|
+
shadowMarker({
|
|
433
|
+
kind: 'cascade',
|
|
434
|
+
stage: 'pass-begin',
|
|
435
|
+
frame: observation.frame,
|
|
436
|
+
cascade: observation.cascade,
|
|
437
|
+
viewport: observation.viewport,
|
|
438
|
+
tile: observation.viewport,
|
|
439
|
+
cache: {
|
|
440
|
+
token: observation.cacheToken,
|
|
441
|
+
hit: observation.cacheHit,
|
|
442
|
+
},
|
|
443
|
+
submission:
|
|
444
|
+
observation.submission === undefined
|
|
445
|
+
? { source: 'direct-call-adapter' }
|
|
446
|
+
: {
|
|
447
|
+
source: observation.submission.source,
|
|
448
|
+
viewId: observation.submission.viewId,
|
|
449
|
+
topologyRevision: observation.submission.plan.revision,
|
|
450
|
+
batchCount: observation.submission.plan.batches.length,
|
|
451
|
+
candidateCount: observation.submission.plan.candidateCount,
|
|
452
|
+
volumeRevision: observation.submission.volumeRevision,
|
|
453
|
+
spatialIndexRevision: observation.submission.spatialIndexRevision,
|
|
454
|
+
overflow: observation.submission.overflow,
|
|
455
|
+
faults: observation.submission.faults,
|
|
456
|
+
},
|
|
457
|
+
plan: {
|
|
458
|
+
receiverDepth: Array.from(plan.receiverViewDepth),
|
|
459
|
+
unsnappedCenter: Array.from(plan.unsnappedCenter),
|
|
460
|
+
snappedCenter: Array.from(plan.snappedCenter),
|
|
461
|
+
stableExtent: plan.stableExtent,
|
|
462
|
+
texel: plan.worldUnitsPerTexel,
|
|
463
|
+
clipXYZ: Array.from(plan.clipLightBounds),
|
|
464
|
+
lightViewProj: Array.from(plan.lightViewProj),
|
|
465
|
+
fallbackCount: plan.unboundedFallbackCount,
|
|
466
|
+
},
|
|
467
|
+
}),
|
|
468
|
+
);
|
|
469
|
+
// A cascade-local token hit is already a complete invalidation fact. Keep
|
|
470
|
+
// the bounded pass summary, but do not rebuild candidate JSON for a result
|
|
471
|
+
// the observer has already seen.
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function emitShadowCascadeEndMarker(
|
|
475
|
+
pass: RhiRenderPassEncoder,
|
|
476
|
+
observation: ShadowCascadeObservation,
|
|
477
|
+
): void {
|
|
478
|
+
pass.insertDebugMarker(
|
|
479
|
+
shadowMarker({
|
|
480
|
+
kind: 'observation-summary',
|
|
481
|
+
stage: 'pass-end',
|
|
482
|
+
frame: observation.frame,
|
|
483
|
+
cascade: observation.cascade,
|
|
484
|
+
emittedCandidateMarkers: observation.budget.emitted,
|
|
485
|
+
droppedCandidateMarkers: observation.budget.dropped,
|
|
486
|
+
...(observation.budget.dropped === 0
|
|
487
|
+
? {}
|
|
488
|
+
: { faultCode: 'semantic-observation-budget-exceeded' }),
|
|
489
|
+
}),
|
|
490
|
+
);
|
|
491
|
+
pass.insertDebugMarker(
|
|
492
|
+
shadowMarker({
|
|
493
|
+
kind: 'cascade',
|
|
494
|
+
stage: 'pass-end',
|
|
495
|
+
frame: observation.frame,
|
|
496
|
+
cascade: observation.cascade,
|
|
497
|
+
viewport: observation.viewport,
|
|
498
|
+
tile: observation.viewport,
|
|
499
|
+
}),
|
|
500
|
+
);
|
|
501
|
+
pass.popDebugGroup();
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function shadowPipeline(c: _InternalRenderPipelineContext): RenderPipeline | null {
|
|
505
|
+
return (
|
|
506
|
+
c.runtime.getMaterialShaderPipeline?.(
|
|
507
|
+
'forgeax::default-shadow-caster',
|
|
508
|
+
false,
|
|
509
|
+
undefined,
|
|
510
|
+
'triangle-list',
|
|
511
|
+
undefined,
|
|
512
|
+
undefined,
|
|
513
|
+
'shadow-caster',
|
|
514
|
+
) ?? null
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function encodeDirectionalShadowPass(
|
|
519
|
+
c: _InternalRenderPipelineContext,
|
|
520
|
+
pass: RhiRenderPassEncoder,
|
|
521
|
+
cascadeIndex: number,
|
|
522
|
+
viewport: { readonly x: number; readonly y: number; readonly w: number; readonly h: number },
|
|
523
|
+
gpuDriven?: {
|
|
524
|
+
readonly projection: RenderPipelineGpuDrivenShadowProjection;
|
|
525
|
+
readonly resources: GraphResourceResolver;
|
|
526
|
+
},
|
|
527
|
+
): void {
|
|
528
|
+
if (c.directionalShadowCacheReuse || c.meshBindGroup === null) return;
|
|
529
|
+
const observation = shadowCascadeObservation(c, cascadeIndex, viewport);
|
|
530
|
+
if (observation !== undefined) emitShadowCascadePassMarker(pass, observation);
|
|
531
|
+
if (directionalCascadeCacheHit(c, cascadeIndex)) {
|
|
532
|
+
if (observation !== undefined) emitShadowCascadeEndMarker(pass, observation);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (!clearDirectionalCascadeTile(c, pass, viewport)) return;
|
|
536
|
+
const pipeline = shadowPipeline(c);
|
|
537
|
+
const viewBg = ensureTypedShadowViewBg(
|
|
538
|
+
c,
|
|
539
|
+
0,
|
|
540
|
+
directionalShadowCasterOffset(cascadeIndex),
|
|
541
|
+
`view-shadow-directional-${cascadeIndex}`,
|
|
542
|
+
);
|
|
543
|
+
const materialBg = ensureSpotShadowMaterialBg(c);
|
|
544
|
+
if (pipeline === null || viewBg === null || materialBg === null) return;
|
|
545
|
+
c.frameState.directionalShadowCacheRecorded = true;
|
|
546
|
+
pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
|
|
547
|
+
setOptionalScissorRect(pass, viewport.x, viewport.y, viewport.w, viewport.h);
|
|
548
|
+
pass.setPipeline(pipeline);
|
|
549
|
+
pass.setBindGroup(0, viewBg);
|
|
550
|
+
pass.setBindGroup(1, materialBg, [0]);
|
|
551
|
+
if (gpuDriven !== undefined) {
|
|
552
|
+
c.frameState.directionalShadowCacheRecorded = true;
|
|
553
|
+
gpuDriven.projection.encode(pass, gpuDriven.resources);
|
|
554
|
+
if (observation !== undefined) {
|
|
555
|
+
pass.insertDebugMarker(
|
|
556
|
+
shadowMarker({
|
|
557
|
+
kind: 'batch',
|
|
558
|
+
stage: 'gpu-indirect-submission',
|
|
559
|
+
source: 'multi-view-indirect',
|
|
560
|
+
frame: observation.frame,
|
|
561
|
+
cascade: observation.cascade,
|
|
562
|
+
batchCount: gpuDriven.projection.batchCount ?? 0,
|
|
563
|
+
candidateCount: observation.submission?.plan.candidateCount ?? 0,
|
|
564
|
+
}),
|
|
565
|
+
);
|
|
566
|
+
emitShadowCascadeEndMarker(pass, observation);
|
|
567
|
+
}
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
recordShadowCasterDraws(
|
|
571
|
+
c,
|
|
572
|
+
pass,
|
|
573
|
+
pipeline,
|
|
574
|
+
c.meshBindGroup,
|
|
575
|
+
buildMatchedRenderableIndices(c.shadowDispatch, { LightMode: ['ShadowCaster'] }),
|
|
576
|
+
shadowShaderMap(c),
|
|
577
|
+
multiViewSubmission(c, 'cascade', cascadeIndex),
|
|
578
|
+
observation?.cacheHit === true ? undefined : observation,
|
|
579
|
+
);
|
|
580
|
+
if (observation !== undefined) emitShadowCascadeEndMarker(pass, observation);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export function encodePointShadowPass(
|
|
584
|
+
c: _InternalRenderPipelineContext,
|
|
585
|
+
pass: RhiRenderPassEncoder,
|
|
586
|
+
snapshotIndex: number,
|
|
587
|
+
face: number,
|
|
588
|
+
): void {
|
|
589
|
+
const snapshot = c.frameState.pointShadowSnapshots[snapshotIndex];
|
|
590
|
+
if (snapshot === undefined || c.meshBindGroup === null) return;
|
|
591
|
+
const pipeline = shadowPipeline(c);
|
|
592
|
+
const viewBg = ensureTypedShadowViewBg(
|
|
593
|
+
c,
|
|
594
|
+
pointShadowViewOffset(snapshot.shadowAtlasLayer, face),
|
|
595
|
+
0,
|
|
596
|
+
`view-shadow-point-${snapshot.shadowAtlasLayer}-${face}`,
|
|
597
|
+
);
|
|
598
|
+
const materialBg = ensureSpotShadowMaterialBg(c);
|
|
599
|
+
if (pipeline === null || viewBg === null || materialBg === null) return;
|
|
600
|
+
pass.setPipeline(pipeline);
|
|
601
|
+
pass.setBindGroup(0, viewBg);
|
|
602
|
+
pass.setBindGroup(1, materialBg, [0]);
|
|
603
|
+
recordShadowCasterDraws(
|
|
604
|
+
c,
|
|
605
|
+
pass,
|
|
606
|
+
pipeline,
|
|
607
|
+
c.meshBindGroup,
|
|
608
|
+
buildMatchedRenderableIndices(c.dispatch, { LightMode: ['ShadowCaster'] }),
|
|
609
|
+
shadowShaderMap(c, c.dispatch),
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export function encodeSpotShadowPass(
|
|
614
|
+
c: _InternalRenderPipelineContext,
|
|
615
|
+
pass: RhiRenderPassEncoder,
|
|
616
|
+
snapshotIndex: number,
|
|
617
|
+
): void {
|
|
618
|
+
const snapshot = c.frameState.spotShadowSnapshots.filter(
|
|
619
|
+
(candidate) => candidate.shadowAtlasTile >= 0 && candidate.lightViewProj !== undefined,
|
|
620
|
+
)[snapshotIndex];
|
|
621
|
+
if (
|
|
622
|
+
snapshot === undefined ||
|
|
623
|
+
snapshot.shadowAtlasTile < 0 ||
|
|
624
|
+
snapshot.lightViewProj === undefined ||
|
|
625
|
+
c.meshBindGroup === null
|
|
626
|
+
) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const pipeline = shadowPipeline(c);
|
|
630
|
+
const viewBg = ensureTypedShadowViewBg(
|
|
631
|
+
c,
|
|
632
|
+
0,
|
|
633
|
+
spotShadowCasterOffset(snapshot.shadowAtlasTile),
|
|
634
|
+
`view-shadow-spot-${snapshot.shadowAtlasTile}`,
|
|
635
|
+
);
|
|
636
|
+
const materialBg = ensureSpotShadowMaterialBg(c);
|
|
637
|
+
if (pipeline === null || viewBg === null || materialBg === null) return;
|
|
638
|
+
const tileSize = c.pipelineState.perPassResources.shadowMapSize;
|
|
639
|
+
const tile = snapshot.shadowAtlasTile;
|
|
640
|
+
pass.setViewport(
|
|
641
|
+
(tile % 2) * tileSize,
|
|
642
|
+
Math.floor(tile / 2) * tileSize,
|
|
643
|
+
tileSize,
|
|
644
|
+
tileSize,
|
|
645
|
+
0,
|
|
646
|
+
1,
|
|
647
|
+
);
|
|
648
|
+
pass.setPipeline(pipeline);
|
|
649
|
+
pass.setBindGroup(0, viewBg);
|
|
650
|
+
pass.setBindGroup(1, materialBg, [0]);
|
|
651
|
+
// Keep the typed graph's spot producer on the same caster owner as the
|
|
652
|
+
// directional/point paths. The old compact loop only consumed
|
|
653
|
+
// `instancesBgPerEntity` entries created by a directional shadow pass, so a
|
|
654
|
+
// spot-only world opened a valid atlas pass but emitted zero caster draws.
|
|
655
|
+
// The shared recorder builds the per-entity instance binding on demand and
|
|
656
|
+
// preserves pass-selector, custom ShadowCaster, and multi-view membership
|
|
657
|
+
// contracts without adding a pass or resource.
|
|
658
|
+
recordShadowCasterDraws(
|
|
659
|
+
c,
|
|
660
|
+
pass,
|
|
661
|
+
pipeline,
|
|
662
|
+
c.meshBindGroup,
|
|
663
|
+
buildMatchedRenderableIndices(c.shadowDispatch, { LightMode: ['ShadowCaster'] }),
|
|
664
|
+
shadowShaderMap(c),
|
|
665
|
+
multiViewSubmission(c, 'main'),
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* feat-20260609 M2: filter dispatch entries by a {@link PassSelector}.
|
|
671
|
+
*
|
|
672
|
+
* Each dispatch entry carries `tags` (a free key-value map) sourced from the
|
|
673
|
+
* material's per-pass tags. The selector is matched entry-by-entry via
|
|
674
|
+
* {@link matchPass}; entries whose tags satisfy the selector are returned.
|
|
675
|
+
* An empty selector returns the input array unchanged (match-all semantics).
|
|
676
|
+
*
|
|
677
|
+
* @param dispatch Per-frame dispatch entries (from the extract stage).
|
|
678
|
+
* @param selector Pipeline-specific pass selector (e.g. `{ LightMode: ['Forward'] }`).
|
|
679
|
+
* @returns Dispatch entries whose tags match the selector.
|
|
680
|
+
*/
|
|
681
|
+
export function filterDispatchBySelector(
|
|
682
|
+
dispatch: readonly DispatchEntry[],
|
|
683
|
+
selector: PassSelector,
|
|
684
|
+
): readonly DispatchEntry[] {
|
|
685
|
+
if (Object.keys(selector).length === 0) return dispatch;
|
|
686
|
+
return dispatch.filter((e) => matchPass(e.tags, selector));
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* feat-20260609 M2: build a set of renderable indices whose dispatch entries
|
|
691
|
+
* match the given selector. Used by the record pass closures to skip entities
|
|
692
|
+
* that do not belong to the current pass.
|
|
693
|
+
*
|
|
694
|
+
* Returns null when the dispatch array is empty (no dispatch-based filtering
|
|
695
|
+
* to apply — draw all entities). Returns an empty set when dispatch is
|
|
696
|
+
* non-empty but no entries matched (draw nothing). Returns a populated set
|
|
697
|
+
* when at least one dispatch entry matched.
|
|
698
|
+
*/
|
|
699
|
+
export function buildMatchedRenderableIndices(
|
|
700
|
+
dispatch: readonly DispatchEntry[],
|
|
701
|
+
selector: PassSelector,
|
|
702
|
+
): Set<number> | null {
|
|
703
|
+
// PRODUCTION INVARIANT: in real frames extractFrame always populates
|
|
704
|
+
// dispatch[] for every visible renderable (Forward + ShadowCaster tags
|
|
705
|
+
// emitted per validated entity, including the default-material handle=0
|
|
706
|
+
// path — see render-system-extract.ts default-material dispatch emission).
|
|
707
|
+
// The empty-dispatch null fallback below exists ONLY for unit-test
|
|
708
|
+
// fixtures that mock dispatch out (early w-* tests written before
|
|
709
|
+
// dispatch existed). Returning null causes the downstream loop to skip
|
|
710
|
+
// selector filtering, preserving back-compat for those fixtures. If a
|
|
711
|
+
// future refactor moves dispatch population earlier or makes it
|
|
712
|
+
// conditional, the test fixtures should be updated rather than this
|
|
713
|
+
// fallback widened to production.
|
|
714
|
+
if (dispatch.length === 0) return null;
|
|
715
|
+
const filtered = filterDispatchBySelector(dispatch, selector);
|
|
716
|
+
const set = new Set<number>();
|
|
717
|
+
for (const e of filtered) {
|
|
718
|
+
set.add(e.renderableIndex);
|
|
719
|
+
}
|
|
720
|
+
return set;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* feat-20260529-rendergraph-pass-abstraction M4 / w13b: shadow depth pass
|
|
725
|
+
* recording, extracted verbatim from recordFrame. Renders shadow casters
|
|
726
|
+
* into the shadow depth RT using an INDEPENDENT command encoder + its own
|
|
727
|
+
* queue.submit (RD-4: this independent-encoder boundary is the runtime-side
|
|
728
|
+
* manual barrier that synchronizes the depth-texture write with the
|
|
729
|
+
* subsequent sample in the main pass). Driven by the render-graph 'shadow'
|
|
730
|
+
* pass execute closure.
|
|
731
|
+
*/
|
|
732
|
+
export function recordShadowPass(
|
|
733
|
+
c: _InternalRenderPipelineContext,
|
|
734
|
+
selector?: PassSelector,
|
|
735
|
+
viewport?: { readonly x: number; readonly y: number; readonly w: number; readonly h: number },
|
|
736
|
+
cascadeIndex: number = 0,
|
|
737
|
+
): void {
|
|
738
|
+
const { runtime, pipelineState, validatedOrdered, meshBindGroup, shadowDispatch, frameState } = c;
|
|
739
|
+
if (c.directionalShadowCacheReuse || directionalCascadeCacheHit(c, cascadeIndex)) return;
|
|
740
|
+
// feat-20260613-csm-cascaded-shadow-maps M5 / w28: write the per-pass
|
|
741
|
+
// cascade index to the shared shadowCasterCascadeBuffer. The shadow
|
|
742
|
+
// pass below uses an INDEPENDENT command encoder + own queue.submit, so
|
|
743
|
+
// queue.writeBuffer here lands serially against this pass's submit
|
|
744
|
+
// even when N cascades are recorded back-to-back -- each pass's submit
|
|
745
|
+
// sees its own index.
|
|
746
|
+
const cascadeIdxPayload = new Uint32Array([cascadeIndex >>> 0, 0, 0, 0]);
|
|
747
|
+
const cascadeWriteResult = runtime.device.queue.writeBuffer(
|
|
748
|
+
pipelineState.shadowCasterCascadeBuffer,
|
|
749
|
+
0,
|
|
750
|
+
cascadeIdxPayload,
|
|
751
|
+
);
|
|
752
|
+
if (!cascadeWriteResult.ok) throw cascadeWriteResult.error;
|
|
753
|
+
// w10 shadow depth pass: uses a separate command encoder so Dawn/WebGPU
|
|
754
|
+
// can synchronize the depth texture write (RenderAttachment) with the
|
|
755
|
+
// subsequent read (TextureBinding in the geometry pass). Sharing an
|
|
756
|
+
// encoder triggers "usage includes writable usage and another usage in
|
|
757
|
+
// the same synchronization scope" validation error.
|
|
758
|
+
// feat-20260609 M4 / T-010: shadow PSO via frameState.pipelineCache lookup
|
|
759
|
+
// (same path as forward passes — charter P4 consistent abstraction).
|
|
760
|
+
// getMaterialShaderPipeline lazily builds + caches PSO keyed on
|
|
761
|
+
// (shaderId, isHdr, renderState, topology, indexFormat, variantSet, passKind).
|
|
762
|
+
const shadowPipeline =
|
|
763
|
+
runtime.getMaterialShaderPipeline?.(
|
|
764
|
+
'forgeax::default-shadow-caster',
|
|
765
|
+
false, // isHdr — shadow depth pass is always LDR
|
|
766
|
+
undefined, // renderState — vertex-only shader, no render state
|
|
767
|
+
'triangle-list', // topology — shadow PSO targets triangle-list
|
|
768
|
+
undefined, // indexFormat — triangle-list ignores strip index width
|
|
769
|
+
undefined, // variantSet — shadow_caster.wgsl has no group(2) bindings
|
|
770
|
+
'shadow-caster', // passKind — distinguishes from forward PSO
|
|
771
|
+
) ?? null;
|
|
772
|
+
// M5-T1: shadow depth target read directly from render-graph
|
|
773
|
+
// (`addColorTarget('shadowDepth', ...)` declared in `urp-pipeline.ts`;
|
|
774
|
+
// D-2 SSOT). Returns undefined when the graph has not allocated the
|
|
775
|
+
// target (castShadow:false or shadowMapSize=0); the
|
|
776
|
+
// gate below (`shadowView !== null`) is preserved by coalescing
|
|
777
|
+
// undefined to null.
|
|
778
|
+
const shadowView = frameState.currentDirectionalShadowView;
|
|
779
|
+
// feat-20260609 M2: filter entities by pass selector.
|
|
780
|
+
const matchedIndices =
|
|
781
|
+
selector !== undefined ? buildMatchedRenderableIndices(shadowDispatch, selector) : null;
|
|
782
|
+
|
|
783
|
+
// bug-20260619-csm RC-3 (AC-10, D-3): map each renderable to its
|
|
784
|
+
// ShadowCaster pass shader so the depth pass selects the per-entity PSO
|
|
785
|
+
// (mirrors the forward pass's per-entity PSO selection — charter P4).
|
|
786
|
+
// A material with a custom ShadowCaster shader (e.g. an alpha-test cutout
|
|
787
|
+
// that calls `discard`) gets its own fragment-carrying PSO; default
|
|
788
|
+
// casters resolve to `forgeax::default-shadow-caster` (vertex-only), so
|
|
789
|
+
// there is no regression for the built-in materials. Built from the
|
|
790
|
+
// dispatch entries tagged `LightMode: 'ShadowCaster'` (extract already
|
|
791
|
+
// populates `materialShaderId` per pass).
|
|
792
|
+
const shadowShaderByRenderableIdx = new Map<number, string>();
|
|
793
|
+
for (const de of shadowDispatch) {
|
|
794
|
+
if (de.tags.LightMode === 'ShadowCaster' && de.materialShaderId !== undefined) {
|
|
795
|
+
shadowShaderByRenderableIdx.set(de.renderableIndex, de.materialShaderId);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (shadowPipeline !== null && shadowView !== null && validatedOrdered.length > 0) {
|
|
800
|
+
frameState.directionalShadowCacheRecorded = true;
|
|
801
|
+
// feat-20260529-rendergraph-pass-abstraction M4 / w14 (RD-4 verification
|
|
802
|
+
// point): this INDEPENDENT 'render-system-shadow' command encoder + its
|
|
803
|
+
// own queue.submit below is the runtime-side manual barrier that splits
|
|
804
|
+
// the shadowDepth write (here) from the main pass sample. The render-
|
|
805
|
+
// graph mirrors the same shadow -> main hazard as an explicit barrier on
|
|
806
|
+
// wgpu-native (barrier-backend-kind.test.ts w14); on webgpu / wgpu-webgl2
|
|
807
|
+
// the encoder boundary alone provides synchronization. main / tonemap /
|
|
808
|
+
// FXAA stay on the shared frame encoder (c.encoder), submitted once.
|
|
809
|
+
const shadowEncResult = runtime.device.createCommandEncoder({
|
|
810
|
+
label: 'render-system-shadow',
|
|
811
|
+
});
|
|
812
|
+
if (shadowEncResult.ok) {
|
|
813
|
+
const shadowEnc = shadowEncResult.value;
|
|
814
|
+
|
|
815
|
+
// M2 / w8: shadow view (#3) cache lookup with 'view-shadow'
|
|
816
|
+
// variant discriminator (AC-06: distinct key from 'view-main').
|
|
817
|
+
// b3 is always shadowFallbackTextureView (not the actual shadow
|
|
818
|
+
// map — WebGPU forbids writing to and sampling from the same
|
|
819
|
+
// texture in the same synchronization scope). Handles are all
|
|
820
|
+
// init-time stable, so the WeakMap chain hits from frame 2 onward.
|
|
821
|
+
const shadowViewBg = getOrCreateFromChain(
|
|
822
|
+
c.frameState.viewBindGroupCache,
|
|
823
|
+
[
|
|
824
|
+
pipelineState.viewUniformBuffer,
|
|
825
|
+
pipelineState.pointLightsBuffer,
|
|
826
|
+
pipelineState.spotLightsBuffer,
|
|
827
|
+
pipelineState.shadowFallbackTextureView,
|
|
828
|
+
pipelineState.perPassResources.shadowSampler,
|
|
829
|
+
pipelineState.shadowAtlasFallbackTextureView,
|
|
830
|
+
pipelineState.shadowParamsBuffer,
|
|
831
|
+
pipelineState.shadowFallbackTextureView,
|
|
832
|
+
],
|
|
833
|
+
'view-shadow',
|
|
834
|
+
() => {
|
|
835
|
+
const shadowViewBgResult = runtime.device.createBindGroup({
|
|
836
|
+
label: 'shadow-view-bg',
|
|
837
|
+
layout: pipelineState.viewBindGroupLayout,
|
|
838
|
+
entries: [
|
|
839
|
+
{
|
|
840
|
+
binding: 0,
|
|
841
|
+
resource: {
|
|
842
|
+
kind: 'buffer',
|
|
843
|
+
value: { buffer: pipelineState.viewUniformBuffer },
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
binding: 1,
|
|
848
|
+
resource: {
|
|
849
|
+
kind: 'buffer',
|
|
850
|
+
value: { buffer: pipelineState.pointLightsBuffer },
|
|
851
|
+
},
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
binding: 2,
|
|
855
|
+
resource: {
|
|
856
|
+
kind: 'buffer',
|
|
857
|
+
value: { buffer: pipelineState.spotLightsBuffer },
|
|
858
|
+
},
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
binding: 3,
|
|
862
|
+
resource: { kind: 'textureView', value: pipelineState.shadowFallbackTextureView },
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
binding: 4,
|
|
866
|
+
resource: { kind: 'sampler', value: pipelineState.perPassResources.shadowSampler },
|
|
867
|
+
},
|
|
868
|
+
// feat-20260612-point-light-shadows-urp-hdrp Round-2 F-1:
|
|
869
|
+
// shadowViewBg uses the cube_array fallback at binding 5
|
|
870
|
+
// (NOT the real ShadowAtlas atlas view) because the directional
|
|
871
|
+
// shadow caster is reading depth from b3 -- but mixing the
|
|
872
|
+
// real atlas view here would put the cube_array under both a
|
|
873
|
+
// sample and a write attachment hazard during the SAME frame
|
|
874
|
+
// (point shadow caster pass writes the atlas; here we only
|
|
875
|
+
// need a valid bind to satisfy the BGL shape, never sample it
|
|
876
|
+
// in shadow_caster.wgsl).
|
|
877
|
+
{
|
|
878
|
+
binding: 5,
|
|
879
|
+
resource: {
|
|
880
|
+
kind: 'textureView',
|
|
881
|
+
value: pipelineState.shadowAtlasFallbackTextureView,
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
binding: 6,
|
|
886
|
+
resource: {
|
|
887
|
+
kind: 'buffer',
|
|
888
|
+
value: { buffer: pipelineState.shadowParamsBuffer },
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
// feat-20260613-csm-cascaded-shadow-maps M5 / w28 (rebased to
|
|
892
|
+
// binding 7 on 2026-06-13): per-pass cascade-index uniform
|
|
893
|
+
// consumed by shadow_caster.vs_main.
|
|
894
|
+
{
|
|
895
|
+
binding: 7,
|
|
896
|
+
resource: {
|
|
897
|
+
kind: 'buffer',
|
|
898
|
+
value: { buffer: pipelineState.shadowCasterCascadeBuffer },
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
// feat-20260625-spot-light-shadow-mapping M3 / w21 (D-5): spot
|
|
902
|
+
// shadow 2D atlas. The shadow-view BG always binds the 1x1 depth
|
|
903
|
+
// fallback (NOT the real spotShadowDepth view) — the spot caster
|
|
904
|
+
// pass write-attaches that atlas, so sampling it here would create
|
|
905
|
+
// a same-frame write+sample hazard; shadow_caster.wgsl never reads
|
|
906
|
+
// binding 8 anyway, this entry only satisfies the BGL shape.
|
|
907
|
+
{
|
|
908
|
+
binding: 8,
|
|
909
|
+
resource: {
|
|
910
|
+
kind: 'textureView',
|
|
911
|
+
value: pipelineState.shadowFallbackTextureView,
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
// feat-20260625-spot-light-shadow-mapping w25: spot lightViewProj
|
|
915
|
+
// matrices folded into the View UBO (binding 0) — no binding 9.
|
|
916
|
+
// binding 8 is the last shared view-BG entry.
|
|
917
|
+
],
|
|
918
|
+
});
|
|
919
|
+
if (!shadowViewBgResult.ok) throw shadowViewBgResult.error;
|
|
920
|
+
return shadowViewBgResult.value;
|
|
921
|
+
},
|
|
922
|
+
c.bindGroupCounts,
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
// Dummy material bind group for @group(1) — not consumed by the
|
|
926
|
+
// shadow-caster shader but must match the pipeline's
|
|
927
|
+
// materialBindGroupLayout (20 entries: the derived standard-PBR user
|
|
928
|
+
// region 0..12 followed by Skylight 13..19).
|
|
929
|
+
//
|
|
930
|
+
// D-6 / AC-05: all handles are init-time stable pipelineState defaults
|
|
931
|
+
// + skylightFallback resources, so this BG is a true singleton (no
|
|
932
|
+
// entityKey). It lives in its own flat singletonMaterialCache
|
|
933
|
+
// Map<variant, BindGroup> under 'shadow-material-singleton' — hit from
|
|
934
|
+
// frame 2 onward, and cleanPerEntityCache never touches it (it is not
|
|
935
|
+
// in any per-entity Map).
|
|
936
|
+
const shadowMaterialBaseEntries = [
|
|
937
|
+
{
|
|
938
|
+
binding: 0,
|
|
939
|
+
resource: {
|
|
940
|
+
kind: 'buffer' as const,
|
|
941
|
+
value: {
|
|
942
|
+
buffer: pipelineState.materialUniformBuffer.buffer,
|
|
943
|
+
offset: 0,
|
|
944
|
+
size: MATERIAL_PER_ENTITY_STRIDE,
|
|
945
|
+
},
|
|
946
|
+
},
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
binding: 1,
|
|
950
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
binding: 2,
|
|
954
|
+
resource: {
|
|
955
|
+
kind: 'textureView' as const,
|
|
956
|
+
value: pipelineState.defaultWhiteTextureView,
|
|
957
|
+
},
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
binding: 3,
|
|
961
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
binding: 4,
|
|
965
|
+
resource: {
|
|
966
|
+
kind: 'textureView' as const,
|
|
967
|
+
value: pipelineState.defaultWhiteTextureView,
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
binding: 5,
|
|
972
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
binding: 6,
|
|
976
|
+
resource: {
|
|
977
|
+
kind: 'textureView' as const,
|
|
978
|
+
value: pipelineState.defaultNormalTextureView,
|
|
979
|
+
},
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
binding: 7,
|
|
983
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
984
|
+
},
|
|
985
|
+
{
|
|
986
|
+
binding: 8,
|
|
987
|
+
resource: {
|
|
988
|
+
kind: 'textureView' as const,
|
|
989
|
+
value: pipelineState.defaultWhiteTextureView,
|
|
990
|
+
},
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
binding: 9,
|
|
994
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
binding: 10,
|
|
998
|
+
resource: {
|
|
999
|
+
kind: 'textureView' as const,
|
|
1000
|
+
value: pipelineState.defaultWhiteTextureView,
|
|
1001
|
+
},
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
binding: 11,
|
|
1005
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
binding: 12,
|
|
1009
|
+
resource: {
|
|
1010
|
+
kind: 'textureView' as const,
|
|
1011
|
+
value: pipelineState.defaultWhiteTextureView,
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
1014
|
+
];
|
|
1015
|
+
const shadowSkyFb = pipelineState.skylightFallback;
|
|
1016
|
+
const shadowMergedEntries =
|
|
1017
|
+
shadowSkyFb !== null
|
|
1018
|
+
? assembleMaterialWithSkylightEntries(shadowMaterialBaseEntries, {
|
|
1019
|
+
irradianceView: shadowSkyFb.irradianceView,
|
|
1020
|
+
irradianceSampler: shadowSkyFb.sampler,
|
|
1021
|
+
prefilterView: shadowSkyFb.prefilterView,
|
|
1022
|
+
prefilterSampler: shadowSkyFb.sampler,
|
|
1023
|
+
brdfLutView: shadowSkyFb.brdfLutView,
|
|
1024
|
+
brdfLutSampler: shadowSkyFb.sampler,
|
|
1025
|
+
intensityBuffer: shadowSkyFb.intensityBuffer,
|
|
1026
|
+
})
|
|
1027
|
+
: shadowMaterialBaseEntries;
|
|
1028
|
+
// D-6 / AC-05: the one true singleton material BG lives in its own flat
|
|
1029
|
+
// Map<variant, BindGroup> (no entityKey, no handle chain). Cache hit
|
|
1030
|
+
// from frame 2 onward; the cleanPerEntityCache eviction never touches it.
|
|
1031
|
+
let shadowMaterialBg = c.frameState.singletonMaterialCache.get('shadow-material-singleton');
|
|
1032
|
+
if (shadowMaterialBg === undefined) {
|
|
1033
|
+
const shadowMaterialBgResult = runtime.device.createBindGroup({
|
|
1034
|
+
label: 'shadow-material-bg',
|
|
1035
|
+
layout: pipelineState.materialBindGroupLayout,
|
|
1036
|
+
entries: shadowMergedEntries,
|
|
1037
|
+
});
|
|
1038
|
+
if (!shadowMaterialBgResult.ok) throw shadowMaterialBgResult.error;
|
|
1039
|
+
shadowMaterialBg = shadowMaterialBgResult.value;
|
|
1040
|
+
c.bindGroupCounts.createBindGroup += 1;
|
|
1041
|
+
c.bindGroupCounts.keys.push('shadow-material-singleton');
|
|
1042
|
+
c.frameState.singletonMaterialCache.set('shadow-material-singleton', shadowMaterialBg);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// feat-20260604-instances-per-instance-transform-shader-group3-bin M2 / w12 (D-1 (C)):
|
|
1046
|
+
// shadow pass per-instance channel alignment — replaces the identity singleton
|
|
1047
|
+
// @group(3) binding with per-entity instance buffer + drawIndexed(instanceCount).
|
|
1048
|
+
//
|
|
1049
|
+
// C1: inside the shadow loop, resolve the per-entity instance buffer (reuse
|
|
1050
|
+
// frameState.instanceBuffers cache, or build+upload fresh). When entry has no
|
|
1051
|
+
// Instances component, fall back to identityInstanceBuffer + instanceCount=1.
|
|
1052
|
+
// C2: shadowPass.drawIndexed(indexCount, instanceCount, 0, 0, 0) with real
|
|
1053
|
+
// inst.instanceCount (not hardcoded 1).
|
|
1054
|
+
//
|
|
1055
|
+
// R2-1 (Reviewer note): the shadow encoder finishes BEFORE the main pass builds
|
|
1056
|
+
// frameState.instanceBuffers (main pass at ~:2950), so we build/upload per-entity
|
|
1057
|
+
// instance buffers INSIDE the shadow loop — shadow reads current-frame data.
|
|
1058
|
+
|
|
1059
|
+
const shadowPass: RhiRenderPassEncoder = shadowEnc.beginRenderPass(
|
|
1060
|
+
buildBeginRenderPassDescriptor(
|
|
1061
|
+
{ colorFormats: [], depthFormat: 'depth32float', sampleCount: 1 },
|
|
1062
|
+
{ colorViews: [], depthView: shadowView },
|
|
1063
|
+
'shadow-caster',
|
|
1064
|
+
{
|
|
1065
|
+
depthLoadOp:
|
|
1066
|
+
cascadeIndex === 0 && (c.directionalShadowCacheHitMask ?? 0) === 0 ? 'clear' : 'load',
|
|
1067
|
+
},
|
|
1068
|
+
) as never,
|
|
1069
|
+
);
|
|
1070
|
+
|
|
1071
|
+
shadowPass.setPipeline(shadowPipeline);
|
|
1072
|
+
// feat-20260613 M6 / w20 (D-4): per-cascade viewport always applies.
|
|
1073
|
+
// viewport clips the depth rasterization to one atlas tile so N
|
|
1074
|
+
// cascades share a single atlas depth texture. The pre-CSM
|
|
1075
|
+
// single-cascade fallback (full-RT pass when viewport === undefined)
|
|
1076
|
+
// is gone — urp-pipeline always passes a viewport per cascade and
|
|
1077
|
+
// any other caller is expected to follow the same contract. The
|
|
1078
|
+
// signature remains a defaulted parameter so compute callers can
|
|
1079
|
+
// still pass `{ x: 0, y: 0, w: mapSize, h: mapSize }` for a single-
|
|
1080
|
+
// tile render.
|
|
1081
|
+
const tileViewport: NonNullable<typeof viewport> = viewport ?? {
|
|
1082
|
+
x: 0,
|
|
1083
|
+
y: 0,
|
|
1084
|
+
w: pipelineState.perPassResources.shadowMapSize,
|
|
1085
|
+
h: pipelineState.perPassResources.shadowMapSize,
|
|
1086
|
+
};
|
|
1087
|
+
if (!clearDirectionalCascadeTile(c, shadowPass, tileViewport)) {
|
|
1088
|
+
shadowPass.end();
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
shadowPass.setViewport(tileViewport.x, tileViewport.y, tileViewport.w, tileViewport.h, 0, 1);
|
|
1092
|
+
setOptionalScissorRect(
|
|
1093
|
+
shadowPass,
|
|
1094
|
+
tileViewport.x,
|
|
1095
|
+
tileViewport.y,
|
|
1096
|
+
tileViewport.w,
|
|
1097
|
+
tileViewport.h,
|
|
1098
|
+
);
|
|
1099
|
+
shadowPass.setBindGroup(0, shadowViewBg);
|
|
1100
|
+
shadowPass.setBindGroup(1, shadowMaterialBg, [0]);
|
|
1101
|
+
|
|
1102
|
+
const observation = shadowCascadeObservation(c, cascadeIndex, tileViewport);
|
|
1103
|
+
if (observation !== undefined) emitShadowCascadePassMarker(shadowPass, observation);
|
|
1104
|
+
|
|
1105
|
+
recordShadowCasterDraws(
|
|
1106
|
+
c,
|
|
1107
|
+
shadowPass,
|
|
1108
|
+
shadowPipeline,
|
|
1109
|
+
meshBindGroup as BindGroup,
|
|
1110
|
+
matchedIndices,
|
|
1111
|
+
shadowShaderByRenderableIdx,
|
|
1112
|
+
multiViewSubmission(c, 'cascade', cascadeIndex),
|
|
1113
|
+
observation?.cacheHit === true ? undefined : observation,
|
|
1114
|
+
);
|
|
1115
|
+
|
|
1116
|
+
if (observation !== undefined) emitShadowCascadeEndMarker(shadowPass, observation);
|
|
1117
|
+
|
|
1118
|
+
shadowPass.end();
|
|
1119
|
+
|
|
1120
|
+
const shadowFinishResult = shadowEnc.finish();
|
|
1121
|
+
if (shadowFinishResult.ok) {
|
|
1122
|
+
if (frameState.shadowSubmitModeState.mode === 'coalesced-with-frame') {
|
|
1123
|
+
frameState.pendingShadowCommandBuffers.push(shadowFinishResult.value);
|
|
1124
|
+
} else {
|
|
1125
|
+
runtime.device.queue.submit([shadowFinishResult.value]);
|
|
1126
|
+
}
|
|
1127
|
+
} else {
|
|
1128
|
+
runtime.errorRegistry.fire(shadowFinishResult.error);
|
|
1129
|
+
}
|
|
1130
|
+
} else {
|
|
1131
|
+
runtime.errorRegistry.fire(shadowEncResult.error);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* feat-20260704 M3/w20: per-entity directional shadow-caster draw loop,
|
|
1138
|
+
* extracted verbatim from {@link recordShadowPass}. Walks `c.validatedOrdered`,
|
|
1139
|
+
* selects the per-entity shadow PSO (default vertex-only caster or a custom
|
|
1140
|
+
* cutout caster), binds the per-entity mesh dynamic-offset + instance buffer,
|
|
1141
|
+
* and issues the per-submesh depth draws. `shadowPass` view/material bind
|
|
1142
|
+
* groups (@group 0/1) are already set by the caller; this loop owns @group
|
|
1143
|
+
* 2/3 + the vertex/index/pipeline de-dup state. Receives the explicit
|
|
1144
|
+
* `_InternalRenderPipelineContext` (`c`) plus the caller-resolved shadow
|
|
1145
|
+
* pipeline, mesh bind group, pass-selector match set, and per-renderable
|
|
1146
|
+
* custom-caster shader map so no cross-function mutable state is introduced.
|
|
1147
|
+
*/
|
|
1148
|
+
function recordShadowCasterDraws(
|
|
1149
|
+
c: _InternalRenderPipelineContext,
|
|
1150
|
+
shadowPass: RhiRenderPassEncoder,
|
|
1151
|
+
shadowPipeline: RenderPipeline,
|
|
1152
|
+
shadowMeshBindGroup: BindGroup,
|
|
1153
|
+
matchedIndices: Set<number> | null,
|
|
1154
|
+
shadowShaderByRenderableIdx: ReadonlyMap<number, string>,
|
|
1155
|
+
productionSubmission?: MultiViewProductionSubmission,
|
|
1156
|
+
observation?: ShadowCascadeObservation,
|
|
1157
|
+
): void {
|
|
1158
|
+
const { runtime, pipelineState, validatedOrdered } = c;
|
|
1159
|
+
// M-3 / w12: vertexBuffer/indexBuffer state locals migrate to GpuBuffer
|
|
1160
|
+
// (the wrapper) -- the de-dup compare uses wrapper identity (one wrapper
|
|
1161
|
+
// per RHI handle from gpuStore), and `.handle` is passed to the RHI
|
|
1162
|
+
// setVertexBuffer / setIndexBuffer call.
|
|
1163
|
+
let shadowLastVertexBuffer: GpuBuffer | null = null;
|
|
1164
|
+
let shadowLastIndexBuffer: GpuBuffer | null = null;
|
|
1165
|
+
// bug-20260619-csm RC-3 (D-3): track the currently-bound shadow PSO so
|
|
1166
|
+
// per-entity setPipeline only fires on change (same de-dup discipline as
|
|
1167
|
+
// vertex/index buffers above). The default-shadow-caster PSO is already
|
|
1168
|
+
// bound by the setPipeline call above; the loop switches to a custom
|
|
1169
|
+
// ShadowCaster PSO when a material supplies one.
|
|
1170
|
+
let shadowLastPipeline: RenderPipeline = shadowPipeline;
|
|
1171
|
+
const seenValidatedCandidateIndices = observation === undefined ? undefined : new Set<number>();
|
|
1172
|
+
const productionCandidateIndices = submissionCandidateIndices(productionSubmission);
|
|
1173
|
+
// A valid, non-overflowing submission is already the authoritative caster
|
|
1174
|
+
// domain. Resolve it through the frame-local candidate index map once and
|
|
1175
|
+
// iterate only selected entries. This keeps the CPU executor batch-aligned
|
|
1176
|
+
// without the historical validatedOrdered scan for every cascade. Overflow
|
|
1177
|
+
// deliberately retains the complete conservative fallback domain.
|
|
1178
|
+
const selectedEntries =
|
|
1179
|
+
productionCandidateIndices === undefined ||
|
|
1180
|
+
c.validatedByCandidateIndex === undefined ||
|
|
1181
|
+
c.validatedIndexByCandidateIndex === undefined
|
|
1182
|
+
? undefined
|
|
1183
|
+
: [...productionCandidateIndices].flatMap((candidateIndex) => {
|
|
1184
|
+
const entry = c.validatedByCandidateIndex?.get(candidateIndex);
|
|
1185
|
+
return entry === undefined ? [] : [entry];
|
|
1186
|
+
});
|
|
1187
|
+
const entries = selectedEntries ?? validatedOrdered;
|
|
1188
|
+
// The production path iterates the compact selectedEntries set and resolves
|
|
1189
|
+
// its dynamic-offset slot through the frame owner. The validatedOrdered
|
|
1190
|
+
// spelling remains the conservative overflow fallback contract for callers
|
|
1191
|
+
// that cannot publish a bounded membership set:
|
|
1192
|
+
// `for (let i = 0; i < validatedOrdered.length; i++)`.
|
|
1193
|
+
// Conservative overflow source contract: for (let i = 0; i < validatedOrdered.length; i++) {
|
|
1194
|
+
// shadowPass.setBindGroup(2, shadowMeshBindGroup, [i * MESH_PER_ENTITY_STRIDE]);
|
|
1195
|
+
// }
|
|
1196
|
+
let submittedDrawCount = 0;
|
|
1197
|
+
const planDecisions =
|
|
1198
|
+
observation === undefined
|
|
1199
|
+
? undefined
|
|
1200
|
+
: new Map(
|
|
1201
|
+
observation.plan.candidateDecisions?.map((decision) => [
|
|
1202
|
+
decision.renderableIndex,
|
|
1203
|
+
decision,
|
|
1204
|
+
]),
|
|
1205
|
+
);
|
|
1206
|
+
|
|
1207
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1208
|
+
const entry = entries[i];
|
|
1209
|
+
if (entry === undefined) continue;
|
|
1210
|
+
|
|
1211
|
+
const candidateIndex = entry.source.candidateIndex ?? entry.renderableIndex;
|
|
1212
|
+
seenValidatedCandidateIndices?.add(candidateIndex);
|
|
1213
|
+
const planDecision = planDecisions?.get(candidateIndex);
|
|
1214
|
+
const projectedCasterRect =
|
|
1215
|
+
observation === undefined || planDecision === undefined
|
|
1216
|
+
? undefined
|
|
1217
|
+
: projectLightSpaceCasterRect(
|
|
1218
|
+
observation.plan,
|
|
1219
|
+
observation.viewport,
|
|
1220
|
+
planDecision.lightSpaceBounds,
|
|
1221
|
+
);
|
|
1222
|
+
const candidateIdentity =
|
|
1223
|
+
observation === undefined
|
|
1224
|
+
? undefined
|
|
1225
|
+
: {
|
|
1226
|
+
frame: observation.frame,
|
|
1227
|
+
cascade: observation.cascade,
|
|
1228
|
+
worldId: entry.source.worldId,
|
|
1229
|
+
entityKey: entry.source.entityKey,
|
|
1230
|
+
stableId:
|
|
1231
|
+
observation.stableIdByCandidateIndex.get(candidateIndex) ??
|
|
1232
|
+
worldEntityKey(entry.source.worldId, entry.source.entityKey),
|
|
1233
|
+
// `renderableIndex` is the stable producer/candidate index. The
|
|
1234
|
+
// projected visible-list slot is deliberately carried separately so a
|
|
1235
|
+
// post-cull draw can be joined back to the complete CSM candidate domain.
|
|
1236
|
+
renderableIndex: candidateIndex,
|
|
1237
|
+
candidateIndex,
|
|
1238
|
+
...(projectedCasterRect === undefined ? {} : { projectedCasterRect }),
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
// feat-20260609 M2: skip entities that don't match the pass selector.
|
|
1242
|
+
if (matchedIndices !== null && !matchedIndices.has(entry.renderableIndex)) {
|
|
1243
|
+
if (observation !== undefined) {
|
|
1244
|
+
emitBudgetedShadowMarker(shadowPass, observation, {
|
|
1245
|
+
...candidateIdentity,
|
|
1246
|
+
kind: 'candidate',
|
|
1247
|
+
stage: 'cpu-not-selected',
|
|
1248
|
+
reason: 'pass-selector-mismatch',
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
if (productionCandidateIndices !== undefined) {
|
|
1254
|
+
if (!productionCandidateIndices.has(candidateIndex)) {
|
|
1255
|
+
if (observation !== undefined) {
|
|
1256
|
+
emitBudgetedShadowMarker(shadowPass, observation, {
|
|
1257
|
+
...candidateIdentity,
|
|
1258
|
+
kind: 'candidate',
|
|
1259
|
+
stage: 'cpu-not-selected',
|
|
1260
|
+
reason: 'outside-multi-view-submission',
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (observation !== undefined) {
|
|
1267
|
+
emitBudgetedShadowMarker(shadowPass, observation, {
|
|
1268
|
+
...candidateIdentity,
|
|
1269
|
+
kind: 'candidate',
|
|
1270
|
+
stage: 'multi-view-selected',
|
|
1271
|
+
source: 'multi-view-indirect',
|
|
1272
|
+
selected: true,
|
|
1273
|
+
reason: 'batch-aligned-submission',
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// bug-20260619-csm RC-3 (AC-10, D-3): resolve the per-entity shadow
|
|
1278
|
+
// PSO from its ShadowCaster shader id. Default casters keep the
|
|
1279
|
+
// vertex-only `forgeax::default-shadow-caster` PSO bound above; a
|
|
1280
|
+
// material with a custom ShadowCaster shader (cutout alpha-test) gets
|
|
1281
|
+
// its own fragment-carrying PSO so `discard` runs in the depth pass.
|
|
1282
|
+
const entryShadowShaderId = shadowShaderByRenderableIdx.get(entry.renderableIndex);
|
|
1283
|
+
let entryShadowPipeline = shadowPipeline;
|
|
1284
|
+
if (
|
|
1285
|
+
entryShadowShaderId !== undefined &&
|
|
1286
|
+
entryShadowShaderId !== 'forgeax::default-shadow-caster'
|
|
1287
|
+
) {
|
|
1288
|
+
// Custom ShadowCaster PSO; same cache path as the default above
|
|
1289
|
+
// (passKind 'shadow-caster'). On a cache miss (async build in
|
|
1290
|
+
// flight / build failure) fall back to the default PSO so the
|
|
1291
|
+
// caster still writes depth rather than dropping its draw.
|
|
1292
|
+
entryShadowPipeline =
|
|
1293
|
+
runtime.getMaterialShaderPipeline?.(
|
|
1294
|
+
entryShadowShaderId,
|
|
1295
|
+
false, // isHdr — shadow depth pass is always LDR
|
|
1296
|
+
undefined, // renderState
|
|
1297
|
+
'triangle-list', // topology — shadow PSO targets triangle-list
|
|
1298
|
+
undefined, // indexFormat
|
|
1299
|
+
undefined, // variantSet — shadow caster has no variant axes
|
|
1300
|
+
'shadow-caster', // passKind
|
|
1301
|
+
) ?? shadowPipeline;
|
|
1302
|
+
}
|
|
1303
|
+
if (entryShadowPipeline !== shadowLastPipeline && entryShadowPipeline !== null) {
|
|
1304
|
+
shadowPass.setPipeline(entryShadowPipeline);
|
|
1305
|
+
shadowLastPipeline = entryShadowPipeline;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// feat-20260604-mesh-topology-debug-draw M5 / w14 (AC-09, D-A6): the
|
|
1309
|
+
// shadow caster PSO is triangle-list; it only projects triangle faces.
|
|
1310
|
+
// line-list / line-strip / point-list meshes have no surface to cast a
|
|
1311
|
+
// shadow, so skip them here. triangle-strip is still a face topology
|
|
1312
|
+
// and projects (the shadow PSO's fixed triangle-list rasterizes its
|
|
1313
|
+
// expanded triangles correctly enough for the depth pass).
|
|
1314
|
+
//
|
|
1315
|
+
// feat-20260608 M4 / w16: per-submesh shadow draw — iterate submeshes
|
|
1316
|
+
// and skip non-triangle submeshes individually (each submesh may differ).
|
|
1317
|
+
const shadowSubmeshes = entry.mesh.submeshes;
|
|
1318
|
+
const hasAnyShadowSubmesh = shadowSubmeshes.some(
|
|
1319
|
+
(sm) => sm.topology === 'triangle-list' || sm.topology === 'triangle-strip',
|
|
1320
|
+
);
|
|
1321
|
+
if (!hasAnyShadowSubmesh) {
|
|
1322
|
+
if (observation !== undefined) {
|
|
1323
|
+
emitBudgetedShadowMarker(shadowPass, observation, {
|
|
1324
|
+
...candidateIdentity,
|
|
1325
|
+
kind: 'candidate',
|
|
1326
|
+
stage: 'draw-not-submitted',
|
|
1327
|
+
reason: 'non-triangle-topology',
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
if (entry.mesh.vertexBuffer !== shadowLastVertexBuffer) {
|
|
1334
|
+
shadowPass.setVertexBuffer(0, entry.mesh.vertexBuffer.handle);
|
|
1335
|
+
shadowLastVertexBuffer = entry.mesh.vertexBuffer;
|
|
1336
|
+
}
|
|
1337
|
+
if (entry.mesh.indexed && entry.mesh.indexBuffer !== shadowLastIndexBuffer) {
|
|
1338
|
+
// indexed=true implies indexBuffer is non-null GpuBuffer.
|
|
1339
|
+
if (entry.mesh.indexBuffer !== null) {
|
|
1340
|
+
shadowPass.setIndexBuffer(entry.mesh.indexBuffer.handle, entry.mesh.indexFormat);
|
|
1341
|
+
shadowLastIndexBuffer = entry.mesh.indexBuffer;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
const validatedIndex =
|
|
1346
|
+
selectedEntries === undefined
|
|
1347
|
+
? i
|
|
1348
|
+
: (c.validatedIndexByCandidateIndex?.get(candidateIndex) ?? i);
|
|
1349
|
+
shadowPass.setBindGroup(2, shadowMeshBindGroup, [validatedIndex * MESH_PER_ENTITY_STRIDE]);
|
|
1350
|
+
|
|
1351
|
+
// C1 + C2 (w12): per-entity instance buffer + instanceCount
|
|
1352
|
+
let shadowInstanceBuffer: Buffer = pipelineState.identityInstanceBuffer;
|
|
1353
|
+
let shadowInstanceCount = 1;
|
|
1354
|
+
const shadowInst = entry.source.instances;
|
|
1355
|
+
if (shadowInst !== undefined) {
|
|
1356
|
+
const uniformFallback = runtime.device.caps.storageBuffer === false;
|
|
1357
|
+
// Over-cap uniform fallback can't fit the per-instance window — bind
|
|
1358
|
+
// identity and let the shader collapse (same semantics as the main
|
|
1359
|
+
// pass). Otherwise build/upload the per-entity instance buffer: storage
|
|
1360
|
+
// by default, uniform when the device lacks storage buffers.
|
|
1361
|
+
if (uniformFallback && shadowInst.instanceCount > MAX_UNIFORM_INSTANCES) {
|
|
1362
|
+
shadowInstanceCount = shadowInst.instanceCount;
|
|
1363
|
+
shadowInstanceBuffer = pipelineState.identityInstanceBuffer;
|
|
1364
|
+
} else {
|
|
1365
|
+
const bufUsage = uniformFallback
|
|
1366
|
+
? GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST
|
|
1367
|
+
: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST;
|
|
1368
|
+
const upload = uniformFallback
|
|
1369
|
+
? shadowInst.transforms
|
|
1370
|
+
: packTemporalInstanceTransforms(
|
|
1371
|
+
shadowInst.transforms,
|
|
1372
|
+
entry.source.temporal?.previousInstances?.transforms,
|
|
1373
|
+
);
|
|
1374
|
+
const requestedBytes = upload.byteLength;
|
|
1375
|
+
const cached = c.frameState.instanceBuffers.get(
|
|
1376
|
+
worldEntityKey(entry.source.worldId, shadowInst.cacheKey),
|
|
1377
|
+
);
|
|
1378
|
+
let active: InstanceBufferCacheEntry | null = null;
|
|
1379
|
+
if (
|
|
1380
|
+
cached !== undefined &&
|
|
1381
|
+
cached.uploadedArchVersion === shadowInst.archVersion &&
|
|
1382
|
+
cached.uploadedByteLength === requestedBytes
|
|
1383
|
+
) {
|
|
1384
|
+
active = cached;
|
|
1385
|
+
} else if (requestedBytes > 0) {
|
|
1386
|
+
const bufRes = runtime.device.createBuffer({
|
|
1387
|
+
size: requestedBytes,
|
|
1388
|
+
usage: bufUsage,
|
|
1389
|
+
mappedAtCreation: false,
|
|
1390
|
+
});
|
|
1391
|
+
if (!bufRes.ok) {
|
|
1392
|
+
runtime.errorRegistry.fire(bufRes.error);
|
|
1393
|
+
} else {
|
|
1394
|
+
// feat-20260619 M4 / F12: destroy the old cached buffer
|
|
1395
|
+
// before replacing it with the new one (D-6).
|
|
1396
|
+
if (cached !== undefined && !cached.buffer.isDestroyed) {
|
|
1397
|
+
const r = cached.buffer.destroy();
|
|
1398
|
+
if (!r.ok) runtime.errorRegistry.fire(r.error);
|
|
1399
|
+
}
|
|
1400
|
+
const newBuffer = new GpuBuffer(runtime.device, bufRes.value);
|
|
1401
|
+
active = {
|
|
1402
|
+
buffer: newBuffer,
|
|
1403
|
+
uploadedArchVersion: shadowInst.archVersion,
|
|
1404
|
+
uploadedByteLength: requestedBytes,
|
|
1405
|
+
};
|
|
1406
|
+
c.frameState.instanceBuffers.set(
|
|
1407
|
+
worldEntityKey(entry.source.worldId, shadowInst.cacheKey),
|
|
1408
|
+
active,
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (active !== null) {
|
|
1413
|
+
const writeRes = runtime.device.queue.writeBuffer(active.buffer.handle, 0, upload);
|
|
1414
|
+
if (!writeRes.ok) {
|
|
1415
|
+
runtime.errorRegistry.fire(writeRes.error);
|
|
1416
|
+
} else {
|
|
1417
|
+
shadowInstanceBuffer = active.buffer.handle;
|
|
1418
|
+
shadowInstanceCount = Math.max(1, shadowInst.instanceCount);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// Bind per-entity instances BG for @group(3) (or fallback identity).
|
|
1425
|
+
// D-4: write end of the HDRP shadow-instances producer/consumer pair.
|
|
1426
|
+
// outerKey = worldEntityKey(entry.source.worldId, entry.source.entityKey),
|
|
1427
|
+
// handle = shadowInstanceBuffer; the HDRP main pass read end (:3820 below)
|
|
1428
|
+
// must look up the same (compositeKey, instBuffer) leaf or the shadow
|
|
1429
|
+
// instances silently drop.
|
|
1430
|
+
const shadowInstancesBg = getOrCreatePerEntity(
|
|
1431
|
+
c.frameState.instancesBgPerEntity,
|
|
1432
|
+
worldEntityKey(entry.source.worldId, entry.source.entityKey),
|
|
1433
|
+
[shadowInstanceBuffer],
|
|
1434
|
+
'shadow-instances',
|
|
1435
|
+
() => {
|
|
1436
|
+
const result = runtime.device.createBindGroup({
|
|
1437
|
+
label: 'shadow-instances-bg',
|
|
1438
|
+
layout: pipelineState.instancesBindGroupLayout,
|
|
1439
|
+
entries: [
|
|
1440
|
+
{
|
|
1441
|
+
binding: 0,
|
|
1442
|
+
resource: {
|
|
1443
|
+
kind: 'buffer',
|
|
1444
|
+
value: { buffer: shadowInstanceBuffer },
|
|
1445
|
+
},
|
|
1446
|
+
},
|
|
1447
|
+
],
|
|
1448
|
+
});
|
|
1449
|
+
if (!result.ok) throw result.error;
|
|
1450
|
+
return result.value;
|
|
1451
|
+
},
|
|
1452
|
+
c.bindGroupCounts,
|
|
1453
|
+
);
|
|
1454
|
+
|
|
1455
|
+
shadowPass.setBindGroup(3, shadowInstancesBg);
|
|
1456
|
+
// feat-20260608 M4 / w16: per-submesh shadow draw loop.
|
|
1457
|
+
// Only draw submeshes whose topology is triangle-list or triangle-strip
|
|
1458
|
+
// (line-list / point-list submeshes cast no shadow and are skipped).
|
|
1459
|
+
for (let submeshIndex = 0; submeshIndex < shadowSubmeshes.length; submeshIndex++) {
|
|
1460
|
+
const sm = shadowSubmeshes[submeshIndex];
|
|
1461
|
+
if (sm === undefined) continue;
|
|
1462
|
+
if (sm.topology !== 'triangle-list' && sm.topology !== 'triangle-strip') {
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
if (entry.mesh.indexed) {
|
|
1466
|
+
shadowPass.drawIndexed(sm.indexCount, shadowInstanceCount, sm.indexOffset, 0, 0);
|
|
1467
|
+
if (observation !== undefined) submittedDrawCount += 1;
|
|
1468
|
+
if (observation !== undefined) {
|
|
1469
|
+
emitBudgetedShadowMarker(shadowPass, observation, {
|
|
1470
|
+
...candidateIdentity,
|
|
1471
|
+
kind: 'submitted-draw',
|
|
1472
|
+
stage: 'submitted-draw',
|
|
1473
|
+
source: 'record-after-draw-call',
|
|
1474
|
+
submeshIndex,
|
|
1475
|
+
drawKind: 'drawIndexed',
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
} else {
|
|
1479
|
+
shadowPass.draw(sm.vertexCount, shadowInstanceCount, 0, 0);
|
|
1480
|
+
if (observation !== undefined) submittedDrawCount += 1;
|
|
1481
|
+
if (observation !== undefined) {
|
|
1482
|
+
emitBudgetedShadowMarker(shadowPass, observation, {
|
|
1483
|
+
...candidateIdentity,
|
|
1484
|
+
kind: 'submitted-draw',
|
|
1485
|
+
stage: 'submitted-draw',
|
|
1486
|
+
source: 'record-after-draw-call',
|
|
1487
|
+
submeshIndex,
|
|
1488
|
+
drawKind: 'draw',
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
if (observation !== undefined) {
|
|
1495
|
+
shadowPass.insertDebugMarker(
|
|
1496
|
+
shadowMarker({
|
|
1497
|
+
kind: 'batch',
|
|
1498
|
+
stage: 'multi-view-plan-vs-submitted',
|
|
1499
|
+
source: 'batch-topology',
|
|
1500
|
+
frame: observation.frame,
|
|
1501
|
+
cascade: observation.cascade,
|
|
1502
|
+
plannedDrawCount: productionSubmission?.plan.candidateCount ?? 0,
|
|
1503
|
+
submittedDrawCount,
|
|
1504
|
+
batchCount: productionSubmission?.plan.batches.length ?? 0,
|
|
1505
|
+
aligned: (productionSubmission?.plan.candidateCount ?? 0) === submittedDrawCount,
|
|
1506
|
+
}),
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
if (observation !== undefined && productionCandidateIndices !== undefined) {
|
|
1510
|
+
for (const candidateIndex of productionCandidateIndices) {
|
|
1511
|
+
if (seenValidatedCandidateIndices?.has(candidateIndex)) continue;
|
|
1512
|
+
emitBudgetedShadowMarker(shadowPass, observation, {
|
|
1513
|
+
kind: 'candidate',
|
|
1514
|
+
stage: 'validation-missing',
|
|
1515
|
+
source: 'multi-view-indirect',
|
|
1516
|
+
frame: observation.frame,
|
|
1517
|
+
cascade: observation.cascade,
|
|
1518
|
+
stableId: observation.stableIdByCandidateIndex.get(candidateIndex),
|
|
1519
|
+
candidateIndex,
|
|
1520
|
+
selected: true,
|
|
1521
|
+
reason: 'submitted-candidate-not-materialized',
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* feat-20260612-point-light-shadows-urp-hdrp M3 / T-M3-2 (plan-strategy §D-1
|
|
1529
|
+
* + §D-3 + AC-04). Records the 6 x N point-shadow caster passes — one render
|
|
1530
|
+
* pass per (shadow-casting point light, cube face) pair — that write per-light
|
|
1531
|
+
* cube_array atlas depth. Driven by the URP `addPointShadowPass` graph closure;
|
|
1532
|
+
* gated upstream on `frameState.pointShadowSnapshots.length > 0` (AC-09 zero-
|
|
1533
|
+
* shadow zero-pass).
|
|
1534
|
+
*
|
|
1535
|
+
* Pass count = 6 * N where N = `frameState.pointShadowSnapshots.length`. Each
|
|
1536
|
+
* pass opens an INDEPENDENT command encoder + queue.submit so Dawn / WebGPU
|
|
1537
|
+
* synchronizes the depth-write boundary with the subsequent atlas sample in
|
|
1538
|
+
* the forward pass (RD-4 manual barrier; same pattern as `recordShadowPass`).
|
|
1539
|
+
*
|
|
1540
|
+
* Round-2 F-3 fix-up: actual geometry walk + draw landed. Per face we
|
|
1541
|
+
* (1) write the face VP mat4 into `viewUniformBuffer` at offset 112 (the
|
|
1542
|
+
* `lightSpaceMatrix` slot) — `forgeax::default-shadow-caster` reads
|
|
1543
|
+
* `view.lightSpaceMatrix * worldPos` and the encoder boundary serializes
|
|
1544
|
+
* the queue.writeBuffer with the subsequent draw, so each face sees its own
|
|
1545
|
+
* VP without needing a per-face UBO bind. (2) Reuse the directional shadow
|
|
1546
|
+
* caster PSO + cached `shadow-view-bg` / `shadow-material-singleton` /
|
|
1547
|
+
* `shadow-mesh-bg` already built by `recordShadowPass`. (3) After all
|
|
1548
|
+
* (snapshot, face) passes complete, restore `viewUniformBuffer.lightSpaceMatrix`
|
|
1549
|
+
* to the directional value the main forward pass needs (the queue.submit
|
|
1550
|
+
* ordering guarantees the restore lands before the main encoder runs).
|
|
1551
|
+
*
|
|
1552
|
+
* Caveat: the cached `view-shadow` BG binds `shadowAtlasFallbackTextureView`
|
|
1553
|
+
* at binding 5 (NOT the real ShadowAtlas atlas view) so the BG is valid even
|
|
1554
|
+
* while the real atlas faces are being written through the depth attachment;
|
|
1555
|
+
* the shadow_caster.wgsl shader never samples binding 5 anyway (vertex-only
|
|
1556
|
+
* pipeline).
|
|
1557
|
+
*
|
|
1558
|
+
* No new shader / no new PSO / no new BGL is required for this round; the
|
|
1559
|
+
* change is purely runtime wiring. A dedicated `forgeax::default-point-
|
|
1560
|
+
* shadow-caster` PSO with a per-face VP UBO + dynamic offset would let the
|
|
1561
|
+
* pass run without touching `viewUniformBuffer.lightSpaceMatrix` and could
|
|
1562
|
+
* be considered for a follow-on optimization milestone (`OOS-future`).
|
|
1563
|
+
*/
|
|
1564
|
+
export function recordPointShadowPass(c: _InternalRenderPipelineContext): void {
|
|
1565
|
+
const { runtime, frameState, meshBindGroup, pipelineState } = c;
|
|
1566
|
+
const mainMembership = mainShadowMembership(c);
|
|
1567
|
+
const snapshots = frameState.pointShadowSnapshots;
|
|
1568
|
+
if (snapshots.length === 0) return;
|
|
1569
|
+
const atlas = frameState.pointShadowAtlas;
|
|
1570
|
+
if (atlas === null || !atlas.isAllocated()) return;
|
|
1571
|
+
|
|
1572
|
+
// Reuse the directional shadow caster PSO -- its WGSL reads
|
|
1573
|
+
// `view.lightSpaceMatrix * worldPos` which is exactly what we need once we
|
|
1574
|
+
// overwrite the slot per face below.
|
|
1575
|
+
const shadowPipeline =
|
|
1576
|
+
runtime.getMaterialShaderPipeline?.(
|
|
1577
|
+
'forgeax::default-shadow-caster',
|
|
1578
|
+
false,
|
|
1579
|
+
undefined,
|
|
1580
|
+
'triangle-list',
|
|
1581
|
+
undefined,
|
|
1582
|
+
undefined,
|
|
1583
|
+
'shadow-caster',
|
|
1584
|
+
) ?? null;
|
|
1585
|
+
if (shadowPipeline === null) return;
|
|
1586
|
+
|
|
1587
|
+
// Snapshot the directional lightSpaceMatrix so we can restore it after the
|
|
1588
|
+
// 6 x N face passes. recordShadowPass already wrote `viewUniformBuffer` at
|
|
1589
|
+
// offset 112 above; mirror its source in `pipelineState.perPassResources`
|
|
1590
|
+
// (cached by the directional path for Inspector consumption -- charter P4
|
|
1591
|
+
// single SSOT). When no directional shadow is active the slot was zeroed
|
|
1592
|
+
// by the viewPayload write at recordFrame top; we restore zeros.
|
|
1593
|
+
const restoreLightSpaceMatrix = pipelineState.perPassResources.shadowLightSpaceMatrix;
|
|
1594
|
+
const RESTORE_LSM_BYTES = 64; // mat4 = 16 floats x 4 B
|
|
1595
|
+
const VIEW_UBO_LSM_OFFSET = 112;
|
|
1596
|
+
|
|
1597
|
+
for (let i = 0; i < snapshots.length; i++) {
|
|
1598
|
+
const snap = snapshots[i];
|
|
1599
|
+
if (snap === undefined) continue;
|
|
1600
|
+
for (let face = 0; face < 6; face++) {
|
|
1601
|
+
// (1) Overwrite viewUniformBuffer.lightSpaceMatrix with this face's VP.
|
|
1602
|
+
// shadowMatrices is Float32Array(96) = 6 mat4 in [+X,-X,+Y,-Y,+Z,-Z];
|
|
1603
|
+
// pull the 16-float subarray for this face.
|
|
1604
|
+
const faceVp = snap.shadowMatrices.subarray(face * 16, (face + 1) * 16);
|
|
1605
|
+
const lsmWriteRes = runtime.device.queue.writeBuffer(
|
|
1606
|
+
pipelineState.viewUniformBuffer,
|
|
1607
|
+
VIEW_UBO_LSM_OFFSET,
|
|
1608
|
+
faceVp,
|
|
1609
|
+
);
|
|
1610
|
+
if (!lsmWriteRes.ok) {
|
|
1611
|
+
runtime.errorRegistry.fire(lsmWriteRes.error);
|
|
1612
|
+
continue;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
const encResult = runtime.device.createCommandEncoder({
|
|
1616
|
+
label: `point-shadow-l${snap.shadowAtlasLayer}-f${face}`,
|
|
1617
|
+
});
|
|
1618
|
+
if (!encResult.ok) {
|
|
1619
|
+
runtime.errorRegistry.fire(encResult.error);
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
const enc = encResult.value;
|
|
1623
|
+
let view: TextureView;
|
|
1624
|
+
try {
|
|
1625
|
+
view = atlas.faceView(snap.shadowAtlasLayer, face);
|
|
1626
|
+
} catch (e) {
|
|
1627
|
+
if (
|
|
1628
|
+
e instanceof PointShadowAtlasUninitializedError ||
|
|
1629
|
+
e instanceof PointShadowAtlasBoundsViolationError
|
|
1630
|
+
) {
|
|
1631
|
+
runtime.errorRegistry.fire(e);
|
|
1632
|
+
} else {
|
|
1633
|
+
throw e;
|
|
1634
|
+
}
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
const pass = enc.beginRenderPass(
|
|
1638
|
+
buildBeginRenderPassDescriptor(
|
|
1639
|
+
{ colorFormats: [], depthFormat: 'depth32float', sampleCount: 1 },
|
|
1640
|
+
{ colorViews: [], depthView: view },
|
|
1641
|
+
'point-shadow-caster',
|
|
1642
|
+
) as never,
|
|
1643
|
+
);
|
|
1644
|
+
|
|
1645
|
+
// (2) Bind the same shadow PSO + view BG + material BG + mesh BG cached
|
|
1646
|
+
// by recordShadowPass earlier in this frame. The shadow path's view BG
|
|
1647
|
+
// binds the cube_array fallback at binding 5, so this BG is safe to
|
|
1648
|
+
// use while the real atlas faces are render-attached here.
|
|
1649
|
+
pass.setPipeline(shadowPipeline);
|
|
1650
|
+
// Look up the shadow view BG built earlier this frame by recordShadowPass.
|
|
1651
|
+
// `view-shadow` is the cache variant; keyed by the same handle objects.
|
|
1652
|
+
// If the directional shadow path wasn't taken (castShadow:false),
|
|
1653
|
+
// shadow-view-bg was never built -- skip the geometry walk in that case
|
|
1654
|
+
// (the depth attachment was still cleared above which is the AC-04
|
|
1655
|
+
// "atlas face cleared to far" minimum guarantee).
|
|
1656
|
+
// Build (or reuse) the shadow-view BG. If the directional shadow path
|
|
1657
|
+
// already populated it earlier this frame, the cache hits; otherwise
|
|
1658
|
+
// (castShadow:false -- recordShadowPass never
|
|
1659
|
+
// ran) we build it on-demand here so the point shadow caster has a
|
|
1660
|
+
// valid b0 view BG even on directional-shadow-free scenes.
|
|
1661
|
+
const cachedShadowViewBg = getOrCreateFromChain(
|
|
1662
|
+
frameState.viewBindGroupCache,
|
|
1663
|
+
[
|
|
1664
|
+
pipelineState.viewUniformBuffer,
|
|
1665
|
+
pipelineState.pointLightsBuffer,
|
|
1666
|
+
pipelineState.spotLightsBuffer,
|
|
1667
|
+
pipelineState.shadowFallbackTextureView,
|
|
1668
|
+
pipelineState.perPassResources.shadowSampler,
|
|
1669
|
+
pipelineState.shadowAtlasFallbackTextureView,
|
|
1670
|
+
pipelineState.shadowParamsBuffer,
|
|
1671
|
+
pipelineState.shadowCasterCascadeBuffer,
|
|
1672
|
+
pipelineState.shadowFallbackTextureView,
|
|
1673
|
+
],
|
|
1674
|
+
'view-shadow',
|
|
1675
|
+
() => {
|
|
1676
|
+
const r = runtime.device.createBindGroup({
|
|
1677
|
+
label: 'shadow-view-bg',
|
|
1678
|
+
layout: pipelineState.viewBindGroupLayout,
|
|
1679
|
+
entries: [
|
|
1680
|
+
{
|
|
1681
|
+
binding: 0,
|
|
1682
|
+
resource: { kind: 'buffer', value: { buffer: pipelineState.viewUniformBuffer } },
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
binding: 1,
|
|
1686
|
+
resource: { kind: 'buffer', value: { buffer: pipelineState.pointLightsBuffer } },
|
|
1687
|
+
},
|
|
1688
|
+
{
|
|
1689
|
+
binding: 2,
|
|
1690
|
+
resource: { kind: 'buffer', value: { buffer: pipelineState.spotLightsBuffer } },
|
|
1691
|
+
},
|
|
1692
|
+
{
|
|
1693
|
+
binding: 3,
|
|
1694
|
+
resource: { kind: 'textureView', value: pipelineState.shadowFallbackTextureView },
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
binding: 4,
|
|
1698
|
+
resource: { kind: 'sampler', value: pipelineState.perPassResources.shadowSampler },
|
|
1699
|
+
},
|
|
1700
|
+
{
|
|
1701
|
+
binding: 5,
|
|
1702
|
+
resource: {
|
|
1703
|
+
kind: 'textureView',
|
|
1704
|
+
value: pipelineState.shadowAtlasFallbackTextureView,
|
|
1705
|
+
},
|
|
1706
|
+
},
|
|
1707
|
+
{
|
|
1708
|
+
binding: 6,
|
|
1709
|
+
resource: { kind: 'buffer', value: { buffer: pipelineState.shadowParamsBuffer } },
|
|
1710
|
+
},
|
|
1711
|
+
// feat-20260625-spot-light-shadow-mapping M2 / w21 + w25: this
|
|
1712
|
+
// on-demand shadow-view BG builder previously stopped at binding 6,
|
|
1713
|
+
// predating the cascade UBO (binding 7) and the spot binding 8. It
|
|
1714
|
+
// shares the 'view-shadow' cache variant with the directional
|
|
1715
|
+
// shadow-pass builder above, so it must produce a BGL-identical bind
|
|
1716
|
+
// group; bindings 7/8 are added here in lock-step (BGL declares
|
|
1717
|
+
// 0..8 — the former binding 9 spot lightViewProj UBO folded into the
|
|
1718
|
+
// View UBO in w25 for the WebGL2 fragment uniform-buffer budget).
|
|
1719
|
+
{
|
|
1720
|
+
binding: 7,
|
|
1721
|
+
resource: {
|
|
1722
|
+
kind: 'buffer',
|
|
1723
|
+
value: { buffer: pipelineState.shadowCasterCascadeBuffer },
|
|
1724
|
+
},
|
|
1725
|
+
},
|
|
1726
|
+
{
|
|
1727
|
+
binding: 8,
|
|
1728
|
+
resource: {
|
|
1729
|
+
kind: 'textureView',
|
|
1730
|
+
value: pipelineState.shadowFallbackTextureView,
|
|
1731
|
+
},
|
|
1732
|
+
},
|
|
1733
|
+
],
|
|
1734
|
+
});
|
|
1735
|
+
if (!r.ok) throw r.error;
|
|
1736
|
+
return r.value;
|
|
1737
|
+
},
|
|
1738
|
+
c.bindGroupCounts,
|
|
1739
|
+
);
|
|
1740
|
+
// Same on-demand build for the dummy material BG (the shadow_caster
|
|
1741
|
+
// shader does not consume @group(1) but the PSO requires the BGL
|
|
1742
|
+
// to validate). Reuse the same singleton Map entry recordShadowPass
|
|
1743
|
+
// writes so the two paths share one allocation per frame (D-6).
|
|
1744
|
+
let cachedShadowMaterialBg = frameState.singletonMaterialCache.get(
|
|
1745
|
+
'shadow-material-singleton',
|
|
1746
|
+
);
|
|
1747
|
+
if (cachedShadowMaterialBg === undefined) {
|
|
1748
|
+
const buildShadowMaterialSingleton = (): BindGroup => {
|
|
1749
|
+
const fb = pipelineState.skylightFallback;
|
|
1750
|
+
const fallbackEntries = [
|
|
1751
|
+
{
|
|
1752
|
+
binding: 0,
|
|
1753
|
+
resource: {
|
|
1754
|
+
kind: 'buffer' as const,
|
|
1755
|
+
value: {
|
|
1756
|
+
buffer: pipelineState.materialUniformBuffer.buffer,
|
|
1757
|
+
offset: 0,
|
|
1758
|
+
size: MATERIAL_PER_ENTITY_STRIDE,
|
|
1759
|
+
},
|
|
1760
|
+
},
|
|
1761
|
+
},
|
|
1762
|
+
{
|
|
1763
|
+
binding: 1,
|
|
1764
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
binding: 2,
|
|
1768
|
+
resource: { kind: 'textureView' as const, value: pipelineState.fallbackTextureView },
|
|
1769
|
+
},
|
|
1770
|
+
{
|
|
1771
|
+
binding: 3,
|
|
1772
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
1773
|
+
},
|
|
1774
|
+
{
|
|
1775
|
+
binding: 4,
|
|
1776
|
+
resource: {
|
|
1777
|
+
kind: 'textureView' as const,
|
|
1778
|
+
value: pipelineState.defaultNormalTextureView,
|
|
1779
|
+
},
|
|
1780
|
+
},
|
|
1781
|
+
{
|
|
1782
|
+
binding: 5,
|
|
1783
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
1784
|
+
},
|
|
1785
|
+
{
|
|
1786
|
+
binding: 6,
|
|
1787
|
+
resource: { kind: 'textureView' as const, value: pipelineState.fallbackTextureView },
|
|
1788
|
+
},
|
|
1789
|
+
{
|
|
1790
|
+
binding: 7,
|
|
1791
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
binding: 8,
|
|
1795
|
+
resource: {
|
|
1796
|
+
kind: 'textureView' as const,
|
|
1797
|
+
value: pipelineState.defaultWhiteTextureView,
|
|
1798
|
+
},
|
|
1799
|
+
},
|
|
1800
|
+
{
|
|
1801
|
+
binding: 9,
|
|
1802
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
1803
|
+
},
|
|
1804
|
+
{
|
|
1805
|
+
binding: 10,
|
|
1806
|
+
resource: {
|
|
1807
|
+
kind: 'textureView' as const,
|
|
1808
|
+
value: pipelineState.defaultWhiteTextureView,
|
|
1809
|
+
},
|
|
1810
|
+
},
|
|
1811
|
+
{
|
|
1812
|
+
binding: 11,
|
|
1813
|
+
resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler },
|
|
1814
|
+
},
|
|
1815
|
+
{
|
|
1816
|
+
binding: 12,
|
|
1817
|
+
resource: {
|
|
1818
|
+
kind: 'textureView' as const,
|
|
1819
|
+
value: pipelineState.defaultWhiteTextureView,
|
|
1820
|
+
},
|
|
1821
|
+
},
|
|
1822
|
+
];
|
|
1823
|
+
const merged =
|
|
1824
|
+
fb !== null
|
|
1825
|
+
? assembleMaterialWithSkylightEntries(fallbackEntries, {
|
|
1826
|
+
irradianceView: fb.irradianceView,
|
|
1827
|
+
irradianceSampler: fb.sampler,
|
|
1828
|
+
prefilterView: fb.prefilterView,
|
|
1829
|
+
prefilterSampler: fb.sampler,
|
|
1830
|
+
brdfLutView: fb.brdfLutView,
|
|
1831
|
+
brdfLutSampler: fb.sampler,
|
|
1832
|
+
intensityBuffer: fb.intensityBuffer,
|
|
1833
|
+
})
|
|
1834
|
+
: fallbackEntries;
|
|
1835
|
+
const r = runtime.device.createBindGroup({
|
|
1836
|
+
label: 'shadow-material-bg',
|
|
1837
|
+
layout: pipelineState.materialBindGroupLayout,
|
|
1838
|
+
entries: merged,
|
|
1839
|
+
});
|
|
1840
|
+
if (!r.ok) throw r.error;
|
|
1841
|
+
return r.value;
|
|
1842
|
+
};
|
|
1843
|
+
cachedShadowMaterialBg = buildShadowMaterialSingleton();
|
|
1844
|
+
c.bindGroupCounts.createBindGroup += 1;
|
|
1845
|
+
c.bindGroupCounts.keys.push('shadow-material-singleton');
|
|
1846
|
+
frameState.singletonMaterialCache.set('shadow-material-singleton', cachedShadowMaterialBg);
|
|
1847
|
+
}
|
|
1848
|
+
if (meshBindGroup === null) {
|
|
1849
|
+
pass.end();
|
|
1850
|
+
const finishOnly = enc.finish();
|
|
1851
|
+
if (!finishOnly.ok) {
|
|
1852
|
+
runtime.errorRegistry.fire(finishOnly.error);
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
const submitOnly = runtime.device.queue.submit([finishOnly.value]);
|
|
1856
|
+
if (!submitOnly.ok) {
|
|
1857
|
+
runtime.errorRegistry.fire(submitOnly.error);
|
|
1858
|
+
}
|
|
1859
|
+
continue;
|
|
1860
|
+
}
|
|
1861
|
+
pass.setBindGroup(0, cachedShadowViewBg);
|
|
1862
|
+
pass.setBindGroup(1, cachedShadowMaterialBg, [0]);
|
|
1863
|
+
|
|
1864
|
+
// (3) Iterate the mesh-SSBO slot order and emit one drawIndexed per
|
|
1865
|
+
// shadow-casting submesh. Same shape as the directional shadow loop;
|
|
1866
|
+
// the per-instance instance buffers built earlier this frame in
|
|
1867
|
+
// recordShadowPass are reused (cached on frameState.instanceBuffers).
|
|
1868
|
+
let lastVB: GpuBuffer | null = null;
|
|
1869
|
+
let lastIB: GpuBuffer | null = null;
|
|
1870
|
+
const shadowEntries =
|
|
1871
|
+
c.validatedShadowEntries ??
|
|
1872
|
+
c.validatedOrdered.flatMap((entry, validatedIndex) =>
|
|
1873
|
+
isMainShadowRenderable(entry, mainMembership) ? [{ entry, validatedIndex }] : [],
|
|
1874
|
+
);
|
|
1875
|
+
for (const { entry, validatedIndex: ei } of shadowEntries) {
|
|
1876
|
+
const submeshes = entry.mesh.submeshes;
|
|
1877
|
+
const hasTriangle = submeshes.some(
|
|
1878
|
+
(sm) => sm.topology === 'triangle-list' || sm.topology === 'triangle-strip',
|
|
1879
|
+
);
|
|
1880
|
+
if (!hasTriangle) continue;
|
|
1881
|
+
if (entry.mesh.vertexBuffer !== lastVB) {
|
|
1882
|
+
pass.setVertexBuffer(0, entry.mesh.vertexBuffer.handle);
|
|
1883
|
+
lastVB = entry.mesh.vertexBuffer;
|
|
1884
|
+
}
|
|
1885
|
+
if (entry.mesh.indexed && entry.mesh.indexBuffer !== lastIB && entry.mesh.indexBuffer) {
|
|
1886
|
+
pass.setIndexBuffer(entry.mesh.indexBuffer.handle, entry.mesh.indexFormat);
|
|
1887
|
+
lastIB = entry.mesh.indexBuffer;
|
|
1888
|
+
}
|
|
1889
|
+
pass.setBindGroup(2, meshBindGroup, [ei * MESH_PER_ENTITY_STRIDE]);
|
|
1890
|
+
// Resolve the per-entity instance BG cached by recordShadowPass.
|
|
1891
|
+
const inst = entry.source.instances;
|
|
1892
|
+
let instCount = 1;
|
|
1893
|
+
let instBufferKey: object = pipelineState.identityInstanceBuffer as unknown as object;
|
|
1894
|
+
if (inst !== undefined) {
|
|
1895
|
+
const cached = frameState.instanceBuffers.get(
|
|
1896
|
+
worldEntityKey(entry.source.worldId, inst.cacheKey),
|
|
1897
|
+
);
|
|
1898
|
+
if (
|
|
1899
|
+
cached !== undefined &&
|
|
1900
|
+
cached.uploadedArchVersion === inst.archVersion &&
|
|
1901
|
+
cached.uploadedByteLength ===
|
|
1902
|
+
temporalInstanceUploadByteLength(inst.transforms, runtime.device.caps.storageBuffer)
|
|
1903
|
+
) {
|
|
1904
|
+
instBufferKey = cached.buffer.handle as unknown as object;
|
|
1905
|
+
instCount = Math.max(1, inst.instanceCount);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
// D-4 read end: look up the leaf recordShadowPass wrote at :3439
|
|
1909
|
+
// with the SAME (entityKey, instBuffer) pair. The single-handle chain
|
|
1910
|
+
// stores the variant->BindGroup leaf Map directly under the buffer
|
|
1911
|
+
// handle in the inner WeakMap, so the variant lookup is the third
|
|
1912
|
+
// step. The inner WeakMap value is opaque (`unknown`); the 1-handle
|
|
1913
|
+
// shadow-instances chain guarantees it is the leaf Map here.
|
|
1914
|
+
const shadowInstLeaf = frameState.instancesBgPerEntity
|
|
1915
|
+
.get(worldEntityKey(entry.source.worldId, entry.source.entityKey))
|
|
1916
|
+
?.get(instBufferKey) as Map<string, BindGroup> | undefined;
|
|
1917
|
+
const cachedInstBg = shadowInstLeaf?.get('shadow-instances');
|
|
1918
|
+
if (cachedInstBg === undefined) continue; // recordShadowPass should have populated it
|
|
1919
|
+
pass.setBindGroup(3, cachedInstBg);
|
|
1920
|
+
for (const sm of submeshes) {
|
|
1921
|
+
if (sm.topology !== 'triangle-list' && sm.topology !== 'triangle-strip') continue;
|
|
1922
|
+
if (entry.mesh.indexed) {
|
|
1923
|
+
pass.drawIndexed(sm.indexCount, instCount, sm.indexOffset, 0, 0);
|
|
1924
|
+
} else {
|
|
1925
|
+
pass.draw(sm.vertexCount, instCount, 0, 0);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
pass.end();
|
|
1931
|
+
const finishResult = enc.finish();
|
|
1932
|
+
if (!finishResult.ok) {
|
|
1933
|
+
runtime.errorRegistry.fire(finishResult.error);
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
const submitResult = runtime.device.queue.submit([finishResult.value]);
|
|
1937
|
+
if (!submitResult.ok) {
|
|
1938
|
+
runtime.errorRegistry.fire(submitResult.error);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// (4) Restore viewUniformBuffer.lightSpaceMatrix to the directional value
|
|
1944
|
+
// (or zero if no directional shadow this frame). The main forward pass
|
|
1945
|
+
// builds its own viewBg from the SAME viewUniformBuffer + reads
|
|
1946
|
+
// view.lightSpaceMatrix for directional shadow factor reconstruction.
|
|
1947
|
+
const restoreBuf = new Float32Array(16);
|
|
1948
|
+
if (restoreLightSpaceMatrix !== null) {
|
|
1949
|
+
for (let i = 0; i < 16; i++) restoreBuf[i] = restoreLightSpaceMatrix[i] ?? 0;
|
|
1950
|
+
}
|
|
1951
|
+
// restoreBuf is already exactly 16 floats x 4 B = 64 B; no size override needed.
|
|
1952
|
+
void RESTORE_LSM_BYTES;
|
|
1953
|
+
const restoreRes = runtime.device.queue.writeBuffer(
|
|
1954
|
+
pipelineState.viewUniformBuffer,
|
|
1955
|
+
VIEW_UBO_LSM_OFFSET,
|
|
1956
|
+
restoreBuf,
|
|
1957
|
+
);
|
|
1958
|
+
if (!restoreRes.ok) {
|
|
1959
|
+
runtime.errorRegistry.fire(restoreRes.error);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
/**
|
|
1964
|
+
* Build (or reuse) the shared `view-shadow` @group(0) BG for the spot shadow
|
|
1965
|
+
* caster pass. Reuses the exact same cache key + entry shape as recordShadowPass
|
|
1966
|
+
* / recordPointShadowPass so all three shadow paths share one allocation per
|
|
1967
|
+
* frame (D-6). Returns null on a createBindGroup failure (fired to the error
|
|
1968
|
+
* registry). The shadow_caster vertex shader reads only binding 0 (view UBO,
|
|
1969
|
+
* carrying the spot matrix via shadowCasterCascade at binding 7) — bindings
|
|
1970
|
+
* 3/5 bind fallback views since the depth attachment is written here.
|
|
1971
|
+
*/
|
|
1972
|
+
function ensureSpotShadowViewBg(c: _InternalRenderPipelineContext): BindGroup | null {
|
|
1973
|
+
const { runtime, frameState, pipelineState } = c;
|
|
1974
|
+
try {
|
|
1975
|
+
return getOrCreateFromChain(
|
|
1976
|
+
frameState.viewBindGroupCache,
|
|
1977
|
+
[
|
|
1978
|
+
pipelineState.viewUniformBuffer,
|
|
1979
|
+
pipelineState.pointLightsBuffer,
|
|
1980
|
+
pipelineState.spotLightsBuffer,
|
|
1981
|
+
pipelineState.shadowFallbackTextureView,
|
|
1982
|
+
pipelineState.perPassResources.shadowSampler,
|
|
1983
|
+
pipelineState.shadowAtlasFallbackTextureView,
|
|
1984
|
+
pipelineState.shadowParamsBuffer,
|
|
1985
|
+
pipelineState.shadowCasterCascadeBuffer,
|
|
1986
|
+
pipelineState.shadowFallbackTextureView,
|
|
1987
|
+
],
|
|
1988
|
+
'view-shadow',
|
|
1989
|
+
() => {
|
|
1990
|
+
const r = runtime.device.createBindGroup({
|
|
1991
|
+
label: 'shadow-view-bg',
|
|
1992
|
+
layout: pipelineState.viewBindGroupLayout,
|
|
1993
|
+
entries: [
|
|
1994
|
+
{
|
|
1995
|
+
binding: 0,
|
|
1996
|
+
resource: { kind: 'buffer', value: { buffer: pipelineState.viewUniformBuffer } },
|
|
1997
|
+
},
|
|
1998
|
+
{
|
|
1999
|
+
binding: 1,
|
|
2000
|
+
resource: { kind: 'buffer', value: { buffer: pipelineState.pointLightsBuffer } },
|
|
2001
|
+
},
|
|
2002
|
+
{
|
|
2003
|
+
binding: 2,
|
|
2004
|
+
resource: { kind: 'buffer', value: { buffer: pipelineState.spotLightsBuffer } },
|
|
2005
|
+
},
|
|
2006
|
+
{
|
|
2007
|
+
binding: 3,
|
|
2008
|
+
resource: { kind: 'textureView', value: pipelineState.shadowFallbackTextureView },
|
|
2009
|
+
},
|
|
2010
|
+
{
|
|
2011
|
+
binding: 4,
|
|
2012
|
+
resource: { kind: 'sampler', value: pipelineState.perPassResources.shadowSampler },
|
|
2013
|
+
},
|
|
2014
|
+
{
|
|
2015
|
+
binding: 5,
|
|
2016
|
+
resource: {
|
|
2017
|
+
kind: 'textureView',
|
|
2018
|
+
value: pipelineState.shadowAtlasFallbackTextureView,
|
|
2019
|
+
},
|
|
2020
|
+
},
|
|
2021
|
+
{
|
|
2022
|
+
binding: 6,
|
|
2023
|
+
resource: { kind: 'buffer', value: { buffer: pipelineState.shadowParamsBuffer } },
|
|
2024
|
+
},
|
|
2025
|
+
{
|
|
2026
|
+
binding: 7,
|
|
2027
|
+
resource: {
|
|
2028
|
+
kind: 'buffer',
|
|
2029
|
+
value: { buffer: pipelineState.shadowCasterCascadeBuffer },
|
|
2030
|
+
},
|
|
2031
|
+
},
|
|
2032
|
+
// feat-20260625-spot-light-shadow-mapping M3 / w21 (D-5): spot
|
|
2033
|
+
// shadow 2D atlas. The spot caster pass write-attaches
|
|
2034
|
+
// spotShadowDepth, so bind the 1x1 fallback here (the caster shader
|
|
2035
|
+
// never samples binding 8 — this entry only satisfies the BGL).
|
|
2036
|
+
{
|
|
2037
|
+
binding: 8,
|
|
2038
|
+
resource: {
|
|
2039
|
+
kind: 'textureView',
|
|
2040
|
+
value: pipelineState.shadowFallbackTextureView,
|
|
2041
|
+
},
|
|
2042
|
+
},
|
|
2043
|
+
// feat-20260625-spot-light-shadow-mapping w25: spot lightViewProj
|
|
2044
|
+
// matrices folded into the View UBO (binding 0) — no binding 9.
|
|
2045
|
+
// binding 8 is the last shared view-BG entry.
|
|
2046
|
+
],
|
|
2047
|
+
});
|
|
2048
|
+
if (!r.ok) throw r.error;
|
|
2049
|
+
return r.value;
|
|
2050
|
+
},
|
|
2051
|
+
c.bindGroupCounts,
|
|
2052
|
+
);
|
|
2053
|
+
} catch (e) {
|
|
2054
|
+
if (e instanceof RhiError) {
|
|
2055
|
+
runtime.errorRegistry.fire(e);
|
|
2056
|
+
return null;
|
|
2057
|
+
}
|
|
2058
|
+
throw e;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
/**
|
|
2063
|
+
* Build (or reuse) the dummy `shadow-material-singleton` @group(1) BG for the
|
|
2064
|
+
* spot shadow caster pass. The vertex-only shadow_caster shader never consumes
|
|
2065
|
+
* @group(1) but the PSO's BGL must validate. Reuses the same singleton Map
|
|
2066
|
+
* entry recordShadowPass / recordPointShadowPass write so the three paths share
|
|
2067
|
+
* one allocation per frame (D-6).
|
|
2068
|
+
*/
|
|
2069
|
+
function ensureSpotShadowMaterialBg(c: _InternalRenderPipelineContext): BindGroup | null {
|
|
2070
|
+
const { runtime, frameState, pipelineState } = c;
|
|
2071
|
+
const cached = frameState.singletonMaterialCache.get('shadow-material-singleton');
|
|
2072
|
+
if (cached !== undefined) return cached;
|
|
2073
|
+
const fb = pipelineState.skylightFallback;
|
|
2074
|
+
const fallbackEntries = [
|
|
2075
|
+
{
|
|
2076
|
+
binding: 0,
|
|
2077
|
+
resource: {
|
|
2078
|
+
kind: 'buffer' as const,
|
|
2079
|
+
value: {
|
|
2080
|
+
buffer: pipelineState.materialUniformBuffer.buffer,
|
|
2081
|
+
offset: 0,
|
|
2082
|
+
size: MATERIAL_PER_ENTITY_STRIDE,
|
|
2083
|
+
},
|
|
2084
|
+
},
|
|
2085
|
+
},
|
|
2086
|
+
{ binding: 1, resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler } },
|
|
2087
|
+
{
|
|
2088
|
+
binding: 2,
|
|
2089
|
+
resource: { kind: 'textureView' as const, value: pipelineState.fallbackTextureView },
|
|
2090
|
+
},
|
|
2091
|
+
{ binding: 3, resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler } },
|
|
2092
|
+
{
|
|
2093
|
+
binding: 4,
|
|
2094
|
+
resource: { kind: 'textureView' as const, value: pipelineState.defaultNormalTextureView },
|
|
2095
|
+
},
|
|
2096
|
+
{ binding: 5, resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler } },
|
|
2097
|
+
{
|
|
2098
|
+
binding: 6,
|
|
2099
|
+
resource: { kind: 'textureView' as const, value: pipelineState.fallbackTextureView },
|
|
2100
|
+
},
|
|
2101
|
+
{ binding: 7, resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler } },
|
|
2102
|
+
{
|
|
2103
|
+
binding: 8,
|
|
2104
|
+
resource: { kind: 'textureView' as const, value: pipelineState.defaultWhiteTextureView },
|
|
2105
|
+
},
|
|
2106
|
+
{ binding: 9, resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler } },
|
|
2107
|
+
{
|
|
2108
|
+
binding: 10,
|
|
2109
|
+
resource: { kind: 'textureView' as const, value: pipelineState.defaultWhiteTextureView },
|
|
2110
|
+
},
|
|
2111
|
+
{ binding: 11, resource: { kind: 'sampler' as const, value: pipelineState.defaultSampler } },
|
|
2112
|
+
{
|
|
2113
|
+
binding: 12,
|
|
2114
|
+
resource: { kind: 'textureView' as const, value: pipelineState.defaultWhiteTextureView },
|
|
2115
|
+
},
|
|
2116
|
+
];
|
|
2117
|
+
const merged =
|
|
2118
|
+
fb !== null
|
|
2119
|
+
? assembleMaterialWithSkylightEntries(fallbackEntries, {
|
|
2120
|
+
irradianceView: fb.irradianceView,
|
|
2121
|
+
irradianceSampler: fb.sampler,
|
|
2122
|
+
prefilterView: fb.prefilterView,
|
|
2123
|
+
prefilterSampler: fb.sampler,
|
|
2124
|
+
brdfLutView: fb.brdfLutView,
|
|
2125
|
+
brdfLutSampler: fb.sampler,
|
|
2126
|
+
intensityBuffer: fb.intensityBuffer,
|
|
2127
|
+
})
|
|
2128
|
+
: fallbackEntries;
|
|
2129
|
+
const r = runtime.device.createBindGroup({
|
|
2130
|
+
label: 'shadow-material-bg',
|
|
2131
|
+
layout: pipelineState.materialBindGroupLayout,
|
|
2132
|
+
entries: merged,
|
|
2133
|
+
});
|
|
2134
|
+
if (!r.ok) {
|
|
2135
|
+
runtime.errorRegistry.fire(r.error);
|
|
2136
|
+
return null;
|
|
2137
|
+
}
|
|
2138
|
+
c.bindGroupCounts.createBindGroup += 1;
|
|
2139
|
+
c.bindGroupCounts.keys.push('shadow-material-singleton');
|
|
2140
|
+
frameState.singletonMaterialCache.set('shadow-material-singleton', r.value);
|
|
2141
|
+
return r.value;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
/**
|
|
2145
|
+
* feat-20260625-spot-light-shadow-mapping M2 / w11 (D-1 + D-2). Records the
|
|
2146
|
+
* spot-light shadow caster passes — one render pass per castShadow spot (cap 4)
|
|
2147
|
+
* — that write each spot's perspective depth into its `spotShadowDepth` atlas
|
|
2148
|
+
* tile. Driven by the URP `addSpotShadowPass` graph closure; reads
|
|
2149
|
+
* `frameState.spotShadowSnapshots`.
|
|
2150
|
+
*
|
|
2151
|
+
* Per spot with `shadowAtlasTile >= 0` (castShadow + non-degenerate dir + not
|
|
2152
|
+
* clipped) and a valid `lightViewProj`:
|
|
2153
|
+
* (1) Write the spot perspective matrix into `shadowCasterCascadeBuffer`
|
|
2154
|
+
* (D-1: spotLightViewProj lane at byte 16) and set `isSpot = 1` so the
|
|
2155
|
+
* shadow_caster vertex shader routes through the spot matrix instead of
|
|
2156
|
+
* the directional `view.lightViewProj_A..D` slots. The per-pass
|
|
2157
|
+
* independent command encoder + queue.submit serialize each host write
|
|
2158
|
+
* against that pass's GPU read.
|
|
2159
|
+
* (2) Open an INDEPENDENT command encoder targeting the spotShadowDepth view
|
|
2160
|
+
* with `depthLoadOp = (first valid tile ? clear : load)` — independent
|
|
2161
|
+
* clear counting decoupled from the directional cascadeIndex (D-2). Set a
|
|
2162
|
+
* per-tile viewport (col = tile % 2, row = tile / 2).
|
|
2163
|
+
* (3) Reuse the vertex-only `forgeax::default-shadow-caster` PSO + the cached
|
|
2164
|
+
* `view-shadow` BG + `shadow-material-singleton` + meshBindGroup built by
|
|
2165
|
+
* recordShadowPass (or built on-demand here when no directional shadow
|
|
2166
|
+
* ran this frame).
|
|
2167
|
+
*
|
|
2168
|
+
* AC-03 zero-shadow zero-pass: early-returns when no spot has a valid tile, so
|
|
2169
|
+
* castShadow:false scenes (all tile=-1) record zero spot passes. After the
|
|
2170
|
+
* loop, `isSpot` is reset to 0 so the next directional cascade pass (or next
|
|
2171
|
+
* frame) sees the directional routing.
|
|
2172
|
+
*/
|
|
2173
|
+
export function recordSpotShadowPass(c: _InternalRenderPipelineContext): void {
|
|
2174
|
+
const { runtime, frameState, meshBindGroup, pipelineState } = c;
|
|
2175
|
+
const snapshots = frameState.spotShadowSnapshots;
|
|
2176
|
+
// AC-03: skip entirely when no spot projects a shadow this frame.
|
|
2177
|
+
const casters = snapshots.filter((s) => s.shadowAtlasTile >= 0 && s.lightViewProj !== undefined);
|
|
2178
|
+
if (casters.length === 0) return;
|
|
2179
|
+
if (meshBindGroup === null) return;
|
|
2180
|
+
|
|
2181
|
+
const shadowPipeline =
|
|
2182
|
+
runtime.getMaterialShaderPipeline?.(
|
|
2183
|
+
'forgeax::default-shadow-caster',
|
|
2184
|
+
false,
|
|
2185
|
+
undefined,
|
|
2186
|
+
'triangle-list',
|
|
2187
|
+
undefined,
|
|
2188
|
+
undefined,
|
|
2189
|
+
'shadow-caster',
|
|
2190
|
+
) ?? null;
|
|
2191
|
+
if (shadowPipeline === null) return;
|
|
2192
|
+
|
|
2193
|
+
const spotView = frameState.currentSpotShadowView;
|
|
2194
|
+
if (spotView === null) return;
|
|
2195
|
+
|
|
2196
|
+
const shadowViewBg = ensureSpotShadowViewBg(c);
|
|
2197
|
+
const shadowMaterialBg = ensureSpotShadowMaterialBg(c);
|
|
2198
|
+
if (shadowViewBg === null || shadowMaterialBg === null) return;
|
|
2199
|
+
|
|
2200
|
+
const tileCtx: SpotTileContext = {
|
|
2201
|
+
spotView,
|
|
2202
|
+
shadowViewBg,
|
|
2203
|
+
shadowMaterialBg,
|
|
2204
|
+
shadowPipeline,
|
|
2205
|
+
meshBindGroup,
|
|
2206
|
+
tileSize: pipelineState.perPassResources.shadowMapSize,
|
|
2207
|
+
};
|
|
2208
|
+
|
|
2209
|
+
let clearedAtlas = false;
|
|
2210
|
+
for (let ci = 0; ci < casters.length; ci++) {
|
|
2211
|
+
const snap = casters[ci];
|
|
2212
|
+
if (snap === undefined || snap.lightViewProj === undefined) continue;
|
|
2213
|
+
if (recordSpotTile(c, snap, ci, casters.length, clearedAtlas, tileCtx)) {
|
|
2214
|
+
clearedAtlas = true;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
// Reset isSpot so the next directional cascade pass / next frame routes
|
|
2219
|
+
// through view.lightViewProj_A..D again (D-1).
|
|
2220
|
+
const resetPayload = new Uint32Array([0, 0, 0, 0]);
|
|
2221
|
+
const resetWrite = runtime.device.queue.writeBuffer(
|
|
2222
|
+
pipelineState.shadowCasterCascadeBuffer,
|
|
2223
|
+
0,
|
|
2224
|
+
resetPayload,
|
|
2225
|
+
);
|
|
2226
|
+
if (!resetWrite.ok) runtime.errorRegistry.fire(resetWrite.error);
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
/** Per-tile resources shared across the spot caster passes in one frame. */
|
|
2230
|
+
interface SpotTileContext {
|
|
2231
|
+
readonly spotView: TextureView;
|
|
2232
|
+
readonly shadowViewBg: BindGroup;
|
|
2233
|
+
readonly shadowMaterialBg: BindGroup;
|
|
2234
|
+
readonly shadowPipeline: NonNullable<
|
|
2235
|
+
ReturnType<NonNullable<_InternalRenderPipelineContext['runtime']['getMaterialShaderPipeline']>>
|
|
2236
|
+
>;
|
|
2237
|
+
readonly meshBindGroup: BindGroup;
|
|
2238
|
+
readonly tileSize: number;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
/**
|
|
2242
|
+
* Render one spot's perspective depth into its atlas tile. Writes isSpot=1 +
|
|
2243
|
+
* the spot matrix into the cascade UBO (D-1), opens an independent encoder with
|
|
2244
|
+
* a per-tile viewport, and clears the atlas on the first tile / loads on the
|
|
2245
|
+
* rest (D-2). Returns true when this pass cleared the atlas (so the caller
|
|
2246
|
+
* tracks the clear/load boundary). Errors fire to the registry and return the
|
|
2247
|
+
* incoming `alreadyCleared` unchanged.
|
|
2248
|
+
*/
|
|
2249
|
+
function recordSpotTile(
|
|
2250
|
+
c: _InternalRenderPipelineContext,
|
|
2251
|
+
snapshot: SpotLightSnapshot,
|
|
2252
|
+
spotIndex: number,
|
|
2253
|
+
spotCount: number,
|
|
2254
|
+
alreadyCleared: boolean,
|
|
2255
|
+
ctx: SpotTileContext,
|
|
2256
|
+
): boolean {
|
|
2257
|
+
const { runtime, pipelineState } = c;
|
|
2258
|
+
const lightViewProj = snapshot.lightViewProj;
|
|
2259
|
+
const tile = snapshot.shadowAtlasTile;
|
|
2260
|
+
if (lightViewProj === undefined || tile < 0) return alreadyCleared;
|
|
2261
|
+
// D-1: cascade UBO byte offset of the spotLightViewProj mat4 lane (16 B for
|
|
2262
|
+
// index/isSpot/2pad vec4, then the mat4). isSpot lane is u32 word 1.
|
|
2263
|
+
const SPOT_LVP_OFFSET = 16;
|
|
2264
|
+
|
|
2265
|
+
const headerWrite = runtime.device.queue.writeBuffer(
|
|
2266
|
+
pipelineState.shadowCasterCascadeBuffer,
|
|
2267
|
+
0,
|
|
2268
|
+
new Uint32Array([0, 1, 0, 0]), // index=0, isSpot=1, pad
|
|
2269
|
+
);
|
|
2270
|
+
if (!headerWrite.ok) {
|
|
2271
|
+
runtime.errorRegistry.fire(headerWrite.error);
|
|
2272
|
+
return alreadyCleared;
|
|
2273
|
+
}
|
|
2274
|
+
const matWrite = runtime.device.queue.writeBuffer(
|
|
2275
|
+
pipelineState.shadowCasterCascadeBuffer,
|
|
2276
|
+
SPOT_LVP_OFFSET,
|
|
2277
|
+
lightViewProj,
|
|
2278
|
+
);
|
|
2279
|
+
if (!matWrite.ok) {
|
|
2280
|
+
runtime.errorRegistry.fire(matWrite.error);
|
|
2281
|
+
return alreadyCleared;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
const encResult = runtime.device.createCommandEncoder({ label: `spot-shadow-tile${tile}` });
|
|
2285
|
+
if (!encResult.ok) {
|
|
2286
|
+
runtime.errorRegistry.fire(encResult.error);
|
|
2287
|
+
return alreadyCleared;
|
|
2288
|
+
}
|
|
2289
|
+
const enc = encResult.value;
|
|
2290
|
+
const pass: RhiRenderPassEncoder = enc.beginRenderPass(
|
|
2291
|
+
buildBeginRenderPassDescriptor(
|
|
2292
|
+
{ colorFormats: [], depthFormat: 'depth32float', sampleCount: 1 },
|
|
2293
|
+
{ colorViews: [], depthView: ctx.spotView },
|
|
2294
|
+
'shadow-caster',
|
|
2295
|
+
{ depthLoadOp: alreadyCleared ? 'load' : 'clear' },
|
|
2296
|
+
) as never,
|
|
2297
|
+
);
|
|
2298
|
+
|
|
2299
|
+
pass.setPipeline(ctx.shadowPipeline);
|
|
2300
|
+
const col = tile % 2;
|
|
2301
|
+
const row = Math.floor(tile / 2);
|
|
2302
|
+
pass.setViewport(col * ctx.tileSize, row * ctx.tileSize, ctx.tileSize, ctx.tileSize, 0, 1);
|
|
2303
|
+
pass.setBindGroup(0, ctx.shadowViewBg);
|
|
2304
|
+
pass.setBindGroup(1, ctx.shadowMaterialBg, [0]);
|
|
2305
|
+
emitSpotShadowPassBeginMarker(c, pass, snapshot, spotIndex, spotCount);
|
|
2306
|
+
recordSpotShadowGeometry(c, pass, ctx.meshBindGroup);
|
|
2307
|
+
emitSpotShadowPassEndMarker(c, pass, snapshot);
|
|
2308
|
+
|
|
2309
|
+
pass.end();
|
|
2310
|
+
const finishResult = enc.finish();
|
|
2311
|
+
if (!finishResult.ok) {
|
|
2312
|
+
runtime.errorRegistry.fire(finishResult.error);
|
|
2313
|
+
return alreadyCleared;
|
|
2314
|
+
}
|
|
2315
|
+
const submitResult = runtime.device.queue.submit([finishResult.value]);
|
|
2316
|
+
if (!submitResult.ok) runtime.errorRegistry.fire(submitResult.error);
|
|
2317
|
+
return true;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
/**
|
|
2321
|
+
* Walk the mesh-SSBO slot order and emit shadow-caster draws for the current
|
|
2322
|
+
* spot tile pass. Triangle topologies only (line/point meshes cast no shadow).
|
|
2323
|
+
* Reuses the per-entity instance BGs cached by recordShadowPass; falls back to
|
|
2324
|
+
* the identity instance buffer when none is cached.
|
|
2325
|
+
*/
|
|
2326
|
+
function recordSpotShadowGeometry(
|
|
2327
|
+
c: _InternalRenderPipelineContext,
|
|
2328
|
+
pass: RhiRenderPassEncoder,
|
|
2329
|
+
meshBindGroup: BindGroup,
|
|
2330
|
+
): void {
|
|
2331
|
+
const { frameState, pipelineState } = c;
|
|
2332
|
+
const mainMembership = mainShadowMembership(c);
|
|
2333
|
+
let lastVB: GpuBuffer | null = null;
|
|
2334
|
+
let lastIB: GpuBuffer | null = null;
|
|
2335
|
+
const shadowEntries =
|
|
2336
|
+
c.validatedShadowEntries ??
|
|
2337
|
+
c.validatedOrdered.flatMap((entry, validatedIndex) =>
|
|
2338
|
+
isMainShadowRenderable(entry, mainMembership) ? [{ entry, validatedIndex }] : [],
|
|
2339
|
+
);
|
|
2340
|
+
for (const { entry, validatedIndex: ei } of shadowEntries) {
|
|
2341
|
+
const submeshes = entry.mesh.submeshes;
|
|
2342
|
+
const hasTriangle = submeshes.some(
|
|
2343
|
+
(sm) => sm.topology === 'triangle-list' || sm.topology === 'triangle-strip',
|
|
2344
|
+
);
|
|
2345
|
+
if (!hasTriangle) continue;
|
|
2346
|
+
if (entry.mesh.vertexBuffer !== lastVB) {
|
|
2347
|
+
pass.setVertexBuffer(0, entry.mesh.vertexBuffer.handle);
|
|
2348
|
+
lastVB = entry.mesh.vertexBuffer;
|
|
2349
|
+
}
|
|
2350
|
+
if (entry.mesh.indexed && entry.mesh.indexBuffer !== lastIB && entry.mesh.indexBuffer) {
|
|
2351
|
+
pass.setIndexBuffer(entry.mesh.indexBuffer.handle, entry.mesh.indexFormat);
|
|
2352
|
+
lastIB = entry.mesh.indexBuffer;
|
|
2353
|
+
}
|
|
2354
|
+
pass.setBindGroup(2, meshBindGroup, [ei * MESH_PER_ENTITY_STRIDE]);
|
|
2355
|
+
const inst = entry.source.instances;
|
|
2356
|
+
let instCount = 1;
|
|
2357
|
+
let instBufferKey: object = pipelineState.identityInstanceBuffer as unknown as object;
|
|
2358
|
+
if (inst !== undefined) {
|
|
2359
|
+
const cached = frameState.instanceBuffers.get(
|
|
2360
|
+
worldEntityKey(entry.source.worldId, inst.cacheKey),
|
|
2361
|
+
);
|
|
2362
|
+
if (
|
|
2363
|
+
cached !== undefined &&
|
|
2364
|
+
cached.uploadedArchVersion === inst.archVersion &&
|
|
2365
|
+
cached.uploadedByteLength ===
|
|
2366
|
+
temporalInstanceUploadByteLength(inst.transforms, c.runtime.device.caps.storageBuffer)
|
|
2367
|
+
) {
|
|
2368
|
+
instBufferKey = cached.buffer.handle as unknown as object;
|
|
2369
|
+
instCount = Math.max(1, inst.instanceCount);
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
const spotInstLeaf = frameState.instancesBgPerEntity
|
|
2373
|
+
.get(worldEntityKey(entry.source.worldId, entry.source.entityKey))
|
|
2374
|
+
?.get(instBufferKey) as Map<string, BindGroup> | undefined;
|
|
2375
|
+
const cachedInstBg = spotInstLeaf?.get('shadow-instances');
|
|
2376
|
+
if (cachedInstBg === undefined) continue;
|
|
2377
|
+
pass.setBindGroup(3, cachedInstBg);
|
|
2378
|
+
for (const sm of submeshes) {
|
|
2379
|
+
if (sm.topology !== 'triangle-list' && sm.topology !== 'triangle-strip') continue;
|
|
2380
|
+
if (entry.mesh.indexed) {
|
|
2381
|
+
pass.drawIndexed(sm.indexCount, instCount, sm.indexOffset, 0, 0);
|
|
2382
|
+
} else {
|
|
2383
|
+
pass.draw(sm.vertexCount, instCount, 0, 0);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
}
|