@forgeax/engine-render 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/assembly/device-feature-admission.d.ts +18 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -0
- package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
- package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +34 -20
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +9 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/render-target-host.d.ts +46 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -0
- package/dist/assets/render-asset-port.d.ts.map +1 -1
- package/dist/authoring.mjs +3 -0
- package/dist/authoring.mjs.map +1 -1
- package/dist/capture/cube-views.d.ts +21 -0
- package/dist/capture/cube-views.d.ts.map +1 -0
- package/dist/capture/scheduler.d.ts +54 -0
- package/dist/capture/scheduler.d.ts.map +1 -0
- package/dist/{chunk-RJRUS55L.mjs → chunk-5YADUCA7.mjs} +1896 -1624
- package/dist/chunk-5YADUCA7.mjs.map +1 -0
- package/dist/{chunk-LOSO4CTV.mjs → chunk-6OPXDQ2E.mjs} +423 -46
- package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
- package/dist/{chunk-IPXCABEP.mjs → chunk-BXBQQKFQ.mjs} +3 -9
- package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
- package/dist/chunk-FS2KNUQH.mjs +1294 -0
- package/dist/chunk-FS2KNUQH.mjs.map +1 -0
- package/dist/components/camera.d.ts +1 -0
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/cube-camera.d.ts +20 -0
- package/dist/components/cube-camera.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +11 -2
- package/dist/components/directional-light.d.ts.map +1 -1
- package/dist/components/directional-shadow-filter.d.ts +25 -0
- package/dist/components/directional-shadow-filter.d.ts.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/reflection-probe.d.ts +17 -0
- package/dist/components/reflection-probe.d.ts.map +1 -0
- package/dist/components/spot-light.d.ts +12 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +8552 -1612
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +19 -2
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +5 -0
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/render.d.ts +209 -93
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/directional-shadow-projection.d.ts +23 -0
- package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
- package/dist/extract/environment.d.ts +14 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/extract/visibility.d.ts.map +1 -1
- package/dist/features/fullscreen.d.ts.map +1 -1
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +10 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +13 -2
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +0 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +2 -0
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +19 -17
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +4 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/kernel-cache.d.ts +36 -0
- package/dist/ibl/kernel-cache.d.ts.map +1 -0
- package/dist/index.d.ts +35 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +51 -0
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +50 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -0
- package/dist/internal.mjs +1 -1
- package/dist/light-buffer-layout.d.ts +30 -13
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +54 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +20 -7
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts +2 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
- package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
- package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
- package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts +4 -13
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +12 -20
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +40 -7
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/point-shadow-inspection.d.ts +21 -0
- package/dist/point-shadow-inspection.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +29 -32
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +65 -15
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts +5 -0
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +12 -5
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
- package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
- package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/index.d.ts +8 -0
- package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
- package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/session.d.ts +43 -0
- package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
- package/dist/record/gpu-timing.d.ts +37 -0
- package/dist/record/gpu-timing.d.ts.map +1 -0
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +23 -2
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +2 -0
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +48 -3
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +81 -5
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +7 -4
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/reflection/filter.d.ts +22 -0
- package/dist/reflection/filter.d.ts.map +1 -0
- package/dist/reflection/gpu-table.d.ts +25 -0
- package/dist/reflection/gpu-table.d.ts.map +1 -0
- package/dist/reflection/projection.d.ts +91 -0
- package/dist/reflection/projection.d.ts.map +1 -0
- package/dist/render-contract.d.ts +114 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-data.d.ts +10 -6
- package/dist/render-data.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +78 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +113 -20
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +24 -3
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer-inspect.d.ts +29 -0
- package/dist/renderer-inspect.d.ts.map +1 -0
- package/dist/renderer.d.ts +43 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/scene/render-scene.d.ts +14 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/shadow-atlas.d.ts +3 -3
- package/dist/shadow-atlas.d.ts.map +1 -1
- package/dist/ssao-buffers.d.ts +3 -0
- package/dist/ssao-buffers.d.ts.map +1 -1
- package/dist/targets/contracts.d.ts +66 -0
- package/dist/targets/contracts.d.ts.map +1 -0
- package/dist/targets/graph-projection.d.ts +29 -0
- package/dist/targets/graph-projection.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +34 -0
- package/dist/targets/material-source.d.ts.map +1 -0
- package/dist/targets/owner.d.ts +32 -0
- package/dist/targets/owner.d.ts.map +1 -0
- package/dist/targets/physical.d.ts +18 -0
- package/dist/targets/physical.d.ts.map +1 -0
- package/dist/targets/readback.d.ts +39 -0
- package/dist/targets/readback.d.ts.map +1 -0
- package/dist/temporal/frame.d.ts +44 -0
- package/dist/temporal/frame.d.ts.map +1 -0
- package/dist/temporal/index.mjs +3 -3
- package/dist/typed-render-graph-primitives.d.ts +6 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-atlas.d.ts +10 -0
- package/dist/typed-shadow-atlas.d.ts.map +1 -0
- package/dist/typed-shadow-passes.d.ts +1 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/capability.d.ts +57 -0
- package/dist/volume/capability.d.ts.map +1 -0
- package/dist/volume/component.d.ts +61 -0
- package/dist/volume/component.d.ts.map +1 -0
- package/dist/volume/extract.d.ts +11 -0
- package/dist/volume/extract.d.ts.map +1 -0
- package/dist/volume/inspection.d.ts +38 -0
- package/dist/volume/inspection.d.ts.map +1 -0
- package/dist/volume/passes.d.ts +38 -0
- package/dist/volume/passes.d.ts.map +1 -0
- package/dist/volume/recovery.d.ts +38 -0
- package/dist/volume/recovery.d.ts.map +1 -0
- package/dist/volume/resources.d.ts +57 -0
- package/dist/volume/resources.d.ts.map +1 -0
- package/dist/volume/temporal.d.ts +35 -0
- package/dist/volume/temporal.d.ts.map +1 -0
- package/package.json +20 -20
- package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
- package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
- package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
- package/src/__tests__/cluster-binner.unit.test.ts +35 -0
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
- package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
- package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
- package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
- package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
- package/src/__tests__/cube-camera.integration.test.ts +42 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
- package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
- package/src/__tests__/factory-contract.integration.test.ts +255 -29
- package/src/__tests__/fog-component.unit.test.ts +67 -0
- package/src/__tests__/fog.integration.test.ts +86 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
- package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
- package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
- package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
- package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
- package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
- package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
- package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
- package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
- package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
- package/src/__tests__/instances-culling.integration.test.ts +94 -0
- package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
- package/src/__tests__/material-observation.integration.test.ts +101 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
- package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
- package/src/__tests__/public-surface.test-d.ts +10 -0
- package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
- package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
- package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
- package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
- package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
- package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +23 -3
- package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
- package/src/__tests__/render-target-admission.unit.test.ts +80 -0
- package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
- package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
- package/src/__tests__/render-target-material-source.test-d.ts +30 -0
- package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
- package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
- package/src/__tests__/render-target-owner.unit.test.ts +122 -0
- package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
- package/src/__tests__/render-target-public-schema.json +107 -0
- package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
- package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
- package/src/__tests__/render-target-readback.integration.test.ts +90 -0
- package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
- package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
- package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
- package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +32 -2
- package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
- package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
- package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
- package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
- package/src/__tests__/standard-lighting.integration.test.ts +67 -0
- package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
- package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
- package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -36
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
- package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
- package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
- package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
- package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
- package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
- package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
- package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
- package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
- package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
- package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
- package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
- package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
- package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
- package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
- package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
- package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
- package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
- package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
- package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
- package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
- package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
- package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
- package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
- package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
- package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
- package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
- package/src/assembly/device-feature-admission.ts +73 -0
- package/src/assembly/directional-shadow-inspection.ts +122 -0
- package/src/assembly/factory.ts +1093 -246
- package/src/assembly/host-contract.ts +25 -0
- package/src/assembly/render-target-host.ts +847 -0
- package/src/assets/asset-decoders.ts +1 -1
- package/src/assets/render-asset-port.ts +7 -1
- package/src/capture/cube-views.ts +69 -0
- package/src/capture/scheduler.ts +265 -0
- package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
- package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
- package/src/components/camera.ts +4 -0
- package/src/components/cube-camera.ts +52 -0
- package/src/components/directional-light.ts +22 -2
- package/src/components/directional-shadow-filter.ts +57 -0
- package/src/components/index.ts +2 -0
- package/src/components/light-helpers.ts +85 -7
- package/src/components/reflection-probe.ts +49 -0
- package/src/components/spot-light.ts +22 -3
- package/src/device/gpu-residency.ts +168 -58
- package/src/environment/frame.ts +6 -24
- package/src/environment/lifecycle.ts +12 -0
- package/src/errors/render.ts +405 -143
- package/src/extract/directional-shadow-projection.ts +61 -0
- package/src/extract/environment.ts +57 -0
- package/src/extract/visibility.ts +25 -1
- package/src/features/fullscreen.ts +3 -0
- package/src/features/targets.ts +4 -1
- package/src/features/types.ts +10 -0
- package/src/fullscreen-post-process-pass.ts +43 -2
- package/src/gpu-driven/batch-topology.ts +1 -0
- package/src/gpu-driven/production-raster.ts +1 -3
- package/src/gpu-scene.ts +8 -1
- package/src/gpu-usage.ts +2 -0
- package/src/hdrp-buffers.ts +165 -59
- package/src/ibl/IblPipelineCache.ts +11 -0
- package/src/ibl/kernel-cache.ts +88 -0
- package/src/index.ts +186 -1
- package/src/inspection-types.ts +56 -0
- package/src/instances-derived-bounds.ts +171 -0
- package/src/light-buffer-layout.ts +74 -26
- package/src/mesh-material-bindings.ts +132 -0
- package/src/pbr-pipeline.ts +139 -27
- package/src/pipeline/standard-forward-lane.ts +65 -15
- package/src/pipeline/standard-lighting/errors.ts +6 -0
- package/src/pipeline/standard-lighting/graph.ts +157 -0
- package/src/pipeline/standard-lighting/inspection.ts +88 -0
- package/src/pipeline/standard-lighting/layout.ts +31 -0
- package/src/pipeline/standard-lighting/light-frame.ts +34 -0
- package/src/pipeline/standard-lighting/membership.ts +43 -0
- package/src/pipeline/standard-lighting/prepare.ts +37 -0
- package/src/pipeline/standard-lighting/topology.ts +120 -0
- package/src/pipeline/standard-lighting/transport.ts +77 -0
- package/src/pipeline/standard-pipeline.ts +141 -192
- package/src/pipeline/standard-post.ts +26 -7
- package/src/pipeline/standard-profile.ts +23 -30
- package/src/pipeline-spec.ts +84 -21
- package/src/point-shadow-inspection.ts +55 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
- package/src/record/frame-lighting.ts +470 -516
- package/src/record/frame-snapshot.ts +76 -28
- package/src/record/frame-targets.ts +23 -2
- package/src/record/frame.ts +1169 -79
- package/src/record/gpu-pass-timing/contract.ts +180 -0
- package/src/record/gpu-pass-timing/errors.ts +34 -0
- package/src/record/gpu-pass-timing/index.ts +37 -0
- package/src/record/gpu-pass-timing/parser.ts +97 -0
- package/src/record/gpu-pass-timing/session.ts +938 -0
- package/src/record/gpu-timing.ts +233 -0
- package/src/record/main-pass-geometry.ts +120 -80
- package/src/record/main-pass-material.ts +101 -11
- package/src/record/main-pass-sprite-draws.ts +54 -25
- package/src/record/main-pass.ts +71 -15
- package/src/record/render-context.ts +77 -0
- package/src/record/shadow-pass.ts +104 -66
- package/src/record/skybox-post-pass.ts +20 -4
- package/src/record/typed-frame-graph.ts +1016 -32
- package/src/record/view-ubo.ts +63 -28
- package/src/reflection/filter.ts +81 -0
- package/src/reflection/gpu-table.ts +44 -0
- package/src/reflection/projection.ts +281 -0
- package/src/render-contract.ts +147 -4
- package/src/render-data.ts +50 -22
- package/src/render-graph-primitives.ts +9 -4
- package/src/render-pipeline.ts +161 -2
- package/src/render-system-extract.ts +886 -103
- package/src/render-system.ts +1174 -66
- package/src/renderer-inspect.ts +66 -0
- package/src/renderer.ts +222 -0
- package/src/scene/render-scene.ts +117 -9
- package/src/shadow-atlas.ts +5 -5
- package/src/ssao-buffers.ts +9 -2
- package/src/targets/contracts.ts +182 -0
- package/src/targets/graph-projection.ts +169 -0
- package/src/targets/material-source.ts +143 -0
- package/src/targets/owner.ts +214 -0
- package/src/targets/physical.ts +18 -0
- package/src/targets/readback.ts +153 -0
- package/src/temporal/frame.ts +128 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +61 -23
- package/src/typed-render-graph-primitives.ts +34 -2
- package/src/typed-shadow-atlas.ts +17 -0
- package/src/typed-shadow-passes.ts +47 -40
- package/src/volume/capability.ts +173 -0
- package/src/volume/component.ts +110 -0
- package/src/volume/extract.ts +21 -0
- package/src/volume/inspection.ts +78 -0
- package/src/volume/passes.ts +686 -0
- package/src/volume/recovery.ts +86 -0
- package/src/volume/resources.ts +236 -0
- package/src/volume/temporal.ts +89 -0
- package/dist/chunk-IPXCABEP.mjs.map +0 -1
- package/dist/chunk-LOSO4CTV.mjs.map +0 -1
- package/dist/chunk-RJRUS55L.mjs.map +0 -1
- package/dist/chunk-YTQ5C7YQ.mjs +0 -584
- package/dist/chunk-YTQ5C7YQ.mjs.map +0 -1
package/src/render-system.ts
CHANGED
|
@@ -21,10 +21,21 @@ import type { World } from '@forgeax/engine-ecs';
|
|
|
21
21
|
import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
|
|
22
22
|
import type { RecorderSession } from '@forgeax/engine-profiler';
|
|
23
23
|
import type { CompiledRenderGraphInfo } from '@forgeax/engine-render-graph';
|
|
24
|
-
import type {
|
|
24
|
+
import type {
|
|
25
|
+
BindGroup,
|
|
26
|
+
BindGroupEntry,
|
|
27
|
+
BindGroupLayout,
|
|
28
|
+
Buffer,
|
|
29
|
+
RenderPipeline,
|
|
30
|
+
Sampler,
|
|
31
|
+
Texture,
|
|
32
|
+
TextureFormat,
|
|
33
|
+
TextureView,
|
|
34
|
+
} from '@forgeax/engine-rhi';
|
|
25
35
|
import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
|
|
26
36
|
import {
|
|
27
37
|
derive,
|
|
38
|
+
deriveTextureLayout,
|
|
28
39
|
type Handle,
|
|
29
40
|
type MaterialAsset,
|
|
30
41
|
type MaterialTextureValue,
|
|
@@ -32,8 +43,14 @@ import {
|
|
|
32
43
|
type RenderPipelineAsset,
|
|
33
44
|
RenderQueue,
|
|
34
45
|
type SamplerAsset,
|
|
46
|
+
type TextureAsset,
|
|
35
47
|
toShared,
|
|
36
48
|
} from '@forgeax/engine-types';
|
|
49
|
+
import {
|
|
50
|
+
type DirectionalShadowInspectionSource,
|
|
51
|
+
directionalShadowProfileFromQuality,
|
|
52
|
+
projectDirectionalShadowInspectionSource,
|
|
53
|
+
} from './assembly/directional-shadow-inspection';
|
|
37
54
|
import { createClusterBinScratch } from './cluster-binner';
|
|
38
55
|
import { EnvironmentLifecycle } from './environment/lifecycle';
|
|
39
56
|
import {
|
|
@@ -57,11 +74,17 @@ import {
|
|
|
57
74
|
DEPTH_MIN_PARAMS_BYTE_SIZE,
|
|
58
75
|
entryHasDepthRead,
|
|
59
76
|
type PostProcessShaderEntry,
|
|
77
|
+
postProcessShaderEntrySignature,
|
|
78
|
+
postProcessShaderModuleLabel,
|
|
79
|
+
postProcessShaderPipelineLabel,
|
|
60
80
|
} from './fullscreen-post-process-pass';
|
|
61
81
|
import { GpuDrivenProduction } from './gpu-driven/production-raster';
|
|
62
82
|
import type {
|
|
63
83
|
BloomInspection,
|
|
84
|
+
DirectionalShadowInspection,
|
|
64
85
|
MotionBlurInspection,
|
|
86
|
+
ReflectionProbeInspection,
|
|
87
|
+
ReflectionProbeSelectionInspection,
|
|
65
88
|
RenderSceneInspection,
|
|
66
89
|
TemporalTargetInspection,
|
|
67
90
|
TransmissionInspection,
|
|
@@ -80,33 +103,59 @@ import {
|
|
|
80
103
|
import { createPointsLinesLaneAdapter, type PointsLinesBackend } from './points-lines/record';
|
|
81
104
|
import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
|
|
82
105
|
import type { PointsLinesRecordOwner, PointsLinesRecordSubmission } from './record/render-context';
|
|
106
|
+
import { inspectVolumetricFog } from './volume/inspection';
|
|
83
107
|
|
|
84
108
|
export type { RenderSceneInspection } from './inspection-types';
|
|
85
109
|
|
|
86
110
|
import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
|
|
111
|
+
import { mat4, vec3 } from '@forgeax/engine-math';
|
|
112
|
+
import { buildCubeCameraFaceViews } from './capture/cube-views';
|
|
113
|
+
import { createCubeCaptureScheduler } from './capture/scheduler';
|
|
114
|
+
import {
|
|
115
|
+
createFaceUniformsBuffer,
|
|
116
|
+
createPrefilterUniformsBuffer,
|
|
117
|
+
writeAllFaceUniforms,
|
|
118
|
+
writeAllPrefilterUniforms,
|
|
119
|
+
} from './device/gpu-residency';
|
|
87
120
|
import type { EnvironmentInspection } from './environment/inspection';
|
|
121
|
+
import {
|
|
122
|
+
GPU_TEXTURE_USAGE_COPY_SRC,
|
|
123
|
+
GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING,
|
|
124
|
+
} from './gpu-texture-usage';
|
|
88
125
|
import {
|
|
89
126
|
GPU_BUFFER_USAGE_COPY_DST,
|
|
90
127
|
GPU_BUFFER_USAGE_INDEX,
|
|
91
128
|
GPU_BUFFER_USAGE_UNIFORM,
|
|
92
129
|
GPU_BUFFER_USAGE_VERTEX,
|
|
93
130
|
} from './gpu-usage';
|
|
131
|
+
import { resetHdrpBuffers } from './hdrp-buffers';
|
|
132
|
+
import {
|
|
133
|
+
CUBEMAP_FACE_VERTICES,
|
|
134
|
+
createIblPipelines,
|
|
135
|
+
getOrCreateIblCache,
|
|
136
|
+
PREFILTER_MIP_LEVELS,
|
|
137
|
+
PREFILTER_SIZE,
|
|
138
|
+
} from './ibl/IblPipelineCache';
|
|
94
139
|
import { assembleMaterialWithSkylightEntries } from './ibl/skylight-bind-group';
|
|
95
140
|
import { disposeInstanceBuffers, disposeTransientInstanceBuffers } from './instance-buffer-cache';
|
|
96
|
-
import type { MeshMaterialBindingObservation } from './mesh-material-bindings';
|
|
97
|
-
import { validateClusterGrid } from './pipeline/standard-pipeline';
|
|
98
141
|
import {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
142
|
+
type MeshMaterialBindingObservation,
|
|
143
|
+
type MeshMaterialBindingPreparationFailure,
|
|
144
|
+
type MeshMaterialBindingResidency,
|
|
145
|
+
projectMeshMaterialBindingObservation,
|
|
146
|
+
} from './mesh-material-bindings';
|
|
147
|
+
import type { StandardLightingInspection } from './pipeline/standard-lighting/inspection';
|
|
148
|
+
import { validateClusterGrid } from './pipeline/standard-pipeline';
|
|
149
|
+
import { DEFAULT_CLUSTER_GRID } from './pipeline/standard-profile';
|
|
103
150
|
import { PipelineSpecError } from './pipeline-spec';
|
|
151
|
+
import type { PointShadowInspection } from './point-shadow-inspection';
|
|
104
152
|
import { PostProcessError } from './post-process-errors';
|
|
105
153
|
import {
|
|
106
154
|
createPreparedGraphicsResolver,
|
|
107
155
|
type PreparedGraphicsResolver,
|
|
108
156
|
} from './prepare/prepared-graphics-resolver';
|
|
109
157
|
import {
|
|
158
|
+
type CubeCaptureFrameInput,
|
|
110
159
|
type FrameObservation,
|
|
111
160
|
type FrameObservationOptions,
|
|
112
161
|
observeCurrentFrame,
|
|
@@ -120,12 +169,35 @@ import type {
|
|
|
120
169
|
RenderFrameState,
|
|
121
170
|
} from './record/frame-snapshot';
|
|
122
171
|
import { getTextureIdentity } from './record/frame-snapshot';
|
|
172
|
+
import type { GpuPassTimingReason } from './record/gpu-pass-timing/errors';
|
|
173
|
+
import type {
|
|
174
|
+
GpuPassTimingCapture,
|
|
175
|
+
GpuPassTimingFrameIdentity,
|
|
176
|
+
GpuPassTimingSession,
|
|
177
|
+
} from './record/gpu-pass-timing/session';
|
|
178
|
+
import type { GpuTimingCapture } from './record/gpu-timing';
|
|
123
179
|
import { applyParamSnapshotToUbo, residentTextureView } from './record/main-pass-material';
|
|
124
|
-
import type { RenderSystemInternals } from './record/render-context';
|
|
180
|
+
import type { ReflectionProbeRecordState, RenderSystemInternals } from './record/render-context';
|
|
181
|
+
import type { CubeCaptureGraphState, ReflectionProbeGraphWork } from './record/typed-frame-graph';
|
|
125
182
|
import {
|
|
126
183
|
type RenderFeatureGraphCandidate,
|
|
127
184
|
resetRenderFeatureGraphState,
|
|
185
|
+
settleVolumetricFogGraphCandidate,
|
|
128
186
|
} from './record/typed-frame-graph';
|
|
187
|
+
import { REFLECTION_PROBE_VIEW_SLOT_BASE, VIEW_UNIFORM_SLOT_STRIDE } from './record/view-ubo';
|
|
188
|
+
import {
|
|
189
|
+
advanceProbeFilter,
|
|
190
|
+
commitProbeFilterStep,
|
|
191
|
+
createProbeFilterState,
|
|
192
|
+
type ProbeFilterState,
|
|
193
|
+
} from './reflection/filter';
|
|
194
|
+
import { buildReflectionProbeTable } from './reflection/gpu-table';
|
|
195
|
+
import {
|
|
196
|
+
admitReflectionProbe,
|
|
197
|
+
DEFAULT_REFLECTION_PROBE_LIMITS,
|
|
198
|
+
type ReflectionProbeFact,
|
|
199
|
+
type ReflectionProbeSelectionResult,
|
|
200
|
+
} from './reflection/projection';
|
|
129
201
|
import {
|
|
130
202
|
type CameraSnapshot,
|
|
131
203
|
type DrawOwnerOptions,
|
|
@@ -134,7 +206,13 @@ import {
|
|
|
134
206
|
type RenderPhaseSkipReason,
|
|
135
207
|
type RenderRecordPhase,
|
|
136
208
|
} from './render-contract';
|
|
137
|
-
import type {
|
|
209
|
+
import type {
|
|
210
|
+
DispatchEntry,
|
|
211
|
+
ExtractedFrame,
|
|
212
|
+
ExtractedLights,
|
|
213
|
+
MaterialSnapshot,
|
|
214
|
+
RenderableSnapshot,
|
|
215
|
+
} from './render-system-extract';
|
|
138
216
|
import { extractFrames } from './render-system-extract';
|
|
139
217
|
import { PersistentRenderScene } from './scene/render-scene';
|
|
140
218
|
import { resolveSsaoParameters } from './ssao-config';
|
|
@@ -145,6 +223,8 @@ import {
|
|
|
145
223
|
TRANSPARENT_SORT_MODE_LAYER_YZ,
|
|
146
224
|
TRANSPARENT_SORT_MODE_LAYER_Z,
|
|
147
225
|
} from './systems/transparent-sort-config';
|
|
226
|
+
import type { RenderTarget } from './targets/contracts';
|
|
227
|
+
import { createTemporalFrameTransaction, type TemporalFrame } from './temporal/frame';
|
|
148
228
|
import { retireTemporalGpuState } from './temporal/gpu';
|
|
149
229
|
import { inspectTemporal, type TemporalInspection } from './temporal/inspection';
|
|
150
230
|
import {
|
|
@@ -159,6 +239,21 @@ import {
|
|
|
159
239
|
inspectTransmissionFromAdmission,
|
|
160
240
|
} from './transmission/inspection';
|
|
161
241
|
|
|
242
|
+
type TimingSessionHost = RenderSystemInternals & {
|
|
243
|
+
gpuPassTimingSession?: GpuPassTimingSession | undefined;
|
|
244
|
+
gpuPassTimingCapture?: GpuPassTimingCapture | undefined;
|
|
245
|
+
gpuPassTimingFrameIdentity?: GpuPassTimingFrameIdentity | undefined;
|
|
246
|
+
gpuPassTimingBeginReason?: GpuPassTimingReason | undefined;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/** Attach the Render-owned timing session without widening the public renderer surface. */
|
|
250
|
+
export function attachGpuPassTimingSession(
|
|
251
|
+
internals: object,
|
|
252
|
+
session: GpuPassTimingSession | undefined,
|
|
253
|
+
): void {
|
|
254
|
+
(internals as TimingSessionHost).gpuPassTimingSession = session;
|
|
255
|
+
}
|
|
256
|
+
|
|
162
257
|
export type {
|
|
163
258
|
_InternalRenderPipelineContext,
|
|
164
259
|
_StandardForwardSceneView,
|
|
@@ -325,6 +420,7 @@ export interface RenderSystem {
|
|
|
325
420
|
worlds: readonly World[],
|
|
326
421
|
opts: DrawOwnerOptions,
|
|
327
422
|
renderReadLeases?: readonly RenderReadLease[],
|
|
423
|
+
timingCapture?: GpuTimingCapture,
|
|
328
424
|
): boolean;
|
|
329
425
|
/** Release renderer-owned persistent state for one detached World. */
|
|
330
426
|
detachScene(world: World): void;
|
|
@@ -349,6 +445,9 @@ export interface RenderSystem {
|
|
|
349
445
|
readonly visibilityStats: { explicitlyHidden: number };
|
|
350
446
|
/** Persistent scene maintenance evidence from the ordinary single-World path. */
|
|
351
447
|
readonly renderScene: RenderSceneInspection;
|
|
448
|
+
readonly reflectionProbes: ReflectionProbeInspection;
|
|
449
|
+
/** True while a progressive CubeCamera candidate still owns unfinished faces. */
|
|
450
|
+
isCubeCapturePending(target: RenderTarget): boolean;
|
|
352
451
|
readonly environment: EnvironmentInspection;
|
|
353
452
|
readonly temporal: TemporalInspection;
|
|
354
453
|
readonly bloom: BloomInspection;
|
|
@@ -356,6 +455,12 @@ export interface RenderSystem {
|
|
|
356
455
|
readonly temporalTargetInspection: TemporalTargetInspection | undefined;
|
|
357
456
|
/** Last completed transmission candidate facts, detached from GPU handles. */
|
|
358
457
|
readonly transmission: TransmissionInspection | undefined;
|
|
458
|
+
/** Single production projection of Directional author/extract/record facts. */
|
|
459
|
+
readonly directionalShadow: DirectionalShadowInspection;
|
|
460
|
+
/** Last prepared Standard transport facts, detached from GPU handles. */
|
|
461
|
+
readonly standardLightingInspection: StandardLightingInspection | undefined;
|
|
462
|
+
/** Last submitted point-shadow atlas budget facts, detached from GPU handles. */
|
|
463
|
+
readonly pointShadowInspection: PointShadowInspection | undefined;
|
|
359
464
|
/** Retained Points/Lines authoring facts from the single scene projection. */
|
|
360
465
|
readonly pointsLinesSnapshots: readonly PointsLinesRetainedSnapshot[];
|
|
361
466
|
/** Current mesh-slot provenance and active diagnostics from the last frame. */
|
|
@@ -392,6 +497,10 @@ export interface RenderSystem {
|
|
|
392
497
|
readonly createBindGroup: number;
|
|
393
498
|
readonly keys: readonly string[];
|
|
394
499
|
};
|
|
500
|
+
/** Latest accepted-submit temporal POD; absent until the first accepted frame. */
|
|
501
|
+
readonly temporalFrame: TemporalFrame | undefined;
|
|
502
|
+
/** Current renderer-owned volumetric fog inspection snapshot. */
|
|
503
|
+
readonly volumetricFog: import('./volume/inspection').VolumetricFogInspection;
|
|
395
504
|
/** Configure the sole Standard graph owner before the first frame. */
|
|
396
505
|
configureStandard(config: RenderPipelineAsset['config']): void;
|
|
397
506
|
/** Register one engine-owned post-process shader used by the Standard lane. */
|
|
@@ -483,6 +592,175 @@ type PointsLinesGpuResources = {
|
|
|
483
592
|
};
|
|
484
593
|
type PointsLinesPreparationAdapter = PointsLinesLanePreparationAdapter<PointsLinesGpuResources>;
|
|
485
594
|
|
|
595
|
+
function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationFailure | undefined {
|
|
596
|
+
if (typeof error !== 'object' || error === null) return undefined;
|
|
597
|
+
const value = error as {
|
|
598
|
+
readonly code?: unknown;
|
|
599
|
+
readonly expected?: unknown;
|
|
600
|
+
readonly hint?: unknown;
|
|
601
|
+
readonly detail?: unknown;
|
|
602
|
+
};
|
|
603
|
+
if (
|
|
604
|
+
typeof value.code !== 'string' ||
|
|
605
|
+
typeof value.expected !== 'string' ||
|
|
606
|
+
typeof value.hint !== 'string'
|
|
607
|
+
) {
|
|
608
|
+
return undefined;
|
|
609
|
+
}
|
|
610
|
+
const detail =
|
|
611
|
+
typeof value.detail === 'object' && value.detail !== null
|
|
612
|
+
? { ...(value.detail as Record<string, unknown>) }
|
|
613
|
+
: undefined;
|
|
614
|
+
return {
|
|
615
|
+
code: value.code,
|
|
616
|
+
expected: value.expected,
|
|
617
|
+
hint: value.hint,
|
|
618
|
+
...(detail === undefined ? {} : { detail }),
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function materialTextureHandles(
|
|
623
|
+
material: MaterialSnapshot,
|
|
624
|
+
): readonly Handle<'TextureAsset', 'shared'>[] {
|
|
625
|
+
const handles = new Map<number, Handle<'TextureAsset', 'shared'>>();
|
|
626
|
+
const add = (handle: Handle<'TextureAsset', 'shared'> | undefined): void => {
|
|
627
|
+
if (handle !== undefined) handles.set(Number(handle), handle);
|
|
628
|
+
};
|
|
629
|
+
for (const handle of material.textureHandles?.values() ?? []) add(handle);
|
|
630
|
+
add(material.baseColorTexture);
|
|
631
|
+
add(material.metallicRoughnessTexture);
|
|
632
|
+
add(material.normalTexture);
|
|
633
|
+
add(material.emissiveTexture);
|
|
634
|
+
add(material.occlusionTexture);
|
|
635
|
+
return [...handles.values()];
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function materialSamplerHandles(
|
|
639
|
+
material: MaterialSnapshot,
|
|
640
|
+
): readonly Handle<'SamplerAsset', 'shared'>[] {
|
|
641
|
+
const handles = new Map<number, Handle<'SamplerAsset', 'shared'>>();
|
|
642
|
+
for (const handle of material.samplerHandles?.values() ?? []) {
|
|
643
|
+
handles.set(Number(handle), handle);
|
|
644
|
+
}
|
|
645
|
+
return [...handles.values()];
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Project the producer-owned material and GPU store state into the one
|
|
650
|
+
* renderer observation surface. This is intentionally recomputed from the
|
|
651
|
+
* current frame's detached material snapshots; no readiness map is retained
|
|
652
|
+
* by RenderSystem. A producer failure with an already resident resource is
|
|
653
|
+
* reported as last-known-good, preserving recovery context without copying
|
|
654
|
+
* the producer's lifecycle state.
|
|
655
|
+
*/
|
|
656
|
+
function observeMaterialResidency(
|
|
657
|
+
world: World | undefined,
|
|
658
|
+
material: MaterialSnapshot,
|
|
659
|
+
internals: RenderSystemInternals,
|
|
660
|
+
): MeshMaterialBindingResidency {
|
|
661
|
+
const textures: Array<{ readonly handle: number; readonly mipLevelCount: number }> = [];
|
|
662
|
+
const samplers: Array<{ readonly handle: number; readonly resident: boolean }> = [];
|
|
663
|
+
let pending = false;
|
|
664
|
+
let resident = false;
|
|
665
|
+
let failure: MeshMaterialBindingPreparationFailure | undefined;
|
|
666
|
+
const keepFailure = (error: unknown): void => {
|
|
667
|
+
if (failure === undefined) failure = preparationFailureFrom(error);
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
if (world === undefined) {
|
|
671
|
+
pending = true;
|
|
672
|
+
} else {
|
|
673
|
+
const materialHandle = material.materialHandle;
|
|
674
|
+
if (materialHandle !== undefined && materialHandle !== 0) {
|
|
675
|
+
const materialResult = resolveAssetHandle<MaterialAsset>(
|
|
676
|
+
world,
|
|
677
|
+
toShared<'MaterialAsset'>(materialHandle),
|
|
678
|
+
);
|
|
679
|
+
if (!materialResult.ok) {
|
|
680
|
+
keepFailure(materialResult.error);
|
|
681
|
+
} else {
|
|
682
|
+
const guid = internals.assets.guidOf(materialResult.value);
|
|
683
|
+
if (guid !== undefined) {
|
|
684
|
+
const readiness = internals.assets.getMaterialReadiness(guid);
|
|
685
|
+
if (readiness?.status === 'Error') keepFailure(readiness.error);
|
|
686
|
+
const load = internals.assets.loadState.get(guid);
|
|
687
|
+
if (load?.status === 'provisional') pending = true;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
for (const handle of materialTextureHandles(material)) {
|
|
693
|
+
const existing = internals.gpuStore.getTextureGpuView(handle, world);
|
|
694
|
+
const pod = resolveAssetHandle<TextureAsset>(world, handle);
|
|
695
|
+
if (!pod.ok) {
|
|
696
|
+
keepFailure(pod.error);
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
let view = existing;
|
|
700
|
+
if (view === undefined) {
|
|
701
|
+
const prepared = internals.gpuStore.ensureResident(handle, pod.value, world);
|
|
702
|
+
if (!prepared.ok) keepFailure(prepared.error);
|
|
703
|
+
view = internals.gpuStore.getTextureGpuView(handle, world);
|
|
704
|
+
}
|
|
705
|
+
if (view === undefined) {
|
|
706
|
+
pending = failure === undefined;
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
const textureLayout = deriveTextureLayout({
|
|
710
|
+
shape: pod.value.shape,
|
|
711
|
+
format: pod.value.format,
|
|
712
|
+
mips: pod.value.mips,
|
|
713
|
+
});
|
|
714
|
+
if (!textureLayout.ok) {
|
|
715
|
+
keepFailure(textureLayout.error);
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
resident = true;
|
|
719
|
+
textures.push({
|
|
720
|
+
handle: Number(handle),
|
|
721
|
+
mipLevelCount: Math.max(1, textureLayout.value.levels.length),
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
for (const handle of materialSamplerHandles(material)) {
|
|
726
|
+
const pod = resolveAssetHandle<SamplerAsset>(world, handle);
|
|
727
|
+
if (!pod.ok) {
|
|
728
|
+
keepFailure(pod.error);
|
|
729
|
+
samplers.push({ handle: Number(handle), resident: false });
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const prepared = internals.gpuStore.ensureSamplerResident(handle, pod.value, world);
|
|
733
|
+
if (!prepared.ok) {
|
|
734
|
+
keepFailure(prepared.error);
|
|
735
|
+
samplers.push({ handle: Number(handle), resident: false });
|
|
736
|
+
} else {
|
|
737
|
+
resident = true;
|
|
738
|
+
samplers.push({ handle: Number(handle), resident: true });
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// Video views are supplied by the dynamic host store at record time. They
|
|
742
|
+
// are not static TextureAsset residents, so this bounded projection stays
|
|
743
|
+
// pending until a dynamic observation contract exists rather than guessing
|
|
744
|
+
// from the VideoAsset GUID.
|
|
745
|
+
if ((material.videoTextureFields?.size ?? 0) > 0) pending = true;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const readiness =
|
|
749
|
+
failure === undefined
|
|
750
|
+
? pending
|
|
751
|
+
? 'pending'
|
|
752
|
+
: 'ready'
|
|
753
|
+
: resident
|
|
754
|
+
? 'last-known-good'
|
|
755
|
+
: 'failed';
|
|
756
|
+
return {
|
|
757
|
+
readiness,
|
|
758
|
+
samplers,
|
|
759
|
+
textures,
|
|
760
|
+
...(failure === undefined ? {} : { preparationFailure: failure }),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
486
764
|
/**
|
|
487
765
|
* The Standard renderer's single Points/Lines prepare owner.
|
|
488
766
|
*
|
|
@@ -744,6 +1022,551 @@ class StandardPointsLinesOwner implements PointsLinesRecordOwner {
|
|
|
744
1022
|
}
|
|
745
1023
|
}
|
|
746
1024
|
|
|
1025
|
+
type ProbeCubeSet = {
|
|
1026
|
+
readonly texture: Texture;
|
|
1027
|
+
readonly cubeView: TextureView;
|
|
1028
|
+
readonly faceViews: readonly TextureView[];
|
|
1029
|
+
readonly mipViews: readonly (readonly TextureView[])[];
|
|
1030
|
+
readonly depthTexture: Texture;
|
|
1031
|
+
readonly depthView: TextureView;
|
|
1032
|
+
readonly size: number;
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
type ProbeGpuResource = {
|
|
1036
|
+
fact: ReflectionProbeFact;
|
|
1037
|
+
index: number;
|
|
1038
|
+
filter: ProbeFilterState;
|
|
1039
|
+
rawFaceCursor: number;
|
|
1040
|
+
readonly raw: ProbeCubeSet;
|
|
1041
|
+
active: ProbeCubeSet | undefined;
|
|
1042
|
+
candidate: ProbeCubeSet;
|
|
1043
|
+
spare: ProbeCubeSet | undefined;
|
|
1044
|
+
readonly sampler: Sampler;
|
|
1045
|
+
readonly uniformBuffer: Buffer;
|
|
1046
|
+
readonly filterGroup1: BindGroup;
|
|
1047
|
+
pending?: {
|
|
1048
|
+
readonly rawCaptureFace: number | undefined;
|
|
1049
|
+
readonly step: ReturnType<typeof advanceProbeFilter>;
|
|
1050
|
+
};
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
type ProbeSharedResources = {
|
|
1054
|
+
readonly faceUniforms: Buffer;
|
|
1055
|
+
readonly prefilterUniforms: Buffer;
|
|
1056
|
+
readonly vertexBuffer: Buffer;
|
|
1057
|
+
readonly sampler: Sampler;
|
|
1058
|
+
readonly filterPipeline: RenderPipeline;
|
|
1059
|
+
readonly cubeGroup1Bgl: BindGroupLayout;
|
|
1060
|
+
readonly prefilterGroup0Bgl: BindGroupLayout;
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
/** Renderer-owned raw capture and incremental PMREM/LKG promotion owner. */
|
|
1064
|
+
class ReflectionProbeRecordOwner {
|
|
1065
|
+
private readonly resources = new Map<string, ProbeGpuResource>();
|
|
1066
|
+
private shared: ProbeSharedResources | undefined;
|
|
1067
|
+
private pipelineWarmup: Promise<void> | undefined;
|
|
1068
|
+
private pipelineWarmupFailure: string | undefined;
|
|
1069
|
+
private pipelineWarmupAttempts = 0;
|
|
1070
|
+
private lastFactCount = 0;
|
|
1071
|
+
private lastAcceptedCount = 0;
|
|
1072
|
+
private lastScheduledRawFaces = 0;
|
|
1073
|
+
private lastScheduledFilteredSteps = 0;
|
|
1074
|
+
private lastSelection: ReflectionProbeSelectionInspection = { kind: 'skylight' };
|
|
1075
|
+
private readonly outputFormat: TextureFormat;
|
|
1076
|
+
|
|
1077
|
+
constructor(private readonly internals: RenderSystemInternals) {
|
|
1078
|
+
this.outputFormat = internals.device.caps.rgba16floatRenderable ? 'rgba16float' : 'rgba8unorm';
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
inspect(): ReflectionProbeInspection {
|
|
1082
|
+
let rawFacesCaptured = 0;
|
|
1083
|
+
let filteredStepsCompleted = 0;
|
|
1084
|
+
const filteredMipLevels = new Set<number>();
|
|
1085
|
+
let activeCount = 0;
|
|
1086
|
+
for (const resource of this.resources.values()) {
|
|
1087
|
+
rawFacesCaptured += resource.rawFaceCursor;
|
|
1088
|
+
filteredStepsCompleted += resource.filter.cursor;
|
|
1089
|
+
const completedMipCount = Math.min(
|
|
1090
|
+
PREFILTER_MIP_LEVELS,
|
|
1091
|
+
Math.floor(resource.filter.cursor / resource.filter.faceCount),
|
|
1092
|
+
);
|
|
1093
|
+
for (let mip = 0; mip < completedMipCount; mip += 1) filteredMipLevels.add(mip);
|
|
1094
|
+
if (resource.active !== undefined) activeCount += 1;
|
|
1095
|
+
}
|
|
1096
|
+
return {
|
|
1097
|
+
selection: this.lastSelection,
|
|
1098
|
+
factCount: this.lastFactCount,
|
|
1099
|
+
acceptedCount: this.lastAcceptedCount,
|
|
1100
|
+
activeCount,
|
|
1101
|
+
rawFacesCaptured,
|
|
1102
|
+
filteredStepsCompleted,
|
|
1103
|
+
filteredMipLevels: [...filteredMipLevels].sort((a, b) => a - b),
|
|
1104
|
+
scheduledRawFaces: this.lastScheduledRawFaces,
|
|
1105
|
+
scheduledFilteredSteps: this.lastScheduledFilteredSteps,
|
|
1106
|
+
pipelineReady: this.shared !== undefined,
|
|
1107
|
+
pipelineWarmupAttempts: this.pipelineWarmupAttempts,
|
|
1108
|
+
...(this.pipelineWarmupFailure === undefined
|
|
1109
|
+
? {}
|
|
1110
|
+
: { pipelineWarmupFailure: this.pipelineWarmupFailure }),
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
private ensurePipelineWarmup(): void {
|
|
1115
|
+
if (this.shared !== undefined || this.pipelineWarmup !== undefined) return;
|
|
1116
|
+
const factory = this.internals.shaderModuleFactory;
|
|
1117
|
+
if (factory === undefined) return;
|
|
1118
|
+
const attempt = this.pipelineWarmupAttempts + 1;
|
|
1119
|
+
this.pipelineWarmupAttempts = attempt;
|
|
1120
|
+
this.pipelineWarmup = createIblPipelines(
|
|
1121
|
+
this.internals.deviceScope,
|
|
1122
|
+
this.internals.device,
|
|
1123
|
+
(device, descriptor) => {
|
|
1124
|
+
const labeled = {
|
|
1125
|
+
...descriptor,
|
|
1126
|
+
label: `${descriptor.label ?? 'ibl'}-reflection-probe-${attempt}`,
|
|
1127
|
+
};
|
|
1128
|
+
return this.internals.createShaderModule === undefined
|
|
1129
|
+
? Promise.resolve(factory.createShaderModule(labeled))
|
|
1130
|
+
: this.internals.createShaderModule(device, labeled);
|
|
1131
|
+
},
|
|
1132
|
+
this.outputFormat,
|
|
1133
|
+
).then((result) => {
|
|
1134
|
+
if (!result.ok) {
|
|
1135
|
+
this.pipelineWarmupFailure = `${result.error.code}: ${result.error.hint}`;
|
|
1136
|
+
console.warn('[forgeax] ReflectionProbe IBL warmup retry', this.pipelineWarmupFailure);
|
|
1137
|
+
this.pipelineWarmup = undefined;
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
this.shared = this.createSharedResources(result.value.prefilterPipeline);
|
|
1141
|
+
if (this.shared === undefined) {
|
|
1142
|
+
this.pipelineWarmupFailure = 'renderer-owned IBL resources could not be allocated';
|
|
1143
|
+
console.warn('[forgeax] ReflectionProbe IBL resource allocation retry');
|
|
1144
|
+
this.pipelineWarmup = undefined;
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
prepare(
|
|
1150
|
+
facts: readonly ReflectionProbeFact[],
|
|
1151
|
+
selections: ReadonlyMap<string, ReflectionProbeSelectionResult>,
|
|
1152
|
+
displayCamera: CameraSnapshot | undefined,
|
|
1153
|
+
): ReflectionProbeRecordState {
|
|
1154
|
+
this.lastFactCount = facts.length;
|
|
1155
|
+
this.lastScheduledRawFaces = 0;
|
|
1156
|
+
this.lastScheduledFilteredSteps = 0;
|
|
1157
|
+
const selectedProbe = [...selections.values()]
|
|
1158
|
+
.filter(
|
|
1159
|
+
(selection): selection is Extract<ReflectionProbeSelectionResult, { kind: 'probe' }> =>
|
|
1160
|
+
selection.kind === 'probe',
|
|
1161
|
+
)
|
|
1162
|
+
.sort((a, b) => a.worldId - b.worldId || a.entityKey - b.entityKey)[0];
|
|
1163
|
+
this.lastSelection =
|
|
1164
|
+
selectedProbe === undefined
|
|
1165
|
+
? { kind: 'skylight' }
|
|
1166
|
+
: { kind: 'probe', worldId: selectedProbe.worldId, entityKey: selectedProbe.entityKey };
|
|
1167
|
+
if (facts.length > 0) this.ensurePipelineWarmup();
|
|
1168
|
+
void this.pipelineWarmup;
|
|
1169
|
+
const ordered = facts
|
|
1170
|
+
.slice()
|
|
1171
|
+
.sort((a, b) => a.worldId - b.worldId || a.entityKey - b.entityKey);
|
|
1172
|
+
let acceptedCount = 0;
|
|
1173
|
+
let acceptedBytes = 0;
|
|
1174
|
+
const accepted = new Map<string, ProbeGpuResource>();
|
|
1175
|
+
for (const fact of ordered) {
|
|
1176
|
+
const admission = admitReflectionProbe(
|
|
1177
|
+
fact,
|
|
1178
|
+
{ acceptedCount, acceptedBytes },
|
|
1179
|
+
DEFAULT_REFLECTION_PROBE_LIMITS,
|
|
1180
|
+
);
|
|
1181
|
+
if (!admission.ok) continue;
|
|
1182
|
+
const key = `${fact.worldId}:${fact.entityKey}`;
|
|
1183
|
+
const resource = this.resourceFor(key, fact, acceptedCount);
|
|
1184
|
+
if (resource === undefined) continue;
|
|
1185
|
+
acceptedCount += 1;
|
|
1186
|
+
acceptedBytes = admission.acceptedBytes;
|
|
1187
|
+
accepted.set(key, resource);
|
|
1188
|
+
}
|
|
1189
|
+
for (const [key, resource] of this.resources) {
|
|
1190
|
+
if (!accepted.has(key)) {
|
|
1191
|
+
this.destroyProbeResource(resource);
|
|
1192
|
+
this.resources.delete(key);
|
|
1193
|
+
} else resource.index = [...accepted.keys()].indexOf(key);
|
|
1194
|
+
}
|
|
1195
|
+
const rows = [...accepted.values()]
|
|
1196
|
+
.map((resource) => {
|
|
1197
|
+
const active = resource.active;
|
|
1198
|
+
if (active === undefined) return undefined;
|
|
1199
|
+
return {
|
|
1200
|
+
primitiveKey: `${resource.fact.worldId}:${resource.fact.entityKey}`,
|
|
1201
|
+
index: resource.index,
|
|
1202
|
+
worldId: resource.fact.worldId,
|
|
1203
|
+
entityKey: resource.fact.entityKey,
|
|
1204
|
+
center: resource.fact.center,
|
|
1205
|
+
halfExtents: resource.fact.halfExtents,
|
|
1206
|
+
intensity: resource.fact.intensity,
|
|
1207
|
+
generation: resource.filter.activeGeneration,
|
|
1208
|
+
filteredView: active.cubeView,
|
|
1209
|
+
sampler: resource.sampler,
|
|
1210
|
+
uniformBuffer: resource.uniformBuffer,
|
|
1211
|
+
};
|
|
1212
|
+
})
|
|
1213
|
+
.filter((row): row is NonNullable<typeof row> => row !== undefined);
|
|
1214
|
+
const table = buildReflectionProbeTable(rows);
|
|
1215
|
+
const work: ReflectionProbeGraphWork[] = [];
|
|
1216
|
+
for (const resource of accepted.values()) {
|
|
1217
|
+
const rawCaptureFace =
|
|
1218
|
+
resource.rawFaceCursor < resource.raw.faceViews.length ? resource.rawFaceCursor : undefined;
|
|
1219
|
+
const step =
|
|
1220
|
+
resource.rawFaceCursor >= resource.raw.faceViews.length
|
|
1221
|
+
? advanceProbeFilter(resource.filter)
|
|
1222
|
+
: undefined;
|
|
1223
|
+
if (this.shared === undefined) continue;
|
|
1224
|
+
if (rawCaptureFace === undefined && step === undefined) continue;
|
|
1225
|
+
const filterGroup0 =
|
|
1226
|
+
step === undefined
|
|
1227
|
+
? undefined
|
|
1228
|
+
: this.internals.device.createBindGroup({
|
|
1229
|
+
label: `reflection-probe-${resource.index}-pmrem-${step.mipLevel}-${step.faceIndex}`,
|
|
1230
|
+
layout: this.shared.prefilterGroup0Bgl,
|
|
1231
|
+
entries: [
|
|
1232
|
+
{
|
|
1233
|
+
binding: 0,
|
|
1234
|
+
resource: {
|
|
1235
|
+
kind: 'buffer',
|
|
1236
|
+
value: {
|
|
1237
|
+
buffer: this.shared.faceUniforms,
|
|
1238
|
+
offset: step.faceIndex * 256,
|
|
1239
|
+
size: 64,
|
|
1240
|
+
},
|
|
1241
|
+
},
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
binding: 1,
|
|
1245
|
+
resource: {
|
|
1246
|
+
kind: 'buffer',
|
|
1247
|
+
value: {
|
|
1248
|
+
buffer: this.shared.prefilterUniforms,
|
|
1249
|
+
offset: (step.mipLevel * 6 + step.faceIndex) * 256,
|
|
1250
|
+
size: 16,
|
|
1251
|
+
},
|
|
1252
|
+
},
|
|
1253
|
+
},
|
|
1254
|
+
],
|
|
1255
|
+
});
|
|
1256
|
+
if (filterGroup0 !== undefined && !filterGroup0.ok) continue;
|
|
1257
|
+
if (resource.pending !== undefined) continue;
|
|
1258
|
+
resource.pending = { rawCaptureFace, step };
|
|
1259
|
+
if (rawCaptureFace !== undefined) this.lastScheduledRawFaces += 1;
|
|
1260
|
+
if (step !== undefined) this.lastScheduledFilteredSteps += 1;
|
|
1261
|
+
const face =
|
|
1262
|
+
rawCaptureFace === undefined
|
|
1263
|
+
? undefined
|
|
1264
|
+
: buildCubeCameraFaceViews({
|
|
1265
|
+
position: resource.fact.center,
|
|
1266
|
+
near: 0.1,
|
|
1267
|
+
far: 100,
|
|
1268
|
+
})[rawCaptureFace];
|
|
1269
|
+
const faceCamera =
|
|
1270
|
+
face === undefined || displayCamera === undefined
|
|
1271
|
+
? undefined
|
|
1272
|
+
: {
|
|
1273
|
+
...displayCamera,
|
|
1274
|
+
position: vec3.create(
|
|
1275
|
+
resource.fact.center[0] ?? 0,
|
|
1276
|
+
resource.fact.center[1] ?? 0,
|
|
1277
|
+
resource.fact.center[2] ?? 0,
|
|
1278
|
+
),
|
|
1279
|
+
world: mat4.invert(mat4.create(), face.view),
|
|
1280
|
+
fov: Math.PI / 2,
|
|
1281
|
+
aspect: 1,
|
|
1282
|
+
near: face.near,
|
|
1283
|
+
far: face.far,
|
|
1284
|
+
projection: 'perspective' as const,
|
|
1285
|
+
};
|
|
1286
|
+
work.push({
|
|
1287
|
+
probeIndex: resource.index,
|
|
1288
|
+
rawTexture: resource.raw.texture,
|
|
1289
|
+
rawCubeView: resource.raw.cubeView,
|
|
1290
|
+
rawFaceViews: resource.raw.faceViews,
|
|
1291
|
+
rawDepthTexture: resource.raw.depthTexture,
|
|
1292
|
+
filteredTexture: resource.candidate.texture,
|
|
1293
|
+
filteredCubeView: resource.candidate.cubeView,
|
|
1294
|
+
filteredFaceViewsByMip: resource.candidate.mipViews,
|
|
1295
|
+
outputFormat: this.outputFormat,
|
|
1296
|
+
sampler: resource.sampler,
|
|
1297
|
+
filterPipeline: this.shared.filterPipeline,
|
|
1298
|
+
filterGroup0: filterGroup0?.value,
|
|
1299
|
+
filterGroup1: resource.filterGroup1,
|
|
1300
|
+
cubeVertexBuffer: this.shared.vertexBuffer,
|
|
1301
|
+
rawCaptureFace,
|
|
1302
|
+
rawDepthView: resource.raw.depthView,
|
|
1303
|
+
rawSize: resource.raw.size,
|
|
1304
|
+
...(faceCamera === undefined ? {} : { faceCamera }),
|
|
1305
|
+
...(rawCaptureFace === undefined
|
|
1306
|
+
? {}
|
|
1307
|
+
: {
|
|
1308
|
+
viewBindGroupDynamicOffset:
|
|
1309
|
+
(REFLECTION_PROBE_VIEW_SLOT_BASE + resource.index) * VIEW_UNIFORM_SLOT_STRIDE,
|
|
1310
|
+
}),
|
|
1311
|
+
filteredSize: resource.candidate.size,
|
|
1312
|
+
step,
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
return {
|
|
1316
|
+
table,
|
|
1317
|
+
selections,
|
|
1318
|
+
graph: { work },
|
|
1319
|
+
completeSubmission: (submitted, completed) => this.completeSubmission(submitted, completed),
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
private completeSubmission(submitted: boolean, completed?: Promise<unknown>): void {
|
|
1324
|
+
if (completed !== undefined) {
|
|
1325
|
+
void completed.then(
|
|
1326
|
+
() => this.completeSubmission(submitted),
|
|
1327
|
+
() => this.completeSubmission(false),
|
|
1328
|
+
);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
for (const resource of this.resources.values()) {
|
|
1332
|
+
const pending = resource.pending;
|
|
1333
|
+
delete resource.pending;
|
|
1334
|
+
if (!submitted || pending === undefined) continue;
|
|
1335
|
+
if (pending.rawCaptureFace !== undefined) {
|
|
1336
|
+
resource.rawFaceCursor = Math.min(
|
|
1337
|
+
resource.rawFaceCursor + 1,
|
|
1338
|
+
resource.raw.faceViews.length,
|
|
1339
|
+
);
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (pending.step === undefined) continue;
|
|
1343
|
+
resource.filter = commitProbeFilterStep(resource.filter, true);
|
|
1344
|
+
if (resource.filter.cursor >= resource.filter.faceCount * resource.filter.mipCount) {
|
|
1345
|
+
const previous = resource.active;
|
|
1346
|
+
resource.active = resource.candidate;
|
|
1347
|
+
resource.candidate = resource.spare ?? previous ?? resource.candidate;
|
|
1348
|
+
resource.spare = undefined;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
dispose(): void {
|
|
1354
|
+
for (const resource of this.resources.values()) {
|
|
1355
|
+
this.destroyProbeResource(resource);
|
|
1356
|
+
}
|
|
1357
|
+
this.resources.clear();
|
|
1358
|
+
if (this.shared !== undefined) {
|
|
1359
|
+
this.internals.device.destroyBuffer(this.shared.faceUniforms);
|
|
1360
|
+
this.internals.device.destroyBuffer(this.shared.prefilterUniforms);
|
|
1361
|
+
this.internals.device.destroyBuffer(this.shared.vertexBuffer);
|
|
1362
|
+
}
|
|
1363
|
+
this.shared = undefined;
|
|
1364
|
+
this.pipelineWarmup = undefined;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
private destroyProbeResource(resource: ProbeGpuResource): void {
|
|
1368
|
+
const sets = new Set<ProbeCubeSet>([resource.raw, resource.candidate]);
|
|
1369
|
+
if (resource.active !== undefined) sets.add(resource.active);
|
|
1370
|
+
if (resource.spare !== undefined) sets.add(resource.spare);
|
|
1371
|
+
for (const set of sets) this.destroyCubeSet(set);
|
|
1372
|
+
this.internals.device.destroyBuffer(resource.uniformBuffer);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
private destroyCubeSet(set: ProbeCubeSet): void {
|
|
1376
|
+
this.internals.device.destroyTexture(set.texture);
|
|
1377
|
+
this.internals.device.destroyTexture(set.depthTexture);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
private resourceFor(
|
|
1381
|
+
key: string,
|
|
1382
|
+
fact: ReflectionProbeFact,
|
|
1383
|
+
index: number,
|
|
1384
|
+
): ProbeGpuResource | undefined {
|
|
1385
|
+
const existing = this.resources.get(key);
|
|
1386
|
+
if (existing !== undefined) {
|
|
1387
|
+
if (existing.fact.revision !== fact.revision) {
|
|
1388
|
+
existing.fact = fact;
|
|
1389
|
+
existing.filter = createProbeFilterState({
|
|
1390
|
+
probeIndex: index,
|
|
1391
|
+
mipCount: PREFILTER_MIP_LEVELS,
|
|
1392
|
+
activeGeneration: existing.filter.activeGeneration,
|
|
1393
|
+
});
|
|
1394
|
+
existing.rawFaceCursor = 0;
|
|
1395
|
+
} else {
|
|
1396
|
+
existing.fact = fact;
|
|
1397
|
+
existing.index = index;
|
|
1398
|
+
}
|
|
1399
|
+
return existing;
|
|
1400
|
+
}
|
|
1401
|
+
if (this.shared === undefined) return undefined;
|
|
1402
|
+
const raw = this.createCubeSet(`reflection-probe-${index}-raw`, fact.resolution);
|
|
1403
|
+
const candidate = this.createCubeSet(`reflection-probe-${index}-candidate`, fact.resolution);
|
|
1404
|
+
const spare = this.createCubeSet(`reflection-probe-${index}-spare`, fact.resolution);
|
|
1405
|
+
if (raw === undefined || candidate === undefined || spare === undefined) return undefined;
|
|
1406
|
+
const uniformBuffer = this.internals.device.createBuffer({
|
|
1407
|
+
label: `reflection-probe-${index}-uniforms`,
|
|
1408
|
+
size: 32,
|
|
1409
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1410
|
+
mappedAtCreation: false,
|
|
1411
|
+
});
|
|
1412
|
+
if (!uniformBuffer.ok) return undefined;
|
|
1413
|
+
const payload = new Float32Array([
|
|
1414
|
+
-Math.max(0, fact.intensity),
|
|
1415
|
+
fact.center[0],
|
|
1416
|
+
fact.center[1],
|
|
1417
|
+
fact.center[2],
|
|
1418
|
+
fact.halfExtents[0],
|
|
1419
|
+
fact.halfExtents[1],
|
|
1420
|
+
fact.halfExtents[2],
|
|
1421
|
+
0,
|
|
1422
|
+
]);
|
|
1423
|
+
const written = this.internals.device.queue.writeBuffer(uniformBuffer.value, 0, payload);
|
|
1424
|
+
if (!written.ok) return undefined;
|
|
1425
|
+
const filterGroup1 = this.internals.device.createBindGroup({
|
|
1426
|
+
label: `reflection-probe-${index}-filter-input`,
|
|
1427
|
+
layout: this.shared.cubeGroup1Bgl,
|
|
1428
|
+
entries: [
|
|
1429
|
+
{ binding: 0, resource: { kind: 'textureView', value: raw.cubeView } },
|
|
1430
|
+
{ binding: 1, resource: { kind: 'sampler', value: this.shared.sampler } },
|
|
1431
|
+
],
|
|
1432
|
+
});
|
|
1433
|
+
if (!filterGroup1.ok) return undefined;
|
|
1434
|
+
const resource: ProbeGpuResource = {
|
|
1435
|
+
fact,
|
|
1436
|
+
index,
|
|
1437
|
+
filter: createProbeFilterState({ probeIndex: index, mipCount: PREFILTER_MIP_LEVELS }),
|
|
1438
|
+
rawFaceCursor: 0,
|
|
1439
|
+
raw,
|
|
1440
|
+
active: undefined,
|
|
1441
|
+
candidate,
|
|
1442
|
+
spare,
|
|
1443
|
+
sampler: this.shared.sampler,
|
|
1444
|
+
uniformBuffer: uniformBuffer.value,
|
|
1445
|
+
filterGroup1: filterGroup1.value,
|
|
1446
|
+
};
|
|
1447
|
+
this.resources.set(key, resource);
|
|
1448
|
+
return resource;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
private createSharedResources(filterPipeline: RenderPipeline): ProbeSharedResources | undefined {
|
|
1452
|
+
const faceUniforms = createFaceUniformsBuffer(this.internals.device);
|
|
1453
|
+
const prefilterUniforms = createPrefilterUniformsBuffer(this.internals.device);
|
|
1454
|
+
if (!faceUniforms.ok || !prefilterUniforms.ok) return undefined;
|
|
1455
|
+
if (!writeAllFaceUniforms(this.internals.device, faceUniforms.value).ok) return undefined;
|
|
1456
|
+
if (!writeAllPrefilterUniforms(this.internals.device, prefilterUniforms.value).ok)
|
|
1457
|
+
return undefined;
|
|
1458
|
+
const vertexBuffer = this.internals.device.createBuffer({
|
|
1459
|
+
label: 'reflection-probe-capture-cube-vertices',
|
|
1460
|
+
size: CUBEMAP_FACE_VERTICES.byteLength,
|
|
1461
|
+
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
1462
|
+
mappedAtCreation: false,
|
|
1463
|
+
});
|
|
1464
|
+
if (!vertexBuffer.ok) return undefined;
|
|
1465
|
+
const vertexWrite = this.internals.device.queue.writeBuffer(
|
|
1466
|
+
vertexBuffer.value,
|
|
1467
|
+
0,
|
|
1468
|
+
CUBEMAP_FACE_VERTICES,
|
|
1469
|
+
);
|
|
1470
|
+
if (!vertexWrite.ok) return undefined;
|
|
1471
|
+
const sampler = this.internals.device.createSampler({
|
|
1472
|
+
label: 'reflection-probe-pmrem-sampler',
|
|
1473
|
+
magFilter: 'linear',
|
|
1474
|
+
minFilter: 'linear',
|
|
1475
|
+
mipmapFilter: 'linear',
|
|
1476
|
+
addressModeU: 'clamp-to-edge',
|
|
1477
|
+
addressModeV: 'clamp-to-edge',
|
|
1478
|
+
addressModeW: 'clamp-to-edge',
|
|
1479
|
+
});
|
|
1480
|
+
const cache = getOrCreateIblCache(this.internals.deviceScope);
|
|
1481
|
+
if (
|
|
1482
|
+
!sampler.ok ||
|
|
1483
|
+
cache.cubeGroup1Bgl === undefined ||
|
|
1484
|
+
cache.prefilterGroup0Bgl === undefined
|
|
1485
|
+
) {
|
|
1486
|
+
return undefined;
|
|
1487
|
+
}
|
|
1488
|
+
return {
|
|
1489
|
+
faceUniforms: faceUniforms.value,
|
|
1490
|
+
prefilterUniforms: prefilterUniforms.value,
|
|
1491
|
+
vertexBuffer: vertexBuffer.value,
|
|
1492
|
+
sampler: sampler.value,
|
|
1493
|
+
filterPipeline,
|
|
1494
|
+
cubeGroup1Bgl: cache.cubeGroup1Bgl,
|
|
1495
|
+
prefilterGroup0Bgl: cache.prefilterGroup0Bgl,
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
private createCubeSet(label: string, requestedSize: number): ProbeCubeSet | undefined {
|
|
1500
|
+
const size = Math.min(Math.max(1, Math.floor(requestedSize)), PREFILTER_SIZE);
|
|
1501
|
+
const mipCount = Math.min(PREFILTER_MIP_LEVELS, Math.floor(Math.log2(size)) + 1);
|
|
1502
|
+
const texture = this.internals.device.createTexture({
|
|
1503
|
+
label,
|
|
1504
|
+
size: { width: size, height: size, depthOrArrayLayers: 6 },
|
|
1505
|
+
mipLevelCount: mipCount,
|
|
1506
|
+
sampleCount: 1,
|
|
1507
|
+
dimension: '2d',
|
|
1508
|
+
format: this.outputFormat,
|
|
1509
|
+
usage: GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_SRC,
|
|
1510
|
+
viewFormats: [],
|
|
1511
|
+
textureBindingViewDimension: undefined,
|
|
1512
|
+
});
|
|
1513
|
+
if (!texture.ok) return undefined;
|
|
1514
|
+
const cubeView = this.internals.device.createTextureView(texture.value, {
|
|
1515
|
+
label: `${label}-cube`,
|
|
1516
|
+
dimension: 'cube',
|
|
1517
|
+
baseMipLevel: 0,
|
|
1518
|
+
mipLevelCount: mipCount,
|
|
1519
|
+
arrayLayerCount: 6,
|
|
1520
|
+
});
|
|
1521
|
+
if (!cubeView.ok) return undefined;
|
|
1522
|
+
const faceViews: TextureView[] = [];
|
|
1523
|
+
const mipViews: TextureView[][] = [];
|
|
1524
|
+
for (let mip = 0; mip < mipCount; mip += 1) {
|
|
1525
|
+
const faces: TextureView[] = [];
|
|
1526
|
+
for (let face = 0; face < 6; face += 1) {
|
|
1527
|
+
const view = this.internals.device.createTextureView(texture.value, {
|
|
1528
|
+
label: `${label}-mip${mip}-face${face}`,
|
|
1529
|
+
dimension: '2d',
|
|
1530
|
+
baseMipLevel: mip,
|
|
1531
|
+
mipLevelCount: 1,
|
|
1532
|
+
baseArrayLayer: face,
|
|
1533
|
+
arrayLayerCount: 1,
|
|
1534
|
+
});
|
|
1535
|
+
if (!view.ok) return undefined;
|
|
1536
|
+
if (mip === 0) faceViews.push(view.value);
|
|
1537
|
+
faces.push(view.value);
|
|
1538
|
+
}
|
|
1539
|
+
mipViews.push(faces);
|
|
1540
|
+
}
|
|
1541
|
+
const depthTexture = this.internals.device.createTexture({
|
|
1542
|
+
label: `${label}-depth`,
|
|
1543
|
+
size: { width: size, height: size, depthOrArrayLayers: 1 },
|
|
1544
|
+
mipLevelCount: 1,
|
|
1545
|
+
sampleCount: 1,
|
|
1546
|
+
dimension: '2d',
|
|
1547
|
+
format: 'depth24plus-stencil8',
|
|
1548
|
+
usage: 0x10,
|
|
1549
|
+
viewFormats: [],
|
|
1550
|
+
textureBindingViewDimension: undefined,
|
|
1551
|
+
});
|
|
1552
|
+
if (!depthTexture.ok) return undefined;
|
|
1553
|
+
const depthView = this.internals.device.createTextureView(depthTexture.value, {
|
|
1554
|
+
label: `${label}-depth-view`,
|
|
1555
|
+
dimension: '2d',
|
|
1556
|
+
});
|
|
1557
|
+
if (!depthView.ok) return undefined;
|
|
1558
|
+
return {
|
|
1559
|
+
texture: texture.value,
|
|
1560
|
+
cubeView: cubeView.value,
|
|
1561
|
+
faceViews,
|
|
1562
|
+
mipViews,
|
|
1563
|
+
depthTexture: depthTexture.value,
|
|
1564
|
+
depthView: depthView.value,
|
|
1565
|
+
size,
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
747
1570
|
export function createRenderSystem(internals: RenderSystemInternals): RenderSystem {
|
|
748
1571
|
const phaseCatalogRegistration = internals.profiler?.registerPhaseCatalog(
|
|
749
1572
|
'render',
|
|
@@ -754,6 +1577,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
754
1577
|
let preparedWorlds: readonly World[] = [];
|
|
755
1578
|
let latestCamera: CameraSnapshot | undefined;
|
|
756
1579
|
const pointsLinesOwner = new StandardPointsLinesOwner(internals);
|
|
1580
|
+
const reflectionProbeOwner = new ReflectionProbeRecordOwner(internals);
|
|
1581
|
+
const cubeCaptureScheduler = createCubeCaptureScheduler({ maxFacesPerFrame: 1 });
|
|
1582
|
+
const cubeCaptureState: CubeCaptureGraphState = { work: [] };
|
|
757
1583
|
const persistentRenderScene = new PersistentRenderScene({
|
|
758
1584
|
getDevice: () => internals.device,
|
|
759
1585
|
onGpuError: (error) => internals.errorRegistry.fire(error),
|
|
@@ -949,10 +1775,27 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
949
1775
|
graphGeneration: 0,
|
|
950
1776
|
successfulTemporalFrameIndex: 0,
|
|
951
1777
|
lastSuccessfulBloom: 'off',
|
|
1778
|
+
temporalFrameTransaction: createTemporalFrameTransaction({ deviceEpoch: 0 }),
|
|
1779
|
+
temporalFrameInput: undefined,
|
|
952
1780
|
directionalShadowCache: null,
|
|
953
1781
|
directionalShadowCacheRecorded: false,
|
|
954
1782
|
compiledFrameGraph: null,
|
|
955
1783
|
compiledFrameGraphTopologyKey: null,
|
|
1784
|
+
compiledFrameGraphGeneration: 0,
|
|
1785
|
+
volumetricFogPreviousGraph: null,
|
|
1786
|
+
volumetricFogPreviousGraphKey: null,
|
|
1787
|
+
volumetricFogCandidateGraph: null,
|
|
1788
|
+
volumetricFogParamsBuffers: [null, null],
|
|
1789
|
+
volumetricFogParamsPendingSlot: null,
|
|
1790
|
+
volumetricFogParamsAcceptedSlot: null,
|
|
1791
|
+
volumetricFogAcceptedParams: undefined,
|
|
1792
|
+
volumetricFogPendingParams: undefined,
|
|
1793
|
+
volumetricFogInspection: inspectVolumetricFog({ authored: false, capability: 'available' }),
|
|
1794
|
+
volumetricFogAccepted: undefined,
|
|
1795
|
+
volumetricFogAcceptedContext: undefined,
|
|
1796
|
+
volumetricFogHistoryGraph: null,
|
|
1797
|
+
volumetricFogHistorySlot: null,
|
|
1798
|
+
volumetricFogHistorySignature: null,
|
|
956
1799
|
retiredCompiledFrameGraphs: new Set(),
|
|
957
1800
|
currentFrameObservationSource: undefined,
|
|
958
1801
|
lastSuccessfulCameraAntialias: undefined,
|
|
@@ -970,7 +1813,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
970
1813
|
hdrpClusterBinScratch: createClusterBinScratch(),
|
|
971
1814
|
hdrpClusterGridScratch: null,
|
|
972
1815
|
hdrpLightIndexListScratch: null,
|
|
973
|
-
|
|
1816
|
+
hdrpClusterMembership: null,
|
|
1817
|
+
standardLightingGraphSignature: '',
|
|
1818
|
+
standardLightingInspection: undefined,
|
|
1819
|
+
pointShadowInspection: undefined,
|
|
974
1820
|
transientInstanceBuffers: [],
|
|
975
1821
|
warnedZeroLightStandard: false,
|
|
976
1822
|
warnedMultiLightDirectional: false,
|
|
@@ -1026,10 +1872,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1026
1872
|
// (its topology is frame-invariant). Standard configuration overwrites this with the
|
|
1027
1873
|
// resolved asset config on every swap so the active graph reads its current config.
|
|
1028
1874
|
installedPipelineConfig: undefined,
|
|
1029
|
-
|
|
1030
|
-
// warn dedup set for hdrp-light-budget-exceeded / hdrp-index-list-overflow.
|
|
1031
|
-
isHdrpActive: false,
|
|
1032
|
-
hdrpOncePerFrameFired: new Set(),
|
|
1875
|
+
standardOncePerFrameFired: new Set(),
|
|
1033
1876
|
// feat-20260612-point-light-shadows-urp-hdrp M3 / T-M3-2 (plan-strategy §D-1):
|
|
1034
1877
|
// cube_array shadow atlas + per-frame snapshot list. Atlas is null until the
|
|
1035
1878
|
// first frame whose extracted lights.pointShadow is non-empty (zero-shadow
|
|
@@ -1048,6 +1891,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1048
1891
|
spotShadowSnapshots: [],
|
|
1049
1892
|
};
|
|
1050
1893
|
let directFrameId = 0;
|
|
1894
|
+
let lastDirectionalShadowLights: ExtractedLights | undefined;
|
|
1895
|
+
let lastDirectionalShadowCandidate: 'accepted' | 'failed' = 'failed';
|
|
1896
|
+
let lastDirectionalShadowError: DirectionalShadowInspectionSource['error'];
|
|
1051
1897
|
|
|
1052
1898
|
function beginProfilePhase(session: RecorderSession | undefined, phase: RenderPhase): boolean {
|
|
1053
1899
|
if (session === undefined) return false;
|
|
@@ -1108,66 +1954,124 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1108
1954
|
let activeFeaturePostProcessEntries: ReadonlyMap<string, PostProcessShaderEntry> = new Map();
|
|
1109
1955
|
const lookupPostProcess = (id: string): PostProcessShaderEntry | undefined =>
|
|
1110
1956
|
builtinPostProcessEntries.get(id) ?? activeFeaturePostProcessEntries.get(id);
|
|
1957
|
+
const invalidatePostProcessModule = (id: string): void => {
|
|
1958
|
+
const entry = lookupPostProcess(id);
|
|
1959
|
+
if (entry !== undefined) {
|
|
1960
|
+
internals.invalidateShaderModule?.(postProcessShaderModuleLabel(id, entry.source));
|
|
1961
|
+
}
|
|
1962
|
+
};
|
|
1111
1963
|
// feat-20260621 M-A2 / w8: expose the eager-created per-id params UBO through
|
|
1112
1964
|
// the narrow runtime surface so dispatchFullscreenPass can writeBuffer the
|
|
1113
1965
|
// per-frame snapshot + bind it at group(1) binding(2).
|
|
1114
1966
|
const getPostProcessParamsBuffer = (id: string): Buffer | undefined =>
|
|
1115
1967
|
postProcessParamsBuffers.get(id);
|
|
1116
|
-
|
|
1968
|
+
interface PostProcessParamsCandidate {
|
|
1969
|
+
readonly buffers: ReadonlyMap<string, Buffer>;
|
|
1970
|
+
readonly created: readonly Buffer[];
|
|
1971
|
+
}
|
|
1972
|
+
const postProcessParamsByteSize = (entry: PostProcessShaderEntry): number =>
|
|
1973
|
+
entry.params?.byteSize ?? (entryHasDepthRead(entry) ? DEPTH_MIN_PARAMS_BYTE_SIZE : 0);
|
|
1974
|
+
const destroyPostProcessParams = (buffers: readonly Buffer[]): void => {
|
|
1975
|
+
for (const buffer of buffers) {
|
|
1976
|
+
const destroyed = internals.device.destroyBuffer(buffer);
|
|
1977
|
+
if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
const preparePostProcessParamsResources = (
|
|
1117
1981
|
featureEntries: ReadonlyMap<string, PostProcessShaderEntry>,
|
|
1118
|
-
):
|
|
1982
|
+
): Result<PostProcessParamsCandidate, RhiError> => {
|
|
1119
1983
|
const declarations = new Map<string, PostProcessShaderEntry>([
|
|
1120
1984
|
...builtinPostProcessEntries.entries(),
|
|
1121
1985
|
...featureEntries.entries(),
|
|
1122
1986
|
]);
|
|
1987
|
+
const next = new Map<string, Buffer>();
|
|
1988
|
+
const allocated: Buffer[] = [];
|
|
1989
|
+
const discardCreated = (): void => destroyPostProcessParams(allocated);
|
|
1123
1990
|
for (const [id, entry] of declarations) {
|
|
1124
1991
|
if (id === 'forgeax.taa-resolve') continue;
|
|
1125
|
-
const byteSize =
|
|
1126
|
-
|
|
1127
|
-
|
|
1992
|
+
const byteSize = postProcessParamsByteSize(entry);
|
|
1993
|
+
if (byteSize === 0) continue;
|
|
1994
|
+
const existing = postProcessParamsBuffers.get(id);
|
|
1995
|
+
const accepted = lookupPostProcess(id);
|
|
1996
|
+
if (
|
|
1997
|
+
existing !== undefined &&
|
|
1998
|
+
accepted !== undefined &&
|
|
1999
|
+
postProcessShaderEntrySignature(accepted) === postProcessShaderEntrySignature(entry)
|
|
2000
|
+
) {
|
|
2001
|
+
next.set(id, existing);
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
1128
2004
|
const created = internals.device.createBuffer({
|
|
1129
2005
|
label: `post-process-params-${id}`,
|
|
1130
2006
|
size: byteSize,
|
|
1131
2007
|
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1132
2008
|
mappedAtCreation: false,
|
|
1133
2009
|
});
|
|
1134
|
-
if (!created.ok)
|
|
2010
|
+
if (!created.ok) {
|
|
2011
|
+
discardCreated();
|
|
2012
|
+
return err(created.error);
|
|
2013
|
+
}
|
|
1135
2014
|
const buffer = created.value;
|
|
2015
|
+
allocated.push(buffer);
|
|
1136
2016
|
if (entry.params !== undefined) {
|
|
1137
2017
|
const written = internals.device.queue.writeBuffer(buffer, 0, entry.params.defaultValue);
|
|
1138
2018
|
if (!written.ok) {
|
|
1139
|
-
|
|
1140
|
-
|
|
2019
|
+
discardCreated();
|
|
2020
|
+
return err(written.error);
|
|
1141
2021
|
}
|
|
1142
2022
|
}
|
|
1143
|
-
|
|
2023
|
+
next.set(id, buffer);
|
|
2024
|
+
}
|
|
2025
|
+
return ok({ buffers: next, created: Object.freeze([...allocated]) });
|
|
2026
|
+
};
|
|
2027
|
+
const discardPostProcessParamsCandidate = (candidate: PostProcessParamsCandidate): void => {
|
|
2028
|
+
destroyPostProcessParams(candidate.created);
|
|
2029
|
+
};
|
|
2030
|
+
const retirePostProcessParams = (buffers: readonly Buffer[]): void => {
|
|
2031
|
+
if (buffers.length === 0) return;
|
|
2032
|
+
const release = (): void => destroyPostProcessParams(buffers);
|
|
2033
|
+
try {
|
|
2034
|
+
void internals.device.queue.onSubmittedWorkDone().then(release, release);
|
|
2035
|
+
} catch {
|
|
2036
|
+
release();
|
|
1144
2037
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
2038
|
+
};
|
|
2039
|
+
const acceptPostProcessParamsCandidate = (candidate: PostProcessParamsCandidate): void => {
|
|
2040
|
+
const previous = [...postProcessParamsBuffers.values()];
|
|
2041
|
+
const next = new Set(candidate.buffers.values());
|
|
2042
|
+
postProcessParamsBuffers.clear();
|
|
2043
|
+
for (const [id, buffer] of candidate.buffers) {
|
|
2044
|
+
postProcessParamsBuffers.set(id, buffer);
|
|
1149
2045
|
}
|
|
2046
|
+
retirePostProcessParams(previous.filter((buffer) => !next.has(buffer)));
|
|
1150
2047
|
};
|
|
1151
|
-
// feat-20260609 M4 / T-10-a: post-process pipeline cache (
|
|
2048
|
+
// feat-20260609 M4 / T-10-a: post-process pipeline cache (declaration|colorFormat -> RhiRenderPipeline).
|
|
1152
2049
|
// Solves CONCERN-1: dispatcher previously passed `pipeline=null` to
|
|
1153
2050
|
// built.createHandle because per-frame execute closures cannot await async
|
|
1154
2051
|
// shader compile. The cache here delegates the actual build to
|
|
1155
2052
|
// `internals.buildPostProcessPipeline` (sync wrapper over the shared shader
|
|
1156
|
-
// adapter; 1-frame warmup), then memoizes by
|
|
2053
|
+
// adapter; 1-frame warmup), then memoizes by declaration identity + format.
|
|
1157
2054
|
const postProcessPipelineCache = new Map<string, RenderPipeline>();
|
|
1158
2055
|
const getPostProcessPipeline = (
|
|
1159
2056
|
id: string,
|
|
1160
2057
|
bgl: BindGroupLayout,
|
|
1161
2058
|
colorFormat: GPUTextureFormat,
|
|
2059
|
+
entryOverride?: PostProcessShaderEntry,
|
|
1162
2060
|
): RenderPipeline | null => {
|
|
1163
|
-
const
|
|
2061
|
+
const entry = entryOverride ?? lookupPostProcess(id);
|
|
2062
|
+
if (entry === undefined) return null;
|
|
2063
|
+
const signature = postProcessShaderEntrySignature(entry);
|
|
2064
|
+
const key = `${id}|${colorFormat}|${signature}`;
|
|
1164
2065
|
const cached = postProcessPipelineCache.get(key);
|
|
1165
2066
|
if (cached !== undefined) return cached;
|
|
1166
|
-
const entry = lookupPostProcess(id);
|
|
1167
|
-
if (entry === undefined) return null;
|
|
1168
2067
|
const factory = internals.buildPostProcessPipeline;
|
|
1169
2068
|
if (factory === undefined) return null;
|
|
1170
|
-
const built = factory(
|
|
2069
|
+
const built = factory(
|
|
2070
|
+
entry,
|
|
2071
|
+
bgl,
|
|
2072
|
+
colorFormat,
|
|
2073
|
+
postProcessShaderPipelineLabel(id, entry.source),
|
|
2074
|
+
);
|
|
1171
2075
|
if (built === null) return null;
|
|
1172
2076
|
postProcessPipelineCache.set(key, built);
|
|
1173
2077
|
return built;
|
|
@@ -1177,6 +2081,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1177
2081
|
if (key.startsWith(`${id}|`)) postProcessPipelineCache.delete(key);
|
|
1178
2082
|
}
|
|
1179
2083
|
};
|
|
2084
|
+
const clearPostProcessPipelineEntry = (id: string, entry: PostProcessShaderEntry): void => {
|
|
2085
|
+
const suffix = `|${postProcessShaderEntrySignature(entry)}`;
|
|
2086
|
+
for (const key of postProcessPipelineCache.keys()) {
|
|
2087
|
+
if (key.startsWith(`${id}|`) && key.endsWith(suffix)) {
|
|
2088
|
+
postProcessPipelineCache.delete(key);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
};
|
|
1180
2092
|
Object.assign(internals, {
|
|
1181
2093
|
lookupPostProcess,
|
|
1182
2094
|
getPostProcessParamsBuffer,
|
|
@@ -1351,7 +2263,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1351
2263
|
if (buffer === undefined) return group;
|
|
1352
2264
|
return ok({
|
|
1353
2265
|
handle: group.value,
|
|
1354
|
-
dynamicOffsets: [0],
|
|
2266
|
+
dynamicOffsets: [0, 0],
|
|
1355
2267
|
release: () => internals.device.destroyBuffer(buffer),
|
|
1356
2268
|
});
|
|
1357
2269
|
};
|
|
@@ -1362,8 +2274,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1362
2274
|
const preparedResolverFactory = (
|
|
1363
2275
|
input: RenderFeaturePreparedGraphicsResolverInput,
|
|
1364
2276
|
): PreparedGraphicsResolver => {
|
|
1365
|
-
activeFeaturePostProcessEntries = input.fullscreenEffects;
|
|
1366
|
-
ensurePostProcessParamsResources(input.fullscreenEffects);
|
|
1367
2277
|
return createPreparedGraphicsResolver({
|
|
1368
2278
|
device: internals.device,
|
|
1369
2279
|
featureIdentity: input.featureIdentity,
|
|
@@ -1387,6 +2297,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1387
2297
|
descriptor.shader,
|
|
1388
2298
|
fullscreen.bindGroupLayout,
|
|
1389
2299
|
descriptor.colorFormats[0] as GPUTextureFormat,
|
|
2300
|
+
postProcessEntry,
|
|
1390
2301
|
);
|
|
1391
2302
|
if (pipeline !== null && pipeline !== undefined) {
|
|
1392
2303
|
preparedPipelineIds.set(pipeline as object, descriptor.shader);
|
|
@@ -1555,6 +2466,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1555
2466
|
gpuDriven: gpuDrivenProduction.inspect(),
|
|
1556
2467
|
};
|
|
1557
2468
|
},
|
|
2469
|
+
get reflectionProbes(): ReflectionProbeInspection {
|
|
2470
|
+
return reflectionProbeOwner.inspect();
|
|
2471
|
+
},
|
|
2472
|
+
isCubeCapturePending(target: RenderTarget): boolean {
|
|
2473
|
+
return cubeCaptureScheduler.inspect(target).candidateGeneration !== undefined;
|
|
2474
|
+
},
|
|
1558
2475
|
get environment(): EnvironmentInspection {
|
|
1559
2476
|
return environmentLifecycle.inspect();
|
|
1560
2477
|
},
|
|
@@ -1647,6 +2564,49 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1647
2564
|
get transmission(): TransmissionInspection | undefined {
|
|
1648
2565
|
return lastTransmissionInspection;
|
|
1649
2566
|
},
|
|
2567
|
+
get directionalShadow(): DirectionalShadowInspection {
|
|
2568
|
+
const lights = lastDirectionalShadowLights;
|
|
2569
|
+
const pipelineState = internals.getPipelineState();
|
|
2570
|
+
const graph = frameState.compiledFrameGraph?.inspect();
|
|
2571
|
+
const requested = directionalShadowProfileFromQuality(lights?.directionalShadowQuality);
|
|
2572
|
+
const mapSize = pipelineState?.perPassResources.shadowMapSize ?? 0;
|
|
2573
|
+
const cascadeCount = lights?.cascadeCount ?? 0;
|
|
2574
|
+
const shadowReady = frameState.currentDirectionalShadowView !== null;
|
|
2575
|
+
const atlasBytes =
|
|
2576
|
+
shadowReady && mapSize > 0 && cascadeCount > 0
|
|
2577
|
+
? mapSize * mapSize * Math.ceil(Math.sqrt(cascadeCount)) ** 2 * 4
|
|
2578
|
+
: 0;
|
|
2579
|
+
return projectDirectionalShadowInspectionSource({
|
|
2580
|
+
requested,
|
|
2581
|
+
candidate: lastDirectionalShadowCandidate,
|
|
2582
|
+
...(frameState.directionalShadowCache === null
|
|
2583
|
+
? {}
|
|
2584
|
+
: {
|
|
2585
|
+
lastKnownGood: directionalShadowProfileFromQuality(
|
|
2586
|
+
frameState.directionalShadowCache.directionalShadowQuality,
|
|
2587
|
+
),
|
|
2588
|
+
}),
|
|
2589
|
+
cascadeCount,
|
|
2590
|
+
mapSize,
|
|
2591
|
+
atlasBytes,
|
|
2592
|
+
writerPasses:
|
|
2593
|
+
graph?.passes.filter((pass) => pass.name.startsWith('shadowCascade')).length ?? 0,
|
|
2594
|
+
shadowAngularRadius: lights?.directionalCsmConfig?.shadowAngularRadius,
|
|
2595
|
+
maxPenumbraTexels: lights?.directionalCsmConfig?.maxPenumbraTexels,
|
|
2596
|
+
deviceGeneration: internals.deviceScope.generation,
|
|
2597
|
+
graphGeneration: frameState.perFrameGraph?.graphGeneration ?? frameState.graphGeneration,
|
|
2598
|
+
shadowReady,
|
|
2599
|
+
...(lastDirectionalShadowError === undefined ? {} : { error: lastDirectionalShadowError }),
|
|
2600
|
+
});
|
|
2601
|
+
},
|
|
2602
|
+
get standardLightingInspection(): StandardLightingInspection | undefined {
|
|
2603
|
+
const inspection = frameState.standardLightingInspection;
|
|
2604
|
+
return inspection === undefined ? undefined : Object.freeze({ ...inspection });
|
|
2605
|
+
},
|
|
2606
|
+
get pointShadowInspection(): PointShadowInspection | undefined {
|
|
2607
|
+
const inspection = frameState.pointShadowInspection;
|
|
2608
|
+
return inspection === undefined ? undefined : Object.freeze({ ...inspection });
|
|
2609
|
+
},
|
|
1650
2610
|
get pointsLinesSnapshots(): readonly PointsLinesRetainedSnapshot[] {
|
|
1651
2611
|
return persistentRenderScene.pointsLinesSnapshots();
|
|
1652
2612
|
},
|
|
@@ -1657,6 +2617,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1657
2617
|
worlds: readonly World[],
|
|
1658
2618
|
opts: DrawOwnerOptions,
|
|
1659
2619
|
renderReadLeases?: readonly RenderReadLease[],
|
|
2620
|
+
timingCapture?: GpuTimingCapture,
|
|
1660
2621
|
): boolean {
|
|
1661
2622
|
preparedWorlds = worlds;
|
|
1662
2623
|
const profileSession = internals.profiler?.activeSession();
|
|
@@ -1729,9 +2690,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1729
2690
|
});
|
|
1730
2691
|
const {
|
|
1731
2692
|
cameras,
|
|
2693
|
+
cubeCameras,
|
|
1732
2694
|
lights,
|
|
1733
2695
|
environment,
|
|
1734
2696
|
environmentReady,
|
|
2697
|
+
volumetricFog,
|
|
1735
2698
|
renderables,
|
|
1736
2699
|
dispatch,
|
|
1737
2700
|
skylight,
|
|
@@ -1739,10 +2702,19 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1739
2702
|
skybox,
|
|
1740
2703
|
skyboxCount,
|
|
1741
2704
|
fog,
|
|
2705
|
+
fogFailure,
|
|
1742
2706
|
frustumStats,
|
|
1743
2707
|
visibilityStats,
|
|
1744
2708
|
postProcessParams,
|
|
1745
2709
|
} = frame;
|
|
2710
|
+
lastDirectionalShadowLights = lights;
|
|
2711
|
+
lastDirectionalShadowCandidate = 'failed';
|
|
2712
|
+
lastDirectionalShadowError = lights.directionalShadowError;
|
|
2713
|
+
const reflectionProbeRecord = reflectionProbeOwner.prepare(
|
|
2714
|
+
frame.reflectionProbes ?? [],
|
|
2715
|
+
persistentRenderScene.reflectionProbeProjection().selected,
|
|
2716
|
+
cameras[0],
|
|
2717
|
+
);
|
|
1746
2718
|
latestCamera = cameras[0];
|
|
1747
2719
|
const temporalDemanded =
|
|
1748
2720
|
cameras[0]?.antialias === 'taa' || (cameras[0]?.motionBlur?.shutterAngle ?? 0) > 0;
|
|
@@ -1750,6 +2722,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1750
2722
|
persistentRenderScene.prepareTemporalFrame(renderables);
|
|
1751
2723
|
}
|
|
1752
2724
|
environmentLifecycle.bindScope(internals.deviceScope);
|
|
2725
|
+
if (fogFailure !== undefined) {
|
|
2726
|
+
environmentLifecycle.recordSelectionFailure(fogFailure);
|
|
2727
|
+
}
|
|
1753
2728
|
if (!environmentReady || environment === undefined) {
|
|
1754
2729
|
frameState.pendingTemporalCommit = { kind: 'none' };
|
|
1755
2730
|
// A world with no camera still has a valid clear-only render path.
|
|
@@ -1778,15 +2753,21 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1778
2753
|
}
|
|
1779
2754
|
if (lastMeshMaterialBindingFrame !== frame) {
|
|
1780
2755
|
lastMeshMaterialBindingFrame = frame;
|
|
1781
|
-
lastMeshMaterialBindings = renderables.map((renderable) =>
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
2756
|
+
lastMeshMaterialBindings = renderables.map((renderable) => {
|
|
2757
|
+
const materialWorld = preparedWorlds[renderable.worldId];
|
|
2758
|
+
return projectMeshMaterialBindingObservation({
|
|
2759
|
+
worldId: renderable.worldId,
|
|
2760
|
+
entityKey: renderable.entityKey,
|
|
2761
|
+
bindings: renderable.materials.map((material, slotIndex) => ({
|
|
2762
|
+
handle: material.materialHandle ?? 0,
|
|
2763
|
+
source: renderable.materialBindingSources[slotIndex] ?? 'engine-default',
|
|
2764
|
+
})),
|
|
2765
|
+
diagnostics: renderable.materialBindingDiagnostics ?? [],
|
|
2766
|
+
residency: renderable.materials.map((material) =>
|
|
2767
|
+
observeMaterialResidency(materialWorld, material, internals),
|
|
2768
|
+
),
|
|
2769
|
+
});
|
|
2770
|
+
});
|
|
1790
2771
|
}
|
|
1791
2772
|
|
|
1792
2773
|
bindGroupCounts.createBindGroup = 0;
|
|
@@ -1852,14 +2833,23 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1852
2833
|
createPreparedGraphicsResolver: preparedResolverFactory,
|
|
1853
2834
|
gpuWork: featureGpuWork,
|
|
1854
2835
|
});
|
|
1855
|
-
activeFeaturePostProcessEntries = featureFrame.fullscreenEffects;
|
|
1856
|
-
ensurePostProcessParamsResources(featureFrame.fullscreenEffects);
|
|
1857
2836
|
lastVisibilityStats.explicitlyHidden = featureFrame.hiddenEntityReports.length;
|
|
1858
2837
|
for (const featureError of featureFrame.errors) {
|
|
1859
2838
|
if (!isPendingRenderFeaturePreparation(featureError)) {
|
|
1860
2839
|
internals.errorRegistry.fire(featureError);
|
|
1861
2840
|
}
|
|
1862
2841
|
}
|
|
2842
|
+
const postProcessParamsCandidate = preparePostProcessParamsResources(
|
|
2843
|
+
featureFrame.fullscreenEffects,
|
|
2844
|
+
);
|
|
2845
|
+
if (!postProcessParamsCandidate.ok) {
|
|
2846
|
+
internals.errorRegistry.fire(postProcessParamsCandidate.error);
|
|
2847
|
+
for (const batch of featureFrame.preparedResourceBatches) {
|
|
2848
|
+
const released = batch.release();
|
|
2849
|
+
if (!released.ok) internals.errorRegistry.fire(released.error);
|
|
2850
|
+
}
|
|
2851
|
+
return [];
|
|
2852
|
+
}
|
|
1863
2853
|
featureGraphCandidate = {
|
|
1864
2854
|
plans: featureFrame.plans,
|
|
1865
2855
|
fullscreenEffects: featureFrame.fullscreenEffects,
|
|
@@ -1873,6 +2863,29 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1873
2863
|
internals.errorRegistry.fire(released.error);
|
|
1874
2864
|
}
|
|
1875
2865
|
}
|
|
2866
|
+
discardPostProcessParamsCandidate(postProcessParamsCandidate.value);
|
|
2867
|
+
},
|
|
2868
|
+
onAbandoned: () => {
|
|
2869
|
+
discardPostProcessParamsCandidate(postProcessParamsCandidate.value);
|
|
2870
|
+
},
|
|
2871
|
+
onAccepted: () => {
|
|
2872
|
+
for (const [id, previous] of activeFeaturePostProcessEntries) {
|
|
2873
|
+
const next = featureFrame.fullscreenEffects.get(id);
|
|
2874
|
+
if (next === undefined) {
|
|
2875
|
+
clearPostProcessPipelineCache(id);
|
|
2876
|
+
invalidatePostProcessModule(id);
|
|
2877
|
+
} else if (
|
|
2878
|
+
postProcessShaderEntrySignature(previous) !==
|
|
2879
|
+
postProcessShaderEntrySignature(next)
|
|
2880
|
+
) {
|
|
2881
|
+
clearPostProcessPipelineEntry(id, previous);
|
|
2882
|
+
if (previous.source !== next.source) {
|
|
2883
|
+
invalidatePostProcessModule(id);
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
acceptPostProcessParamsCandidate(postProcessParamsCandidate.value);
|
|
2888
|
+
activeFeaturePostProcessEntries = featureFrame.fullscreenEffects;
|
|
1876
2889
|
},
|
|
1877
2890
|
};
|
|
1878
2891
|
return featureFrame.preparedResourceBatches;
|
|
@@ -1949,20 +2962,50 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1949
2962
|
{
|
|
1950
2963
|
owner: gpuDrivenProduction,
|
|
1951
2964
|
scene: persistentRenderScene.compositionGpuDrivenState(),
|
|
1952
|
-
onCoverage: (ownsAll) => {
|
|
1953
|
-
const key = `${internals.gpuStore.meshResidencyEpoch}:${
|
|
2965
|
+
onCoverage: (ownsAll, clusteredLighting) => {
|
|
2966
|
+
const key = `${internals.gpuStore.meshResidencyEpoch}:${clusteredLighting ? 'clustered' : 'standard'}`;
|
|
1954
2967
|
persistentRenderScene.setCompositionGpuDrivenCoverage(key, ownsAll);
|
|
1955
2968
|
},
|
|
1956
2969
|
},
|
|
1957
2970
|
renderReadLeases,
|
|
1958
2971
|
featureGraphCandidate,
|
|
1959
2972
|
pointsLinesOwner,
|
|
2973
|
+
{
|
|
2974
|
+
snapshots: cubeCameras,
|
|
2975
|
+
scheduler: cubeCaptureScheduler,
|
|
2976
|
+
state: cubeCaptureState,
|
|
2977
|
+
reflectionProbes: reflectionProbeRecord,
|
|
2978
|
+
} satisfies CubeCaptureFrameInput,
|
|
1960
2979
|
environment?.environmentSignature ?? '',
|
|
1961
2980
|
environment?.fogSignature ?? '',
|
|
1962
2981
|
environmentReady && environment !== undefined,
|
|
1963
2982
|
persistentRenderScene.transmissionTopologyDemand(),
|
|
2983
|
+
volumetricFog,
|
|
2984
|
+
timingCapture,
|
|
1964
2985
|
),
|
|
1965
2986
|
);
|
|
2987
|
+
const directionalQuality = lights.directionalShadowQuality;
|
|
2988
|
+
const directionalShadowReady =
|
|
2989
|
+
directionalQuality === undefined ||
|
|
2990
|
+
(frameState.currentDirectionalShadowView !== null &&
|
|
2991
|
+
(lights.lightViewProj?.length ?? 0) > 0 &&
|
|
2992
|
+
(internals.getPipelineState()?.perPassResources.shadowMapSize ?? 0) > 0);
|
|
2993
|
+
if (submitted && directionalShadowReady) {
|
|
2994
|
+
lastDirectionalShadowCandidate = 'accepted';
|
|
2995
|
+
} else if (directionalQuality !== undefined) {
|
|
2996
|
+
lastDirectionalShadowError = {
|
|
2997
|
+
code: 'directional-shadow-candidate-failed',
|
|
2998
|
+
expected:
|
|
2999
|
+
'the extracted Directional CSM candidate has a valid graph target and fitted matrices',
|
|
3000
|
+
hint: 'inspect the retained LKG and retry after the graph or resource owner recovers',
|
|
3001
|
+
detail: {
|
|
3002
|
+
submitted,
|
|
3003
|
+
hasShadowView: frameState.currentDirectionalShadowView !== null,
|
|
3004
|
+
matrixCount: lights.lightViewProj?.length ?? 0,
|
|
3005
|
+
shadowMapSize: internals.getPipelineState()?.perPassResources.shadowMapSize ?? 0,
|
|
3006
|
+
},
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
1966
3009
|
if (submitted) {
|
|
1967
3010
|
const temporalCommit = persistentRenderScene.commitTemporalFrame();
|
|
1968
3011
|
if (!temporalCommit.ok) {
|
|
@@ -2061,6 +3104,20 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2061
3104
|
frameState.graphTargetCapture = request;
|
|
2062
3105
|
},
|
|
2063
3106
|
bindGroupCounts: bindGroupCounts,
|
|
3107
|
+
get temporalFrame(): TemporalFrame | undefined {
|
|
3108
|
+
const accepted = frameState.temporalFrame;
|
|
3109
|
+
if (accepted === undefined) return undefined;
|
|
3110
|
+
return {
|
|
3111
|
+
...accepted,
|
|
3112
|
+
currentViewProjection: new Float32Array(accepted.currentViewProjection),
|
|
3113
|
+
previousViewProjection:
|
|
3114
|
+
accepted.previousViewProjection === undefined
|
|
3115
|
+
? undefined
|
|
3116
|
+
: new Float32Array(accepted.previousViewProjection),
|
|
3117
|
+
jitter: [accepted.jitter[0], accepted.jitter[1]],
|
|
3118
|
+
viewport: { ...accepted.viewport },
|
|
3119
|
+
};
|
|
3120
|
+
},
|
|
2064
3121
|
frustumStats: lastFrustumStats,
|
|
2065
3122
|
visibilityStats: lastVisibilityStats,
|
|
2066
3123
|
get meshMaterialBindings(): readonly MeshMaterialBindingObservation[] {
|
|
@@ -2075,15 +3132,16 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2075
3132
|
get perFrameGraphInfo(): CompiledRenderGraphInfo | undefined {
|
|
2076
3133
|
return frameState.compiledFrameGraph?.inspect();
|
|
2077
3134
|
},
|
|
3135
|
+
get volumetricFog(): import('./volume/inspection').VolumetricFogInspection {
|
|
3136
|
+
return frameState.volumetricFogInspection;
|
|
3137
|
+
},
|
|
2078
3138
|
configureStandard(config: RenderPipelineAsset['config']): void {
|
|
2079
3139
|
const profileConfig = internals.standardProfile;
|
|
2080
3140
|
const resolvedConfig =
|
|
2081
3141
|
profileConfig === undefined
|
|
2082
3142
|
? config
|
|
2083
3143
|
: {
|
|
2084
|
-
|
|
2085
|
-
? { clusterGrid: DEFAULT_CLUSTER_GRID }
|
|
2086
|
-
: {}),
|
|
3144
|
+
clusterGrid: DEFAULT_CLUSTER_GRID,
|
|
2087
3145
|
...(profileConfig.ssao ? { ssao: { enabled: true } } : {}),
|
|
2088
3146
|
...config,
|
|
2089
3147
|
};
|
|
@@ -2108,16 +3166,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2108
3166
|
// is a rare boot/swap event, never a per-frame cost.
|
|
2109
3167
|
installEpoch += 1;
|
|
2110
3168
|
frameState.installedPipelineHandle = installEpoch;
|
|
2111
|
-
// The installed profile may request clustered lighting, but the actual
|
|
2112
|
-
// graph lane is capability-gated. On WebGL2 (including WebKit's wgpu
|
|
2113
|
-
// fallback) the forward lane is selected when compute/storage buffers
|
|
2114
|
-
// are unavailable; keep the frame bindings on that same lane instead of
|
|
2115
|
-
// advertising HDRP's clustered bind-group layout to URP pipelines.
|
|
2116
|
-
frameState.isHdrpActive =
|
|
2117
|
-
resolveStandardLane(internals.standardProfile ?? DEFAULT_STANDARD_PROFILE, {
|
|
2118
|
-
compute: internals.device.caps.compute,
|
|
2119
|
-
storageBuffer: internals.device.caps.storageBuffer,
|
|
2120
|
-
}) === 'clustered';
|
|
2121
3169
|
frameState.installedPipelineConfig = resolvedConfig;
|
|
2122
3170
|
},
|
|
2123
3171
|
registerBuiltinPostProcess(id: string, entry: PostProcessShaderEntry): () => void {
|
|
@@ -2161,6 +3209,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2161
3209
|
}
|
|
2162
3210
|
builtinPostProcessEntries.set(id, entry);
|
|
2163
3211
|
if (paramsBuffer !== undefined) postProcessParamsBuffers.set(id, paramsBuffer);
|
|
3212
|
+
const moduleLabel = postProcessShaderModuleLabel(id, entry.source);
|
|
2164
3213
|
return () => {
|
|
2165
3214
|
if (builtinPostProcessEntries.get(id) === entry) {
|
|
2166
3215
|
builtinPostProcessEntries.delete(id);
|
|
@@ -2170,9 +3219,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2170
3219
|
internals.device.destroyBuffer(current);
|
|
2171
3220
|
postProcessParamsBuffers.delete(id);
|
|
2172
3221
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
}
|
|
3222
|
+
clearPostProcessPipelineCache(id);
|
|
3223
|
+
internals.invalidateShaderModule?.(moduleLabel);
|
|
2176
3224
|
};
|
|
2177
3225
|
} catch (cause) {
|
|
2178
3226
|
if (paramsBuffer !== undefined) internals.device.destroyBuffer(paramsBuffer);
|
|
@@ -2180,16 +3228,24 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2180
3228
|
}
|
|
2181
3229
|
},
|
|
2182
3230
|
disposeFrameState(): void {
|
|
3231
|
+
(internals as TimingSessionHost).gpuPassTimingSession?.dispose();
|
|
3232
|
+
(internals as TimingSessionHost).gpuPassTimingSession = undefined;
|
|
3233
|
+
(internals as TimingSessionHost).gpuPassTimingCapture = undefined;
|
|
3234
|
+
(internals as TimingSessionHost).gpuPassTimingSubmittedWork = undefined;
|
|
3235
|
+
(internals as TimingSessionHost).gpuPassTimingFrameIdentity = undefined;
|
|
3236
|
+
(internals as TimingSessionHost).gpuPassTimingBeginReason = undefined;
|
|
2183
3237
|
persistentRenderScene.dispose();
|
|
2184
3238
|
gpuDrivenProduction.dispose();
|
|
2185
3239
|
disposeFeatureGpuWork();
|
|
3240
|
+
reflectionProbeOwner.dispose();
|
|
2186
3241
|
// Bloom generations are renderer-owned rather than graph-owned. Drain
|
|
2187
3242
|
// candidate, active, and fence-retiring bundles before the rest of the
|
|
2188
3243
|
// frame state is torn down; the owner callback is idempotent so the
|
|
2189
3244
|
// outer Renderer.dispose cascade can safely call this once only.
|
|
2190
3245
|
internals.getPipelineState()?.perPassResources.drainBloomResources?.();
|
|
2191
3246
|
internals.clearPostProcessPipelineCache?.('forgeax.taa-resolve');
|
|
2192
|
-
|
|
3247
|
+
invalidatePostProcessModule('forgeax.taa-resolve');
|
|
3248
|
+
resetHdrpBuffers(internals);
|
|
2193
3249
|
// Retire graph-owned resources and dispose instance-buffer caches.
|
|
2194
3250
|
// Both calls are idempotent + tolerate per-handle errors silently;
|
|
2195
3251
|
// the Renderer.dispose() cascade owns the surrounding try/catch
|
|
@@ -2214,9 +3270,30 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2214
3270
|
retireTemporalGpuState(retiring);
|
|
2215
3271
|
}
|
|
2216
3272
|
frameState.retiringTemporalGpuStates.clear();
|
|
3273
|
+
settleVolumetricFogGraphCandidate(frameState, false);
|
|
3274
|
+
for (const buffer of frameState.volumetricFogParamsBuffers) {
|
|
3275
|
+
if (buffer === null) continue;
|
|
3276
|
+
const destroyed = internals.device.destroyBuffer(buffer);
|
|
3277
|
+
if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
|
|
3278
|
+
}
|
|
3279
|
+
frameState.volumetricFogParamsBuffers = [null, null];
|
|
3280
|
+
frameState.volumetricFogParamsPendingSlot = null;
|
|
3281
|
+
frameState.volumetricFogParamsAcceptedSlot = null;
|
|
3282
|
+
frameState.volumetricFogAcceptedParams = undefined;
|
|
3283
|
+
frameState.volumetricFogPendingParams = undefined;
|
|
3284
|
+
frameState.volumetricFogAccepted = undefined;
|
|
3285
|
+
frameState.volumetricFogAcceptedContext = undefined;
|
|
3286
|
+
frameState.volumetricFogHistoryGraph = null;
|
|
3287
|
+
frameState.volumetricFogHistorySlot = null;
|
|
3288
|
+
frameState.volumetricFogHistorySignature = null;
|
|
3289
|
+
frameState.hdrpClusterMembership = null;
|
|
2217
3290
|
const compiled = frameState.compiledFrameGraph;
|
|
2218
3291
|
frameState.compiledFrameGraph = null;
|
|
2219
3292
|
frameState.compiledFrameGraphTopologyKey = null;
|
|
3293
|
+
frameState.perFrameGraph = null;
|
|
3294
|
+
frameState.standardLightingGraphSignature = '';
|
|
3295
|
+
frameState.standardLightingInspection = undefined;
|
|
3296
|
+
frameState.pointShadowInspection = undefined;
|
|
2220
3297
|
if (compiled !== null) compiled.retire().catch(() => undefined);
|
|
2221
3298
|
for (const retired of frameState.retiredCompiledFrameGraphs) {
|
|
2222
3299
|
retired.retire().catch(() => undefined);
|
|
@@ -2251,6 +3328,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2251
3328
|
lastTransmissionKey = '';
|
|
2252
3329
|
transmissionCapabilityGeneration = -1;
|
|
2253
3330
|
transmissionCapability = undefined;
|
|
3331
|
+
// The replacement device cannot consume buffers minted by the lost
|
|
3332
|
+
// device. Drop the shared Standard Cluster bundle before the next
|
|
3333
|
+
// frame asks for a new generation; resetHdrpBuffers fences the bundle
|
|
3334
|
+
// using its own device/queue instead of the replacement runtime device.
|
|
3335
|
+
resetHdrpBuffers(internals);
|
|
2254
3336
|
// Keep the persistent material demand observable across device loss.
|
|
2255
3337
|
// The physical resource has already been fenced by markDeviceLost(); the
|
|
2256
3338
|
// detached inspection now exposes the rebuild-required lifecycle until a
|
|
@@ -2260,6 +3342,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2260
3342
|
gpuDrivenProduction.dispose();
|
|
2261
3343
|
gpuDrivenProduction = new GpuDrivenProduction(internals.device, gpuDrivenShaderFactory);
|
|
2262
3344
|
disposeFeatureGpuWork();
|
|
3345
|
+
reflectionProbeOwner.dispose();
|
|
2263
3346
|
// A recovered device cannot retain handles from the lost device. The
|
|
2264
3347
|
// Bloom owner abandons candidates and retires every committed generation
|
|
2265
3348
|
// before the new PipelineState is rebuilt.
|
|
@@ -2269,9 +3352,33 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2269
3352
|
// must be discarded, not merely marked for destruction: their opaque
|
|
2270
3353
|
// handles cannot be used on the fresh device and the next draw must
|
|
2271
3354
|
// lazily build a new graph from the preserved ECS / asset POD caches.
|
|
3355
|
+
settleVolumetricFogGraphCandidate(frameState, false);
|
|
2272
3356
|
frameState.compiledFrameGraph?.retire().catch(() => undefined);
|
|
2273
3357
|
frameState.compiledFrameGraph = null;
|
|
2274
3358
|
frameState.compiledFrameGraphTopologyKey = null;
|
|
3359
|
+
frameState.volumetricFogPreviousGraph = null;
|
|
3360
|
+
frameState.volumetricFogPreviousGraphKey = null;
|
|
3361
|
+
frameState.volumetricFogCandidateGraph = null;
|
|
3362
|
+
frameState.volumetricFogAccepted = undefined;
|
|
3363
|
+
frameState.volumetricFogAcceptedContext = undefined;
|
|
3364
|
+
frameState.volumetricFogHistoryGraph = null;
|
|
3365
|
+
frameState.volumetricFogHistorySlot = null;
|
|
3366
|
+
frameState.volumetricFogHistorySignature = null;
|
|
3367
|
+
// The old device owns these transient params buffers. Discard their
|
|
3368
|
+
// handles without calling destroy() on the lost device; the next frame
|
|
3369
|
+
// must allocate fresh buffers on the replacement device.
|
|
3370
|
+
frameState.volumetricFogParamsBuffers = [null, null];
|
|
3371
|
+
frameState.volumetricFogParamsPendingSlot = null;
|
|
3372
|
+
frameState.volumetricFogParamsAcceptedSlot = null;
|
|
3373
|
+
frameState.volumetricFogAcceptedParams = undefined;
|
|
3374
|
+
frameState.volumetricFogPendingParams = undefined;
|
|
3375
|
+
// Do not expose graph-owned texture/descriptor accessors from the lost
|
|
3376
|
+
// device while recovery is between generations. Inspection and capture
|
|
3377
|
+
// must remain detached until the replacement graph is accepted.
|
|
3378
|
+
frameState.perFrameGraph = null;
|
|
3379
|
+
frameState.standardLightingGraphSignature = '';
|
|
3380
|
+
frameState.standardLightingInspection = undefined;
|
|
3381
|
+
frameState.pointShadowInspection = undefined;
|
|
2275
3382
|
frameState.directionalShadowCache = null;
|
|
2276
3383
|
frameState.directionalShadowCacheRecorded = false;
|
|
2277
3384
|
frameState.currentFrameObservationSource = undefined;
|
|
@@ -2290,6 +3397,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2290
3397
|
retireTemporalGpuState(retiring);
|
|
2291
3398
|
}
|
|
2292
3399
|
frameState.retiringTemporalGpuStates.clear();
|
|
3400
|
+
frameState.hdrpClusterMembership = null;
|
|
2293
3401
|
for (const retired of frameState.retiredCompiledFrameGraphs) {
|
|
2294
3402
|
retired.retire().catch(() => undefined);
|
|
2295
3403
|
}
|