@forgeax/engine-render 0.1.20 → 0.1.23
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 +365 -29
- package/dist/assembly/backend-contract.d.ts +13 -0
- package/dist/assembly/backend-contract.d.ts.map +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 +30 -19
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +19 -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 +4 -1
- 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-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
- package/dist/chunk-4L3P6DT5.mjs.map +1 -0
- package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
- package/dist/chunk-4NPUASZR.mjs.map +1 -0
- package/dist/chunk-DIYW3M5D.mjs +5085 -0
- package/dist/chunk-DIYW3M5D.mjs.map +1 -0
- package/dist/chunk-FBC4R44J.mjs +1003 -0
- package/dist/chunk-FBC4R44J.mjs.map +1 -0
- package/dist/{chunk-CDTWTHZR.mjs → chunk-FF7K3FSO.mjs} +474 -168
- package/dist/chunk-FF7K3FSO.mjs.map +1 -0
- package/dist/chunk-HZUPSYW7.mjs +1104 -0
- package/dist/chunk-HZUPSYW7.mjs.map +1 -0
- package/dist/{chunk-TBQJOK2L.mjs → chunk-QODA5UKU.mjs} +321 -269
- package/dist/chunk-QODA5UKU.mjs.map +1 -0
- package/dist/chunk-YALPS44A.mjs +296 -0
- package/dist/chunk-YALPS44A.mjs.map +1 -0
- package/dist/{chunk-4XINDLAY.mjs → chunk-ZUWKAZ2I.mjs} +1963 -2178
- package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
- package/dist/cluster-binner.d.ts +5 -0
- package/dist/cluster-binner.d.ts.map +1 -1
- 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 +5 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts +4 -0
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/light-probe.d.ts +9 -0
- package/dist/components/light-probe.d.ts.map +1 -0
- package/dist/components/rect-area-light.d.ts +12 -0
- package/dist/components/rect-area-light.d.ts.map +1 -0
- package/dist/components/reflection-probe.d.ts +17 -0
- package/dist/components/reflection-probe.d.ts.map +1 -0
- package/dist/components/scene-instance.d.ts +4 -0
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/spot-light.d.ts +3 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +14836 -9620
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +8 -1
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/lifecycle-transaction.d.ts +2 -0
- package/dist/device/lifecycle-transaction.d.ts.map +1 -0
- 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/gpu-driven.d.ts +26 -0
- package/dist/errors/gpu-driven.d.ts.map +1 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +9 -1
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +166 -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/gpu-driven.d.ts +50 -0
- package/dist/extract/gpu-driven.d.ts.map +1 -0
- 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 +34 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +63 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +97 -2
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +64 -1
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +14 -0
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +8 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +0 -1
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +21 -19
- 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 +39 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +91 -6
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +214 -1
- 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.d.ts +9 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +23 -2
- package/dist/internal.mjs.map +1 -1
- package/dist/light-buffer-layout.d.ts +58 -140
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.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 +38 -14
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts +2 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
- package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
- package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
- package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts +4 -13
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +5 -26
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -0
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +40 -7
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/plugin.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/prepare/extended-lighting/ltc-resources.d.ts +31 -0
- package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/resources.d.ts +72 -0
- package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/state.d.ts +24 -0
- package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +33 -36
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +78 -24
- 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 +16 -6
- 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 +20 -3
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/helpers.d.ts +0 -20
- package/dist/record/helpers.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +6 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +25 -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/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +98 -6
- 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 +119 -4
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +7 -5
- 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/inspection.d.ts +10 -0
- package/dist/reflection/inspection.d.ts.map +1 -0
- package/dist/reflection/projection.d.ts +140 -0
- package/dist/reflection/projection.d.ts.map +1 -0
- package/dist/reflection/record-owner.d.ts +60 -0
- package/dist/reflection/record-owner.d.ts.map +1 -0
- package/dist/render-contract.d.ts +62 -14
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +49 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +160 -31
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +28 -2
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer.d.ts +43 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/scene/probe-blend-record.d.ts +22 -0
- package/dist/scene/probe-blend-record.d.ts.map +1 -0
- package/dist/scene/probe-blend.d.ts +142 -0
- package/dist/scene/probe-blend.d.ts.map +1 -0
- package/dist/scene/render-scene-types.d.ts +0 -6
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +117 -9
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/visibility/bounds.d.ts +20 -0
- package/dist/scene/visibility/bounds.d.ts.map +1 -0
- package/dist/scene/visibility/budget.d.ts +16 -0
- package/dist/scene/visibility/budget.d.ts.map +1 -0
- package/dist/scene/visibility/facet.d.ts +73 -0
- package/dist/scene/visibility/facet.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-lod.d.ts +46 -0
- package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
- package/dist/scene/visibility/inspection.d.ts +132 -0
- package/dist/scene/visibility/inspection.d.ts.map +1 -0
- package/dist/scene/visibility/lod-selector.d.ts +25 -0
- package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
- package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
- package/dist/scene/visibility/types.d.ts +34 -0
- package/dist/scene/visibility/types.d.ts.map +1 -0
- 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/ssr/admission.d.ts +122 -0
- package/dist/ssr/admission.d.ts.map +1 -0
- package/dist/ssr/composition.d.ts +32 -0
- package/dist/ssr/composition.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +39 -0
- package/dist/ssr/errors.d.ts.map +1 -0
- package/dist/ssr/identity.d.ts +13 -0
- package/dist/ssr/identity.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +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-coordinator.d.ts +13 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +2 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -5
- package/dist/temporal/view.d.ts +7 -0
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/tilemap-chunk-extract-system.d.ts +1 -1
- package/dist/typed-render-graph-primitives.d.ts +5 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +1 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +9 -5
- package/dist/volume/passes.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
- package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
- package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
- package/src/__tests__/cluster-binner.unit.test.ts +35 -0
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +12 -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__/detached-inspection.integration.test.ts +6 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +85 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +62 -6
- package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
- package/src/__tests__/environment-generation.integration.test.ts +16 -0
- package/src/__tests__/evidence-identity.ts +13 -0
- package/src/__tests__/evidence-identity.unit.test.ts +24 -0
- package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
- package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
- package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
- package/src/__tests__/factory-contract.integration.test.ts +288 -29
- package/src/__tests__/fog-component.unit.test.ts +67 -0
- package/src/__tests__/fog.integration.test.ts +86 -0
- package/src/__tests__/frame-observation.contract.test.ts +15 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -8
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +18 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +579 -17
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +101 -6
- package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
- package/src/__tests__/gpu-lod-evidence.ts +188 -0
- package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
- package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
- package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
- package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
- package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
- package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
- package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
- 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__/gpu-resource-store-stride.unit.test.ts +66 -1
- package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
- package/src/__tests__/gpu-scene.unit.test.ts +49 -3
- 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__/light-buffer-layout.unit.test.ts +113 -3
- package/src/__tests__/light-inspection.unit.test.ts +55 -0
- package/src/__tests__/light-resource-error.unit.test.ts +43 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
- package/src/__tests__/lod-bounds.unit.test.ts +42 -0
- package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
- package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
- package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
- package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
- package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
- package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
- package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
- package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
- package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
- package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
- package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
- package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
- package/src/__tests__/lod-selector.profile.test.ts +35 -0
- package/src/__tests__/lod-selector.unit.test.ts +68 -0
- package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
- package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
- package/src/__tests__/ltc-resources.unit.test.ts +32 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
- package/src/__tests__/material-observation.integration.test.ts +101 -0
- package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
- package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
- package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
- package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
- package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
- package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
- package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
- package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
- package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
- package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
- package/src/__tests__/occlusion-query.evidence.ts +178 -0
- package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
- package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
- package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
- package/src/__tests__/probe-bindings.unit.test.ts +200 -0
- package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
- package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
- package/src/__tests__/probe-capacity.integration.test.ts +97 -0
- package/src/__tests__/probe-recovery.integration.test.ts +104 -0
- package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
- package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
- package/src/__tests__/profiler-phase-token.test.ts +1 -0
- package/src/__tests__/public-surface.test-d.ts +10 -0
- package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
- package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
- package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
- package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
- package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
- package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -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-owner-placement.unit.test.ts +39 -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-record-owner.characterization.unit.test.ts +120 -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 +19 -3
- package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
- package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
- package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
- 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 +124 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
- package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
- package/src/__tests__/skin-motion-regression.unit.test.ts +154 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +13 -4
- package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
- package/src/__tests__/ssr-composition.unit.test.ts +46 -0
- package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
- package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
- 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 +299 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +15 -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 +441 -0
- package/src/__tests__/standard-lighting.integration.test.ts +67 -0
- package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
- package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
- package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
- package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
- package/src/__tests__/temporal-view.unit.test.ts +3 -3
- package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
- 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-budget.integration.test.ts +140 -0
- package/src/__tests__/visibility-budget.unit.test.ts +70 -0
- package/src/__tests__/visibility-extract.integration.test.ts +11 -6
- package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
- package/src/assembly/backend-contract.ts +13 -0
- package/src/assembly/device-feature-admission.ts +80 -0
- package/src/assembly/directional-shadow-inspection.ts +122 -0
- package/src/assembly/factory.ts +1411 -366
- package/src/assembly/host-contract.ts +35 -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/cluster-binner.ts +31 -0
- package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
- package/src/components/__tests__/rect-area-light.unit.test.ts +33 -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 +5 -0
- package/src/components/light-helpers.ts +133 -8
- package/src/components/light-probe.ts +13 -0
- package/src/components/rect-area-light.ts +15 -0
- package/src/components/reflection-probe.ts +49 -0
- package/src/components/scene-instance.ts +6 -0
- package/src/components/spot-light.ts +3 -0
- package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
- package/src/device/gpu-residency.ts +140 -9
- package/src/device/lifecycle-transaction.ts +12 -0
- package/src/environment/frame.ts +6 -24
- package/src/environment/lifecycle.ts +20 -1
- package/src/errors/gpu-driven.ts +92 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/recover.ts +32 -0
- package/src/errors/render.ts +341 -143
- package/src/extract/camera.ts +3 -3
- package/src/extract/directional-shadow-projection.ts +61 -0
- package/src/extract/environment.ts +57 -0
- package/src/extract/gpu-driven.ts +108 -0
- package/src/extract/visibility.ts +2 -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 +207 -28
- package/src/gpu-driven/prepared-draw.ts +208 -0
- package/src/gpu-driven/production-raster.ts +953 -43
- package/src/gpu-driven/view-gpu.ts +411 -29
- package/src/gpu-scene-schema.ts +13 -0
- package/src/gpu-scene.ts +140 -64
- package/src/gpu-usage.ts +0 -1
- package/src/hdrp-buffers.ts +169 -63
- package/src/ibl/IblPipelineCache.ts +11 -0
- package/src/ibl/kernel-cache.ts +88 -0
- package/src/index.ts +222 -0
- package/src/inspection-types.ts +276 -1
- package/src/instances-derived-bounds.ts +171 -0
- package/src/internal.ts +38 -0
- package/src/light-buffer-layout.ts +155 -310
- package/src/materials.ts +16 -2
- package/src/mesh-material-bindings.ts +132 -0
- package/src/pbr-pipeline.ts +255 -60
- package/src/pipeline/standard-forward-lane.ts +93 -16
- package/src/pipeline/standard-lighting/errors.ts +6 -0
- package/src/pipeline/standard-lighting/graph.ts +158 -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 +35 -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 +78 -0
- package/src/pipeline/standard-pipeline.ts +124 -193
- package/src/pipeline/standard-post.ts +26 -7
- package/src/pipeline/standard-profile.ts +17 -47
- package/src/pipeline-builder.ts +5 -2
- package/src/pipeline-spec.ts +95 -21
- package/src/plugin.ts +4 -0
- package/src/point-shadow-inspection.ts +55 -0
- package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
- package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
- package/src/prepare/extended-lighting/resources.ts +389 -0
- package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
- package/src/prepare/extended-lighting/state.ts +92 -0
- package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +64 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
- package/src/record/frame-lighting.ts +591 -452
- package/src/record/frame-snapshot.ts +92 -39
- package/src/record/frame-targets.ts +17 -0
- package/src/record/frame.ts +662 -158
- 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 +112 -39
- package/src/record/helpers.ts +0 -58
- package/src/record/main-pass-geometry.ts +324 -119
- package/src/record/main-pass-material.ts +103 -10
- package/src/record/main-pass-sprite-draws.ts +99 -103
- package/src/record/main-pass.ts +71 -15
- package/src/record/mesh-ssbo.ts +47 -36
- package/src/record/render-context.ts +136 -3
- package/src/record/shadow-pass.ts +83 -68
- package/src/record/skybox-post-pass.ts +20 -4
- package/src/record/typed-frame-graph.ts +1034 -43
- package/src/record/view-ubo.ts +72 -47
- package/src/reflection/filter.ts +81 -0
- package/src/reflection/gpu-table.ts +44 -0
- package/src/reflection/inspection.ts +67 -0
- package/src/reflection/projection.ts +476 -0
- package/src/reflection/record-owner.ts +1283 -0
- package/src/render-contract.ts +93 -10
- package/src/render-graph-primitives.ts +9 -4
- package/src/render-pipeline.ts +122 -2
- package/src/render-system-extract.ts +1041 -225
- package/src/render-system.ts +1310 -85
- package/src/renderer.ts +222 -0
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
- package/src/scene/probe-blend-record.ts +54 -0
- package/src/scene/probe-blend.ts +813 -0
- package/src/scene/render-scene-types.ts +0 -7
- package/src/scene/render-scene.ts +942 -171
- package/src/scene/visibility/bounds.ts +57 -0
- package/src/scene/visibility/budget.ts +40 -0
- package/src/scene/visibility/facet.ts +400 -0
- package/src/scene/visibility/gpu-frame-timing.ts +101 -0
- package/src/scene/visibility/gpu-lod.ts +119 -0
- package/src/scene/visibility/inspection.ts +313 -0
- package/src/scene/visibility/lod-selector.ts +84 -0
- package/src/scene/visibility/occlusion-confidence.ts +478 -0
- package/src/scene/visibility/occlusion-pass.ts +165 -0
- package/src/scene/visibility/occlusion-query-pool.ts +178 -0
- package/src/scene/visibility/occlusion-runtime.ts +672 -0
- package/src/scene/visibility/types.ts +50 -0
- package/src/shadow-atlas.ts +5 -5
- package/src/ssao-buffers.ts +9 -2
- package/src/ssr/admission.ts +451 -0
- package/src/ssr/composition.ts +73 -0
- package/src/ssr/errors.ts +76 -0
- package/src/ssr/identity.ts +12 -0
- package/src/systems/skin-palette-allocator.ts +1 -1
- 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-coordinator.ts +26 -0
- package/src/temporal/frame.ts +14 -1
- package/src/temporal/view.ts +11 -0
- package/src/tilemap-chunk-extract-system.ts +4 -4
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
- package/src/typed-render-graph-primitives.ts +53 -3
- package/src/typed-shadow-passes.ts +46 -40
- package/src/volume/passes.ts +61 -58
- package/dist/.tsbuildinfo +0 -1
- package/dist/chunk-4XINDLAY.mjs.map +0 -1
- package/dist/chunk-CDTWTHZR.mjs.map +0 -1
- package/dist/chunk-M2MP4ZOU.mjs +0 -73
- package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
- package/dist/chunk-PCHBPYML.mjs.map +0 -1
- package/dist/chunk-TBQJOK2L.mjs.map +0 -1
- package/dist/chunk-VFPLTKCU.mjs.map +0 -1
|
@@ -112,15 +112,12 @@ import {
|
|
|
112
112
|
SpriteInstancesMutuallyExclusiveWithInstancesError,
|
|
113
113
|
SpriteInstancesRequiresSpriteShaderError,
|
|
114
114
|
} from '@forgeax/engine-ecs/projection';
|
|
115
|
+
import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
|
|
115
116
|
import { box3, frustum, type Mat4, mat4, type Vec3, vec3 } from '@forgeax/engine-math';
|
|
116
117
|
import { AssetGuid, type AssetGuid as AssetGuidBytes } from '@forgeax/engine-pack/guid';
|
|
117
118
|
import { RhiError } from '@forgeax/engine-rhi';
|
|
118
|
-
import {
|
|
119
|
-
|
|
120
|
-
projectHierarchy,
|
|
121
|
-
type SceneHierarchySnapshot,
|
|
122
|
-
Transform,
|
|
123
|
-
} from '@forgeax/engine-scene';
|
|
119
|
+
import { GlobalTransform, MorphWeights, Transform } from '@forgeax/engine-scene';
|
|
120
|
+
import type { MaterialShaderArtifact } from '@forgeax/engine-shader';
|
|
124
121
|
import {
|
|
125
122
|
JointCountMismatchError,
|
|
126
123
|
JointEntityDanglingError,
|
|
@@ -131,6 +128,7 @@ import {
|
|
|
131
128
|
import type {
|
|
132
129
|
Asset,
|
|
133
130
|
Handle,
|
|
131
|
+
IesProfileAsset,
|
|
134
132
|
MaterialAsset,
|
|
135
133
|
MaterialColorParameterSchema,
|
|
136
134
|
MaterialParameter,
|
|
@@ -139,6 +137,7 @@ import type {
|
|
|
139
137
|
MaterialTextureCoordinates,
|
|
140
138
|
MaterialTextureValue,
|
|
141
139
|
MeshAsset,
|
|
140
|
+
MeshLodLevel,
|
|
142
141
|
ParamSchemaEntry,
|
|
143
142
|
PrimitiveTopology,
|
|
144
143
|
SkeletonAsset,
|
|
@@ -148,53 +147,84 @@ import {
|
|
|
148
147
|
ASSET_ERROR_HINTS,
|
|
149
148
|
AssetError,
|
|
150
149
|
derive,
|
|
150
|
+
handleSlot,
|
|
151
151
|
materialGuidText,
|
|
152
152
|
materialValuesToLinearRuntime,
|
|
153
153
|
toShared,
|
|
154
154
|
} from '@forgeax/engine-types';
|
|
155
155
|
import {
|
|
156
156
|
Atmosphere,
|
|
157
|
+
antialiasFromF32,
|
|
158
|
+
bloomEnabledFromF32,
|
|
159
|
+
Camera,
|
|
160
|
+
CubeCamera,
|
|
161
|
+
cameraProjectionFromF32,
|
|
162
|
+
cubeCameraUpdateIntentFromF32,
|
|
157
163
|
DirectionalLight,
|
|
158
164
|
Fog,
|
|
159
165
|
Instances,
|
|
160
166
|
Layer,
|
|
167
|
+
LightProbe,
|
|
161
168
|
Lines,
|
|
162
169
|
MeshFilter,
|
|
163
170
|
MeshRenderer,
|
|
171
|
+
MotionBlur,
|
|
164
172
|
PointLight,
|
|
165
173
|
PointLightShadow,
|
|
166
174
|
Points,
|
|
167
175
|
PostProcessParams,
|
|
168
176
|
pointShapeFromU32,
|
|
177
|
+
RectAreaLight,
|
|
178
|
+
ReflectionProbe,
|
|
179
|
+
reflectionProbeUpdateIntentFromF32,
|
|
169
180
|
SkyboxBackground,
|
|
170
181
|
Skylight,
|
|
171
182
|
SortKey,
|
|
172
183
|
SpotLight,
|
|
173
184
|
SpriteInstances,
|
|
174
185
|
SpriteRegionOverride,
|
|
186
|
+
tonemapFromF32,
|
|
175
187
|
tonemapToU32,
|
|
176
188
|
} from './components';
|
|
189
|
+
import {
|
|
190
|
+
type DirectionalShadowQuality,
|
|
191
|
+
directionalShadowQualityFromF32,
|
|
192
|
+
} from './components/directional-shadow-filter';
|
|
177
193
|
import { GlyphText } from './components/glyph-text';
|
|
178
|
-
import {
|
|
194
|
+
import {
|
|
195
|
+
computeInvRangeSquared,
|
|
196
|
+
degToCos,
|
|
197
|
+
type LightValidationError,
|
|
198
|
+
validateDirectionalLightData,
|
|
199
|
+
validateLightProbeData,
|
|
200
|
+
validateRectAreaLightData,
|
|
201
|
+
} from './components/light-helpers';
|
|
179
202
|
import { selectEnvironment } from './environment/frame';
|
|
180
203
|
import {
|
|
181
204
|
MaterialSkinAttrMissingError,
|
|
182
205
|
ShadowInvalidConfigError,
|
|
183
206
|
SkinMaterialMismatchError,
|
|
184
207
|
} from './errors/render';
|
|
185
|
-
import {
|
|
208
|
+
import {
|
|
209
|
+
type DirectionalShadowCascadeFit,
|
|
210
|
+
projectDirectionalShadow,
|
|
211
|
+
} from './extract/directional-shadow-projection';
|
|
186
212
|
import type {
|
|
187
213
|
EnvironmentCandidate,
|
|
188
214
|
EnvironmentFrame,
|
|
189
215
|
FogCandidate,
|
|
190
216
|
FogFrame,
|
|
217
|
+
FogSelectionFailure,
|
|
191
218
|
} from './extract/environment';
|
|
219
|
+
import { buildGpuDrivenDraws, resolvePreparedGpuDrivenDraw } from './extract/gpu-driven';
|
|
192
220
|
import { resolveVisibility, type VisibilitySnapshot } from './extract/visibility';
|
|
193
221
|
import type {
|
|
194
222
|
RenderFeatureHiddenEntityReport,
|
|
195
223
|
RenderFeatureWorldVisibilitySnapshot,
|
|
196
224
|
} from './features/types';
|
|
197
225
|
import { ensureGlyphMeshMaterialSlots } from './glyph-text-layout-system';
|
|
226
|
+
import type { GpuDrivenGeometryReceipt } from './gpu-driven/prepared-draw';
|
|
227
|
+
import { deriveInstancesUnionBounds } from './instances-derived-bounds';
|
|
198
228
|
import {
|
|
199
229
|
type MeshMaterialBindingDiagnostic,
|
|
200
230
|
type MeshMaterialBindingSource,
|
|
@@ -203,13 +233,22 @@ import {
|
|
|
203
233
|
import { isStandardPbrMaterialShader } from './pbr-pipeline';
|
|
204
234
|
import { expandPointsLinesBounds } from './points-lines/bounds';
|
|
205
235
|
import type { PointsLinesRetainedSnapshot, PointsLinesStyle } from './points-lines/snapshot';
|
|
206
|
-
import {
|
|
236
|
+
import { COOKIE_SLICE_CAPACITY, IES_SLICE_CAPACITY } from './prepare/extended-lighting/resources';
|
|
237
|
+
import { prepareCookieProjection } from './prepare/extended-lighting/spot-modifiers';
|
|
238
|
+
import type { ReflectionProbeFact } from './reflection/projection';
|
|
239
|
+
import {
|
|
240
|
+
type CameraSnapshot,
|
|
241
|
+
type CubeCameraSnapshot,
|
|
242
|
+
STANDARD_OUTPUT_TRANSFORM_FEATURE_ID,
|
|
243
|
+
} from './render-contract';
|
|
244
|
+
import { SHADOW_ATLAS_DEFAULT_LAYERS } from './shadow-atlas';
|
|
245
|
+
import { getActiveCamera, selectActiveCameraIndex } from './systems/active-camera';
|
|
207
246
|
import { selectPasses } from './systems/pass-selector';
|
|
208
247
|
|
|
209
|
-
export { extractCameraSnapshots } from './extract/camera';
|
|
210
|
-
|
|
211
248
|
import type { SkinPaletteAllocator } from './systems/skin-palette-allocator';
|
|
212
249
|
import type { SkinPaletteSlice } from './systems/skin-palette-types';
|
|
250
|
+
import type { RenderTarget, RenderTargetTextureSource } from './targets/contracts';
|
|
251
|
+
import { resolveRenderTargetMaterialSource } from './targets/material-source';
|
|
213
252
|
import { resolveSelectedVolumetricLight } from './volume/capability';
|
|
214
253
|
import {
|
|
215
254
|
type ValidatedVolumetricFog,
|
|
@@ -252,23 +291,25 @@ export interface PointLightSnapshot {
|
|
|
252
291
|
readonly invRangeSquared: number;
|
|
253
292
|
/**
|
|
254
293
|
* feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-8 + M4 / T-M4-4:
|
|
255
|
-
* cube_array atlas layer index for this light's shadow map (0..
|
|
256
|
-
* casters; sentinel `-1` for non-
|
|
294
|
+
* cube_array atlas layer index for this light's shadow map (0..capacity-1
|
|
295
|
+
* for admitted shadow casters; sentinel `-1` for non-admitted lights).
|
|
296
|
+
* Default `-1` per
|
|
257
297
|
* plan-strategy §D-2 — record stage / shader skips shadow sampling when the
|
|
258
298
|
* lane equals the sentinel. Joined with `pointShadow[]` by entity at the
|
|
259
299
|
* end of extract; HDRP record stage threads `shadowAtlasLayer + shadowNear +
|
|
260
|
-
* shadowFar` through
|
|
300
|
+
* shadowFar` through the unified direct-light metadata payload.
|
|
261
301
|
*/
|
|
262
302
|
readonly shadowAtlasLayer?: number;
|
|
263
303
|
/**
|
|
264
304
|
* Per-face perspective near plane (matches `PointLightShadow.nearPlane`).
|
|
265
|
-
* Used by HDRP `evalPointShadowed` for depth-ref reconstruction;
|
|
266
|
-
*
|
|
305
|
+
* Used by HDRP `evalPointShadowed` for depth-ref reconstruction; URP keeps
|
|
306
|
+
* the projection constants in its dedicated shadow-parameter buffer.
|
|
267
307
|
*/
|
|
268
308
|
readonly shadowNear?: number;
|
|
269
309
|
/**
|
|
270
310
|
* Per-face perspective far plane (matches `PointLightShadow.farPlane`).
|
|
271
|
-
*
|
|
311
|
+
* It is consumed with the shadow atlas layer through the shared
|
|
312
|
+
* shadow-parameter buffer; DirectLightSlot carries only the layer identity.
|
|
272
313
|
*/
|
|
273
314
|
readonly shadowFar?: number;
|
|
274
315
|
}
|
|
@@ -290,9 +331,9 @@ export interface PointLightSnapshot {
|
|
|
290
331
|
*
|
|
291
332
|
* `mapSize` / `nearPlane` / `farPlane` ride here so record stage can size the
|
|
292
333
|
* cube atlas faces and feed shader the proj constants without re-querying ECS.
|
|
293
|
-
* `shadowAtlasLayer` is assigned by extract in spawn order
|
|
294
|
-
*
|
|
295
|
-
*
|
|
334
|
+
* `shadowAtlasLayer` is assigned by extract in spawn order up to the renderer
|
|
335
|
+
* atlas capacity; over-budget requests retain the sentinel `-1` so the
|
|
336
|
+
* downstream DirectLightSlot packing carries no invalid layer identity.
|
|
296
337
|
*/
|
|
297
338
|
export interface PointShadowSnapshot {
|
|
298
339
|
/** Spawning entity index (joins PointLight + PointLightShadow + Transform). */
|
|
@@ -304,9 +345,9 @@ export interface PointShadowSnapshot {
|
|
|
304
345
|
readonly nearPlane: number;
|
|
305
346
|
readonly farPlane: number;
|
|
306
347
|
/**
|
|
307
|
-
* Atlas layer assigned by extract in spawn order
|
|
308
|
-
*
|
|
309
|
-
* shader-side
|
|
348
|
+
* Atlas layer assigned by extract in spawn order while capacity remains.
|
|
349
|
+
* Sentinel -1 is used for a request that cannot be admitted and is
|
|
350
|
+
* reserved by the shader-side DirectLightSlot metadata for no-shadow lights.
|
|
310
351
|
*/
|
|
311
352
|
readonly shadowAtlasLayer: number;
|
|
312
353
|
/**
|
|
@@ -345,6 +386,27 @@ export interface SpotLightSnapshot {
|
|
|
345
386
|
readonly invRangeSquared: number;
|
|
346
387
|
readonly cosInner: number;
|
|
347
388
|
readonly cosOuter: number;
|
|
389
|
+
/** Shared asset identities projected from the authored modifier fields. */
|
|
390
|
+
readonly iesProfileHandle?: number;
|
|
391
|
+
readonly cookieHandle?: number;
|
|
392
|
+
/** Resource-array slice identities; absent means the modifier falls back. */
|
|
393
|
+
readonly iesProfileSlice?: number;
|
|
394
|
+
readonly cookieSlice?: number;
|
|
395
|
+
/** Resolved authored payloads; absent means the renderer keeps identity LKG. */
|
|
396
|
+
readonly iesProfileData?: Uint8Array;
|
|
397
|
+
readonly cookieData?: Uint8Array;
|
|
398
|
+
/** Aspect-only UV matrix paired with `cookieSlice`; upload is dirty-ranged. */
|
|
399
|
+
readonly cookieMatrix?: Float32Array;
|
|
400
|
+
/**
|
|
401
|
+
* Legacy projector payload projected into the shared Cookie array lane when
|
|
402
|
+
* the adapter exposes the extended-lighting topology. The projector keeps
|
|
403
|
+
* its lightViewProj sampling semantics; only the texture transport is
|
|
404
|
+
* shared with authored Cookie resources.
|
|
405
|
+
*/
|
|
406
|
+
readonly projectorSlice?: number;
|
|
407
|
+
readonly projectorData?: Uint8Array;
|
|
408
|
+
readonly projectorMatrix?: Float32Array;
|
|
409
|
+
readonly rollDeg?: number;
|
|
348
410
|
// ── shadow fields (feat-20260625-spot-light-shadow-mapping M1) ──
|
|
349
411
|
readonly castShadow: boolean;
|
|
350
412
|
readonly lightViewProj: Float32Array | undefined;
|
|
@@ -365,20 +427,111 @@ export interface SpotLightSnapshot {
|
|
|
365
427
|
readonly projectorRevision?: number;
|
|
366
428
|
}
|
|
367
429
|
|
|
430
|
+
/** RectArea direct-light snapshot carried by the unified slot contract. */
|
|
431
|
+
export interface RectAreaDirectLightSnapshot {
|
|
432
|
+
readonly kind: 'rect-area';
|
|
433
|
+
readonly position: Vec3;
|
|
434
|
+
readonly color: Vec3;
|
|
435
|
+
readonly intensity: number;
|
|
436
|
+
readonly invRangeSquared: number;
|
|
437
|
+
readonly halfWidth: number;
|
|
438
|
+
readonly halfHeight: number;
|
|
439
|
+
readonly axisX: Vec3;
|
|
440
|
+
readonly axisY: Vec3;
|
|
441
|
+
readonly shadowAtlasTile?: number;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Normalized world-space frame used by Rect extraction, culling, and BRDF. */
|
|
445
|
+
export interface RectAreaWorldFrame {
|
|
446
|
+
readonly center: Vec3;
|
|
447
|
+
readonly axisX: Vec3;
|
|
448
|
+
readonly axisY: Vec3;
|
|
449
|
+
readonly normal: Vec3;
|
|
450
|
+
readonly halfWidth: number;
|
|
451
|
+
readonly halfHeight: number;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Build the single normalized Rect frame shared by direct and cluster paths. */
|
|
455
|
+
export function buildRectAreaWorldFrame(input: {
|
|
456
|
+
readonly center: ArrayLike<number>;
|
|
457
|
+
readonly axisX: ArrayLike<number>;
|
|
458
|
+
readonly axisY: ArrayLike<number>;
|
|
459
|
+
readonly width: number;
|
|
460
|
+
readonly height: number;
|
|
461
|
+
}): RectAreaWorldFrame {
|
|
462
|
+
const center = vec3.create(input.center[0] ?? 0, input.center[1] ?? 0, input.center[2] ?? 0);
|
|
463
|
+
const axisX = vec3.normalize(
|
|
464
|
+
vec3.create(),
|
|
465
|
+
vec3.create(input.axisX[0] ?? 0, input.axisX[1] ?? 0, input.axisX[2] ?? 0),
|
|
466
|
+
);
|
|
467
|
+
const axisY = vec3.normalize(
|
|
468
|
+
vec3.create(),
|
|
469
|
+
vec3.create(input.axisY[0] ?? 0, input.axisY[1] ?? 0, input.axisY[2] ?? 0),
|
|
470
|
+
);
|
|
471
|
+
const normal = vec3.normalize(vec3.create(), vec3.cross(vec3.create(), axisX, axisY));
|
|
472
|
+
normal[0] = normal[0] === 0 ? 0 : (normal[0] ?? 0);
|
|
473
|
+
normal[1] = normal[1] === 0 ? 0 : (normal[1] ?? 0);
|
|
474
|
+
normal[2] = normal[2] === 0 ? 0 : (normal[2] ?? 0);
|
|
475
|
+
return {
|
|
476
|
+
center,
|
|
477
|
+
axisX,
|
|
478
|
+
axisY,
|
|
479
|
+
normal,
|
|
480
|
+
halfWidth: Math.max(0, input.width * 0.5),
|
|
481
|
+
halfHeight: Math.max(0, input.height * 0.5),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** Return true only on the authored front side of a one-sided Rect. */
|
|
486
|
+
export function rectAreaFacesPoint(frame: RectAreaWorldFrame, point: ArrayLike<number>): boolean {
|
|
487
|
+
const toPoint = vec3.create(
|
|
488
|
+
(point[0] ?? 0) - (frame.center[0] ?? 0),
|
|
489
|
+
(point[1] ?? 0) - (frame.center[1] ?? 0),
|
|
490
|
+
(point[2] ?? 0) - (frame.center[2] ?? 0),
|
|
491
|
+
);
|
|
492
|
+
return vec3.dot(frame.normal, toPoint) > 0;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Project a world point onto the finite authored Rect boundary. */
|
|
496
|
+
export function closestPointOnRectArea(frame: RectAreaWorldFrame, point: ArrayLike<number>): Vec3 {
|
|
497
|
+
const toPoint = vec3.create(
|
|
498
|
+
(point[0] ?? 0) - (frame.center[0] ?? 0),
|
|
499
|
+
(point[1] ?? 0) - (frame.center[1] ?? 0),
|
|
500
|
+
(point[2] ?? 0) - (frame.center[2] ?? 0),
|
|
501
|
+
);
|
|
502
|
+
const localX = Math.min(
|
|
503
|
+
frame.halfWidth,
|
|
504
|
+
Math.max(-frame.halfWidth, vec3.dot(toPoint, frame.axisX)),
|
|
505
|
+
);
|
|
506
|
+
const localY = Math.min(
|
|
507
|
+
frame.halfHeight,
|
|
508
|
+
Math.max(-frame.halfHeight, vec3.dot(toPoint, frame.axisY)),
|
|
509
|
+
);
|
|
510
|
+
return vec3.create(
|
|
511
|
+
(frame.center[0] ?? 0) + (frame.axisX[0] ?? 0) * localX + (frame.axisY[0] ?? 0) * localY,
|
|
512
|
+
(frame.center[1] ?? 0) + (frame.axisX[1] ?? 0) * localX + (frame.axisY[1] ?? 0) * localY,
|
|
513
|
+
(frame.center[2] ?? 0) + (frame.axisX[2] ?? 0) * localX + (frame.axisY[2] ?? 0) * localY,
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
368
517
|
/**
|
|
369
|
-
* LightSnapshot — discriminated union of the
|
|
518
|
+
* LightSnapshot — discriminated union of the directional and punctual
|
|
370
519
|
* variants. AI users + the record stage perform an exhaustive switch on
|
|
371
520
|
* `kind`; missing arms are caught at compile time (no `default`,
|
|
372
521
|
* `assertNever`-style guards). Plan-strategy R-10.
|
|
373
522
|
*/
|
|
374
|
-
export type LightSnapshot =
|
|
523
|
+
export type LightSnapshot =
|
|
524
|
+
| DirectionalLightSnapshot
|
|
525
|
+
| PointLightSnapshot
|
|
526
|
+
| SpotLightSnapshot
|
|
527
|
+
| RectAreaDirectLightSnapshot;
|
|
375
528
|
|
|
376
529
|
/**
|
|
377
530
|
* ExtractedLights — three-bucket output of the extractFrame three-query
|
|
378
531
|
* union (M2 / w16 / AC-03). Plan-strategy section 3.1 EXT node:
|
|
379
532
|
* - `directional` — at most one (record-stage N>1 fail-fast lands in M3)
|
|
380
|
-
* - `point[]`
|
|
381
|
-
*
|
|
533
|
+
* - `point[]` / `spot[]` / `rect[]` — authored local-light snapshots; the
|
|
534
|
+
* record stage preserves this order in one bounded Cluster corpus
|
|
382
535
|
* `directionalCount` exposes the raw count of DirectionalLight entities the
|
|
383
536
|
* extract observed (so the record stage can fire `render-system-multi-light`
|
|
384
537
|
* when N>1 without re-running the query). M3 / w19 will widen this with
|
|
@@ -390,6 +543,7 @@ export interface ExtractedLights {
|
|
|
390
543
|
readonly directionalCount: number;
|
|
391
544
|
readonly point: readonly PointLightSnapshot[];
|
|
392
545
|
readonly spot: readonly SpotLightSnapshot[];
|
|
546
|
+
readonly rect: readonly RectAreaDirectLightSnapshot[];
|
|
393
547
|
/**
|
|
394
548
|
* feat-20260613-csm-cascaded-shadow-maps M2 / w9: per-cascade light-view-
|
|
395
549
|
* projection matrices (one per cascade, length 4 pre-allocated). Each matrix
|
|
@@ -404,6 +558,7 @@ export interface ExtractedLights {
|
|
|
404
558
|
* unused slots are 0.0f. Undefined when castShadow=false or no directional
|
|
405
559
|
* light.
|
|
406
560
|
*/
|
|
561
|
+
/** Four vec4 lanes: split, worldUnitsPerTexel, lightDepthWorldSpan, reserved. */
|
|
407
562
|
readonly splitPlanes: Float32Array | undefined;
|
|
408
563
|
/**
|
|
409
564
|
* feat-20260613-csm-cascaded-shadow-maps M2 / w9: effective cascade count
|
|
@@ -439,19 +594,17 @@ export interface ExtractedLights {
|
|
|
439
594
|
* undefined otherwise.
|
|
440
595
|
*/
|
|
441
596
|
readonly normalBias: number | undefined;
|
|
442
|
-
/**
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
* undefined otherwise.
|
|
447
|
-
*/
|
|
448
|
-
readonly pcfKernelSize: number | undefined;
|
|
597
|
+
/** Accepted closed Directional filter projected from the first query result. */
|
|
598
|
+
readonly directionalShadowQuality: DirectionalShadowQuality | undefined;
|
|
599
|
+
/** Structured author-config failure; the candidate is withheld from record. */
|
|
600
|
+
readonly directionalShadowError: LightValidationError | undefined;
|
|
449
601
|
/**
|
|
450
602
|
* feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-7:
|
|
451
603
|
* shadow-casting point lights (PointLight + PointLightShadow + Transform
|
|
452
604
|
* archetype join). Each entry carries the per-light 6-face VP matrices and
|
|
453
605
|
* the assigned cube_array atlas layer (0..3 in spawn order; sentinel -1 is
|
|
454
|
-
* reserved by the shader-side
|
|
606
|
+
* reserved by the shader-side DirectLightSlot metadata for no-shadow point
|
|
607
|
+
* lights).
|
|
455
608
|
*
|
|
456
609
|
* Empty array when no PointLightShadow components exist (zero-cost gate per
|
|
457
610
|
* AC-09; record stage skips atlas allocation + shadow pass dispatch).
|
|
@@ -511,6 +664,15 @@ export interface SkylightSnapshot {
|
|
|
511
664
|
readonly entityHandle: number;
|
|
512
665
|
}
|
|
513
666
|
|
|
667
|
+
/** Local diffuse probe facts; specular remains owned by Skylight/ReflectionProbe. */
|
|
668
|
+
export interface LightProbeSnapshot {
|
|
669
|
+
readonly identity: string;
|
|
670
|
+
readonly position: readonly [number, number, number];
|
|
671
|
+
readonly radius: number;
|
|
672
|
+
readonly irradiance: Float32Array;
|
|
673
|
+
readonly admitted: boolean;
|
|
674
|
+
}
|
|
675
|
+
|
|
514
676
|
/**
|
|
515
677
|
* SkyboxSnapshot -- extract-stage view of one SkyboxBackground entity
|
|
516
678
|
* (feat-20260531-skybox-env-background M2 / w5).
|
|
@@ -567,6 +729,9 @@ export interface ExtractedVolumetricFog {
|
|
|
567
729
|
export interface RenderableSnapshot {
|
|
568
730
|
readonly assetHandle: number;
|
|
569
731
|
readonly transform: TransformSnapshot;
|
|
732
|
+
/** Producer-authored lower-detail references projected from MeshAsset. */
|
|
733
|
+
readonly lods?: readonly MeshLodLevel[];
|
|
734
|
+
readonly lodHysteresis?: number;
|
|
570
735
|
/** Finite producer-owned local bounds retained for persistent CPU view work. */
|
|
571
736
|
readonly localAabb?: Float32Array;
|
|
572
737
|
/** Indexed single-submesh facts consumed by the first GPU-driven rigid lane. */
|
|
@@ -674,6 +839,8 @@ export interface RenderableSnapshot {
|
|
|
674
839
|
readonly temporal?: RenderableTemporalSnapshot;
|
|
675
840
|
/** Detached M2 authoring facts for the optional Points/Lines projection. */
|
|
676
841
|
readonly pointsLines?: PointsLinesRetainedSnapshot;
|
|
842
|
+
/** Renderer-owned per-object probe record uploaded at group(3) binding(1). */
|
|
843
|
+
readonly probeBlendRecord?: import('./scene/probe-blend-record').ProbeBlendRecord;
|
|
677
844
|
}
|
|
678
845
|
|
|
679
846
|
export type RenderablePreviousSource = 'last-submitted' | 'current-seed';
|
|
@@ -710,6 +877,16 @@ export interface GpuDrivenDrawSnapshot {
|
|
|
710
877
|
readonly topology: PrimitiveTopology;
|
|
711
878
|
readonly pipelineClass: string;
|
|
712
879
|
readonly materialResourceClass: string;
|
|
880
|
+
/** Producer-owned prepared ABI facts used by the Standard PBR GPU lane. */
|
|
881
|
+
readonly prepared?: import('./gpu-driven/prepared-draw').PreparedGpuDrivenDraw;
|
|
882
|
+
/** Optional projected height supplied by direct GPU-view evidence/tests. */
|
|
883
|
+
readonly projectedHeight?: number;
|
|
884
|
+
/** Root plus lower-detail index/vertex ranges, in LOD order. */
|
|
885
|
+
readonly lodRanges?: readonly {
|
|
886
|
+
readonly first: number;
|
|
887
|
+
readonly count: number;
|
|
888
|
+
readonly baseVertex: number;
|
|
889
|
+
}[];
|
|
713
890
|
}
|
|
714
891
|
|
|
715
892
|
export interface InstancesSnapshot {
|
|
@@ -762,7 +939,7 @@ export interface SpriteInstancesSnapshot {
|
|
|
762
939
|
/**
|
|
763
940
|
* TransformSnapshot: extract-stage view of one entity's resolved world
|
|
764
941
|
* transform (feat-20260601 D-3). Holds the single `world` mat4 (column-major
|
|
765
|
-
* 16 floats, copied from the entity's `
|
|
942
|
+
* 16 floats, copied from the entity's `GlobalTransform.world` view written by
|
|
766
943
|
* propagateTransforms). The record stage copies these 16 floats straight into
|
|
767
944
|
* the mesh SSBO slot (zero `mat4.compose`); position / scale consumers derive
|
|
768
945
|
* from the mat4 via `mat4.getTranslation` / basis-column lengths.
|
|
@@ -851,6 +1028,8 @@ export interface MaterialSnapshot {
|
|
|
851
1028
|
* them in a lightmap injection region.
|
|
852
1029
|
*/
|
|
853
1030
|
readonly textureHandles?: ReadonlyMap<string, Handle<'TextureAsset', 'shared'>> | undefined;
|
|
1031
|
+
/** Renderer-local target sources; never projected as TextureAsset handles. */
|
|
1032
|
+
readonly textureSources?: ReadonlyMap<string, RenderTargetTextureSource> | undefined;
|
|
854
1033
|
/**
|
|
855
1034
|
* User-region texture field names whose paramValue resolved to a VideoAsset
|
|
856
1035
|
* (kind `'video'`) rather than a static TextureAsset
|
|
@@ -948,20 +1127,6 @@ export interface MaterialSnapshotCacheEntry {
|
|
|
948
1127
|
export type MaterialSnapshotCache = Map<number, MaterialSnapshotCacheEntry>;
|
|
949
1128
|
export type MaterialSnapshotCachesByWorld = WeakMap<World, MaterialSnapshotCache>;
|
|
950
1129
|
|
|
951
|
-
function gpuDrivenMaterialResourceClass(material: MaterialSnapshot): string {
|
|
952
|
-
const textures = [...(material.textureHandles?.entries() ?? [])]
|
|
953
|
-
.map(([name, handle]) => [name, Number(handle)] as const)
|
|
954
|
-
.sort(([left], [right]) => left.localeCompare(right));
|
|
955
|
-
const samplers = [...(material.samplerHandles?.entries() ?? [])]
|
|
956
|
-
.map(([name, handle]) => [name, Number(handle)] as const)
|
|
957
|
-
.sort(([left], [right]) => left.localeCompare(right));
|
|
958
|
-
return JSON.stringify({
|
|
959
|
-
textures,
|
|
960
|
-
samplers,
|
|
961
|
-
video: [...(material.videoTextureFields?.keys() ?? [])].sort(),
|
|
962
|
-
});
|
|
963
|
-
}
|
|
964
|
-
|
|
965
1130
|
// === DispatchEntry — M3 / w26 single dispatch list (feat-20260526-material-asset-multipass-renderstate) ===
|
|
966
1131
|
//
|
|
967
1132
|
// Plan-strategy D-3: single dispatch list sorted by queue value,
|
|
@@ -1060,6 +1225,12 @@ function appendMaterialDispatchEntries(
|
|
|
1060
1225
|
|
|
1061
1226
|
export interface ExtractedFrame {
|
|
1062
1227
|
readonly cameras: CameraSnapshot[];
|
|
1228
|
+
/** Auxiliary target cameras selected after the display camera is fixed. */
|
|
1229
|
+
readonly auxiliaryCameras: readonly CameraSnapshot[];
|
|
1230
|
+
/** Cube capture intents extracted beside the display camera in one pass. */
|
|
1231
|
+
readonly cubeCameras: readonly CubeCameraSnapshot[];
|
|
1232
|
+
/** Reflection probe facts projected from the same World extraction. */
|
|
1233
|
+
readonly reflectionProbes?: readonly ReflectionProbeFact[];
|
|
1063
1234
|
readonly lights: ExtractedLights;
|
|
1064
1235
|
/** Resource-owner environment facts selected once at the extract boundary. */
|
|
1065
1236
|
readonly environment: EnvironmentFrame | undefined;
|
|
@@ -1076,10 +1247,14 @@ export interface ExtractedFrame {
|
|
|
1076
1247
|
readonly dispatch: DispatchEntry[];
|
|
1077
1248
|
readonly skylight: SkylightSnapshot | undefined;
|
|
1078
1249
|
readonly skylightCount: number;
|
|
1250
|
+
/** World-local diffuse probe candidates; never a direct-light slot. */
|
|
1251
|
+
readonly lightProbes?: readonly LightProbeSnapshot[];
|
|
1079
1252
|
readonly skybox: SkyboxSnapshot | undefined;
|
|
1080
1253
|
readonly skyboxCount: number;
|
|
1081
1254
|
/** Resource-owner Fog facts selected once at the frame boundary. */
|
|
1082
1255
|
readonly fog: FogFrame | undefined;
|
|
1256
|
+
/** Last invalid Fog update retained alongside the current valid/LKG frame. */
|
|
1257
|
+
readonly fogFailure?: FogSelectionFailure;
|
|
1083
1258
|
/**
|
|
1084
1259
|
* feat-20260528-frustum-culling M3 / w11: frustum culling statistics
|
|
1085
1260
|
* collected during the extract phase. `total` is the count of entities
|
|
@@ -1105,6 +1280,65 @@ export interface ExtractedFrame {
|
|
|
1105
1280
|
readonly hiddenEntityReports: readonly RenderFeatureHiddenEntityReport[];
|
|
1106
1281
|
}
|
|
1107
1282
|
|
|
1283
|
+
export type CameraTargetUpdate = 'once' | 'on-demand' | 'continuous';
|
|
1284
|
+
|
|
1285
|
+
export interface CameraTargetCandidate {
|
|
1286
|
+
readonly worldId: number;
|
|
1287
|
+
readonly entityKey: number;
|
|
1288
|
+
readonly target?: RenderTarget;
|
|
1289
|
+
readonly requestVersion: number;
|
|
1290
|
+
readonly update: CameraTargetUpdate;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
export interface CameraTargetRejection {
|
|
1294
|
+
readonly entityKey: number;
|
|
1295
|
+
readonly reason: 'budget' | 'display-target' | 'duplicate-target';
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
export interface CameraTargetSelection {
|
|
1299
|
+
readonly display: CameraTargetCandidate | undefined;
|
|
1300
|
+
readonly auxiliary: readonly CameraTargetCandidate[];
|
|
1301
|
+
readonly rejected: readonly CameraTargetRejection[];
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
export function selectCameraTargetViews(
|
|
1305
|
+
candidates: readonly CameraTargetCandidate[],
|
|
1306
|
+
options: { readonly displayEntityKey?: number; readonly budget: number },
|
|
1307
|
+
): CameraTargetSelection {
|
|
1308
|
+
const display = candidates.find(
|
|
1309
|
+
(candidate) =>
|
|
1310
|
+
candidate.entityKey === options.displayEntityKey && candidate.target === undefined,
|
|
1311
|
+
);
|
|
1312
|
+
const rejected: CameraTargetRejection[] = [];
|
|
1313
|
+
const ordered = candidates
|
|
1314
|
+
.filter((candidate) => candidate.target !== undefined)
|
|
1315
|
+
.sort((left, right) => left.worldId - right.worldId || left.entityKey - right.entityKey);
|
|
1316
|
+
const auxiliary: CameraTargetCandidate[] = [];
|
|
1317
|
+
const seenTargets = new Set<RenderTarget>();
|
|
1318
|
+
for (const candidate of ordered) {
|
|
1319
|
+
const duplicate = candidate.target !== undefined && seenTargets.has(candidate.target);
|
|
1320
|
+
if (candidate.target !== undefined) seenTargets.add(candidate.target);
|
|
1321
|
+
if (candidate.entityKey === options.displayEntityKey) {
|
|
1322
|
+
rejected.push({ entityKey: candidate.entityKey, reason: 'display-target' });
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (candidate.target === undefined || duplicate) {
|
|
1326
|
+
rejected.push({ entityKey: candidate.entityKey, reason: 'duplicate-target' });
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
if (auxiliary.length >= Math.max(0, options.budget)) {
|
|
1330
|
+
rejected.push({ entityKey: candidate.entityKey, reason: 'budget' });
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
auxiliary.push(candidate);
|
|
1334
|
+
}
|
|
1335
|
+
return {
|
|
1336
|
+
display,
|
|
1337
|
+
auxiliary: Object.freeze(auxiliary),
|
|
1338
|
+
rejected: Object.freeze(rejected),
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1108
1342
|
/**
|
|
1109
1343
|
* Internal world surface used by extract for archetype-graph traversal and
|
|
1110
1344
|
* error routing. The packed `Entity` handle for a row is read directly from
|
|
@@ -1147,6 +1381,14 @@ function createWorldInternalView(world: World): WorldInternalView {
|
|
|
1147
1381
|
};
|
|
1148
1382
|
}
|
|
1149
1383
|
|
|
1384
|
+
function resolveCameraTarget(world: World, raw: number | undefined): RenderTarget | undefined {
|
|
1385
|
+
if (raw === undefined || raw <= 0) return undefined;
|
|
1386
|
+
const resolved = world.sharedRefs.resolve<'RenderTarget', RenderTarget>(
|
|
1387
|
+
toShared<'RenderTarget'>(Math.round(raw)),
|
|
1388
|
+
);
|
|
1389
|
+
return resolved.ok ? resolved.value : undefined;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1150
1392
|
/**
|
|
1151
1393
|
* The user-region texture fields the built-in standard-PBR material declares.
|
|
1152
1394
|
* Used as the fallback texture-field set when the shader id is not registered
|
|
@@ -1427,6 +1669,24 @@ function collectMaterialTextureCoordinates(
|
|
|
1427
1669
|
return out;
|
|
1428
1670
|
}
|
|
1429
1671
|
|
|
1672
|
+
function collectMaterialTextureSources(
|
|
1673
|
+
values: Readonly<Record<string, unknown>>,
|
|
1674
|
+
world: World,
|
|
1675
|
+
): Map<string, RenderTargetTextureSource> {
|
|
1676
|
+
const out = new Map<string, RenderTargetTextureSource>();
|
|
1677
|
+
for (const [field, value] of Object.entries(values)) {
|
|
1678
|
+
const reference = materialTextureRef(value);
|
|
1679
|
+
if (typeof reference !== 'number') continue;
|
|
1680
|
+
const resolved = world.sharedRefs.resolve<
|
|
1681
|
+
'RenderTargetTextureSource',
|
|
1682
|
+
RenderTargetTextureSource
|
|
1683
|
+
>(toShared<'RenderTargetTextureSource'>(reference));
|
|
1684
|
+
if (!resolved.ok || resolveRenderTargetMaterialSource(resolved.value) === undefined) continue;
|
|
1685
|
+
out.set(field, resolved.value);
|
|
1686
|
+
}
|
|
1687
|
+
return out;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1430
1690
|
function collectMaterialTextureSamplers(
|
|
1431
1691
|
values: Readonly<Record<string, unknown>>,
|
|
1432
1692
|
resolveSampler: (value: unknown) => Handle<'SamplerAsset', 'shared'> | undefined,
|
|
@@ -1517,9 +1777,14 @@ function materialParametersToParamSchema(
|
|
|
1517
1777
|
return parameters.flatMap((parameter): ParamSchemaEntry[] => {
|
|
1518
1778
|
// Boolean material values are runtime-only and have no UBO representation.
|
|
1519
1779
|
if (parameter.type === 'bool') return [];
|
|
1520
|
-
if (parameter.type === 'texture') {
|
|
1780
|
+
if (parameter.type === 'texture' || parameter.type === 'texture_cube') {
|
|
1521
1781
|
if (engineInjectedTextureFields?.has(parameter.name) === true) return [];
|
|
1522
|
-
return [
|
|
1782
|
+
return [
|
|
1783
|
+
{
|
|
1784
|
+
name: parameter.name,
|
|
1785
|
+
type: parameter.type === 'texture_cube' ? 'texture_cube' : 'texture2d',
|
|
1786
|
+
},
|
|
1787
|
+
];
|
|
1523
1788
|
}
|
|
1524
1789
|
return [
|
|
1525
1790
|
{
|
|
@@ -1779,6 +2044,7 @@ export function resolveMaterialSnapshot(
|
|
|
1779
2044
|
const samplerHandles = collectMaterialTextureSamplers(pv, (value) =>
|
|
1780
2045
|
resolveTexLike(materialTextureRef(value), 'SamplerAsset'),
|
|
1781
2046
|
);
|
|
2047
|
+
const textureSources = collectMaterialTextureSources(pv, world);
|
|
1782
2048
|
const emissiveTextureHandle = resolveTexLike(
|
|
1783
2049
|
materialTextureRef(pv.emissiveTexture),
|
|
1784
2050
|
'TextureAsset',
|
|
@@ -1814,6 +2080,7 @@ export function resolveMaterialSnapshot(
|
|
|
1814
2080
|
...(materialParamSchema.length > 0 && { materialParamSchema }),
|
|
1815
2081
|
...(textureCoordinates.size > 0 && { textureCoordinates }),
|
|
1816
2082
|
...(textureHandles.size > 0 && { textureHandles }),
|
|
2083
|
+
...(textureSources.size > 0 && { textureSources }),
|
|
1817
2084
|
...(videoTextureFields.size > 0 && { videoTextureFields }),
|
|
1818
2085
|
...(samplerHandles.size > 0 && { samplerHandles }),
|
|
1819
2086
|
...(baseColorTextureHandle !== undefined && { baseColorTexture: baseColorTextureHandle }),
|
|
@@ -2117,6 +2384,9 @@ export interface DirectionalCsmConfig {
|
|
|
2117
2384
|
readonly cascadeBlend: number;
|
|
2118
2385
|
readonly mapSize: number;
|
|
2119
2386
|
readonly shadowDistance: number;
|
|
2387
|
+
readonly shadowFilter: number;
|
|
2388
|
+
readonly shadowAngularRadius: number;
|
|
2389
|
+
readonly maxPenumbraTexels: number;
|
|
2120
2390
|
}
|
|
2121
2391
|
|
|
2122
2392
|
/**
|
|
@@ -2131,6 +2401,7 @@ export interface DirectionalCsmResult {
|
|
|
2131
2401
|
readonly cascadeCount: number;
|
|
2132
2402
|
readonly cascadeBlend: number;
|
|
2133
2403
|
readonly shadowMapSize: number;
|
|
2404
|
+
readonly directionalShadowQuality: DirectionalShadowQuality;
|
|
2134
2405
|
}
|
|
2135
2406
|
|
|
2136
2407
|
/**
|
|
@@ -2163,9 +2434,6 @@ export function computeDirectionalCsm(
|
|
|
2163
2434
|
|
|
2164
2435
|
// PSSM split planes: [camera near, shadowDistance], not the camera far.
|
|
2165
2436
|
const splits = pssmSplit(sNear, sFar, cascadeCount, config.splitLambda);
|
|
2166
|
-
const paddedSplits = new Float32Array(4);
|
|
2167
|
-
for (let i = 0; i < splits.length; i++) paddedSplits[i] = splits[i] ?? 0;
|
|
2168
|
-
|
|
2169
2437
|
// Light view matrix: camera at origin, looking along the light direction.
|
|
2170
2438
|
const lightDirN = vec3.normalize(vec3.create(), direction);
|
|
2171
2439
|
const lightTarget = vec3.create(lightDirN[0] ?? 0, lightDirN[1] ?? 0, lightDirN[2] ?? 0);
|
|
@@ -2196,6 +2464,7 @@ export function computeDirectionalCsm(
|
|
|
2196
2464
|
mat4.multiply(cameraVP, camProj, camView);
|
|
2197
2465
|
|
|
2198
2466
|
const resultLightViewProjs: Float32Array[] = [];
|
|
2467
|
+
const cascadeFits: DirectionalShadowCascadeFit[] = [];
|
|
2199
2468
|
|
|
2200
2469
|
// bug-20260619 RC-2 (AC-05): toward-light Z reach. Extend the near (toward-
|
|
2201
2470
|
// light) bound of EVERY cascade to the toward-light extreme of the WHOLE
|
|
@@ -2259,16 +2528,34 @@ export function computeDirectionalCsm(
|
|
|
2259
2528
|
// lightViewProj = orthoProj * lightView — pure clip-space [-1,1]. Atlas
|
|
2260
2529
|
// tile placement is handled by the per-cascade viewport + fragment-side UV
|
|
2261
2530
|
// math (evalDirectional), so shadow_caster.gl_Position stays clip-space.
|
|
2262
|
-
|
|
2531
|
+
cascadeFits.push({
|
|
2532
|
+
split: splits[cIdx] ?? sFar,
|
|
2533
|
+
minX,
|
|
2534
|
+
maxX,
|
|
2535
|
+
minY,
|
|
2536
|
+
maxY,
|
|
2537
|
+
minZ,
|
|
2538
|
+
maxZ,
|
|
2539
|
+
});
|
|
2263
2540
|
resultLightViewProjs.push(new Float32Array(mat4.multiply(mat4.create(), orthoProj, lightView)));
|
|
2264
2541
|
}
|
|
2265
2542
|
|
|
2543
|
+
const projection = projectDirectionalShadow(
|
|
2544
|
+
cascadeFits,
|
|
2545
|
+
config.mapSize,
|
|
2546
|
+
config.shadowFilter ?? 2,
|
|
2547
|
+
config.shadowAngularRadius ?? 0.00465,
|
|
2548
|
+
config.maxPenumbraTexels ?? 32,
|
|
2549
|
+
);
|
|
2550
|
+
if (projection === undefined) return null;
|
|
2551
|
+
|
|
2266
2552
|
return {
|
|
2267
2553
|
lightViewProj: resultLightViewProjs,
|
|
2268
|
-
splitPlanes:
|
|
2554
|
+
splitPlanes: projection.splitPlanes,
|
|
2269
2555
|
cascadeCount,
|
|
2270
2556
|
cascadeBlend: config.cascadeBlend,
|
|
2271
2557
|
shadowMapSize: config.mapSize,
|
|
2558
|
+
directionalShadowQuality: projection.directionalShadowQuality,
|
|
2272
2559
|
};
|
|
2273
2560
|
}
|
|
2274
2561
|
|
|
@@ -2296,8 +2583,177 @@ export interface PreparedExtractContext {
|
|
|
2296
2583
|
readonly resourceOwnerFog: FogFrame | null | undefined;
|
|
2297
2584
|
readonly cull: 'self' | 'none' | 'external';
|
|
2298
2585
|
readonly cullCameras: readonly CameraSnapshot[] | undefined;
|
|
2299
|
-
readonly
|
|
2586
|
+
readonly renderables: 'full' | 'none';
|
|
2300
2587
|
readonly visibility: VisibilitySnapshot;
|
|
2588
|
+
/** Producer-owned artifact lookup; extraction only, never record-time asset reads. */
|
|
2589
|
+
readonly getMaterialShaderArtifact:
|
|
2590
|
+
| ((materialShaderId: string) => MaterialShaderArtifact | undefined)
|
|
2591
|
+
| undefined;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
const ALL_VISIBLE: VisibilitySnapshot = {
|
|
2595
|
+
diagnostics: [],
|
|
2596
|
+
hasAnyIntent: false,
|
|
2597
|
+
hasAnyHiddenIntent: false,
|
|
2598
|
+
get: () => undefined,
|
|
2599
|
+
effective: () => 'visible',
|
|
2600
|
+
};
|
|
2601
|
+
|
|
2602
|
+
function collectCameraSnapshots(world: World): CameraSnapshot[] {
|
|
2603
|
+
const worldInternal = createWorldInternalView(world);
|
|
2604
|
+
const cameras: CameraSnapshot[] = [];
|
|
2605
|
+
const cameraQuery = world
|
|
2606
|
+
.query({ read: [Camera], optional: [MotionBlur], with: [Transform, GlobalTransform] })
|
|
2607
|
+
.unwrap();
|
|
2608
|
+
for (const row of cameraQuery) {
|
|
2609
|
+
const cam = row.get(Camera);
|
|
2610
|
+
const motionBlur = row.get(MotionBlur);
|
|
2611
|
+
const entity = row.entity;
|
|
2612
|
+
const view = worldInternal._getArrayView(entity, GlobalTransform, 'world');
|
|
2613
|
+
if (view === undefined) continue;
|
|
2614
|
+
const worldMat = new Float32Array(view);
|
|
2615
|
+
const target = resolveCameraTarget(world, cam.target);
|
|
2616
|
+
cameras.push({
|
|
2617
|
+
entityKey: entity as number,
|
|
2618
|
+
...(target === undefined ? {} : { target }),
|
|
2619
|
+
historyVersion: cam.historyVersion,
|
|
2620
|
+
position: mat4.getTranslation(vec3.create(), worldMat),
|
|
2621
|
+
world: worldMat,
|
|
2622
|
+
fov: cam.fov,
|
|
2623
|
+
aspect: cam.aspect,
|
|
2624
|
+
near: cam.near,
|
|
2625
|
+
far: cam.far,
|
|
2626
|
+
projection: cameraProjectionFromF32(cam.projection),
|
|
2627
|
+
orthoLeft: cam.left,
|
|
2628
|
+
orthoRight: cam.right,
|
|
2629
|
+
orthoBottom: cam.bottom,
|
|
2630
|
+
orthoTop: cam.top,
|
|
2631
|
+
tonemap: tonemapFromF32(cam.tonemap),
|
|
2632
|
+
exposure: cam.exposure,
|
|
2633
|
+
whitePoint: cam.whitePoint,
|
|
2634
|
+
antialias: antialiasFromF32(cam.antialias),
|
|
2635
|
+
bloom: bloomEnabledFromF32(cam.bloom),
|
|
2636
|
+
bloomThreshold: cam.bloomThreshold,
|
|
2637
|
+
bloomIntensity: cam.bloomIntensity,
|
|
2638
|
+
bloomBlurRadius: cam.bloomBlurRadius,
|
|
2639
|
+
clearColor: [
|
|
2640
|
+
cam.clearColor[0] ?? 0,
|
|
2641
|
+
cam.clearColor[1] ?? 0,
|
|
2642
|
+
cam.clearColor[2] ?? 0,
|
|
2643
|
+
cam.clearColor[3] ?? 1,
|
|
2644
|
+
],
|
|
2645
|
+
...(motionBlur === undefined
|
|
2646
|
+
? {}
|
|
2647
|
+
: {
|
|
2648
|
+
motionBlur: {
|
|
2649
|
+
shutterAngle: motionBlur.shutterAngle,
|
|
2650
|
+
maxRadiusPixels: motionBlur.maxRadiusPixels,
|
|
2651
|
+
sampleCount: motionBlur.sampleCount,
|
|
2652
|
+
},
|
|
2653
|
+
}),
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
return cameras;
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
function collectCubeCameraSnapshots(world: World): CubeCameraSnapshot[] {
|
|
2660
|
+
const worldInternal = createWorldInternalView(world);
|
|
2661
|
+
const snapshots: CubeCameraSnapshot[] = [];
|
|
2662
|
+
const query = world.query({ read: [CubeCamera], with: [Transform, GlobalTransform] }).unwrap();
|
|
2663
|
+
for (const row of query) {
|
|
2664
|
+
const camera = row.get(CubeCamera);
|
|
2665
|
+
const target = resolveCameraTarget(world, camera.target);
|
|
2666
|
+
if (target === undefined) continue;
|
|
2667
|
+
const view = worldInternal._getArrayView(row.entity, GlobalTransform, 'world');
|
|
2668
|
+
if (view === undefined) continue;
|
|
2669
|
+
snapshots.push({
|
|
2670
|
+
entityKey: row.entity as number,
|
|
2671
|
+
target,
|
|
2672
|
+
position: [view[12] ?? 0, view[13] ?? 0, view[14] ?? 0],
|
|
2673
|
+
near: camera.near,
|
|
2674
|
+
far: camera.far,
|
|
2675
|
+
updateIntent: cubeCameraUpdateIntentFromF32(camera.updateIntent),
|
|
2676
|
+
requestVersion: camera.requestVersion,
|
|
2677
|
+
faceBudget: camera.faceBudget,
|
|
2678
|
+
});
|
|
2679
|
+
}
|
|
2680
|
+
return snapshots;
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
function collectReflectionProbeFacts(world: World): ReflectionProbeFact[] {
|
|
2684
|
+
const worldInternal = createWorldInternalView(world);
|
|
2685
|
+
const facts: ReflectionProbeFact[] = [];
|
|
2686
|
+
const query = world
|
|
2687
|
+
.query({ read: [ReflectionProbe], with: [Transform, GlobalTransform] })
|
|
2688
|
+
.unwrap();
|
|
2689
|
+
for (const row of query) {
|
|
2690
|
+
const probe = row.get(ReflectionProbe);
|
|
2691
|
+
const view = worldInternal._getArrayView(row.entity, GlobalTransform, 'world');
|
|
2692
|
+
if (view === undefined) {
|
|
2693
|
+
continue;
|
|
2694
|
+
}
|
|
2695
|
+
facts.push({
|
|
2696
|
+
worldId: 0,
|
|
2697
|
+
entityKey: row.entity as number,
|
|
2698
|
+
center: [view[12] ?? 0, view[13] ?? 0, view[14] ?? 0],
|
|
2699
|
+
halfExtents: [
|
|
2700
|
+
probe.halfExtents[0] ?? 0,
|
|
2701
|
+
probe.halfExtents[1] ?? 0,
|
|
2702
|
+
probe.halfExtents[2] ?? 0,
|
|
2703
|
+
],
|
|
2704
|
+
priority: probe.priority,
|
|
2705
|
+
intensity: probe.intensity,
|
|
2706
|
+
resolution: probe.resolution,
|
|
2707
|
+
revision: probe.invalidationVersion,
|
|
2708
|
+
updateIntent: reflectionProbeUpdateIntentFromF32(probe.updateIntent),
|
|
2709
|
+
invalidationVersion: probe.invalidationVersion,
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
return facts;
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
interface CameraRoleSelection {
|
|
2716
|
+
readonly display: readonly CameraSnapshot[];
|
|
2717
|
+
readonly auxiliary: readonly CameraSnapshot[];
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
function selectCameraRoles(world: World): CameraRoleSelection {
|
|
2721
|
+
const cameras = collectCameraSnapshots(world);
|
|
2722
|
+
const displayEntities = cameras
|
|
2723
|
+
.filter((camera) => camera.target === undefined)
|
|
2724
|
+
.map((camera) => camera.entityKey ?? 0);
|
|
2725
|
+
const activeCameraIndex = selectActiveCameraIndex(
|
|
2726
|
+
displayEntities,
|
|
2727
|
+
getActiveCamera(world)?.entity,
|
|
2728
|
+
);
|
|
2729
|
+
const displayEntityKey = displayEntities[activeCameraIndex] ?? displayEntities[0];
|
|
2730
|
+
const display =
|
|
2731
|
+
displayEntityKey === undefined
|
|
2732
|
+
? []
|
|
2733
|
+
: cameras.filter((camera) => camera.entityKey === displayEntityKey);
|
|
2734
|
+
const candidates: CameraTargetCandidate[] = cameras.map((camera) => ({
|
|
2735
|
+
worldId: 0,
|
|
2736
|
+
entityKey: camera.entityKey ?? 0,
|
|
2737
|
+
...(camera.target === undefined ? {} : { target: camera.target }),
|
|
2738
|
+
requestVersion: 0,
|
|
2739
|
+
update: 'continuous',
|
|
2740
|
+
}));
|
|
2741
|
+
const selected = selectCameraTargetViews(candidates, {
|
|
2742
|
+
...(displayEntityKey === undefined ? {} : { displayEntityKey }),
|
|
2743
|
+
budget: 1,
|
|
2744
|
+
});
|
|
2745
|
+
const auxiliary = selected.auxiliary.flatMap((candidate) =>
|
|
2746
|
+
cameras.filter((camera) => camera.entityKey === candidate.entityKey),
|
|
2747
|
+
);
|
|
2748
|
+
return { display, auxiliary };
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
export function extractCameraSnapshots(world: World): CameraSnapshot[] {
|
|
2752
|
+
return [...selectCameraRoles(world).display];
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
export function extractCameraTargetSnapshots(world: World): readonly CameraSnapshot[] {
|
|
2756
|
+
return selectCameraRoles(world).auxiliary;
|
|
2301
2757
|
}
|
|
2302
2758
|
|
|
2303
2759
|
function tonemapParams(camera: CameraSnapshot): Uint8Array {
|
|
@@ -2319,9 +2775,13 @@ export function prepareExtractContext(
|
|
|
2319
2775
|
readonly resourceOwnerFog?: FogFrame | null;
|
|
2320
2776
|
readonly cull?: 'self' | 'none' | 'external';
|
|
2321
2777
|
readonly cullCameras?: readonly CameraSnapshot[];
|
|
2778
|
+
readonly renderables?: 'full' | 'none';
|
|
2779
|
+
readonly getMaterialShaderArtifact?: (
|
|
2780
|
+
materialShaderId: string,
|
|
2781
|
+
) => MaterialShaderArtifact | undefined;
|
|
2322
2782
|
} = {},
|
|
2323
2783
|
): PreparedExtractContext {
|
|
2324
|
-
const
|
|
2784
|
+
const renderables = options.renderables ?? 'full';
|
|
2325
2785
|
return {
|
|
2326
2786
|
assets: options.assets,
|
|
2327
2787
|
pipelineState: options.pipelineState,
|
|
@@ -2329,12 +2789,28 @@ export function prepareExtractContext(
|
|
|
2329
2789
|
resourceOwnerFog: options.resourceOwnerFog,
|
|
2330
2790
|
cull: options.cull ?? 'self',
|
|
2331
2791
|
cullCameras: options.cullCameras,
|
|
2332
|
-
|
|
2333
|
-
visibility: resolveVisibility(world
|
|
2792
|
+
renderables,
|
|
2793
|
+
visibility: renderables === 'none' ? ALL_VISIBLE : resolveVisibility(world),
|
|
2794
|
+
getMaterialShaderArtifact: options.getMaterialShaderArtifact,
|
|
2334
2795
|
};
|
|
2335
2796
|
}
|
|
2336
2797
|
|
|
2337
2798
|
const fogQueries = new WeakMap<World, Query<readonly [typeof Fog]>>();
|
|
2799
|
+
const fogLkgStates = new WeakMap<
|
|
2800
|
+
World,
|
|
2801
|
+
{ lkg: FogFrame | undefined; failure: FogSelectionFailure | undefined }
|
|
2802
|
+
>();
|
|
2803
|
+
|
|
2804
|
+
function fogState(world: World): {
|
|
2805
|
+
lkg: FogFrame | undefined;
|
|
2806
|
+
failure: FogSelectionFailure | undefined;
|
|
2807
|
+
} {
|
|
2808
|
+
const existing = fogLkgStates.get(world);
|
|
2809
|
+
if (existing !== undefined) return existing;
|
|
2810
|
+
const created = { lkg: undefined, failure: undefined };
|
|
2811
|
+
fogLkgStates.set(world, created);
|
|
2812
|
+
return created;
|
|
2813
|
+
}
|
|
2338
2814
|
|
|
2339
2815
|
function selectFogFrame(world: World): FogFrame | undefined {
|
|
2340
2816
|
// Query compilation is owned once per World; Query refreshes its matched
|
|
@@ -2362,10 +2838,22 @@ function selectFogFrame(world: World): FogFrame | undefined {
|
|
|
2362
2838
|
suns: [],
|
|
2363
2839
|
lane: 'direct',
|
|
2364
2840
|
});
|
|
2365
|
-
|
|
2841
|
+
const state = fogState(world);
|
|
2842
|
+
if (fogSelection.ok) {
|
|
2843
|
+
state.lkg = fogSelection.value.fog;
|
|
2844
|
+
state.failure = undefined;
|
|
2845
|
+
return fogSelection.value.fog;
|
|
2846
|
+
}
|
|
2847
|
+
state.failure = {
|
|
2848
|
+
code: fogSelection.error.code,
|
|
2849
|
+
expected: fogSelection.error.expected,
|
|
2850
|
+
hint: fogSelection.error.hint,
|
|
2851
|
+
detail: Object.freeze({ ...fogSelection.error.detail }),
|
|
2852
|
+
};
|
|
2366
2853
|
routeWorldError(world, fogSelection.error, {
|
|
2367
2854
|
systemName: 'RenderSystem.extract (fog-selection)',
|
|
2368
2855
|
});
|
|
2856
|
+
if (state.lkg !== undefined) return state.lkg;
|
|
2369
2857
|
throw fogSelection.error;
|
|
2370
2858
|
}
|
|
2371
2859
|
|
|
@@ -2547,7 +3035,13 @@ export function extractFrames(
|
|
|
2547
3035
|
assets?: AssetRegistry | null,
|
|
2548
3036
|
pipelineState?: ExtractPipelineSurface | null,
|
|
2549
3037
|
materialSnapshotCachesByWorld?: MaterialSnapshotCachesByWorld,
|
|
2550
|
-
options: {
|
|
3038
|
+
options: {
|
|
3039
|
+
readonly cull?: 'normal' | 'none';
|
|
3040
|
+
readonly renderables?: 'full' | 'none';
|
|
3041
|
+
readonly getMaterialShaderArtifact?: (
|
|
3042
|
+
materialShaderId: string,
|
|
3043
|
+
) => MaterialShaderArtifact | undefined;
|
|
3044
|
+
} = {},
|
|
2551
3045
|
): ExtractedFrame {
|
|
2552
3046
|
// w4: normalize the owner argument. A bare number is the legacy single-owner
|
|
2553
3047
|
// form (cameraOwner === resourceOwner); an object carries the two split
|
|
@@ -2617,7 +3111,11 @@ export function extractFrames(
|
|
|
2617
3111
|
})(),
|
|
2618
3112
|
}),
|
|
2619
3113
|
cull: options.cull === 'none' ? 'none' : isCameraOwner ? 'self' : 'external',
|
|
3114
|
+
...(options.getMaterialShaderArtifact === undefined
|
|
3115
|
+
? {}
|
|
3116
|
+
: { getMaterialShaderArtifact: options.getMaterialShaderArtifact }),
|
|
2620
3117
|
...(cameraOwnerFrame === undefined ? {} : { cullCameras: cameraOwnerFrame.cameras }),
|
|
3118
|
+
renderables: options.renderables ?? 'full',
|
|
2621
3119
|
});
|
|
2622
3120
|
const frame = extractFrame(world, prepared);
|
|
2623
3121
|
|
|
@@ -2688,6 +3186,7 @@ export function extractFrames(
|
|
|
2688
3186
|
// frames); directionalCount sum.
|
|
2689
3187
|
const point: PointLightSnapshot[] = [];
|
|
2690
3188
|
const spot: SpotLightSnapshot[] = [];
|
|
3189
|
+
const rect: RectAreaDirectLightSnapshot[] = [];
|
|
2691
3190
|
let directional: DirectionalLightSnapshot | undefined;
|
|
2692
3191
|
let directionalCount = 0;
|
|
2693
3192
|
let lightViewProj: readonly Float32Array[] | undefined;
|
|
@@ -2697,7 +3196,8 @@ export function extractFrames(
|
|
|
2697
3196
|
let shadowMapSize: number | undefined;
|
|
2698
3197
|
let depthBias: number | undefined;
|
|
2699
3198
|
let normalBias: number | undefined;
|
|
2700
|
-
let
|
|
3199
|
+
let directionalShadowQuality: DirectionalShadowQuality | undefined;
|
|
3200
|
+
let directionalShadowError: LightValidationError | undefined;
|
|
2701
3201
|
const pointShadow: PointShadowSnapshot[] = [];
|
|
2702
3202
|
// bug-20260710-editor-cross-world-shadow: carry the raw CSM config +
|
|
2703
3203
|
// direction of the first-hit directional so the merge layer can recompute
|
|
@@ -2708,6 +3208,7 @@ export function extractFrames(
|
|
|
2708
3208
|
for (const f of succeededFrames) {
|
|
2709
3209
|
for (const p of f.lights.point) point.push(p);
|
|
2710
3210
|
for (const s of f.lights.spot) spot.push(s);
|
|
3211
|
+
for (const r of f.lights.rect) rect.push(r);
|
|
2711
3212
|
for (const ps of f.lights.pointShadow) pointShadow.push(ps);
|
|
2712
3213
|
if (directional === undefined && f.lights.directional !== undefined) {
|
|
2713
3214
|
directional = f.lights.directional;
|
|
@@ -2719,7 +3220,8 @@ export function extractFrames(
|
|
|
2719
3220
|
shadowMapSize = f.lights.shadowMapSize;
|
|
2720
3221
|
depthBias = f.lights.depthBias;
|
|
2721
3222
|
normalBias = f.lights.normalBias;
|
|
2722
|
-
|
|
3223
|
+
directionalShadowQuality = f.lights.directionalShadowQuality;
|
|
3224
|
+
directionalShadowError = f.lights.directionalShadowError;
|
|
2723
3225
|
directionalCsmConfig = f.lights.directionalCsmConfig;
|
|
2724
3226
|
directionalCsmDirection = f.lights.directionalCsmDirection;
|
|
2725
3227
|
}
|
|
@@ -2738,6 +3240,9 @@ export function extractFrames(
|
|
|
2738
3240
|
if (succeededIndices[fi] === resourceOwner) resourceOwnerFrame = succeededFrames[fi];
|
|
2739
3241
|
}
|
|
2740
3242
|
const cameras = cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.cameras] : [];
|
|
3243
|
+
const auxiliaryCameras =
|
|
3244
|
+
cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.auxiliaryCameras] : [];
|
|
3245
|
+
const cubeCameras = cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.cubeCameras] : [];
|
|
2741
3246
|
|
|
2742
3247
|
// bug-20260710-editor-cross-world-shadow: RECOMPUTE directional CSM matrices
|
|
2743
3248
|
// against the surfaced camera. In a single-world app the per-world extract
|
|
@@ -2776,6 +3281,7 @@ export function extractFrames(
|
|
|
2776
3281
|
cascadeCount = csm.cascadeCount;
|
|
2777
3282
|
cascadeBlend = csm.cascadeBlend;
|
|
2778
3283
|
shadowMapSize = csm.shadowMapSize;
|
|
3284
|
+
directionalShadowQuality = csm.directionalShadowQuality;
|
|
2779
3285
|
}
|
|
2780
3286
|
// csm === null (no surfaced camera at all): keep the per-world carry —
|
|
2781
3287
|
// there is no better data, and a cameraless frame renders nothing anyway.
|
|
@@ -2786,6 +3292,7 @@ export function extractFrames(
|
|
|
2786
3292
|
directionalCount,
|
|
2787
3293
|
point,
|
|
2788
3294
|
spot,
|
|
3295
|
+
rect,
|
|
2789
3296
|
lightViewProj,
|
|
2790
3297
|
splitPlanes,
|
|
2791
3298
|
cascadeCount,
|
|
@@ -2793,7 +3300,8 @@ export function extractFrames(
|
|
|
2793
3300
|
shadowMapSize,
|
|
2794
3301
|
depthBias,
|
|
2795
3302
|
normalBias,
|
|
2796
|
-
|
|
3303
|
+
directionalShadowQuality,
|
|
3304
|
+
directionalShadowError,
|
|
2797
3305
|
pointShadow,
|
|
2798
3306
|
directionalCsmConfig,
|
|
2799
3307
|
directionalCsmDirection,
|
|
@@ -2804,7 +3312,9 @@ export function extractFrames(
|
|
|
2804
3312
|
const skybox = resourceOwnerFrame?.skybox;
|
|
2805
3313
|
const skyboxCount = resourceOwnerFrame?.skyboxCount ?? 0;
|
|
2806
3314
|
const fog = resourceOwnerFrame?.fog;
|
|
3315
|
+
const fogFailure = resourceOwnerFrame?.fogFailure;
|
|
2807
3316
|
const environment = resourceOwnerFrame?.environment;
|
|
3317
|
+
const lightProbes = succeededFrames.flatMap((frame) => frame.lightProbes ?? []);
|
|
2808
3318
|
// User-authored PostProcessParams entities are singleton scene resources, so
|
|
2809
3319
|
// they come from the resourceOwner world (holistic snapshot selection).
|
|
2810
3320
|
const postProcessParams = new Map(resourceOwnerFrame?.postProcessParams);
|
|
@@ -2843,9 +3353,16 @@ export function extractFrames(
|
|
|
2843
3353
|
const visibilityStats = {
|
|
2844
3354
|
explicitlyHidden: succeededFrames.reduce((s, f) => s + f.visibilityStats.explicitlyHidden, 0),
|
|
2845
3355
|
};
|
|
3356
|
+
const reflectionProbes = succeededFrames.flatMap((f, index) => {
|
|
3357
|
+
const worldId = succeededIndices[index] ?? 0;
|
|
3358
|
+
return (f.reflectionProbes ?? []).map((probe) => ({ ...probe, worldId }));
|
|
3359
|
+
});
|
|
2846
3360
|
|
|
2847
3361
|
return {
|
|
2848
3362
|
cameras,
|
|
3363
|
+
auxiliaryCameras,
|
|
3364
|
+
cubeCameras,
|
|
3365
|
+
reflectionProbes,
|
|
2849
3366
|
lights,
|
|
2850
3367
|
...(volumetricFog === undefined
|
|
2851
3368
|
? {}
|
|
@@ -2854,9 +3371,11 @@ export function extractFrames(
|
|
|
2854
3371
|
dispatch: dispatchEntries,
|
|
2855
3372
|
skylight,
|
|
2856
3373
|
skylightCount,
|
|
3374
|
+
lightProbes,
|
|
2857
3375
|
skybox,
|
|
2858
3376
|
skyboxCount,
|
|
2859
3377
|
fog,
|
|
3378
|
+
...(fogFailure === undefined ? {} : { fogFailure }),
|
|
2860
3379
|
environment,
|
|
2861
3380
|
environmentReady: resourceOwnerFrame !== undefined && environment !== undefined,
|
|
2862
3381
|
frustumStats,
|
|
@@ -2947,18 +3466,20 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2947
3466
|
materialSnapshotCache: persistentMaterialSnapshotCache,
|
|
2948
3467
|
cull: cullMode,
|
|
2949
3468
|
resourceOwnerFog,
|
|
3469
|
+
getMaterialShaderArtifact,
|
|
2950
3470
|
} = context;
|
|
2951
3471
|
// Environment reads are owned by the retained resource projection and are
|
|
2952
3472
|
// established at the frame boundary; record never revisits World state.
|
|
2953
3473
|
const fog =
|
|
2954
3474
|
resourceOwnerFog === undefined ? selectFogFrame(world) : (resourceOwnerFog ?? undefined);
|
|
3475
|
+
const fogFailure = fogState(world).failure;
|
|
2955
3476
|
const visibility = context.visibility.hasAnyHiddenIntent ? context.visibility : undefined;
|
|
2956
3477
|
const skinPaletteAllocator = pipelineState?.skinPaletteAllocator ?? null;
|
|
2957
3478
|
|
|
2958
3479
|
const directionalLightQuery = world.query({ read: [DirectionalLight] }).unwrap();
|
|
2959
3480
|
|
|
2960
3481
|
// feat-20260601 D-3: camera / point / spot light world transforms are read
|
|
2961
|
-
// through the single resolved `
|
|
3482
|
+
// through the single resolved `GlobalTransform.world` mat4 (written by
|
|
2962
3483
|
// propagateTransforms), not the retired GlobalTransform-column-switch.
|
|
2963
3484
|
//
|
|
2964
3485
|
// Each segment routes through a World-owned Query with explicit read and
|
|
@@ -2971,7 +3492,11 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2971
3492
|
const worldInternal = createWorldInternalView(world);
|
|
2972
3493
|
let volumetricFog = extractVolumeSnapshot(world, assets, worldInternal);
|
|
2973
3494
|
|
|
2974
|
-
const
|
|
3495
|
+
const cameraRoles = selectCameraRoles(world);
|
|
3496
|
+
const cameras = [...cameraRoles.display];
|
|
3497
|
+
const auxiliaryCameras = cameraRoles.auxiliary;
|
|
3498
|
+
const cubeCameras = collectCubeCameraSnapshots(world);
|
|
3499
|
+
const reflectionProbes = collectReflectionProbeFacts(world);
|
|
2975
3500
|
|
|
2976
3501
|
// Three-query union (M2 / w16 / AC-03): directional has no Transform
|
|
2977
3502
|
// dependency (sun-like infinite-source semantics); point + spot pull
|
|
@@ -2992,7 +3517,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2992
3517
|
depthBias: number;
|
|
2993
3518
|
normalBias: number;
|
|
2994
3519
|
shadowDistance: number;
|
|
2995
|
-
|
|
3520
|
+
shadowFilter: number;
|
|
3521
|
+
shadowAngularRadius: number;
|
|
3522
|
+
maxPenumbraTexels: number;
|
|
2996
3523
|
}
|
|
2997
3524
|
| undefined;
|
|
2998
3525
|
for (const row of directionalLightQuery) {
|
|
@@ -3022,11 +3549,29 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3022
3549
|
depthBias: l.depthBias,
|
|
3023
3550
|
normalBias: l.normalBias,
|
|
3024
3551
|
shadowDistance: l.shadowDistance,
|
|
3025
|
-
|
|
3552
|
+
shadowFilter: l.shadowFilter,
|
|
3553
|
+
shadowAngularRadius: l.shadowAngularRadius,
|
|
3554
|
+
maxPenumbraTexels: l.maxPenumbraTexels,
|
|
3026
3555
|
};
|
|
3027
3556
|
}
|
|
3028
3557
|
}
|
|
3029
3558
|
|
|
3559
|
+
let directionalShadowError: LightValidationError | undefined;
|
|
3560
|
+
if (directional !== undefined && firstHitShadowFields !== undefined) {
|
|
3561
|
+
const validation = validateDirectionalLightData({
|
|
3562
|
+
direction: directional.direction,
|
|
3563
|
+
castShadow: firstHitCastShadow,
|
|
3564
|
+
...firstHitShadowFields,
|
|
3565
|
+
});
|
|
3566
|
+
if (!validation.ok) {
|
|
3567
|
+
directionalShadowError = validation.error;
|
|
3568
|
+
worldInternal._routeError(directionalShadowError, {
|
|
3569
|
+
severity: Severity.Error,
|
|
3570
|
+
systemName: 'RenderSystem.extract (directional-shadow)',
|
|
3571
|
+
});
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3030
3575
|
const pointSnapshots: PointLightSnapshot[] = [];
|
|
3031
3576
|
// feat-20260612-point-light-shadows-urp-hdrp M4 / T-M4-4: track entity per
|
|
3032
3577
|
// pointSnapshots index so the post-extract pointShadow join can stamp
|
|
@@ -3035,22 +3580,22 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3035
3580
|
const pointLightQuery = world
|
|
3036
3581
|
.query({
|
|
3037
3582
|
read: [PointLight],
|
|
3038
|
-
optional: [Transform],
|
|
3583
|
+
optional: [Transform, GlobalTransform],
|
|
3039
3584
|
})
|
|
3040
3585
|
.unwrap();
|
|
3041
3586
|
for (const row of pointLightQuery) {
|
|
3042
3587
|
const p = row.get(PointLight);
|
|
3043
3588
|
// K-2 scheme B: archetype-edge sniff -- `bundle.Transform` key is absent
|
|
3044
3589
|
// when the archetype does not carry the Transform column.
|
|
3045
|
-
const hasTransform = row.get(
|
|
3590
|
+
const hasTransform = row.get(GlobalTransform) !== undefined;
|
|
3046
3591
|
const intensity = p.intensity;
|
|
3047
3592
|
const range = p.range;
|
|
3048
3593
|
const entityId = row.entity;
|
|
3049
|
-
// Position = world-space translation extracted from
|
|
3594
|
+
// Position = world-space translation extracted from GlobalTransform.world.
|
|
3050
3595
|
// A point light archetype without a Transform column sits at the origin.
|
|
3051
3596
|
let worldMat: Float32Array | undefined;
|
|
3052
3597
|
if (hasTransform) {
|
|
3053
|
-
const view = worldInternal._getArrayView(entityId,
|
|
3598
|
+
const view = worldInternal._getArrayView(entityId, GlobalTransform, 'world');
|
|
3054
3599
|
if (view !== undefined) worldMat = new Float32Array(view);
|
|
3055
3600
|
}
|
|
3056
3601
|
const position =
|
|
@@ -3074,16 +3619,19 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3074
3619
|
const spotLightQuery = world
|
|
3075
3620
|
.query({
|
|
3076
3621
|
read: [SpotLight],
|
|
3077
|
-
optional: [Transform],
|
|
3622
|
+
optional: [Transform, GlobalTransform],
|
|
3078
3623
|
})
|
|
3079
3624
|
.unwrap();
|
|
3080
3625
|
// feat-20260625-spot-light-shadow-mapping M1 w5: tile allocation for castShadow spots.
|
|
3081
3626
|
// Cap = 4 (OOS-5), sentinel -1 = unassigned (plan-strategy D-4).
|
|
3082
3627
|
// Direction degeneration (near-zero) also skips shadow (requirements $112).
|
|
3083
3628
|
let spotTileNext = 0;
|
|
3629
|
+
const iesSlices = new Map<number, number>();
|
|
3630
|
+
const cookieSlices = new Map<number, number>();
|
|
3631
|
+
const projectorSlices = new Map<number, number>();
|
|
3084
3632
|
for (const row of spotLightQuery) {
|
|
3085
3633
|
const s = row.get(SpotLight);
|
|
3086
|
-
const hasTransform = row.get(
|
|
3634
|
+
const hasTransform = row.get(GlobalTransform) !== undefined;
|
|
3087
3635
|
const intensity = s.intensity;
|
|
3088
3636
|
const range = s.range;
|
|
3089
3637
|
const innerConeDeg = s.innerConeDeg;
|
|
@@ -3091,7 +3639,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3091
3639
|
let worldMat: Float32Array | undefined;
|
|
3092
3640
|
if (hasTransform) {
|
|
3093
3641
|
const entity = row.entity;
|
|
3094
|
-
const view = worldInternal._getArrayView(entity,
|
|
3642
|
+
const view = worldInternal._getArrayView(entity, GlobalTransform, 'world');
|
|
3095
3643
|
if (view !== undefined) worldMat = new Float32Array(view);
|
|
3096
3644
|
}
|
|
3097
3645
|
const position =
|
|
@@ -3129,13 +3677,93 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3129
3677
|
const sMapSize = s.mapSize;
|
|
3130
3678
|
const sNearPlane = s.nearPlane;
|
|
3131
3679
|
const sFarPlane = s.farPlane;
|
|
3680
|
+
const iesProfileAsset =
|
|
3681
|
+
s.iesProfile === undefined
|
|
3682
|
+
? undefined
|
|
3683
|
+
: resolveAssetHandle<IesProfileAsset>(world, s.iesProfile);
|
|
3684
|
+
const cookieAsset =
|
|
3685
|
+
s.cookie === undefined ? undefined : resolveAssetHandle<TextureAsset>(world, s.cookie);
|
|
3686
|
+
// Resolve the projector before assigning array slices so the
|
|
3687
|
+
// extended-lighting path can reuse its texture transport while retaining
|
|
3688
|
+
// the authored lightViewProj projection semantics.
|
|
3689
|
+
const hasProjector = Number(s.projector) > 0;
|
|
3690
|
+
let projectorHandle: Handle<'TextureAsset', 'shared'> | undefined;
|
|
3691
|
+
let projectorAsset: TextureAsset | undefined;
|
|
3692
|
+
let projectorGuid: string | undefined;
|
|
3693
|
+
if (hasProjector) {
|
|
3694
|
+
const candidate = toShared<'TextureAsset'>(Math.round(Number(s.projector)));
|
|
3695
|
+
const resolvedProjector = resolveAssetHandle<TextureAsset>(world, candidate);
|
|
3696
|
+
if (!resolvedProjector.ok || resolvedProjector.value.kind !== 'texture') {
|
|
3697
|
+
worldInternal._routeError(
|
|
3698
|
+
resolvedProjector.ok
|
|
3699
|
+
? new RhiError({
|
|
3700
|
+
code: 'asset-not-registered',
|
|
3701
|
+
expected: 'SpotLight.projector resolves to a TextureAsset',
|
|
3702
|
+
hint: 'load the authored projector TextureAsset before assigning SpotLight.projector',
|
|
3703
|
+
detail: { assetHandle: Number(candidate) },
|
|
3704
|
+
})
|
|
3705
|
+
: resolvedProjector.error,
|
|
3706
|
+
{ severity: Severity.Error, systemName: 'RenderSystem.extract (spot-projector)' },
|
|
3707
|
+
);
|
|
3708
|
+
} else {
|
|
3709
|
+
projectorHandle = candidate;
|
|
3710
|
+
projectorAsset = resolvedProjector.value;
|
|
3711
|
+
projectorGuid = assets?.guidOf(projectorAsset) ?? `handle:${Number(candidate)}`;
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
const iesProfileData =
|
|
3715
|
+
iesProfileAsset?.ok && iesProfileAsset.value.kind === 'ies-profile'
|
|
3716
|
+
? new Uint8Array(iesProfileAsset.value.data)
|
|
3717
|
+
: undefined;
|
|
3718
|
+
const cookieProjection =
|
|
3719
|
+
cookieAsset?.ok && cookieAsset.value.kind === 'texture'
|
|
3720
|
+
? prepareCookieProjection(cookieAsset.value)
|
|
3721
|
+
: undefined;
|
|
3722
|
+
const cookieData = cookieProjection?.data;
|
|
3723
|
+
const projectorProjection =
|
|
3724
|
+
projectorAsset === undefined ? undefined : prepareCookieProjection(projectorAsset);
|
|
3725
|
+
const projectorData = projectorProjection?.data;
|
|
3726
|
+
const iesProfileHandle = s.iesProfile === undefined ? undefined : handleSlot(s.iesProfile);
|
|
3727
|
+
const cookieHandle = s.cookie === undefined ? undefined : handleSlot(s.cookie);
|
|
3728
|
+
let iesProfileSlice =
|
|
3729
|
+
iesProfileHandle === undefined ? undefined : iesSlices.get(iesProfileHandle);
|
|
3730
|
+
if (
|
|
3731
|
+
iesProfileSlice === undefined &&
|
|
3732
|
+
iesProfileHandle !== undefined &&
|
|
3733
|
+
iesProfileData !== undefined &&
|
|
3734
|
+
iesSlices.size < IES_SLICE_CAPACITY
|
|
3735
|
+
) {
|
|
3736
|
+
iesProfileSlice = iesSlices.size;
|
|
3737
|
+
iesSlices.set(iesProfileHandle, iesProfileSlice);
|
|
3738
|
+
}
|
|
3739
|
+
let cookieSlice = cookieHandle === undefined ? undefined : cookieSlices.get(cookieHandle);
|
|
3740
|
+
if (
|
|
3741
|
+
cookieSlice === undefined &&
|
|
3742
|
+
cookieHandle !== undefined &&
|
|
3743
|
+
cookieData !== undefined &&
|
|
3744
|
+
cookieSlices.size < COOKIE_SLICE_CAPACITY
|
|
3745
|
+
) {
|
|
3746
|
+
cookieSlice = cookieSlices.size;
|
|
3747
|
+
cookieSlices.set(cookieHandle, cookieSlice);
|
|
3748
|
+
}
|
|
3749
|
+
let projectorSlice =
|
|
3750
|
+
projectorHandle === undefined
|
|
3751
|
+
? undefined
|
|
3752
|
+
: (projectorSlices.get(projectorHandle) ?? cookieSlices.get(projectorHandle));
|
|
3753
|
+
if (
|
|
3754
|
+
projectorSlice === undefined &&
|
|
3755
|
+
projectorHandle !== undefined &&
|
|
3756
|
+
projectorData !== undefined &&
|
|
3757
|
+
cookieSlices.size + projectorSlices.size < COOKIE_SLICE_CAPACITY
|
|
3758
|
+
) {
|
|
3759
|
+
projectorSlice = cookieSlices.size + projectorSlices.size;
|
|
3760
|
+
projectorSlices.set(projectorHandle, projectorSlice);
|
|
3761
|
+
}
|
|
3132
3762
|
const shadowIntensity = s.shadowIntensity;
|
|
3133
3763
|
// `shared<T>` columns are u32-backed and zero-filled when the optional
|
|
3134
3764
|
// field is absent. Treat only a positive handle as an authored projector;
|
|
3135
3765
|
// checking nullish values alone would turn the zero sentinel into a
|
|
3136
3766
|
// phantom projector matrix and an avoidable asset-not-found diagnostic.
|
|
3137
|
-
const hasProjector = Number(s.projector) > 0;
|
|
3138
|
-
|
|
3139
3767
|
let lightViewProj: Float32Array | undefined;
|
|
3140
3768
|
let shadowAtlasTile = -1;
|
|
3141
3769
|
|
|
@@ -3167,31 +3795,6 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3167
3795
|
}
|
|
3168
3796
|
}
|
|
3169
3797
|
|
|
3170
|
-
let projectorHandle: Handle<'TextureAsset', 'shared'> | undefined;
|
|
3171
|
-
let projectorAsset: TextureAsset | undefined;
|
|
3172
|
-
let projectorGuid: string | undefined;
|
|
3173
|
-
if (hasProjector) {
|
|
3174
|
-
const candidate = toShared<'TextureAsset'>(Math.round(Number(s.projector)));
|
|
3175
|
-
const resolvedProjector = resolveAssetHandle<TextureAsset>(world, candidate);
|
|
3176
|
-
if (!resolvedProjector.ok || resolvedProjector.value.kind !== 'texture') {
|
|
3177
|
-
worldInternal._routeError(
|
|
3178
|
-
resolvedProjector.ok
|
|
3179
|
-
? new RhiError({
|
|
3180
|
-
code: 'asset-not-registered',
|
|
3181
|
-
expected: 'SpotLight.projector resolves to a TextureAsset',
|
|
3182
|
-
hint: 'load the authored projector TextureAsset before assigning SpotLight.projector',
|
|
3183
|
-
detail: { assetHandle: Number(candidate) },
|
|
3184
|
-
})
|
|
3185
|
-
: resolvedProjector.error,
|
|
3186
|
-
{ severity: Severity.Error, systemName: 'RenderSystem.extract (spot-projector)' },
|
|
3187
|
-
);
|
|
3188
|
-
} else {
|
|
3189
|
-
projectorHandle = candidate;
|
|
3190
|
-
projectorAsset = resolvedProjector.value;
|
|
3191
|
-
projectorGuid = assets?.guidOf(projectorAsset) ?? `handle:${Number(candidate)}`;
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3194
|
-
|
|
3195
3798
|
spotSnapshots.push({
|
|
3196
3799
|
kind: 'spot',
|
|
3197
3800
|
entity: row.entity,
|
|
@@ -3208,6 +3811,25 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3208
3811
|
invRangeSquared: computeInvRangeSquared(range),
|
|
3209
3812
|
cosInner: degToCos(innerConeDeg),
|
|
3210
3813
|
cosOuter: degToCos(outerConeDeg),
|
|
3814
|
+
...(s.iesProfile === undefined
|
|
3815
|
+
? {}
|
|
3816
|
+
: {
|
|
3817
|
+
iesProfileHandle: handleSlot(s.iesProfile),
|
|
3818
|
+
...(iesProfileSlice === undefined ? {} : { iesProfileSlice }),
|
|
3819
|
+
}),
|
|
3820
|
+
...(s.cookie === undefined
|
|
3821
|
+
? {}
|
|
3822
|
+
: {
|
|
3823
|
+
cookieHandle: handleSlot(s.cookie),
|
|
3824
|
+
...(cookieSlice === undefined ? {} : { cookieSlice }),
|
|
3825
|
+
}),
|
|
3826
|
+
...(iesProfileData === undefined ? {} : { iesProfileData }),
|
|
3827
|
+
...(cookieData === undefined ? {} : { cookieData }),
|
|
3828
|
+
...(cookieProjection === undefined ? {} : { cookieMatrix: cookieProjection.matrix }),
|
|
3829
|
+
...(projectorSlice === undefined ? {} : { projectorSlice }),
|
|
3830
|
+
...(projectorData === undefined ? {} : { projectorData }),
|
|
3831
|
+
...(projectorProjection === undefined ? {} : { projectorMatrix: projectorProjection.matrix }),
|
|
3832
|
+
rollDeg: s.rollDeg,
|
|
3211
3833
|
// ── shadow fields ──
|
|
3212
3834
|
castShadow,
|
|
3213
3835
|
lightViewProj,
|
|
@@ -3233,6 +3855,59 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3233
3855
|
});
|
|
3234
3856
|
}
|
|
3235
3857
|
|
|
3858
|
+
const rectSnapshots: RectAreaDirectLightSnapshot[] = [];
|
|
3859
|
+
const rectLightQuery = world
|
|
3860
|
+
.query({
|
|
3861
|
+
read: [RectAreaLight],
|
|
3862
|
+
optional: [Transform, GlobalTransform],
|
|
3863
|
+
})
|
|
3864
|
+
.unwrap();
|
|
3865
|
+
for (const row of rectLightQuery) {
|
|
3866
|
+
const light = row.get(RectAreaLight);
|
|
3867
|
+
const validation = validateRectAreaLightData({
|
|
3868
|
+
intensity: light.intensity,
|
|
3869
|
+
color: light.color,
|
|
3870
|
+
width: light.width,
|
|
3871
|
+
height: light.height,
|
|
3872
|
+
range: light.range,
|
|
3873
|
+
});
|
|
3874
|
+
if (!validation.ok) {
|
|
3875
|
+
worldInternal._routeError(validation.error, {
|
|
3876
|
+
severity: Severity.Error,
|
|
3877
|
+
systemName: 'RenderSystem.extract (rect-area-light)',
|
|
3878
|
+
});
|
|
3879
|
+
continue;
|
|
3880
|
+
}
|
|
3881
|
+
const worldView = row.get(GlobalTransform);
|
|
3882
|
+
const worldMat =
|
|
3883
|
+
worldView === undefined
|
|
3884
|
+
? undefined
|
|
3885
|
+
: new Float32Array(worldInternal._getArrayView(row.entity, GlobalTransform, 'world') ?? []);
|
|
3886
|
+
const position =
|
|
3887
|
+
worldMat !== undefined ? mat4.getTranslation(vec3.create(), worldMat) : vec3.create(0, 0, 0);
|
|
3888
|
+
const frame = buildRectAreaWorldFrame({
|
|
3889
|
+
center: position,
|
|
3890
|
+
axisX: [worldMat?.[0] ?? 1, worldMat?.[1] ?? 0, worldMat?.[2] ?? 0],
|
|
3891
|
+
axisY: [worldMat?.[4] ?? 0, worldMat?.[5] ?? 1, worldMat?.[6] ?? 0],
|
|
3892
|
+
width: light.width,
|
|
3893
|
+
height: light.height,
|
|
3894
|
+
});
|
|
3895
|
+
rectSnapshots.push({
|
|
3896
|
+
kind: 'rect-area',
|
|
3897
|
+
position,
|
|
3898
|
+
color: vec3.create(
|
|
3899
|
+
(light.color[0] ?? 1) * light.intensity,
|
|
3900
|
+
(light.color[1] ?? 1) * light.intensity,
|
|
3901
|
+
(light.color[2] ?? 1) * light.intensity,
|
|
3902
|
+
),
|
|
3903
|
+
intensity: light.intensity,
|
|
3904
|
+
invRangeSquared: computeInvRangeSquared(light.range),
|
|
3905
|
+
halfWidth: frame.halfWidth,
|
|
3906
|
+
halfHeight: frame.halfHeight,
|
|
3907
|
+
axisX: frame.axisX,
|
|
3908
|
+
axisY: frame.axisY,
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3236
3911
|
// The SpotLight is the sole projector owner. Once all light snapshots have
|
|
3237
3912
|
// been extracted, publish its accepted TextureAsset tuple on the volume
|
|
3238
3913
|
// snapshot so surface and volume consume the same identity and revision.
|
|
@@ -3282,6 +3957,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3282
3957
|
let cascadeCount: number | undefined;
|
|
3283
3958
|
let cascadeBlend: number | undefined;
|
|
3284
3959
|
let shadowMapSize: number | undefined;
|
|
3960
|
+
let directionalShadowQuality: DirectionalShadowQuality | undefined;
|
|
3285
3961
|
let directionalCsmConfig: DirectionalCsmConfig | undefined;
|
|
3286
3962
|
let directionalCsmDirection: Vec3 | undefined;
|
|
3287
3963
|
|
|
@@ -3307,7 +3983,11 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3307
3983
|
// feat-20260621 M2: CSM computation gated on castShadow from the
|
|
3308
3984
|
// merged DirectionalLight. castShadow defaults to true (first-hit-wins
|
|
3309
3985
|
// semantics, D-6 no cardinality cap).
|
|
3310
|
-
if (
|
|
3986
|
+
if (
|
|
3987
|
+
directional !== undefined &&
|
|
3988
|
+
firstHitCastShadow !== false &&
|
|
3989
|
+
directionalShadowError === undefined
|
|
3990
|
+
) {
|
|
3311
3991
|
const dirSnapshot = directional;
|
|
3312
3992
|
const sf = firstHitShadowFields;
|
|
3313
3993
|
if (sf !== undefined) {
|
|
@@ -3317,6 +3997,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3317
3997
|
cascadeBlend: sf.cascadeBlend,
|
|
3318
3998
|
mapSize: sf.mapSize,
|
|
3319
3999
|
shadowDistance: sf.shadowDistance,
|
|
4000
|
+
shadowFilter: sf.shadowFilter,
|
|
4001
|
+
shadowAngularRadius: sf.shadowAngularRadius,
|
|
4002
|
+
maxPenumbraTexels: sf.maxPenumbraTexels,
|
|
3320
4003
|
};
|
|
3321
4004
|
directionalCsmDirection = dirSnapshot.direction;
|
|
3322
4005
|
const csm = computeDirectionalCsm(dirSnapshot.direction, directionalCsmConfig, cameraData);
|
|
@@ -3331,13 +4014,19 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3331
4014
|
if (csm !== null) {
|
|
3332
4015
|
lightViewProj = csm.lightViewProj;
|
|
3333
4016
|
splitPlanes = csm.splitPlanes;
|
|
4017
|
+
directionalShadowQuality = csm.directionalShadowQuality;
|
|
3334
4018
|
} else {
|
|
3335
4019
|
const sNear = cameraData?.near ?? 0.1;
|
|
3336
4020
|
const splits = pssmSplit(sNear, sf.shadowDistance, cascadeCount, sf.splitLambda);
|
|
3337
|
-
const padded = new Float32Array(
|
|
3338
|
-
for (let i = 0; i < splits.length; i++) padded[i] = splits[i] ?? 0;
|
|
4021
|
+
const padded = new Float32Array(16);
|
|
4022
|
+
for (let i = 0; i < splits.length; i++) padded[i * 4] = splits[i] ?? 0;
|
|
3339
4023
|
splitPlanes = padded;
|
|
3340
4024
|
}
|
|
4025
|
+
directionalShadowQuality = directionalShadowQualityFromF32(
|
|
4026
|
+
sf.shadowFilter,
|
|
4027
|
+
sf.shadowAngularRadius,
|
|
4028
|
+
sf.maxPenumbraTexels,
|
|
4029
|
+
);
|
|
3341
4030
|
}
|
|
3342
4031
|
}
|
|
3343
4032
|
|
|
@@ -3346,7 +4035,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3346
4035
|
// the View UBO tail keeps a stable layout regardless of the runtime
|
|
3347
4036
|
// cascadeCount. Host-side correctness invariant: only the first
|
|
3348
4037
|
// cascadeCount slots are ever read by the WGSL kernel.
|
|
3349
|
-
const paddedSplitPlanes = new Float32Array(
|
|
4038
|
+
const paddedSplitPlanes = new Float32Array(16);
|
|
3350
4039
|
if (splitPlanes !== undefined) {
|
|
3351
4040
|
for (let i = 0; i < splitPlanes.length; i++) {
|
|
3352
4041
|
paddedSplitPlanes[i] = splitPlanes[i] ?? 0;
|
|
@@ -3356,18 +4045,17 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3356
4045
|
// feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-7 (plan-strategy §D-3,
|
|
3357
4046
|
// requirements §5.3): query (PointLight + PointLightShadow + Transform)
|
|
3358
4047
|
// archetype join. For each shadow caster, build 6 face VP matrices and pack
|
|
3359
|
-
// into Float32Array(96). Atlas layer assigned in spawn order
|
|
3360
|
-
// sentinel -1 is shader-side and
|
|
3361
|
-
//
|
|
4048
|
+
// into Float32Array(96). Atlas layer is assigned in spawn order while the
|
|
4049
|
+
// renderer-owned atlas has capacity; the sentinel -1 is shader-side and
|
|
4050
|
+
// applies to non-admitted PointLightShadow requests as well.
|
|
3362
4051
|
const pointShadowSnapshots: PointShadowSnapshot[] = [];
|
|
3363
4052
|
{
|
|
3364
4053
|
const pointShadowQuery = world
|
|
3365
|
-
.query({ read: [Transform, PointLightShadow], with: [PointLight] })
|
|
4054
|
+
.query({ read: [Transform, GlobalTransform, PointLightShadow], with: [PointLight] })
|
|
3366
4055
|
.unwrap();
|
|
3367
4056
|
for (const row of pointShadowQuery) {
|
|
3368
|
-
const t = row.get(Transform);
|
|
3369
4057
|
const ps = row.get(PointLightShadow);
|
|
3370
|
-
// Read world-space position from
|
|
4058
|
+
// Read world-space position from GlobalTransform.world (mat4 column-major;
|
|
3371
4059
|
// translation lives at indices 12..14, mirroring CameraSnapshot.world
|
|
3372
4060
|
// semantics in this file).
|
|
3373
4061
|
// feat-20260614 M4 / w13: TypedArrayFor for `array<f32, 16>` now
|
|
@@ -3377,7 +4065,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3377
4065
|
// `t.world?.[i]` form silently returned a single element under the
|
|
3378
4066
|
// `never`-typed bundle path and `wRow[12]` widened to `undefined ?? 0`
|
|
3379
4067
|
// so light positions clamped to the origin.
|
|
3380
|
-
const wRow =
|
|
4068
|
+
const wRow = row.get(GlobalTransform).world;
|
|
3381
4069
|
if (wRow === undefined) continue;
|
|
3382
4070
|
const px = wRow[12] ?? 0;
|
|
3383
4071
|
const py = wRow[13] ?? 0;
|
|
@@ -3386,7 +4074,13 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3386
4074
|
const mapSize = ps.mapSize;
|
|
3387
4075
|
const nearPlane = ps.nearPlane;
|
|
3388
4076
|
const farPlane = ps.farPlane;
|
|
3389
|
-
|
|
4077
|
+
// The renderer, not ECS, owns the atlas budget. Preserve every request
|
|
4078
|
+
// for inspection, but emit the shader sentinel for entries that cannot
|
|
4079
|
+
// acquire a layer so record never calls faceView outside the atlas.
|
|
4080
|
+
const layer =
|
|
4081
|
+
pointShadowSnapshots.length < SHADOW_ATLAS_DEFAULT_LAYERS
|
|
4082
|
+
? pointShadowSnapshots.length
|
|
4083
|
+
: -1;
|
|
3390
4084
|
|
|
3391
4085
|
const matrices = buildPointShadowMatrices(lightPos, nearPlane, farPlane);
|
|
3392
4086
|
const packed = new Float32Array(96);
|
|
@@ -3412,7 +4106,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3412
4106
|
// feat-20260612-point-light-shadows-urp-hdrp M4 / T-M4-4 (plan-strategy §D-8):
|
|
3413
4107
|
// join pointShadow snapshots into the matching PointLightSnapshot so the
|
|
3414
4108
|
// record stage threads `shadowAtlasLayer + shadowNear + shadowFar` through
|
|
3415
|
-
//
|
|
4109
|
+
// the unified direct-light metadata payload.
|
|
3416
4110
|
// Mutates the freshly-built PointLightSnapshot in place; the snapshot is
|
|
3417
4111
|
// not exposed elsewhere this frame yet (consumed only by lights.point[]).
|
|
3418
4112
|
if (pointShadowSnapshots.length > 0) {
|
|
@@ -3437,6 +4131,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3437
4131
|
directionalCount,
|
|
3438
4132
|
point: pointSnapshots,
|
|
3439
4133
|
spot: spotSnapshots,
|
|
4134
|
+
rect: rectSnapshots,
|
|
3440
4135
|
lightViewProj,
|
|
3441
4136
|
splitPlanes: splitPlanes !== undefined ? paddedSplitPlanes : undefined,
|
|
3442
4137
|
cascadeCount,
|
|
@@ -3444,7 +4139,8 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3444
4139
|
shadowMapSize,
|
|
3445
4140
|
depthBias: firstHitCastShadow !== false ? firstHitShadowFields?.depthBias : undefined,
|
|
3446
4141
|
normalBias: firstHitCastShadow !== false ? firstHitShadowFields?.normalBias : undefined,
|
|
3447
|
-
|
|
4142
|
+
directionalShadowQuality,
|
|
4143
|
+
directionalShadowError,
|
|
3448
4144
|
pointShadow: pointShadowSnapshots,
|
|
3449
4145
|
// bug-20260710-editor-cross-world-shadow: raw CSM config + light direction
|
|
3450
4146
|
// so the merge layer can recompute matrices against the surfaced camera.
|
|
@@ -3489,6 +4185,31 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3489
4185
|
}
|
|
3490
4186
|
}
|
|
3491
4187
|
|
|
4188
|
+
const lightProbeQuery = world.query({ read: [LightProbe, GlobalTransform] }).unwrap();
|
|
4189
|
+
const lightProbes: LightProbeSnapshot[] = [];
|
|
4190
|
+
for (const row of lightProbeQuery) {
|
|
4191
|
+
const probe = row.get(LightProbe);
|
|
4192
|
+
const validation = validateLightProbeData({
|
|
4193
|
+
irradiance: probe.irradiance,
|
|
4194
|
+
radius: probe.radius,
|
|
4195
|
+
});
|
|
4196
|
+
if (!validation.ok) {
|
|
4197
|
+
worldInternal._routeError(validation.error, {
|
|
4198
|
+
severity: Severity.Error,
|
|
4199
|
+
systemName: 'RenderSystem.extract (light-probe)',
|
|
4200
|
+
});
|
|
4201
|
+
continue;
|
|
4202
|
+
}
|
|
4203
|
+
const transform = row.get(GlobalTransform);
|
|
4204
|
+
lightProbes.push({
|
|
4205
|
+
identity: String(row.entity),
|
|
4206
|
+
position: [transform.world[12] ?? 0, transform.world[13] ?? 0, transform.world[14] ?? 0],
|
|
4207
|
+
radius: probe.radius,
|
|
4208
|
+
irradiance: new Float32Array(probe.irradiance),
|
|
4209
|
+
admitted: true,
|
|
4210
|
+
});
|
|
4211
|
+
}
|
|
4212
|
+
|
|
3492
4213
|
// feat-20260531-skybox-env-background M2 / w5: query SkyboxBackground entities.
|
|
3493
4214
|
// First archetype hit wins (mirrors Skylight pattern); multi-entity
|
|
3494
4215
|
// once-warn in record stage uses skyboxCount.
|
|
@@ -3683,24 +4404,28 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3683
4404
|
// column id for the sprite-bucket per-entity region override read
|
|
3684
4405
|
void SpriteRegionOverride;
|
|
3685
4406
|
|
|
3686
|
-
const meshRendererQuery =
|
|
3687
|
-
.
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
4407
|
+
const meshRendererQuery =
|
|
4408
|
+
context.renderables === 'none'
|
|
4409
|
+
? []
|
|
4410
|
+
: world
|
|
4411
|
+
.query({
|
|
4412
|
+
read: [MeshRenderer],
|
|
4413
|
+
optional: [
|
|
4414
|
+
Transform,
|
|
4415
|
+
GlobalTransform,
|
|
4416
|
+
MeshFilter,
|
|
4417
|
+
Instances,
|
|
4418
|
+
Skin,
|
|
4419
|
+
Layer,
|
|
4420
|
+
MorphWeights,
|
|
4421
|
+
SpriteRegionOverride,
|
|
4422
|
+
SpriteInstances,
|
|
4423
|
+
Points,
|
|
4424
|
+
Lines,
|
|
4425
|
+
SortKey,
|
|
4426
|
+
],
|
|
4427
|
+
})
|
|
4428
|
+
.unwrap();
|
|
3704
4429
|
// QueryRow deliberately exposes component data, not an archetype version.
|
|
3705
4430
|
// The optimized ECS owner keeps table storage private, so instance-buffer
|
|
3706
4431
|
// invalidation uses a content fingerprint of the managed arrays instead.
|
|
@@ -3757,12 +4482,11 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3757
4482
|
points !== undefined ? 'Points' : lines !== undefined ? 'Lines' : undefined;
|
|
3758
4483
|
const isRenderable = hasTransform && hasMeshFilter;
|
|
3759
4484
|
|
|
3760
|
-
//
|
|
3761
|
-
//
|
|
3762
|
-
//
|
|
3763
|
-
//
|
|
3764
|
-
//
|
|
3765
|
-
// "ChildOf but forgot GlobalTransform" misconfiguration cannot occur.
|
|
4485
|
+
// The resolved world transform is read per-entity from the single
|
|
4486
|
+
// `GlobalTransform.world` mat4 (propagateTransforms output) inside the row
|
|
4487
|
+
// loop below. Normal structural paths materialize this carrier through
|
|
4488
|
+
// Transform's generic ECS requirement; keep the defensive row check because
|
|
4489
|
+
// an owner can explicitly remove a required component during recovery.
|
|
3766
4490
|
const fAssetHandle = meshFilter?.assetHandle;
|
|
3767
4491
|
// feat-20260520-2d-sprite-layer-mvp M-3 / w22: Layer column read here;
|
|
3768
4492
|
// value folded into each DispatchEntry so the render-system sort can use
|
|
@@ -3854,6 +4578,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3854
4578
|
let materialBindingDiagnostics: MeshMaterialBindingDiagnostic[] = [];
|
|
3855
4579
|
let gpuDrivenSubmeshes: readonly MeshAsset['submeshes'][number][] = [];
|
|
3856
4580
|
let gpuDrivenIndexed = false;
|
|
4581
|
+
let gpuDrivenGeometry: GpuDrivenGeometryReceipt | undefined;
|
|
3857
4582
|
|
|
3858
4583
|
// Resolve instance overrides against mesh-owned slot defaults once.
|
|
3859
4584
|
const fAssetHandleVal = fAssetHandle;
|
|
@@ -3867,8 +4592,20 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3867
4592
|
const meshRes = resolveAssetHandle<Asset>(world, meshHandle);
|
|
3868
4593
|
if (meshRes.ok && meshRes.value.kind === 'mesh') {
|
|
3869
4594
|
const meshAsset = meshRes.value as MeshAsset;
|
|
4595
|
+
const guid = (meshRes.value as { guid?: string }).guid ?? '<no-guid>';
|
|
3870
4596
|
gpuDrivenSubmeshes = meshAsset.submeshes;
|
|
3871
4597
|
gpuDrivenIndexed = meshAsset.indices !== undefined;
|
|
4598
|
+
const layoutProjection = deriveVertexLayoutProjection(meshAsset.attributes);
|
|
4599
|
+
gpuDrivenGeometry = {
|
|
4600
|
+
identity: `${guid}:${layoutProjection.digest}`,
|
|
4601
|
+
vertexInputs: layoutProjection.attributes.map((attribute) => ({
|
|
4602
|
+
semantic: attribute.key,
|
|
4603
|
+
location: attribute.shaderLocation,
|
|
4604
|
+
format: attribute.format,
|
|
4605
|
+
})),
|
|
4606
|
+
topology: meshAsset.submeshes[0]?.topology ?? 'triangle-list',
|
|
4607
|
+
indexed: gpuDrivenIndexed,
|
|
4608
|
+
};
|
|
3872
4609
|
// extractFrames is also a public read path and can run before the
|
|
3873
4610
|
// Renderer.draw pre-render stage. GlyphText proves the derived mesh
|
|
3874
4611
|
// owns exactly one Default slot; arbitrary meshes still fail closed
|
|
@@ -3876,7 +4613,6 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3876
4613
|
if (!Array.isArray(meshAsset.materialSlots) && world.get(entity, GlyphText).ok) {
|
|
3877
4614
|
ensureGlyphMeshMaterialSlots(world, meshHandle);
|
|
3878
4615
|
}
|
|
3879
|
-
const guid = (meshRes.value as { guid?: string }).guid ?? '<no-guid>';
|
|
3880
4616
|
const resolvedBindings = resolveMeshMaterialBindings(meshAsset, materialsView ?? [], {
|
|
3881
4617
|
isValidOverride(handle) {
|
|
3882
4618
|
const resolved = resolveAssetHandle(world, toShared<'MaterialAsset'>(handle));
|
|
@@ -4336,6 +5072,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4336
5072
|
pv.occlusionTexture,
|
|
4337
5073
|
);
|
|
4338
5074
|
const textureCoordinates = collectMaterialTextureCoordinates(pv);
|
|
5075
|
+
const textureSources = collectMaterialTextureSources(pv, world);
|
|
4339
5076
|
const emissivePv = pv.emissive as readonly number[] | undefined;
|
|
4340
5077
|
// feat-20260625 M2 / w6: first-pass transparency flag folds into
|
|
4341
5078
|
// MaterialSnapshot.transparent so the record stage can drive the
|
|
@@ -4435,6 +5172,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4435
5172
|
...(textureCoordinates.size > 0 && { textureCoordinates }),
|
|
4436
5173
|
...(samplerHandles.size > 0 && { samplerHandles }),
|
|
4437
5174
|
...(textureHandles.size > 0 && { textureHandles }),
|
|
5175
|
+
...(textureSources.size > 0 && { textureSources }),
|
|
4438
5176
|
...(videoTextureFields.size > 0 && { videoTextureFields }),
|
|
4439
5177
|
...(baseColorTextureHandle !== undefined && {
|
|
4440
5178
|
baseColorTexture: baseColorTextureHandle,
|
|
@@ -4600,7 +5338,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4600
5338
|
);
|
|
4601
5339
|
continue;
|
|
4602
5340
|
}
|
|
4603
|
-
// (c) Resolve only each joint's
|
|
5341
|
+
// (c) Resolve only each joint's GlobalTransform.world column. The
|
|
4604
5342
|
// transient view is consumed before a structural mutation and
|
|
4605
5343
|
// therefore preserves the same dangling-joint behavior without
|
|
4606
5344
|
// constructing every other Transform field.
|
|
@@ -4611,7 +5349,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4611
5349
|
for (let jIdx = 0; jIdx < skeleton.jointCount; jIdx++) {
|
|
4612
5350
|
const jointEntityRaw = skinJoints[jIdx] ?? 0;
|
|
4613
5351
|
const jointEntity = jointEntityRaw as EntityHandle;
|
|
4614
|
-
const jointWorld = worldInternal._getArrayView(jointEntity,
|
|
5352
|
+
const jointWorld = worldInternal._getArrayView(jointEntity, GlobalTransform, 'world');
|
|
4615
5353
|
if (jointWorld === undefined) {
|
|
4616
5354
|
jointDangling = jIdx;
|
|
4617
5355
|
break;
|
|
@@ -4653,14 +5391,23 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4653
5391
|
}
|
|
4654
5392
|
}
|
|
4655
5393
|
|
|
5394
|
+
// A pbr-skin PSO requires the complete three-binding group(2) skin
|
|
5395
|
+
// projection. Do not publish a renderable or its staged dispatch when
|
|
5396
|
+
// palette extraction was unavailable; the record stage must never
|
|
5397
|
+
// reinterpret a skinned vertex stream through an ordinary mesh group.
|
|
5398
|
+
if (materialSnap.materialShaderId === 'forgeax::pbr-skin' && skinSlice === undefined) {
|
|
5399
|
+
pendingDispatch.length = 0;
|
|
5400
|
+
continue;
|
|
5401
|
+
}
|
|
5402
|
+
|
|
4656
5403
|
// feat-20260601 D-3: read the resolved world mat4 (propagateTransforms
|
|
4657
|
-
// output) straight from the
|
|
5404
|
+
// output) straight from the GlobalTransform.world column array view. The
|
|
4658
5405
|
// record stage copies these 16 floats into the mesh SSBO with zero
|
|
4659
5406
|
// per-snapshot `mat4.compose` (AC-07). A stale slot (generation gone)
|
|
4660
5407
|
// skips the renderable, mirroring the Instances dangling-row sweep.
|
|
4661
5408
|
// tweak-20260611 M1 / K-3: `_getArrayView` call survives untouched;
|
|
4662
5409
|
// only the `entity` source switched to `bundle.Entity.self[i]`.
|
|
4663
|
-
const worldView = worldInternal._getArrayView(entity,
|
|
5410
|
+
const worldView = worldInternal._getArrayView(entity, GlobalTransform, 'world');
|
|
4664
5411
|
if (worldView === undefined) continue;
|
|
4665
5412
|
const worldMat = new Float32Array(worldView);
|
|
4666
5413
|
const transformSnap: TransformSnapshot = { world: worldMat };
|
|
@@ -4791,12 +5538,16 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4791
5538
|
}
|
|
4792
5539
|
|
|
4793
5540
|
let localAabb: Float32Array | undefined;
|
|
5541
|
+
let lods: readonly MeshLodLevel[] | undefined;
|
|
5542
|
+
let lodHysteresis: number | undefined;
|
|
4794
5543
|
let morph: MorphSnapshot | undefined;
|
|
4795
5544
|
const assetHandleRaw = Math.round(fAssetHandle ?? 0);
|
|
4796
5545
|
if (assetHandleRaw !== 0) {
|
|
4797
5546
|
const meshRes = resolveAssetHandle(world, toShared<'MeshAsset'>(assetHandleRaw));
|
|
4798
5547
|
if (meshRes.ok && meshRes.value.kind === 'mesh') {
|
|
4799
5548
|
const meshAsset = meshRes.value as MeshAsset;
|
|
5549
|
+
lods = meshAsset.lods;
|
|
5550
|
+
lodHysteresis = meshAsset.lodHysteresis;
|
|
4800
5551
|
morph = morphSnapshotFor(meshAsset, morphWeightsView);
|
|
4801
5552
|
const meshAabb = meshAsset.aabb;
|
|
4802
5553
|
// Morph targets can expand the authored bounds. Keep the
|
|
@@ -4841,41 +5592,95 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4841
5592
|
projection: identityProjection(),
|
|
4842
5593
|
} satisfies PointsLinesRetainedSnapshot);
|
|
4843
5594
|
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
5595
|
+
// Instances are snapshotted before the CPU visibility decision so the
|
|
5596
|
+
// culling owner can derive the union of every instance's transformed
|
|
5597
|
+
// mesh bounds. The same detached snapshot is then carried to record;
|
|
5598
|
+
// do not re-read the ECS array after culling (a structural write must
|
|
5599
|
+
// not make the cull and draw disagree within one frame).
|
|
5600
|
+
let instancesSnap: InstancesSnapshot | undefined;
|
|
5601
|
+
if (hasInstances) {
|
|
5602
|
+
const transforms = worldInternal._getArrayView(entity, Instances, 'transforms');
|
|
5603
|
+
if (transforms === undefined) {
|
|
5604
|
+
// A present component with an unavailable array is an empty
|
|
5605
|
+
// instance contribution, not an absent component. Preserve the
|
|
5606
|
+
// zero-draw contract so the record stage cannot synthesize one
|
|
5607
|
+
// identity instance from an incomplete row.
|
|
5608
|
+
instancesSnap = {
|
|
5609
|
+
transforms: new Float32Array(),
|
|
5610
|
+
instanceCount: 0,
|
|
5611
|
+
cacheKey: entity,
|
|
5612
|
+
archVersion,
|
|
5613
|
+
};
|
|
5614
|
+
} else {
|
|
5615
|
+
const actualLength = transforms.length;
|
|
5616
|
+
if (actualLength % 16 !== 0) {
|
|
5617
|
+
worldInternal._routeError(new InstanceTransformsStrideMismatchError(actualLength), {
|
|
5618
|
+
severity: Severity.Error,
|
|
5619
|
+
systemName: 'RenderSystem.extract (Instances stride)',
|
|
5620
|
+
});
|
|
5621
|
+
continue;
|
|
5622
|
+
}
|
|
5623
|
+
const snapshotCopy = new Float32Array(transforms);
|
|
5624
|
+
instancesSnap = {
|
|
5625
|
+
transforms: snapshotCopy,
|
|
5626
|
+
instanceCount: actualLength / 16,
|
|
5627
|
+
cacheKey: entity,
|
|
5628
|
+
archVersion,
|
|
5629
|
+
};
|
|
5630
|
+
}
|
|
5631
|
+
}
|
|
5632
|
+
|
|
4863
5633
|
const baseRenderable: RenderableSnapshot = {
|
|
4864
5634
|
assetHandle: Math.round(fAssetHandle ?? 0),
|
|
4865
5635
|
transform: transformSnap,
|
|
5636
|
+
...(lods === undefined ? {} : { lods }),
|
|
5637
|
+
...(lodHysteresis === undefined ? {} : { lodHysteresis }),
|
|
4866
5638
|
...(localAabb !== undefined ? { localAabb: cullingLocalAabb } : {}),
|
|
4867
5639
|
material: materialSnap,
|
|
4868
5640
|
materials: materialsArr,
|
|
4869
5641
|
materialBindingSources,
|
|
4870
5642
|
materialBindingDiagnostics,
|
|
4871
|
-
...(morph === undefined && gpuDrivenDraws.length > 0 ? { gpuDrivenDraws } : {}),
|
|
4872
5643
|
worldId: 0,
|
|
4873
5644
|
entityKey: entity,
|
|
4874
5645
|
...(skinSlice !== undefined ? { skin: skinSlice } : {}),
|
|
4875
5646
|
...(morph !== undefined ? { morph } : {}),
|
|
5647
|
+
...(instancesSnap !== undefined ? { instances: instancesSnap } : {}),
|
|
4876
5648
|
...(spriteInstancesSnap !== undefined ? { spriteInstances: spriteInstancesSnap } : {}),
|
|
4877
5649
|
...(pointsLines !== undefined ? { pointsLines } : {}),
|
|
4878
5650
|
};
|
|
5651
|
+
const gpuDrivenDraws = buildGpuDrivenDraws({
|
|
5652
|
+
submeshes: gpuDrivenSubmeshes,
|
|
5653
|
+
indexed: gpuDrivenIndexed,
|
|
5654
|
+
materials: materialsArr,
|
|
5655
|
+
fallbackMaterial: materialSnap,
|
|
5656
|
+
...(gpuDrivenGeometry === undefined || getMaterialShaderArtifact === undefined
|
|
5657
|
+
? {}
|
|
5658
|
+
: {
|
|
5659
|
+
prepare(draw, drawMaterial) {
|
|
5660
|
+
const materialShaderId = drawMaterial.materialShaderId;
|
|
5661
|
+
if (materialShaderId === undefined) return undefined;
|
|
5662
|
+
const artifact = getMaterialShaderArtifact(materialShaderId);
|
|
5663
|
+
if (artifact?.receipt === undefined) return undefined;
|
|
5664
|
+
const geometry: GpuDrivenGeometryReceipt = {
|
|
5665
|
+
...gpuDrivenGeometry,
|
|
5666
|
+
topology: draw.topology,
|
|
5667
|
+
indexed: draw.kind === 'indexed',
|
|
5668
|
+
};
|
|
5669
|
+
const result = resolvePreparedGpuDrivenDraw({
|
|
5670
|
+
snapshot: { ...baseRenderable, material: drawMaterial },
|
|
5671
|
+
artifact,
|
|
5672
|
+
geometry,
|
|
5673
|
+
generation: artifact.receipt.generation,
|
|
5674
|
+
draw,
|
|
5675
|
+
});
|
|
5676
|
+
return result.ok ? result.value : undefined;
|
|
5677
|
+
},
|
|
5678
|
+
}),
|
|
5679
|
+
});
|
|
5680
|
+
const renderable =
|
|
5681
|
+
morph === undefined && gpuDrivenDraws.length > 0
|
|
5682
|
+
? { ...baseRenderable, gpuDrivenDraws }
|
|
5683
|
+
: baseRenderable;
|
|
4879
5684
|
|
|
4880
5685
|
// feat-20260528-frustum-culling M3 / w10: frustum culling check.
|
|
4881
5686
|
// Skip the entity if a valid AABB exists AND ALL cameras' frusta
|
|
@@ -4884,66 +5689,70 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4884
5689
|
// producer-owned finite local-space AABBs. Culling is unconditional
|
|
4885
5690
|
// engine behavior; there is no per-entity opt-out.
|
|
4886
5691
|
if (localAabb !== undefined) {
|
|
4887
|
-
//
|
|
4888
|
-
//
|
|
4889
|
-
//
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
frustumCulled += 1;
|
|
4911
|
-
continue;
|
|
4912
|
-
}
|
|
4913
|
-
}
|
|
4914
|
-
|
|
4915
|
-
if (hasInstances) {
|
|
4916
|
-
const transforms = worldInternal._getArrayView(entity, Instances, 'transforms');
|
|
4917
|
-
if (transforms === undefined) {
|
|
4918
|
-
flushPendingDispatch(pendingDispatch);
|
|
4919
|
-
renderables.push(baseRenderable);
|
|
5692
|
+
// An empty or malformed Instances payload has no meaningful bounds.
|
|
5693
|
+
// Keep it conservative (the draw path later emits zero instances)
|
|
5694
|
+
// instead of manufacturing the entity's identity bounds. For a
|
|
5695
|
+
// valid payload, derive one world-space union for CPU culling while
|
|
5696
|
+
// leaving the mesh-local AABB untouched for GPU per-instance cull.
|
|
5697
|
+
const derivedInstancesBounds =
|
|
5698
|
+
instancesSnap === undefined || instancesSnap.instanceCount === 0
|
|
5699
|
+
? undefined
|
|
5700
|
+
: deriveInstancesUnionBounds({
|
|
5701
|
+
meshAabb: cullingLocalAabb,
|
|
5702
|
+
entityWorld: transformSnap.world,
|
|
5703
|
+
transforms: instancesSnap.transforms,
|
|
5704
|
+
});
|
|
5705
|
+
if (instancesSnap?.instanceCount === 0) {
|
|
5706
|
+
// No instance can contribute a visible bound. The retained
|
|
5707
|
+
// snapshot still records instanceCount=0 for the record stage.
|
|
5708
|
+
} else if (
|
|
5709
|
+
hasInstances &&
|
|
5710
|
+
(instancesSnap === undefined || derivedInstancesBounds === undefined)
|
|
5711
|
+
) {
|
|
5712
|
+
// Missing/malformed derived facts are a conservative no-cull
|
|
5713
|
+
// result. The stride validator above already rejects malformed
|
|
5714
|
+
// lengths; this branch covers non-finite producer matrices/AABB.
|
|
4920
5715
|
} else {
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
5716
|
+
// feat-20260601 D-3: cull AABB uses the resolved world mat4
|
|
5717
|
+
// directly (no compose) -- same source the record stage feeds
|
|
5718
|
+
// the mesh SSBO, so cull stays same-source with render (AC-05).
|
|
5719
|
+
const worldAabb = box3.create();
|
|
5720
|
+
if (derivedInstancesBounds !== undefined) {
|
|
5721
|
+
worldAabb[0] = derivedInstancesBounds[0] ?? 0;
|
|
5722
|
+
worldAabb[1] = derivedInstancesBounds[1] ?? 0;
|
|
5723
|
+
worldAabb[2] = derivedInstancesBounds[2] ?? 0;
|
|
5724
|
+
worldAabb[3] = derivedInstancesBounds[3] ?? 0;
|
|
5725
|
+
worldAabb[4] = derivedInstancesBounds[4] ?? 0;
|
|
5726
|
+
worldAabb[5] = derivedInstancesBounds[5] ?? 0;
|
|
5727
|
+
} else {
|
|
5728
|
+
box3.transformBox3(worldAabb, cullingLocalAabb, transformSnap.world);
|
|
5729
|
+
}
|
|
4927
5730
|
|
|
5731
|
+
// Test against all cameras. Entity is visible if one camera
|
|
5732
|
+
// frustum intersects the world-space AABB (or planes are empty
|
|
5733
|
+
// from degenerate projection).
|
|
5734
|
+
frustumTotal += 1;
|
|
5735
|
+
let visible = frustumPlanes.length === 0;
|
|
5736
|
+
for (let ci = 0; ci < frustumPlanes.length; ci++) {
|
|
5737
|
+
const planes = frustumPlanes[ci] as Float32Array;
|
|
5738
|
+
if (planes.length === 0) {
|
|
5739
|
+
visible = true;
|
|
5740
|
+
break;
|
|
5741
|
+
}
|
|
5742
|
+
if (frustum.intersectsBox(planes as frustum.Frustum, worldAabb as box3.Box3Like)) {
|
|
5743
|
+
visible = true;
|
|
5744
|
+
break;
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5747
|
+
if (!visible) {
|
|
5748
|
+
frustumCulled += 1;
|
|
4928
5749
|
continue;
|
|
4929
5750
|
}
|
|
4930
|
-
const snapshotCopy = new Float32Array(transforms);
|
|
4931
|
-
const instanceCount = Math.max(1, Math.floor(actualLength / 16));
|
|
4932
|
-
flushPendingDispatch(pendingDispatch);
|
|
4933
|
-
renderables.push({
|
|
4934
|
-
...baseRenderable,
|
|
4935
|
-
instances: {
|
|
4936
|
-
transforms: snapshotCopy,
|
|
4937
|
-
instanceCount,
|
|
4938
|
-
cacheKey: entity,
|
|
4939
|
-
archVersion,
|
|
4940
|
-
},
|
|
4941
|
-
});
|
|
4942
5751
|
}
|
|
4943
|
-
} else {
|
|
4944
|
-
flushPendingDispatch(pendingDispatch);
|
|
4945
|
-
renderables.push(baseRenderable);
|
|
4946
5752
|
}
|
|
5753
|
+
|
|
5754
|
+
flushPendingDispatch(pendingDispatch);
|
|
5755
|
+
renderables.push(renderable);
|
|
4947
5756
|
}
|
|
4948
5757
|
|
|
4949
5758
|
// feat-20260520-2d-sprite-layer-mvp M-3 / w22 + w25: finalise the
|
|
@@ -4978,10 +5787,12 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4978
5787
|
// whitePoint / tonemap` onto the SAME unified params channel custom
|
|
4979
5788
|
// post-processes use — `Camera.exposure` stays the AI-user-facing SSOT (D-5),
|
|
4980
5789
|
// the engine itself acts as the provider for the Standard tonemap shader
|
|
4981
|
-
// id. The 16B layout
|
|
4982
|
-
// (render-system-record.ts pre-w14): Float32 [exposure, whitePoint, _,
|
|
4983
|
-
// with the mode u32 occupying the third 4-byte slot via
|
|
4984
|
-
// camera.ts).
|
|
5790
|
+
// id. The 16B layout keeps the prior recordTonemapPass packing
|
|
5791
|
+
// (render-system-record.ts pre-w14): Float32 [exposure, whitePoint, _,
|
|
5792
|
+
// ditherEnabled], with the mode u32 occupying the third 4-byte slot via
|
|
5793
|
+
// tonemapToU32 (SSOT in camera.ts). The final Output Transform makes a
|
|
5794
|
+
// per-pass copy and writes ditherEnabled at byte offset 12; the extracted
|
|
5795
|
+
// camera snapshot remains the SSOT. Run AFTER the user-entity collection above so the engine's
|
|
4985
5796
|
// built-in provider is authoritative for its own reserved key (a user entity
|
|
4986
5797
|
// can never shadow the Standard tonemap identity. The single active camera mirrors
|
|
4987
5798
|
// recordFrame's `activeCameras[0]` selection.
|
|
@@ -4992,6 +5803,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4992
5803
|
|
|
4993
5804
|
return {
|
|
4994
5805
|
cameras,
|
|
5806
|
+
auxiliaryCameras,
|
|
5807
|
+
cubeCameras,
|
|
5808
|
+
reflectionProbes,
|
|
4995
5809
|
lights,
|
|
4996
5810
|
environment,
|
|
4997
5811
|
environmentReady: true,
|
|
@@ -5000,9 +5814,11 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
5000
5814
|
dispatch,
|
|
5001
5815
|
skylight,
|
|
5002
5816
|
skylightCount,
|
|
5817
|
+
lightProbes,
|
|
5003
5818
|
skybox,
|
|
5004
5819
|
skyboxCount,
|
|
5005
5820
|
fog,
|
|
5821
|
+
...(fogFailure === undefined ? {} : { fogFailure }),
|
|
5006
5822
|
frustumStats: { culled: frustumCulled, total: frustumTotal },
|
|
5007
5823
|
visibilityStats: { explicitlyHidden: explicitlyHidden.size },
|
|
5008
5824
|
postProcessParams,
|