@forgeax/engine-render 0.1.19 → 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 +34 -20
- 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-RJRUS55L.mjs → chunk-5YADUCA7.mjs} +1896 -1624
- package/dist/chunk-5YADUCA7.mjs.map +1 -0
- package/dist/{chunk-LOSO4CTV.mjs → chunk-6OPXDQ2E.mjs} +423 -46
- package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
- package/dist/{chunk-IPXCABEP.mjs → chunk-BXBQQKFQ.mjs} +3 -9
- package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
- package/dist/chunk-FS2KNUQH.mjs +1294 -0
- 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/components/spot-light.d.ts +12 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +8552 -1612
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +19 -2
- package/dist/device/gpu-residency.d.ts.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 +209 -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/gpu-usage.d.ts +2 -0
- package/dist/gpu-usage.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 +35 -4
- 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 +30 -13
- 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 +20 -7
- 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 +12 -20
- 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 +29 -32
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +65 -15
- 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 +12 -5
- 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 +37 -0
- package/dist/record/gpu-timing.d.ts.map +1 -0
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +23 -2
- 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 +48 -3
- 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 +81 -5
- 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 +114 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-data.d.ts +10 -6
- package/dist/render-data.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 +78 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +113 -20
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +24 -3
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer-inspect.d.ts +29 -0
- package/dist/renderer-inspect.d.ts.map +1 -0
- 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/frame.d.ts +44 -0
- package/dist/temporal/frame.d.ts.map +1 -0
- package/dist/temporal/index.mjs +3 -3
- package/dist/typed-render-graph-primitives.d.ts +6 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-atlas.d.ts +10 -0
- package/dist/typed-shadow-atlas.d.ts.map +1 -0
- package/dist/typed-shadow-passes.d.ts +1 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/capability.d.ts +57 -0
- package/dist/volume/capability.d.ts.map +1 -0
- package/dist/volume/component.d.ts +61 -0
- package/dist/volume/component.d.ts.map +1 -0
- package/dist/volume/extract.d.ts +11 -0
- package/dist/volume/extract.d.ts.map +1 -0
- package/dist/volume/inspection.d.ts +38 -0
- package/dist/volume/inspection.d.ts.map +1 -0
- package/dist/volume/passes.d.ts +38 -0
- package/dist/volume/passes.d.ts.map +1 -0
- package/dist/volume/recovery.d.ts +38 -0
- package/dist/volume/recovery.d.ts.map +1 -0
- package/dist/volume/resources.d.ts +57 -0
- package/dist/volume/resources.d.ts.map +1 -0
- package/dist/volume/temporal.d.ts +35 -0
- package/dist/volume/temporal.d.ts.map +1 -0
- 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__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
- package/src/__tests__/material-observation.integration.test.ts +101 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
- 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 +23 -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 +76 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
- 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 +32 -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 -36
- 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/__tests__/volume-optics-oracle.unit.test.ts +85 -0
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
- package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
- package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
- package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
- package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
- package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
- package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
- package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
- package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
- package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
- package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
- package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
- package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
- package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
- package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
- package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
- package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
- package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
- package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
- package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
- package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
- package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
- package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
- package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
- package/src/assembly/device-feature-admission.ts +73 -0
- package/src/assembly/directional-shadow-inspection.ts +122 -0
- package/src/assembly/factory.ts +1093 -246
- 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 +85 -7
- package/src/components/reflection-probe.ts +49 -0
- package/src/components/spot-light.ts +22 -3
- package/src/device/gpu-residency.ts +168 -58
- package/src/environment/frame.ts +6 -24
- package/src/environment/lifecycle.ts +12 -0
- package/src/errors/render.ts +405 -143
- 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/gpu-usage.ts +2 -0
- 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 +186 -1
- package/src/inspection-types.ts +56 -0
- package/src/instances-derived-bounds.ts +171 -0
- package/src/light-buffer-layout.ts +74 -26
- package/src/mesh-material-bindings.ts +132 -0
- package/src/pbr-pipeline.ts +139 -27
- package/src/pipeline/standard-forward-lane.ts +65 -15
- 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 +141 -192
- package/src/pipeline/standard-post.ts +26 -7
- package/src/pipeline/standard-profile.ts +23 -30
- 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 +470 -516
- package/src/record/frame-snapshot.ts +76 -28
- package/src/record/frame-targets.ts +23 -2
- package/src/record/frame.ts +1169 -79
- 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 +233 -0
- package/src/record/main-pass-geometry.ts +120 -80
- package/src/record/main-pass-material.ts +101 -11
- package/src/record/main-pass-sprite-draws.ts +54 -25
- package/src/record/main-pass.ts +71 -15
- package/src/record/render-context.ts +77 -0
- package/src/record/shadow-pass.ts +104 -66
- package/src/record/skybox-post-pass.ts +20 -4
- package/src/record/typed-frame-graph.ts +1016 -32
- 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 +147 -4
- package/src/render-data.ts +50 -22
- package/src/render-graph-primitives.ts +9 -4
- package/src/render-pipeline.ts +161 -2
- package/src/render-system-extract.ts +886 -103
- package/src/render-system.ts +1174 -66
- package/src/renderer-inspect.ts +66 -0
- package/src/renderer.ts +222 -0
- package/src/scene/render-scene.ts +117 -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/temporal/frame.ts +128 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +61 -23
- package/src/typed-render-graph-primitives.ts +34 -2
- package/src/typed-shadow-atlas.ts +17 -0
- package/src/typed-shadow-passes.ts +47 -40
- package/src/volume/capability.ts +173 -0
- package/src/volume/component.ts +110 -0
- package/src/volume/extract.ts +21 -0
- package/src/volume/inspection.ts +78 -0
- package/src/volume/passes.ts +686 -0
- package/src/volume/recovery.ts +86 -0
- package/src/volume/resources.ts +236 -0
- package/src/volume/temporal.ts +89 -0
- package/dist/chunk-IPXCABEP.mjs.map +0 -1
- package/dist/chunk-LOSO4CTV.mjs.map +0 -1
- package/dist/chunk-RJRUS55L.mjs.map +0 -1
- package/dist/chunk-YTQ5C7YQ.mjs +0 -584
- package/dist/chunk-YTQ5C7YQ.mjs.map +0 -1
|
@@ -142,6 +142,7 @@ import type {
|
|
|
142
142
|
ParamSchemaEntry,
|
|
143
143
|
PrimitiveTopology,
|
|
144
144
|
SkeletonAsset,
|
|
145
|
+
TextureAsset,
|
|
145
146
|
} from '@forgeax/engine-types';
|
|
146
147
|
import {
|
|
147
148
|
ASSET_ERROR_HINTS,
|
|
@@ -153,6 +154,12 @@ import {
|
|
|
153
154
|
} from '@forgeax/engine-types';
|
|
154
155
|
import {
|
|
155
156
|
Atmosphere,
|
|
157
|
+
antialiasFromF32,
|
|
158
|
+
bloomEnabledFromF32,
|
|
159
|
+
Camera,
|
|
160
|
+
CubeCamera,
|
|
161
|
+
cameraProjectionFromF32,
|
|
162
|
+
cubeCameraUpdateIntentFromF32,
|
|
156
163
|
DirectionalLight,
|
|
157
164
|
Fog,
|
|
158
165
|
Instances,
|
|
@@ -160,33 +167,50 @@ import {
|
|
|
160
167
|
Lines,
|
|
161
168
|
MeshFilter,
|
|
162
169
|
MeshRenderer,
|
|
170
|
+
MotionBlur,
|
|
163
171
|
PointLight,
|
|
164
172
|
PointLightShadow,
|
|
165
173
|
Points,
|
|
166
174
|
PostProcessParams,
|
|
167
175
|
pointShapeFromU32,
|
|
176
|
+
ReflectionProbe,
|
|
177
|
+
reflectionProbeUpdateIntentFromF32,
|
|
168
178
|
SkyboxBackground,
|
|
169
179
|
Skylight,
|
|
170
180
|
SortKey,
|
|
171
181
|
SpotLight,
|
|
172
182
|
SpriteInstances,
|
|
173
183
|
SpriteRegionOverride,
|
|
184
|
+
tonemapFromF32,
|
|
174
185
|
tonemapToU32,
|
|
175
186
|
} from './components';
|
|
187
|
+
import {
|
|
188
|
+
type DirectionalShadowQuality,
|
|
189
|
+
directionalShadowQualityFromF32,
|
|
190
|
+
} from './components/directional-shadow-filter';
|
|
176
191
|
import { GlyphText } from './components/glyph-text';
|
|
177
|
-
import {
|
|
192
|
+
import {
|
|
193
|
+
computeInvRangeSquared,
|
|
194
|
+
degToCos,
|
|
195
|
+
type LightValidationError,
|
|
196
|
+
validateDirectionalLightData,
|
|
197
|
+
} from './components/light-helpers';
|
|
178
198
|
import { selectEnvironment } from './environment/frame';
|
|
179
199
|
import {
|
|
180
200
|
MaterialSkinAttrMissingError,
|
|
181
201
|
ShadowInvalidConfigError,
|
|
182
202
|
SkinMaterialMismatchError,
|
|
183
203
|
} from './errors/render';
|
|
184
|
-
import {
|
|
204
|
+
import {
|
|
205
|
+
type DirectionalShadowCascadeFit,
|
|
206
|
+
projectDirectionalShadow,
|
|
207
|
+
} from './extract/directional-shadow-projection';
|
|
185
208
|
import type {
|
|
186
209
|
EnvironmentCandidate,
|
|
187
210
|
EnvironmentFrame,
|
|
188
211
|
FogCandidate,
|
|
189
212
|
FogFrame,
|
|
213
|
+
FogSelectionFailure,
|
|
190
214
|
} from './extract/environment';
|
|
191
215
|
import { resolveVisibility, type VisibilitySnapshot } from './extract/visibility';
|
|
192
216
|
import type {
|
|
@@ -194,6 +218,7 @@ import type {
|
|
|
194
218
|
RenderFeatureWorldVisibilitySnapshot,
|
|
195
219
|
} from './features/types';
|
|
196
220
|
import { ensureGlyphMeshMaterialSlots } from './glyph-text-layout-system';
|
|
221
|
+
import { deriveInstancesUnionBounds } from './instances-derived-bounds';
|
|
197
222
|
import {
|
|
198
223
|
type MeshMaterialBindingDiagnostic,
|
|
199
224
|
type MeshMaterialBindingSource,
|
|
@@ -202,13 +227,29 @@ import {
|
|
|
202
227
|
import { isStandardPbrMaterialShader } from './pbr-pipeline';
|
|
203
228
|
import { expandPointsLinesBounds } from './points-lines/bounds';
|
|
204
229
|
import type { PointsLinesRetainedSnapshot, PointsLinesStyle } from './points-lines/snapshot';
|
|
205
|
-
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';
|
|
206
238
|
import { selectPasses } from './systems/pass-selector';
|
|
207
239
|
|
|
208
|
-
export { extractCameraSnapshots } from './extract/camera';
|
|
209
|
-
|
|
210
240
|
import type { SkinPaletteAllocator } from './systems/skin-palette-allocator';
|
|
211
241
|
import type { SkinPaletteSlice } from './systems/skin-palette-types';
|
|
242
|
+
import type { RenderTarget, RenderTargetTextureSource } from './targets/contracts';
|
|
243
|
+
import { resolveRenderTargetMaterialSource } from './targets/material-source';
|
|
244
|
+
import { resolveSelectedVolumetricLight } from './volume/capability';
|
|
245
|
+
import {
|
|
246
|
+
type ValidatedVolumetricFog,
|
|
247
|
+
type VolumeDensityBinding,
|
|
248
|
+
VolumetricFog,
|
|
249
|
+
type VolumetricFogAuthoring,
|
|
250
|
+
} from './volume/component';
|
|
251
|
+
import { extractVolumetricFog } from './volume/extract';
|
|
252
|
+
import type { VolumeProjectorTuple } from './volume/temporal';
|
|
212
253
|
|
|
213
254
|
/**
|
|
214
255
|
* DirectionalLightSnapshot — sun-like infinite light variant of the
|
|
@@ -221,6 +262,7 @@ import type { SkinPaletteSlice } from './systems/skin-palette-types';
|
|
|
221
262
|
*/
|
|
222
263
|
export interface DirectionalLightSnapshot {
|
|
223
264
|
readonly kind: 'directional';
|
|
265
|
+
readonly entity?: EntityHandle;
|
|
224
266
|
readonly direction: Vec3;
|
|
225
267
|
readonly color: Vec3;
|
|
226
268
|
readonly intensity: number;
|
|
@@ -234,14 +276,16 @@ export interface DirectionalLightSnapshot {
|
|
|
234
276
|
*/
|
|
235
277
|
export interface PointLightSnapshot {
|
|
236
278
|
readonly kind: 'point';
|
|
279
|
+
readonly entity?: EntityHandle | number;
|
|
237
280
|
readonly position: Vec3;
|
|
238
281
|
readonly color: Vec3;
|
|
239
282
|
readonly intensity: number;
|
|
240
283
|
readonly invRangeSquared: number;
|
|
241
284
|
/**
|
|
242
285
|
* feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-8 + M4 / T-M4-4:
|
|
243
|
-
* cube_array atlas layer index for this light's shadow map (0..
|
|
244
|
-
* 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
|
|
245
289
|
* plan-strategy §D-2 — record stage / shader skips shadow sampling when the
|
|
246
290
|
* lane equals the sentinel. Joined with `pointShadow[]` by entity at the
|
|
247
291
|
* end of extract; HDRP record stage threads `shadowAtlasLayer + shadowNear +
|
|
@@ -278,8 +322,9 @@ export interface PointLightSnapshot {
|
|
|
278
322
|
*
|
|
279
323
|
* `mapSize` / `nearPlane` / `farPlane` ride here so record stage can size the
|
|
280
324
|
* cube atlas faces and feed shader the proj constants without re-querying ECS.
|
|
281
|
-
* `shadowAtlasLayer` is assigned by extract in spawn order
|
|
282
|
-
*
|
|
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
|
|
283
328
|
* pointPadW (T-M1-8 will rename to shadowAtlasLayer).
|
|
284
329
|
*/
|
|
285
330
|
export interface PointShadowSnapshot {
|
|
@@ -292,9 +337,9 @@ export interface PointShadowSnapshot {
|
|
|
292
337
|
readonly nearPlane: number;
|
|
293
338
|
readonly farPlane: number;
|
|
294
339
|
/**
|
|
295
|
-
* Atlas layer assigned by extract in spawn order
|
|
296
|
-
*
|
|
297
|
-
* 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).
|
|
298
343
|
*/
|
|
299
344
|
readonly shadowAtlasLayer: number;
|
|
300
345
|
/**
|
|
@@ -317,10 +362,15 @@ export interface PointShadowSnapshot {
|
|
|
317
362
|
* is false, dir degenerates, or the light is clipped). shadowAtlasTile
|
|
318
363
|
* (i32 sentinel -1) is the allocated tile index 0..3 or -1 for unassigned
|
|
319
364
|
* (plan-strategy D-4). mapSize / nearPlane / farPlane are the source
|
|
320
|
-
* component shadow parameters carried through to the record stage.
|
|
365
|
+
* component shadow parameters carried through to the record stage. The
|
|
366
|
+
* author-facing `shadowIntensity` remains in [0,1] and is copied unchanged;
|
|
367
|
+
* shader consumers apply it only to shadow visibility, never to candela
|
|
368
|
+
* radiance.
|
|
321
369
|
*/
|
|
322
370
|
export interface SpotLightSnapshot {
|
|
323
371
|
readonly kind: 'spot';
|
|
372
|
+
/** Entity identity is branded in extracted frames; numeric fixtures remain compatible. */
|
|
373
|
+
readonly entity?: EntityHandle | number;
|
|
324
374
|
readonly position: Vec3;
|
|
325
375
|
readonly direction: Vec3;
|
|
326
376
|
readonly color: Vec3;
|
|
@@ -335,6 +385,17 @@ export interface SpotLightSnapshot {
|
|
|
335
385
|
readonly nearPlane: number;
|
|
336
386
|
readonly farPlane: number;
|
|
337
387
|
readonly shadowAtlasTile: number;
|
|
388
|
+
/** Fraction of sampled shadow visibility to retain; 1 = fully opaque shadow. */
|
|
389
|
+
readonly shadowIntensity?: number;
|
|
390
|
+
readonly depthBias?: number;
|
|
391
|
+
readonly normalBias?: number;
|
|
392
|
+
readonly pcfKernelSize?: number;
|
|
393
|
+
/** Authoritative TextureAsset projector resolved from the SpotLight handle. */
|
|
394
|
+
readonly projectorHandle?: Handle<'TextureAsset', 'shared'>;
|
|
395
|
+
readonly projectorAsset?: TextureAsset;
|
|
396
|
+
readonly projectorGuid?: string;
|
|
397
|
+
readonly projectorGeneration?: number;
|
|
398
|
+
readonly projectorRevision?: number;
|
|
338
399
|
}
|
|
339
400
|
|
|
340
401
|
/**
|
|
@@ -376,6 +437,7 @@ export interface ExtractedLights {
|
|
|
376
437
|
* unused slots are 0.0f. Undefined when castShadow=false or no directional
|
|
377
438
|
* light.
|
|
378
439
|
*/
|
|
440
|
+
/** Four vec4 lanes: split, worldUnitsPerTexel, lightDepthWorldSpan, reserved. */
|
|
379
441
|
readonly splitPlanes: Float32Array | undefined;
|
|
380
442
|
/**
|
|
381
443
|
* feat-20260613-csm-cascaded-shadow-maps M2 / w9: effective cascade count
|
|
@@ -411,13 +473,10 @@ export interface ExtractedLights {
|
|
|
411
473
|
* undefined otherwise.
|
|
412
474
|
*/
|
|
413
475
|
readonly normalBias: number | undefined;
|
|
414
|
-
/**
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
* undefined otherwise.
|
|
419
|
-
*/
|
|
420
|
-
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;
|
|
421
480
|
/**
|
|
422
481
|
* feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-7:
|
|
423
482
|
* shadow-casting point lights (PointLight + PointLightShadow + Transform
|
|
@@ -505,6 +564,37 @@ export interface SkyboxSnapshot {
|
|
|
505
564
|
readonly entityHandle: number;
|
|
506
565
|
}
|
|
507
566
|
|
|
567
|
+
/**
|
|
568
|
+
* Resource-owner projection for the single ECS VolumetricFog singleton.
|
|
569
|
+
*
|
|
570
|
+
* The renderer carries the validated authoring facts and the source POD all
|
|
571
|
+
* the way to the record stage. No profile flag or source/catalog byte is
|
|
572
|
+
* allowed to enable the graph: `status: 'available'` is produced only after
|
|
573
|
+
* the ECS component, its shared TextureAsset, and the volume validator all
|
|
574
|
+
* agree on one linear 3D density owner.
|
|
575
|
+
*/
|
|
576
|
+
export interface ExtractedVolumetricFog {
|
|
577
|
+
readonly status: 'off' | 'available' | 'degraded';
|
|
578
|
+
readonly fog?: ValidatedVolumetricFog;
|
|
579
|
+
readonly densityHandle?: Handle<'TextureAsset', 'shared'>;
|
|
580
|
+
readonly densityAsset?: TextureAsset;
|
|
581
|
+
readonly guid?: string;
|
|
582
|
+
readonly generation?: number;
|
|
583
|
+
readonly digest?: string;
|
|
584
|
+
readonly worldId?: number;
|
|
585
|
+
/** Required same-World light selected by the authoring component. */
|
|
586
|
+
readonly lightEntity?: EntityHandle;
|
|
587
|
+
readonly lightKind?: 'directional' | 'point' | 'spot';
|
|
588
|
+
/** Optional Point+Spot pair identities consumed by the volume integrator. */
|
|
589
|
+
readonly pointLightEntity?: EntityHandle;
|
|
590
|
+
readonly spotLightEntity?: EntityHandle;
|
|
591
|
+
/** Accepted projector tuple shared by surface and volume when authored. */
|
|
592
|
+
readonly projector?: VolumeProjectorTuple;
|
|
593
|
+
/** The resolved projector payload shared with the surface light snapshot. */
|
|
594
|
+
readonly projectorHandle?: Handle<'TextureAsset', 'shared'>;
|
|
595
|
+
readonly projectorAsset?: TextureAsset;
|
|
596
|
+
}
|
|
597
|
+
|
|
508
598
|
export interface RenderableSnapshot {
|
|
509
599
|
readonly assetHandle: number;
|
|
510
600
|
readonly transform: TransformSnapshot;
|
|
@@ -792,6 +882,8 @@ export interface MaterialSnapshot {
|
|
|
792
882
|
* them in a lightmap injection region.
|
|
793
883
|
*/
|
|
794
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;
|
|
795
887
|
/**
|
|
796
888
|
* User-region texture field names whose paramValue resolved to a VideoAsset
|
|
797
889
|
* (kind `'video'`) rather than a static TextureAsset
|
|
@@ -1001,11 +1093,19 @@ function appendMaterialDispatchEntries(
|
|
|
1001
1093
|
|
|
1002
1094
|
export interface ExtractedFrame {
|
|
1003
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[];
|
|
1004
1102
|
readonly lights: ExtractedLights;
|
|
1005
1103
|
/** Resource-owner environment facts selected once at the extract boundary. */
|
|
1006
1104
|
readonly environment: EnvironmentFrame | undefined;
|
|
1007
1105
|
/** False when the resource-owner world failed environment/fog selection. */
|
|
1008
1106
|
readonly environmentReady: boolean;
|
|
1107
|
+
/** One resource-owner volume projection; absent means no authored fog. */
|
|
1108
|
+
readonly volumetricFog?: ExtractedVolumetricFog;
|
|
1009
1109
|
readonly renderables: RenderableSnapshot[];
|
|
1010
1110
|
/**
|
|
1011
1111
|
* Single dispatch list sorted by queue value (ascending, stable sort).
|
|
@@ -1019,6 +1119,8 @@ export interface ExtractedFrame {
|
|
|
1019
1119
|
readonly skyboxCount: number;
|
|
1020
1120
|
/** Resource-owner Fog facts selected once at the frame boundary. */
|
|
1021
1121
|
readonly fog: FogFrame | undefined;
|
|
1122
|
+
/** Last invalid Fog update retained alongside the current valid/LKG frame. */
|
|
1123
|
+
readonly fogFailure?: FogSelectionFailure;
|
|
1022
1124
|
/**
|
|
1023
1125
|
* feat-20260528-frustum-culling M3 / w11: frustum culling statistics
|
|
1024
1126
|
* collected during the extract phase. `total` is the count of entities
|
|
@@ -1044,6 +1146,65 @@ export interface ExtractedFrame {
|
|
|
1044
1146
|
readonly hiddenEntityReports: readonly RenderFeatureHiddenEntityReport[];
|
|
1045
1147
|
}
|
|
1046
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
|
+
|
|
1047
1208
|
/**
|
|
1048
1209
|
* Internal world surface used by extract for archetype-graph traversal and
|
|
1049
1210
|
* error routing. The packed `Entity` handle for a row is read directly from
|
|
@@ -1086,6 +1247,14 @@ function createWorldInternalView(world: World): WorldInternalView {
|
|
|
1086
1247
|
};
|
|
1087
1248
|
}
|
|
1088
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
|
+
|
|
1089
1258
|
/**
|
|
1090
1259
|
* The user-region texture fields the built-in standard-PBR material declares.
|
|
1091
1260
|
* Used as the fallback texture-field set when the shader id is not registered
|
|
@@ -1366,6 +1535,24 @@ function collectMaterialTextureCoordinates(
|
|
|
1366
1535
|
return out;
|
|
1367
1536
|
}
|
|
1368
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
|
+
|
|
1369
1556
|
function collectMaterialTextureSamplers(
|
|
1370
1557
|
values: Readonly<Record<string, unknown>>,
|
|
1371
1558
|
resolveSampler: (value: unknown) => Handle<'SamplerAsset', 'shared'> | undefined,
|
|
@@ -1456,9 +1643,14 @@ function materialParametersToParamSchema(
|
|
|
1456
1643
|
return parameters.flatMap((parameter): ParamSchemaEntry[] => {
|
|
1457
1644
|
// Boolean material values are runtime-only and have no UBO representation.
|
|
1458
1645
|
if (parameter.type === 'bool') return [];
|
|
1459
|
-
if (parameter.type === 'texture') {
|
|
1646
|
+
if (parameter.type === 'texture' || parameter.type === 'texture_cube') {
|
|
1460
1647
|
if (engineInjectedTextureFields?.has(parameter.name) === true) return [];
|
|
1461
|
-
return [
|
|
1648
|
+
return [
|
|
1649
|
+
{
|
|
1650
|
+
name: parameter.name,
|
|
1651
|
+
type: parameter.type === 'texture_cube' ? 'texture_cube' : 'texture2d',
|
|
1652
|
+
},
|
|
1653
|
+
];
|
|
1462
1654
|
}
|
|
1463
1655
|
return [
|
|
1464
1656
|
{
|
|
@@ -1718,6 +1910,7 @@ export function resolveMaterialSnapshot(
|
|
|
1718
1910
|
const samplerHandles = collectMaterialTextureSamplers(pv, (value) =>
|
|
1719
1911
|
resolveTexLike(materialTextureRef(value), 'SamplerAsset'),
|
|
1720
1912
|
);
|
|
1913
|
+
const textureSources = collectMaterialTextureSources(pv, world);
|
|
1721
1914
|
const emissiveTextureHandle = resolveTexLike(
|
|
1722
1915
|
materialTextureRef(pv.emissiveTexture),
|
|
1723
1916
|
'TextureAsset',
|
|
@@ -1753,6 +1946,7 @@ export function resolveMaterialSnapshot(
|
|
|
1753
1946
|
...(materialParamSchema.length > 0 && { materialParamSchema }),
|
|
1754
1947
|
...(textureCoordinates.size > 0 && { textureCoordinates }),
|
|
1755
1948
|
...(textureHandles.size > 0 && { textureHandles }),
|
|
1949
|
+
...(textureSources.size > 0 && { textureSources }),
|
|
1756
1950
|
...(videoTextureFields.size > 0 && { videoTextureFields }),
|
|
1757
1951
|
...(samplerHandles.size > 0 && { samplerHandles }),
|
|
1758
1952
|
...(baseColorTextureHandle !== undefined && { baseColorTexture: baseColorTextureHandle }),
|
|
@@ -2056,6 +2250,9 @@ export interface DirectionalCsmConfig {
|
|
|
2056
2250
|
readonly cascadeBlend: number;
|
|
2057
2251
|
readonly mapSize: number;
|
|
2058
2252
|
readonly shadowDistance: number;
|
|
2253
|
+
readonly shadowFilter: number;
|
|
2254
|
+
readonly shadowAngularRadius: number;
|
|
2255
|
+
readonly maxPenumbraTexels: number;
|
|
2059
2256
|
}
|
|
2060
2257
|
|
|
2061
2258
|
/**
|
|
@@ -2070,6 +2267,7 @@ export interface DirectionalCsmResult {
|
|
|
2070
2267
|
readonly cascadeCount: number;
|
|
2071
2268
|
readonly cascadeBlend: number;
|
|
2072
2269
|
readonly shadowMapSize: number;
|
|
2270
|
+
readonly directionalShadowQuality: DirectionalShadowQuality;
|
|
2073
2271
|
}
|
|
2074
2272
|
|
|
2075
2273
|
/**
|
|
@@ -2102,9 +2300,6 @@ export function computeDirectionalCsm(
|
|
|
2102
2300
|
|
|
2103
2301
|
// PSSM split planes: [camera near, shadowDistance], not the camera far.
|
|
2104
2302
|
const splits = pssmSplit(sNear, sFar, cascadeCount, config.splitLambda);
|
|
2105
|
-
const paddedSplits = new Float32Array(4);
|
|
2106
|
-
for (let i = 0; i < splits.length; i++) paddedSplits[i] = splits[i] ?? 0;
|
|
2107
|
-
|
|
2108
2303
|
// Light view matrix: camera at origin, looking along the light direction.
|
|
2109
2304
|
const lightDirN = vec3.normalize(vec3.create(), direction);
|
|
2110
2305
|
const lightTarget = vec3.create(lightDirN[0] ?? 0, lightDirN[1] ?? 0, lightDirN[2] ?? 0);
|
|
@@ -2135,6 +2330,7 @@ export function computeDirectionalCsm(
|
|
|
2135
2330
|
mat4.multiply(cameraVP, camProj, camView);
|
|
2136
2331
|
|
|
2137
2332
|
const resultLightViewProjs: Float32Array[] = [];
|
|
2333
|
+
const cascadeFits: DirectionalShadowCascadeFit[] = [];
|
|
2138
2334
|
|
|
2139
2335
|
// bug-20260619 RC-2 (AC-05): toward-light Z reach. Extend the near (toward-
|
|
2140
2336
|
// light) bound of EVERY cascade to the toward-light extreme of the WHOLE
|
|
@@ -2198,16 +2394,34 @@ export function computeDirectionalCsm(
|
|
|
2198
2394
|
// lightViewProj = orthoProj * lightView — pure clip-space [-1,1]. Atlas
|
|
2199
2395
|
// tile placement is handled by the per-cascade viewport + fragment-side UV
|
|
2200
2396
|
// math (evalDirectional), so shadow_caster.gl_Position stays clip-space.
|
|
2201
|
-
|
|
2397
|
+
cascadeFits.push({
|
|
2398
|
+
split: splits[cIdx] ?? sFar,
|
|
2399
|
+
minX,
|
|
2400
|
+
maxX,
|
|
2401
|
+
minY,
|
|
2402
|
+
maxY,
|
|
2403
|
+
minZ,
|
|
2404
|
+
maxZ,
|
|
2405
|
+
});
|
|
2202
2406
|
resultLightViewProjs.push(new Float32Array(mat4.multiply(mat4.create(), orthoProj, lightView)));
|
|
2203
2407
|
}
|
|
2204
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
|
+
|
|
2205
2418
|
return {
|
|
2206
2419
|
lightViewProj: resultLightViewProjs,
|
|
2207
|
-
splitPlanes:
|
|
2420
|
+
splitPlanes: projection.splitPlanes,
|
|
2208
2421
|
cascadeCount,
|
|
2209
2422
|
cascadeBlend: config.cascadeBlend,
|
|
2210
2423
|
shadowMapSize: config.mapSize,
|
|
2424
|
+
directionalShadowQuality: projection.directionalShadowQuality,
|
|
2211
2425
|
};
|
|
2212
2426
|
}
|
|
2213
2427
|
|
|
@@ -2239,6 +2453,161 @@ export interface PreparedExtractContext {
|
|
|
2239
2453
|
readonly visibility: VisibilitySnapshot;
|
|
2240
2454
|
}
|
|
2241
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
|
+
|
|
2242
2611
|
function tonemapParams(camera: CameraSnapshot): Uint8Array {
|
|
2243
2612
|
const bytes = new ArrayBuffer(16);
|
|
2244
2613
|
const floats = new Float32Array(bytes);
|
|
@@ -2274,6 +2643,21 @@ export function prepareExtractContext(
|
|
|
2274
2643
|
}
|
|
2275
2644
|
|
|
2276
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
|
+
}
|
|
2277
2661
|
|
|
2278
2662
|
function selectFogFrame(world: World): FogFrame | undefined {
|
|
2279
2663
|
// Query compilation is owned once per World; Query refreshes its matched
|
|
@@ -2301,13 +2685,197 @@ function selectFogFrame(world: World): FogFrame | undefined {
|
|
|
2301
2685
|
suns: [],
|
|
2302
2686
|
lane: 'direct',
|
|
2303
2687
|
});
|
|
2304
|
-
|
|
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
|
+
};
|
|
2305
2700
|
routeWorldError(world, fogSelection.error, {
|
|
2306
2701
|
systemName: 'RenderSystem.extract (fog-selection)',
|
|
2307
2702
|
});
|
|
2703
|
+
if (state.lkg !== undefined) return state.lkg;
|
|
2308
2704
|
throw fogSelection.error;
|
|
2309
2705
|
}
|
|
2310
2706
|
|
|
2707
|
+
function textureDigest(texture: TextureAsset): string {
|
|
2708
|
+
// The render inspection needs a stable identity token, not a second byte
|
|
2709
|
+
// store. FNV-1a is deterministic, synchronous, and deliberately labelled
|
|
2710
|
+
// as a non-cryptographic digest so it cannot be mistaken for Catalog proof.
|
|
2711
|
+
let hash = 2166136261;
|
|
2712
|
+
for (const byte of texture.data) hash = Math.imul(hash ^ byte, 16777619) >>> 0;
|
|
2713
|
+
return `fnv1a:${hash.toString(16).padStart(8, '0')}`;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
function extractVolumeSnapshot(
|
|
2717
|
+
world: World,
|
|
2718
|
+
assets: AssetRegistry | null | undefined,
|
|
2719
|
+
worldInternal: WorldInternalView,
|
|
2720
|
+
): ExtractedVolumetricFog | undefined {
|
|
2721
|
+
const query = world.query({ read: [VolumetricFog] }).unwrap();
|
|
2722
|
+
const authorings: VolumetricFogAuthoring[] = [];
|
|
2723
|
+
let selectedLight:
|
|
2724
|
+
| {
|
|
2725
|
+
readonly entity: EntityHandle;
|
|
2726
|
+
readonly kind: 'directional' | 'point' | 'spot';
|
|
2727
|
+
readonly pointLightEntity?: EntityHandle;
|
|
2728
|
+
readonly spotLightEntity?: EntityHandle;
|
|
2729
|
+
}
|
|
2730
|
+
| undefined;
|
|
2731
|
+
let first:
|
|
2732
|
+
| {
|
|
2733
|
+
readonly densityHandle: Handle<'TextureAsset', 'shared'>;
|
|
2734
|
+
readonly densityAsset: TextureAsset;
|
|
2735
|
+
readonly guid: string;
|
|
2736
|
+
}
|
|
2737
|
+
| undefined;
|
|
2738
|
+
|
|
2739
|
+
for (const row of query) {
|
|
2740
|
+
const source = row.get(VolumetricFog);
|
|
2741
|
+
if (source.light === null) continue;
|
|
2742
|
+
const selected = resolveSelectedVolumetricLight(world, source.light);
|
|
2743
|
+
if (selected.status === 'available') {
|
|
2744
|
+
const pairSpot = source.spotLight === null ? undefined : source.spotLight;
|
|
2745
|
+
selectedLight ??= {
|
|
2746
|
+
entity: selected.entity,
|
|
2747
|
+
kind: selected.kind,
|
|
2748
|
+
...(selected.kind === 'point' ? { pointLightEntity: selected.entity } : {}),
|
|
2749
|
+
...(pairSpot === undefined ? {} : { spotLightEntity: pairSpot }),
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
const densityHandle = toShared<'TextureAsset'>(Math.round(Number(source.density)));
|
|
2753
|
+
const resolved = resolveAssetHandle<TextureAsset>(world, densityHandle);
|
|
2754
|
+
if (!resolved.ok || resolved.value.kind !== 'texture') {
|
|
2755
|
+
worldInternal._routeError(
|
|
2756
|
+
resolved.ok
|
|
2757
|
+
? new RhiError({
|
|
2758
|
+
code: 'asset-not-registered',
|
|
2759
|
+
expected: 'VolumetricFog.density resolves to a TextureAsset',
|
|
2760
|
+
hint: 'load a TextureAsset before assigning it to VolumetricFog.density',
|
|
2761
|
+
detail: { assetHandle: Number(densityHandle) },
|
|
2762
|
+
})
|
|
2763
|
+
: resolved.error,
|
|
2764
|
+
{ severity: Severity.Error, systemName: 'RenderSystem.extract (volumetric-density)' },
|
|
2765
|
+
);
|
|
2766
|
+
return { status: 'degraded' };
|
|
2767
|
+
}
|
|
2768
|
+
const densityAsset = resolved.value;
|
|
2769
|
+
const guid = assets?.guidOf(densityAsset) ?? `handle:${Number(densityHandle)}`;
|
|
2770
|
+
const shape = densityAsset.shape;
|
|
2771
|
+
const density: VolumeDensityBinding =
|
|
2772
|
+
shape.viewDimension === '3d'
|
|
2773
|
+
? {
|
|
2774
|
+
guid,
|
|
2775
|
+
generation: 1,
|
|
2776
|
+
shape: {
|
|
2777
|
+
viewDimension: '3d',
|
|
2778
|
+
extent: {
|
|
2779
|
+
width: shape.extent.width,
|
|
2780
|
+
height: shape.extent.height,
|
|
2781
|
+
depth: shape.extent.depth,
|
|
2782
|
+
},
|
|
2783
|
+
},
|
|
2784
|
+
format: densityAsset.format,
|
|
2785
|
+
colorSpace: densityAsset.colorSpace,
|
|
2786
|
+
}
|
|
2787
|
+
: {
|
|
2788
|
+
guid,
|
|
2789
|
+
generation: 1,
|
|
2790
|
+
shape: {
|
|
2791
|
+
viewDimension: shape.viewDimension,
|
|
2792
|
+
extent:
|
|
2793
|
+
shape.viewDimension === '2d-array'
|
|
2794
|
+
? {
|
|
2795
|
+
width: shape.extent.width,
|
|
2796
|
+
height: shape.extent.height,
|
|
2797
|
+
layers: shape.extent.layers,
|
|
2798
|
+
}
|
|
2799
|
+
: { width: shape.extent.width, height: shape.extent.height },
|
|
2800
|
+
},
|
|
2801
|
+
format: densityAsset.format,
|
|
2802
|
+
colorSpace: densityAsset.colorSpace,
|
|
2803
|
+
};
|
|
2804
|
+
const authoring: VolumetricFogAuthoring = {
|
|
2805
|
+
light: source.light,
|
|
2806
|
+
density,
|
|
2807
|
+
bounds: {
|
|
2808
|
+
min: [source.boundsMin[0] ?? 0, source.boundsMin[1] ?? 0, source.boundsMin[2] ?? 0],
|
|
2809
|
+
max: [source.boundsMax[0] ?? 0, source.boundsMax[1] ?? 0, source.boundsMax[2] ?? 0],
|
|
2810
|
+
},
|
|
2811
|
+
extinction: [source.extinction[0] ?? 0, source.extinction[1] ?? 0, source.extinction[2] ?? 0],
|
|
2812
|
+
albedo: [source.albedo[0] ?? 0, source.albedo[1] ?? 0, source.albedo[2] ?? 0],
|
|
2813
|
+
emission: [source.emission[0] ?? 0, source.emission[1] ?? 0, source.emission[2] ?? 0],
|
|
2814
|
+
anisotropy: source.anisotropy,
|
|
2815
|
+
maxDistance: source.maxDistance,
|
|
2816
|
+
};
|
|
2817
|
+
authorings.push(authoring);
|
|
2818
|
+
first ??= { densityHandle, densityAsset, guid };
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
if (authorings.length === 0) return undefined;
|
|
2822
|
+
const extracted = extractVolumetricFog(authorings);
|
|
2823
|
+
if (!extracted.ok) {
|
|
2824
|
+
worldInternal._routeError(extracted.error, {
|
|
2825
|
+
severity: Severity.Error,
|
|
2826
|
+
systemName: 'RenderSystem.extract (volumetric-fog)',
|
|
2827
|
+
});
|
|
2828
|
+
return {
|
|
2829
|
+
status: 'degraded',
|
|
2830
|
+
...(first === undefined
|
|
2831
|
+
? {}
|
|
2832
|
+
: {
|
|
2833
|
+
densityHandle: first.densityHandle,
|
|
2834
|
+
densityAsset: first.densityAsset,
|
|
2835
|
+
guid: first.guid,
|
|
2836
|
+
generation: 1,
|
|
2837
|
+
digest: textureDigest(first.densityAsset),
|
|
2838
|
+
...(selectedLight === undefined
|
|
2839
|
+
? {}
|
|
2840
|
+
: {
|
|
2841
|
+
lightEntity: selectedLight.entity,
|
|
2842
|
+
lightKind: selectedLight.kind,
|
|
2843
|
+
...(selectedLight.pointLightEntity === undefined
|
|
2844
|
+
? {}
|
|
2845
|
+
: { pointLightEntity: selectedLight.pointLightEntity }),
|
|
2846
|
+
...(selectedLight.spotLightEntity === undefined
|
|
2847
|
+
? {}
|
|
2848
|
+
: { spotLightEntity: selectedLight.spotLightEntity }),
|
|
2849
|
+
}),
|
|
2850
|
+
}),
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
if (extracted.value.status === 'off' || first === undefined) return { status: 'off' };
|
|
2854
|
+
const selectedLightFields =
|
|
2855
|
+
selectedLight === undefined
|
|
2856
|
+
? {}
|
|
2857
|
+
: {
|
|
2858
|
+
lightEntity: selectedLight.entity,
|
|
2859
|
+
lightKind: selectedLight.kind,
|
|
2860
|
+
...(selectedLight.pointLightEntity === undefined
|
|
2861
|
+
? {}
|
|
2862
|
+
: { pointLightEntity: selectedLight.pointLightEntity }),
|
|
2863
|
+
...(selectedLight.spotLightEntity === undefined
|
|
2864
|
+
? {}
|
|
2865
|
+
: { spotLightEntity: selectedLight.spotLightEntity }),
|
|
2866
|
+
};
|
|
2867
|
+
return {
|
|
2868
|
+
status: 'available',
|
|
2869
|
+
fog: extracted.value.fog,
|
|
2870
|
+
densityHandle: first.densityHandle,
|
|
2871
|
+
densityAsset: first.densityAsset,
|
|
2872
|
+
guid: first.guid,
|
|
2873
|
+
generation: 1,
|
|
2874
|
+
digest: textureDigest(first.densityAsset),
|
|
2875
|
+
...selectedLightFields,
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2311
2879
|
export function extractFrames(
|
|
2312
2880
|
worlds: readonly World[],
|
|
2313
2881
|
owner: number | ExtractFramesOwner,
|
|
@@ -2464,7 +3032,8 @@ export function extractFrames(
|
|
|
2464
3032
|
let shadowMapSize: number | undefined;
|
|
2465
3033
|
let depthBias: number | undefined;
|
|
2466
3034
|
let normalBias: number | undefined;
|
|
2467
|
-
let
|
|
3035
|
+
let directionalShadowQuality: DirectionalShadowQuality | undefined;
|
|
3036
|
+
let directionalShadowError: LightValidationError | undefined;
|
|
2468
3037
|
const pointShadow: PointShadowSnapshot[] = [];
|
|
2469
3038
|
// bug-20260710-editor-cross-world-shadow: carry the raw CSM config +
|
|
2470
3039
|
// direction of the first-hit directional so the merge layer can recompute
|
|
@@ -2486,7 +3055,8 @@ export function extractFrames(
|
|
|
2486
3055
|
shadowMapSize = f.lights.shadowMapSize;
|
|
2487
3056
|
depthBias = f.lights.depthBias;
|
|
2488
3057
|
normalBias = f.lights.normalBias;
|
|
2489
|
-
|
|
3058
|
+
directionalShadowQuality = f.lights.directionalShadowQuality;
|
|
3059
|
+
directionalShadowError = f.lights.directionalShadowError;
|
|
2490
3060
|
directionalCsmConfig = f.lights.directionalCsmConfig;
|
|
2491
3061
|
directionalCsmDirection = f.lights.directionalCsmDirection;
|
|
2492
3062
|
}
|
|
@@ -2505,6 +3075,9 @@ export function extractFrames(
|
|
|
2505
3075
|
if (succeededIndices[fi] === resourceOwner) resourceOwnerFrame = succeededFrames[fi];
|
|
2506
3076
|
}
|
|
2507
3077
|
const cameras = cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.cameras] : [];
|
|
3078
|
+
const auxiliaryCameras =
|
|
3079
|
+
cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.auxiliaryCameras] : [];
|
|
3080
|
+
const cubeCameras = cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.cubeCameras] : [];
|
|
2508
3081
|
|
|
2509
3082
|
// bug-20260710-editor-cross-world-shadow: RECOMPUTE directional CSM matrices
|
|
2510
3083
|
// against the surfaced camera. In a single-world app the per-world extract
|
|
@@ -2543,6 +3116,7 @@ export function extractFrames(
|
|
|
2543
3116
|
cascadeCount = csm.cascadeCount;
|
|
2544
3117
|
cascadeBlend = csm.cascadeBlend;
|
|
2545
3118
|
shadowMapSize = csm.shadowMapSize;
|
|
3119
|
+
directionalShadowQuality = csm.directionalShadowQuality;
|
|
2546
3120
|
}
|
|
2547
3121
|
// csm === null (no surfaced camera at all): keep the per-world carry —
|
|
2548
3122
|
// there is no better data, and a cameraless frame renders nothing anyway.
|
|
@@ -2560,16 +3134,19 @@ export function extractFrames(
|
|
|
2560
3134
|
shadowMapSize,
|
|
2561
3135
|
depthBias,
|
|
2562
3136
|
normalBias,
|
|
2563
|
-
|
|
3137
|
+
directionalShadowQuality,
|
|
3138
|
+
directionalShadowError,
|
|
2564
3139
|
pointShadow,
|
|
2565
3140
|
directionalCsmConfig,
|
|
2566
3141
|
directionalCsmDirection,
|
|
2567
3142
|
};
|
|
3143
|
+
const volumetricFog = resourceOwnerFrame?.volumetricFog;
|
|
2568
3144
|
const skylight = resourceOwnerFrame?.skylight;
|
|
2569
3145
|
const skylightCount = resourceOwnerFrame?.skylightCount ?? 0;
|
|
2570
3146
|
const skybox = resourceOwnerFrame?.skybox;
|
|
2571
3147
|
const skyboxCount = resourceOwnerFrame?.skyboxCount ?? 0;
|
|
2572
3148
|
const fog = resourceOwnerFrame?.fog;
|
|
3149
|
+
const fogFailure = resourceOwnerFrame?.fogFailure;
|
|
2573
3150
|
const environment = resourceOwnerFrame?.environment;
|
|
2574
3151
|
// User-authored PostProcessParams entities are singleton scene resources, so
|
|
2575
3152
|
// they come from the resourceOwner world (holistic snapshot selection).
|
|
@@ -2609,10 +3186,20 @@ export function extractFrames(
|
|
|
2609
3186
|
const visibilityStats = {
|
|
2610
3187
|
explicitlyHidden: succeededFrames.reduce((s, f) => s + f.visibilityStats.explicitlyHidden, 0),
|
|
2611
3188
|
};
|
|
3189
|
+
const reflectionProbes = succeededFrames.flatMap((f, index) => {
|
|
3190
|
+
const worldId = succeededIndices[index] ?? 0;
|
|
3191
|
+
return (f.reflectionProbes ?? []).map((probe) => ({ ...probe, worldId }));
|
|
3192
|
+
});
|
|
2612
3193
|
|
|
2613
3194
|
return {
|
|
2614
3195
|
cameras,
|
|
3196
|
+
auxiliaryCameras,
|
|
3197
|
+
cubeCameras,
|
|
3198
|
+
reflectionProbes,
|
|
2615
3199
|
lights,
|
|
3200
|
+
...(volumetricFog === undefined
|
|
3201
|
+
? {}
|
|
3202
|
+
: { volumetricFog: { ...volumetricFog, worldId: resourceOwner } }),
|
|
2616
3203
|
renderables,
|
|
2617
3204
|
dispatch: dispatchEntries,
|
|
2618
3205
|
skylight,
|
|
@@ -2620,6 +3207,7 @@ export function extractFrames(
|
|
|
2620
3207
|
skybox,
|
|
2621
3208
|
skyboxCount,
|
|
2622
3209
|
fog,
|
|
3210
|
+
...(fogFailure === undefined ? {} : { fogFailure }),
|
|
2623
3211
|
environment,
|
|
2624
3212
|
environmentReady: resourceOwnerFrame !== undefined && environment !== undefined,
|
|
2625
3213
|
frustumStats,
|
|
@@ -2715,6 +3303,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2715
3303
|
// established at the frame boundary; record never revisits World state.
|
|
2716
3304
|
const fog =
|
|
2717
3305
|
resourceOwnerFog === undefined ? selectFogFrame(world) : (resourceOwnerFog ?? undefined);
|
|
3306
|
+
const fogFailure = fogState(world).failure;
|
|
2718
3307
|
const visibility = context.visibility.hasAnyHiddenIntent ? context.visibility : undefined;
|
|
2719
3308
|
const skinPaletteAllocator = pipelineState?.skinPaletteAllocator ?? null;
|
|
2720
3309
|
|
|
@@ -2732,8 +3321,13 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2732
3321
|
// `row.get(X) !== undefined`); K-3 invariant preserved (`_getArrayView`
|
|
2733
3322
|
// calls survive untouched, only the entity source changes).
|
|
2734
3323
|
const worldInternal = createWorldInternalView(world);
|
|
3324
|
+
let volumetricFog = extractVolumeSnapshot(world, assets, worldInternal);
|
|
2735
3325
|
|
|
2736
|
-
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);
|
|
2737
3331
|
|
|
2738
3332
|
// Three-query union (M2 / w16 / AC-03): directional has no Transform
|
|
2739
3333
|
// dependency (sun-like infinite-source semantics); point + spot pull
|
|
@@ -2754,7 +3348,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2754
3348
|
depthBias: number;
|
|
2755
3349
|
normalBias: number;
|
|
2756
3350
|
shadowDistance: number;
|
|
2757
|
-
|
|
3351
|
+
shadowFilter: number;
|
|
3352
|
+
shadowAngularRadius: number;
|
|
3353
|
+
maxPenumbraTexels: number;
|
|
2758
3354
|
}
|
|
2759
3355
|
| undefined;
|
|
2760
3356
|
for (const row of directionalLightQuery) {
|
|
@@ -2763,6 +3359,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2763
3359
|
const intensity = l.intensity;
|
|
2764
3360
|
const snapshot: DirectionalLightSnapshot = {
|
|
2765
3361
|
kind: 'directional',
|
|
3362
|
+
entity: row.entity,
|
|
2766
3363
|
direction: vec3.create(l.direction[0] ?? 0, l.direction[1] ?? -1, l.direction[2] ?? 0),
|
|
2767
3364
|
color: vec3.create(
|
|
2768
3365
|
(l.color[0] ?? 1) * intensity,
|
|
@@ -2783,11 +3380,29 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2783
3380
|
depthBias: l.depthBias,
|
|
2784
3381
|
normalBias: l.normalBias,
|
|
2785
3382
|
shadowDistance: l.shadowDistance,
|
|
2786
|
-
|
|
3383
|
+
shadowFilter: l.shadowFilter,
|
|
3384
|
+
shadowAngularRadius: l.shadowAngularRadius,
|
|
3385
|
+
maxPenumbraTexels: l.maxPenumbraTexels,
|
|
2787
3386
|
};
|
|
2788
3387
|
}
|
|
2789
3388
|
}
|
|
2790
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
|
+
|
|
2791
3406
|
const pointSnapshots: PointLightSnapshot[] = [];
|
|
2792
3407
|
// feat-20260612-point-light-shadows-urp-hdrp M4 / T-M4-4: track entity per
|
|
2793
3408
|
// pointSnapshots index so the post-extract pointShadow join can stamp
|
|
@@ -2818,6 +3433,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2818
3433
|
worldMat !== undefined ? mat4.getTranslation(vec3.create(), worldMat) : vec3.create(0, 0, 0);
|
|
2819
3434
|
pointSnapshots.push({
|
|
2820
3435
|
kind: 'point',
|
|
3436
|
+
entity: entityId,
|
|
2821
3437
|
position,
|
|
2822
3438
|
color: vec3.create(
|
|
2823
3439
|
(p.color[0] ?? 1) * intensity,
|
|
@@ -2889,11 +3505,20 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2889
3505
|
const sMapSize = s.mapSize;
|
|
2890
3506
|
const sNearPlane = s.nearPlane;
|
|
2891
3507
|
const sFarPlane = s.farPlane;
|
|
3508
|
+
const shadowIntensity = s.shadowIntensity;
|
|
3509
|
+
// `shared<T>` columns are u32-backed and zero-filled when the optional
|
|
3510
|
+
// field is absent. Treat only a positive handle as an authored projector;
|
|
3511
|
+
// checking nullish values alone would turn the zero sentinel into a
|
|
3512
|
+
// phantom projector matrix and an avoidable asset-not-found diagnostic.
|
|
3513
|
+
const hasProjector = Number(s.projector) > 0;
|
|
2892
3514
|
|
|
2893
3515
|
let lightViewProj: Float32Array | undefined;
|
|
2894
3516
|
let shadowAtlasTile = -1;
|
|
2895
3517
|
|
|
2896
|
-
|
|
3518
|
+
// A projector needs the same perspective transform even when its SpotLight
|
|
3519
|
+
// has no shadow atlas tile. The matrix remains a derived light fact; the
|
|
3520
|
+
// shadow tile stays -1 so shadow visibility is still independently gated.
|
|
3521
|
+
if ((castShadow || hasProjector) && hasValidDirection) {
|
|
2897
3522
|
const target = vec3.create(
|
|
2898
3523
|
(position[0] ?? 0) + (dirN[0] ?? 0),
|
|
2899
3524
|
(position[1] ?? 0) + (dirN[1] ?? 0),
|
|
@@ -2912,14 +3537,40 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2912
3537
|
mat4.multiply(lightViewProj as Mat4, proj, view);
|
|
2913
3538
|
|
|
2914
3539
|
// D-4: allocate tile 0..3; 5th+ = -1 sentinel.
|
|
2915
|
-
if (spotTileNext < 4) {
|
|
3540
|
+
if (castShadow && spotTileNext < 4) {
|
|
2916
3541
|
shadowAtlasTile = spotTileNext;
|
|
2917
3542
|
spotTileNext += 1;
|
|
2918
3543
|
}
|
|
2919
3544
|
}
|
|
2920
3545
|
|
|
3546
|
+
let projectorHandle: Handle<'TextureAsset', 'shared'> | undefined;
|
|
3547
|
+
let projectorAsset: TextureAsset | undefined;
|
|
3548
|
+
let projectorGuid: string | undefined;
|
|
3549
|
+
if (hasProjector) {
|
|
3550
|
+
const candidate = toShared<'TextureAsset'>(Math.round(Number(s.projector)));
|
|
3551
|
+
const resolvedProjector = resolveAssetHandle<TextureAsset>(world, candidate);
|
|
3552
|
+
if (!resolvedProjector.ok || resolvedProjector.value.kind !== 'texture') {
|
|
3553
|
+
worldInternal._routeError(
|
|
3554
|
+
resolvedProjector.ok
|
|
3555
|
+
? new RhiError({
|
|
3556
|
+
code: 'asset-not-registered',
|
|
3557
|
+
expected: 'SpotLight.projector resolves to a TextureAsset',
|
|
3558
|
+
hint: 'load the authored projector TextureAsset before assigning SpotLight.projector',
|
|
3559
|
+
detail: { assetHandle: Number(candidate) },
|
|
3560
|
+
})
|
|
3561
|
+
: resolvedProjector.error,
|
|
3562
|
+
{ severity: Severity.Error, systemName: 'RenderSystem.extract (spot-projector)' },
|
|
3563
|
+
);
|
|
3564
|
+
} else {
|
|
3565
|
+
projectorHandle = candidate;
|
|
3566
|
+
projectorAsset = resolvedProjector.value;
|
|
3567
|
+
projectorGuid = assets?.guidOf(projectorAsset) ?? `handle:${Number(candidate)}`;
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
|
|
2921
3571
|
spotSnapshots.push({
|
|
2922
3572
|
kind: 'spot',
|
|
3573
|
+
entity: row.entity,
|
|
2923
3574
|
// D-6: position reflects world transform; direction stays sourced
|
|
2924
3575
|
// from SpotLight.direction (NOT rotated by the parent).
|
|
2925
3576
|
position,
|
|
@@ -2940,9 +3591,59 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2940
3591
|
nearPlane: sNearPlane,
|
|
2941
3592
|
farPlane: sFarPlane,
|
|
2942
3593
|
shadowAtlasTile,
|
|
3594
|
+
shadowIntensity,
|
|
3595
|
+
depthBias: s.depthBias,
|
|
3596
|
+
normalBias: s.normalBias,
|
|
3597
|
+
pcfKernelSize: s.pcfKernelSize,
|
|
3598
|
+
...(projectorHandle === undefined ||
|
|
3599
|
+
projectorAsset === undefined ||
|
|
3600
|
+
projectorGuid === undefined
|
|
3601
|
+
? {}
|
|
3602
|
+
: {
|
|
3603
|
+
projectorHandle,
|
|
3604
|
+
projectorAsset,
|
|
3605
|
+
projectorGuid,
|
|
3606
|
+
projectorGeneration: 1,
|
|
3607
|
+
projectorRevision: 1,
|
|
3608
|
+
}),
|
|
2943
3609
|
});
|
|
2944
3610
|
}
|
|
2945
3611
|
|
|
3612
|
+
// The SpotLight is the sole projector owner. Once all light snapshots have
|
|
3613
|
+
// been extracted, publish its accepted TextureAsset tuple on the volume
|
|
3614
|
+
// snapshot so surface and volume consume the same identity and revision.
|
|
3615
|
+
let volumeSpot: SpotLightSnapshot | undefined;
|
|
3616
|
+
const extractedFog = volumetricFog;
|
|
3617
|
+
if (extractedFog !== undefined) {
|
|
3618
|
+
if (extractedFog.spotLightEntity !== undefined) {
|
|
3619
|
+
volumeSpot = spotSnapshots.find((light) => light.entity === extractedFog.spotLightEntity);
|
|
3620
|
+
} else if (extractedFog.lightKind === 'spot') {
|
|
3621
|
+
volumeSpot = spotSnapshots.find((light) => light.entity === extractedFog.lightEntity);
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
if (
|
|
3625
|
+
volumetricFog !== undefined &&
|
|
3626
|
+
volumetricFog.status === 'available' &&
|
|
3627
|
+
volumeSpot?.projectorAsset !== undefined &&
|
|
3628
|
+
volumeSpot.projectorHandle !== undefined
|
|
3629
|
+
) {
|
|
3630
|
+
const projectorHandle = volumeSpot.projectorHandle;
|
|
3631
|
+
const projectorAsset = volumeSpot.projectorAsset;
|
|
3632
|
+
volumetricFog = {
|
|
3633
|
+
...volumetricFog,
|
|
3634
|
+
projector: {
|
|
3635
|
+
guid: volumeSpot.projectorGuid ?? `handle:${Number(volumeSpot.projectorHandle)}`,
|
|
3636
|
+
generation: volumeSpot.projectorGeneration ?? 1,
|
|
3637
|
+
view: '2d',
|
|
3638
|
+
sampler: 'linear-clamp-to-edge',
|
|
3639
|
+
projection: 'spot-projection',
|
|
3640
|
+
revision: volumeSpot.projectorRevision ?? 1,
|
|
3641
|
+
},
|
|
3642
|
+
projectorHandle,
|
|
3643
|
+
projectorAsset,
|
|
3644
|
+
};
|
|
3645
|
+
}
|
|
3646
|
+
|
|
2946
3647
|
// bug-20260710-editor-cross-world-shadow: CSM matrices are computed by the
|
|
2947
3648
|
// shared pure {@link computeDirectionalCsm}, called here per-world with THIS
|
|
2948
3649
|
// world's own `cameras[0]`. In a single-world app the light and camera share
|
|
@@ -2957,6 +3658,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2957
3658
|
let cascadeCount: number | undefined;
|
|
2958
3659
|
let cascadeBlend: number | undefined;
|
|
2959
3660
|
let shadowMapSize: number | undefined;
|
|
3661
|
+
let directionalShadowQuality: DirectionalShadowQuality | undefined;
|
|
2960
3662
|
let directionalCsmConfig: DirectionalCsmConfig | undefined;
|
|
2961
3663
|
let directionalCsmDirection: Vec3 | undefined;
|
|
2962
3664
|
|
|
@@ -2982,7 +3684,11 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2982
3684
|
// feat-20260621 M2: CSM computation gated on castShadow from the
|
|
2983
3685
|
// merged DirectionalLight. castShadow defaults to true (first-hit-wins
|
|
2984
3686
|
// semantics, D-6 no cardinality cap).
|
|
2985
|
-
if (
|
|
3687
|
+
if (
|
|
3688
|
+
directional !== undefined &&
|
|
3689
|
+
firstHitCastShadow !== false &&
|
|
3690
|
+
directionalShadowError === undefined
|
|
3691
|
+
) {
|
|
2986
3692
|
const dirSnapshot = directional;
|
|
2987
3693
|
const sf = firstHitShadowFields;
|
|
2988
3694
|
if (sf !== undefined) {
|
|
@@ -2992,6 +3698,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2992
3698
|
cascadeBlend: sf.cascadeBlend,
|
|
2993
3699
|
mapSize: sf.mapSize,
|
|
2994
3700
|
shadowDistance: sf.shadowDistance,
|
|
3701
|
+
shadowFilter: sf.shadowFilter,
|
|
3702
|
+
shadowAngularRadius: sf.shadowAngularRadius,
|
|
3703
|
+
maxPenumbraTexels: sf.maxPenumbraTexels,
|
|
2995
3704
|
};
|
|
2996
3705
|
directionalCsmDirection = dirSnapshot.direction;
|
|
2997
3706
|
const csm = computeDirectionalCsm(dirSnapshot.direction, directionalCsmConfig, cameraData);
|
|
@@ -3006,13 +3715,19 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3006
3715
|
if (csm !== null) {
|
|
3007
3716
|
lightViewProj = csm.lightViewProj;
|
|
3008
3717
|
splitPlanes = csm.splitPlanes;
|
|
3718
|
+
directionalShadowQuality = csm.directionalShadowQuality;
|
|
3009
3719
|
} else {
|
|
3010
3720
|
const sNear = cameraData?.near ?? 0.1;
|
|
3011
3721
|
const splits = pssmSplit(sNear, sf.shadowDistance, cascadeCount, sf.splitLambda);
|
|
3012
|
-
const padded = new Float32Array(
|
|
3013
|
-
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;
|
|
3014
3724
|
splitPlanes = padded;
|
|
3015
3725
|
}
|
|
3726
|
+
directionalShadowQuality = directionalShadowQualityFromF32(
|
|
3727
|
+
sf.shadowFilter,
|
|
3728
|
+
sf.shadowAngularRadius,
|
|
3729
|
+
sf.maxPenumbraTexels,
|
|
3730
|
+
);
|
|
3016
3731
|
}
|
|
3017
3732
|
}
|
|
3018
3733
|
|
|
@@ -3021,7 +3736,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3021
3736
|
// the View UBO tail keeps a stable layout regardless of the runtime
|
|
3022
3737
|
// cascadeCount. Host-side correctness invariant: only the first
|
|
3023
3738
|
// cascadeCount slots are ever read by the WGSL kernel.
|
|
3024
|
-
const paddedSplitPlanes = new Float32Array(
|
|
3739
|
+
const paddedSplitPlanes = new Float32Array(16);
|
|
3025
3740
|
if (splitPlanes !== undefined) {
|
|
3026
3741
|
for (let i = 0; i < splitPlanes.length; i++) {
|
|
3027
3742
|
paddedSplitPlanes[i] = splitPlanes[i] ?? 0;
|
|
@@ -3031,9 +3746,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3031
3746
|
// feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-7 (plan-strategy §D-3,
|
|
3032
3747
|
// requirements §5.3): query (PointLight + PointLightShadow + Transform)
|
|
3033
3748
|
// archetype join. For each shadow caster, build 6 face VP matrices and pack
|
|
3034
|
-
// into Float32Array(96). Atlas layer assigned in spawn order
|
|
3035
|
-
// sentinel -1 is shader-side and
|
|
3036
|
-
//
|
|
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.
|
|
3037
3752
|
const pointShadowSnapshots: PointShadowSnapshot[] = [];
|
|
3038
3753
|
{
|
|
3039
3754
|
const pointShadowQuery = world
|
|
@@ -3061,7 +3776,13 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3061
3776
|
const mapSize = ps.mapSize;
|
|
3062
3777
|
const nearPlane = ps.nearPlane;
|
|
3063
3778
|
const farPlane = ps.farPlane;
|
|
3064
|
-
|
|
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;
|
|
3065
3786
|
|
|
3066
3787
|
const matrices = buildPointShadowMatrices(lightPos, nearPlane, farPlane);
|
|
3067
3788
|
const packed = new Float32Array(96);
|
|
@@ -3119,7 +3840,8 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3119
3840
|
shadowMapSize,
|
|
3120
3841
|
depthBias: firstHitCastShadow !== false ? firstHitShadowFields?.depthBias : undefined,
|
|
3121
3842
|
normalBias: firstHitCastShadow !== false ? firstHitShadowFields?.normalBias : undefined,
|
|
3122
|
-
|
|
3843
|
+
directionalShadowQuality,
|
|
3844
|
+
directionalShadowError,
|
|
3123
3845
|
pointShadow: pointShadowSnapshots,
|
|
3124
3846
|
// bug-20260710-editor-cross-world-shadow: raw CSM config + light direction
|
|
3125
3847
|
// so the merge layer can recompute matrices against the surfaced camera.
|
|
@@ -4011,6 +4733,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4011
4733
|
pv.occlusionTexture,
|
|
4012
4734
|
);
|
|
4013
4735
|
const textureCoordinates = collectMaterialTextureCoordinates(pv);
|
|
4736
|
+
const textureSources = collectMaterialTextureSources(pv, world);
|
|
4014
4737
|
const emissivePv = pv.emissive as readonly number[] | undefined;
|
|
4015
4738
|
// feat-20260625 M2 / w6: first-pass transparency flag folds into
|
|
4016
4739
|
// MaterialSnapshot.transparent so the record stage can drive the
|
|
@@ -4110,6 +4833,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4110
4833
|
...(textureCoordinates.size > 0 && { textureCoordinates }),
|
|
4111
4834
|
...(samplerHandles.size > 0 && { samplerHandles }),
|
|
4112
4835
|
...(textureHandles.size > 0 && { textureHandles }),
|
|
4836
|
+
...(textureSources.size > 0 && { textureSources }),
|
|
4113
4837
|
...(videoTextureFields.size > 0 && { videoTextureFields }),
|
|
4114
4838
|
...(baseColorTextureHandle !== undefined && {
|
|
4115
4839
|
baseColorTexture: baseColorTextureHandle,
|
|
@@ -4328,6 +5052,15 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4328
5052
|
}
|
|
4329
5053
|
}
|
|
4330
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
|
+
|
|
4331
5064
|
// feat-20260601 D-3: read the resolved world mat4 (propagateTransforms
|
|
4332
5065
|
// output) straight from the Transform.world column array view. The
|
|
4333
5066
|
// record stage copies these 16 floats into the mesh SSBO with zero
|
|
@@ -4516,6 +5249,44 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4516
5249
|
projection: identityProjection(),
|
|
4517
5250
|
} satisfies PointsLinesRetainedSnapshot);
|
|
4518
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
|
+
|
|
4519
5290
|
let nonIndexedFirst = 0;
|
|
4520
5291
|
const gpuDrivenDraws = gpuDrivenSubmeshes.flatMap((submesh) => {
|
|
4521
5292
|
const drawMaterial = materialsArr[submesh.materialSlot] ?? materialSnap;
|
|
@@ -4548,6 +5319,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4548
5319
|
entityKey: entity,
|
|
4549
5320
|
...(skinSlice !== undefined ? { skin: skinSlice } : {}),
|
|
4550
5321
|
...(morph !== undefined ? { morph } : {}),
|
|
5322
|
+
...(instancesSnap !== undefined ? { instances: instancesSnap } : {}),
|
|
4551
5323
|
...(spriteInstancesSnap !== undefined ? { spriteInstances: spriteInstancesSnap } : {}),
|
|
4552
5324
|
...(pointsLines !== undefined ? { pointsLines } : {}),
|
|
4553
5325
|
};
|
|
@@ -4559,66 +5331,70 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4559
5331
|
// producer-owned finite local-space AABBs. Culling is unconditional
|
|
4560
5332
|
// engine behavior; there is no per-entity opt-out.
|
|
4561
5333
|
if (localAabb !== undefined) {
|
|
4562
|
-
//
|
|
4563
|
-
//
|
|
4564
|
-
//
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
frustumCulled += 1;
|
|
4586
|
-
continue;
|
|
4587
|
-
}
|
|
4588
|
-
}
|
|
4589
|
-
|
|
4590
|
-
if (hasInstances) {
|
|
4591
|
-
const transforms = worldInternal._getArrayView(entity, Instances, 'transforms');
|
|
4592
|
-
if (transforms === undefined) {
|
|
4593
|
-
flushPendingDispatch(pendingDispatch);
|
|
4594
|
-
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.
|
|
4595
5357
|
} else {
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
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
|
+
}
|
|
4602
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;
|
|
4603
5391
|
continue;
|
|
4604
5392
|
}
|
|
4605
|
-
const snapshotCopy = new Float32Array(transforms);
|
|
4606
|
-
const instanceCount = Math.max(1, Math.floor(actualLength / 16));
|
|
4607
|
-
flushPendingDispatch(pendingDispatch);
|
|
4608
|
-
renderables.push({
|
|
4609
|
-
...baseRenderable,
|
|
4610
|
-
instances: {
|
|
4611
|
-
transforms: snapshotCopy,
|
|
4612
|
-
instanceCount,
|
|
4613
|
-
cacheKey: entity,
|
|
4614
|
-
archVersion,
|
|
4615
|
-
},
|
|
4616
|
-
});
|
|
4617
5393
|
}
|
|
4618
|
-
} else {
|
|
4619
|
-
flushPendingDispatch(pendingDispatch);
|
|
4620
|
-
renderables.push(baseRenderable);
|
|
4621
5394
|
}
|
|
5395
|
+
|
|
5396
|
+
flushPendingDispatch(pendingDispatch);
|
|
5397
|
+
renderables.push(baseRenderable);
|
|
4622
5398
|
}
|
|
4623
5399
|
|
|
4624
5400
|
// feat-20260520-2d-sprite-layer-mvp M-3 / w22 + w25: finalise the
|
|
@@ -4653,10 +5429,12 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4653
5429
|
// whitePoint / tonemap` onto the SAME unified params channel custom
|
|
4654
5430
|
// post-processes use — `Camera.exposure` stays the AI-user-facing SSOT (D-5),
|
|
4655
5431
|
// the engine itself acts as the provider for the Standard tonemap shader
|
|
4656
|
-
// id. The 16B layout
|
|
4657
|
-
// (render-system-record.ts pre-w14): Float32 [exposure, whitePoint, _,
|
|
4658
|
-
// with the mode u32 occupying the third 4-byte slot via
|
|
4659
|
-
// 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
|
|
4660
5438
|
// built-in provider is authoritative for its own reserved key (a user entity
|
|
4661
5439
|
// can never shadow the Standard tonemap identity. The single active camera mirrors
|
|
4662
5440
|
// recordFrame's `activeCameras[0]` selection.
|
|
@@ -4667,9 +5445,13 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4667
5445
|
|
|
4668
5446
|
return {
|
|
4669
5447
|
cameras,
|
|
5448
|
+
auxiliaryCameras,
|
|
5449
|
+
cubeCameras,
|
|
5450
|
+
reflectionProbes,
|
|
4670
5451
|
lights,
|
|
4671
5452
|
environment,
|
|
4672
5453
|
environmentReady: true,
|
|
5454
|
+
...(volumetricFog === undefined ? {} : { volumetricFog }),
|
|
4673
5455
|
renderables,
|
|
4674
5456
|
dispatch,
|
|
4675
5457
|
skylight,
|
|
@@ -4677,6 +5459,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4677
5459
|
skybox,
|
|
4678
5460
|
skyboxCount,
|
|
4679
5461
|
fog,
|
|
5462
|
+
...(fogFailure === undefined ? {} : { fogFailure }),
|
|
4680
5463
|
frustumStats: { culled: frustumCulled, total: frustumTotal },
|
|
4681
5464
|
visibilityStats: { explicitlyHidden: explicitlyHidden.size },
|
|
4682
5465
|
postProcessParams,
|