@forgeax/engine-render 0.1.33 → 0.1.35
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 +1062 -127
- package/dist/assembly/backend-contract.d.ts +2 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -1
- package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
- package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
- package/dist/assembly/canvas-draw-guard.d.ts +3 -0
- package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
- package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
- package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
- package/dist/assembly/device-feature-admission.d.ts +2 -2
- package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
- package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
- package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
- package/dist/assembly/factory.d.ts +1 -1
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +6 -5
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
- package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
- package/dist/assembly/material-shader-policy.d.ts +16 -2
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
- package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
- package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/render-feature-post-processes.d.ts +6 -0
- package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
- package/dist/assembly/render-target-host.d.ts +5 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
- package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
- package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-inspection.d.ts +11 -0
- package/dist/assembly/renderer-inspection.d.ts.map +1 -1
- package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
- package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +1 -1
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
- package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +2 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/authoring.mjs +2 -4
- package/dist/authoring.mjs.map +1 -1
- package/dist/barrel-distortion.d.ts +47 -0
- package/dist/barrel-distortion.d.ts.map +1 -0
- package/dist/chunk-3XUBWRY2.mjs +1842 -0
- package/dist/chunk-3XUBWRY2.mjs.map +1 -0
- package/dist/{chunk-OO3DRYK7.mjs → chunk-G6HDNGY7.mjs} +2400 -1767
- package/dist/chunk-G6HDNGY7.mjs.map +1 -0
- package/dist/{chunk-ZDQWGQW7.mjs → chunk-LM5IE7GF.mjs} +4 -4
- package/dist/{chunk-ZDQWGQW7.mjs.map → chunk-LM5IE7GF.mjs.map} +1 -1
- package/dist/{chunk-VOZWDM3L.mjs → chunk-LNIUIKQ7.mjs} +1514 -498
- package/dist/chunk-LNIUIKQ7.mjs.map +1 -0
- package/dist/chunk-MXTWKZO4.mjs +3268 -0
- package/dist/chunk-MXTWKZO4.mjs.map +1 -0
- package/dist/chunk-MZ4C7EHF.mjs +6942 -0
- package/dist/chunk-MZ4C7EHF.mjs.map +1 -0
- package/dist/chunk-RIPAEGI6.mjs +48 -0
- package/dist/chunk-RIPAEGI6.mjs.map +1 -0
- package/dist/{chunk-LEAXOMYJ.mjs → chunk-SGPUJW3F.mjs} +117 -5
- package/dist/chunk-SGPUJW3F.mjs.map +1 -0
- package/dist/{chunk-PA5DRJNI.mjs → chunk-X2KA6WHM.mjs} +29 -4
- package/dist/chunk-X2KA6WHM.mjs.map +1 -0
- package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
- package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
- package/dist/cloud/density.d.ts +55 -0
- package/dist/cloud/density.d.ts.map +1 -0
- package/dist/cloud/extract.d.ts +25 -0
- package/dist/cloud/extract.d.ts.map +1 -0
- package/dist/cloud/feature.d.ts +57 -0
- package/dist/cloud/feature.d.ts.map +1 -0
- package/dist/cloud/frame-context.d.ts +8 -0
- package/dist/cloud/frame-context.d.ts.map +1 -0
- package/dist/cloud/inspection.d.ts +59 -0
- package/dist/cloud/inspection.d.ts.map +1 -0
- package/dist/cloud/optics.d.ts +56 -0
- package/dist/cloud/optics.d.ts.map +1 -0
- package/dist/cloud/parameters.d.ts +73 -0
- package/dist/cloud/parameters.d.ts.map +1 -0
- package/dist/cloud/resources.d.ts +60 -0
- package/dist/cloud/resources.d.ts.map +1 -0
- package/dist/cloud/shadow.d.ts +42 -0
- package/dist/cloud/shadow.d.ts.map +1 -0
- package/dist/cloud/temporal.d.ts +74 -0
- package/dist/cloud/temporal.d.ts.map +1 -0
- package/dist/components/barrel-distortion.d.ts +13 -0
- package/dist/components/barrel-distortion.d.ts.map +1 -0
- package/dist/components/cloud-layer.d.ts +29 -0
- package/dist/components/cloud-layer.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/instances.d.ts +20 -6
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/construct-renderer.d.ts +1 -1
- package/dist/construct-renderer.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +8406 -5785
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +19 -19
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/dynamic-geometry.d.ts +5 -1
- package/dist/dynamic-geometry.d.ts.map +1 -1
- package/dist/errors/cloud.d.ts +59 -0
- package/dist/errors/cloud.d.ts.map +1 -0
- package/dist/errors/render.d.ts +20 -5
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/examples/single-layer-medium-surface.d.ts +47 -0
- package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +5 -1
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/extract/material-context.d.ts +7 -0
- package/dist/extract/material-context.d.ts.map +1 -0
- package/dist/features/barrel-distortion.d.ts +29 -0
- package/dist/features/barrel-distortion.d.ts.map +1 -0
- package/dist/features/error-reporting.d.ts +6 -2
- package/dist/features/error-reporting.d.ts.map +1 -1
- package/dist/features/host.d.ts +15 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/plan.d.ts +10 -0
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts +5 -0
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/features/render-graph-raster.d.ts +1 -0
- package/dist/features/render-graph-raster.d.ts.map +1 -1
- package/dist/features/targets.d.ts +2 -0
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +48 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +15 -7
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/glyph-text-layout-system.d.ts +1 -1
- package/dist/glyph-text-layout-system.d.ts.map +1 -1
- package/dist/gpu-dirty-ranges.d.ts +11 -0
- package/dist/gpu-dirty-ranges.d.ts.map +1 -0
- package/dist/gpu-driven/batch-topology.d.ts +17 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/material-artifacts.d.ts +6 -0
- package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster-material.d.ts +178 -0
- package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +47 -3
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/resource-allocation.d.ts +31 -0
- package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
- package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
- package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
- package/dist/gpu-driven/view-gpu.d.ts +26 -2
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +5 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +2 -2
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/frame-resources.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +14 -5
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +30 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +780 -9
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +135 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instance-buffer-cache.d.ts +4 -4
- package/dist/instance-buffer-cache.d.ts.map +1 -1
- package/dist/instances.d.ts +47 -100
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +7 -8
- package/dist/internal.mjs.map +1 -1
- package/dist/pbr-pipeline.d.ts +22 -4
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
- package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
- package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
- package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
- package/dist/pipeline/standard-output/resources.d.ts +2 -2
- package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
- package/dist/pipeline/standard-output/types.d.ts +4 -2
- package/dist/pipeline/standard-output/types.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +1 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
- package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
- package/dist/publication/contract.d.ts +77 -0
- package/dist/publication/contract.d.ts.map +1 -0
- package/dist/publication/dependencies.d.ts +7 -0
- package/dist/publication/dependencies.d.ts.map +1 -0
- package/dist/publication/prepare-geometry.d.ts +9 -0
- package/dist/publication/prepare-geometry.d.ts.map +1 -0
- package/dist/publication/programs.d.ts +5 -0
- package/dist/publication/programs.d.ts.map +1 -0
- package/dist/publication/publisher.d.ts +33 -0
- package/dist/publication/publisher.d.ts.map +1 -0
- package/dist/publication/receiver.d.ts +29 -0
- package/dist/publication/receiver.d.ts.map +1 -0
- package/dist/publication/resource-scope.d.ts +18 -0
- package/dist/publication/resource-scope.d.ts.map +1 -0
- package/dist/publication/targets.d.ts +33 -0
- package/dist/publication/targets.d.ts.map +1 -0
- package/dist/publication/video.d.ts +5 -0
- package/dist/publication/video.d.ts.map +1 -0
- package/dist/record/barrel-distortion-frame.d.ts +15 -0
- package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
- package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
- package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
- package/dist/record/fold-buckets.d.ts +95 -0
- package/dist/record/fold-buckets.d.ts.map +1 -0
- package/dist/record/fold-instance-buffer.d.ts +6 -0
- package/dist/record/fold-instance-buffer.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +1 -0
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +43 -9
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts +15 -65
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-draw-receipts.d.ts +11 -0
- package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
- package/dist/record/gpu-timing.d.ts +8 -0
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/helpers.d.ts +2 -2
- package/dist/record/helpers.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +22 -6
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +14 -10
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +3 -16
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +13 -2
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/motion-blur-receipt.d.ts +11 -0
- package/dist/record/motion-blur-receipt.d.ts.map +1 -0
- package/dist/record/prepared-material-bindings.d.ts +3 -3
- package/dist/record/prepared-material-bindings.d.ts.map +1 -1
- package/dist/record/probe-blend-buffer.d.ts +16 -0
- package/dist/record/probe-blend-buffer.d.ts.map +1 -0
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +24 -5
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +2 -0
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/sprite-instance-buffer.d.ts +31 -0
- package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
- package/dist/record/target-capture-graph.d.ts +19 -0
- package/dist/record/target-capture-graph.d.ts.map +1 -0
- package/dist/record/typed-frame-graph.d.ts +24 -15
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +11 -3
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/record/volume-params.d.ts +9 -0
- package/dist/record/volume-params.d.ts.map +1 -0
- package/dist/recovery/render-system-candidate.d.ts +14 -4
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +46 -5
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +103 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +3 -2
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +18 -9
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system-presentation.d.ts +23 -0
- package/dist/render-system-presentation.d.ts.map +1 -0
- package/dist/render-system-producer-roots.d.ts +10 -0
- package/dist/render-system-producer-roots.d.ts.map +1 -0
- package/dist/render-system-projections.d.ts +3 -2
- package/dist/render-system-projections.d.ts.map +1 -1
- package/dist/render-system-residency.d.ts +12 -0
- package/dist/render-system-residency.d.ts.map +1 -0
- package/dist/render-system.d.ts +22 -21
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/probe-blend.d.ts +27 -0
- package/dist/scene/probe-blend.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +18 -7
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/render-source.d.ts +42 -0
- package/dist/scene/render-source.d.ts.map +1 -0
- package/dist/scene/source-systems.d.ts +6 -0
- package/dist/scene/source-systems.d.ts.map +1 -0
- package/dist/ssao-buffers.d.ts +9 -0
- package/dist/ssao-buffers.d.ts.map +1 -1
- package/dist/surface/dynamic-input.d.ts +133 -0
- package/dist/surface/dynamic-input.d.ts.map +1 -0
- package/dist/surface/gpu-driven.d.ts +41 -0
- package/dist/surface/gpu-driven.d.ts.map +1 -0
- package/dist/surface/submission-observation.d.ts +136 -0
- package/dist/surface/submission-observation.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/systems/skin-palette-types.d.ts +10 -0
- package/dist/systems/skin-palette-types.d.ts.map +1 -1
- package/dist/systems/transparent-dispatch.d.ts +2 -2
- package/dist/systems/transparent-dispatch.d.ts.map +1 -1
- package/dist/systems/transparent-sort-config.d.ts +2 -1
- package/dist/systems/transparent-sort-config.d.ts.map +1 -1
- package/dist/temporal/gpu.d.ts +13 -1
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -7
- package/dist/temporal/inspection.d.ts.map +1 -1
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
- package/dist/transmission/single-layer-medium.d.ts +89 -0
- package/dist/transmission/single-layer-medium.d.ts.map +1 -0
- package/dist/typed-render-graph-primitives.d.ts +14 -2
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/component.d.ts +9 -4
- package/dist/volume/component.d.ts.map +1 -1
- package/dist/volume/extract.d.ts +1 -1
- package/dist/volume/extract.d.ts.map +1 -1
- package/dist/volume/inspection.d.ts +2 -0
- package/dist/volume/inspection.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +4 -1
- package/dist/volume/passes.d.ts.map +1 -1
- package/dist/volume/resources.d.ts.map +1 -1
- package/package.json +22 -21
- package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
- package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
- package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
- package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
- package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
- package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
- package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
- package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
- package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
- package/src/__tests__/factory-contract.integration.test.ts +295 -45
- package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
- package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
- package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +5 -6
- package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +6 -5
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +94 -6
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
- package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
- package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
- package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
- package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
- package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
- package/src/__tests__/gpu-scene.unit.test.ts +119 -9
- package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
- package/src/__tests__/instances-culling.integration.test.ts +10 -36
- package/src/__tests__/instances-store.unit.test.ts +97 -305
- package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
- package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +145 -0
- package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
- package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
- package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
- package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
- package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
- package/src/__tests__/motion-blur-compute.dawn.test.ts +13 -7
- package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
- package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
- package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +66 -8
- package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
- package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
- package/src/__tests__/probe-bindings.unit.test.ts +582 -1
- package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
- package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +8 -0
- package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
- package/src/__tests__/render-feature-error-reporting.unit.test.ts +21 -1
- package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
- package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
- package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
- package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +20 -20
- package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
- package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
- package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +42 -8
- package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +26 -10
- package/src/__tests__/resource-allocation.unit.test.ts +123 -0
- package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
- package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
- package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +15 -0
- package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
- package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
- package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
- package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
- package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
- package/src/__tests__/standard-pipeline.integration.test.ts +182 -1
- package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
- package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
- package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
- package/src/__tests__/temporal-performance.integration.test.ts +3 -3
- package/src/__tests__/visibility-instances.integration.test.ts +7 -19
- package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
- package/src/__tests__/volume-optics-oracle.unit.test.ts +4 -2
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +30 -20
- package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
- package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
- package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +113 -9
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +5 -3
- package/src/assembly/backend-contract.ts +30 -0
- package/src/assembly/barrel-distortion-inspection.ts +22 -0
- package/src/assembly/canvas-draw-guard.ts +11 -0
- package/src/assembly/cloud-shadow-entry.ts +22 -0
- package/src/assembly/device-feature-admission.ts +2 -2
- package/src/assembly/dynamic-geometry-host.ts +65 -37
- package/src/assembly/dynamic-geometry-runtime.ts +7 -1
- package/src/assembly/factory.ts +2 -30
- package/src/assembly/host-contract.ts +8 -5
- package/src/assembly/material/assembly.ts +25 -13
- package/src/assembly/material/pipeline-helpers.ts +53 -12
- package/src/assembly/material-shader-policy.ts +55 -0
- package/src/assembly/material-shader-prewarm.ts +21 -10
- package/src/assembly/recovery/device-loss-fanout.ts +13 -0
- package/src/assembly/recovery/renderer-recover.ts +50 -18
- package/src/assembly/render-feature-post-processes.ts +54 -0
- package/src/assembly/render-target-host.ts +57 -47
- package/src/assembly/renderer-facade.ts +7 -2
- package/src/assembly/renderer-feature-inspection.ts +8 -2
- package/src/assembly/renderer-frame-transaction.ts +23 -0
- package/src/assembly/renderer-inspection.ts +28 -0
- package/src/assembly/skin-palette-owner.ts +1 -1
- package/src/assembly/temporal-post-process-prewarm.ts +3 -3
- package/src/assembly/webgpu-pbr-ready.ts +9 -1
- package/src/assembly/webgpu-ready-per-pass.ts +104 -0
- package/src/assembly/webgpu-ready.ts +116 -62
- package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
- package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
- package/src/assembly/webgpu-renderer.ts +303 -313
- package/src/assembly/webgpu-vertex-layouts.ts +1 -1
- package/src/barrel-distortion.ts +247 -0
- package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
- package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
- package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
- package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
- package/src/cloud/density.ts +600 -0
- package/src/cloud/extract.ts +110 -0
- package/src/cloud/feature.ts +1810 -0
- package/src/cloud/frame-context.ts +42 -0
- package/src/cloud/inspection.ts +130 -0
- package/src/cloud/optics.ts +285 -0
- package/src/cloud/parameters.ts +255 -0
- package/src/cloud/resources.ts +122 -0
- package/src/cloud/shadow.ts +167 -0
- package/src/cloud/temporal.ts +226 -0
- package/src/components/barrel-distortion.ts +46 -0
- package/src/components/cloud-layer.ts +47 -0
- package/src/components/index.ts +2 -0
- package/src/components/instances.ts +18 -45
- package/src/components/sprite-instances.ts +1 -1
- package/src/construct-renderer.ts +1 -1
- package/src/device/gpu-residency.ts +26 -25
- package/src/dynamic-geometry.ts +12 -1
- package/src/errors/cloud.ts +103 -0
- package/src/errors/render.ts +47 -8
- package/src/examples/single-layer-medium-surface.ts +59 -0
- package/src/extract/camera.ts +15 -1
- package/src/extract/contracts.ts +5 -0
- package/src/extract/material-context.ts +21 -0
- package/src/features/barrel-distortion.ts +172 -0
- package/src/features/error-reporting.ts +26 -6
- package/src/features/host.ts +128 -19
- package/src/features/plan.ts +10 -0
- package/src/features/render-graph-contribution.ts +9 -0
- package/src/features/render-graph-raster.ts +45 -3
- package/src/features/targets.ts +66 -0
- package/src/features/types.ts +51 -0
- package/src/fullscreen-post-process-pass.ts +41 -9
- package/src/glyph-text-layout-system.ts +2 -2
- package/src/gpu-dirty-ranges.ts +48 -0
- package/src/gpu-driven/batch-topology.ts +53 -0
- package/src/gpu-driven/material-artifacts.ts +33 -25
- package/src/gpu-driven/prepared-draw.ts +12 -4
- package/src/gpu-driven/production-raster-material.ts +1894 -0
- package/src/gpu-driven/production-raster.ts +825 -784
- package/src/gpu-driven/resource-allocation.ts +142 -0
- package/src/gpu-driven/shadow-ownership.ts +71 -0
- package/src/gpu-driven/view-gpu.ts +164 -10
- package/src/gpu-scene.ts +75 -84
- package/src/hdrp-buffers.ts +4 -3
- package/src/ibl/frame-resources.ts +0 -1
- package/src/ibl/skylight-bind-group.ts +62 -32
- package/src/index.ts +205 -9
- package/src/inspection-types.ts +162 -0
- package/src/instance-buffer-cache.ts +4 -4
- package/src/instances.ts +152 -569
- package/src/pbr-pipeline.ts +230 -30
- package/src/pipeline/single-layer-medium-passes.ts +267 -0
- package/src/pipeline/standard-forward-lane.ts +313 -57
- package/src/pipeline/standard-output/graph.ts +8 -2
- package/src/pipeline/standard-output/lut-gpu.ts +3 -3
- package/src/pipeline/standard-output/resources.ts +2 -2
- package/src/pipeline/standard-output/types.ts +12 -1
- package/src/pipeline/standard-pipeline.ts +291 -18
- package/src/pipeline/standard-post.ts +90 -24
- package/src/pipeline-builder.ts +1 -1
- package/src/plugin.ts +4 -0
- package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
- package/src/prepare/prepared-graphics-resolver.ts +2 -0
- package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
- package/src/publication/__tests__/features.integration.test.ts +122 -0
- package/src/publication/__tests__/programs.unit.test.ts +25 -0
- package/src/publication/__tests__/publication.integration.test.ts +370 -0
- package/src/publication/__tests__/targets.integration.test.ts +120 -0
- package/src/publication/__tests__/video.browser.test.ts +50 -0
- package/src/publication/contract.ts +104 -0
- package/src/publication/dependencies.ts +54 -0
- package/src/publication/prepare-geometry.ts +94 -0
- package/src/publication/programs.ts +45 -0
- package/src/publication/publisher.ts +618 -0
- package/src/publication/receiver.ts +297 -0
- package/src/publication/resource-scope.ts +31 -0
- package/src/publication/targets.ts +182 -0
- package/src/publication/video.ts +33 -0
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
- package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +9 -11
- package/src/record/__tests__/temporal-payload.unit.test.ts +1 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
- package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
- package/src/record/barrel-distortion-frame.ts +110 -0
- package/src/record/dynamic-geometry-consumption.ts +39 -9
- package/src/record/fold-buckets.ts +855 -0
- package/src/record/fold-instance-buffer.ts +51 -0
- package/src/record/frame-lighting.ts +36 -2
- package/src/record/frame-snapshot.ts +45 -9
- package/src/record/frame.ts +258 -1105
- package/src/record/gpu-draw-receipts.ts +38 -0
- package/src/record/gpu-timing.ts +48 -5
- package/src/record/helpers.ts +2 -2
- package/src/record/main-pass-geometry.ts +272 -84
- package/src/record/main-pass-material.ts +102 -36
- package/src/record/main-pass-sprite-draws.ts +13 -217
- package/src/record/main-pass.ts +164 -27
- package/src/record/motion-blur-receipt.ts +91 -0
- package/src/record/prepared-material-bindings.ts +11 -6
- package/src/record/probe-blend-buffer.ts +180 -0
- package/src/record/recovery-pipeline.ts +35 -3
- package/src/record/render-context.ts +41 -5
- package/src/record/shadow-pass.ts +30 -4
- package/src/record/skybox-post-pass.ts +28 -4
- package/src/record/sprite-instance-buffer.ts +155 -0
- package/src/record/target-capture-graph.ts +258 -0
- package/src/record/typed-frame-graph.ts +776 -340
- package/src/record/view-ubo.ts +27 -4
- package/src/record/volume-params.ts +225 -0
- package/src/recovery/render-system-candidate.ts +243 -50
- package/src/reflection/record-owner.ts +7 -3
- package/src/render-contract.ts +54 -5
- package/src/render-graph-primitives.ts +7 -1
- package/src/render-pipeline.ts +130 -0
- package/src/render-system-extract-tail.ts +90 -78
- package/src/render-system-extract.ts +76 -76
- package/src/render-system-presentation.ts +56 -0
- package/src/render-system-producer-roots.ts +96 -0
- package/src/render-system-projections.ts +3 -2
- package/src/render-system-residency.ts +159 -0
- package/src/render-system.ts +421 -408
- package/src/scene/probe-blend.ts +96 -4
- package/src/scene/render-scene.ts +416 -337
- package/src/scene/render-source.ts +90 -0
- package/src/scene/source-systems.ts +33 -0
- package/src/ssao-buffers.ts +95 -2
- package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
- package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
- package/src/surface/dynamic-input.ts +658 -0
- package/src/surface/gpu-driven.ts +147 -0
- package/src/surface/submission-observation.ts +517 -0
- package/src/systems/skin-palette-allocator.ts +5 -1
- package/src/systems/skin-palette-types.ts +11 -0
- package/src/systems/transparent-dispatch.ts +2 -2
- package/src/systems/transparent-sort-config.ts +3 -1
- package/src/temporal/gpu.ts +101 -17
- package/src/temporal/inspection.ts +7 -1
- package/src/temporal/standard-scene-data.ts +5 -0
- package/src/temporal/target-coverage-attachment.ts +4 -0
- package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
- package/src/transmission/single-layer-medium.ts +322 -0
- package/src/typed-render-graph-primitives.ts +70 -2
- package/src/volume/component.ts +10 -9
- package/src/volume/extract.ts +11 -5
- package/src/volume/inspection.ts +3 -0
- package/src/volume/passes.ts +87 -4
- package/src/volume/resources.ts +2 -1
- package/dist/chunk-3RTCJTH4.mjs +0 -1610
- package/dist/chunk-3RTCJTH4.mjs.map +0 -1
- package/dist/chunk-BLXGONR2.mjs.map +0 -1
- package/dist/chunk-ILQ5MEZF.mjs +0 -32
- package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
- package/dist/chunk-LEAXOMYJ.mjs.map +0 -1
- package/dist/chunk-NB2S7ZRX.mjs +0 -1783
- package/dist/chunk-NB2S7ZRX.mjs.map +0 -1
- package/dist/chunk-NNQL7CB3.mjs +0 -1713
- package/dist/chunk-NNQL7CB3.mjs.map +0 -1
- package/dist/chunk-OO3DRYK7.mjs.map +0 -1
- package/dist/chunk-PA5DRJNI.mjs.map +0 -1
- package/dist/chunk-PLAXOOX5.mjs +0 -1907
- package/dist/chunk-PLAXOOX5.mjs.map +0 -1
- package/dist/chunk-RXZ3RGOU.mjs +0 -53
- package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
- package/dist/chunk-VOZWDM3L.mjs.map +0 -1
package/src/record/frame.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { encodeAnalyticFogParams } from '../environment/analytic-fog';
|
|
2
2
|
import type { FogFrame } from '../extract/environment';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
collectGpuDrivenMaterialArtifacts,
|
|
5
|
+
requiresProbeBlendRecord,
|
|
6
|
+
} from '../gpu-driven/material-artifacts';
|
|
7
|
+
import { projectMaterialBindingClasses } from '../gpu-driven/material-bindings';
|
|
8
|
+
import type { RenderResourceScope } from '../publication/resource-scope';
|
|
9
|
+
import { renderAssetByGuid, renderTime } from '../publication/resource-scope';
|
|
10
|
+
import {
|
|
11
|
+
buildDispatchPlan,
|
|
12
|
+
cleanPerFrameCaches,
|
|
13
|
+
computeFoldBuckets,
|
|
14
|
+
validateRenderables,
|
|
15
|
+
} from './fold-buckets';
|
|
5
16
|
import { uploadMaterialUniforms } from './material-uniforms';
|
|
6
17
|
// @forgeax/engine-runtime - RenderSystem record stage: frame.
|
|
7
18
|
// Extracted from render-system-record.ts (feat-20260704 M3/w17, pure move).
|
|
8
19
|
|
|
9
|
-
import {
|
|
10
|
-
import { Time, type World } from '@forgeax/engine-ecs';
|
|
20
|
+
import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
|
|
11
21
|
import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
|
|
12
|
-
import { deriveVertexCount } from '@forgeax/engine-geometry';
|
|
13
22
|
import { frustum, mat4, vec3 } from '@forgeax/engine-math';
|
|
14
23
|
import {
|
|
15
24
|
type CurrentFrameObservationLease,
|
|
@@ -25,8 +34,12 @@ import {
|
|
|
25
34
|
type TextureFormat,
|
|
26
35
|
type TextureView,
|
|
27
36
|
} from '@forgeax/engine-rhi';
|
|
28
|
-
import type {
|
|
37
|
+
import type { MaterialShaderArtifact } from '@forgeax/engine-shader';
|
|
38
|
+
import type { MeshAsset } from '@forgeax/engine-types';
|
|
29
39
|
import { BUILTIN_BASE, handleSlot, toShared } from '@forgeax/engine-types';
|
|
40
|
+
import type { ExtractedCloudLayer } from '../cloud/extract';
|
|
41
|
+
import { cloudShadowResolutionForQuality } from '../cloud/parameters';
|
|
42
|
+
import { type CloudShadowProjection, createCloudShadowProjection } from '../cloud/shadow';
|
|
30
43
|
import { CUBE_CAMERA_FACE_ORDER } from '../components/cube-camera';
|
|
31
44
|
import type { DirectionalShadowQuality } from '../components/directional-shadow-filter';
|
|
32
45
|
import type { MeshGpuHandles } from '../device/gpu-residency';
|
|
@@ -35,7 +48,7 @@ import {
|
|
|
35
48
|
type ObservationUnavailableReason,
|
|
36
49
|
RendererOperationError,
|
|
37
50
|
} from '../errors/render';
|
|
38
|
-
import {
|
|
51
|
+
import { BARREL_DISTORTION_POST_PROCESS_ID } from '../features/barrel-distortion';
|
|
39
52
|
import {
|
|
40
53
|
DEPTH_OF_FIELD_MSAA_POST_PROCESS_IDS,
|
|
41
54
|
DEPTH_OF_FIELD_POST_PROCESS_IDS,
|
|
@@ -47,15 +60,8 @@ import {
|
|
|
47
60
|
import type { GpuDrivenProduction, PreparedGpuDrivenFrame } from '../gpu-driven/production-raster';
|
|
48
61
|
import type { ShadowViewIdentity } from '../gpu-driven/shadow-views';
|
|
49
62
|
import type { GpuDrivenLodSubmitIdentity } from '../gpu-driven/view-gpu';
|
|
50
|
-
import { GpuBuffer } from '../gpu-resource';
|
|
51
63
|
import { GPU_TEXTURE_USAGE_COPY_SRC } from '../gpu-texture-usage';
|
|
52
|
-
import {
|
|
53
|
-
GPU_BUFFER_USAGE_COPY_DST,
|
|
54
|
-
GPU_BUFFER_USAGE_MAP_READ,
|
|
55
|
-
GPU_BUFFER_USAGE_UNIFORM,
|
|
56
|
-
GPU_BUFFER_USAGE_VERTEX,
|
|
57
|
-
} from '../gpu-usage';
|
|
58
|
-
import { disposeTransientInstanceBuffers } from '../instance-buffer-cache';
|
|
64
|
+
import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_MAP_READ } from '../gpu-usage';
|
|
59
65
|
import { deriveRenderExtent } from '../pipeline/render-extent';
|
|
60
66
|
import { inspectStandardLighting } from '../pipeline/standard-lighting/inspection';
|
|
61
67
|
import { standardLightingTopologySignature } from '../pipeline/standard-lighting/topology';
|
|
@@ -71,7 +77,6 @@ import type {
|
|
|
71
77
|
DispatchEntry,
|
|
72
78
|
ExtractedLights,
|
|
73
79
|
ExtractedVolumetricFog,
|
|
74
|
-
MaterialSnapshot,
|
|
75
80
|
RenderableSnapshot,
|
|
76
81
|
ShadowCasterMembership,
|
|
77
82
|
SkyboxSnapshot,
|
|
@@ -89,14 +94,10 @@ import {
|
|
|
89
94
|
type SsrSpatialAdmission,
|
|
90
95
|
} from '../ssr/admission';
|
|
91
96
|
import { createSsrHistoryOwner } from '../ssr/history';
|
|
92
|
-
import {
|
|
93
|
-
getTransparentSortConfig,
|
|
94
|
-
TRANSPARENT_SORT_MODE_LAYER_Y,
|
|
95
|
-
TRANSPARENT_SORT_MODE_LAYER_Z,
|
|
96
|
-
} from '../systems/transparent-sort-config';
|
|
97
|
+
import type { SurfaceDynamicInputFrame } from '../surface/dynamic-input';
|
|
97
98
|
import type { TransmissionDemand } from '../transmission/projection';
|
|
98
99
|
import { hasVolumetricFogCapability } from '../volume/capability';
|
|
99
|
-
import { inspectVolumetricFogResources
|
|
100
|
+
import { inspectVolumetricFogResources } from '../volume/resources';
|
|
100
101
|
import { resolveVolumeTemporalReset, type VolumeTemporalSignature } from '../volume/temporal';
|
|
101
102
|
import {
|
|
102
103
|
buildPerFrameBindGroups,
|
|
@@ -114,7 +115,6 @@ import {
|
|
|
114
115
|
type DispatchCounts,
|
|
115
116
|
type FrameObservationSource,
|
|
116
117
|
getTextureIdentity,
|
|
117
|
-
instanceCollectionCacheKey,
|
|
118
118
|
makeZeroCameraFallbackSnapshot,
|
|
119
119
|
type ReflectionFallbackReadbackRequest,
|
|
120
120
|
type RenderFrameState,
|
|
@@ -122,28 +122,22 @@ import {
|
|
|
122
122
|
validateGraphTargetCaptureReadback,
|
|
123
123
|
worldEntityKey,
|
|
124
124
|
} from './frame-snapshot';
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// entries with equal (Layer.value, sortKey, materialHandle) into FoldBucket
|
|
128
|
-
// descriptors. The drawIndexed swap (1 instanced draw per bucket vs N
|
|
129
|
-
// per-entity draws) hooks into the sprite-pass dispatch loop below
|
|
130
|
-
// (w4-record-swap), using {@link buildFoldDispatchPlan} to translate
|
|
131
|
-
// renderableIndex-keyed buckets into validatedOrdered-index-keyed
|
|
132
|
-
// head/skip maps.
|
|
133
|
-
import {
|
|
134
|
-
buildFoldDispatchPlan,
|
|
135
|
-
evaluateFoldBucketUniformCap,
|
|
136
|
-
type FoldBucket,
|
|
137
|
-
type FoldDispatchPlan,
|
|
138
|
-
foldDispatchBuckets,
|
|
139
|
-
incrementFoldedDrawsMetric,
|
|
140
|
-
} from './mesh-ssbo';
|
|
125
|
+
import { emitGpuDrivenDrawReceipts, type RenderableDrawReceipt } from './gpu-draw-receipts';
|
|
126
|
+
import { ensureProbeBlendRecordBuffer } from './probe-blend-buffer';
|
|
141
127
|
import type {
|
|
142
128
|
_InternalRenderPipelineContext,
|
|
143
129
|
PipelineState,
|
|
144
130
|
RenderSystemInternals,
|
|
145
131
|
} from './render-context';
|
|
146
|
-
|
|
132
|
+
import { prepareVolumetricFogFrame, stageVolumetricFogParams } from './volume-params';
|
|
133
|
+
|
|
134
|
+
export {
|
|
135
|
+
buildDispatchPlan,
|
|
136
|
+
buildMaterialSlotPlan,
|
|
137
|
+
findRenderablePrefixForSlotCapacity,
|
|
138
|
+
materialSlotCountForPrefix,
|
|
139
|
+
validateRenderables,
|
|
140
|
+
} from './fold-buckets';
|
|
147
141
|
export type { FrameObservationSource } from './frame-snapshot';
|
|
148
142
|
|
|
149
143
|
export type FrameObservationReadback = (
|
|
@@ -151,42 +145,20 @@ export type FrameObservationReadback = (
|
|
|
151
145
|
) => Promise<Result<Uint8Array, Error>>;
|
|
152
146
|
|
|
153
147
|
/**
|
|
154
|
-
* Fold
|
|
148
|
+
* Fold RenderResourceScope identity into the renderer-owned validation token. The GPU
|
|
155
149
|
* production owner receives only this opaque key; ECS identity and ordering
|
|
156
|
-
* stay in the record owner where the
|
|
150
|
+
* stay in the record owner where the RenderResourceScope list is authoritative.
|
|
157
151
|
*/
|
|
158
|
-
function residencyValidationCacheKey(
|
|
152
|
+
function residencyValidationCacheKey(
|
|
153
|
+
world: RenderResourceScope,
|
|
154
|
+
worlds: readonly RenderResourceScope[],
|
|
155
|
+
): string {
|
|
159
156
|
return [world.identity, ...worlds.map((candidate) => candidate.identity)].join('\u001f');
|
|
160
157
|
}
|
|
161
|
-
|
|
162
158
|
function renderableIdentity(snapshot: Pick<RenderableSnapshot, 'worldId' | 'entityKey'>): string {
|
|
163
159
|
return `${snapshot.worldId}:${snapshot.entityKey}`;
|
|
164
160
|
}
|
|
165
161
|
|
|
166
|
-
/**
|
|
167
|
-
* A GPU-owned indirect draw has no CPU submit callback. Keep the same
|
|
168
|
-
* record-stage consumption proof for downstream lifecycle owners by checking
|
|
169
|
-
* the concrete draw-key projection that was actually encoded for this frame.
|
|
170
|
-
*/
|
|
171
|
-
function gpuDrivenRenderableConsumed(
|
|
172
|
-
entry: ValidatedRenderable,
|
|
173
|
-
drawKeys: ReadonlySet<string>,
|
|
174
|
-
): boolean {
|
|
175
|
-
const draws = entry.source.gpuDrivenDraws;
|
|
176
|
-
if (draws === undefined || draws.length === 0) return false;
|
|
177
|
-
const worldEntity = worldEntityKey(entry.source.worldId, entry.source.entityKey);
|
|
178
|
-
return draws.some((draw, compactIndex) => {
|
|
179
|
-
const material = entry.source.materials[draw.materialSlot] ?? entry.source.material;
|
|
180
|
-
return drawKeys.has(
|
|
181
|
-
gpuDrivenDrawKey(
|
|
182
|
-
worldEntity,
|
|
183
|
-
material.materialHandle ?? -1,
|
|
184
|
-
gpuDrivenSourceDrawItemIndex(draw, compactIndex),
|
|
185
|
-
),
|
|
186
|
-
);
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
162
|
/**
|
|
191
163
|
* Project one shared validation result into a cull-specific renderable index
|
|
192
164
|
* domain. The retained ShadowCaster projection is the superset; the primary
|
|
@@ -317,6 +289,10 @@ import {
|
|
|
317
289
|
} from '../features/motion-blur/motion-blur-params';
|
|
318
290
|
import { createTemporalView, resolveTemporalReset, type TemporalView } from '../temporal/view';
|
|
319
291
|
import { inspectVolumetricFog } from '../volume/inspection';
|
|
292
|
+
import {
|
|
293
|
+
packBarrelDistortionParams,
|
|
294
|
+
resolveSubmittedBarrelMapping,
|
|
295
|
+
} from './barrel-distortion-frame';
|
|
320
296
|
import {
|
|
321
297
|
acquireSwapChainTarget,
|
|
322
298
|
graphExecutionPhase,
|
|
@@ -329,12 +305,11 @@ import {
|
|
|
329
305
|
computeViewMatrix,
|
|
330
306
|
driveLazyEquirectProjection,
|
|
331
307
|
selectLazyEquirectHandle,
|
|
332
|
-
variantSetFromDefines,
|
|
333
308
|
warnMultiSkybox,
|
|
334
309
|
warnMultiSkylight,
|
|
335
310
|
} from './helpers';
|
|
336
311
|
import { computeSplitLdrSprite } from './main-pass-sprite-draws';
|
|
337
|
-
import {
|
|
312
|
+
import { uploadMeshSsboBatch } from './mesh-ssbo';
|
|
338
313
|
import { writeShadowCasterUniforms } from './shadow-pass';
|
|
339
314
|
import {
|
|
340
315
|
type CubeCaptureGraphState,
|
|
@@ -342,6 +317,7 @@ import {
|
|
|
342
317
|
executeCompiledFrameGraph,
|
|
343
318
|
getRenderFeatureGraphState,
|
|
344
319
|
type RenderFeatureGraphCandidate,
|
|
320
|
+
settleBarrelDistortionGraphCandidate,
|
|
345
321
|
settleDepthOfFieldGraphCandidate,
|
|
346
322
|
settleVolumetricFogGraphCandidate,
|
|
347
323
|
} from './typed-frame-graph';
|
|
@@ -355,7 +331,7 @@ import {
|
|
|
355
331
|
/**
|
|
356
332
|
* Resolve the GPU mesh view used by the optional production raster before its
|
|
357
333
|
* filtering pass runs. The result is keyed by the retained RenderScene slot,
|
|
358
|
-
* which preserves the
|
|
334
|
+
* which preserves the RenderResourceScope namespace already used by gpuStore. User meshes
|
|
359
335
|
* are pull-resident during record; relying only on the boot-time builtin map
|
|
360
336
|
* would make the first frame classify every imported mesh as a CPU fallback
|
|
361
337
|
* and would hide its LOD ranges from the GPU cull. Builtins are the only
|
|
@@ -364,7 +340,7 @@ import {
|
|
|
364
340
|
export function gpuDrivenMeshesForFrame(
|
|
365
341
|
internals: RenderSystemInternals,
|
|
366
342
|
pipelineState: PipelineState,
|
|
367
|
-
worlds: readonly
|
|
343
|
+
worlds: readonly RenderResourceScope[],
|
|
368
344
|
scene: PersistentGpuDrivenState | undefined,
|
|
369
345
|
): ReadonlyMap<number, MeshGpuHandles> {
|
|
370
346
|
const meshes = new Map<number, MeshGpuHandles>();
|
|
@@ -385,7 +361,7 @@ export function gpuDrivenMeshesForFrame(
|
|
|
385
361
|
const asset = resolveAssetHandle<MeshAsset>(world, sharedHandle);
|
|
386
362
|
if (asset.ok) {
|
|
387
363
|
const lodMeshes = slot.snapshot.lods?.map((lod) =>
|
|
388
|
-
|
|
364
|
+
renderAssetByGuid<MeshAsset>(world, internals.assets, lod.mesh),
|
|
389
365
|
);
|
|
390
366
|
const resolvedLodMeshes =
|
|
391
367
|
lodMeshes?.every((lod): lod is MeshAsset => lod?.kind === 'mesh') === true
|
|
@@ -405,115 +381,12 @@ export function gpuDrivenMeshesForFrame(
|
|
|
405
381
|
return meshes;
|
|
406
382
|
}
|
|
407
383
|
|
|
408
|
-
/**
|
|
409
|
-
* A frame-local material table. Repeated snapshot objects share one UBO slot;
|
|
410
|
-
* each renderable keeps only the slot index used by each authored material.
|
|
411
|
-
*/
|
|
412
|
-
export interface MaterialSlotPlan<T extends object> {
|
|
413
|
-
readonly slotIndices: readonly (readonly number[])[];
|
|
414
|
-
readonly slots: readonly T[];
|
|
415
|
-
/** Index of the first renderable that referenced each slot. */
|
|
416
|
-
readonly slotOwners: readonly number[];
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Intern identical material snapshots into one frame-local slot table.
|
|
421
|
-
* Snapshot identity is the extract layer's invalidation token, so this keeps
|
|
422
|
-
* the same correctness boundary while avoiding duplicate payload assembly and
|
|
423
|
-
* upload for repeated scene instances.
|
|
424
|
-
*/
|
|
425
|
-
export function buildMaterialSlotPlan<T extends object>(
|
|
426
|
-
materialGroups: readonly (readonly T[])[],
|
|
427
|
-
bindingClass?: (material: T, owner: number) => string,
|
|
428
|
-
): MaterialSlotPlan<T> {
|
|
429
|
-
const slotByMaterial = new Map<T, Map<string, number>>();
|
|
430
|
-
const slots: T[] = [];
|
|
431
|
-
const slotOwners: number[] = [];
|
|
432
|
-
const slotIndices = materialGroups.map((materials, ownerIndex) =>
|
|
433
|
-
materials.map((material) => {
|
|
434
|
-
const resourceClass = bindingClass?.(material, ownerIndex) ?? '';
|
|
435
|
-
let materialClasses = slotByMaterial.get(material);
|
|
436
|
-
if (materialClasses === undefined) {
|
|
437
|
-
materialClasses = new Map();
|
|
438
|
-
slotByMaterial.set(material, materialClasses);
|
|
439
|
-
}
|
|
440
|
-
const cached = materialClasses.get(resourceClass);
|
|
441
|
-
if (cached !== undefined) return cached;
|
|
442
|
-
const slot = slots.length;
|
|
443
|
-
slots.push(material);
|
|
444
|
-
slotOwners.push(ownerIndex);
|
|
445
|
-
materialClasses.set(resourceClass, slot);
|
|
446
|
-
return slot;
|
|
447
|
-
}),
|
|
448
|
-
);
|
|
449
|
-
return { slotIndices, slots, slotOwners };
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Return the largest complete renderable prefix supported by a shared
|
|
454
|
-
* mesh/material capacity. Mesh rows consume one slot per renderable; material
|
|
455
|
-
* rows use the deduplicated slot indices assigned in first-seen order.
|
|
456
|
-
*/
|
|
457
|
-
export function findRenderablePrefixForSlotCapacity(
|
|
458
|
-
materialSlotIndices: readonly (readonly number[])[],
|
|
459
|
-
slotCapacity: number,
|
|
460
|
-
): number {
|
|
461
|
-
const capacity = Math.max(0, Math.floor(slotCapacity));
|
|
462
|
-
const maxRenderableCount = Math.min(materialSlotIndices.length, capacity);
|
|
463
|
-
let renderableCount = 0;
|
|
464
|
-
while (renderableCount < maxRenderableCount) {
|
|
465
|
-
const slots = materialSlotIndices[renderableCount] ?? [];
|
|
466
|
-
if (slots.some((slot) => slot >= capacity)) break;
|
|
467
|
-
renderableCount += 1;
|
|
468
|
-
}
|
|
469
|
-
return renderableCount;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/** Count material slots reachable from a prefix of a first-seen slot plan. */
|
|
473
|
-
export function materialSlotCountForPrefix(
|
|
474
|
-
materialSlotIndices: readonly (readonly number[])[],
|
|
475
|
-
renderableCount: number,
|
|
476
|
-
): number {
|
|
477
|
-
let maxSlot = -1;
|
|
478
|
-
for (let index = 0; index < renderableCount; index += 1) {
|
|
479
|
-
for (const slot of materialSlotIndices[index] ?? []) maxSlot = Math.max(maxSlot, slot);
|
|
480
|
-
}
|
|
481
|
-
return maxSlot + 1;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
type MeshSsboCapacityResult = ReturnType<typeof ensureMeshSsboCapacity>;
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Resolve the actual mesh-SSBO ceiling after the shared grow contract ran.
|
|
488
|
-
* A missing grow controller is the legacy/test surface with no renderer-owned
|
|
489
|
-
* ceiling; a failed grow explicitly exposes the pre-grow capacity and must
|
|
490
|
-
* constrain every row written by this frame, including retained shadow rows.
|
|
491
|
-
*/
|
|
492
|
-
function meshSsboSlotCapacityAfterEnsure(
|
|
493
|
-
internals: Pick<RenderSystemInternals, 'growMeshSsbo' | 'meshSsboState'>,
|
|
494
|
-
neededSlots: number,
|
|
495
|
-
result: MeshSsboCapacityResult,
|
|
496
|
-
): number {
|
|
497
|
-
if (internals.growMeshSsbo === undefined) return Number.POSITIVE_INFINITY;
|
|
498
|
-
if (!result.ok) return Math.max(0, result.degradedToSlotCount);
|
|
499
|
-
return internals.meshSsboState?.slotCount ?? neededSlots;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Keep a row sequence inside the capacity published by the mesh-SSBO owner.
|
|
504
|
-
* The caller has already reserved earlier rows (the primary view), so this
|
|
505
|
-
* helper only accepts the remaining slots for the retained shadow sequence.
|
|
506
|
-
*/
|
|
507
|
-
function limitRowsToMeshSsboCapacity<T>(rows: readonly T[], availableSlots: number): readonly T[] {
|
|
508
|
-
if (availableSlots === Number.POSITIVE_INFINITY) return rows;
|
|
509
|
-
return rows.slice(0, Math.max(0, Math.floor(availableSlots)));
|
|
510
|
-
}
|
|
511
|
-
|
|
512
384
|
export type RecordProfileRunner = <T>(phase: RenderRecordPhase, action: () => T) => T;
|
|
513
385
|
|
|
514
386
|
export interface CubeCaptureFrameInput {
|
|
515
387
|
readonly captureDispatch?: readonly DispatchEntry[];
|
|
516
388
|
readonly snapshots: readonly CubeCameraSnapshot[];
|
|
389
|
+
readonly auxiliaryCameras: readonly CameraSnapshot[];
|
|
517
390
|
readonly scheduler: import('../capture/scheduler').CubeCaptureScheduler;
|
|
518
391
|
readonly state: CubeCaptureGraphState;
|
|
519
392
|
readonly reflectionProbes?: import('./render-context').ReflectionProbeRecordState;
|
|
@@ -569,6 +442,18 @@ function prepareCubeCapture(
|
|
|
569
442
|
(CUBE_CAPTURE_VIEW_SLOT_BASE + physicalWork.length) * VIEW_UNIFORM_SLOT_STRIDE,
|
|
570
443
|
});
|
|
571
444
|
}
|
|
445
|
+
for (const camera of input.auxiliaryCameras) {
|
|
446
|
+
if (camera.target === undefined) continue;
|
|
447
|
+
const physical = internals.getRenderTargetPhysical?.(camera.target);
|
|
448
|
+
if (physical?.descriptor.shape !== '2d') continue;
|
|
449
|
+
physicalWork.push({
|
|
450
|
+
target: camera.target,
|
|
451
|
+
faceIndex: 0,
|
|
452
|
+
physical,
|
|
453
|
+
faceCamera: camera,
|
|
454
|
+
viewBindGroupDynamicOffset: (CUBE_CAPTURE_VIEW_SLOT_BASE + 6) * VIEW_UNIFORM_SLOT_STRIDE,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
572
457
|
input.state.work = physicalWork;
|
|
573
458
|
return physicalWork;
|
|
574
459
|
}
|
|
@@ -738,46 +623,6 @@ async function mapReflectionFallbackReadback(
|
|
|
738
623
|
|
|
739
624
|
const VOLUMETRIC_FOG_PASS_COUNT = 4;
|
|
740
625
|
|
|
741
|
-
type VolumetricFogParamsSlot = 0 | 1;
|
|
742
|
-
|
|
743
|
-
function nextVolumetricFogParamsSlot(frameState: RenderFrameState): VolumetricFogParamsSlot {
|
|
744
|
-
const accepted = frameState.volumetricFogParamsAcceptedSlot;
|
|
745
|
-
const pending = frameState.volumetricFogParamsPendingSlot;
|
|
746
|
-
if (pending !== null && pending !== accepted) return pending;
|
|
747
|
-
return accepted === null ? 0 : accepted === 0 ? 1 : 0;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
function stageVolumetricFogParams(
|
|
751
|
-
internals: RenderSystemInternals,
|
|
752
|
-
frameState: RenderFrameState,
|
|
753
|
-
params: Float32Array,
|
|
754
|
-
): Buffer | undefined {
|
|
755
|
-
const slot = nextVolumetricFogParamsSlot(frameState);
|
|
756
|
-
let buffer = frameState.volumetricFogParamsBuffers[slot];
|
|
757
|
-
if (buffer === null) {
|
|
758
|
-
const created = internals.device.createBuffer({
|
|
759
|
-
label: `volumetric-fog-params-${slot}`,
|
|
760
|
-
size: VOLUMETRIC_FOG_PARAMS_BYTES,
|
|
761
|
-
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
762
|
-
mappedAtCreation: false,
|
|
763
|
-
});
|
|
764
|
-
if (!created.ok) {
|
|
765
|
-
internals.errorRegistry.fire(created.error);
|
|
766
|
-
return undefined;
|
|
767
|
-
}
|
|
768
|
-
buffer = created.value;
|
|
769
|
-
frameState.volumetricFogParamsBuffers[slot] = buffer;
|
|
770
|
-
}
|
|
771
|
-
const written = internals.device.queue.writeBuffer(buffer, 0, params);
|
|
772
|
-
if (!written.ok) {
|
|
773
|
-
internals.errorRegistry.fire(written.error);
|
|
774
|
-
return undefined;
|
|
775
|
-
}
|
|
776
|
-
frameState.volumetricFogParamsPendingSlot = slot;
|
|
777
|
-
frameState.volumetricFogPendingParams = new Float32Array(params);
|
|
778
|
-
return buffer;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
626
|
/** Retire parameter buffers only after the submit that stopped using them. */
|
|
782
627
|
function retireVolumetricFogParams(
|
|
783
628
|
internals: RenderSystemInternals,
|
|
@@ -814,6 +659,19 @@ function volumeRevision(values: readonly number[]): number {
|
|
|
814
659
|
return hash;
|
|
815
660
|
}
|
|
816
661
|
|
|
662
|
+
/** Resolve the stable world-space projection shared by cloud and lighting. */
|
|
663
|
+
function cloudShadowProjectionFor(
|
|
664
|
+
cloudLayer: ExtractedCloudLayer | undefined,
|
|
665
|
+
): CloudShadowProjection | undefined {
|
|
666
|
+
if (cloudLayer?.sunDirection === undefined) return undefined;
|
|
667
|
+
return createCloudShadowProjection({
|
|
668
|
+
center: [0, 0, 0],
|
|
669
|
+
sunDirection: cloudLayer.sunDirection,
|
|
670
|
+
range: cloudLayer.params.shadowRange,
|
|
671
|
+
resolution: cloudShadowResolutionForQuality(cloudLayer.params.quality),
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
817
675
|
function volumeTemporalSignature(
|
|
818
676
|
fog: ExtractedVolumetricFog,
|
|
819
677
|
prepared: VolumetricFogFrameContext,
|
|
@@ -835,7 +693,7 @@ function volumeTemporalSignature(
|
|
|
835
693
|
camera.orthoBottom,
|
|
836
694
|
camera.orthoTop,
|
|
837
695
|
]);
|
|
838
|
-
|
|
696
|
+
let fogRevision =
|
|
839
697
|
authored === undefined
|
|
840
698
|
? 0
|
|
841
699
|
: volumeRevision([
|
|
@@ -846,6 +704,8 @@ function volumeTemporalSignature(
|
|
|
846
704
|
...authored.emission,
|
|
847
705
|
authored.anisotropy,
|
|
848
706
|
authored.maxDistance,
|
|
707
|
+
authored.sampling === 'density' ? 1 : 0,
|
|
708
|
+
...Array.from(`${fog.guid}:${fog.digest}`, (char) => char.charCodeAt(0)),
|
|
849
709
|
]);
|
|
850
710
|
const selectedSpot =
|
|
851
711
|
fog.spotLightEntity === undefined
|
|
@@ -863,7 +723,7 @@ function volumeTemporalSignature(
|
|
|
863
723
|
fog.lightKind === 'directional' && directional?.entity === fog.lightEntity
|
|
864
724
|
? directional
|
|
865
725
|
: undefined;
|
|
866
|
-
|
|
726
|
+
let lightRevision =
|
|
867
727
|
selectedSpot === undefined && selectedPoint === undefined && selectedDirectional === undefined
|
|
868
728
|
? 0
|
|
869
729
|
: volumeRevision(
|
|
@@ -902,12 +762,30 @@ function volumeTemporalSignature(
|
|
|
902
762
|
selectedSpot.normalBias ?? 0.05,
|
|
903
763
|
selectedSpot.pcfKernelSize ?? 3,
|
|
904
764
|
selectedSpot.shadowAtlasTile,
|
|
765
|
+
Number(selectedSpot.projectorHandle ?? 0),
|
|
766
|
+
selectedSpot.projectorGeneration ?? 0,
|
|
767
|
+
selectedSpot.projectorRevision ?? 0,
|
|
905
768
|
...(selectedSpot.lightViewProj === undefined
|
|
906
769
|
? []
|
|
907
770
|
: [...selectedSpot.lightViewProj]),
|
|
908
771
|
]),
|
|
909
772
|
],
|
|
910
773
|
);
|
|
774
|
+
for (const [index, member] of (fog.additional ?? []).entries()) {
|
|
775
|
+
const signature = volumeTemporalSignature(
|
|
776
|
+
member,
|
|
777
|
+
{
|
|
778
|
+
...prepared,
|
|
779
|
+
densityGeneration: prepared.additional?.[index]?.densityGeneration ?? 0,
|
|
780
|
+
},
|
|
781
|
+
camera,
|
|
782
|
+
lights,
|
|
783
|
+
width,
|
|
784
|
+
height,
|
|
785
|
+
);
|
|
786
|
+
fogRevision = volumeRevision([fogRevision, signature.fogRevision, signature.densityGeneration]);
|
|
787
|
+
lightRevision = volumeRevision([lightRevision, signature.lightRevision]);
|
|
788
|
+
}
|
|
911
789
|
return {
|
|
912
790
|
cameraRevision,
|
|
913
791
|
fogRevision,
|
|
@@ -969,137 +847,6 @@ function resolveVolumetricFogHistoryContext(
|
|
|
969
847
|
};
|
|
970
848
|
}
|
|
971
849
|
|
|
972
|
-
/**
|
|
973
|
-
* Resolve the authored density POD into the renderer-owned GPU resources used
|
|
974
|
-
* by the graph. This is deliberately a pull operation: a missing/stale
|
|
975
|
-
* handle or failed upload produces a structured error and leaves the graph
|
|
976
|
-
* candidate disabled for this frame rather than binding a synthetic density.
|
|
977
|
-
*/
|
|
978
|
-
function prepareVolumetricFogFrame(
|
|
979
|
-
internals: RenderSystemInternals,
|
|
980
|
-
frameState: RenderFrameState,
|
|
981
|
-
world: World,
|
|
982
|
-
worlds: readonly World[],
|
|
983
|
-
fog: ExtractedVolumetricFog | undefined,
|
|
984
|
-
lights: ExtractedLights,
|
|
985
|
-
volumeCapability: boolean,
|
|
986
|
-
projector: SpotLightProjectorFrameContext | undefined,
|
|
987
|
-
): VolumetricFogFrameContext | undefined {
|
|
988
|
-
if (
|
|
989
|
-
!volumeCapability ||
|
|
990
|
-
fog?.status !== 'available' ||
|
|
991
|
-
fog.fog === undefined ||
|
|
992
|
-
fog.densityHandle === undefined ||
|
|
993
|
-
fog.densityAsset === undefined ||
|
|
994
|
-
(fog.lightKind === 'spot'
|
|
995
|
-
? lights.spot.find((light) => light.entity === fog.lightEntity) === undefined
|
|
996
|
-
: fog.lightKind === 'point'
|
|
997
|
-
? lights.point.find((light) => light.entity === fog.lightEntity) === undefined
|
|
998
|
-
: lights.directional === undefined)
|
|
999
|
-
) {
|
|
1000
|
-
return undefined;
|
|
1001
|
-
}
|
|
1002
|
-
const ownerWorld = worlds[fog.worldId ?? 0] ?? world;
|
|
1003
|
-
const resident = internals.gpuStore.ensureResident(
|
|
1004
|
-
fog.densityHandle,
|
|
1005
|
-
fog.densityAsset,
|
|
1006
|
-
ownerWorld,
|
|
1007
|
-
);
|
|
1008
|
-
if (!resident.ok || !('texture' in resident.value)) {
|
|
1009
|
-
if (!resident.ok) {
|
|
1010
|
-
internals.errorRegistry.fire(
|
|
1011
|
-
resident.error instanceof RhiError
|
|
1012
|
-
? resident.error
|
|
1013
|
-
: new RhiError({
|
|
1014
|
-
code: 'webgpu-runtime-error',
|
|
1015
|
-
expected: 'volumetric density residency to resolve a GPU texture',
|
|
1016
|
-
hint: 'repair the authored TextureAsset payload and retry the next frame',
|
|
1017
|
-
detail: { error: resident.error },
|
|
1018
|
-
}),
|
|
1019
|
-
);
|
|
1020
|
-
}
|
|
1021
|
-
return undefined;
|
|
1022
|
-
}
|
|
1023
|
-
const params = new Float32Array(36);
|
|
1024
|
-
params.set(fog.fog.bounds.min, 0);
|
|
1025
|
-
params.set(fog.fog.bounds.max, 4);
|
|
1026
|
-
params.set(fog.fog.extinction, 8);
|
|
1027
|
-
params.set(fog.fog.albedo, 12);
|
|
1028
|
-
params.set(fog.fog.emission, 16);
|
|
1029
|
-
const selectedPoint =
|
|
1030
|
-
fog.pointLightEntity === undefined
|
|
1031
|
-
? undefined
|
|
1032
|
-
: lights.point.find((light) => light.entity === fog.pointLightEntity);
|
|
1033
|
-
const selectedSpot =
|
|
1034
|
-
fog.spotLightEntity === undefined
|
|
1035
|
-
? fog.lightKind === 'spot'
|
|
1036
|
-
? lights.spot.find((light) => light.entity === fog.lightEntity)
|
|
1037
|
-
: undefined
|
|
1038
|
-
: lights.spot.find((light) => light.entity === fog.spotLightEntity);
|
|
1039
|
-
const lightDirection =
|
|
1040
|
-
selectedSpot?.direction ?? lights.directional?.direction ?? vec3.create(0, -1, 0);
|
|
1041
|
-
const lightColor = selectedSpot?.color ?? lights.directional?.color ?? vec3.create(0, 0, 0);
|
|
1042
|
-
if (lightDirection === undefined || lightColor === undefined) return undefined;
|
|
1043
|
-
params.set(lightDirection, 20);
|
|
1044
|
-
// Reserved padding: render frame identity must not move volume samples.
|
|
1045
|
-
params[23] = 0;
|
|
1046
|
-
const selectedPointIndex =
|
|
1047
|
-
selectedPoint === undefined
|
|
1048
|
-
? -1
|
|
1049
|
-
: lights.point.findIndex((light) => light.entity === selectedPoint.entity);
|
|
1050
|
-
const selectedSpotIndex =
|
|
1051
|
-
selectedSpot === undefined
|
|
1052
|
-
? -1
|
|
1053
|
-
: lights.spot.findIndex((light) => light.entity === selectedSpot.entity);
|
|
1054
|
-
// Cluster light_data is one point/spot/rect array. Point slots are emitted
|
|
1055
|
-
// first, so volume stores the global slot rather than a per-kind array index.
|
|
1056
|
-
const selectedSpotClusterIndex =
|
|
1057
|
-
selectedSpotIndex < 0 ? -1 : lights.point.length + selectedSpotIndex;
|
|
1058
|
-
params.set(lightColor, 24);
|
|
1059
|
-
params[19] = selectedPointIndex;
|
|
1060
|
-
params[27] = selectedSpotClusterIndex;
|
|
1061
|
-
params[28] = fog.fog.maxDistance;
|
|
1062
|
-
params[29] = fog.fog.anisotropy;
|
|
1063
|
-
params[30] =
|
|
1064
|
-
selectedPoint !== undefined && selectedSpot !== undefined
|
|
1065
|
-
? 3
|
|
1066
|
-
: selectedPoint !== undefined
|
|
1067
|
-
? 1
|
|
1068
|
-
: selectedSpot !== undefined
|
|
1069
|
-
? 2
|
|
1070
|
-
: 0;
|
|
1071
|
-
// The temporal shader reads this lane after the record stage resolves the
|
|
1072
|
-
// accepted graph/slot. It is overwritten on the next queue-visible frame;
|
|
1073
|
-
// the first candidate deliberately starts with history disabled.
|
|
1074
|
-
params[31] =
|
|
1075
|
-
frameState.temporalFrame !== undefined && frameState.temporalFrame.resetReason === 'none'
|
|
1076
|
-
? 1
|
|
1077
|
-
: 0;
|
|
1078
|
-
// World elapsed time is the sole authored-medium clock. The adjacent
|
|
1079
|
-
// frame phase above remains the deterministic sub-froxel jitter identity;
|
|
1080
|
-
// it never advances density motion.
|
|
1081
|
-
params[32] = fog.worldTimeSeconds ?? 0;
|
|
1082
|
-
|
|
1083
|
-
const paramsBuffer = stageVolumetricFogParams(internals, frameState, params);
|
|
1084
|
-
if (paramsBuffer === undefined) return undefined;
|
|
1085
|
-
return {
|
|
1086
|
-
densityTexture: resident.value.texture.handle,
|
|
1087
|
-
densityView: resident.value.view,
|
|
1088
|
-
paramsBuffer,
|
|
1089
|
-
densityGeneration: resident.value.receipt.generation,
|
|
1090
|
-
historyReadSlot: null,
|
|
1091
|
-
historyWriteSlot: 0,
|
|
1092
|
-
historyValid: false,
|
|
1093
|
-
...(projector === undefined
|
|
1094
|
-
? {}
|
|
1095
|
-
: {
|
|
1096
|
-
projectorTexture: projector.texture,
|
|
1097
|
-
projectorView: projector.view,
|
|
1098
|
-
projectorSampler: projector.sampler,
|
|
1099
|
-
}),
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
850
|
/**
|
|
1104
851
|
* Resolve the SpotLight-owned cookie independently of volumetric fog. Surface
|
|
1105
852
|
* lighting must keep the authored projector when the fog component is absent;
|
|
@@ -1110,8 +857,8 @@ function prepareVolumetricFogFrame(
|
|
|
1110
857
|
*/
|
|
1111
858
|
function prepareSpotLightProjectorFrame(
|
|
1112
859
|
internals: RenderSystemInternals,
|
|
1113
|
-
world:
|
|
1114
|
-
worlds: readonly
|
|
860
|
+
world: RenderResourceScope,
|
|
861
|
+
worlds: readonly RenderResourceScope[],
|
|
1115
862
|
lights: ExtractedLights,
|
|
1116
863
|
fog: ExtractedVolumetricFog | undefined,
|
|
1117
864
|
): SpotLightProjectorFrameContext | undefined {
|
|
@@ -1252,15 +999,15 @@ interface FrameClockSample {
|
|
|
1252
999
|
|
|
1253
1000
|
/** Read the render sample clock from the camera's composition world once per frame. */
|
|
1254
1001
|
function frameClockSample(
|
|
1255
|
-
world:
|
|
1256
|
-
worlds: readonly
|
|
1002
|
+
world: RenderResourceScope,
|
|
1003
|
+
worlds: readonly RenderResourceScope[],
|
|
1257
1004
|
camera: CameraSnapshot,
|
|
1258
1005
|
sampleTimeSeconds?: number,
|
|
1259
1006
|
): FrameClockSample {
|
|
1260
1007
|
const owner = worlds[camera.worldId ?? 0] ?? world;
|
|
1261
1008
|
const invalidSampleTime = sampleTimeSeconds !== undefined && !Number.isFinite(sampleTimeSeconds);
|
|
1262
1009
|
try {
|
|
1263
|
-
const clock = owner
|
|
1010
|
+
const clock = renderTime(owner);
|
|
1264
1011
|
return {
|
|
1265
1012
|
deltaSeconds: Number.isFinite(clock.delta) ? Math.max(0, clock.delta) : 0,
|
|
1266
1013
|
// Keep the raw host sample even when ECS accepted only a clamped
|
|
@@ -1277,7 +1024,7 @@ function frameClockSample(
|
|
|
1277
1024
|
invalidSampleTime,
|
|
1278
1025
|
};
|
|
1279
1026
|
} catch {
|
|
1280
|
-
// Test and recovery callers may provide a minimal
|
|
1027
|
+
// Test and recovery callers may provide a minimal RenderResourceScope-shaped owner. An
|
|
1281
1028
|
// absent clock is an invalid temporal pair and therefore safely resets.
|
|
1282
1029
|
return {
|
|
1283
1030
|
deltaSeconds: 0,
|
|
@@ -1642,7 +1389,7 @@ function updateVolumetricFogInspection(
|
|
|
1642
1389
|
|
|
1643
1390
|
export function recordFrame(
|
|
1644
1391
|
internals: RenderSystemInternals,
|
|
1645
|
-
world:
|
|
1392
|
+
world: RenderResourceScope,
|
|
1646
1393
|
cameras: CameraSnapshot[],
|
|
1647
1394
|
lights: ExtractedLights,
|
|
1648
1395
|
renderables: readonly RenderableSnapshot[],
|
|
@@ -1662,7 +1409,7 @@ export function recordFrame(
|
|
|
1662
1409
|
// the per-world extract stage). Optional + defaulting to `[world]` keeps the
|
|
1663
1410
|
// pre-split single-world identity path (worldId always 0) byte-for-byte and
|
|
1664
1411
|
// the existing unit-test callers (which pass a single mock world) valid.
|
|
1665
|
-
worlds: readonly
|
|
1412
|
+
worlds: readonly RenderResourceScope[] = [world],
|
|
1666
1413
|
profilePhase?: RecordProfileRunner,
|
|
1667
1414
|
gpuDriven?: {
|
|
1668
1415
|
readonly owner: GpuDrivenProduction;
|
|
@@ -1673,6 +1420,10 @@ export function recordFrame(
|
|
|
1673
1420
|
readonly telemetryCandidateCount?: number;
|
|
1674
1421
|
readonly telemetrySubmit?: GpuDrivenLodSubmitIdentity;
|
|
1675
1422
|
readonly occlusion?: OcclusionFrameProjection;
|
|
1423
|
+
readonly frameTime?: number;
|
|
1424
|
+
readonly deviceGeneration?: number;
|
|
1425
|
+
readonly surfaceDynamicInput?: SurfaceDynamicInputFrame;
|
|
1426
|
+
readonly geometryLane?: 'automatic' | 'direct';
|
|
1676
1427
|
},
|
|
1677
1428
|
renderReadLeases?: readonly RenderReadLease[],
|
|
1678
1429
|
featureGraphCandidate?: RenderFeatureGraphCandidate,
|
|
@@ -1691,10 +1442,15 @@ export function recordFrame(
|
|
|
1691
1442
|
shadowCasterWorldKeys?: readonly number[],
|
|
1692
1443
|
preparedLighting?: ReturnType<typeof prepareFrameLighting>,
|
|
1693
1444
|
ssrDependencies?: SsrDependenciesInspection,
|
|
1694
|
-
onRenderableDraw?: (
|
|
1445
|
+
onRenderableDraw?: (
|
|
1446
|
+
entry: ValidatedRenderable,
|
|
1447
|
+
submeshIndex?: number,
|
|
1448
|
+
receipt?: RenderableDrawReceipt,
|
|
1449
|
+
) => void,
|
|
1695
1450
|
analyticFog?: FogFrame,
|
|
1696
1451
|
sampleTimeSeconds?: number,
|
|
1697
1452
|
temporalReset = false,
|
|
1453
|
+
cloudLayer?: ExtractedCloudLayer,
|
|
1698
1454
|
): boolean {
|
|
1699
1455
|
void shadowCasterEntityKeys;
|
|
1700
1456
|
frameState.reflectionFallbackObservationSource = undefined;
|
|
@@ -1905,6 +1661,7 @@ export function recordFrame(
|
|
|
1905
1661
|
lights,
|
|
1906
1662
|
volumetricFog,
|
|
1907
1663
|
);
|
|
1664
|
+
const cloudShadowProjection = cloudShadowProjectionFor(cloudLayer);
|
|
1908
1665
|
|
|
1909
1666
|
const preparedVolumetricFog = prepareVolumetricFogFrame(
|
|
1910
1667
|
internals,
|
|
@@ -1951,21 +1708,29 @@ export function recordFrame(
|
|
|
1951
1708
|
(volumetricFog?.status === 'degraded' && acceptedFallback !== undefined);
|
|
1952
1709
|
|
|
1953
1710
|
let preparedGpuDriven: PreparedGpuDrivenFrame | undefined;
|
|
1711
|
+
let acceptedMaterialArtifacts: ReadonlyMap<string, MaterialShaderArtifact> | undefined;
|
|
1712
|
+
let acceptedShadowMaterialArtifacts: ReadonlyMap<string, MaterialShaderArtifact> | undefined;
|
|
1954
1713
|
let materialBindingClasses: ReadonlyMap<string, string> | undefined;
|
|
1955
1714
|
const abortPreparedGpuDriven = (): void => {
|
|
1956
1715
|
preparedGpuDriven?._abortResourceReplacement?.();
|
|
1957
1716
|
};
|
|
1958
1717
|
const cubeCaptureWork = prepareCubeCapture(internals, cubeCapture, camera);
|
|
1959
|
-
const reflectionFallbackCandidate =
|
|
1718
|
+
const reflectionFallbackCandidate =
|
|
1719
|
+
cubeCapture?.reflectionProbes?.fallbackDemand === true ||
|
|
1720
|
+
(ssrDependencies?.requested === true && ssrDependencies.reflectionFallback !== undefined);
|
|
1721
|
+
frameState.surfaceSubmissionObservation?.setActualLaneReason(
|
|
1722
|
+
reflectionFallbackCandidate ? 'reflection-fallback-mrt' : undefined,
|
|
1723
|
+
);
|
|
1724
|
+
// The Standard PBR GPU raster owns one color target. Reflection fallback
|
|
1725
|
+
// adds an MRT attachment, so keep this frame on the ordinary CPU producer
|
|
1726
|
+
// instead of claiming rows that the one-target GPU pipeline cannot draw.
|
|
1727
|
+
const frameGpuDriven = reflectionFallbackCandidate ? undefined : gpuDriven;
|
|
1960
1728
|
// Capture views have independent draw ownership; display-camera GPU work
|
|
1961
1729
|
// remains active while their faces and SSR fallback attachments are recorded.
|
|
1962
|
-
// ProbeBlendRecord is
|
|
1963
|
-
//
|
|
1964
|
-
//
|
|
1965
|
-
//
|
|
1966
|
-
// the scene-index lane carries the same record contract.
|
|
1967
|
-
const probeBlendFrame = renderables.some((entry) => entry.probeBlendRecord !== undefined);
|
|
1968
|
-
const frameGpuDriven = probeBlendFrame ? undefined : gpuDriven;
|
|
1730
|
+
// ProbeBlendRecord is a shared object-level fragment ABI. The prepared
|
|
1731
|
+
// GPU owner receives the renderer-owned record buffer below and binds it
|
|
1732
|
+
// at the same slot used by the direct material path, so probe-bearing rows
|
|
1733
|
+
// remain eligible for the GPU lane.
|
|
1969
1734
|
|
|
1970
1735
|
// Validate first so user meshes are resident in the same frame. The GPU
|
|
1971
1736
|
// owner consumes this producer-owned map together with the builtin mesh
|
|
@@ -2090,16 +1855,37 @@ export function recordFrame(
|
|
|
2090
1855
|
frameState.installedPipelineConfig?.ssao?.enabled === true),
|
|
2091
1856
|
reflectionFallback: frameState.reflectionFallbackDemand === true,
|
|
2092
1857
|
});
|
|
1858
|
+
acceptedMaterialArtifacts = materialArtifacts;
|
|
1859
|
+
acceptedShadowMaterialArtifacts = shadowMaterialArtifacts;
|
|
2093
1860
|
const paletteUploadBytes = validatedForResidency.reduce(
|
|
2094
1861
|
(total, row) => total + (row.source.skin?.uploadBytes ?? 0),
|
|
2095
1862
|
0,
|
|
2096
1863
|
);
|
|
1864
|
+
const gpuProbeBlendRequested = [...materialArtifacts.values()].some((artifact) =>
|
|
1865
|
+
requiresProbeBlendRecord(artifact),
|
|
1866
|
+
);
|
|
1867
|
+
const gpuProbeBlendBuffer = gpuProbeBlendRequested
|
|
1868
|
+
? ensureProbeBlendRecordBuffer(
|
|
1869
|
+
internals.device,
|
|
1870
|
+
frameState,
|
|
1871
|
+
frameGpuDriven.scene?.probeBlend,
|
|
1872
|
+
)
|
|
1873
|
+
: undefined;
|
|
2097
1874
|
const prepared = frameGpuDriven.owner.prepare({
|
|
2098
1875
|
scene: frameGpuDriven.scene,
|
|
2099
1876
|
camera,
|
|
2100
1877
|
meshBySlot: gpuDrivenMeshes,
|
|
2101
1878
|
viewBindGroupLayout: pipelineState.viewBindGroupLayout,
|
|
2102
1879
|
meshResidencyEpoch: internals.gpuStore.meshResidencyEpoch,
|
|
1880
|
+
// `hdrp` remains a compatibility stop for callers that cannot provide
|
|
1881
|
+
// the clustered topology. The production frame supplies the explicit
|
|
1882
|
+
// Standard topology below, so the GPU owner can select a matching
|
|
1883
|
+
// published group(2) artifact and bind the existing cluster owner.
|
|
1884
|
+
hdrp:
|
|
1885
|
+
standard.kind === 'clustered' &&
|
|
1886
|
+
(standard.prepared.local.length > 0 ||
|
|
1887
|
+
frameState.installedPipelineConfig?.ssao?.enabled === true),
|
|
1888
|
+
clustered: standard.kind === 'clustered',
|
|
2103
1889
|
materialBindingClasses,
|
|
2104
1890
|
...(frameGpuDriven.activeEntityKeys === undefined
|
|
2105
1891
|
? {}
|
|
@@ -2114,6 +1900,13 @@ export function recordFrame(
|
|
|
2114
1900
|
? {}
|
|
2115
1901
|
: { telemetrySubmit: frameGpuDriven.telemetrySubmit }),
|
|
2116
1902
|
...(frameGpuDriven.occlusion === undefined ? {} : { occlusion: frameGpuDriven.occlusion }),
|
|
1903
|
+
...(frameGpuDriven.frameTime === undefined ? {} : { frameTime: frameGpuDriven.frameTime }),
|
|
1904
|
+
...(frameGpuDriven.deviceGeneration === undefined
|
|
1905
|
+
? {}
|
|
1906
|
+
: { deviceGeneration: frameGpuDriven.deviceGeneration }),
|
|
1907
|
+
...(frameGpuDriven.surfaceDynamicInput === undefined
|
|
1908
|
+
? {}
|
|
1909
|
+
: { surfaceDynamicInput: frameGpuDriven.surfaceDynamicInput }),
|
|
2117
1910
|
...(frameGpuDriven.scene?.structureMetrics === undefined
|
|
2118
1911
|
? {}
|
|
2119
1912
|
: {
|
|
@@ -2126,6 +1919,9 @@ export function recordFrame(
|
|
|
2126
1919
|
...(firstSkinArtifact === undefined ? {} : { materialSkinArtifact: firstSkinArtifact }),
|
|
2127
1920
|
...(materialArtifacts.size === 0 ? {} : { materialArtifacts }),
|
|
2128
1921
|
...(shadowMaterialArtifacts.size === 0 ? {} : { shadowMaterialArtifacts }),
|
|
1922
|
+
...(gpuProbeBlendBuffer === undefined
|
|
1923
|
+
? {}
|
|
1924
|
+
: { probeBlendRecordBuffer: gpuProbeBlendBuffer }),
|
|
2129
1925
|
materialPipelineState: pipelineState,
|
|
2130
1926
|
...(internals.getMaterialShaderPipelineEntry === undefined
|
|
2131
1927
|
? {}
|
|
@@ -2186,7 +1982,12 @@ export function recordFrame(
|
|
|
2186
1982
|
}
|
|
2187
1983
|
}
|
|
2188
1984
|
|
|
2189
|
-
|
|
1985
|
+
// Direct acceptance keeps producer preparation/residency active so its
|
|
1986
|
+
// page/frame/shared-time candidate is validated and committed through the
|
|
1987
|
+
// same submit hooks. Only raster projection changes lanes.
|
|
1988
|
+
const rasterGpuDriven =
|
|
1989
|
+
frameGpuDriven?.geometryLane === 'direct' ? undefined : preparedGpuDriven;
|
|
1990
|
+
const gpuOwnedDrawKeys = rasterGpuDriven?.drawKeys ?? EMPTY_DRAW_KEYS;
|
|
2190
1991
|
// The GPU lane owns only the primary opaque raster today. Directional,
|
|
2191
1992
|
// point, and spot shadow views consume the same CPU-validated rows; they do
|
|
2192
1993
|
// not require a second residency walk because the first validation already
|
|
@@ -2203,6 +2004,7 @@ export function recordFrame(
|
|
|
2203
2004
|
validated,
|
|
2204
2005
|
gpuOwnedDrawKeys,
|
|
2205
2006
|
validationInput.cacheKey,
|
|
2007
|
+
rasterGpuDriven?.worldKeys,
|
|
2206
2008
|
);
|
|
2207
2009
|
const cacheValidated =
|
|
2208
2010
|
validatedShadowForResidency === undefined
|
|
@@ -2245,7 +2047,7 @@ export function recordFrame(
|
|
|
2245
2047
|
Math.max(1, internals.canvas.width),
|
|
2246
2048
|
Math.max(1, internals.canvas.height),
|
|
2247
2049
|
graphShadowMapSize,
|
|
2248
|
-
|
|
2050
|
+
rasterGpuDriven,
|
|
2249
2051
|
featureGraphCandidate,
|
|
2250
2052
|
cubeCapture?.state,
|
|
2251
2053
|
cameras.length === 0,
|
|
@@ -2257,7 +2059,13 @@ export function recordFrame(
|
|
|
2257
2059
|
undefined,
|
|
2258
2060
|
ssrAdmission,
|
|
2259
2061
|
renderExtent,
|
|
2062
|
+
validated.some((entry) =>
|
|
2063
|
+
entry.source.materials.some(
|
|
2064
|
+
(material) => material.surfaceModel === 'single-layer-medium',
|
|
2065
|
+
),
|
|
2066
|
+
),
|
|
2260
2067
|
analyticFog !== undefined && analyticFog.density > 0 && analyticFog.maxOpacity > 0,
|
|
2068
|
+
cloudShadowProjection?.resolution,
|
|
2261
2069
|
),
|
|
2262
2070
|
);
|
|
2263
2071
|
if (preflightGraph === null) {
|
|
@@ -2334,6 +2142,13 @@ export function recordFrame(
|
|
|
2334
2142
|
const view = swapTarget.view;
|
|
2335
2143
|
const targetW = swapTarget.targetW;
|
|
2336
2144
|
const targetH = swapTarget.targetH;
|
|
2145
|
+
const submittedBarrelMapping = resolveSubmittedBarrelMapping(
|
|
2146
|
+
frameState,
|
|
2147
|
+
preflightGraph,
|
|
2148
|
+
camera,
|
|
2149
|
+
targetW,
|
|
2150
|
+
targetH,
|
|
2151
|
+
);
|
|
2337
2152
|
|
|
2338
2153
|
const previousTemporalView = frameState.lastSuccessfulTemporalView;
|
|
2339
2154
|
const motionBlurDemand =
|
|
@@ -2472,7 +2287,7 @@ export function recordFrame(
|
|
|
2472
2287
|
activeVolumetricFogSignature = resolvedHistory.signature;
|
|
2473
2288
|
const previousTime = frameState.volumetricFogHistorySignature?.worldTimeSeconds;
|
|
2474
2289
|
const currentTime = resolvedHistory.signature.worldTimeSeconds;
|
|
2475
|
-
// Adjacent UBO lanes: validity,
|
|
2290
|
+
// Adjacent UBO lanes: validity, RenderResourceScope time, elapsed time since accepted history.
|
|
2476
2291
|
const historyParams = new Float32Array([
|
|
2477
2292
|
activeVolumetricFogContext.historyValid ? 1 : 0,
|
|
2478
2293
|
currentTime ?? 0,
|
|
@@ -2642,6 +2457,8 @@ export function recordFrame(
|
|
|
2642
2457
|
// View UBO + CSM/spot-shadow matrix pack: assembled + uploaded in one
|
|
2643
2458
|
// queue.writeBuffer round-trip. Extracted to view-ubo.ts (M3/w18) so
|
|
2644
2459
|
// recordFrame stays an orchestration skeleton (D-2).
|
|
2460
|
+
// The stable projection is carried in the View UBO. Direct-sun shader
|
|
2461
|
+
// consumers sample the renderer-owned map at each receiver position.
|
|
2645
2462
|
writeViewUbo(
|
|
2646
2463
|
internals.device.queue,
|
|
2647
2464
|
pipelineState.viewUniformBuffer,
|
|
@@ -2651,6 +2468,7 @@ export function recordFrame(
|
|
|
2651
2468
|
frameState.spotShadowSnapshots,
|
|
2652
2469
|
temporalView,
|
|
2653
2470
|
preparedSpotLightProjector?.spotIndex,
|
|
2471
|
+
cloudShadowProjection,
|
|
2654
2472
|
);
|
|
2655
2473
|
if (pipelineState.pointsLinesViewBuffer !== undefined) {
|
|
2656
2474
|
writePointsLinesViewUbo(
|
|
@@ -2722,7 +2540,7 @@ export function recordFrame(
|
|
|
2722
2540
|
internals,
|
|
2723
2541
|
frameState,
|
|
2724
2542
|
pipelineState,
|
|
2725
|
-
validated.length > 0 ||
|
|
2543
|
+
validated.length > 0 || rasterGpuDriven !== undefined,
|
|
2726
2544
|
bindGroupCounts,
|
|
2727
2545
|
undefined,
|
|
2728
2546
|
true,
|
|
@@ -2740,6 +2558,7 @@ export function recordFrame(
|
|
|
2740
2558
|
frameState.spotShadowSnapshots,
|
|
2741
2559
|
capture.viewBindGroupDynamicOffset,
|
|
2742
2560
|
preparedSpotLightProjector?.spotIndex,
|
|
2561
|
+
cloudShadowProjection,
|
|
2743
2562
|
);
|
|
2744
2563
|
}
|
|
2745
2564
|
for (const probe of cubeCapture?.reflectionProbes?.graph.work ?? []) {
|
|
@@ -2754,6 +2573,7 @@ export function recordFrame(
|
|
|
2754
2573
|
frameState.spotShadowSnapshots,
|
|
2755
2574
|
probe.viewBindGroupDynamicOffset,
|
|
2756
2575
|
preparedSpotLightProjector?.spotIndex,
|
|
2576
|
+
cloudShadowProjection,
|
|
2757
2577
|
);
|
|
2758
2578
|
}
|
|
2759
2579
|
|
|
@@ -2816,7 +2636,18 @@ export function recordFrame(
|
|
|
2816
2636
|
framePostProcessParams.set(id, dofParams);
|
|
2817
2637
|
}
|
|
2818
2638
|
}
|
|
2639
|
+
if (submittedBarrelMapping !== undefined && submittedBarrelMapping.strength > 0) {
|
|
2640
|
+
framePostProcessParams.set(
|
|
2641
|
+
BARREL_DISTORTION_POST_PROCESS_ID,
|
|
2642
|
+
packBarrelDistortionParams(submittedBarrelMapping),
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2819
2645
|
|
|
2646
|
+
if (preparedGpuDriven !== undefined) {
|
|
2647
|
+
frameState.surfaceSubmissionObservation?.setResourceGeneration(
|
|
2648
|
+
preparedGpuDriven.standardPbrFrameResources.resourceGeneration,
|
|
2649
|
+
);
|
|
2650
|
+
}
|
|
2820
2651
|
const passCtx: _InternalRenderPipelineContext = {
|
|
2821
2652
|
assets: internals.assets,
|
|
2822
2653
|
world,
|
|
@@ -2879,12 +2710,22 @@ export function recordFrame(
|
|
|
2879
2710
|
...(preparedGpuDriven === undefined
|
|
2880
2711
|
? {}
|
|
2881
2712
|
: {
|
|
2882
|
-
gpuDrivenStandardPbrFrameResources:
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2713
|
+
gpuDrivenStandardPbrFrameResources: {
|
|
2714
|
+
...preparedGpuDriven.standardPbrFrameResources,
|
|
2715
|
+
...(frameState.surfaceSubmissionObservation === undefined
|
|
2716
|
+
? {}
|
|
2717
|
+
: { surfaceSubmissionObservation: frameState.surfaceSubmissionObservation }),
|
|
2718
|
+
},
|
|
2719
|
+
...(rasterGpuDriven === undefined
|
|
2720
|
+
? {}
|
|
2721
|
+
: {
|
|
2722
|
+
gpuDrivenDrawKeys: rasterGpuDriven.drawKeys,
|
|
2723
|
+
gpuDrivenWorldKeys: rasterGpuDriven.worldKeys,
|
|
2724
|
+
gpuDrivenShadowViews: rasterGpuDriven.shadowViewPool,
|
|
2725
|
+
gpuDrivenShadowBatchProjections: rasterGpuDriven.shadowBatchProjections,
|
|
2726
|
+
gpuDrivenShadowDrawKeys: rasterGpuDriven.shadowDrawKeys,
|
|
2727
|
+
gpuDrivenShadowDrawKeysByView: rasterGpuDriven.shadowDrawKeysByView,
|
|
2728
|
+
}),
|
|
2888
2729
|
}),
|
|
2889
2730
|
...(frameGpuDriven?.scene === undefined
|
|
2890
2731
|
? {}
|
|
@@ -2958,6 +2799,12 @@ export function recordFrame(
|
|
|
2958
2799
|
frameState.ssrHistoryCandidate = undefined;
|
|
2959
2800
|
}
|
|
2960
2801
|
preparedGpuDriven?._commitResourceReplacement();
|
|
2802
|
+
const consumed = preparedGpuDriven?._consumeSurfaceDynamicInput?.(
|
|
2803
|
+
frameState.frameNumber,
|
|
2804
|
+
);
|
|
2805
|
+
if (consumed !== undefined && !consumed.ok) {
|
|
2806
|
+
internals.errorRegistry.fire(consumed.error);
|
|
2807
|
+
}
|
|
2961
2808
|
frameGpuDriven?.occlusion?.commit(true, frameState.frameNumber, profilePhase);
|
|
2962
2809
|
},
|
|
2963
2810
|
onAborted: () => {
|
|
@@ -2982,6 +2829,7 @@ export function recordFrame(
|
|
|
2982
2829
|
cubeCaptureWork.length === 0 ? undefined : cubeCaptureWork.map((work) => work.faceIndex),
|
|
2983
2830
|
(done) => {
|
|
2984
2831
|
completion = done;
|
|
2832
|
+
frameState.surfaceSubmissionObservation?.submit(done, frameState.graphGeneration);
|
|
2985
2833
|
if (fallbackReadbackRequest !== undefined) {
|
|
2986
2834
|
fallbackReadback = done.then(
|
|
2987
2835
|
() =>
|
|
@@ -3000,21 +2848,34 @@ export function recordFrame(
|
|
|
3000
2848
|
timingCapture,
|
|
3001
2849
|
),
|
|
3002
2850
|
);
|
|
3003
|
-
if (submitted &&
|
|
2851
|
+
if (submitted && onRenderableDraw !== undefined && rasterGpuDriven?.drawKeys !== undefined) {
|
|
3004
2852
|
for (const entry of validated) {
|
|
3005
|
-
|
|
3006
|
-
onRenderableDraw(entry);
|
|
3007
|
-
}
|
|
2853
|
+
emitGpuDrivenDrawReceipts(entry, rasterGpuDriven.drawKeys, onRenderableDraw);
|
|
3008
2854
|
}
|
|
3009
2855
|
}
|
|
3010
2856
|
if (submitted) {
|
|
2857
|
+
// `ensureCompiledFrameGraph` can deliberately return the accepted LKG
|
|
2858
|
+
// while a topology candidate is still preparing. The submitted graph is
|
|
2859
|
+
// the picture authority: an identity frame publishes an identity
|
|
2860
|
+
// mapping, while a failed enable/disable candidate retains the previous
|
|
2861
|
+
// mapping together with its old picture. Never pair an LKG graph with
|
|
2862
|
+
// the live RenderResourceScope's newer camera component.
|
|
2863
|
+
// The exact mapping used to populate the accepted graph's UBO is the
|
|
2864
|
+
// only mapping promoted with its receipt. This keeps an identity frame,
|
|
2865
|
+
// a retained barrel LKG and a normal nonzero frame on one context.
|
|
2866
|
+
if (submittedBarrelMapping !== undefined) {
|
|
2867
|
+
frameState.lastSuccessfulBarrelDistortion = submittedBarrelMapping;
|
|
2868
|
+
}
|
|
3011
2869
|
// Feature plans are submission-sensitive: VFX keeps queued fixed ticks
|
|
3012
2870
|
// until the graph that consumed them is actually submitted. Acknowledge
|
|
3013
2871
|
// the exact accepted graph here so successful frames do not replay every
|
|
3014
2872
|
// prior tick into the next topology.
|
|
3015
2873
|
acknowledgeRenderFeatureSubmission(getRenderFeatureGraphState(internals).plans);
|
|
3016
2874
|
}
|
|
3017
|
-
if (
|
|
2875
|
+
if (
|
|
2876
|
+
cubeCapture !== undefined &&
|
|
2877
|
+
cubeCaptureWork.some((work) => work.physical.descriptor.shape === 'cube')
|
|
2878
|
+
) {
|
|
3018
2879
|
const completed = cubeCapture.scheduler.completeSubmission(submitted, completion);
|
|
3019
2880
|
if (!completed.ok) internals.errorRegistry.fire(completed.error);
|
|
3020
2881
|
}
|
|
@@ -3046,6 +2907,8 @@ export function recordFrame(
|
|
|
3046
2907
|
: null;
|
|
3047
2908
|
}
|
|
3048
2909
|
if (submitted) {
|
|
2910
|
+
frameState.recoveryMaterialArtifacts = acceptedMaterialArtifacts;
|
|
2911
|
+
frameState.recoveryShadowMaterialArtifacts = acceptedShadowMaterialArtifacts;
|
|
3049
2912
|
if (graphVolumetricFog === undefined) {
|
|
3050
2913
|
frameState.volumetricFogAccepted = undefined;
|
|
3051
2914
|
frameState.volumetricFogAcceptedContext = undefined;
|
|
@@ -3098,12 +2961,13 @@ export function recordFrame(
|
|
|
3098
2961
|
frameState.ssrHistoryOwner?.abortFrame(frameState.ssrHistoryCandidate, 'submit');
|
|
3099
2962
|
frameState.ssrHistoryCandidate = undefined;
|
|
3100
2963
|
}
|
|
3101
|
-
// DoF and volume topology replacements are provisional until the
|
|
3102
|
-
// submission succeeds. This runs for every early-return path as
|
|
3103
|
-
// swapchain, encoder, or graph failure cannot leave a
|
|
3104
|
-
// candidate as the next frame's accepted graph.
|
|
2964
|
+
// Output, DoF, and volume topology replacements are provisional until the
|
|
2965
|
+
// queue submission succeeds. This runs for every early-return path as
|
|
2966
|
+
// well, so a swapchain, encoder, or graph failure cannot leave a
|
|
2967
|
+
// non-submitted candidate as the next frame's accepted graph.
|
|
3105
2968
|
settleVolumetricFogGraphCandidate(frameState, submitted);
|
|
3106
2969
|
settleDepthOfFieldGraphCandidate(frameState, submitted);
|
|
2970
|
+
settleBarrelDistortionGraphCandidate(frameState, submitted);
|
|
3107
2971
|
if (volumeInspectionReady && (submitted || volumeSubmissionAttempted)) {
|
|
3108
2972
|
updateVolumetricFogInspection(
|
|
3109
2973
|
frameState,
|
|
@@ -3136,7 +3000,7 @@ function readWorldState(lease: RenderReadLease): DirectionalShadowWorldState {
|
|
|
3136
3000
|
|
|
3137
3001
|
function sameWorldState(
|
|
3138
3002
|
cached: DirectionalShadowCache,
|
|
3139
|
-
worlds: readonly
|
|
3003
|
+
worlds: readonly RenderResourceScope[],
|
|
3140
3004
|
leases: readonly RenderReadLease[] | undefined,
|
|
3141
3005
|
): boolean {
|
|
3142
3006
|
if (leases === undefined || leases.length !== worlds.length) return false;
|
|
@@ -3155,11 +3019,13 @@ function sameWorldState(
|
|
|
3155
3019
|
changes = lease.readChanges(cachedState.version);
|
|
3156
3020
|
} catch {
|
|
3157
3021
|
// Treat malformed or foreign version evidence as stale and rebuild the
|
|
3158
|
-
// atlas rather than reaching through
|
|
3022
|
+
// atlas rather than reaching through RenderResourceScope for a second mutation clock.
|
|
3159
3023
|
return false;
|
|
3160
3024
|
}
|
|
3161
|
-
if (
|
|
3162
|
-
|
|
3025
|
+
if (
|
|
3026
|
+
changes.version.structureEpoch !== cachedState.version.structureEpoch ||
|
|
3027
|
+
changes.world.changedComponentIds.length !== 0
|
|
3028
|
+
) {
|
|
3163
3029
|
return false;
|
|
3164
3030
|
}
|
|
3165
3031
|
}
|
|
@@ -3201,7 +3067,7 @@ function sameDirectionalShadowQuality(
|
|
|
3201
3067
|
function prepareDirectionalShadowCache(
|
|
3202
3068
|
internals: RenderSystemInternals,
|
|
3203
3069
|
frameState: RenderFrameState,
|
|
3204
|
-
worlds: readonly
|
|
3070
|
+
worlds: readonly RenderResourceScope[],
|
|
3205
3071
|
leases: readonly RenderReadLease[] | undefined,
|
|
3206
3072
|
lights: ExtractedLights,
|
|
3207
3073
|
shadowMapSize: number | undefined,
|
|
@@ -3256,7 +3122,7 @@ function prepareDirectionalShadowCache(
|
|
|
3256
3122
|
if (leases === undefined || leases.length !== worlds.length) {
|
|
3257
3123
|
// Direct record-stage test harnesses may omit the host-owned leases. They
|
|
3258
3124
|
// still exercise the real shadow encoder, but without the ECS read owner
|
|
3259
|
-
// they must conservatively rebuild rather than inspect
|
|
3125
|
+
// they must conservatively rebuild rather than inspect RenderResourceScope internals.
|
|
3260
3126
|
return { reuse: false, next: null };
|
|
3261
3127
|
}
|
|
3262
3128
|
const worldStateTokens = leases.map(readWorldState);
|
|
@@ -3277,716 +3143,3 @@ function prepareDirectionalShadowCache(
|
|
|
3277
3143
|
},
|
|
3278
3144
|
};
|
|
3279
3145
|
}
|
|
3280
|
-
|
|
3281
|
-
/**
|
|
3282
|
-
* feat-20260704 M3/w18: validate renderable handles + collect the render plan,
|
|
3283
|
-
* extracted verbatim from `recordFrame`. Empty `renderables` input or all-
|
|
3284
|
-
* unregistered handles both yield an empty result (the Case E clear-pass-only
|
|
3285
|
-
* path). Builds per-entry renderState / stencilReference overlays from the
|
|
3286
|
-
* transparent-dispatch entries, resolves each MeshFilter.assetHandle through
|
|
3287
|
-
* the AssetRegistry + GPU store (with the sprite 9-slice mesh swap), and fires
|
|
3288
|
-
* structured `asset-not-registered` errors for handles that fail to resolve.
|
|
3289
|
-
*
|
|
3290
|
-
* @internal
|
|
3291
|
-
*/
|
|
3292
|
-
export function validateRenderables(
|
|
3293
|
-
internals: RenderSystemInternals,
|
|
3294
|
-
world: World,
|
|
3295
|
-
// feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2): the full
|
|
3296
|
-
// worlds[] list. Each renderable's mesh handle is a user-tier slot in its OWN
|
|
3297
|
-
// world's sharedRefs; resolving against a foreign world either misses
|
|
3298
|
-
// (asset-not-registered) or resolves the wrong slot payload. `world` (the
|
|
3299
|
-
// resource-owner) is retained as the fallback for renderables whose worldId
|
|
3300
|
-
// is out of range (defensive; extractFrames always stamps a valid index).
|
|
3301
|
-
worlds: readonly World[],
|
|
3302
|
-
pipelineState: PipelineState,
|
|
3303
|
-
frameState: RenderFrameState,
|
|
3304
|
-
renderables: readonly RenderableSnapshot[],
|
|
3305
|
-
transparentDispatch: readonly DispatchEntry[],
|
|
3306
|
-
excludedEntityKeys: ReadonlySet<number> = new Set<number>(),
|
|
3307
|
-
worldKeys?: readonly number[],
|
|
3308
|
-
): ValidatedRenderable[] {
|
|
3309
|
-
// bug-20260527-renderstate-pipeline-dispatch-gap D-4:
|
|
3310
|
-
// build a renderableIndex -> renderState map from dispatch entries
|
|
3311
|
-
// so each ValidatedRenderable carries its per-material renderState
|
|
3312
|
-
// override without an O(n^2) back-scan in the draw loop.
|
|
3313
|
-
const renderStateByRenderableIdx = new Map<number, MaterialRenderState | undefined>();
|
|
3314
|
-
// w10: also build a renderableIndex -> stencilReference map from
|
|
3315
|
-
// dispatch entries for per-draw setStencilReference calls.
|
|
3316
|
-
const stencilRefByRenderableIdx = new Map<number, number | undefined>();
|
|
3317
|
-
const variantSetByRenderableIdx = new Map<number, string | undefined>();
|
|
3318
|
-
// Keep these three independent maps because each overlay has its own
|
|
3319
|
-
// consumer and the last dispatch entry remains authoritative. Populate
|
|
3320
|
-
// them in one pass: the old code walked the same dispatch list three times
|
|
3321
|
-
// and repeated the same renderableIndex branch / hash writes.
|
|
3322
|
-
for (const de of transparentDispatch) {
|
|
3323
|
-
const renderableIndex = de.renderableIndex;
|
|
3324
|
-
if (renderableIndex === undefined) continue;
|
|
3325
|
-
renderStateByRenderableIdx.set(renderableIndex, de.renderState);
|
|
3326
|
-
stencilRefByRenderableIdx.set(renderableIndex, de.stencilReference);
|
|
3327
|
-
variantSetByRenderableIdx.set(renderableIndex, variantSetFromDefines(de.defines));
|
|
3328
|
-
}
|
|
3329
|
-
const validated: ValidatedRenderable[] = [];
|
|
3330
|
-
for (let rIdx = 0; rIdx < renderables.length; rIdx++) {
|
|
3331
|
-
const r = renderables[rIdx];
|
|
3332
|
-
if (r === undefined) continue;
|
|
3333
|
-
if (excludedEntityKeys.has(worldEntityKey(worldKeys?.[r.worldId] ?? r.worldId, r.entityKey)))
|
|
3334
|
-
continue;
|
|
3335
|
-
// feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2): resolve
|
|
3336
|
-
// this renderable's mesh against the world it was EXTRACTED from
|
|
3337
|
-
// (worlds[r.worldId]) — NOT the single resource-owner `world`. Builtin mesh
|
|
3338
|
-
// slots (< BUILTIN_BASE) resolve process-statically regardless of world, so
|
|
3339
|
-
// the per-world pick only matters for user-tier handles, but selecting it
|
|
3340
|
-
// unconditionally keeps a single code path. Falls back to the resource-owner
|
|
3341
|
-
// world if worldId is out of range (defensive; extractFrames always stamps
|
|
3342
|
-
// a valid index into worlds[]).
|
|
3343
|
-
const renderableWorld = worlds[r.worldId] ?? world;
|
|
3344
|
-
const assetRes = resolveAssetHandle<MeshAsset>(
|
|
3345
|
-
renderableWorld,
|
|
3346
|
-
toShared<'MeshAsset'>(r.assetHandle),
|
|
3347
|
-
);
|
|
3348
|
-
if (!assetRes.ok) {
|
|
3349
|
-
internals.errorRegistry.fire(
|
|
3350
|
-
new RhiError({
|
|
3351
|
-
code: 'asset-not-registered',
|
|
3352
|
-
expected: 'MeshFilter.assetHandle in AssetRegistry',
|
|
3353
|
-
hint: 'use HANDLE_CUBE / HANDLE_TRIANGLE imports; custom mesh register path: feat-future-asset-system',
|
|
3354
|
-
detail: { assetHandle: r.assetHandle },
|
|
3355
|
-
}),
|
|
3356
|
-
);
|
|
3357
|
-
continue;
|
|
3358
|
-
}
|
|
3359
|
-
// feat-20260601-device/gpu-residency-extraction M1 (D-1): builtin meshes
|
|
3360
|
-
// (slots through HANDLE_NINESLICE_QUAD) keep the createRenderer step-3
|
|
3361
|
-
// direct-upload + `pipelineState.meshes` path -- they are NOT routed
|
|
3362
|
-
// through `ensureResident`. User-registered meshes pull through the store
|
|
3363
|
-
// on first access (the register->upload push was severed in this M1);
|
|
3364
|
-
// the POD fetched above (assetRes.value) is passed in, store holds no
|
|
3365
|
-
// registry ref (D-2). A first-access miss builds the GPU buffers; later
|
|
3366
|
-
// frames hit the O(1) cache.
|
|
3367
|
-
const meshAssetHandle = toShared<'MeshAsset'>(r.assetHandle);
|
|
3368
|
-
let meshHandles = internals.gpuStore.getMeshGpuHandles(meshAssetHandle, renderableWorld);
|
|
3369
|
-
const lodMeshes = r.lods?.map((lod) => internals.assets.lookup<MeshAsset>(lod.mesh));
|
|
3370
|
-
const resolvedLodMeshes =
|
|
3371
|
-
lodMeshes?.every((lod): lod is MeshAsset => lod?.kind === 'mesh') === true
|
|
3372
|
-
? lodMeshes
|
|
3373
|
-
: undefined;
|
|
3374
|
-
if (meshHandles === undefined && r.assetHandle > handleSlot(HANDLE_NINESLICE_QUAD)) {
|
|
3375
|
-
const residentRes = internals.gpuStore.ensureResident(
|
|
3376
|
-
meshAssetHandle,
|
|
3377
|
-
assetRes.value,
|
|
3378
|
-
renderableWorld,
|
|
3379
|
-
resolvedLodMeshes,
|
|
3380
|
-
);
|
|
3381
|
-
if (residentRes.ok) {
|
|
3382
|
-
meshHandles = residentRes.value;
|
|
3383
|
-
} else if (residentRes.error instanceof RhiError) {
|
|
3384
|
-
internals.errorRegistry.fire(residentRes.error);
|
|
3385
|
-
}
|
|
3386
|
-
}
|
|
3387
|
-
meshHandles = meshHandles ?? pipelineState.meshes.get(r.assetHandle);
|
|
3388
|
-
if (meshHandles === undefined) {
|
|
3389
|
-
internals.errorRegistry.fire(
|
|
3390
|
-
new RhiError({
|
|
3391
|
-
code: 'asset-not-registered',
|
|
3392
|
-
expected: 'GPU mesh buffers uploaded for assetHandle',
|
|
3393
|
-
hint: 'await renderer.initialization before draw([world], { cameraOwner: 0, resourceOwner: 0 }); ensure AssetRegistry.configureGpuDevice ran so user meshes are uploaded',
|
|
3394
|
-
detail: { assetHandle: r.assetHandle },
|
|
3395
|
-
}),
|
|
3396
|
-
);
|
|
3397
|
-
continue;
|
|
3398
|
-
}
|
|
3399
|
-
// feat-20260527-sprite-nineslice M2 / w11 (plan-strategy section D-2):
|
|
3400
|
-
// sprite branch with non-zero `slicesAndMode` (post-w12 paramSnapshot
|
|
3401
|
-
// entry name) overrides the user-supplied mesh handle (typically
|
|
3402
|
-
// HANDLE_QUAD = 3) with the 16-vertex / 54-index HANDLE_NINESLICE_QUAD
|
|
3403
|
-
// (id=5) topology so the vertex shader sees the 4x4 grid required for
|
|
3404
|
-
// 9-region anchor mapping. Default slicesAndMode ([0, 0, 0, 0]) keeps
|
|
3405
|
-
// the legacy HANDLE_QUAD path; a flip from zero to non-zero on the
|
|
3406
|
-
// same entity routes here per-frame so AI users can toggle 9-slice
|
|
3407
|
-
// on the fly without re-spawning the entity (charter F1 minimum
|
|
3408
|
-
// surface). The HANDLE_NINESLICE_QUAD GPU buffers are seeded by
|
|
3409
|
-
// createRenderer step-3.
|
|
3410
|
-
//
|
|
3411
|
-
// feat-20260625-refactor-sprite-as-transparent-mesh M3 / w13: judgement
|
|
3412
|
-
// key migrated from `shadingModel === 'sprite'` to
|
|
3413
|
-
// `materialShaderId === 'forgeax::sprite'` (plan-strategy D-10); slices
|
|
3414
|
-
// sourced from `paramSnapshot.slicesAndMode` (post-w12 UBO-aligned
|
|
3415
|
-
// overlay path).
|
|
3416
|
-
//
|
|
3417
|
-
// feat-20260624-sprite-lit-shading-model-pure-2d-lighting M1' / t7:
|
|
3418
|
-
// sprite-lit shares the sprite paramSchema (5 fields, t4 mirror) so
|
|
3419
|
-
// the 9-slices mesh swap applies identically.
|
|
3420
|
-
let effectiveMeshHandles = meshHandles;
|
|
3421
|
-
if (
|
|
3422
|
-
r.material.materialShaderId === 'forgeax::sprite' ||
|
|
3423
|
-
r.material.materialShaderId === 'forgeax::sprite-lit'
|
|
3424
|
-
) {
|
|
3425
|
-
const slicesArr = r.material.paramSnapshot?.slicesAndMode as readonly number[] | undefined;
|
|
3426
|
-
if (
|
|
3427
|
-
slicesArr !== undefined &&
|
|
3428
|
-
slicesArr.length >= 4 &&
|
|
3429
|
-
(slicesArr[0] !== 0 || slicesArr[1] !== 0 || slicesArr[2] !== 0 || slicesArr[3] !== 0)
|
|
3430
|
-
) {
|
|
3431
|
-
const nineSliceHandles = pipelineState.meshes.get(handleSlot(HANDLE_NINESLICE_QUAD));
|
|
3432
|
-
if (nineSliceHandles !== undefined) {
|
|
3433
|
-
effectiveMeshHandles = nineSliceHandles;
|
|
3434
|
-
}
|
|
3435
|
-
}
|
|
3436
|
-
}
|
|
3437
|
-
if (r.morph !== undefined) {
|
|
3438
|
-
const morphed = prepareMorphMesh(
|
|
3439
|
-
internals,
|
|
3440
|
-
frameState,
|
|
3441
|
-
worldEntityKey(r.worldId, r.entityKey),
|
|
3442
|
-
effectiveMeshHandles,
|
|
3443
|
-
assetRes.value,
|
|
3444
|
-
r.morph,
|
|
3445
|
-
);
|
|
3446
|
-
if (morphed !== undefined) effectiveMeshHandles = morphed;
|
|
3447
|
-
}
|
|
3448
|
-
validated.push({
|
|
3449
|
-
source: r,
|
|
3450
|
-
world: renderableWorld,
|
|
3451
|
-
mesh: effectiveMeshHandles,
|
|
3452
|
-
renderableIndex: rIdx,
|
|
3453
|
-
renderState: renderStateByRenderableIdx.get(rIdx),
|
|
3454
|
-
variantSet: variantSetByRenderableIdx.get(rIdx),
|
|
3455
|
-
stencilReference: stencilRefByRenderableIdx.get(rIdx),
|
|
3456
|
-
});
|
|
3457
|
-
}
|
|
3458
|
-
return validated;
|
|
3459
|
-
}
|
|
3460
|
-
|
|
3461
|
-
// Keep the primary record-stage name explicit at its call site while sharing
|
|
3462
|
-
// the same owner implementation for the complete retained ShadowCaster view.
|
|
3463
|
-
|
|
3464
|
-
function prepareMorphMesh(
|
|
3465
|
-
internals: RenderSystemInternals,
|
|
3466
|
-
frameState: RenderFrameState,
|
|
3467
|
-
cacheKey: number,
|
|
3468
|
-
base: MeshGpuHandles,
|
|
3469
|
-
mesh: MeshAsset,
|
|
3470
|
-
morph: RenderableSnapshot['morph'],
|
|
3471
|
-
): MeshGpuHandles | undefined {
|
|
3472
|
-
if (morph === undefined || frameState.morphBuffers === undefined) return undefined;
|
|
3473
|
-
const targets = mesh.morphTargets;
|
|
3474
|
-
if (targets === undefined || targets.length !== morph.targetCount) return undefined;
|
|
3475
|
-
const position = base.layoutProjection.attributes.find(
|
|
3476
|
-
(attribute) => attribute.key === 'position',
|
|
3477
|
-
);
|
|
3478
|
-
if (
|
|
3479
|
-
position === undefined ||
|
|
3480
|
-
position.offset !== 0 ||
|
|
3481
|
-
position.shaderLocation !== 0 ||
|
|
3482
|
-
position.format !== 'float32x3' ||
|
|
3483
|
-
position.byteLength !== 12
|
|
3484
|
-
) {
|
|
3485
|
-
return undefined;
|
|
3486
|
-
}
|
|
3487
|
-
const vertexCount = deriveVertexCount(mesh.vertices, base.layoutProjection);
|
|
3488
|
-
if (vertexCount === undefined || vertexCount !== base.vertexCount || vertexCount <= 0) {
|
|
3489
|
-
return undefined;
|
|
3490
|
-
}
|
|
3491
|
-
const stride = base.layoutProjection.arrayStride / Float32Array.BYTES_PER_ELEMENT;
|
|
3492
|
-
|
|
3493
|
-
let active = false;
|
|
3494
|
-
for (const weight of morph.weights) {
|
|
3495
|
-
if (weight !== 0) {
|
|
3496
|
-
active = true;
|
|
3497
|
-
break;
|
|
3498
|
-
}
|
|
3499
|
-
}
|
|
3500
|
-
if (!active) {
|
|
3501
|
-
frameState.morphBuffers.get(cacheKey)?.buffer.destroy();
|
|
3502
|
-
frameState.morphBuffers.delete(cacheKey);
|
|
3503
|
-
return undefined;
|
|
3504
|
-
}
|
|
3505
|
-
|
|
3506
|
-
const vertices = new Float32Array(mesh.vertices);
|
|
3507
|
-
for (let targetIndex = 0; targetIndex < targets.length; targetIndex += 1) {
|
|
3508
|
-
const weight = morph.weights[targetIndex] ?? 0;
|
|
3509
|
-
const positions = targets[targetIndex]?.position;
|
|
3510
|
-
if (positions === undefined || positions.length !== base.vertexCount * 3) return undefined;
|
|
3511
|
-
for (let vertex = 0; vertex < base.vertexCount; vertex += 1) {
|
|
3512
|
-
const vertexBase = vertex * stride;
|
|
3513
|
-
const positionBase = vertex * 3;
|
|
3514
|
-
vertices[vertexBase] = (vertices[vertexBase] ?? 0) + (positions[positionBase] ?? 0) * weight;
|
|
3515
|
-
vertices[vertexBase + 1] =
|
|
3516
|
-
(vertices[vertexBase + 1] ?? 0) + (positions[positionBase + 1] ?? 0) * weight;
|
|
3517
|
-
vertices[vertexBase + 2] =
|
|
3518
|
-
(vertices[vertexBase + 2] ?? 0) + (positions[positionBase + 2] ?? 0) * weight;
|
|
3519
|
-
}
|
|
3520
|
-
}
|
|
3521
|
-
|
|
3522
|
-
let cached = frameState.morphBuffers.get(cacheKey);
|
|
3523
|
-
if (cached === undefined || cached.byteLength !== vertices.byteLength) {
|
|
3524
|
-
if (cached !== undefined && !cached.buffer.isDestroyed) {
|
|
3525
|
-
const destroyed = cached.buffer.destroy();
|
|
3526
|
-
if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
|
|
3527
|
-
}
|
|
3528
|
-
const created = internals.device.createBuffer({
|
|
3529
|
-
label: `morph-${cacheKey}-vbo`,
|
|
3530
|
-
size: vertices.byteLength,
|
|
3531
|
-
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
3532
|
-
mappedAtCreation: false,
|
|
3533
|
-
});
|
|
3534
|
-
if (!created.ok) {
|
|
3535
|
-
internals.errorRegistry.fire(created.error);
|
|
3536
|
-
frameState.morphBuffers.delete(cacheKey);
|
|
3537
|
-
return undefined;
|
|
3538
|
-
}
|
|
3539
|
-
const buffer = new GpuBuffer(internals.device, created.value);
|
|
3540
|
-
internals.deviceScope._adopt('buffer', buffer, (owned) => {
|
|
3541
|
-
if (!owned.isDestroyed) owned.destroy();
|
|
3542
|
-
});
|
|
3543
|
-
cached = {
|
|
3544
|
-
buffer,
|
|
3545
|
-
byteLength: vertices.byteLength,
|
|
3546
|
-
};
|
|
3547
|
-
frameState.morphBuffers.set(cacheKey, cached);
|
|
3548
|
-
}
|
|
3549
|
-
const written = internals.device.queue.writeBuffer(cached.buffer.handle, 0, vertices);
|
|
3550
|
-
if (!written.ok) {
|
|
3551
|
-
internals.errorRegistry.fire(written.error);
|
|
3552
|
-
return undefined;
|
|
3553
|
-
}
|
|
3554
|
-
return { ...base, vertexBuffer: cached.buffer };
|
|
3555
|
-
}
|
|
3556
|
-
|
|
3557
|
-
/**
|
|
3558
|
-
* feat-20260704 M3/w18: build the dispatch-ordered render plan, extracted
|
|
3559
|
-
* verbatim from `recordFrame`. (1) M3/w26 dispatch-ordered reorder: reorder
|
|
3560
|
-
* `validated` to follow the transparent-dispatch order (extract-order fallback
|
|
3561
|
-
* for unmatched entries). (2) feat-20260608 mesh-SSBO capacity gate: size the
|
|
3562
|
-
* mesh-SSBO + material-UBO pair to the larger of entity vs cumulative
|
|
3563
|
-
* material-slot count, truncating on ceiling (graceful degradation). (3)
|
|
3564
|
-
* feat-20260622 fold dispatch plan: build + apply the WebGL2 uniform-cap
|
|
3565
|
-
* fallback + bump the folded-draws metric.
|
|
3566
|
-
*
|
|
3567
|
-
* @internal
|
|
3568
|
-
*/
|
|
3569
|
-
export function buildDispatchPlan(
|
|
3570
|
-
internals: RenderSystemInternals,
|
|
3571
|
-
validated: readonly ValidatedRenderable[],
|
|
3572
|
-
transparentDispatch: readonly DispatchEntry[],
|
|
3573
|
-
foldBuckets: readonly FoldBucket[],
|
|
3574
|
-
shadowValidated: readonly ValidatedRenderable[] = [],
|
|
3575
|
-
materialBindingClasses?: ReadonlyMap<string, string>,
|
|
3576
|
-
): {
|
|
3577
|
-
validatedOrdered: readonly ValidatedRenderable[];
|
|
3578
|
-
shadowValidatedOrdered: readonly ValidatedRenderable[];
|
|
3579
|
-
foldDispatchPlan: FoldDispatchPlan | null;
|
|
3580
|
-
materialSlotIndices: readonly (readonly number[])[];
|
|
3581
|
-
materialSlots: readonly MaterialSnapshot[];
|
|
3582
|
-
materialSlotOwners: readonly ValidatedRenderable[];
|
|
3583
|
-
materialSlotCount: number;
|
|
3584
|
-
} {
|
|
3585
|
-
// A production GPU-owned frame has no CPU validated rows by design. The
|
|
3586
|
-
// dispatch list can still contain one entry per authored entity, but no
|
|
3587
|
-
// CPU plan, material table, or fold bucket is consumed in that lane. Return
|
|
3588
|
-
// the empty plan without walking that list so a steady 100k scene remains
|
|
3589
|
-
// bounded by the persistent GPU batches rather than per-entity record work.
|
|
3590
|
-
if (validated.length === 0 && shadowValidated.length === 0) {
|
|
3591
|
-
const meshSsboCapResult = ensureMeshSsboCapacity(internals, shadowValidated.length);
|
|
3592
|
-
const meshSsboSlotCapacity = meshSsboSlotCapacityAfterEnsure(
|
|
3593
|
-
internals,
|
|
3594
|
-
shadowValidated.length,
|
|
3595
|
-
meshSsboCapResult,
|
|
3596
|
-
);
|
|
3597
|
-
return {
|
|
3598
|
-
validatedOrdered: [],
|
|
3599
|
-
shadowValidatedOrdered: limitRowsToMeshSsboCapacity(shadowValidated, meshSsboSlotCapacity),
|
|
3600
|
-
foldDispatchPlan: null,
|
|
3601
|
-
materialSlotIndices: [],
|
|
3602
|
-
materialSlots: [],
|
|
3603
|
-
materialSlotOwners: [],
|
|
3604
|
-
materialSlotCount: 0,
|
|
3605
|
-
};
|
|
3606
|
-
}
|
|
3607
|
-
// M3 / w26: dispatch-ordered render. The dispatch list is pre-sorted
|
|
3608
|
-
// by queue (ascending, stable) by the extract stage per plan-strategy D-3.
|
|
3609
|
-
// Reorder validated renderables to follow the dispatch order, falling
|
|
3610
|
-
// back to extract order for renderables with no matching dispatch entry.
|
|
3611
|
-
let dispatchOrderMatchesValidated = transparentDispatch.length === validated.length;
|
|
3612
|
-
if (dispatchOrderMatchesValidated) {
|
|
3613
|
-
for (let i = 0; i < validated.length; i++) {
|
|
3614
|
-
if (transparentDispatch[i]?.renderableIndex !== validated[i]?.renderableIndex) {
|
|
3615
|
-
dispatchOrderMatchesValidated = false;
|
|
3616
|
-
break;
|
|
3617
|
-
}
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
let validatedOrdered: readonly ValidatedRenderable[] = dispatchOrderMatchesValidated
|
|
3621
|
-
? validated
|
|
3622
|
-
: [...validated];
|
|
3623
|
-
if (transparentDispatch.length > 0 && !dispatchOrderMatchesValidated) {
|
|
3624
|
-
const validatedByRenderableIdx = new Map<number, ValidatedRenderable>();
|
|
3625
|
-
const seen = new Set<number>();
|
|
3626
|
-
for (const v of validated) {
|
|
3627
|
-
validatedByRenderableIdx.set(v.renderableIndex, v);
|
|
3628
|
-
}
|
|
3629
|
-
const ordered: ValidatedRenderable[] = [];
|
|
3630
|
-
for (const de of transparentDispatch) {
|
|
3631
|
-
if (de.renderableIndex === undefined) continue;
|
|
3632
|
-
const v = validatedByRenderableIdx.get(de.renderableIndex);
|
|
3633
|
-
if (v !== undefined && !seen.has(de.renderableIndex)) {
|
|
3634
|
-
seen.add(de.renderableIndex);
|
|
3635
|
-
ordered.push(v);
|
|
3636
|
-
}
|
|
3637
|
-
}
|
|
3638
|
-
// Append renderables not in the dispatch list (e.g. default-material entities)
|
|
3639
|
-
for (const v of validated) {
|
|
3640
|
-
if (!seen.has(v.renderableIndex)) {
|
|
3641
|
-
ordered.push(v);
|
|
3642
|
-
}
|
|
3643
|
-
}
|
|
3644
|
-
validatedOrdered = ordered;
|
|
3645
|
-
}
|
|
3646
|
-
|
|
3647
|
-
// Shadow-only/off-camera CPU residuals consume the same material UBO as
|
|
3648
|
-
// visible draws. Their preparation must not depend on main-view visibility.
|
|
3649
|
-
const materialRows = [...validatedOrdered, ...shadowValidated];
|
|
3650
|
-
const materialGroups = materialRows.map((entry): readonly MaterialSnapshot[] => {
|
|
3651
|
-
const shaderId = entry.source.material.materialShaderId;
|
|
3652
|
-
if (shaderId === 'forgeax::sprite' || shaderId === 'forgeax::sprite-lit') {
|
|
3653
|
-
return [entry.source.material];
|
|
3654
|
-
}
|
|
3655
|
-
return entry.source.materials.length > 0 ? entry.source.materials : [entry.source.material];
|
|
3656
|
-
});
|
|
3657
|
-
const materialSlotPlan = buildMaterialSlotPlan(materialGroups, (material, owner) => {
|
|
3658
|
-
const row = materialRows[owner];
|
|
3659
|
-
return row === undefined
|
|
3660
|
-
? ''
|
|
3661
|
-
: (materialBindingClasses?.get(
|
|
3662
|
-
materialBindingKey(row.source, material.materialHandle ?? -1),
|
|
3663
|
-
) ?? '');
|
|
3664
|
-
});
|
|
3665
|
-
let materialSlotIndices = materialSlotPlan.slotIndices;
|
|
3666
|
-
let materialSlots = materialSlotPlan.slots;
|
|
3667
|
-
let materialSlotOwners = materialSlotPlan.slotOwners.map((index) => {
|
|
3668
|
-
const owner = materialRows[index];
|
|
3669
|
-
if (owner === undefined) throw new Error('material slot has no validated owner');
|
|
3670
|
-
return owner;
|
|
3671
|
-
});
|
|
3672
|
-
let materialSlotCount = materialSlots.length;
|
|
3673
|
-
let materialRenderableCount = materialRows.length;
|
|
3674
|
-
|
|
3675
|
-
// feat-20260608-mesh-ssbo-dynamic-grow-l1-lift-1024-entity-cap M3 / T-M3-04:
|
|
3676
|
-
// ensure the mesh-SSBO + material-UBO buffer pair is large enough to hold
|
|
3677
|
-
// the render plan BEFORE the first per-entity writeBuffer.
|
|
3678
|
-
// On `ok:false` the controller has already fired a structured RuntimeError
|
|
3679
|
-
// (`mesh-ssbo-ceiling-reached` / `mesh-ssbo-capacity-exceeded`); we truncate
|
|
3680
|
-
// the draw list to the largest complete entity prefix whose cumulative
|
|
3681
|
-
// material slots fit `degradedToSlotCount` (graceful degradation per
|
|
3682
|
-
// plan-strategy D-2): render the subset that fits, discard overflow, no
|
|
3683
|
-
// black frame. The helper is idempotent across same-frame re-calls (AC-09)
|
|
3684
|
-
// and short-circuits on length=0 / length<=slotCount (boundary table).
|
|
3685
|
-
//
|
|
3686
|
-
// bug-20260609: feat-20260608 M5 amend made the material UBO indexed by
|
|
3687
|
-
// cumulative *material-slot* count (one slot per submesh material),
|
|
3688
|
-
// which is >= entity count once an entity carries `materials.length>1`.
|
|
3689
|
-
// The mesh + material buffer pair share `slotCount` (single allocator),
|
|
3690
|
-
// so we size against the larger of the two requirements: entity count
|
|
3691
|
-
// (mesh-SSBO consumer) vs cumulative material-slot count (material-UBO
|
|
3692
|
-
// consumer). Sprite entities collapse to 1 slot in the material table,
|
|
3693
|
-
// mirroring the same rule (sprite per-submesh OOS-1;
|
|
3694
|
-
// post-w13 judgement key migrated to materialShaderId).
|
|
3695
|
-
//
|
|
3696
|
-
// feat-20260624 M1' / t7: sprite-lit treated identically to sprite
|
|
3697
|
-
// for material-slot accounting (paramSchema mirror, t4).
|
|
3698
|
-
const neededSlots = Math.max(validatedOrdered.length + shadowValidated.length, materialSlotCount);
|
|
3699
|
-
const meshSsboCapResult = ensureMeshSsboCapacity(internals, neededSlots);
|
|
3700
|
-
const meshSsboSlotCapacity = meshSsboSlotCapacityAfterEnsure(
|
|
3701
|
-
internals,
|
|
3702
|
-
neededSlots,
|
|
3703
|
-
meshSsboCapResult,
|
|
3704
|
-
);
|
|
3705
|
-
if (!meshSsboCapResult.ok) {
|
|
3706
|
-
// Graceful degradation: the controller reports slots, but this stage
|
|
3707
|
-
// consumes entities. Find a complete prefix instead of slicing at the
|
|
3708
|
-
// numeric slot count; a multi-material entity can consume several slots.
|
|
3709
|
-
const degradedRenderableCount = findRenderablePrefixForSlotCapacity(
|
|
3710
|
-
materialSlotIndices,
|
|
3711
|
-
meshSsboCapResult.degradedToSlotCount,
|
|
3712
|
-
);
|
|
3713
|
-
materialRenderableCount = degradedRenderableCount;
|
|
3714
|
-
materialSlotCount = materialSlotCountForPrefix(materialSlotIndices, degradedRenderableCount);
|
|
3715
|
-
materialSlotIndices = materialSlotIndices.slice(0, degradedRenderableCount);
|
|
3716
|
-
materialSlots = materialSlots.slice(0, materialSlotCount);
|
|
3717
|
-
materialSlotOwners = materialSlotOwners.slice(0, materialSlotCount);
|
|
3718
|
-
validatedOrdered = validatedOrdered.slice(0, degradedRenderableCount);
|
|
3719
|
-
}
|
|
3720
|
-
const shadowValidatedOrdered = limitRowsToMeshSsboCapacity(
|
|
3721
|
-
shadowValidated,
|
|
3722
|
-
Math.min(meshSsboSlotCapacity, materialRenderableCount) - validatedOrdered.length,
|
|
3723
|
-
);
|
|
3724
|
-
|
|
3725
|
-
// feat-20260622-chunk-gpu-instancing-sprite-tilemap M1 / w4-record-swap
|
|
3726
|
-
// (D-1): build the fold dispatch plan once `validatedOrdered` is final
|
|
3727
|
-
// (post truncation by mesh-SSBO capacity gate). The plan re-keys each
|
|
3728
|
-
// non-singleton bucket from `renderableIndex` to the validated-ordered
|
|
3729
|
-
// index `i` consumed by the dispatch loops; the loops use it to skip
|
|
3730
|
-
// non-head bucket members and emit one instanced drawIndexed per
|
|
3731
|
-
// bucket head. Empty plan (no fold-eligible buckets) is a byte-
|
|
3732
|
-
// identical no-op for the dispatch loops below (charter P3: silent
|
|
3733
|
-
// pass-through, no error path).
|
|
3734
|
-
let renderableToValidatedIdx: Map<number, number> | null = null;
|
|
3735
|
-
let foldDispatchPlan: FoldDispatchPlan | null = null;
|
|
3736
|
-
if (foldBuckets.length > 0) {
|
|
3737
|
-
renderableToValidatedIdx = new Map<number, number>();
|
|
3738
|
-
for (let i = 0; i < validatedOrdered.length; i++) {
|
|
3739
|
-
const e = validatedOrdered[i];
|
|
3740
|
-
if (e === undefined) continue;
|
|
3741
|
-
renderableToValidatedIdx.set(e.renderableIndex, i);
|
|
3742
|
-
}
|
|
3743
|
-
foldDispatchPlan = buildFoldDispatchPlan(foldBuckets, renderableToValidatedIdx);
|
|
3744
|
-
|
|
3745
|
-
// feat-20260622 M2 / w11 (D-2 + D-9 + AC-05): WebGL2 uniform-fallback
|
|
3746
|
-
// per-bucket instance-count cap. When caps.storageBuffer===false AND
|
|
3747
|
-
// a fold bucket carries more than FOLD_UNIFORM_INSTANCE_CAP (128)
|
|
3748
|
-
// instances, fire RhiError({code:'instancing-exceeds-uniform-cap'})
|
|
3749
|
-
// AND remove the bucket from the dispatch plan so its members fall
|
|
3750
|
-
// through to the per-entity drawIndexed exit (the same exit the
|
|
3751
|
-
// mode-gate bypass uses — D-9 "shared fallback exit"). The frame
|
|
3752
|
-
// stays visually correct (charter proposition 9 graceful
|
|
3753
|
-
// degradation: no identity-collapse / black screen) while the cap
|
|
3754
|
-
// event surfaces structurally for AI users (proposition 4 explicit
|
|
3755
|
-
// failure on .code).
|
|
3756
|
-
//
|
|
3757
|
-
// Scope discrimination: tilemap-chunk-extract-system encodes
|
|
3758
|
-
// Layer.value = (layerOrder<<20) | (chunkIndex & 0xfffff), so a
|
|
3759
|
-
// bucket whose head entry carries non-zero low-20-bits is
|
|
3760
|
-
// definitively a tilemap-chunk dispatch site. Plain sprite buckets
|
|
3761
|
-
// use SPRITE_LAYER_VALUE = layerOrder<<20 (low-20 zero) by the
|
|
3762
|
-
// documented convention (apps/hello/asi-world main.ts pattern).
|
|
3763
|
-
// The chunkIndex===0 edge case maps to 'sprite' (the helper's
|
|
3764
|
-
// default branch) — a one-bucket ambiguity per layerOrder that is
|
|
3765
|
-
// acceptable for the AI-user affordance level (the error semantics
|
|
3766
|
-
// — "this bucket exceeded the cap" — is the actionable signal;
|
|
3767
|
-
// scope=sprite vs tilemap-chunk only refines the recovery hint).
|
|
3768
|
-
if (foldDispatchPlan.headBuckets.size > 0 && !internals.device.caps.storageBuffer) {
|
|
3769
|
-
const filteredHeads = new Map<number, FoldBucket>(foldDispatchPlan.headBuckets);
|
|
3770
|
-
const filteredSkips = new Set<number>(foldDispatchPlan.skipIndices);
|
|
3771
|
-
let filteredCount = foldDispatchPlan.foldedBucketCount;
|
|
3772
|
-
for (const [headIdx, bucket] of foldDispatchPlan.headBuckets) {
|
|
3773
|
-
const scope: 'sprite' | 'tilemap-chunk' =
|
|
3774
|
-
(bucket.layer & 0xfffff) !== 0 ? 'tilemap-chunk' : 'sprite';
|
|
3775
|
-
const decision = evaluateFoldBucketUniformCap(bucket, internals.device.caps, scope);
|
|
3776
|
-
if (decision.fallback && decision.error !== undefined) {
|
|
3777
|
-
internals.errorRegistry.fire(decision.error);
|
|
3778
|
-
filteredHeads.delete(headIdx);
|
|
3779
|
-
filteredCount -= 1;
|
|
3780
|
-
for (let j = 1; j < bucket.entries.length; j++) {
|
|
3781
|
-
const memberEntry = bucket.entries[j];
|
|
3782
|
-
if (memberEntry === undefined) continue;
|
|
3783
|
-
const memberValidatedIdx = renderableToValidatedIdx.get(memberEntry.renderableIndex);
|
|
3784
|
-
if (memberValidatedIdx !== undefined) {
|
|
3785
|
-
filteredSkips.delete(memberValidatedIdx);
|
|
3786
|
-
}
|
|
3787
|
-
}
|
|
3788
|
-
}
|
|
3789
|
-
}
|
|
3790
|
-
if (filteredCount !== foldDispatchPlan.foldedBucketCount) {
|
|
3791
|
-
foldDispatchPlan = {
|
|
3792
|
-
headBuckets: filteredHeads,
|
|
3793
|
-
skipIndices: filteredSkips,
|
|
3794
|
-
foldedBucketCount: filteredCount,
|
|
3795
|
-
};
|
|
3796
|
-
}
|
|
3797
|
-
}
|
|
3798
|
-
|
|
3799
|
-
// feat-20260622-chunk-gpu-instancing-sprite-tilemap M3 / w13 (D-3 +
|
|
3800
|
-
// AC-06): increment `render.instancing.foldedDraws` once per fold-
|
|
3801
|
-
// eligible head bucket retained after the cap-fallback filter above.
|
|
3802
|
-
// The metric tracks instanced drawIndexed call count for this frame
|
|
3803
|
-
// — cap-overrun buckets routed through the per-entity fallback exit
|
|
3804
|
-
// are removed from `foldDispatchPlan` and therefore not counted, by
|
|
3805
|
-
// construction (M2 / w11 cap-fallback + plan-strategy D-3 semantics).
|
|
3806
|
-
// Singleton buckets (mode-bypass under D-5, or non-foldable under
|
|
3807
|
-
// mode 0) carry `bucketSize === 1` and never enter `headBuckets`, so
|
|
3808
|
-
// the per-entity drawIndexed path correctly does not count.
|
|
3809
|
-
// SSOT helper lives in this record owner — engine code never
|
|
3810
|
-
// hardcodes the metric key string.
|
|
3811
|
-
incrementFoldedDrawsMetric(foldDispatchPlan, internals.metrics);
|
|
3812
|
-
}
|
|
3813
|
-
|
|
3814
|
-
return {
|
|
3815
|
-
validatedOrdered,
|
|
3816
|
-
shadowValidatedOrdered,
|
|
3817
|
-
foldDispatchPlan,
|
|
3818
|
-
materialSlotIndices,
|
|
3819
|
-
materialSlots,
|
|
3820
|
-
materialSlotOwners,
|
|
3821
|
-
materialSlotCount,
|
|
3822
|
-
};
|
|
3823
|
-
}
|
|
3824
|
-
|
|
3825
|
-
/**
|
|
3826
|
-
* feat-20260704 M3/w18: per-frame cache clean-up (despawn eviction), extracted
|
|
3827
|
-
* verbatim from `recordFrame`.
|
|
3828
|
-
*
|
|
3829
|
-
* feat-20260531-per-frame-bind-group-cache M4 / w14 (D-5): drop per-entity BG
|
|
3830
|
-
* cache entries (materialBgPerEntity / instancesBgPerEntity) + instance buffers
|
|
3831
|
-
* whose outer-Map entityKey (packed Entity u32) is absent from the current
|
|
3832
|
-
* validated set, preventing unbounded growth after entity despawn. view + mesh
|
|
3833
|
-
* caches are frame-shared (keyed by GPU resource handle objects, WeakMap chains
|
|
3834
|
-
* naturally bounded) and are not touched. feat-20260619 M4 / F11: destroy the
|
|
3835
|
-
* GPU instance buffer before Map.delete (D-6 symmetric release); failure fires
|
|
3836
|
-
* errorRegistry + continues the sweep.
|
|
3837
|
-
*
|
|
3838
|
-
* @internal
|
|
3839
|
-
*/
|
|
3840
|
-
function cleanPerFrameCaches(
|
|
3841
|
-
internals: RenderSystemInternals,
|
|
3842
|
-
frameState: RenderFrameState,
|
|
3843
|
-
validated: readonly ValidatedRenderable[],
|
|
3844
|
-
retainedEntityKeys: ReadonlySet<number> = new Set<number>(),
|
|
3845
|
-
): void {
|
|
3846
|
-
if (frameState.transientInstanceBuffers !== undefined) {
|
|
3847
|
-
disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
|
|
3848
|
-
}
|
|
3849
|
-
// Build a Set<number> of worldEntityKey composites from the validated
|
|
3850
|
-
// renderables. D-1a #4: validatedEntityKeys are worldEntityKey(worldId, entityKey)
|
|
3851
|
-
// composites matching the write-side keys of #1-#3 — cross-world false eviction
|
|
3852
|
-
// is prevented because worldEntityKey(0, k) !== worldEntityKey(1, k).
|
|
3853
|
-
const validatedEntityKeys = new Set<number>(retainedEntityKeys);
|
|
3854
|
-
const validatedInstanceKeys = new Set<number>(retainedEntityKeys);
|
|
3855
|
-
for (const v of validated) {
|
|
3856
|
-
const entityKey = worldEntityKey(v.source.worldId, v.source.entityKey);
|
|
3857
|
-
validatedEntityKeys.add(entityKey);
|
|
3858
|
-
validatedInstanceKeys.add(entityKey);
|
|
3859
|
-
if (v.source.instances !== undefined) {
|
|
3860
|
-
validatedInstanceKeys.add(instanceCollectionCacheKey(v.source.worldId, v.source.instances));
|
|
3861
|
-
}
|
|
3862
|
-
}
|
|
3863
|
-
|
|
3864
|
-
// Clean per-entity material BG cache: drop outer-Map entries whose
|
|
3865
|
-
// entityKey is absent from the current validated set. The shared and
|
|
3866
|
-
// singleton material caches have no entityKey and are not touched here.
|
|
3867
|
-
cleanPerEntityCache(frameState.materialBgPerEntity, validatedEntityKeys);
|
|
3868
|
-
|
|
3869
|
-
// Clean per-entity instances BG cache.
|
|
3870
|
-
cleanPerEntityCache(frameState.instancesBgPerEntity, validatedEntityKeys);
|
|
3871
|
-
|
|
3872
|
-
// D-5 retrofit: instanceBuffers clean-up. The instanceBuffers Map is
|
|
3873
|
-
// keyed by cacheKey (packed Entity u32, same as entityKey on
|
|
3874
|
-
// RenderableSnapshot). Drop entries whose key is no longer in the
|
|
3875
|
-
// validated set (OQ-3 / R-4). feat-20260619 M4 / F11: destroy the GPU
|
|
3876
|
-
// buffer before Map.delete so despawned entities release their
|
|
3877
|
-
// instance-buffer backing memory symmetrically (D-6).
|
|
3878
|
-
//
|
|
3879
|
-
// D-1a #1: instanceBuffers keys on the positive half (>= 0) are
|
|
3880
|
-
// worldEntityKey composites matching the write side. Negative half
|
|
3881
|
-
// fold-bucket keys (< 0) are NOT worldEntityKey; they are
|
|
3882
|
-
// material-handle-based and cross-world collision is semantically
|
|
3883
|
-
// correct (same material renders in same fold bucket).
|
|
3884
|
-
for (const [key, entry] of frameState.instanceBuffers.entries()) {
|
|
3885
|
-
if (!validatedInstanceKeys.has(key)) {
|
|
3886
|
-
if (!entry.buffer.isDestroyed) {
|
|
3887
|
-
const r = entry.buffer.destroy();
|
|
3888
|
-
if (!r.ok) internals.errorRegistry.fire(r.error);
|
|
3889
|
-
}
|
|
3890
|
-
frameState.instanceBuffers.delete(key);
|
|
3891
|
-
}
|
|
3892
|
-
}
|
|
3893
|
-
|
|
3894
|
-
// Chunk keys are `${worldEntityKey}:${chunkStart}`. Keep the key opaque to
|
|
3895
|
-
// the authoring API, but retain the numeric owner prefix for eviction.
|
|
3896
|
-
if (frameState.instanceBufferChunks !== undefined) {
|
|
3897
|
-
for (const [key, entry] of frameState.instanceBufferChunks.entries()) {
|
|
3898
|
-
const separator = key.indexOf(':');
|
|
3899
|
-
const ownerKey = separator < 0 ? Number.NaN : Number(key.slice(0, separator));
|
|
3900
|
-
if (validatedInstanceKeys.has(ownerKey)) continue;
|
|
3901
|
-
if (!entry.buffer.isDestroyed) {
|
|
3902
|
-
const r = entry.buffer.destroy();
|
|
3903
|
-
if (!r.ok) internals.errorRegistry.fire(r.error);
|
|
3904
|
-
}
|
|
3905
|
-
frameState.instanceBufferChunks.delete(key);
|
|
3906
|
-
}
|
|
3907
|
-
}
|
|
3908
|
-
|
|
3909
|
-
if (frameState.morphBuffers !== undefined) {
|
|
3910
|
-
for (const [key, entry] of frameState.morphBuffers.entries()) {
|
|
3911
|
-
if (validatedEntityKeys.has(key)) continue;
|
|
3912
|
-
if (!entry.buffer.isDestroyed) {
|
|
3913
|
-
const result = entry.buffer.destroy();
|
|
3914
|
-
if (!result.ok) internals.errorRegistry.fire(result.error);
|
|
3915
|
-
}
|
|
3916
|
-
frameState.morphBuffers.delete(key);
|
|
3917
|
-
}
|
|
3918
|
-
}
|
|
3919
|
-
}
|
|
3920
|
-
|
|
3921
|
-
/**
|
|
3922
|
-
* feat-20260704 M3/w18: record-stage fold operator linear scan, extracted
|
|
3923
|
-
* verbatim from `recordFrame`.
|
|
3924
|
-
*
|
|
3925
|
-
* feat-20260622-chunk-gpu-instancing-sprite-tilemap M1 / w4 + w5 (D-1, D-5):
|
|
3926
|
-
* groups transparent-sort-ordered dispatch entries with equal (Layer.value,
|
|
3927
|
-
* sortKey, materialHandle) into fold buckets. Mode-gate (D-5 extended): modes 0
|
|
3928
|
-
* (LAYER_Z) and 1 (LAYER_Y) fold using the pos z/y lanes; modes 2/3 bypass per-entity
|
|
3929
|
-
* (each entry a singleton bucket). Records `frameState.lastFoldBucketCount` =
|
|
3930
|
-
* fold-eligible buckets (bucketSize > 1) for the AC-06 metric. Empty dispatch
|
|
3931
|
-
* short-circuits (test fixtures pass null world).
|
|
3932
|
-
*
|
|
3933
|
-
* @internal
|
|
3934
|
-
*/
|
|
3935
|
-
function computeFoldBuckets(
|
|
3936
|
-
world: World,
|
|
3937
|
-
frameState: RenderFrameState,
|
|
3938
|
-
transparentDispatch: readonly DispatchEntry[],
|
|
3939
|
-
renderables: readonly RenderableSnapshot[],
|
|
3940
|
-
): readonly FoldBucket[] {
|
|
3941
|
-
if (transparentDispatch.length === 0) {
|
|
3942
|
-
frameState.lastFoldBucketCount = 0;
|
|
3943
|
-
return [];
|
|
3944
|
-
}
|
|
3945
|
-
const transparentSortCfg = getTransparentSortConfig(world);
|
|
3946
|
-
|
|
3947
|
-
// Only layer-Z / layer-Y modes can produce non-singleton fold buckets.
|
|
3948
|
-
// The other modes make one inert singleton per entry, which is discarded by
|
|
3949
|
-
// buildFoldDispatchPlan. Returning the same empty plan here avoids allocating
|
|
3950
|
-
// a transform matrix for every renderable when folding is disabled by mode.
|
|
3951
|
-
if (
|
|
3952
|
-
transparentSortCfg.mode !== TRANSPARENT_SORT_MODE_LAYER_Z &&
|
|
3953
|
-
transparentSortCfg.mode !== TRANSPARENT_SORT_MODE_LAYER_Y
|
|
3954
|
-
) {
|
|
3955
|
-
frameState.lastFoldBucketCount = 0;
|
|
3956
|
-
return [];
|
|
3957
|
-
}
|
|
3958
|
-
|
|
3959
|
-
// `transparentDispatch` is the legacy name for the complete sorted dispatch
|
|
3960
|
-
// list. Opaque entries can never participate in the transparent-only fold
|
|
3961
|
-
// plan, yet foldDispatchBuckets must preserve its general helper contract and
|
|
3962
|
-
// therefore materializes an inert singleton (including a Float32Array(16))
|
|
3963
|
-
// for each one. Filter only at this private production call site so the fold
|
|
3964
|
-
// helper keeps its testable semantics while record avoids work whose result
|
|
3965
|
-
// is provably discarded.
|
|
3966
|
-
let transparentCount = 0;
|
|
3967
|
-
for (const entry of transparentDispatch) {
|
|
3968
|
-
if (renderables[entry.renderableIndex]?.material.transparent === true) {
|
|
3969
|
-
transparentCount += 1;
|
|
3970
|
-
}
|
|
3971
|
-
}
|
|
3972
|
-
if (transparentCount === 0) {
|
|
3973
|
-
frameState.lastFoldBucketCount = 0;
|
|
3974
|
-
return [];
|
|
3975
|
-
}
|
|
3976
|
-
const foldCandidates =
|
|
3977
|
-
transparentCount === transparentDispatch.length
|
|
3978
|
-
? transparentDispatch
|
|
3979
|
-
: transparentDispatch.filter(
|
|
3980
|
-
(entry) => renderables[entry.renderableIndex]?.material.transparent === true,
|
|
3981
|
-
);
|
|
3982
|
-
const foldBuckets = foldDispatchBuckets(foldCandidates, transparentSortCfg.mode, renderables);
|
|
3983
|
-
// Count only fold-eligible buckets (bucketSize > 1) so the metric
|
|
3984
|
-
// surfaces fold actually reducing draws — singleton buckets under
|
|
3985
|
-
// mode bypass do not change draw count, so they do not contribute.
|
|
3986
|
-
let foldEligibleCount = 0;
|
|
3987
|
-
for (const b of foldBuckets) {
|
|
3988
|
-
if (b.bucketSize > 1) foldEligibleCount += 1;
|
|
3989
|
-
}
|
|
3990
|
-
frameState.lastFoldBucketCount = foldEligibleCount;
|
|
3991
|
-
return foldBuckets;
|
|
3992
|
-
}
|