@forgeax/engine-render 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/assembly/device-feature-admission.d.ts +18 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -0
- package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
- package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +21 -18
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +9 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/render-target-host.d.ts +46 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -0
- package/dist/assets/render-asset-port.d.ts.map +1 -1
- package/dist/authoring.mjs +3 -0
- package/dist/authoring.mjs.map +1 -1
- package/dist/capture/cube-views.d.ts +21 -0
- package/dist/capture/cube-views.d.ts.map +1 -0
- package/dist/capture/scheduler.d.ts +54 -0
- package/dist/capture/scheduler.d.ts.map +1 -0
- package/dist/{chunk-4XINDLAY.mjs → chunk-5YADUCA7.mjs} +1552 -1361
- package/dist/chunk-5YADUCA7.mjs.map +1 -0
- package/dist/{chunk-TBQJOK2L.mjs → chunk-6OPXDQ2E.mjs} +364 -51
- package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
- package/dist/{chunk-M2MP4ZOU.mjs → chunk-BXBQQKFQ.mjs} +3 -9
- package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
- package/dist/{chunk-CDTWTHZR.mjs → chunk-FS2KNUQH.mjs} +548 -6
- package/dist/chunk-FS2KNUQH.mjs.map +1 -0
- package/dist/components/camera.d.ts +1 -0
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/cube-camera.d.ts +20 -0
- package/dist/components/cube-camera.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +11 -2
- package/dist/components/directional-light.d.ts.map +1 -1
- package/dist/components/directional-shadow-filter.d.ts +25 -0
- package/dist/components/directional-shadow-filter.d.ts.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/reflection-probe.d.ts +17 -0
- package/dist/components/reflection-probe.d.ts.map +1 -0
- package/dist/construct-renderer.mjs +5826 -1003
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +5 -0
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/render.d.ts +141 -93
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/directional-shadow-projection.d.ts +23 -0
- package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
- package/dist/extract/environment.d.ts +14 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/extract/visibility.d.ts.map +1 -1
- package/dist/features/fullscreen.d.ts.map +1 -1
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +10 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +13 -2
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +0 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +19 -17
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +4 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/kernel-cache.d.ts +36 -0
- package/dist/ibl/kernel-cache.d.ts.map +1 -0
- package/dist/index.d.ts +28 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +51 -0
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +50 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -0
- package/dist/internal.mjs +1 -1
- package/dist/light-buffer-layout.d.ts +10 -1
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +54 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +14 -4
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts +2 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
- package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
- package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
- package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts +4 -13
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +5 -26
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +40 -7
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/point-shadow-inspection.d.ts +21 -0
- package/dist/point-shadow-inspection.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +27 -32
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +28 -14
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts +5 -0
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +10 -4
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
- package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
- package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/index.d.ts +8 -0
- package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
- package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/session.d.ts +43 -0
- package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
- package/dist/record/gpu-timing.d.ts +8 -3
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +22 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +2 -0
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +43 -2
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +72 -4
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +7 -4
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/reflection/filter.d.ts +22 -0
- package/dist/reflection/filter.d.ts.map +1 -0
- package/dist/reflection/gpu-table.d.ts +25 -0
- package/dist/reflection/gpu-table.d.ts.map +1 -0
- package/dist/reflection/projection.d.ts +91 -0
- package/dist/reflection/projection.d.ts.map +1 -0
- package/dist/render-contract.d.ts +49 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +42 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +58 -17
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +17 -2
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer.d.ts +43 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/scene/render-scene.d.ts +14 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/shadow-atlas.d.ts +3 -3
- package/dist/shadow-atlas.d.ts.map +1 -1
- package/dist/ssao-buffers.d.ts +3 -0
- package/dist/ssao-buffers.d.ts.map +1 -1
- package/dist/targets/contracts.d.ts +66 -0
- package/dist/targets/contracts.d.ts.map +1 -0
- package/dist/targets/graph-projection.d.ts +29 -0
- package/dist/targets/graph-projection.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +34 -0
- package/dist/targets/material-source.d.ts.map +1 -0
- package/dist/targets/owner.d.ts +32 -0
- package/dist/targets/owner.d.ts.map +1 -0
- package/dist/targets/physical.d.ts +18 -0
- package/dist/targets/physical.d.ts.map +1 -0
- package/dist/targets/readback.d.ts +39 -0
- package/dist/targets/readback.d.ts.map +1 -0
- package/dist/temporal/index.mjs +3 -3
- package/dist/typed-render-graph-primitives.d.ts +1 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +1 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +2 -2
- package/dist/volume/passes.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
- package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
- package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
- package/src/__tests__/cluster-binner.unit.test.ts +35 -0
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
- package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
- package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
- package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
- package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
- package/src/__tests__/cube-camera.integration.test.ts +42 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
- package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
- package/src/__tests__/factory-contract.integration.test.ts +255 -29
- package/src/__tests__/fog-component.unit.test.ts +67 -0
- package/src/__tests__/fog.integration.test.ts +86 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
- package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
- package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
- package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
- package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
- package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
- package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
- package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
- package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
- package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
- package/src/__tests__/instances-culling.integration.test.ts +94 -0
- package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
- package/src/__tests__/material-observation.integration.test.ts +101 -0
- package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
- package/src/__tests__/public-surface.test-d.ts +10 -0
- package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
- package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
- package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
- package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
- package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
- package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +18 -3
- package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
- package/src/__tests__/render-target-admission.unit.test.ts +80 -0
- package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
- package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
- package/src/__tests__/render-target-material-source.test-d.ts +30 -0
- package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
- package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
- package/src/__tests__/render-target-owner.unit.test.ts +122 -0
- package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
- package/src/__tests__/render-target-public-schema.json +107 -0
- package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
- package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
- package/src/__tests__/render-target-readback.integration.test.ts +90 -0
- package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +48 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
- package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
- package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +11 -2
- package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
- package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
- package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
- package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
- package/src/__tests__/standard-lighting.integration.test.ts +67 -0
- package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
- package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
- package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
- package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
- package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
- package/src/assembly/device-feature-admission.ts +73 -0
- package/src/assembly/directional-shadow-inspection.ts +122 -0
- package/src/assembly/factory.ts +877 -286
- package/src/assembly/host-contract.ts +25 -0
- package/src/assembly/render-target-host.ts +847 -0
- package/src/assets/asset-decoders.ts +1 -1
- package/src/assets/render-asset-port.ts +7 -1
- package/src/capture/cube-views.ts +69 -0
- package/src/capture/scheduler.ts +265 -0
- package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
- package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
- package/src/components/camera.ts +4 -0
- package/src/components/cube-camera.ts +52 -0
- package/src/components/directional-light.ts +22 -2
- package/src/components/directional-shadow-filter.ts +57 -0
- package/src/components/index.ts +2 -0
- package/src/components/light-helpers.ts +81 -7
- package/src/components/reflection-probe.ts +49 -0
- package/src/environment/frame.ts +6 -24
- package/src/environment/lifecycle.ts +12 -0
- package/src/errors/render.ts +286 -142
- package/src/extract/directional-shadow-projection.ts +61 -0
- package/src/extract/environment.ts +57 -0
- package/src/extract/visibility.ts +25 -1
- package/src/features/fullscreen.ts +3 -0
- package/src/features/targets.ts +4 -1
- package/src/features/types.ts +10 -0
- package/src/fullscreen-post-process-pass.ts +43 -2
- package/src/gpu-driven/batch-topology.ts +1 -0
- package/src/gpu-driven/production-raster.ts +1 -3
- package/src/gpu-scene.ts +8 -1
- package/src/hdrp-buffers.ts +165 -59
- package/src/ibl/IblPipelineCache.ts +11 -0
- package/src/ibl/kernel-cache.ts +88 -0
- package/src/index.ts +148 -0
- package/src/inspection-types.ts +56 -0
- package/src/instances-derived-bounds.ts +171 -0
- package/src/light-buffer-layout.ts +20 -5
- package/src/mesh-material-bindings.ts +132 -0
- package/src/pbr-pipeline.ts +106 -17
- package/src/pipeline/standard-forward-lane.ts +46 -16
- package/src/pipeline/standard-lighting/errors.ts +6 -0
- package/src/pipeline/standard-lighting/graph.ts +157 -0
- package/src/pipeline/standard-lighting/inspection.ts +88 -0
- package/src/pipeline/standard-lighting/layout.ts +31 -0
- package/src/pipeline/standard-lighting/light-frame.ts +34 -0
- package/src/pipeline/standard-lighting/membership.ts +43 -0
- package/src/pipeline/standard-lighting/prepare.ts +37 -0
- package/src/pipeline/standard-lighting/topology.ts +120 -0
- package/src/pipeline/standard-lighting/transport.ts +77 -0
- package/src/pipeline/standard-pipeline.ts +122 -193
- package/src/pipeline/standard-post.ts +26 -7
- package/src/pipeline/standard-profile.ts +17 -47
- package/src/pipeline-spec.ts +84 -21
- package/src/point-shadow-inspection.ts +55 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
- package/src/record/frame-lighting.ts +367 -427
- package/src/record/frame-snapshot.ts +35 -27
- package/src/record/frame-targets.ts +17 -0
- package/src/record/frame.ts +261 -74
- package/src/record/gpu-pass-timing/contract.ts +180 -0
- package/src/record/gpu-pass-timing/errors.ts +34 -0
- package/src/record/gpu-pass-timing/index.ts +37 -0
- package/src/record/gpu-pass-timing/parser.ts +97 -0
- package/src/record/gpu-pass-timing/session.ts +938 -0
- package/src/record/gpu-timing.ts +21 -12
- package/src/record/main-pass-geometry.ts +120 -80
- package/src/record/main-pass-material.ts +95 -9
- package/src/record/main-pass-sprite-draws.ts +54 -25
- package/src/record/main-pass.ts +71 -15
- package/src/record/render-context.ts +72 -0
- package/src/record/shadow-pass.ts +17 -28
- package/src/record/skybox-post-pass.ts +20 -4
- package/src/record/typed-frame-graph.ts +893 -44
- package/src/record/view-ubo.ts +63 -28
- package/src/reflection/filter.ts +81 -0
- package/src/reflection/gpu-table.ts +44 -0
- package/src/reflection/projection.ts +281 -0
- package/src/render-contract.ts +76 -5
- package/src/render-graph-primitives.ts +9 -4
- package/src/render-pipeline.ts +117 -2
- package/src/render-system-extract.ts +557 -100
- package/src/render-system.ts +1095 -66
- package/src/renderer.ts +222 -0
- package/src/scene/render-scene.ts +115 -9
- package/src/shadow-atlas.ts +5 -5
- package/src/ssao-buffers.ts +9 -2
- package/src/targets/contracts.ts +182 -0
- package/src/targets/graph-projection.ts +169 -0
- package/src/targets/material-source.ts +143 -0
- package/src/targets/owner.ts +214 -0
- package/src/targets/physical.ts +18 -0
- package/src/targets/readback.ts +153 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
- package/src/typed-render-graph-primitives.ts +5 -3
- package/src/typed-shadow-passes.ts +46 -40
- package/src/volume/passes.ts +15 -9
- package/dist/chunk-4XINDLAY.mjs.map +0 -1
- package/dist/chunk-CDTWTHZR.mjs.map +0 -1
- package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
- package/dist/chunk-TBQJOK2L.mjs.map +0 -1
|
@@ -154,6 +154,12 @@ import {
|
|
|
154
154
|
} from '@forgeax/engine-types';
|
|
155
155
|
import {
|
|
156
156
|
Atmosphere,
|
|
157
|
+
antialiasFromF32,
|
|
158
|
+
bloomEnabledFromF32,
|
|
159
|
+
Camera,
|
|
160
|
+
CubeCamera,
|
|
161
|
+
cameraProjectionFromF32,
|
|
162
|
+
cubeCameraUpdateIntentFromF32,
|
|
157
163
|
DirectionalLight,
|
|
158
164
|
Fog,
|
|
159
165
|
Instances,
|
|
@@ -161,33 +167,50 @@ import {
|
|
|
161
167
|
Lines,
|
|
162
168
|
MeshFilter,
|
|
163
169
|
MeshRenderer,
|
|
170
|
+
MotionBlur,
|
|
164
171
|
PointLight,
|
|
165
172
|
PointLightShadow,
|
|
166
173
|
Points,
|
|
167
174
|
PostProcessParams,
|
|
168
175
|
pointShapeFromU32,
|
|
176
|
+
ReflectionProbe,
|
|
177
|
+
reflectionProbeUpdateIntentFromF32,
|
|
169
178
|
SkyboxBackground,
|
|
170
179
|
Skylight,
|
|
171
180
|
SortKey,
|
|
172
181
|
SpotLight,
|
|
173
182
|
SpriteInstances,
|
|
174
183
|
SpriteRegionOverride,
|
|
184
|
+
tonemapFromF32,
|
|
175
185
|
tonemapToU32,
|
|
176
186
|
} from './components';
|
|
187
|
+
import {
|
|
188
|
+
type DirectionalShadowQuality,
|
|
189
|
+
directionalShadowQualityFromF32,
|
|
190
|
+
} from './components/directional-shadow-filter';
|
|
177
191
|
import { GlyphText } from './components/glyph-text';
|
|
178
|
-
import {
|
|
192
|
+
import {
|
|
193
|
+
computeInvRangeSquared,
|
|
194
|
+
degToCos,
|
|
195
|
+
type LightValidationError,
|
|
196
|
+
validateDirectionalLightData,
|
|
197
|
+
} from './components/light-helpers';
|
|
179
198
|
import { selectEnvironment } from './environment/frame';
|
|
180
199
|
import {
|
|
181
200
|
MaterialSkinAttrMissingError,
|
|
182
201
|
ShadowInvalidConfigError,
|
|
183
202
|
SkinMaterialMismatchError,
|
|
184
203
|
} from './errors/render';
|
|
185
|
-
import {
|
|
204
|
+
import {
|
|
205
|
+
type DirectionalShadowCascadeFit,
|
|
206
|
+
projectDirectionalShadow,
|
|
207
|
+
} from './extract/directional-shadow-projection';
|
|
186
208
|
import type {
|
|
187
209
|
EnvironmentCandidate,
|
|
188
210
|
EnvironmentFrame,
|
|
189
211
|
FogCandidate,
|
|
190
212
|
FogFrame,
|
|
213
|
+
FogSelectionFailure,
|
|
191
214
|
} from './extract/environment';
|
|
192
215
|
import { resolveVisibility, type VisibilitySnapshot } from './extract/visibility';
|
|
193
216
|
import type {
|
|
@@ -195,6 +218,7 @@ import type {
|
|
|
195
218
|
RenderFeatureWorldVisibilitySnapshot,
|
|
196
219
|
} from './features/types';
|
|
197
220
|
import { ensureGlyphMeshMaterialSlots } from './glyph-text-layout-system';
|
|
221
|
+
import { deriveInstancesUnionBounds } from './instances-derived-bounds';
|
|
198
222
|
import {
|
|
199
223
|
type MeshMaterialBindingDiagnostic,
|
|
200
224
|
type MeshMaterialBindingSource,
|
|
@@ -203,13 +227,20 @@ import {
|
|
|
203
227
|
import { isStandardPbrMaterialShader } from './pbr-pipeline';
|
|
204
228
|
import { expandPointsLinesBounds } from './points-lines/bounds';
|
|
205
229
|
import type { PointsLinesRetainedSnapshot, PointsLinesStyle } from './points-lines/snapshot';
|
|
206
|
-
import
|
|
230
|
+
import type { ReflectionProbeFact } from './reflection/projection';
|
|
231
|
+
import {
|
|
232
|
+
type CameraSnapshot,
|
|
233
|
+
type CubeCameraSnapshot,
|
|
234
|
+
STANDARD_OUTPUT_TRANSFORM_FEATURE_ID,
|
|
235
|
+
} from './render-contract';
|
|
236
|
+
import { SHADOW_ATLAS_DEFAULT_LAYERS } from './shadow-atlas';
|
|
237
|
+
import { getActiveCamera, selectActiveCameraIndex } from './systems/active-camera';
|
|
207
238
|
import { selectPasses } from './systems/pass-selector';
|
|
208
239
|
|
|
209
|
-
export { extractCameraSnapshots } from './extract/camera';
|
|
210
|
-
|
|
211
240
|
import type { SkinPaletteAllocator } from './systems/skin-palette-allocator';
|
|
212
241
|
import type { SkinPaletteSlice } from './systems/skin-palette-types';
|
|
242
|
+
import type { RenderTarget, RenderTargetTextureSource } from './targets/contracts';
|
|
243
|
+
import { resolveRenderTargetMaterialSource } from './targets/material-source';
|
|
213
244
|
import { resolveSelectedVolumetricLight } from './volume/capability';
|
|
214
245
|
import {
|
|
215
246
|
type ValidatedVolumetricFog,
|
|
@@ -252,8 +283,9 @@ export interface PointLightSnapshot {
|
|
|
252
283
|
readonly invRangeSquared: number;
|
|
253
284
|
/**
|
|
254
285
|
* feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-8 + M4 / T-M4-4:
|
|
255
|
-
* cube_array atlas layer index for this light's shadow map (0..
|
|
256
|
-
* casters; sentinel `-1` for non-
|
|
286
|
+
* cube_array atlas layer index for this light's shadow map (0..capacity-1
|
|
287
|
+
* for admitted shadow casters; sentinel `-1` for non-admitted lights).
|
|
288
|
+
* Default `-1` per
|
|
257
289
|
* plan-strategy §D-2 — record stage / shader skips shadow sampling when the
|
|
258
290
|
* lane equals the sentinel. Joined with `pointShadow[]` by entity at the
|
|
259
291
|
* end of extract; HDRP record stage threads `shadowAtlasLayer + shadowNear +
|
|
@@ -290,8 +322,9 @@ export interface PointLightSnapshot {
|
|
|
290
322
|
*
|
|
291
323
|
* `mapSize` / `nearPlane` / `farPlane` ride here so record stage can size the
|
|
292
324
|
* cube atlas faces and feed shader the proj constants without re-querying ECS.
|
|
293
|
-
* `shadowAtlasLayer` is assigned by extract in spawn order
|
|
294
|
-
*
|
|
325
|
+
* `shadowAtlasLayer` is assigned by extract in spawn order up to the renderer
|
|
326
|
+
* atlas capacity; over-budget requests retain the sentinel `-1` so the
|
|
327
|
+
* downstream LightSlot / packPointLight packers carry no invalid layer in
|
|
295
328
|
* pointPadW (T-M1-8 will rename to shadowAtlasLayer).
|
|
296
329
|
*/
|
|
297
330
|
export interface PointShadowSnapshot {
|
|
@@ -304,9 +337,9 @@ export interface PointShadowSnapshot {
|
|
|
304
337
|
readonly nearPlane: number;
|
|
305
338
|
readonly farPlane: number;
|
|
306
339
|
/**
|
|
307
|
-
* Atlas layer assigned by extract in spawn order
|
|
308
|
-
*
|
|
309
|
-
* shader-side LightSlot for no-shadow lights (T-M1-8).
|
|
340
|
+
* Atlas layer assigned by extract in spawn order while capacity remains.
|
|
341
|
+
* Sentinel -1 is used for a request that cannot be admitted and is
|
|
342
|
+
* reserved by the shader-side LightSlot for no-shadow lights (T-M1-8).
|
|
310
343
|
*/
|
|
311
344
|
readonly shadowAtlasLayer: number;
|
|
312
345
|
/**
|
|
@@ -404,6 +437,7 @@ export interface ExtractedLights {
|
|
|
404
437
|
* unused slots are 0.0f. Undefined when castShadow=false or no directional
|
|
405
438
|
* light.
|
|
406
439
|
*/
|
|
440
|
+
/** Four vec4 lanes: split, worldUnitsPerTexel, lightDepthWorldSpan, reserved. */
|
|
407
441
|
readonly splitPlanes: Float32Array | undefined;
|
|
408
442
|
/**
|
|
409
443
|
* feat-20260613-csm-cascaded-shadow-maps M2 / w9: effective cascade count
|
|
@@ -439,13 +473,10 @@ export interface ExtractedLights {
|
|
|
439
473
|
* undefined otherwise.
|
|
440
474
|
*/
|
|
441
475
|
readonly normalBias: number | undefined;
|
|
442
|
-
/**
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
* undefined otherwise.
|
|
447
|
-
*/
|
|
448
|
-
readonly pcfKernelSize: number | undefined;
|
|
476
|
+
/** Accepted closed Directional filter projected from the first query result. */
|
|
477
|
+
readonly directionalShadowQuality: DirectionalShadowQuality | undefined;
|
|
478
|
+
/** Structured author-config failure; the candidate is withheld from record. */
|
|
479
|
+
readonly directionalShadowError: LightValidationError | undefined;
|
|
449
480
|
/**
|
|
450
481
|
* feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-7:
|
|
451
482
|
* shadow-casting point lights (PointLight + PointLightShadow + Transform
|
|
@@ -851,6 +882,8 @@ export interface MaterialSnapshot {
|
|
|
851
882
|
* them in a lightmap injection region.
|
|
852
883
|
*/
|
|
853
884
|
readonly textureHandles?: ReadonlyMap<string, Handle<'TextureAsset', 'shared'>> | undefined;
|
|
885
|
+
/** Renderer-local target sources; never projected as TextureAsset handles. */
|
|
886
|
+
readonly textureSources?: ReadonlyMap<string, RenderTargetTextureSource> | undefined;
|
|
854
887
|
/**
|
|
855
888
|
* User-region texture field names whose paramValue resolved to a VideoAsset
|
|
856
889
|
* (kind `'video'`) rather than a static TextureAsset
|
|
@@ -1060,6 +1093,12 @@ function appendMaterialDispatchEntries(
|
|
|
1060
1093
|
|
|
1061
1094
|
export interface ExtractedFrame {
|
|
1062
1095
|
readonly cameras: CameraSnapshot[];
|
|
1096
|
+
/** Auxiliary target cameras selected after the display camera is fixed. */
|
|
1097
|
+
readonly auxiliaryCameras: readonly CameraSnapshot[];
|
|
1098
|
+
/** Cube capture intents extracted beside the display camera in one pass. */
|
|
1099
|
+
readonly cubeCameras: readonly CubeCameraSnapshot[];
|
|
1100
|
+
/** Reflection probe facts projected from the same World extraction. */
|
|
1101
|
+
readonly reflectionProbes?: readonly ReflectionProbeFact[];
|
|
1063
1102
|
readonly lights: ExtractedLights;
|
|
1064
1103
|
/** Resource-owner environment facts selected once at the extract boundary. */
|
|
1065
1104
|
readonly environment: EnvironmentFrame | undefined;
|
|
@@ -1080,6 +1119,8 @@ export interface ExtractedFrame {
|
|
|
1080
1119
|
readonly skyboxCount: number;
|
|
1081
1120
|
/** Resource-owner Fog facts selected once at the frame boundary. */
|
|
1082
1121
|
readonly fog: FogFrame | undefined;
|
|
1122
|
+
/** Last invalid Fog update retained alongside the current valid/LKG frame. */
|
|
1123
|
+
readonly fogFailure?: FogSelectionFailure;
|
|
1083
1124
|
/**
|
|
1084
1125
|
* feat-20260528-frustum-culling M3 / w11: frustum culling statistics
|
|
1085
1126
|
* collected during the extract phase. `total` is the count of entities
|
|
@@ -1105,6 +1146,65 @@ export interface ExtractedFrame {
|
|
|
1105
1146
|
readonly hiddenEntityReports: readonly RenderFeatureHiddenEntityReport[];
|
|
1106
1147
|
}
|
|
1107
1148
|
|
|
1149
|
+
export type CameraTargetUpdate = 'once' | 'on-demand' | 'continuous';
|
|
1150
|
+
|
|
1151
|
+
export interface CameraTargetCandidate {
|
|
1152
|
+
readonly worldId: number;
|
|
1153
|
+
readonly entityKey: number;
|
|
1154
|
+
readonly target?: RenderTarget;
|
|
1155
|
+
readonly requestVersion: number;
|
|
1156
|
+
readonly update: CameraTargetUpdate;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
export interface CameraTargetRejection {
|
|
1160
|
+
readonly entityKey: number;
|
|
1161
|
+
readonly reason: 'budget' | 'display-target' | 'duplicate-target';
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
export interface CameraTargetSelection {
|
|
1165
|
+
readonly display: CameraTargetCandidate | undefined;
|
|
1166
|
+
readonly auxiliary: readonly CameraTargetCandidate[];
|
|
1167
|
+
readonly rejected: readonly CameraTargetRejection[];
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
export function selectCameraTargetViews(
|
|
1171
|
+
candidates: readonly CameraTargetCandidate[],
|
|
1172
|
+
options: { readonly displayEntityKey?: number; readonly budget: number },
|
|
1173
|
+
): CameraTargetSelection {
|
|
1174
|
+
const display = candidates.find(
|
|
1175
|
+
(candidate) =>
|
|
1176
|
+
candidate.entityKey === options.displayEntityKey && candidate.target === undefined,
|
|
1177
|
+
);
|
|
1178
|
+
const rejected: CameraTargetRejection[] = [];
|
|
1179
|
+
const ordered = candidates
|
|
1180
|
+
.filter((candidate) => candidate.target !== undefined)
|
|
1181
|
+
.sort((left, right) => left.worldId - right.worldId || left.entityKey - right.entityKey);
|
|
1182
|
+
const auxiliary: CameraTargetCandidate[] = [];
|
|
1183
|
+
const seenTargets = new Set<RenderTarget>();
|
|
1184
|
+
for (const candidate of ordered) {
|
|
1185
|
+
const duplicate = candidate.target !== undefined && seenTargets.has(candidate.target);
|
|
1186
|
+
if (candidate.target !== undefined) seenTargets.add(candidate.target);
|
|
1187
|
+
if (candidate.entityKey === options.displayEntityKey) {
|
|
1188
|
+
rejected.push({ entityKey: candidate.entityKey, reason: 'display-target' });
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
if (candidate.target === undefined || duplicate) {
|
|
1192
|
+
rejected.push({ entityKey: candidate.entityKey, reason: 'duplicate-target' });
|
|
1193
|
+
continue;
|
|
1194
|
+
}
|
|
1195
|
+
if (auxiliary.length >= Math.max(0, options.budget)) {
|
|
1196
|
+
rejected.push({ entityKey: candidate.entityKey, reason: 'budget' });
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
auxiliary.push(candidate);
|
|
1200
|
+
}
|
|
1201
|
+
return {
|
|
1202
|
+
display,
|
|
1203
|
+
auxiliary: Object.freeze(auxiliary),
|
|
1204
|
+
rejected: Object.freeze(rejected),
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1108
1208
|
/**
|
|
1109
1209
|
* Internal world surface used by extract for archetype-graph traversal and
|
|
1110
1210
|
* error routing. The packed `Entity` handle for a row is read directly from
|
|
@@ -1147,6 +1247,14 @@ function createWorldInternalView(world: World): WorldInternalView {
|
|
|
1147
1247
|
};
|
|
1148
1248
|
}
|
|
1149
1249
|
|
|
1250
|
+
function resolveCameraTarget(world: World, raw: number | undefined): RenderTarget | undefined {
|
|
1251
|
+
if (raw === undefined || raw <= 0) return undefined;
|
|
1252
|
+
const resolved = world.sharedRefs.resolve<'RenderTarget', RenderTarget>(
|
|
1253
|
+
toShared<'RenderTarget'>(Math.round(raw)),
|
|
1254
|
+
);
|
|
1255
|
+
return resolved.ok ? resolved.value : undefined;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1150
1258
|
/**
|
|
1151
1259
|
* The user-region texture fields the built-in standard-PBR material declares.
|
|
1152
1260
|
* Used as the fallback texture-field set when the shader id is not registered
|
|
@@ -1427,6 +1535,24 @@ function collectMaterialTextureCoordinates(
|
|
|
1427
1535
|
return out;
|
|
1428
1536
|
}
|
|
1429
1537
|
|
|
1538
|
+
function collectMaterialTextureSources(
|
|
1539
|
+
values: Readonly<Record<string, unknown>>,
|
|
1540
|
+
world: World,
|
|
1541
|
+
): Map<string, RenderTargetTextureSource> {
|
|
1542
|
+
const out = new Map<string, RenderTargetTextureSource>();
|
|
1543
|
+
for (const [field, value] of Object.entries(values)) {
|
|
1544
|
+
const reference = materialTextureRef(value);
|
|
1545
|
+
if (typeof reference !== 'number') continue;
|
|
1546
|
+
const resolved = world.sharedRefs.resolve<
|
|
1547
|
+
'RenderTargetTextureSource',
|
|
1548
|
+
RenderTargetTextureSource
|
|
1549
|
+
>(toShared<'RenderTargetTextureSource'>(reference));
|
|
1550
|
+
if (!resolved.ok || resolveRenderTargetMaterialSource(resolved.value) === undefined) continue;
|
|
1551
|
+
out.set(field, resolved.value);
|
|
1552
|
+
}
|
|
1553
|
+
return out;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1430
1556
|
function collectMaterialTextureSamplers(
|
|
1431
1557
|
values: Readonly<Record<string, unknown>>,
|
|
1432
1558
|
resolveSampler: (value: unknown) => Handle<'SamplerAsset', 'shared'> | undefined,
|
|
@@ -1517,9 +1643,14 @@ function materialParametersToParamSchema(
|
|
|
1517
1643
|
return parameters.flatMap((parameter): ParamSchemaEntry[] => {
|
|
1518
1644
|
// Boolean material values are runtime-only and have no UBO representation.
|
|
1519
1645
|
if (parameter.type === 'bool') return [];
|
|
1520
|
-
if (parameter.type === 'texture') {
|
|
1646
|
+
if (parameter.type === 'texture' || parameter.type === 'texture_cube') {
|
|
1521
1647
|
if (engineInjectedTextureFields?.has(parameter.name) === true) return [];
|
|
1522
|
-
return [
|
|
1648
|
+
return [
|
|
1649
|
+
{
|
|
1650
|
+
name: parameter.name,
|
|
1651
|
+
type: parameter.type === 'texture_cube' ? 'texture_cube' : 'texture2d',
|
|
1652
|
+
},
|
|
1653
|
+
];
|
|
1523
1654
|
}
|
|
1524
1655
|
return [
|
|
1525
1656
|
{
|
|
@@ -1779,6 +1910,7 @@ export function resolveMaterialSnapshot(
|
|
|
1779
1910
|
const samplerHandles = collectMaterialTextureSamplers(pv, (value) =>
|
|
1780
1911
|
resolveTexLike(materialTextureRef(value), 'SamplerAsset'),
|
|
1781
1912
|
);
|
|
1913
|
+
const textureSources = collectMaterialTextureSources(pv, world);
|
|
1782
1914
|
const emissiveTextureHandle = resolveTexLike(
|
|
1783
1915
|
materialTextureRef(pv.emissiveTexture),
|
|
1784
1916
|
'TextureAsset',
|
|
@@ -1814,6 +1946,7 @@ export function resolveMaterialSnapshot(
|
|
|
1814
1946
|
...(materialParamSchema.length > 0 && { materialParamSchema }),
|
|
1815
1947
|
...(textureCoordinates.size > 0 && { textureCoordinates }),
|
|
1816
1948
|
...(textureHandles.size > 0 && { textureHandles }),
|
|
1949
|
+
...(textureSources.size > 0 && { textureSources }),
|
|
1817
1950
|
...(videoTextureFields.size > 0 && { videoTextureFields }),
|
|
1818
1951
|
...(samplerHandles.size > 0 && { samplerHandles }),
|
|
1819
1952
|
...(baseColorTextureHandle !== undefined && { baseColorTexture: baseColorTextureHandle }),
|
|
@@ -2117,6 +2250,9 @@ export interface DirectionalCsmConfig {
|
|
|
2117
2250
|
readonly cascadeBlend: number;
|
|
2118
2251
|
readonly mapSize: number;
|
|
2119
2252
|
readonly shadowDistance: number;
|
|
2253
|
+
readonly shadowFilter: number;
|
|
2254
|
+
readonly shadowAngularRadius: number;
|
|
2255
|
+
readonly maxPenumbraTexels: number;
|
|
2120
2256
|
}
|
|
2121
2257
|
|
|
2122
2258
|
/**
|
|
@@ -2131,6 +2267,7 @@ export interface DirectionalCsmResult {
|
|
|
2131
2267
|
readonly cascadeCount: number;
|
|
2132
2268
|
readonly cascadeBlend: number;
|
|
2133
2269
|
readonly shadowMapSize: number;
|
|
2270
|
+
readonly directionalShadowQuality: DirectionalShadowQuality;
|
|
2134
2271
|
}
|
|
2135
2272
|
|
|
2136
2273
|
/**
|
|
@@ -2163,9 +2300,6 @@ export function computeDirectionalCsm(
|
|
|
2163
2300
|
|
|
2164
2301
|
// PSSM split planes: [camera near, shadowDistance], not the camera far.
|
|
2165
2302
|
const splits = pssmSplit(sNear, sFar, cascadeCount, config.splitLambda);
|
|
2166
|
-
const paddedSplits = new Float32Array(4);
|
|
2167
|
-
for (let i = 0; i < splits.length; i++) paddedSplits[i] = splits[i] ?? 0;
|
|
2168
|
-
|
|
2169
2303
|
// Light view matrix: camera at origin, looking along the light direction.
|
|
2170
2304
|
const lightDirN = vec3.normalize(vec3.create(), direction);
|
|
2171
2305
|
const lightTarget = vec3.create(lightDirN[0] ?? 0, lightDirN[1] ?? 0, lightDirN[2] ?? 0);
|
|
@@ -2196,6 +2330,7 @@ export function computeDirectionalCsm(
|
|
|
2196
2330
|
mat4.multiply(cameraVP, camProj, camView);
|
|
2197
2331
|
|
|
2198
2332
|
const resultLightViewProjs: Float32Array[] = [];
|
|
2333
|
+
const cascadeFits: DirectionalShadowCascadeFit[] = [];
|
|
2199
2334
|
|
|
2200
2335
|
// bug-20260619 RC-2 (AC-05): toward-light Z reach. Extend the near (toward-
|
|
2201
2336
|
// light) bound of EVERY cascade to the toward-light extreme of the WHOLE
|
|
@@ -2259,16 +2394,34 @@ export function computeDirectionalCsm(
|
|
|
2259
2394
|
// lightViewProj = orthoProj * lightView — pure clip-space [-1,1]. Atlas
|
|
2260
2395
|
// tile placement is handled by the per-cascade viewport + fragment-side UV
|
|
2261
2396
|
// math (evalDirectional), so shadow_caster.gl_Position stays clip-space.
|
|
2262
|
-
|
|
2397
|
+
cascadeFits.push({
|
|
2398
|
+
split: splits[cIdx] ?? sFar,
|
|
2399
|
+
minX,
|
|
2400
|
+
maxX,
|
|
2401
|
+
minY,
|
|
2402
|
+
maxY,
|
|
2403
|
+
minZ,
|
|
2404
|
+
maxZ,
|
|
2405
|
+
});
|
|
2263
2406
|
resultLightViewProjs.push(new Float32Array(mat4.multiply(mat4.create(), orthoProj, lightView)));
|
|
2264
2407
|
}
|
|
2265
2408
|
|
|
2409
|
+
const projection = projectDirectionalShadow(
|
|
2410
|
+
cascadeFits,
|
|
2411
|
+
config.mapSize,
|
|
2412
|
+
config.shadowFilter ?? 2,
|
|
2413
|
+
config.shadowAngularRadius ?? 0.00465,
|
|
2414
|
+
config.maxPenumbraTexels ?? 32,
|
|
2415
|
+
);
|
|
2416
|
+
if (projection === undefined) return null;
|
|
2417
|
+
|
|
2266
2418
|
return {
|
|
2267
2419
|
lightViewProj: resultLightViewProjs,
|
|
2268
|
-
splitPlanes:
|
|
2420
|
+
splitPlanes: projection.splitPlanes,
|
|
2269
2421
|
cascadeCount,
|
|
2270
2422
|
cascadeBlend: config.cascadeBlend,
|
|
2271
2423
|
shadowMapSize: config.mapSize,
|
|
2424
|
+
directionalShadowQuality: projection.directionalShadowQuality,
|
|
2272
2425
|
};
|
|
2273
2426
|
}
|
|
2274
2427
|
|
|
@@ -2300,6 +2453,161 @@ export interface PreparedExtractContext {
|
|
|
2300
2453
|
readonly visibility: VisibilitySnapshot;
|
|
2301
2454
|
}
|
|
2302
2455
|
|
|
2456
|
+
function collectCameraSnapshots(world: World): CameraSnapshot[] {
|
|
2457
|
+
const worldInternal = createWorldInternalView(world);
|
|
2458
|
+
const cameras: CameraSnapshot[] = [];
|
|
2459
|
+
const cameraQuery = world
|
|
2460
|
+
.query({ read: [Camera], optional: [MotionBlur], with: [Transform] })
|
|
2461
|
+
.unwrap();
|
|
2462
|
+
for (const row of cameraQuery) {
|
|
2463
|
+
const cam = row.get(Camera);
|
|
2464
|
+
const motionBlur = row.get(MotionBlur);
|
|
2465
|
+
const entity = row.entity;
|
|
2466
|
+
const view = worldInternal._getArrayView(entity, Transform, 'world');
|
|
2467
|
+
if (view === undefined) continue;
|
|
2468
|
+
const worldMat = new Float32Array(view);
|
|
2469
|
+
const target = resolveCameraTarget(world, cam.target);
|
|
2470
|
+
cameras.push({
|
|
2471
|
+
entityKey: entity as number,
|
|
2472
|
+
...(target === undefined ? {} : { target }),
|
|
2473
|
+
historyVersion: cam.historyVersion,
|
|
2474
|
+
position: mat4.getTranslation(vec3.create(), worldMat),
|
|
2475
|
+
world: worldMat,
|
|
2476
|
+
fov: cam.fov,
|
|
2477
|
+
aspect: cam.aspect,
|
|
2478
|
+
near: cam.near,
|
|
2479
|
+
far: cam.far,
|
|
2480
|
+
projection: cameraProjectionFromF32(cam.projection),
|
|
2481
|
+
orthoLeft: cam.left,
|
|
2482
|
+
orthoRight: cam.right,
|
|
2483
|
+
orthoBottom: cam.bottom,
|
|
2484
|
+
orthoTop: cam.top,
|
|
2485
|
+
tonemap: tonemapFromF32(cam.tonemap),
|
|
2486
|
+
exposure: cam.exposure,
|
|
2487
|
+
whitePoint: cam.whitePoint,
|
|
2488
|
+
antialias: antialiasFromF32(cam.antialias),
|
|
2489
|
+
bloom: bloomEnabledFromF32(cam.bloom),
|
|
2490
|
+
bloomThreshold: cam.bloomThreshold,
|
|
2491
|
+
bloomIntensity: cam.bloomIntensity,
|
|
2492
|
+
bloomBlurRadius: cam.bloomBlurRadius,
|
|
2493
|
+
clearColor: [
|
|
2494
|
+
cam.clearColor[0] ?? 0,
|
|
2495
|
+
cam.clearColor[1] ?? 0,
|
|
2496
|
+
cam.clearColor[2] ?? 0,
|
|
2497
|
+
cam.clearColor[3] ?? 1,
|
|
2498
|
+
],
|
|
2499
|
+
...(motionBlur === undefined
|
|
2500
|
+
? {}
|
|
2501
|
+
: {
|
|
2502
|
+
motionBlur: {
|
|
2503
|
+
shutterAngle: motionBlur.shutterAngle,
|
|
2504
|
+
maxRadiusPixels: motionBlur.maxRadiusPixels,
|
|
2505
|
+
sampleCount: motionBlur.sampleCount,
|
|
2506
|
+
},
|
|
2507
|
+
}),
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
return cameras;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
function collectCubeCameraSnapshots(world: World): CubeCameraSnapshot[] {
|
|
2514
|
+
const worldInternal = createWorldInternalView(world);
|
|
2515
|
+
const snapshots: CubeCameraSnapshot[] = [];
|
|
2516
|
+
const query = world.query({ read: [CubeCamera], with: [Transform] }).unwrap();
|
|
2517
|
+
for (const row of query) {
|
|
2518
|
+
const camera = row.get(CubeCamera);
|
|
2519
|
+
const target = resolveCameraTarget(world, camera.target);
|
|
2520
|
+
if (target === undefined) continue;
|
|
2521
|
+
const view = worldInternal._getArrayView(row.entity, Transform, 'world');
|
|
2522
|
+
if (view === undefined) continue;
|
|
2523
|
+
snapshots.push({
|
|
2524
|
+
entityKey: row.entity as number,
|
|
2525
|
+
target,
|
|
2526
|
+
position: [view[12] ?? 0, view[13] ?? 0, view[14] ?? 0],
|
|
2527
|
+
near: camera.near,
|
|
2528
|
+
far: camera.far,
|
|
2529
|
+
updateIntent: cubeCameraUpdateIntentFromF32(camera.updateIntent),
|
|
2530
|
+
requestVersion: camera.requestVersion,
|
|
2531
|
+
faceBudget: camera.faceBudget,
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
return snapshots;
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
function collectReflectionProbeFacts(world: World): ReflectionProbeFact[] {
|
|
2538
|
+
const worldInternal = createWorldInternalView(world);
|
|
2539
|
+
const facts: ReflectionProbeFact[] = [];
|
|
2540
|
+
const query = world.query({ read: [ReflectionProbe], with: [Transform] }).unwrap();
|
|
2541
|
+
for (const row of query) {
|
|
2542
|
+
const probe = row.get(ReflectionProbe);
|
|
2543
|
+
const view = worldInternal._getArrayView(row.entity, Transform, 'world');
|
|
2544
|
+
if (view === undefined) {
|
|
2545
|
+
continue;
|
|
2546
|
+
}
|
|
2547
|
+
facts.push({
|
|
2548
|
+
worldId: 0,
|
|
2549
|
+
entityKey: row.entity as number,
|
|
2550
|
+
center: [view[12] ?? 0, view[13] ?? 0, view[14] ?? 0],
|
|
2551
|
+
halfExtents: [
|
|
2552
|
+
probe.halfExtents[0] ?? 0,
|
|
2553
|
+
probe.halfExtents[1] ?? 0,
|
|
2554
|
+
probe.halfExtents[2] ?? 0,
|
|
2555
|
+
],
|
|
2556
|
+
priority: probe.priority,
|
|
2557
|
+
intensity: probe.intensity,
|
|
2558
|
+
resolution: probe.resolution,
|
|
2559
|
+
revision: probe.invalidationVersion,
|
|
2560
|
+
updateIntent: reflectionProbeUpdateIntentFromF32(probe.updateIntent),
|
|
2561
|
+
invalidationVersion: probe.invalidationVersion,
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
return facts;
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
interface CameraRoleSelection {
|
|
2568
|
+
readonly display: readonly CameraSnapshot[];
|
|
2569
|
+
readonly auxiliary: readonly CameraSnapshot[];
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
function selectCameraRoles(world: World): CameraRoleSelection {
|
|
2573
|
+
const cameras = collectCameraSnapshots(world);
|
|
2574
|
+
const displayEntities = cameras
|
|
2575
|
+
.filter((camera) => camera.target === undefined)
|
|
2576
|
+
.map((camera) => camera.entityKey ?? 0);
|
|
2577
|
+
const activeCameraIndex = selectActiveCameraIndex(
|
|
2578
|
+
displayEntities,
|
|
2579
|
+
getActiveCamera(world)?.entity,
|
|
2580
|
+
);
|
|
2581
|
+
const displayEntityKey = displayEntities[activeCameraIndex] ?? displayEntities[0];
|
|
2582
|
+
const display =
|
|
2583
|
+
displayEntityKey === undefined
|
|
2584
|
+
? []
|
|
2585
|
+
: cameras.filter((camera) => camera.entityKey === displayEntityKey);
|
|
2586
|
+
const candidates: CameraTargetCandidate[] = cameras.map((camera) => ({
|
|
2587
|
+
worldId: 0,
|
|
2588
|
+
entityKey: camera.entityKey ?? 0,
|
|
2589
|
+
...(camera.target === undefined ? {} : { target: camera.target }),
|
|
2590
|
+
requestVersion: 0,
|
|
2591
|
+
update: 'continuous',
|
|
2592
|
+
}));
|
|
2593
|
+
const selected = selectCameraTargetViews(candidates, {
|
|
2594
|
+
...(displayEntityKey === undefined ? {} : { displayEntityKey }),
|
|
2595
|
+
budget: 1,
|
|
2596
|
+
});
|
|
2597
|
+
const auxiliary = selected.auxiliary.flatMap((candidate) =>
|
|
2598
|
+
cameras.filter((camera) => camera.entityKey === candidate.entityKey),
|
|
2599
|
+
);
|
|
2600
|
+
return { display, auxiliary };
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
export function extractCameraSnapshots(world: World): CameraSnapshot[] {
|
|
2604
|
+
return [...selectCameraRoles(world).display];
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
export function extractCameraTargetSnapshots(world: World): readonly CameraSnapshot[] {
|
|
2608
|
+
return selectCameraRoles(world).auxiliary;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2303
2611
|
function tonemapParams(camera: CameraSnapshot): Uint8Array {
|
|
2304
2612
|
const bytes = new ArrayBuffer(16);
|
|
2305
2613
|
const floats = new Float32Array(bytes);
|
|
@@ -2335,6 +2643,21 @@ export function prepareExtractContext(
|
|
|
2335
2643
|
}
|
|
2336
2644
|
|
|
2337
2645
|
const fogQueries = new WeakMap<World, Query<readonly [typeof Fog]>>();
|
|
2646
|
+
const fogLkgStates = new WeakMap<
|
|
2647
|
+
World,
|
|
2648
|
+
{ lkg: FogFrame | undefined; failure: FogSelectionFailure | undefined }
|
|
2649
|
+
>();
|
|
2650
|
+
|
|
2651
|
+
function fogState(world: World): {
|
|
2652
|
+
lkg: FogFrame | undefined;
|
|
2653
|
+
failure: FogSelectionFailure | undefined;
|
|
2654
|
+
} {
|
|
2655
|
+
const existing = fogLkgStates.get(world);
|
|
2656
|
+
if (existing !== undefined) return existing;
|
|
2657
|
+
const created = { lkg: undefined, failure: undefined };
|
|
2658
|
+
fogLkgStates.set(world, created);
|
|
2659
|
+
return created;
|
|
2660
|
+
}
|
|
2338
2661
|
|
|
2339
2662
|
function selectFogFrame(world: World): FogFrame | undefined {
|
|
2340
2663
|
// Query compilation is owned once per World; Query refreshes its matched
|
|
@@ -2362,10 +2685,22 @@ function selectFogFrame(world: World): FogFrame | undefined {
|
|
|
2362
2685
|
suns: [],
|
|
2363
2686
|
lane: 'direct',
|
|
2364
2687
|
});
|
|
2365
|
-
|
|
2688
|
+
const state = fogState(world);
|
|
2689
|
+
if (fogSelection.ok) {
|
|
2690
|
+
state.lkg = fogSelection.value.fog;
|
|
2691
|
+
state.failure = undefined;
|
|
2692
|
+
return fogSelection.value.fog;
|
|
2693
|
+
}
|
|
2694
|
+
state.failure = {
|
|
2695
|
+
code: fogSelection.error.code,
|
|
2696
|
+
expected: fogSelection.error.expected,
|
|
2697
|
+
hint: fogSelection.error.hint,
|
|
2698
|
+
detail: Object.freeze({ ...fogSelection.error.detail }),
|
|
2699
|
+
};
|
|
2366
2700
|
routeWorldError(world, fogSelection.error, {
|
|
2367
2701
|
systemName: 'RenderSystem.extract (fog-selection)',
|
|
2368
2702
|
});
|
|
2703
|
+
if (state.lkg !== undefined) return state.lkg;
|
|
2369
2704
|
throw fogSelection.error;
|
|
2370
2705
|
}
|
|
2371
2706
|
|
|
@@ -2697,7 +3032,8 @@ export function extractFrames(
|
|
|
2697
3032
|
let shadowMapSize: number | undefined;
|
|
2698
3033
|
let depthBias: number | undefined;
|
|
2699
3034
|
let normalBias: number | undefined;
|
|
2700
|
-
let
|
|
3035
|
+
let directionalShadowQuality: DirectionalShadowQuality | undefined;
|
|
3036
|
+
let directionalShadowError: LightValidationError | undefined;
|
|
2701
3037
|
const pointShadow: PointShadowSnapshot[] = [];
|
|
2702
3038
|
// bug-20260710-editor-cross-world-shadow: carry the raw CSM config +
|
|
2703
3039
|
// direction of the first-hit directional so the merge layer can recompute
|
|
@@ -2719,7 +3055,8 @@ export function extractFrames(
|
|
|
2719
3055
|
shadowMapSize = f.lights.shadowMapSize;
|
|
2720
3056
|
depthBias = f.lights.depthBias;
|
|
2721
3057
|
normalBias = f.lights.normalBias;
|
|
2722
|
-
|
|
3058
|
+
directionalShadowQuality = f.lights.directionalShadowQuality;
|
|
3059
|
+
directionalShadowError = f.lights.directionalShadowError;
|
|
2723
3060
|
directionalCsmConfig = f.lights.directionalCsmConfig;
|
|
2724
3061
|
directionalCsmDirection = f.lights.directionalCsmDirection;
|
|
2725
3062
|
}
|
|
@@ -2738,6 +3075,9 @@ export function extractFrames(
|
|
|
2738
3075
|
if (succeededIndices[fi] === resourceOwner) resourceOwnerFrame = succeededFrames[fi];
|
|
2739
3076
|
}
|
|
2740
3077
|
const cameras = cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.cameras] : [];
|
|
3078
|
+
const auxiliaryCameras =
|
|
3079
|
+
cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.auxiliaryCameras] : [];
|
|
3080
|
+
const cubeCameras = cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.cubeCameras] : [];
|
|
2741
3081
|
|
|
2742
3082
|
// bug-20260710-editor-cross-world-shadow: RECOMPUTE directional CSM matrices
|
|
2743
3083
|
// against the surfaced camera. In a single-world app the per-world extract
|
|
@@ -2776,6 +3116,7 @@ export function extractFrames(
|
|
|
2776
3116
|
cascadeCount = csm.cascadeCount;
|
|
2777
3117
|
cascadeBlend = csm.cascadeBlend;
|
|
2778
3118
|
shadowMapSize = csm.shadowMapSize;
|
|
3119
|
+
directionalShadowQuality = csm.directionalShadowQuality;
|
|
2779
3120
|
}
|
|
2780
3121
|
// csm === null (no surfaced camera at all): keep the per-world carry —
|
|
2781
3122
|
// there is no better data, and a cameraless frame renders nothing anyway.
|
|
@@ -2793,7 +3134,8 @@ export function extractFrames(
|
|
|
2793
3134
|
shadowMapSize,
|
|
2794
3135
|
depthBias,
|
|
2795
3136
|
normalBias,
|
|
2796
|
-
|
|
3137
|
+
directionalShadowQuality,
|
|
3138
|
+
directionalShadowError,
|
|
2797
3139
|
pointShadow,
|
|
2798
3140
|
directionalCsmConfig,
|
|
2799
3141
|
directionalCsmDirection,
|
|
@@ -2804,6 +3146,7 @@ export function extractFrames(
|
|
|
2804
3146
|
const skybox = resourceOwnerFrame?.skybox;
|
|
2805
3147
|
const skyboxCount = resourceOwnerFrame?.skyboxCount ?? 0;
|
|
2806
3148
|
const fog = resourceOwnerFrame?.fog;
|
|
3149
|
+
const fogFailure = resourceOwnerFrame?.fogFailure;
|
|
2807
3150
|
const environment = resourceOwnerFrame?.environment;
|
|
2808
3151
|
// User-authored PostProcessParams entities are singleton scene resources, so
|
|
2809
3152
|
// they come from the resourceOwner world (holistic snapshot selection).
|
|
@@ -2843,9 +3186,16 @@ export function extractFrames(
|
|
|
2843
3186
|
const visibilityStats = {
|
|
2844
3187
|
explicitlyHidden: succeededFrames.reduce((s, f) => s + f.visibilityStats.explicitlyHidden, 0),
|
|
2845
3188
|
};
|
|
3189
|
+
const reflectionProbes = succeededFrames.flatMap((f, index) => {
|
|
3190
|
+
const worldId = succeededIndices[index] ?? 0;
|
|
3191
|
+
return (f.reflectionProbes ?? []).map((probe) => ({ ...probe, worldId }));
|
|
3192
|
+
});
|
|
2846
3193
|
|
|
2847
3194
|
return {
|
|
2848
3195
|
cameras,
|
|
3196
|
+
auxiliaryCameras,
|
|
3197
|
+
cubeCameras,
|
|
3198
|
+
reflectionProbes,
|
|
2849
3199
|
lights,
|
|
2850
3200
|
...(volumetricFog === undefined
|
|
2851
3201
|
? {}
|
|
@@ -2857,6 +3207,7 @@ export function extractFrames(
|
|
|
2857
3207
|
skybox,
|
|
2858
3208
|
skyboxCount,
|
|
2859
3209
|
fog,
|
|
3210
|
+
...(fogFailure === undefined ? {} : { fogFailure }),
|
|
2860
3211
|
environment,
|
|
2861
3212
|
environmentReady: resourceOwnerFrame !== undefined && environment !== undefined,
|
|
2862
3213
|
frustumStats,
|
|
@@ -2952,6 +3303,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2952
3303
|
// established at the frame boundary; record never revisits World state.
|
|
2953
3304
|
const fog =
|
|
2954
3305
|
resourceOwnerFog === undefined ? selectFogFrame(world) : (resourceOwnerFog ?? undefined);
|
|
3306
|
+
const fogFailure = fogState(world).failure;
|
|
2955
3307
|
const visibility = context.visibility.hasAnyHiddenIntent ? context.visibility : undefined;
|
|
2956
3308
|
const skinPaletteAllocator = pipelineState?.skinPaletteAllocator ?? null;
|
|
2957
3309
|
|
|
@@ -2971,7 +3323,11 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2971
3323
|
const worldInternal = createWorldInternalView(world);
|
|
2972
3324
|
let volumetricFog = extractVolumeSnapshot(world, assets, worldInternal);
|
|
2973
3325
|
|
|
2974
|
-
const
|
|
3326
|
+
const cameraRoles = selectCameraRoles(world);
|
|
3327
|
+
const cameras = [...cameraRoles.display];
|
|
3328
|
+
const auxiliaryCameras = cameraRoles.auxiliary;
|
|
3329
|
+
const cubeCameras = collectCubeCameraSnapshots(world);
|
|
3330
|
+
const reflectionProbes = collectReflectionProbeFacts(world);
|
|
2975
3331
|
|
|
2976
3332
|
// Three-query union (M2 / w16 / AC-03): directional has no Transform
|
|
2977
3333
|
// dependency (sun-like infinite-source semantics); point + spot pull
|
|
@@ -2992,7 +3348,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2992
3348
|
depthBias: number;
|
|
2993
3349
|
normalBias: number;
|
|
2994
3350
|
shadowDistance: number;
|
|
2995
|
-
|
|
3351
|
+
shadowFilter: number;
|
|
3352
|
+
shadowAngularRadius: number;
|
|
3353
|
+
maxPenumbraTexels: number;
|
|
2996
3354
|
}
|
|
2997
3355
|
| undefined;
|
|
2998
3356
|
for (const row of directionalLightQuery) {
|
|
@@ -3022,11 +3380,29 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3022
3380
|
depthBias: l.depthBias,
|
|
3023
3381
|
normalBias: l.normalBias,
|
|
3024
3382
|
shadowDistance: l.shadowDistance,
|
|
3025
|
-
|
|
3383
|
+
shadowFilter: l.shadowFilter,
|
|
3384
|
+
shadowAngularRadius: l.shadowAngularRadius,
|
|
3385
|
+
maxPenumbraTexels: l.maxPenumbraTexels,
|
|
3026
3386
|
};
|
|
3027
3387
|
}
|
|
3028
3388
|
}
|
|
3029
3389
|
|
|
3390
|
+
let directionalShadowError: LightValidationError | undefined;
|
|
3391
|
+
if (directional !== undefined && firstHitShadowFields !== undefined) {
|
|
3392
|
+
const validation = validateDirectionalLightData({
|
|
3393
|
+
direction: directional.direction,
|
|
3394
|
+
castShadow: firstHitCastShadow,
|
|
3395
|
+
...firstHitShadowFields,
|
|
3396
|
+
});
|
|
3397
|
+
if (!validation.ok) {
|
|
3398
|
+
directionalShadowError = validation.error;
|
|
3399
|
+
worldInternal._routeError(directionalShadowError, {
|
|
3400
|
+
severity: Severity.Error,
|
|
3401
|
+
systemName: 'RenderSystem.extract (directional-shadow)',
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3030
3406
|
const pointSnapshots: PointLightSnapshot[] = [];
|
|
3031
3407
|
// feat-20260612-point-light-shadows-urp-hdrp M4 / T-M4-4: track entity per
|
|
3032
3408
|
// pointSnapshots index so the post-extract pointShadow join can stamp
|
|
@@ -3282,6 +3658,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3282
3658
|
let cascadeCount: number | undefined;
|
|
3283
3659
|
let cascadeBlend: number | undefined;
|
|
3284
3660
|
let shadowMapSize: number | undefined;
|
|
3661
|
+
let directionalShadowQuality: DirectionalShadowQuality | undefined;
|
|
3285
3662
|
let directionalCsmConfig: DirectionalCsmConfig | undefined;
|
|
3286
3663
|
let directionalCsmDirection: Vec3 | undefined;
|
|
3287
3664
|
|
|
@@ -3307,7 +3684,11 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3307
3684
|
// feat-20260621 M2: CSM computation gated on castShadow from the
|
|
3308
3685
|
// merged DirectionalLight. castShadow defaults to true (first-hit-wins
|
|
3309
3686
|
// semantics, D-6 no cardinality cap).
|
|
3310
|
-
if (
|
|
3687
|
+
if (
|
|
3688
|
+
directional !== undefined &&
|
|
3689
|
+
firstHitCastShadow !== false &&
|
|
3690
|
+
directionalShadowError === undefined
|
|
3691
|
+
) {
|
|
3311
3692
|
const dirSnapshot = directional;
|
|
3312
3693
|
const sf = firstHitShadowFields;
|
|
3313
3694
|
if (sf !== undefined) {
|
|
@@ -3317,6 +3698,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3317
3698
|
cascadeBlend: sf.cascadeBlend,
|
|
3318
3699
|
mapSize: sf.mapSize,
|
|
3319
3700
|
shadowDistance: sf.shadowDistance,
|
|
3701
|
+
shadowFilter: sf.shadowFilter,
|
|
3702
|
+
shadowAngularRadius: sf.shadowAngularRadius,
|
|
3703
|
+
maxPenumbraTexels: sf.maxPenumbraTexels,
|
|
3320
3704
|
};
|
|
3321
3705
|
directionalCsmDirection = dirSnapshot.direction;
|
|
3322
3706
|
const csm = computeDirectionalCsm(dirSnapshot.direction, directionalCsmConfig, cameraData);
|
|
@@ -3331,13 +3715,19 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3331
3715
|
if (csm !== null) {
|
|
3332
3716
|
lightViewProj = csm.lightViewProj;
|
|
3333
3717
|
splitPlanes = csm.splitPlanes;
|
|
3718
|
+
directionalShadowQuality = csm.directionalShadowQuality;
|
|
3334
3719
|
} else {
|
|
3335
3720
|
const sNear = cameraData?.near ?? 0.1;
|
|
3336
3721
|
const splits = pssmSplit(sNear, sf.shadowDistance, cascadeCount, sf.splitLambda);
|
|
3337
|
-
const padded = new Float32Array(
|
|
3338
|
-
for (let i = 0; i < splits.length; i++) padded[i] = splits[i] ?? 0;
|
|
3722
|
+
const padded = new Float32Array(16);
|
|
3723
|
+
for (let i = 0; i < splits.length; i++) padded[i * 4] = splits[i] ?? 0;
|
|
3339
3724
|
splitPlanes = padded;
|
|
3340
3725
|
}
|
|
3726
|
+
directionalShadowQuality = directionalShadowQualityFromF32(
|
|
3727
|
+
sf.shadowFilter,
|
|
3728
|
+
sf.shadowAngularRadius,
|
|
3729
|
+
sf.maxPenumbraTexels,
|
|
3730
|
+
);
|
|
3341
3731
|
}
|
|
3342
3732
|
}
|
|
3343
3733
|
|
|
@@ -3346,7 +3736,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3346
3736
|
// the View UBO tail keeps a stable layout regardless of the runtime
|
|
3347
3737
|
// cascadeCount. Host-side correctness invariant: only the first
|
|
3348
3738
|
// cascadeCount slots are ever read by the WGSL kernel.
|
|
3349
|
-
const paddedSplitPlanes = new Float32Array(
|
|
3739
|
+
const paddedSplitPlanes = new Float32Array(16);
|
|
3350
3740
|
if (splitPlanes !== undefined) {
|
|
3351
3741
|
for (let i = 0; i < splitPlanes.length; i++) {
|
|
3352
3742
|
paddedSplitPlanes[i] = splitPlanes[i] ?? 0;
|
|
@@ -3356,9 +3746,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3356
3746
|
// feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-7 (plan-strategy §D-3,
|
|
3357
3747
|
// requirements §5.3): query (PointLight + PointLightShadow + Transform)
|
|
3358
3748
|
// archetype join. For each shadow caster, build 6 face VP matrices and pack
|
|
3359
|
-
// into Float32Array(96). Atlas layer assigned in spawn order
|
|
3360
|
-
// sentinel -1 is shader-side and
|
|
3361
|
-
//
|
|
3749
|
+
// into Float32Array(96). Atlas layer is assigned in spawn order while the
|
|
3750
|
+
// renderer-owned atlas has capacity; the sentinel -1 is shader-side and
|
|
3751
|
+
// applies to non-admitted PointLightShadow requests as well.
|
|
3362
3752
|
const pointShadowSnapshots: PointShadowSnapshot[] = [];
|
|
3363
3753
|
{
|
|
3364
3754
|
const pointShadowQuery = world
|
|
@@ -3386,7 +3776,13 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3386
3776
|
const mapSize = ps.mapSize;
|
|
3387
3777
|
const nearPlane = ps.nearPlane;
|
|
3388
3778
|
const farPlane = ps.farPlane;
|
|
3389
|
-
|
|
3779
|
+
// The renderer, not ECS, owns the atlas budget. Preserve every request
|
|
3780
|
+
// for inspection, but emit the shader sentinel for entries that cannot
|
|
3781
|
+
// acquire a layer so record never calls faceView outside the atlas.
|
|
3782
|
+
const layer =
|
|
3783
|
+
pointShadowSnapshots.length < SHADOW_ATLAS_DEFAULT_LAYERS
|
|
3784
|
+
? pointShadowSnapshots.length
|
|
3785
|
+
: -1;
|
|
3390
3786
|
|
|
3391
3787
|
const matrices = buildPointShadowMatrices(lightPos, nearPlane, farPlane);
|
|
3392
3788
|
const packed = new Float32Array(96);
|
|
@@ -3444,7 +3840,8 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3444
3840
|
shadowMapSize,
|
|
3445
3841
|
depthBias: firstHitCastShadow !== false ? firstHitShadowFields?.depthBias : undefined,
|
|
3446
3842
|
normalBias: firstHitCastShadow !== false ? firstHitShadowFields?.normalBias : undefined,
|
|
3447
|
-
|
|
3843
|
+
directionalShadowQuality,
|
|
3844
|
+
directionalShadowError,
|
|
3448
3845
|
pointShadow: pointShadowSnapshots,
|
|
3449
3846
|
// bug-20260710-editor-cross-world-shadow: raw CSM config + light direction
|
|
3450
3847
|
// so the merge layer can recompute matrices against the surfaced camera.
|
|
@@ -4336,6 +4733,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4336
4733
|
pv.occlusionTexture,
|
|
4337
4734
|
);
|
|
4338
4735
|
const textureCoordinates = collectMaterialTextureCoordinates(pv);
|
|
4736
|
+
const textureSources = collectMaterialTextureSources(pv, world);
|
|
4339
4737
|
const emissivePv = pv.emissive as readonly number[] | undefined;
|
|
4340
4738
|
// feat-20260625 M2 / w6: first-pass transparency flag folds into
|
|
4341
4739
|
// MaterialSnapshot.transparent so the record stage can drive the
|
|
@@ -4435,6 +4833,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4435
4833
|
...(textureCoordinates.size > 0 && { textureCoordinates }),
|
|
4436
4834
|
...(samplerHandles.size > 0 && { samplerHandles }),
|
|
4437
4835
|
...(textureHandles.size > 0 && { textureHandles }),
|
|
4836
|
+
...(textureSources.size > 0 && { textureSources }),
|
|
4438
4837
|
...(videoTextureFields.size > 0 && { videoTextureFields }),
|
|
4439
4838
|
...(baseColorTextureHandle !== undefined && {
|
|
4440
4839
|
baseColorTexture: baseColorTextureHandle,
|
|
@@ -4653,6 +5052,15 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4653
5052
|
}
|
|
4654
5053
|
}
|
|
4655
5054
|
|
|
5055
|
+
// A pbr-skin PSO requires the complete three-binding group(2) skin
|
|
5056
|
+
// projection. Do not publish a renderable or its staged dispatch when
|
|
5057
|
+
// palette extraction was unavailable; the record stage must never
|
|
5058
|
+
// reinterpret a skinned vertex stream through an ordinary mesh group.
|
|
5059
|
+
if (materialSnap.materialShaderId === 'forgeax::pbr-skin' && skinSlice === undefined) {
|
|
5060
|
+
pendingDispatch.length = 0;
|
|
5061
|
+
continue;
|
|
5062
|
+
}
|
|
5063
|
+
|
|
4656
5064
|
// feat-20260601 D-3: read the resolved world mat4 (propagateTransforms
|
|
4657
5065
|
// output) straight from the Transform.world column array view. The
|
|
4658
5066
|
// record stage copies these 16 floats into the mesh SSBO with zero
|
|
@@ -4841,6 +5249,44 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4841
5249
|
projection: identityProjection(),
|
|
4842
5250
|
} satisfies PointsLinesRetainedSnapshot);
|
|
4843
5251
|
|
|
5252
|
+
// Instances are snapshotted before the CPU visibility decision so the
|
|
5253
|
+
// culling owner can derive the union of every instance's transformed
|
|
5254
|
+
// mesh bounds. The same detached snapshot is then carried to record;
|
|
5255
|
+
// do not re-read the ECS array after culling (a structural write must
|
|
5256
|
+
// not make the cull and draw disagree within one frame).
|
|
5257
|
+
let instancesSnap: InstancesSnapshot | undefined;
|
|
5258
|
+
if (hasInstances) {
|
|
5259
|
+
const transforms = worldInternal._getArrayView(entity, Instances, 'transforms');
|
|
5260
|
+
if (transforms === undefined) {
|
|
5261
|
+
// A present component with an unavailable array is an empty
|
|
5262
|
+
// instance contribution, not an absent component. Preserve the
|
|
5263
|
+
// zero-draw contract so the record stage cannot synthesize one
|
|
5264
|
+
// identity instance from an incomplete row.
|
|
5265
|
+
instancesSnap = {
|
|
5266
|
+
transforms: new Float32Array(),
|
|
5267
|
+
instanceCount: 0,
|
|
5268
|
+
cacheKey: entity,
|
|
5269
|
+
archVersion,
|
|
5270
|
+
};
|
|
5271
|
+
} else {
|
|
5272
|
+
const actualLength = transforms.length;
|
|
5273
|
+
if (actualLength % 16 !== 0) {
|
|
5274
|
+
worldInternal._routeError(new InstanceTransformsStrideMismatchError(actualLength), {
|
|
5275
|
+
severity: Severity.Error,
|
|
5276
|
+
systemName: 'RenderSystem.extract (Instances stride)',
|
|
5277
|
+
});
|
|
5278
|
+
continue;
|
|
5279
|
+
}
|
|
5280
|
+
const snapshotCopy = new Float32Array(transforms);
|
|
5281
|
+
instancesSnap = {
|
|
5282
|
+
transforms: snapshotCopy,
|
|
5283
|
+
instanceCount: actualLength / 16,
|
|
5284
|
+
cacheKey: entity,
|
|
5285
|
+
archVersion,
|
|
5286
|
+
};
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
|
|
4844
5290
|
let nonIndexedFirst = 0;
|
|
4845
5291
|
const gpuDrivenDraws = gpuDrivenSubmeshes.flatMap((submesh) => {
|
|
4846
5292
|
const drawMaterial = materialsArr[submesh.materialSlot] ?? materialSnap;
|
|
@@ -4873,6 +5319,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4873
5319
|
entityKey: entity,
|
|
4874
5320
|
...(skinSlice !== undefined ? { skin: skinSlice } : {}),
|
|
4875
5321
|
...(morph !== undefined ? { morph } : {}),
|
|
5322
|
+
...(instancesSnap !== undefined ? { instances: instancesSnap } : {}),
|
|
4876
5323
|
...(spriteInstancesSnap !== undefined ? { spriteInstances: spriteInstancesSnap } : {}),
|
|
4877
5324
|
...(pointsLines !== undefined ? { pointsLines } : {}),
|
|
4878
5325
|
};
|
|
@@ -4884,66 +5331,70 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4884
5331
|
// producer-owned finite local-space AABBs. Culling is unconditional
|
|
4885
5332
|
// engine behavior; there is no per-entity opt-out.
|
|
4886
5333
|
if (localAabb !== undefined) {
|
|
4887
|
-
//
|
|
4888
|
-
//
|
|
4889
|
-
//
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
frustumCulled += 1;
|
|
4911
|
-
continue;
|
|
4912
|
-
}
|
|
4913
|
-
}
|
|
4914
|
-
|
|
4915
|
-
if (hasInstances) {
|
|
4916
|
-
const transforms = worldInternal._getArrayView(entity, Instances, 'transforms');
|
|
4917
|
-
if (transforms === undefined) {
|
|
4918
|
-
flushPendingDispatch(pendingDispatch);
|
|
4919
|
-
renderables.push(baseRenderable);
|
|
5334
|
+
// An empty or malformed Instances payload has no meaningful bounds.
|
|
5335
|
+
// Keep it conservative (the draw path later emits zero instances)
|
|
5336
|
+
// instead of manufacturing the entity's identity bounds. For a
|
|
5337
|
+
// valid payload, derive one world-space union for CPU culling while
|
|
5338
|
+
// leaving the mesh-local AABB untouched for GPU per-instance cull.
|
|
5339
|
+
const derivedInstancesBounds =
|
|
5340
|
+
instancesSnap === undefined || instancesSnap.instanceCount === 0
|
|
5341
|
+
? undefined
|
|
5342
|
+
: deriveInstancesUnionBounds({
|
|
5343
|
+
meshAabb: cullingLocalAabb,
|
|
5344
|
+
entityWorld: transformSnap.world,
|
|
5345
|
+
transforms: instancesSnap.transforms,
|
|
5346
|
+
});
|
|
5347
|
+
if (instancesSnap?.instanceCount === 0) {
|
|
5348
|
+
// No instance can contribute a visible bound. The retained
|
|
5349
|
+
// snapshot still records instanceCount=0 for the record stage.
|
|
5350
|
+
} else if (
|
|
5351
|
+
hasInstances &&
|
|
5352
|
+
(instancesSnap === undefined || derivedInstancesBounds === undefined)
|
|
5353
|
+
) {
|
|
5354
|
+
// Missing/malformed derived facts are a conservative no-cull
|
|
5355
|
+
// result. The stride validator above already rejects malformed
|
|
5356
|
+
// lengths; this branch covers non-finite producer matrices/AABB.
|
|
4920
5357
|
} else {
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
5358
|
+
// feat-20260601 D-3: cull AABB uses the resolved world mat4
|
|
5359
|
+
// directly (no compose) -- same source the record stage feeds
|
|
5360
|
+
// the mesh SSBO, so cull stays same-source with render (AC-05).
|
|
5361
|
+
const worldAabb = box3.create();
|
|
5362
|
+
if (derivedInstancesBounds !== undefined) {
|
|
5363
|
+
worldAabb[0] = derivedInstancesBounds[0] ?? 0;
|
|
5364
|
+
worldAabb[1] = derivedInstancesBounds[1] ?? 0;
|
|
5365
|
+
worldAabb[2] = derivedInstancesBounds[2] ?? 0;
|
|
5366
|
+
worldAabb[3] = derivedInstancesBounds[3] ?? 0;
|
|
5367
|
+
worldAabb[4] = derivedInstancesBounds[4] ?? 0;
|
|
5368
|
+
worldAabb[5] = derivedInstancesBounds[5] ?? 0;
|
|
5369
|
+
} else {
|
|
5370
|
+
box3.transformBox3(worldAabb, cullingLocalAabb, transformSnap.world);
|
|
5371
|
+
}
|
|
4927
5372
|
|
|
5373
|
+
// Test against all cameras. Entity is visible if one camera
|
|
5374
|
+
// frustum intersects the world-space AABB (or planes are empty
|
|
5375
|
+
// from degenerate projection).
|
|
5376
|
+
frustumTotal += 1;
|
|
5377
|
+
let visible = frustumPlanes.length === 0;
|
|
5378
|
+
for (let ci = 0; ci < frustumPlanes.length; ci++) {
|
|
5379
|
+
const planes = frustumPlanes[ci] as Float32Array;
|
|
5380
|
+
if (planes.length === 0) {
|
|
5381
|
+
visible = true;
|
|
5382
|
+
break;
|
|
5383
|
+
}
|
|
5384
|
+
if (frustum.intersectsBox(planes as frustum.Frustum, worldAabb as box3.Box3Like)) {
|
|
5385
|
+
visible = true;
|
|
5386
|
+
break;
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
5389
|
+
if (!visible) {
|
|
5390
|
+
frustumCulled += 1;
|
|
4928
5391
|
continue;
|
|
4929
5392
|
}
|
|
4930
|
-
const snapshotCopy = new Float32Array(transforms);
|
|
4931
|
-
const instanceCount = Math.max(1, Math.floor(actualLength / 16));
|
|
4932
|
-
flushPendingDispatch(pendingDispatch);
|
|
4933
|
-
renderables.push({
|
|
4934
|
-
...baseRenderable,
|
|
4935
|
-
instances: {
|
|
4936
|
-
transforms: snapshotCopy,
|
|
4937
|
-
instanceCount,
|
|
4938
|
-
cacheKey: entity,
|
|
4939
|
-
archVersion,
|
|
4940
|
-
},
|
|
4941
|
-
});
|
|
4942
5393
|
}
|
|
4943
|
-
} else {
|
|
4944
|
-
flushPendingDispatch(pendingDispatch);
|
|
4945
|
-
renderables.push(baseRenderable);
|
|
4946
5394
|
}
|
|
5395
|
+
|
|
5396
|
+
flushPendingDispatch(pendingDispatch);
|
|
5397
|
+
renderables.push(baseRenderable);
|
|
4947
5398
|
}
|
|
4948
5399
|
|
|
4949
5400
|
// feat-20260520-2d-sprite-layer-mvp M-3 / w22 + w25: finalise the
|
|
@@ -4978,10 +5429,12 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4978
5429
|
// whitePoint / tonemap` onto the SAME unified params channel custom
|
|
4979
5430
|
// post-processes use — `Camera.exposure` stays the AI-user-facing SSOT (D-5),
|
|
4980
5431
|
// the engine itself acts as the provider for the Standard tonemap shader
|
|
4981
|
-
// id. The 16B layout
|
|
4982
|
-
// (render-system-record.ts pre-w14): Float32 [exposure, whitePoint, _,
|
|
4983
|
-
// with the mode u32 occupying the third 4-byte slot via
|
|
4984
|
-
// camera.ts).
|
|
5432
|
+
// id. The 16B layout keeps the prior recordTonemapPass packing
|
|
5433
|
+
// (render-system-record.ts pre-w14): Float32 [exposure, whitePoint, _,
|
|
5434
|
+
// ditherEnabled], with the mode u32 occupying the third 4-byte slot via
|
|
5435
|
+
// tonemapToU32 (SSOT in camera.ts). The final Output Transform makes a
|
|
5436
|
+
// per-pass copy and writes ditherEnabled at byte offset 12; the extracted
|
|
5437
|
+
// camera snapshot remains the SSOT. Run AFTER the user-entity collection above so the engine's
|
|
4985
5438
|
// built-in provider is authoritative for its own reserved key (a user entity
|
|
4986
5439
|
// can never shadow the Standard tonemap identity. The single active camera mirrors
|
|
4987
5440
|
// recordFrame's `activeCameras[0]` selection.
|
|
@@ -4992,6 +5445,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4992
5445
|
|
|
4993
5446
|
return {
|
|
4994
5447
|
cameras,
|
|
5448
|
+
auxiliaryCameras,
|
|
5449
|
+
cubeCameras,
|
|
5450
|
+
reflectionProbes,
|
|
4995
5451
|
lights,
|
|
4996
5452
|
environment,
|
|
4997
5453
|
environmentReady: true,
|
|
@@ -5003,6 +5459,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
5003
5459
|
skybox,
|
|
5004
5460
|
skyboxCount,
|
|
5005
5461
|
fog,
|
|
5462
|
+
...(fogFailure === undefined ? {} : { fogFailure }),
|
|
5006
5463
|
frustumStats: { culled: frustumCulled, total: frustumTotal },
|
|
5007
5464
|
visibilityStats: { explicitlyHidden: explicitlyHidden.size },
|
|
5008
5465
|
postProcessParams,
|