@forgeax/engine-render 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/assembly/device-feature-admission.d.ts +18 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -0
- package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
- package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +21 -18
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +9 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/render-target-host.d.ts +46 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -0
- package/dist/assets/render-asset-port.d.ts.map +1 -1
- package/dist/authoring.mjs +3 -0
- package/dist/authoring.mjs.map +1 -1
- package/dist/capture/cube-views.d.ts +21 -0
- package/dist/capture/cube-views.d.ts.map +1 -0
- package/dist/capture/scheduler.d.ts +54 -0
- package/dist/capture/scheduler.d.ts.map +1 -0
- package/dist/{chunk-4XINDLAY.mjs → chunk-5YADUCA7.mjs} +1552 -1361
- package/dist/chunk-5YADUCA7.mjs.map +1 -0
- package/dist/{chunk-TBQJOK2L.mjs → chunk-6OPXDQ2E.mjs} +364 -51
- package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
- package/dist/{chunk-M2MP4ZOU.mjs → chunk-BXBQQKFQ.mjs} +3 -9
- package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
- package/dist/{chunk-CDTWTHZR.mjs → chunk-FS2KNUQH.mjs} +548 -6
- package/dist/chunk-FS2KNUQH.mjs.map +1 -0
- package/dist/components/camera.d.ts +1 -0
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/cube-camera.d.ts +20 -0
- package/dist/components/cube-camera.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +11 -2
- package/dist/components/directional-light.d.ts.map +1 -1
- package/dist/components/directional-shadow-filter.d.ts +25 -0
- package/dist/components/directional-shadow-filter.d.ts.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/reflection-probe.d.ts +17 -0
- package/dist/components/reflection-probe.d.ts.map +1 -0
- package/dist/construct-renderer.mjs +5826 -1003
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +5 -0
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/render.d.ts +141 -93
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/directional-shadow-projection.d.ts +23 -0
- package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
- package/dist/extract/environment.d.ts +14 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/extract/visibility.d.ts.map +1 -1
- package/dist/features/fullscreen.d.ts.map +1 -1
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +10 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +13 -2
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +0 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +19 -17
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +4 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/kernel-cache.d.ts +36 -0
- package/dist/ibl/kernel-cache.d.ts.map +1 -0
- package/dist/index.d.ts +28 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +51 -0
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +50 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -0
- package/dist/internal.mjs +1 -1
- package/dist/light-buffer-layout.d.ts +10 -1
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +54 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +14 -4
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts +2 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
- package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
- package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
- package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts +4 -13
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +5 -26
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +40 -7
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/point-shadow-inspection.d.ts +21 -0
- package/dist/point-shadow-inspection.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +27 -32
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +28 -14
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts +5 -0
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +10 -4
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
- package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
- package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/index.d.ts +8 -0
- package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
- package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/session.d.ts +43 -0
- package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
- package/dist/record/gpu-timing.d.ts +8 -3
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +22 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +2 -0
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +43 -2
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +72 -4
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +7 -4
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/reflection/filter.d.ts +22 -0
- package/dist/reflection/filter.d.ts.map +1 -0
- package/dist/reflection/gpu-table.d.ts +25 -0
- package/dist/reflection/gpu-table.d.ts.map +1 -0
- package/dist/reflection/projection.d.ts +91 -0
- package/dist/reflection/projection.d.ts.map +1 -0
- package/dist/render-contract.d.ts +49 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +42 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +58 -17
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +17 -2
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer.d.ts +43 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/scene/render-scene.d.ts +14 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/shadow-atlas.d.ts +3 -3
- package/dist/shadow-atlas.d.ts.map +1 -1
- package/dist/ssao-buffers.d.ts +3 -0
- package/dist/ssao-buffers.d.ts.map +1 -1
- package/dist/targets/contracts.d.ts +66 -0
- package/dist/targets/contracts.d.ts.map +1 -0
- package/dist/targets/graph-projection.d.ts +29 -0
- package/dist/targets/graph-projection.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +34 -0
- package/dist/targets/material-source.d.ts.map +1 -0
- package/dist/targets/owner.d.ts +32 -0
- package/dist/targets/owner.d.ts.map +1 -0
- package/dist/targets/physical.d.ts +18 -0
- package/dist/targets/physical.d.ts.map +1 -0
- package/dist/targets/readback.d.ts +39 -0
- package/dist/targets/readback.d.ts.map +1 -0
- package/dist/temporal/index.mjs +3 -3
- package/dist/typed-render-graph-primitives.d.ts +1 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +1 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +2 -2
- package/dist/volume/passes.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
- package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
- package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
- package/src/__tests__/cluster-binner.unit.test.ts +35 -0
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
- package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
- package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
- package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
- package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
- package/src/__tests__/cube-camera.integration.test.ts +42 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
- package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
- package/src/__tests__/factory-contract.integration.test.ts +255 -29
- package/src/__tests__/fog-component.unit.test.ts +67 -0
- package/src/__tests__/fog.integration.test.ts +86 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
- package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
- package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
- package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
- package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
- package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
- package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
- package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
- package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
- package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
- package/src/__tests__/instances-culling.integration.test.ts +94 -0
- package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
- package/src/__tests__/material-observation.integration.test.ts +101 -0
- package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
- package/src/__tests__/public-surface.test-d.ts +10 -0
- package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
- package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
- package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
- package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
- package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
- package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +18 -3
- package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
- package/src/__tests__/render-target-admission.unit.test.ts +80 -0
- package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
- package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
- package/src/__tests__/render-target-material-source.test-d.ts +30 -0
- package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
- package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
- package/src/__tests__/render-target-owner.unit.test.ts +122 -0
- package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
- package/src/__tests__/render-target-public-schema.json +107 -0
- package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
- package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
- package/src/__tests__/render-target-readback.integration.test.ts +90 -0
- package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +48 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
- package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
- package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +11 -2
- package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
- package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
- package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
- package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
- package/src/__tests__/standard-lighting.integration.test.ts +67 -0
- package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
- package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
- package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
- package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
- package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
- package/src/assembly/device-feature-admission.ts +73 -0
- package/src/assembly/directional-shadow-inspection.ts +122 -0
- package/src/assembly/factory.ts +877 -286
- package/src/assembly/host-contract.ts +25 -0
- package/src/assembly/render-target-host.ts +847 -0
- package/src/assets/asset-decoders.ts +1 -1
- package/src/assets/render-asset-port.ts +7 -1
- package/src/capture/cube-views.ts +69 -0
- package/src/capture/scheduler.ts +265 -0
- package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
- package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
- package/src/components/camera.ts +4 -0
- package/src/components/cube-camera.ts +52 -0
- package/src/components/directional-light.ts +22 -2
- package/src/components/directional-shadow-filter.ts +57 -0
- package/src/components/index.ts +2 -0
- package/src/components/light-helpers.ts +81 -7
- package/src/components/reflection-probe.ts +49 -0
- package/src/environment/frame.ts +6 -24
- package/src/environment/lifecycle.ts +12 -0
- package/src/errors/render.ts +286 -142
- package/src/extract/directional-shadow-projection.ts +61 -0
- package/src/extract/environment.ts +57 -0
- package/src/extract/visibility.ts +25 -1
- package/src/features/fullscreen.ts +3 -0
- package/src/features/targets.ts +4 -1
- package/src/features/types.ts +10 -0
- package/src/fullscreen-post-process-pass.ts +43 -2
- package/src/gpu-driven/batch-topology.ts +1 -0
- package/src/gpu-driven/production-raster.ts +1 -3
- package/src/gpu-scene.ts +8 -1
- package/src/hdrp-buffers.ts +165 -59
- package/src/ibl/IblPipelineCache.ts +11 -0
- package/src/ibl/kernel-cache.ts +88 -0
- package/src/index.ts +148 -0
- package/src/inspection-types.ts +56 -0
- package/src/instances-derived-bounds.ts +171 -0
- package/src/light-buffer-layout.ts +20 -5
- package/src/mesh-material-bindings.ts +132 -0
- package/src/pbr-pipeline.ts +106 -17
- package/src/pipeline/standard-forward-lane.ts +46 -16
- package/src/pipeline/standard-lighting/errors.ts +6 -0
- package/src/pipeline/standard-lighting/graph.ts +157 -0
- package/src/pipeline/standard-lighting/inspection.ts +88 -0
- package/src/pipeline/standard-lighting/layout.ts +31 -0
- package/src/pipeline/standard-lighting/light-frame.ts +34 -0
- package/src/pipeline/standard-lighting/membership.ts +43 -0
- package/src/pipeline/standard-lighting/prepare.ts +37 -0
- package/src/pipeline/standard-lighting/topology.ts +120 -0
- package/src/pipeline/standard-lighting/transport.ts +77 -0
- package/src/pipeline/standard-pipeline.ts +122 -193
- package/src/pipeline/standard-post.ts +26 -7
- package/src/pipeline/standard-profile.ts +17 -47
- package/src/pipeline-spec.ts +84 -21
- package/src/point-shadow-inspection.ts +55 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
- package/src/record/frame-lighting.ts +367 -427
- package/src/record/frame-snapshot.ts +35 -27
- package/src/record/frame-targets.ts +17 -0
- package/src/record/frame.ts +261 -74
- package/src/record/gpu-pass-timing/contract.ts +180 -0
- package/src/record/gpu-pass-timing/errors.ts +34 -0
- package/src/record/gpu-pass-timing/index.ts +37 -0
- package/src/record/gpu-pass-timing/parser.ts +97 -0
- package/src/record/gpu-pass-timing/session.ts +938 -0
- package/src/record/gpu-timing.ts +21 -12
- package/src/record/main-pass-geometry.ts +120 -80
- package/src/record/main-pass-material.ts +95 -9
- package/src/record/main-pass-sprite-draws.ts +54 -25
- package/src/record/main-pass.ts +71 -15
- package/src/record/render-context.ts +72 -0
- package/src/record/shadow-pass.ts +17 -28
- package/src/record/skybox-post-pass.ts +20 -4
- package/src/record/typed-frame-graph.ts +893 -44
- package/src/record/view-ubo.ts +63 -28
- package/src/reflection/filter.ts +81 -0
- package/src/reflection/gpu-table.ts +44 -0
- package/src/reflection/projection.ts +281 -0
- package/src/render-contract.ts +76 -5
- package/src/render-graph-primitives.ts +9 -4
- package/src/render-pipeline.ts +117 -2
- package/src/render-system-extract.ts +557 -100
- package/src/render-system.ts +1095 -66
- package/src/renderer.ts +222 -0
- package/src/scene/render-scene.ts +115 -9
- package/src/shadow-atlas.ts +5 -5
- package/src/ssao-buffers.ts +9 -2
- package/src/targets/contracts.ts +182 -0
- package/src/targets/graph-projection.ts +169 -0
- package/src/targets/material-source.ts +143 -0
- package/src/targets/owner.ts +214 -0
- package/src/targets/physical.ts +18 -0
- package/src/targets/readback.ts +153 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
- package/src/typed-render-graph-primitives.ts +5 -3
- package/src/typed-shadow-passes.ts +46 -40
- package/src/volume/passes.ts +15 -9
- package/dist/chunk-4XINDLAY.mjs.map +0 -1
- package/dist/chunk-CDTWTHZR.mjs.map +0 -1
- package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
- package/dist/chunk-TBQJOK2L.mjs.map +0 -1
package/src/renderer.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { err, ok } from '@forgeax/engine-types';
|
|
2
|
+
import type { RenderError } from './errors/render.js';
|
|
3
|
+
import { FrameReceiptStaleError } from './errors/render.js';
|
|
4
|
+
import type {
|
|
5
|
+
GpuPassTimingObservation,
|
|
6
|
+
GpuPassTimingReason,
|
|
7
|
+
GpuPassTimingRef,
|
|
8
|
+
} from './record/gpu-pass-timing/index.js';
|
|
9
|
+
import type {
|
|
10
|
+
FrameObservationRequest,
|
|
11
|
+
FrameReceipt,
|
|
12
|
+
FrameReceiptObservation,
|
|
13
|
+
RenderResult,
|
|
14
|
+
} from './render-contract.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Internal Render producer callback; public callers receive only frozen facts.
|
|
18
|
+
* The callback is receipt-bound and never exposes RHI handles or query state.
|
|
19
|
+
*/
|
|
20
|
+
export type GpuPassTimingObservationSource = () => Promise<GpuPassTimingObservation>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Receipt observation seam for the public `draw()` then `observe()` route.
|
|
24
|
+
* `status`, `completeness`, and `latestKnownGood` are separate projections;
|
|
25
|
+
* pass duration is not frame latency. Timing errors retain the closed code,
|
|
26
|
+
* expected, hint, and detail fields so a caller can follow producer recovery.
|
|
27
|
+
* See the bounded contract and fail-closed validator in the GPU pass timing
|
|
28
|
+
* record and benchmark modules.
|
|
29
|
+
*/
|
|
30
|
+
export interface GpuPassTimingObservationStore {
|
|
31
|
+
register(receipt: FrameReceipt, source?: GpuPassTimingObservationSource): void;
|
|
32
|
+
observe(
|
|
33
|
+
receipt: FrameReceipt,
|
|
34
|
+
request: FrameObservationRequest,
|
|
35
|
+
): Promise<RenderResult<FrameReceiptObservation, RenderError>>;
|
|
36
|
+
inspect(): {
|
|
37
|
+
readonly status: GpuPassTimingObservation['status'];
|
|
38
|
+
readonly completeness: 'complete' | 'partial' | 'unavailable' | 'failed';
|
|
39
|
+
readonly latestKnownGood?: GpuPassTimingRef | undefined;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Preserve the receipt observation contract without constructing the timing
|
|
45
|
+
* retention store when GPU timing was not opted in.
|
|
46
|
+
*/
|
|
47
|
+
export async function observeGpuPassTimingDisabled(
|
|
48
|
+
receipt: FrameReceipt,
|
|
49
|
+
request: FrameObservationRequest,
|
|
50
|
+
currentDeviceGeneration: () => number,
|
|
51
|
+
): Promise<RenderResult<FrameReceiptObservation, RenderError>> {
|
|
52
|
+
const completed = await receipt.completed;
|
|
53
|
+
if (!completed.ok) return completed;
|
|
54
|
+
if (receipt.deviceGeneration !== currentDeviceGeneration()) {
|
|
55
|
+
return err(
|
|
56
|
+
new FrameReceiptStaleError({
|
|
57
|
+
frameId: receipt.frameId,
|
|
58
|
+
receiptGeneration: receipt.deviceGeneration,
|
|
59
|
+
currentGeneration: currentDeviceGeneration(),
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const base = {
|
|
64
|
+
frameId: receipt.frameId,
|
|
65
|
+
deviceGeneration: receipt.deviceGeneration,
|
|
66
|
+
include: freeze([...request.include]),
|
|
67
|
+
} as const;
|
|
68
|
+
if (!request.include.includes('timings')) return ok(freeze(base));
|
|
69
|
+
return ok(freeze({ ...base, timings: notEnabled }));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface ReceiptRecord {
|
|
73
|
+
readonly source: GpuPassTimingObservationSource;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface ObservationStoreOptions {
|
|
77
|
+
readonly retentionFrames: number;
|
|
78
|
+
readonly currentDeviceGeneration: () => number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const notEnabled: GpuPassTimingObservation = {
|
|
82
|
+
status: 'unavailable',
|
|
83
|
+
reason: {
|
|
84
|
+
code: 'gpu-timing-not-enabled',
|
|
85
|
+
expected: 'gpuPassTiming is enabled in RendererOptions',
|
|
86
|
+
hint: 'set RendererOptions.gpuPassTiming to opt into bounded timing facts',
|
|
87
|
+
detail: {},
|
|
88
|
+
},
|
|
89
|
+
capability: { timestampQuery: false, timestampPeriodNanoseconds: null },
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function failedObservation(
|
|
93
|
+
error: GpuPassTimingReason,
|
|
94
|
+
latestKnownGood?: GpuPassTimingRef,
|
|
95
|
+
): GpuPassTimingObservation {
|
|
96
|
+
return {
|
|
97
|
+
status: 'failed',
|
|
98
|
+
error,
|
|
99
|
+
...(latestKnownGood === undefined ? {} : { latestKnownGood }),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function retentionExpired(): GpuPassTimingObservation {
|
|
104
|
+
return failedObservation({
|
|
105
|
+
code: 'timing-retention-expired',
|
|
106
|
+
expected: 'the requested receipt remains inside the bounded timing retention window',
|
|
107
|
+
hint: 'observe a newer receipt before the retention window expires',
|
|
108
|
+
detail: {},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function freeze<T>(value: T): T {
|
|
113
|
+
if (value === null || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
114
|
+
Object.freeze(value);
|
|
115
|
+
for (const child of Object.values(value as Record<string, unknown>)) freeze(child);
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Keep receipt-bound observations in a bounded Render-owned retention ring.
|
|
121
|
+
* This helper does not expose RHI handles, query objects, or a timing
|
|
122
|
+
* controller; the public lifecycle remains `draw` then `observe`.
|
|
123
|
+
* Omitting `timings` from the observation request does not materialize facts.
|
|
124
|
+
*/
|
|
125
|
+
export function createGpuPassTimingObservationStore(
|
|
126
|
+
options: ObservationStoreOptions,
|
|
127
|
+
): GpuPassTimingObservationStore {
|
|
128
|
+
const records = new Map<FrameReceipt, ReceiptRecord>();
|
|
129
|
+
const order: FrameReceipt[] = [];
|
|
130
|
+
const expired = new WeakSet<FrameReceipt>();
|
|
131
|
+
let latestKnownGood: GpuPassTimingRef | undefined;
|
|
132
|
+
let currentStatus: GpuPassTimingObservation['status'] = 'unavailable';
|
|
133
|
+
let currentCompleteness: 'complete' | 'partial' | 'unavailable' | 'failed' = 'unavailable';
|
|
134
|
+
|
|
135
|
+
const evict = (): void => {
|
|
136
|
+
while (order.length > options.retentionFrames) {
|
|
137
|
+
const old = order.shift();
|
|
138
|
+
if (old === undefined) break;
|
|
139
|
+
records.delete(old);
|
|
140
|
+
expired.add(old);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
register(receipt, source): void {
|
|
146
|
+
records.set(receipt, { source: source ?? (async () => notEnabled) });
|
|
147
|
+
order.push(receipt);
|
|
148
|
+
evict();
|
|
149
|
+
},
|
|
150
|
+
async observe(receipt, request): Promise<RenderResult<FrameReceiptObservation, RenderError>> {
|
|
151
|
+
const record = records.get(receipt);
|
|
152
|
+
if (record === undefined) {
|
|
153
|
+
if (expired.has(receipt) && request.include.includes('timings')) {
|
|
154
|
+
return ok(
|
|
155
|
+
freeze({
|
|
156
|
+
frameId: receipt.frameId,
|
|
157
|
+
deviceGeneration: receipt.deviceGeneration,
|
|
158
|
+
include: freeze([...request.include]),
|
|
159
|
+
timings: retentionExpired(),
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return err(
|
|
164
|
+
new FrameReceiptStaleError({
|
|
165
|
+
frameId: receipt.frameId,
|
|
166
|
+
receiptGeneration: receipt.deviceGeneration,
|
|
167
|
+
currentGeneration: options.currentDeviceGeneration(),
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
const completed = await receipt.completed;
|
|
172
|
+
if (!completed.ok) return completed;
|
|
173
|
+
if (receipt.deviceGeneration !== options.currentDeviceGeneration()) {
|
|
174
|
+
return err(
|
|
175
|
+
new FrameReceiptStaleError({
|
|
176
|
+
frameId: receipt.frameId,
|
|
177
|
+
receiptGeneration: receipt.deviceGeneration,
|
|
178
|
+
currentGeneration: options.currentDeviceGeneration(),
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const base = {
|
|
183
|
+
frameId: receipt.frameId,
|
|
184
|
+
deviceGeneration: receipt.deviceGeneration,
|
|
185
|
+
include: freeze([...request.include]),
|
|
186
|
+
} as const;
|
|
187
|
+
if (!request.include.includes('timings')) return ok(freeze(base));
|
|
188
|
+
let timings: GpuPassTimingObservation;
|
|
189
|
+
try {
|
|
190
|
+
timings = await record.source();
|
|
191
|
+
} catch (cause) {
|
|
192
|
+
timings = failedObservation(
|
|
193
|
+
{
|
|
194
|
+
code: 'timestamp-readback-failed',
|
|
195
|
+
expected: 'the Render-owned timing observation completes without throwing',
|
|
196
|
+
hint: 'observe a later receipt and inspect latestKnownGood',
|
|
197
|
+
detail: { frameId: receipt.frameId },
|
|
198
|
+
cause: { message: cause instanceof Error ? cause.message : String(cause) },
|
|
199
|
+
},
|
|
200
|
+
latestKnownGood,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
currentStatus = timings.status;
|
|
204
|
+
currentCompleteness = timings.status;
|
|
205
|
+
if (timings.status === 'complete') {
|
|
206
|
+
latestKnownGood = {
|
|
207
|
+
frameId: timings.frame.frameId,
|
|
208
|
+
deviceGeneration: timings.frame.deviceGeneration,
|
|
209
|
+
graphGeneration: timings.frame.graphGeneration,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return ok(freeze({ ...base, timings }));
|
|
213
|
+
},
|
|
214
|
+
inspect() {
|
|
215
|
+
return freeze({
|
|
216
|
+
status: currentStatus,
|
|
217
|
+
completeness: currentCompleteness,
|
|
218
|
+
latestKnownGood,
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
PointLight,
|
|
17
17
|
PointLightShadow,
|
|
18
18
|
PostProcessParams,
|
|
19
|
+
ReflectionProbe,
|
|
19
20
|
SkyboxBackground,
|
|
20
21
|
Skylight,
|
|
21
22
|
SpotLight,
|
|
@@ -30,8 +31,14 @@ import {
|
|
|
30
31
|
} from '../gpu-driven/batch-topology';
|
|
31
32
|
import { GpuScene } from '../gpu-scene';
|
|
32
33
|
import type { PersistentRenderSceneInspection } from '../inspection-types';
|
|
34
|
+
import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
|
|
33
35
|
import type { PointsLinesInspection } from '../points-lines/inspection';
|
|
34
36
|
import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
|
|
37
|
+
import {
|
|
38
|
+
type ReflectionProbeFact,
|
|
39
|
+
ReflectionProbeProjection,
|
|
40
|
+
type ReflectionProbeSelectionResult,
|
|
41
|
+
} from '../reflection/projection';
|
|
35
42
|
import type { CameraSnapshot } from '../render-contract';
|
|
36
43
|
import type {
|
|
37
44
|
DispatchEntry,
|
|
@@ -220,6 +227,22 @@ function worldBounds(snapshot: RenderableSnapshot): RenderSceneBounds | undefine
|
|
|
220
227
|
return { min, max };
|
|
221
228
|
}
|
|
222
229
|
|
|
230
|
+
function boundsFromArray(bounds: ArrayLike<number>): RenderSceneBounds | undefined {
|
|
231
|
+
if (bounds.length < 6) return undefined;
|
|
232
|
+
const min: [number, number, number] = [Number(bounds[0]), Number(bounds[1]), Number(bounds[2])];
|
|
233
|
+
const max: [number, number, number] = [Number(bounds[3]), Number(bounds[4]), Number(bounds[5])];
|
|
234
|
+
if (
|
|
235
|
+
!min.every(Number.isFinite) ||
|
|
236
|
+
!max.every(Number.isFinite) ||
|
|
237
|
+
min[0] > max[0] ||
|
|
238
|
+
min[1] > max[1] ||
|
|
239
|
+
min[2] > max[2]
|
|
240
|
+
) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
return { min, max };
|
|
244
|
+
}
|
|
245
|
+
|
|
223
246
|
/**
|
|
224
247
|
* The renderer's single rebuildable CPU scene authority.
|
|
225
248
|
*
|
|
@@ -241,6 +264,7 @@ export class RenderScene {
|
|
|
241
264
|
// derived value by snapshot identity so repeated view queries do not redo
|
|
242
265
|
// the eight-corner transform for every view in the same frame.
|
|
243
266
|
private readonly worldBoundsCache = new WeakMap<RenderableSnapshot, RenderSceneBounds | null>();
|
|
267
|
+
private readonly instanceBoundsCache = new InstanceBoundsCache();
|
|
244
268
|
private orderedSlots: number[] = [];
|
|
245
269
|
private materialized: readonly RenderableSnapshot[] | undefined;
|
|
246
270
|
private slotSnapshot: readonly RenderSceneSlot[] | undefined;
|
|
@@ -552,11 +576,7 @@ export class RenderScene {
|
|
|
552
576
|
const record = this.slots[slot];
|
|
553
577
|
if (record === undefined) continue;
|
|
554
578
|
const snapshot = record.snapshot;
|
|
555
|
-
|
|
556
|
-
if (candidate === undefined && !this.worldBoundsCache.has(snapshot)) {
|
|
557
|
-
candidate = worldBounds(snapshot) ?? null;
|
|
558
|
-
this.worldBoundsCache.set(snapshot, candidate);
|
|
559
|
-
}
|
|
579
|
+
const candidate = this.cullingWorldBounds(snapshot);
|
|
560
580
|
if (candidate === null || candidate === undefined) continue;
|
|
561
581
|
if (intersects(candidate, bounds)) records.push(record);
|
|
562
582
|
}
|
|
@@ -629,6 +649,7 @@ export class RenderScene {
|
|
|
629
649
|
|
|
630
650
|
private release(record: RenderSceneSlot): void {
|
|
631
651
|
this.slots[record.slot] = undefined;
|
|
652
|
+
this.instanceBoundsCache.invalidate(record.entityKey, record.worldId);
|
|
632
653
|
this.unindexMaterials(record);
|
|
633
654
|
const entities = this.slotsByWorld.get(record.worldId);
|
|
634
655
|
entities?.delete(record.entityKey);
|
|
@@ -706,6 +727,41 @@ export class RenderScene {
|
|
|
706
727
|
this.materialized = undefined;
|
|
707
728
|
this.slotSnapshot = undefined;
|
|
708
729
|
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Return the CPU culling projection for one retained snapshot. Instances
|
|
733
|
+
* use a renderer-derived union, while the GPU scene keeps the mesh-local
|
|
734
|
+
* bounds for its independent per-instance visibility pass. Unknown or
|
|
735
|
+
* empty instance facts deliberately return undefined (conservative no-cull).
|
|
736
|
+
*/
|
|
737
|
+
cullingWorldBounds(snapshot: RenderableSnapshot): RenderSceneBounds | undefined {
|
|
738
|
+
let candidate = this.worldBoundsCache.get(snapshot);
|
|
739
|
+
if (candidate !== undefined || this.worldBoundsCache.has(snapshot)) {
|
|
740
|
+
return candidate ?? undefined;
|
|
741
|
+
}
|
|
742
|
+
if (snapshot.instances !== undefined) {
|
|
743
|
+
const instanceCount = snapshot.instances.instanceCount;
|
|
744
|
+
if (instanceCount === 0 || snapshot.localAabb === undefined) {
|
|
745
|
+
this.worldBoundsCache.set(snapshot, null);
|
|
746
|
+
return undefined;
|
|
747
|
+
}
|
|
748
|
+
const derived = this.instanceBoundsCache.get({
|
|
749
|
+
worldId: snapshot.worldId,
|
|
750
|
+
entityKey: snapshot.entityKey,
|
|
751
|
+
meshGeneration: fingerprintNumericArray(snapshot.localAabb) ^ (snapshot.assetHandle >>> 0),
|
|
752
|
+
transformGeneration: fingerprintNumericArray(snapshot.transform.world),
|
|
753
|
+
matrixGeneration: snapshot.instances.archVersion,
|
|
754
|
+
meshAabb: snapshot.localAabb,
|
|
755
|
+
entityWorld: snapshot.transform.world,
|
|
756
|
+
transforms: snapshot.instances.transforms,
|
|
757
|
+
});
|
|
758
|
+
candidate = boundsFromArray(derived ?? []);
|
|
759
|
+
} else {
|
|
760
|
+
candidate = worldBounds(snapshot);
|
|
761
|
+
}
|
|
762
|
+
this.worldBoundsCache.set(snapshot, candidate ?? null);
|
|
763
|
+
return candidate;
|
|
764
|
+
}
|
|
709
765
|
}
|
|
710
766
|
|
|
711
767
|
function readTransformWorld(world: World, entity: EntityHandle): Float32Array | undefined {
|
|
@@ -762,6 +818,7 @@ const RENDER_RELEVANT_COMPONENT_IDS = new Set([
|
|
|
762
818
|
componentId(Skylight),
|
|
763
819
|
componentId(SkyboxBackground),
|
|
764
820
|
componentId(PostProcessParams),
|
|
821
|
+
componentId(ReflectionProbe),
|
|
765
822
|
componentId(VolumetricFog),
|
|
766
823
|
]);
|
|
767
824
|
|
|
@@ -810,6 +867,7 @@ function cameraFrusta(cameras: readonly CameraSnapshot[]): readonly Float32Array
|
|
|
810
867
|
function cullPersistentFrame(
|
|
811
868
|
frame: ExtractedFrame,
|
|
812
869
|
candidates: readonly RenderableSnapshot[],
|
|
870
|
+
boundsOf: (snapshot: RenderableSnapshot) => RenderSceneBounds | undefined = worldBounds,
|
|
813
871
|
): ExtractedFrame {
|
|
814
872
|
const planes = cameraFrusta(frame.cameras);
|
|
815
873
|
const visible: RenderableSnapshot[] = [];
|
|
@@ -822,10 +880,16 @@ function cullPersistentFrame(
|
|
|
822
880
|
const candidate = candidates[index];
|
|
823
881
|
if (candidate === undefined) continue;
|
|
824
882
|
let isVisible = true;
|
|
825
|
-
|
|
883
|
+
const candidateBounds = boundsOf(candidate);
|
|
884
|
+
if (candidateBounds !== undefined) {
|
|
826
885
|
total += 1;
|
|
827
886
|
const worldAabb = box3.create();
|
|
828
|
-
|
|
887
|
+
worldAabb[0] = candidateBounds.min[0] ?? 0;
|
|
888
|
+
worldAabb[1] = candidateBounds.min[1] ?? 0;
|
|
889
|
+
worldAabb[2] = candidateBounds.min[2] ?? 0;
|
|
890
|
+
worldAabb[3] = candidateBounds.max[0] ?? 0;
|
|
891
|
+
worldAabb[4] = candidateBounds.max[1] ?? 0;
|
|
892
|
+
worldAabb[5] = candidateBounds.max[2] ?? 0;
|
|
829
893
|
isVisible = planes.length === 0;
|
|
830
894
|
for (const cameraPlanes of planes) {
|
|
831
895
|
if (
|
|
@@ -879,6 +943,7 @@ export class PersistentRenderScene {
|
|
|
879
943
|
| { readonly projection: RenderScene; readonly capture: RenderSceneSubmissionCapture }
|
|
880
944
|
| undefined;
|
|
881
945
|
private pointsLinesInspections: readonly PointsLinesInspection[] = [];
|
|
946
|
+
private readonly reflectionProbes = new ReflectionProbeProjection();
|
|
882
947
|
|
|
883
948
|
constructor(private readonly options: PersistentRenderSceneOptions = {}) {}
|
|
884
949
|
|
|
@@ -887,6 +952,37 @@ export class PersistentRenderScene {
|
|
|
887
952
|
return this.materialSnapshotCaches;
|
|
888
953
|
}
|
|
889
954
|
|
|
955
|
+
/** Project probe facts and per-primitive selection into the persistent scene owner. */
|
|
956
|
+
projectReflectionProbes(probes: readonly ReflectionProbeFact[]): void {
|
|
957
|
+
const projection = this.composition?.projection;
|
|
958
|
+
if (projection === undefined) return;
|
|
959
|
+
const primitives = projection
|
|
960
|
+
.slotsSnapshot()
|
|
961
|
+
.map((slot) => {
|
|
962
|
+
const resolved = worldBounds(slot.snapshot);
|
|
963
|
+
if (resolved === undefined || resolved === null) return undefined;
|
|
964
|
+
return {
|
|
965
|
+
worldId: slot.worldId,
|
|
966
|
+
entityKey: slot.entityKey,
|
|
967
|
+
center: [
|
|
968
|
+
(resolved.min[0] + resolved.max[0]) / 2,
|
|
969
|
+
(resolved.min[1] + resolved.max[1]) / 2,
|
|
970
|
+
(resolved.min[2] + resolved.max[2]) / 2,
|
|
971
|
+
] as [number, number, number],
|
|
972
|
+
};
|
|
973
|
+
})
|
|
974
|
+
.filter((primitive): primitive is NonNullable<typeof primitive> => primitive !== undefined);
|
|
975
|
+
this.reflectionProbes.update(probes, primitives);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
reflectionProbeSelection(worldId: number, entityKey: number): ReflectionProbeSelectionResult {
|
|
979
|
+
return this.reflectionProbes.selection(worldId, entityKey);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
reflectionProbeProjection(): ReturnType<ReflectionProbeProjection['snapshot']> {
|
|
983
|
+
return this.reflectionProbes.snapshot();
|
|
984
|
+
}
|
|
985
|
+
|
|
890
986
|
extractComposition(
|
|
891
987
|
worlds: readonly World[],
|
|
892
988
|
owner: { readonly cameraOwner: number; readonly resourceOwner: number },
|
|
@@ -1195,6 +1291,7 @@ export class PersistentRenderScene {
|
|
|
1195
1291
|
transmissionDemand.apply(transmissionDemandOperations(candidateFrame.renderables));
|
|
1196
1292
|
const topology = new BatchTopology();
|
|
1197
1293
|
topology.rebuild(projection.slotsSnapshot());
|
|
1294
|
+
this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
|
|
1198
1295
|
const token = {};
|
|
1199
1296
|
const entry: PersistentCompositionEntry = {
|
|
1200
1297
|
token,
|
|
@@ -1224,7 +1321,11 @@ export class PersistentRenderScene {
|
|
|
1224
1321
|
const candidates = projection
|
|
1225
1322
|
.materialize()
|
|
1226
1323
|
.map((snapshot) => projection.withTemporal(snapshot));
|
|
1227
|
-
return cullPersistentFrame(
|
|
1324
|
+
return cullPersistentFrame(
|
|
1325
|
+
{ ...candidateFrame, renderables: [...candidates] },
|
|
1326
|
+
candidates,
|
|
1327
|
+
(snapshot) => projection.cullingWorldBounds(snapshot),
|
|
1328
|
+
);
|
|
1228
1329
|
}
|
|
1229
1330
|
|
|
1230
1331
|
/** Internal topology fact; the renderer does not expose demand as public API. */
|
|
@@ -1246,10 +1347,15 @@ export class PersistentRenderScene {
|
|
|
1246
1347
|
buildCandidateFrame: () => ExtractedFrame,
|
|
1247
1348
|
): ExtractedFrame {
|
|
1248
1349
|
const candidateFrame = buildCandidateFrame();
|
|
1350
|
+
this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
|
|
1249
1351
|
const candidates = entry.projection
|
|
1250
1352
|
.materialize()
|
|
1251
1353
|
.map((snapshot) => entry.projection.withTemporal(snapshot));
|
|
1252
|
-
return cullPersistentFrame(
|
|
1354
|
+
return cullPersistentFrame(
|
|
1355
|
+
{ ...candidateFrame, renderables: [...candidates] },
|
|
1356
|
+
candidates,
|
|
1357
|
+
(snapshot) => entry.projection.cullingWorldBounds(snapshot),
|
|
1358
|
+
);
|
|
1253
1359
|
}
|
|
1254
1360
|
|
|
1255
1361
|
private syncGpuOwner(
|
package/src/shadow-atlas.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// feat-20260612-point-light-shadows-urp-hdrp M3 / T-M3-1 (plan-strategy §D-1).
|
|
4
4
|
//
|
|
5
5
|
// Lazy-allocated `texture_depth_cube_array` (depth32float) + per-face 2D
|
|
6
|
-
// TextureView cache. One layer per shadow-casting PointLight
|
|
7
|
-
//
|
|
6
|
+
// TextureView cache. One layer per admitted shadow-casting PointLight; the
|
|
7
|
+
// renderer owns the bounded atlas admission policy. Total texel footprint at
|
|
8
8
|
// the 512x512 default = 4 layers x 6 faces x 512 x 512 x 4 B = 24 MiB.
|
|
9
9
|
//
|
|
10
10
|
// Why a separate module (vs inlining into createRenderer / pipelineState):
|
|
@@ -53,19 +53,19 @@ import { GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING } from './gpu-t
|
|
|
53
53
|
export interface ShadowAtlasOptions {
|
|
54
54
|
/** Per-face square size in pixels. Default 512 (PointLightShadow.mapSize default). */
|
|
55
55
|
readonly faceSize?: number;
|
|
56
|
-
/** Number of cube layers. Default
|
|
56
|
+
/** Number of cube layers. Default is the renderer atlas admission capacity. */
|
|
57
57
|
readonly layers?: number;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/** Default per-face square size in pixels. Mirrors PointLightShadow.mapSize default. */
|
|
61
61
|
export const SHADOW_ATLAS_DEFAULT_FACE_SIZE = 512;
|
|
62
|
-
/** Default cube layer count.
|
|
62
|
+
/** Default cube layer count. Renderer-owned point-shadow admission capacity. */
|
|
63
63
|
export const SHADOW_ATLAS_DEFAULT_LAYERS = 4;
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Cube-array depth atlas for point-light shadows.
|
|
67
67
|
*
|
|
68
|
-
* Shape: `texture_depth_cube_array` (depth32float) with `layers
|
|
68
|
+
* Shape: `texture_depth_cube_array` (depth32float) with `layers` and per-
|
|
69
69
|
* face size `faceSize x faceSize`. The cube_array sampling view feeds
|
|
70
70
|
* `@group(0) @binding(5)` in URP shaders; the per-face 2D view feeds the
|
|
71
71
|
* shadow-caster render pass attachment for one (layer, face) at a time
|
package/src/ssao-buffers.ts
CHANGED
|
@@ -106,6 +106,7 @@ export const SSAO_UNIFORM_BYTES = UNIFORM_BYTES;
|
|
|
106
106
|
// recompile across enable/disable.
|
|
107
107
|
|
|
108
108
|
interface SsaoFallbackResources {
|
|
109
|
+
readonly device: RenderSystemRuntime['device'];
|
|
109
110
|
readonly texture: Texture;
|
|
110
111
|
readonly view: TextureView;
|
|
111
112
|
readonly sampler: Sampler;
|
|
@@ -126,7 +127,8 @@ export function getOrCreateSsaoFallbackTexture(
|
|
|
126
127
|
runtime: RenderSystemRuntime,
|
|
127
128
|
): SsaoFallbackResources | null {
|
|
128
129
|
const cached = fallbackCache.get(runtime);
|
|
129
|
-
if (cached !== undefined) return cached;
|
|
130
|
+
if (cached !== undefined && cached.device === runtime.device) return cached;
|
|
131
|
+
if (cached !== undefined) fallbackCache.delete(runtime);
|
|
130
132
|
|
|
131
133
|
const device = runtime.device;
|
|
132
134
|
const texRes = device.createTexture({
|
|
@@ -191,6 +193,7 @@ export function getOrCreateSsaoFallbackTexture(
|
|
|
191
193
|
}
|
|
192
194
|
|
|
193
195
|
const resources: SsaoFallbackResources = {
|
|
196
|
+
device,
|
|
194
197
|
texture: texRes.value,
|
|
195
198
|
view: viewRes.value,
|
|
196
199
|
sampler: samplerRes.value,
|
|
@@ -205,6 +208,8 @@ export function getOrCreateSsaoFallbackTexture(
|
|
|
205
208
|
* Fields are readonly per charter P5: one resource, one owner.
|
|
206
209
|
*/
|
|
207
210
|
export interface SsaoBuffers {
|
|
211
|
+
/** Device identity that owns every handle in this bundle. */
|
|
212
|
+
readonly device: RenderSystemRuntime['device'];
|
|
208
213
|
/** 64 padded vec4 uniform buffer for hemisphere samples. */
|
|
209
214
|
readonly kernelBuffer: Buffer;
|
|
210
215
|
readonly kernelBytes: number;
|
|
@@ -229,7 +234,8 @@ const cache = new WeakMap<RenderSystemRuntime, SsaoBuffers>();
|
|
|
229
234
|
*/
|
|
230
235
|
export function getOrCreateSsaoBuffers(runtime: RenderSystemRuntime): SsaoBuffers | null {
|
|
231
236
|
const cached = cache.get(runtime);
|
|
232
|
-
if (cached !== undefined) return cached;
|
|
237
|
+
if (cached !== undefined && cached.device === runtime.device) return cached;
|
|
238
|
+
if (cached !== undefined) cache.delete(runtime);
|
|
233
239
|
|
|
234
240
|
const device = runtime.device;
|
|
235
241
|
|
|
@@ -322,6 +328,7 @@ export function getOrCreateSsaoBuffers(runtime: RenderSystemRuntime): SsaoBuffer
|
|
|
322
328
|
}
|
|
323
329
|
|
|
324
330
|
const buffers: SsaoBuffers = {
|
|
331
|
+
device,
|
|
325
332
|
kernelBuffer: kernelBufferRes.value,
|
|
326
333
|
kernelBytes: SSAO_KERNEL_SAMPLE_COUNT * BYTES_PER_KERNEL_ELEMENT,
|
|
327
334
|
noiseTexture: noiseTexRes.value,
|