@forgeax/engine-render 0.1.33 → 0.1.34
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 +1010 -124
- 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/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 +3 -3
- 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/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 +1 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/authoring.mjs +1 -1
- package/dist/barrel-distortion.d.ts +47 -0
- package/dist/barrel-distortion.d.ts.map +1 -0
- package/dist/{chunk-PA5DRJNI.mjs → chunk-ELJFJ2BU.mjs} +2 -2
- package/dist/chunk-ELJFJ2BU.mjs.map +1 -0
- package/dist/{chunk-ZDQWGQW7.mjs → chunk-HEB564WF.mjs} +4 -4
- package/dist/{chunk-ZDQWGQW7.mjs.map → chunk-HEB564WF.mjs.map} +1 -1
- package/dist/{chunk-VOZWDM3L.mjs → chunk-MKDIT2EM.mjs} +1307 -188
- package/dist/chunk-MKDIT2EM.mjs.map +1 -0
- package/dist/{chunk-LEAXOMYJ.mjs → chunk-OCDLEI6I.mjs} +3 -3
- package/dist/{chunk-LEAXOMYJ.mjs.map → chunk-OCDLEI6I.mjs.map} +1 -1
- package/dist/chunk-ODZWR6JQ.mjs +4927 -0
- package/dist/chunk-ODZWR6JQ.mjs.map +1 -0
- package/dist/{chunk-3RTCJTH4.mjs → chunk-SHFUFJ5N.mjs} +184 -13
- package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
- package/dist/{chunk-OO3DRYK7.mjs → chunk-UI5SFODX.mjs} +176 -96
- package/dist/chunk-UI5SFODX.mjs.map +1 -0
- package/dist/{chunk-PLAXOOX5.mjs → chunk-UINHLCZA.mjs} +561 -79
- package/dist/chunk-UINHLCZA.mjs.map +1 -0
- package/dist/{chunk-NB2S7ZRX.mjs → chunk-WEX45HKH.mjs} +127 -386
- package/dist/chunk-WEX45HKH.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 +6759 -2875
- package/dist/construct-renderer.mjs.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 +18 -4
- 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/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 +10 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/plan.d.ts +8 -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 +44 -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/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/skylight-bind-group.d.ts +10 -1
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +27 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +258 -8
- 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 +48 -100
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +7 -7
- package/dist/pbr-pipeline.d.ts +19 -1
- 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/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/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 +37 -3
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts +10 -61
- 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/main-pass-geometry.d.ts +21 -5
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +7 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +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/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 +22 -3
- 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/typed-frame-graph.d.ts +22 -2
- 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 +10 -0
- 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 +42 -4
- 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 +16 -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-residency.d.ts +12 -0
- package/dist/render-system-residency.d.ts.map +1 -0
- package/dist/render-system.d.ts +19 -14
- 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 +12 -5
- package/dist/scene/render-scene.d.ts.map +1 -1
- 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/temporal/gpu.d.ts +13 -1
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- 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__/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-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 +2 -2
- 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__/main-pass-material-medium-selection.unit.test.ts +146 -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__/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 +58 -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 +7 -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 +41 -7
- 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/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 +3 -3
- 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 +41 -18
- package/src/assembly/render-feature-post-processes.ts +54 -0
- package/src/assembly/renderer-facade.ts +4 -1
- 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 +115 -61
- 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 +214 -252
- 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/dynamic-geometry.ts +12 -1
- package/src/errors/cloud.ts +103 -0
- package/src/errors/render.ts +44 -7
- 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/features/barrel-distortion.ts +172 -0
- package/src/features/error-reporting.ts +26 -6
- package/src/features/host.ts +115 -14
- package/src/features/plan.ts +8 -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 +47 -0
- package/src/fullscreen-post-process-pass.ts +41 -9
- 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/skylight-bind-group.ts +49 -7
- package/src/index.ts +196 -9
- package/src/inspection-types.ts +162 -0
- package/src/instance-buffer-cache.ts +4 -4
- package/src/instances.ts +149 -569
- package/src/pbr-pipeline.ts +219 -13
- 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/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/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +1 -1
- 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__/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 +852 -0
- package/src/record/fold-instance-buffer.ts +51 -0
- package/src/record/frame-lighting.ts +36 -2
- package/src/record/frame-snapshot.ts +39 -3
- package/src/record/frame.ts +217 -1084
- package/src/record/gpu-draw-receipts.ts +38 -0
- package/src/record/gpu-timing.ts +48 -5
- package/src/record/main-pass-geometry.ts +251 -82
- package/src/record/main-pass-material.ts +66 -4
- package/src/record/main-pass-sprite-draws.ts +7 -65
- package/src/record/main-pass.ts +156 -20
- package/src/record/motion-blur-receipt.ts +91 -0
- package/src/record/probe-blend-buffer.ts +180 -0
- package/src/record/recovery-pipeline.ts +35 -3
- package/src/record/render-context.ts +39 -3
- package/src/record/shadow-pass.ts +30 -1
- package/src/record/skybox-post-pass.ts +28 -4
- package/src/record/typed-frame-graph.ts +770 -134
- package/src/record/view-ubo.ts +26 -3
- package/src/record/volume-params.ts +225 -0
- package/src/recovery/render-system-candidate.ts +235 -42
- package/src/reflection/record-owner.ts +7 -3
- package/src/render-contract.ts +48 -4
- package/src/render-graph-primitives.ts +7 -1
- package/src/render-pipeline.ts +130 -0
- package/src/render-system-extract-tail.ts +57 -61
- package/src/render-system-extract.ts +74 -76
- package/src/render-system-presentation.ts +56 -0
- package/src/render-system-producer-roots.ts +96 -0
- package/src/render-system-residency.ts +159 -0
- package/src/render-system.ts +347 -363
- package/src/scene/probe-blend.ts +96 -4
- package/src/scene/render-scene.ts +349 -280
- 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 +4 -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.map +0 -1
- 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.map +0 -1
- package/dist/chunk-VOZWDM3L.mjs.map +0 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { deriveVertexBufferLayoutFromProjection } from '@forgeax/engine-geometry';
|
|
2
1
|
import { frustum, mat3, mat4 } from '@forgeax/engine-math';
|
|
3
2
|
import type {
|
|
4
3
|
GraphAccess,
|
|
@@ -28,11 +27,11 @@ import {
|
|
|
28
27
|
gpuDrivenShadowDrawKey,
|
|
29
28
|
gpuDrivenSourceDrawItemIndex,
|
|
30
29
|
} from '../extract/gpu-driven';
|
|
31
|
-
import { GPU_SCENE_LAYOUTS } from '../gpu-scene-schema';
|
|
32
30
|
import {
|
|
33
31
|
GPU_BUFFER_USAGE_COPY_DST,
|
|
34
32
|
GPU_BUFFER_USAGE_INDEX,
|
|
35
33
|
GPU_BUFFER_USAGE_STORAGE,
|
|
34
|
+
GPU_BUFFER_USAGE_UNIFORM,
|
|
36
35
|
GPU_BUFFER_USAGE_VERTEX,
|
|
37
36
|
} from '../gpu-usage';
|
|
38
37
|
import type {
|
|
@@ -42,12 +41,15 @@ import type {
|
|
|
42
41
|
GpuDrivenProductionInspection,
|
|
43
42
|
GpuDrivenStructureMetrics,
|
|
44
43
|
} from '../inspection-types';
|
|
45
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
isCanonicalStandardPbrMaterialShader,
|
|
46
|
+
isStandardPbrMaterialShader,
|
|
47
|
+
resolvePipelineGroup2Contract,
|
|
48
|
+
} from '../pbr-pipeline';
|
|
46
49
|
import type { PipelineBuilderShaderModuleFactory } from '../pipeline-builder';
|
|
47
50
|
import type { ValidatedRenderable } from '../record/frame-snapshot';
|
|
48
|
-
import { worldEntityKey } from '../record/frame-snapshot';
|
|
51
|
+
import { getOpaqueResourceIdentity, worldEntityKey } from '../record/frame-snapshot';
|
|
49
52
|
import { geometryRenderStateForTopology } from '../record/main-pass-material';
|
|
50
|
-
import { MESH_SSBO_BYTES, packInstanceStorageBuffer } from '../record/mesh-ssbo';
|
|
51
53
|
import {
|
|
52
54
|
MATERIAL_PER_ENTITY_STRIDE,
|
|
53
55
|
type PipelineState,
|
|
@@ -55,7 +57,6 @@ import {
|
|
|
55
57
|
} from '../record/render-context';
|
|
56
58
|
import type { CameraSnapshot } from '../render-contract';
|
|
57
59
|
import type {
|
|
58
|
-
GpuDrivenSceneMeshBinding,
|
|
59
60
|
GpuDrivenStandardPbrFrameResources,
|
|
60
61
|
RenderPipelineFrame,
|
|
61
62
|
RenderPipelineGpuDrivenProjection,
|
|
@@ -69,14 +70,27 @@ import type {
|
|
|
69
70
|
import type { PersistentGpuDrivenState } from '../scene/render-scene';
|
|
70
71
|
import { selectLod } from '../scene/visibility/lod-selector';
|
|
71
72
|
import type { OcclusionFrameProjection } from '../scene/visibility/occlusion-runtime';
|
|
73
|
+
import type {
|
|
74
|
+
DynamicInputRange,
|
|
75
|
+
ReadonlyDynamicInputPage,
|
|
76
|
+
SurfaceDynamicInputFrame,
|
|
77
|
+
} from '../surface/dynamic-input';
|
|
78
|
+
import {
|
|
79
|
+
admitSingleLayerMediumSubmission,
|
|
80
|
+
type SurfaceGpuSubmissionAdmission,
|
|
81
|
+
} from '../surface/gpu-driven';
|
|
82
|
+
import type { SurfaceSubmissionCandidate } from '../surface/submission-observation';
|
|
72
83
|
import {
|
|
73
84
|
type GpuDrivenBatch,
|
|
74
85
|
type GpuDrivenCandidate,
|
|
86
|
+
inspectResourceClassSplits,
|
|
75
87
|
projectedHeightForCandidate,
|
|
76
88
|
type SubmissionPlan,
|
|
77
89
|
} from './batch-topology';
|
|
90
|
+
import { requiresProbeBlendRecord } from './material-artifacts';
|
|
78
91
|
import { materialBindingKey } from './material-bindings';
|
|
79
|
-
import {
|
|
92
|
+
import { MaterialAbiRasterAdapter } from './production-raster-material';
|
|
93
|
+
import { combineGpuResourceAllocationInspections } from './resource-allocation';
|
|
80
94
|
import {
|
|
81
95
|
type ShadowViewIdentity,
|
|
82
96
|
type ShadowViewProjection,
|
|
@@ -100,6 +114,39 @@ export interface StandardPbrSceneCandidate {
|
|
|
100
114
|
readonly materialIndex?: number;
|
|
101
115
|
}
|
|
102
116
|
|
|
117
|
+
/** Keep visible.x and the selected group(2) mesh page in one address domain. */
|
|
118
|
+
export function resolveGpuDrivenMeshGroup(input: {
|
|
119
|
+
readonly clustered: boolean;
|
|
120
|
+
readonly deformation: 'rigid' | 'skin';
|
|
121
|
+
readonly meshBindGroup: BindGroup;
|
|
122
|
+
readonly meshProjectionBuffer: Buffer;
|
|
123
|
+
readonly meshProjectionBytes: number;
|
|
124
|
+
readonly frameResources: Pick<
|
|
125
|
+
GpuDrivenStandardPbrFrameResources,
|
|
126
|
+
'clusterBindGroup' | 'clusterBindGroupForMesh' | 'clusterBindGroupForSkin'
|
|
127
|
+
>;
|
|
128
|
+
readonly skinPaletteBinding?:
|
|
129
|
+
| { readonly buffer: Buffer; readonly bindingWindowBytes: number }
|
|
130
|
+
| undefined;
|
|
131
|
+
}): BindGroup | undefined {
|
|
132
|
+
if (!input.clustered) return input.meshBindGroup;
|
|
133
|
+
if (input.deformation === 'skin') {
|
|
134
|
+
const palette = input.skinPaletteBinding;
|
|
135
|
+
return palette === undefined
|
|
136
|
+
? undefined
|
|
137
|
+
: input.frameResources.clusterBindGroupForSkin?.(
|
|
138
|
+
input.meshProjectionBuffer,
|
|
139
|
+
input.meshProjectionBytes,
|
|
140
|
+
palette.buffer,
|
|
141
|
+
palette.bindingWindowBytes,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return input.frameResources.clusterBindGroupForMesh?.(
|
|
145
|
+
input.meshProjectionBuffer,
|
|
146
|
+
input.meshProjectionBytes,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
103
150
|
export interface StandardPbrSceneProjection {
|
|
104
151
|
readonly entryPoint: string;
|
|
105
152
|
readonly layoutIdentity: string;
|
|
@@ -258,21 +305,34 @@ interface PreparedBatch {
|
|
|
258
305
|
readonly slotGeneration: number;
|
|
259
306
|
}
|
|
260
307
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
308
|
+
/**
|
|
309
|
+
* Renderer-owned join between the scene-index candidate rows and a Surface
|
|
310
|
+
* producer range. The user-facing range keeps its formal instance address;
|
|
311
|
+
* the frame row is assigned after the RenderScene plan has been filtered and
|
|
312
|
+
* its visible bases are known.
|
|
313
|
+
*/
|
|
314
|
+
interface SurfaceFrameRange {
|
|
315
|
+
readonly frameIndex: number;
|
|
316
|
+
readonly range?: DynamicInputRange;
|
|
317
|
+
readonly directMember: {
|
|
318
|
+
readonly worldIdentity: string;
|
|
319
|
+
readonly worldId: number;
|
|
320
|
+
readonly entityKey: number;
|
|
321
|
+
readonly drawItemIndex: number;
|
|
322
|
+
readonly instanceOrdinal: number;
|
|
273
323
|
};
|
|
274
324
|
}
|
|
275
325
|
|
|
326
|
+
interface SurfaceFrameProjection {
|
|
327
|
+
readonly ranges: readonly SurfaceFrameRange[];
|
|
328
|
+
readonly consumptionRanges: readonly DynamicInputRange[];
|
|
329
|
+
readonly capacity: number;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function surfaceDynamicInputError(expected: string, hint: string): RhiError {
|
|
333
|
+
return new RhiError({ code: 'rhi-descriptor-invalid', expected, hint });
|
|
334
|
+
}
|
|
335
|
+
|
|
276
336
|
function activeFrustum(camera: CameraSnapshot): Float32Array {
|
|
277
337
|
const projection = mat4.create();
|
|
278
338
|
if (camera.projection === 'orthographic') {
|
|
@@ -930,6 +990,136 @@ function shadowCasterMembershipSignature(
|
|
|
930
990
|
|
|
931
991
|
type FilteredProductionPlan = ReturnType<typeof filteredPlan>;
|
|
932
992
|
|
|
993
|
+
function isSingleLayerMediumArtifact(artifact: MaterialShaderArtifact): boolean {
|
|
994
|
+
return artifact.receipt?.surface?.model === 'single-layer-medium';
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function isSingleLayerMediumBatch(batch: PreparedBatch): boolean {
|
|
998
|
+
return isSingleLayerMediumArtifact(batch.artifact);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Join Surface ranges to the renderer-owned candidate rows in the visible
|
|
1003
|
+
* stream. The GPU culler may assign atomic compact slots in any order, but
|
|
1004
|
+
* each member preserves the candidate's batch-local projected row in
|
|
1005
|
+
* `visibleItems.x`; frame rows use that same identity rather than the slot.
|
|
1006
|
+
* The producer's `instanceIndex` remains in the range and is copied into the
|
|
1007
|
+
* frame record for the Surface ABI.
|
|
1008
|
+
*/
|
|
1009
|
+
function buildSurfaceFrameRanges(
|
|
1010
|
+
filtered: FilteredProductionPlan,
|
|
1011
|
+
ranges: readonly DynamicInputRange[],
|
|
1012
|
+
scene: PersistentGpuDrivenState,
|
|
1013
|
+
): Result<readonly SurfaceFrameRange[], RhiError> {
|
|
1014
|
+
const slotsByPrimitive = new Map(scene.slots.map((slot) => [slot.slot, slot]));
|
|
1015
|
+
const surfaceMembers: Array<{
|
|
1016
|
+
readonly frameIndex: number;
|
|
1017
|
+
readonly identity: string;
|
|
1018
|
+
readonly directMember: SurfaceFrameRange['directMember'];
|
|
1019
|
+
}> = [];
|
|
1020
|
+
for (const prepared of filtered.batches) {
|
|
1021
|
+
if (!isSingleLayerMediumBatch(prepared)) continue;
|
|
1022
|
+
let visibleMemberIndex = 0;
|
|
1023
|
+
for (const candidate of prepared.batch.candidates) {
|
|
1024
|
+
if (
|
|
1025
|
+
(candidate as GpuDrivenCandidate & { readonly submitAdmission?: boolean })
|
|
1026
|
+
.submitAdmission === false
|
|
1027
|
+
) {
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
const slot = slotsByPrimitive.get(candidate.primitiveIndex);
|
|
1031
|
+
const worldIdentity = slot === undefined ? undefined : scene.worldIdentities?.[slot.worldId];
|
|
1032
|
+
if (slot === undefined || worldIdentity === undefined) {
|
|
1033
|
+
return err(
|
|
1034
|
+
new RhiError({
|
|
1035
|
+
code: 'rhi-descriptor-invalid',
|
|
1036
|
+
expected:
|
|
1037
|
+
'every admitted medium draw member has a renderer slot and public World identity',
|
|
1038
|
+
hint: `rebuild the persistent scene projection before publishing Surface ranges (primitive=${candidate.primitiveIndex})`,
|
|
1039
|
+
}),
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
surfaceMembers.push({
|
|
1043
|
+
frameIndex: prepared.batch.visibleBase + visibleMemberIndex,
|
|
1044
|
+
directMember: {
|
|
1045
|
+
worldIdentity,
|
|
1046
|
+
worldId: slot.worldId,
|
|
1047
|
+
entityKey: slot.entityKey,
|
|
1048
|
+
drawItemIndex: candidate.drawItemIndex,
|
|
1049
|
+
instanceOrdinal: candidate.instanceOrdinal,
|
|
1050
|
+
},
|
|
1051
|
+
identity: JSON.stringify([
|
|
1052
|
+
worldIdentity,
|
|
1053
|
+
slot.entityKey,
|
|
1054
|
+
candidate.drawItemIndex,
|
|
1055
|
+
candidate.instanceOrdinal,
|
|
1056
|
+
]),
|
|
1057
|
+
});
|
|
1058
|
+
visibleMemberIndex += 1;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
if (ranges.length === 0) {
|
|
1062
|
+
return ok(
|
|
1063
|
+
Object.freeze(
|
|
1064
|
+
surfaceMembers.map(({ frameIndex, directMember }) => ({ frameIndex, directMember })),
|
|
1065
|
+
),
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
const rangeByIdentity = new Map<string, DynamicInputRange>();
|
|
1069
|
+
for (const range of ranges) {
|
|
1070
|
+
const identity = JSON.stringify([
|
|
1071
|
+
range.member.worldIdentity,
|
|
1072
|
+
range.member.entityKey,
|
|
1073
|
+
range.member.drawItemIndex,
|
|
1074
|
+
range.member.instanceOrdinal,
|
|
1075
|
+
]);
|
|
1076
|
+
if (rangeByIdentity.has(identity)) {
|
|
1077
|
+
return err(
|
|
1078
|
+
new RhiError({
|
|
1079
|
+
code: 'rhi-descriptor-invalid',
|
|
1080
|
+
expected: 'at most one Surface dynamic range for each stable draw member',
|
|
1081
|
+
hint: `remove the duplicate producer range for member ${identity}`,
|
|
1082
|
+
}),
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
rangeByIdentity.set(identity, range);
|
|
1086
|
+
}
|
|
1087
|
+
if (ranges.length !== surfaceMembers.length) {
|
|
1088
|
+
return err(
|
|
1089
|
+
new RhiError({
|
|
1090
|
+
code: 'rhi-descriptor-invalid',
|
|
1091
|
+
expected: 'one stable-identity Surface dynamic range for every admitted medium draw member',
|
|
1092
|
+
hint: `publish one range per admitted Surface member (ranges=${ranges.length}, members=${surfaceMembers.length})`,
|
|
1093
|
+
}),
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
const frameRanges: SurfaceFrameRange[] = [];
|
|
1097
|
+
for (const member of surfaceMembers) {
|
|
1098
|
+
const range = rangeByIdentity.get(member.identity);
|
|
1099
|
+
if (range === undefined) {
|
|
1100
|
+
return err(
|
|
1101
|
+
new RhiError({
|
|
1102
|
+
code: 'rhi-descriptor-invalid',
|
|
1103
|
+
expected: 'every admitted medium draw member resolves one stable-identity Surface range',
|
|
1104
|
+
hint: `publish the missing producer range for member ${member.identity}`,
|
|
1105
|
+
}),
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
rangeByIdentity.delete(member.identity);
|
|
1109
|
+
frameRanges.push({ frameIndex: member.frameIndex, range, directMember: member.directMember });
|
|
1110
|
+
}
|
|
1111
|
+
if (rangeByIdentity.size !== 0) {
|
|
1112
|
+
return err(
|
|
1113
|
+
new RhiError({
|
|
1114
|
+
code: 'rhi-descriptor-invalid',
|
|
1115
|
+
expected: 'every Surface range identifies an admitted medium draw member',
|
|
1116
|
+
hint: `remove ranges for non-admitted members: ${[...rangeByIdentity.keys()].join(', ')}`,
|
|
1117
|
+
}),
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
return ok(Object.freeze(frameRanges));
|
|
1121
|
+
}
|
|
1122
|
+
|
|
933
1123
|
function sceneRequiresGpuDrivenArtifact(scene: PersistentGpuDrivenState): boolean {
|
|
934
1124
|
return scene.plan.batches.some((batch) =>
|
|
935
1125
|
batch.candidates.some((candidate) => {
|
|
@@ -1139,736 +1329,11 @@ function materialArtifactSignature(
|
|
|
1139
1329
|
}
|
|
1140
1330
|
|
|
1141
1331
|
/** The GPU-driven bridge to the existing four-group Standard PBR pipeline. */
|
|
1142
|
-
class MaterialAbiRasterAdapter {
|
|
1143
|
-
private readonly pipelines = new Map<string, RenderPipeline>();
|
|
1144
|
-
private readonly projectionBuffers = new Set<Buffer>();
|
|
1145
|
-
private readonly projectionBindings = new Map<
|
|
1146
|
-
string,
|
|
1147
|
-
{
|
|
1148
|
-
readonly buffer: Buffer;
|
|
1149
|
-
readonly bindGroup: BindGroup;
|
|
1150
|
-
readonly capacityBytes: number;
|
|
1151
|
-
rows: Float32Array;
|
|
1152
|
-
}
|
|
1153
|
-
>();
|
|
1154
|
-
/**
|
|
1155
|
-
* Projection rows are graph-generation resources. A new topology may stage
|
|
1156
|
-
* a replacement before the graph is promoted; keeping staged bindings out
|
|
1157
|
-
* of the active map lets an aborted graph leave the last submitted rows
|
|
1158
|
-
* intact. Superseded buffers are fence-retired on promotion.
|
|
1159
|
-
*/
|
|
1160
|
-
private readonly stagedProjectionBindings = new Map<
|
|
1161
|
-
string,
|
|
1162
|
-
{
|
|
1163
|
-
readonly buffer: Buffer;
|
|
1164
|
-
readonly bindGroup: BindGroup;
|
|
1165
|
-
readonly capacityBytes: number;
|
|
1166
|
-
rows: Float32Array;
|
|
1167
|
-
}
|
|
1168
|
-
>();
|
|
1169
|
-
private readonly pendingProjectionRetirements = new Set<Buffer>();
|
|
1170
|
-
private readonly destroyedProjectionBuffers = new Set<Buffer>();
|
|
1171
|
-
private projectionDisposed = false;
|
|
1172
|
-
private stagedProjectionKeys = new Set<string>();
|
|
1173
|
-
private readonly activeProjectionRowsAtStage = new Map<string, Float32Array>();
|
|
1174
|
-
private readonly visibleWindowBindGroups = new Map<Buffer, Map<string, BindGroup>>();
|
|
1175
|
-
|
|
1176
|
-
private constructor(
|
|
1177
|
-
private readonly device: RhiDevice,
|
|
1178
|
-
private readonly pipelineLayout: import('@forgeax/engine-rhi').PipelineLayout,
|
|
1179
|
-
private readonly meshLayout: BindGroupLayout,
|
|
1180
|
-
private readonly instancesBindGroupLayout: BindGroupLayout,
|
|
1181
|
-
private readonly programs: ReadonlyMap<string, GpuDrivenPbrProgram> | undefined,
|
|
1182
|
-
private instancesBindGroup: BindGroup,
|
|
1183
|
-
private readonly instanceBuffer: Buffer,
|
|
1184
|
-
private readonly instanceBufferSize: number,
|
|
1185
|
-
private visibleBuffer: Buffer,
|
|
1186
|
-
private readonly artifact: MaterialShaderArtifact,
|
|
1187
|
-
private readonly deformation: 'rigid' | 'skin',
|
|
1188
|
-
private readonly pipelineFactory: MaterialPipelineFactory | undefined,
|
|
1189
|
-
private paletteBuffer: Buffer | undefined,
|
|
1190
|
-
private paletteBindingWindowBytes: number | undefined,
|
|
1191
|
-
) {}
|
|
1192
|
-
|
|
1193
|
-
static create(input: {
|
|
1194
|
-
readonly device: RhiDevice;
|
|
1195
|
-
readonly pipelineState: PipelineState;
|
|
1196
|
-
readonly artifact: MaterialShaderArtifact;
|
|
1197
|
-
readonly visibleBuffer: Buffer;
|
|
1198
|
-
readonly deformation?: 'rigid' | 'skin';
|
|
1199
|
-
readonly paletteBuffer?: Buffer;
|
|
1200
|
-
readonly paletteBindingWindowBytes?: number;
|
|
1201
|
-
/** Renderer-owned factory for the selected artifact's actual PSO. */
|
|
1202
|
-
readonly pipelineFactory?: MaterialPipelineFactory;
|
|
1203
|
-
}): Result<MaterialAbiRasterAdapter, RhiError | GpuDrivenPreparationError> {
|
|
1204
|
-
const deformation = input.deformation ?? 'rigid';
|
|
1205
|
-
const receipt = input.artifact.receipt;
|
|
1206
|
-
if (receipt === undefined) {
|
|
1207
|
-
return err(
|
|
1208
|
-
new RhiError({
|
|
1209
|
-
code: 'rhi-not-available',
|
|
1210
|
-
expected: 'published MaterialProgramAbi receipt for the selected material artifact',
|
|
1211
|
-
hint: 'recook and publish the material before creating a GPU-driven raster owner',
|
|
1212
|
-
}),
|
|
1213
|
-
);
|
|
1214
|
-
}
|
|
1215
|
-
const materialRowMatchesSceneTable =
|
|
1216
|
-
receipt.materialRow.byteLength === GPU_SCENE_LAYOUTS.material.stride &&
|
|
1217
|
-
new Set(receipt.materialRow.fields).size === receipt.materialRow.fields.length;
|
|
1218
|
-
if (!materialRowMatchesSceneTable) {
|
|
1219
|
-
return err(
|
|
1220
|
-
new GpuDrivenPreparationError('material-abi-not-executable', {
|
|
1221
|
-
reason: 'material-abi-not-executable',
|
|
1222
|
-
owner: 'material',
|
|
1223
|
-
expected: `scene-index material row ${GPU_SCENE_LAYOUTS.material.stride} bytes with unique producer fields`,
|
|
1224
|
-
actual: `row ${receipt.materialRow.byteLength} bytes with fields ${receipt.materialRow.fields.join(',')}`,
|
|
1225
|
-
}),
|
|
1226
|
-
);
|
|
1227
|
-
}
|
|
1228
|
-
if (
|
|
1229
|
-
receipt.reflection.layoutIdentity !== input.artifact.layoutIdentity ||
|
|
1230
|
-
receipt.directEntry.length === 0 ||
|
|
1231
|
-
receipt.sceneIndexEntry.length === 0 ||
|
|
1232
|
-
receipt.resourceSlots.some((slot) => slot.group !== 1)
|
|
1233
|
-
) {
|
|
1234
|
-
return err(
|
|
1235
|
-
new RhiError({
|
|
1236
|
-
code: 'rhi-descriptor-invalid',
|
|
1237
|
-
expected: `complete selected MaterialProgramAbi reflection, scene row stride ${GPU_SCENE_LAYOUTS.material.stride}, and resource slots`,
|
|
1238
|
-
hint: 'repair the producer publication or use the owner for its non-scene material layout',
|
|
1239
|
-
}),
|
|
1240
|
-
);
|
|
1241
|
-
}
|
|
1242
|
-
// Scene-index shaders declare the dedicated slot-3 ABI. Keep the legacy
|
|
1243
|
-
// fields as a compatibility fallback for synthetic fixtures; the real
|
|
1244
|
-
// factory publishes the dedicated handles whenever the capable storage
|
|
1245
|
-
// lane is enabled, so production draws never bind the direct-draw BGL.
|
|
1246
|
-
const pipelineLayout =
|
|
1247
|
-
deformation === 'skin'
|
|
1248
|
-
? input.pipelineState.gpuDrivenPbrSkinPipelineLayout
|
|
1249
|
-
: input.pipelineState.gpuDrivenPbrPipelineLayout;
|
|
1250
|
-
const instancesBindGroupLayout = input.pipelineState.gpuDrivenInstancesBindGroupLayout;
|
|
1251
|
-
if (pipelineLayout == null || instancesBindGroupLayout == null) {
|
|
1252
|
-
return err(
|
|
1253
|
-
new RhiError({
|
|
1254
|
-
code: 'rhi-not-available',
|
|
1255
|
-
expected: `${deformation === 'skin' ? 'skinned ' : ''}published material pipeline layout`,
|
|
1256
|
-
hint: 'initialize the producer material pipeline before enabling GPU-driven raster',
|
|
1257
|
-
}),
|
|
1258
|
-
);
|
|
1259
|
-
}
|
|
1260
|
-
const programs = input.pipelineState.gpuDrivenPbrPrograms;
|
|
1261
|
-
if (
|
|
1262
|
-
input.pipelineFactory === undefined &&
|
|
1263
|
-
(programs === undefined ||
|
|
1264
|
-
resolveStandardPbrProgram(
|
|
1265
|
-
programs,
|
|
1266
|
-
input.artifact.material,
|
|
1267
|
-
input.artifact.receipt?.vertexInputs.some((attribute) => attribute.semantic === 'color'),
|
|
1268
|
-
) === undefined)
|
|
1269
|
-
) {
|
|
1270
|
-
return err(
|
|
1271
|
-
new RhiError({
|
|
1272
|
-
code: 'rhi-not-available',
|
|
1273
|
-
expected: `producer-owned ${deformation === 'skin' ? 'skinned ' : ''}material shader module for the current device generation`,
|
|
1274
|
-
hint: 'publish the selected material module from buildReadyWebGPU before enabling GPU-driven raster',
|
|
1275
|
-
}),
|
|
1276
|
-
);
|
|
1277
|
-
}
|
|
1278
|
-
const meshLayout =
|
|
1279
|
-
deformation === 'skin'
|
|
1280
|
-
? input.pipelineState.pbrSkinMeshBindGroupLayout
|
|
1281
|
-
: input.pipelineState.meshBindGroupLayout;
|
|
1282
|
-
if (meshLayout === null) {
|
|
1283
|
-
return err(
|
|
1284
|
-
new RhiError({
|
|
1285
|
-
code: 'rhi-not-available',
|
|
1286
|
-
expected: `${deformation === 'skin' ? 'skinned ' : ''}material mesh bind-group layout`,
|
|
1287
|
-
hint: 'publish the exact mesh-array layout selected by the material receipt',
|
|
1288
|
-
}),
|
|
1289
|
-
);
|
|
1290
|
-
}
|
|
1291
|
-
if (
|
|
1292
|
-
deformation === 'skin' &&
|
|
1293
|
-
(input.paletteBuffer === undefined || input.paletteBindingWindowBytes === undefined)
|
|
1294
|
-
) {
|
|
1295
|
-
return err(
|
|
1296
|
-
new RhiError({
|
|
1297
|
-
code: 'rhi-not-available',
|
|
1298
|
-
expected: 'persistent skin palette buffer and binding window',
|
|
1299
|
-
hint: 'keep the palette receipt and the GPU-driven skinned projection on one frame identity',
|
|
1300
|
-
}),
|
|
1301
|
-
);
|
|
1302
|
-
}
|
|
1303
|
-
const identity = new Float32Array(16);
|
|
1304
|
-
identity[0] = 1;
|
|
1305
|
-
identity[5] = 1;
|
|
1306
|
-
identity[10] = 1;
|
|
1307
|
-
identity[15] = 1;
|
|
1308
|
-
const instancePayload = packInstanceStorageBuffer(identity);
|
|
1309
|
-
const instanceBuffer = input.device.createBuffer({
|
|
1310
|
-
label: 'standard-pbr-gpu-driven-instance-data',
|
|
1311
|
-
size: instancePayload.byteLength,
|
|
1312
|
-
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
1313
|
-
});
|
|
1314
|
-
if (!instanceBuffer.ok) return instanceBuffer;
|
|
1315
|
-
const instanceWrite = input.device.queue.writeBuffer(instanceBuffer.value, 0, instancePayload);
|
|
1316
|
-
if (!instanceWrite.ok) return instanceWrite;
|
|
1317
|
-
const instancesBindGroup = input.device.createBindGroup({
|
|
1318
|
-
label: 'standard-pbr-gpu-driven-visible-instances-bg',
|
|
1319
|
-
layout: instancesBindGroupLayout,
|
|
1320
|
-
entries: [
|
|
1321
|
-
{
|
|
1322
|
-
binding: 0,
|
|
1323
|
-
resource: bufferBinding(instanceBuffer.value, 0, instancePayload.byteLength),
|
|
1324
|
-
},
|
|
1325
|
-
{
|
|
1326
|
-
binding: 1,
|
|
1327
|
-
resource: bufferBinding(input.visibleBuffer),
|
|
1328
|
-
},
|
|
1329
|
-
{
|
|
1330
|
-
binding: 2,
|
|
1331
|
-
resource: bufferBinding(input.visibleBuffer),
|
|
1332
|
-
},
|
|
1333
|
-
],
|
|
1334
|
-
});
|
|
1335
|
-
if (!instancesBindGroup.ok) return instancesBindGroup;
|
|
1336
|
-
return ok(
|
|
1337
|
-
new MaterialAbiRasterAdapter(
|
|
1338
|
-
input.device,
|
|
1339
|
-
pipelineLayout,
|
|
1340
|
-
meshLayout,
|
|
1341
|
-
instancesBindGroupLayout,
|
|
1342
|
-
programs,
|
|
1343
|
-
instancesBindGroup.value,
|
|
1344
|
-
instanceBuffer.value,
|
|
1345
|
-
instancePayload.byteLength,
|
|
1346
|
-
input.visibleBuffer,
|
|
1347
|
-
input.artifact,
|
|
1348
|
-
deformation,
|
|
1349
|
-
input.pipelineFactory,
|
|
1350
|
-
input.paletteBuffer,
|
|
1351
|
-
input.paletteBindingWindowBytes,
|
|
1352
|
-
),
|
|
1353
|
-
);
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
ensurePaletteBuffer(
|
|
1357
|
-
paletteBuffer: Buffer | undefined,
|
|
1358
|
-
paletteBindingWindowBytes: number | undefined,
|
|
1359
|
-
): Result<void, RhiError> {
|
|
1360
|
-
if (this.deformation !== 'skin') return ok(undefined);
|
|
1361
|
-
if (paletteBuffer === undefined || paletteBindingWindowBytes === undefined) {
|
|
1362
|
-
return err(
|
|
1363
|
-
new RhiError({
|
|
1364
|
-
code: 'rhi-not-available',
|
|
1365
|
-
expected: 'persistent skin palette buffer and binding window',
|
|
1366
|
-
hint: 'publish a storage-backed SkinPaletteReceipt before the GPU-driven skinned lane',
|
|
1367
|
-
}),
|
|
1368
|
-
);
|
|
1369
|
-
}
|
|
1370
|
-
this.paletteBuffer = paletteBuffer;
|
|
1371
|
-
this.paletteBindingWindowBytes = paletteBindingWindowBytes;
|
|
1372
|
-
return ok(undefined);
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
ensureVisibleBuffer(visibleBuffer: Buffer): Result<void, RhiError> {
|
|
1376
|
-
if (this.visibleBuffer === visibleBuffer) return ok(undefined);
|
|
1377
|
-
const created = this.device.createBindGroup({
|
|
1378
|
-
label: 'standard-pbr-gpu-driven-visible-instances-bg',
|
|
1379
|
-
layout: this.instancesBindGroupLayout,
|
|
1380
|
-
entries: [
|
|
1381
|
-
{
|
|
1382
|
-
binding: 0,
|
|
1383
|
-
resource: bufferBinding(this.instanceBuffer, 0, this.instanceBufferSize),
|
|
1384
|
-
},
|
|
1385
|
-
{ binding: 1, resource: bufferBinding(visibleBuffer) },
|
|
1386
|
-
{ binding: 2, resource: bufferBinding(visibleBuffer) },
|
|
1387
|
-
],
|
|
1388
|
-
});
|
|
1389
|
-
if (!created.ok) return created;
|
|
1390
|
-
this.instancesBindGroup = created.value;
|
|
1391
|
-
this.visibleBuffer = visibleBuffer;
|
|
1392
|
-
return ok(undefined);
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
visibleWindowBindGroup(
|
|
1396
|
-
visibleBase: number,
|
|
1397
|
-
visibleCapacity: number,
|
|
1398
|
-
): Result<BindGroup, RhiError> {
|
|
1399
|
-
return this.visibleWindowBindGroupForBuffer(this.visibleBuffer, visibleBase, visibleCapacity);
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
visibleWindowBindGroupForBuffer(
|
|
1403
|
-
visibleBuffer: Buffer,
|
|
1404
|
-
visibleBase: number,
|
|
1405
|
-
visibleCapacity: number,
|
|
1406
|
-
): Result<BindGroup, RhiError> {
|
|
1407
|
-
const offset = visibleBase * 8;
|
|
1408
|
-
const size = visibleCapacity * 8;
|
|
1409
|
-
if (offset % 256 !== 0 || size <= 0) {
|
|
1410
|
-
return err(
|
|
1411
|
-
new RhiError({
|
|
1412
|
-
code: 'rhi-descriptor-invalid',
|
|
1413
|
-
expected: '256-byte-aligned non-empty visible window',
|
|
1414
|
-
hint: `allocate each GPU-driven batch visible window on the portable storage-buffer boundary (base=${visibleBase}, capacity=${visibleCapacity}, offset=${offset}, size=${size})`,
|
|
1415
|
-
}),
|
|
1416
|
-
);
|
|
1417
|
-
}
|
|
1418
|
-
let groups = this.visibleWindowBindGroups.get(visibleBuffer);
|
|
1419
|
-
if (groups === undefined) {
|
|
1420
|
-
groups = new Map();
|
|
1421
|
-
this.visibleWindowBindGroups.set(visibleBuffer, groups);
|
|
1422
|
-
}
|
|
1423
|
-
const key = `${visibleBase}:${visibleCapacity}`;
|
|
1424
|
-
const cached = groups.get(key);
|
|
1425
|
-
if (cached !== undefined) return ok(cached);
|
|
1426
|
-
const created = this.device.createBindGroup({
|
|
1427
|
-
label: `standard-pbr-gpu-driven-visible-window-${visibleBase}`,
|
|
1428
|
-
layout: this.instancesBindGroupLayout,
|
|
1429
|
-
entries: [
|
|
1430
|
-
{
|
|
1431
|
-
binding: 0,
|
|
1432
|
-
resource: bufferBinding(this.instanceBuffer, 0, this.instanceBufferSize),
|
|
1433
|
-
},
|
|
1434
|
-
{ binding: 1, resource: bufferBinding(visibleBuffer, offset, size) },
|
|
1435
|
-
{ binding: 2, resource: bufferBinding(visibleBuffer, offset, size) },
|
|
1436
|
-
],
|
|
1437
|
-
});
|
|
1438
|
-
if (!created.ok) return created;
|
|
1439
|
-
groups.set(key, created.value);
|
|
1440
|
-
return ok(created.value);
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
pipeline(
|
|
1444
|
-
format: TextureFormat,
|
|
1445
|
-
sampleCount: 1 | 4,
|
|
1446
|
-
layoutProjection: MeshGpuHandles['layoutProjection'],
|
|
1447
|
-
shaderUvSetCount: number,
|
|
1448
|
-
topology: GpuDrivenBatch['key']['topology'],
|
|
1449
|
-
stripIndexFormat: 'uint16' | 'uint32' | undefined,
|
|
1450
|
-
renderState: MaterialRenderState | undefined,
|
|
1451
|
-
selectedArtifact: MaterialShaderArtifact = this.artifact,
|
|
1452
|
-
additionalColorFormats: readonly TextureFormat[] = [],
|
|
1453
|
-
): Result<RenderPipeline, RhiError> {
|
|
1454
|
-
const vertexLayout = deriveVertexBufferLayoutFromProjection(layoutProjection, {
|
|
1455
|
-
shaderUvSetCount,
|
|
1456
|
-
})[0];
|
|
1457
|
-
if (vertexLayout === undefined) {
|
|
1458
|
-
return err(
|
|
1459
|
-
new RhiError({
|
|
1460
|
-
code: 'shader-compile-failed',
|
|
1461
|
-
expected: 'non-empty geometry VertexLayoutProjection',
|
|
1462
|
-
hint: 'prepare a receipt-compatible mesh projection before Standard PBR raster',
|
|
1463
|
-
}),
|
|
1464
|
-
);
|
|
1465
|
-
}
|
|
1466
|
-
const receipt = selectedArtifact.receipt;
|
|
1467
|
-
if (receipt === undefined) {
|
|
1468
|
-
return err(
|
|
1469
|
-
new RhiError({
|
|
1470
|
-
code: 'rhi-not-available',
|
|
1471
|
-
expected: 'selected MaterialProgramAbi receipt for GPU-driven raster',
|
|
1472
|
-
hint: 'keep the draw on the producer recovery path until its ABI is published',
|
|
1473
|
-
}),
|
|
1474
|
-
);
|
|
1475
|
-
}
|
|
1476
|
-
const program =
|
|
1477
|
-
this.programs === undefined
|
|
1478
|
-
? undefined
|
|
1479
|
-
: resolveStandardPbrProgram(
|
|
1480
|
-
this.programs,
|
|
1481
|
-
this.artifact.material,
|
|
1482
|
-
layoutProjection.attributes.some((attribute) => attribute.key === 'color'),
|
|
1483
|
-
);
|
|
1484
|
-
if (this.pipelineFactory === undefined && program === undefined) {
|
|
1485
|
-
return err(
|
|
1486
|
-
new RhiError({
|
|
1487
|
-
code: 'rhi-not-available',
|
|
1488
|
-
expected: 'producer-owned Standard PBR program matching the geometry vertex layout',
|
|
1489
|
-
hint: 'publish both vertex-color variants for the current device generation',
|
|
1490
|
-
}),
|
|
1491
|
-
);
|
|
1492
|
-
}
|
|
1493
|
-
const key = `${format}:${additionalColorFormats.join(',')}|${sampleCount}|${vertexLayout.arrayStride}|${topology}|${stripIndexFormat ?? ''}|${JSON.stringify(renderState ?? null)}|${selectedArtifact.material}|${selectedArtifact.layoutIdentity}|${receipt.receiptIdentity}|${receipt.generation}|${selectedArtifact.variantSet ?? ''}|${materialArtifactProgramIdentity(selectedArtifact)}|${selectedArtifact.vertexEntry ?? receipt.sceneIndexEntry}|${selectedArtifact.fragmentEntry ?? ''}|${layoutProjection.digest}|uv${shaderUvSetCount}`;
|
|
1494
|
-
const cached = this.pipelines.get(key);
|
|
1495
|
-
if (cached !== undefined) return ok(cached);
|
|
1496
|
-
if (this.pipelineFactory !== undefined) {
|
|
1497
|
-
const pipelineEntry = this.pipelineFactory(
|
|
1498
|
-
selectedArtifact.specializationKey ?? selectedArtifact.material,
|
|
1499
|
-
false,
|
|
1500
|
-
renderState,
|
|
1501
|
-
topology,
|
|
1502
|
-
stripIndexFormat,
|
|
1503
|
-
selectedArtifact.variantSet,
|
|
1504
|
-
'forward',
|
|
1505
|
-
undefined,
|
|
1506
|
-
sampleCount,
|
|
1507
|
-
format,
|
|
1508
|
-
selectedArtifact.uvSetCount ?? shaderUvSetCount,
|
|
1509
|
-
'depth24plus-stencil8',
|
|
1510
|
-
undefined,
|
|
1511
|
-
layoutProjection,
|
|
1512
|
-
// GPU-driven receipts are already producer-validated. Use the
|
|
1513
|
-
// synchronous shader-module adapter for this submission seam so a
|
|
1514
|
-
// newly loaded authored material cannot lose its first draw to the
|
|
1515
|
-
// ordinary async pre-bake warm-up window.
|
|
1516
|
-
'immediate',
|
|
1517
|
-
this.deformation === 'skin' ? 'gpu-driven-skin' : 'gpu-driven-pbr',
|
|
1518
|
-
receipt.sceneIndexEntry,
|
|
1519
|
-
additionalColorFormats,
|
|
1520
|
-
selectedArtifact.vertexEntry,
|
|
1521
|
-
selectedArtifact.fragmentEntry,
|
|
1522
|
-
);
|
|
1523
|
-
if (pipelineEntry === null) {
|
|
1524
|
-
return err(
|
|
1525
|
-
new RhiError({
|
|
1526
|
-
code: 'rhi-not-available',
|
|
1527
|
-
expected: `producer-owned scene-index pipeline for material '${selectedArtifact.material}'`,
|
|
1528
|
-
hint: 'publish a matching program, entry, layout and resource contract before GPU admission',
|
|
1529
|
-
}),
|
|
1530
|
-
);
|
|
1531
|
-
}
|
|
1532
|
-
const expectedGroup2Contract = resolvePipelineGroup2Contract(selectedArtifact.wgsl);
|
|
1533
|
-
if (pipelineEntry.group2Contract !== expectedGroup2Contract) {
|
|
1534
|
-
return err(
|
|
1535
|
-
new RhiError({
|
|
1536
|
-
code: 'rhi-descriptor-invalid',
|
|
1537
|
-
expected: `GPU-driven ${this.deformation === 'skin' ? 'skinned ' : ''}material pipeline group(2) ${expectedGroup2Contract} contract`,
|
|
1538
|
-
hint: 'custom attachment or incompatible group(2) layouts remain on their specialized lane',
|
|
1539
|
-
}),
|
|
1540
|
-
);
|
|
1541
|
-
}
|
|
1542
|
-
this.pipelines.set(key, pipelineEntry.pipeline);
|
|
1543
|
-
return ok(pipelineEntry.pipeline);
|
|
1544
|
-
}
|
|
1545
|
-
if (
|
|
1546
|
-
program === undefined ||
|
|
1547
|
-
selectedArtifact.material !== this.artifact.material ||
|
|
1548
|
-
selectedArtifact.layoutIdentity !== this.artifact.layoutIdentity ||
|
|
1549
|
-
selectedArtifact.receipt?.receiptIdentity !== this.artifact.receipt?.receiptIdentity
|
|
1550
|
-
) {
|
|
1551
|
-
return err(
|
|
1552
|
-
new RhiError({
|
|
1553
|
-
code: 'rhi-not-available',
|
|
1554
|
-
expected: 'selected material pipeline factory for the requested ABI artifact',
|
|
1555
|
-
hint: 'do not pair a receipt-backed custom or color variant with the legacy fixed module',
|
|
1556
|
-
}),
|
|
1557
|
-
);
|
|
1558
|
-
}
|
|
1559
|
-
const pipeline = this.device.createRenderPipeline({
|
|
1560
|
-
label: `standard-pbr-gpu-driven.${key}`,
|
|
1561
|
-
layout: this.pipelineLayout,
|
|
1562
|
-
vertex: {
|
|
1563
|
-
module: program.module,
|
|
1564
|
-
entryPoint: selectedArtifact.vertexEntry ?? receipt.sceneIndexEntry,
|
|
1565
|
-
buffers: [
|
|
1566
|
-
{
|
|
1567
|
-
arrayStride: vertexLayout.arrayStride,
|
|
1568
|
-
stepMode: 'vertex',
|
|
1569
|
-
attributes: vertexLayout.attributes.map(({ shaderLocation, offset, format }) => ({
|
|
1570
|
-
shaderLocation,
|
|
1571
|
-
offset,
|
|
1572
|
-
format: format as GPUVertexFormat,
|
|
1573
|
-
})),
|
|
1574
|
-
},
|
|
1575
|
-
],
|
|
1576
|
-
},
|
|
1577
|
-
fragment: {
|
|
1578
|
-
module: program.module,
|
|
1579
|
-
entryPoint: 'fs_main',
|
|
1580
|
-
targets: [format, ...additionalColorFormats].map((format) => ({ format })),
|
|
1581
|
-
},
|
|
1582
|
-
primitive: {
|
|
1583
|
-
topology,
|
|
1584
|
-
cullMode: renderState?.cullMode ?? 'back',
|
|
1585
|
-
frontFace: renderState?.frontFace ?? 'ccw',
|
|
1586
|
-
...(stripIndexFormat === undefined ? {} : { stripIndexFormat }),
|
|
1587
|
-
},
|
|
1588
|
-
depthStencil: {
|
|
1589
|
-
format: 'depth24plus-stencil8',
|
|
1590
|
-
depthWriteEnabled: renderState?.depthWriteEnabled ?? true,
|
|
1591
|
-
depthCompare: renderState?.depthCompare ?? 'less',
|
|
1592
|
-
},
|
|
1593
|
-
...(sampleCount === 4 ? { multisample: { count: 4 } } : {}),
|
|
1594
|
-
});
|
|
1595
|
-
if (!pipeline.ok) return pipeline;
|
|
1596
|
-
this.pipelines.set(key, pipeline.value);
|
|
1597
|
-
return pipeline;
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
/**
|
|
1601
|
-
* Drop only program-derived caches when a publication switches. Resource
|
|
1602
|
-
* buffers stay alive until their normal fence retirement, while the next
|
|
1603
|
-
* draw must obtain a PSO for the new artifact rather than reuse the old one.
|
|
1604
|
-
*/
|
|
1605
|
-
invalidatePipelineCache(): void {
|
|
1606
|
-
this.pipelines.clear();
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
projectionBindGroup(
|
|
1610
|
-
rows: Float32Array,
|
|
1611
|
-
key?: string,
|
|
1612
|
-
capacityRows?: number,
|
|
1613
|
-
): Result<BindGroup, RhiError> {
|
|
1614
|
-
if (key !== undefined) {
|
|
1615
|
-
const capacityBytes = Math.max(
|
|
1616
|
-
MESH_SSBO_BYTES,
|
|
1617
|
-
(capacityRows ?? Math.ceil(rows.byteLength / MESH_SSBO_BYTES)) * MESH_SSBO_BYTES,
|
|
1618
|
-
);
|
|
1619
|
-
const retained = this.stagedProjectionBindings.get(key) ?? this.projectionBindings.get(key);
|
|
1620
|
-
if (retained !== undefined && retained.capacityBytes >= capacityBytes) {
|
|
1621
|
-
const write = this.writeProjectionRows(key, retained, rows);
|
|
1622
|
-
if (!write.ok) return write;
|
|
1623
|
-
return ok(retained.bindGroup);
|
|
1624
|
-
}
|
|
1625
|
-
const created = this.createProjectionBinding(rows, capacityBytes);
|
|
1626
|
-
if (!created.ok) return created;
|
|
1627
|
-
this.stagedProjectionBindings.set(key, created.value);
|
|
1628
|
-
return ok(created.value.bindGroup);
|
|
1629
|
-
}
|
|
1630
|
-
const created = this.createProjectionBinding(rows, Math.max(MESH_SSBO_BYTES, rows.byteLength));
|
|
1631
|
-
if (!created.ok) return created;
|
|
1632
|
-
return ok(created.value.bindGroup);
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
refreshProjectionRows(key: string, rows: Float32Array): Result<void, RhiError> {
|
|
1636
|
-
const retained = this.stagedProjectionBindings.get(key) ?? this.projectionBindings.get(key);
|
|
1637
|
-
if (retained === undefined) return ok(undefined);
|
|
1638
|
-
if (rows.byteLength > retained.capacityBytes) {
|
|
1639
|
-
const capacityBytes = Math.max(
|
|
1640
|
-
MESH_SSBO_BYTES,
|
|
1641
|
-
Math.ceil(rows.byteLength / MESH_SSBO_BYTES) * MESH_SSBO_BYTES,
|
|
1642
|
-
);
|
|
1643
|
-
const replacement = this.createProjectionBinding(rows, capacityBytes);
|
|
1644
|
-
if (!replacement.ok) return replacement;
|
|
1645
|
-
const previousStaged = this.stagedProjectionBindings.get(key);
|
|
1646
|
-
if (previousStaged !== undefined && previousStaged.buffer !== replacement.value.buffer) {
|
|
1647
|
-
this.retireProjectionBuffer(previousStaged.buffer);
|
|
1648
|
-
}
|
|
1649
|
-
this.stagedProjectionBindings.set(key, replacement.value);
|
|
1650
|
-
return ok(undefined);
|
|
1651
|
-
}
|
|
1652
|
-
return this.writeProjectionRows(key, retained, rows);
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
sceneMeshBinding(key: string): GpuDrivenSceneMeshBinding {
|
|
1656
|
-
const binding = this.stagedProjectionBindings.get(key) ?? this.projectionBindings.get(key);
|
|
1657
|
-
if (binding === undefined)
|
|
1658
|
-
throw new RhiError({
|
|
1659
|
-
code: 'rhi-not-available',
|
|
1660
|
-
expected: 'retained scene mesh projection',
|
|
1661
|
-
hint: 'prepare this batch projection before binding its lighting resources',
|
|
1662
|
-
});
|
|
1663
|
-
return {
|
|
1664
|
-
meshBuffer: binding.buffer,
|
|
1665
|
-
meshBytes: binding.capacityBytes,
|
|
1666
|
-
...(this.deformation === 'skin' && this.paletteBuffer !== undefined
|
|
1667
|
-
? { paletteBuffer: this.paletteBuffer }
|
|
1668
|
-
: {}),
|
|
1669
|
-
};
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
/** Stage the projection keys required by the frame being prepared. */
|
|
1673
|
-
stageProjectionKeys(keys: ReadonlySet<string>): void {
|
|
1674
|
-
this.stagedProjectionKeys = new Set(keys);
|
|
1675
|
-
this.activeProjectionRowsAtStage.clear();
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
/** Add a shadow projection key discovered while updating a view plan. */
|
|
1679
|
-
noteProjectionKey(key: string): void {
|
|
1680
|
-
this.stagedProjectionKeys.add(key);
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
/** Promote staged projection resources after the command buffer submits. */
|
|
1684
|
-
commitProjectionBindings(): void {
|
|
1685
|
-
const keep = new Set(this.stagedProjectionKeys);
|
|
1686
|
-
for (const [key, staged] of this.stagedProjectionBindings) {
|
|
1687
|
-
if (!keep.has(key)) {
|
|
1688
|
-
this.retireProjectionBuffer(staged.buffer);
|
|
1689
|
-
continue;
|
|
1690
|
-
}
|
|
1691
|
-
const previous = this.projectionBindings.get(key);
|
|
1692
|
-
this.projectionBindings.set(key, staged);
|
|
1693
|
-
if (previous !== undefined && previous.buffer !== staged.buffer) {
|
|
1694
|
-
this.retireProjectionBuffer(previous.buffer);
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
this.stagedProjectionBindings.clear();
|
|
1698
|
-
for (const [key, active] of this.projectionBindings) {
|
|
1699
|
-
if (!keep.has(key)) {
|
|
1700
|
-
this.projectionBindings.delete(key);
|
|
1701
|
-
this.retireProjectionBuffer(active.buffer);
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
this.stagedProjectionKeys.clear();
|
|
1705
|
-
this.activeProjectionRowsAtStage.clear();
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
/** Discard resources created for an aborted graph while retaining LKG rows. */
|
|
1709
|
-
abortProjectionBindings(): void {
|
|
1710
|
-
for (const [key, rows] of this.activeProjectionRowsAtStage) {
|
|
1711
|
-
const active = this.projectionBindings.get(key);
|
|
1712
|
-
if (active === undefined) continue;
|
|
1713
|
-
const restored = this.device.queue.writeBuffer(active.buffer, 0, rows);
|
|
1714
|
-
if (restored.ok) active.rows = new Float32Array(rows);
|
|
1715
|
-
}
|
|
1716
|
-
for (const staged of this.stagedProjectionBindings.values()) {
|
|
1717
|
-
this.retireProjectionBuffer(staged.buffer);
|
|
1718
|
-
}
|
|
1719
|
-
this.stagedProjectionBindings.clear();
|
|
1720
|
-
this.stagedProjectionKeys.clear();
|
|
1721
|
-
this.activeProjectionRowsAtStage.clear();
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
private createProjectionBinding(
|
|
1725
|
-
rows: Float32Array,
|
|
1726
|
-
capacityBytes: number,
|
|
1727
|
-
): Result<
|
|
1728
|
-
{
|
|
1729
|
-
readonly buffer: Buffer;
|
|
1730
|
-
readonly bindGroup: BindGroup;
|
|
1731
|
-
readonly capacityBytes: number;
|
|
1732
|
-
rows: Float32Array;
|
|
1733
|
-
},
|
|
1734
|
-
RhiError
|
|
1735
|
-
> {
|
|
1736
|
-
const buffer = this.device.createBuffer({
|
|
1737
|
-
label: 'standard-pbr-gpu-driven-scene-meshes',
|
|
1738
|
-
size: capacityBytes,
|
|
1739
|
-
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
1740
|
-
});
|
|
1741
|
-
if (!buffer.ok) return buffer;
|
|
1742
|
-
const write = this.device.queue.writeBuffer(buffer.value, 0, rows);
|
|
1743
|
-
if (!write.ok) {
|
|
1744
|
-
this.destroyProjectionBuffer(buffer.value);
|
|
1745
|
-
return write;
|
|
1746
|
-
}
|
|
1747
|
-
this.projectionBuffers.add(buffer.value);
|
|
1748
|
-
const entries = [
|
|
1749
|
-
{ binding: 0, resource: bufferBinding(buffer.value, 0, capacityBytes) },
|
|
1750
|
-
...(this.deformation === 'skin' &&
|
|
1751
|
-
this.paletteBuffer !== undefined &&
|
|
1752
|
-
this.paletteBindingWindowBytes !== undefined
|
|
1753
|
-
? [
|
|
1754
|
-
{
|
|
1755
|
-
binding: 1,
|
|
1756
|
-
// The scene-index second lane is a global palette row. Bind the
|
|
1757
|
-
// complete arena so rows beyond the legacy 255-joint window
|
|
1758
|
-
// remain addressable; the shader performs the row selection.
|
|
1759
|
-
resource: bufferBinding(this.paletteBuffer),
|
|
1760
|
-
},
|
|
1761
|
-
{
|
|
1762
|
-
binding: 2,
|
|
1763
|
-
// The dedicated GPU-driven skin layout keeps the current /
|
|
1764
|
-
// previous palette pair used by the direct skin layout. The
|
|
1765
|
-
// scene-index shader currently reads the current arena, but
|
|
1766
|
-
// the shared layout still requires a complete binding shape.
|
|
1767
|
-
resource: bufferBinding(this.paletteBuffer),
|
|
1768
|
-
},
|
|
1769
|
-
]
|
|
1770
|
-
: []),
|
|
1771
|
-
];
|
|
1772
|
-
const bindGroup = this.device.createBindGroup({
|
|
1773
|
-
label: 'standard-pbr-gpu-driven-scene-mesh-bg',
|
|
1774
|
-
layout: this.meshLayout,
|
|
1775
|
-
entries,
|
|
1776
|
-
});
|
|
1777
|
-
if (!bindGroup.ok) {
|
|
1778
|
-
this.destroyProjectionBuffer(buffer.value);
|
|
1779
|
-
return bindGroup;
|
|
1780
|
-
}
|
|
1781
|
-
return ok({
|
|
1782
|
-
buffer: buffer.value,
|
|
1783
|
-
bindGroup: bindGroup.value,
|
|
1784
|
-
capacityBytes,
|
|
1785
|
-
rows: new Float32Array(rows),
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
private writeProjectionRows(
|
|
1790
|
-
key: string,
|
|
1791
|
-
binding: {
|
|
1792
|
-
readonly buffer: Buffer;
|
|
1793
|
-
readonly bindGroup: BindGroup;
|
|
1794
|
-
readonly capacityBytes: number;
|
|
1795
|
-
rows: Float32Array;
|
|
1796
|
-
},
|
|
1797
|
-
rows: Float32Array,
|
|
1798
|
-
): Result<void, RhiError> {
|
|
1799
|
-
// Active bindings are the last-known-good graph resources. Keep one CPU
|
|
1800
|
-
// snapshot so an aborted candidate can restore their exact contents
|
|
1801
|
-
// without allocating a second GPU buffer on every stable frame.
|
|
1802
|
-
if (
|
|
1803
|
-
this.stagedProjectionBindings.get(key) === undefined &&
|
|
1804
|
-
!this.activeProjectionRowsAtStage.has(key)
|
|
1805
|
-
) {
|
|
1806
|
-
this.activeProjectionRowsAtStage.set(key, new Float32Array(binding.rows));
|
|
1807
|
-
}
|
|
1808
|
-
const write = this.device.queue.writeBuffer(binding.buffer, 0, rows);
|
|
1809
|
-
if (!write.ok) return write;
|
|
1810
|
-
binding.rows = new Float32Array(rows);
|
|
1811
|
-
return ok(undefined);
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
dispose(): void {
|
|
1815
|
-
this.projectionDisposed = true;
|
|
1816
|
-
for (const binding of this.projectionBindings.values()) {
|
|
1817
|
-
this.destroyProjectionBuffer(binding.buffer);
|
|
1818
|
-
}
|
|
1819
|
-
for (const binding of this.stagedProjectionBindings.values()) {
|
|
1820
|
-
this.destroyProjectionBuffer(binding.buffer);
|
|
1821
|
-
}
|
|
1822
|
-
for (const buffer of this.projectionBuffers) this.destroyProjectionBuffer(buffer);
|
|
1823
|
-
for (const buffer of this.pendingProjectionRetirements) {
|
|
1824
|
-
this.destroyProjectionBuffer(buffer);
|
|
1825
|
-
}
|
|
1826
|
-
this.projectionBindings.clear();
|
|
1827
|
-
this.stagedProjectionBindings.clear();
|
|
1828
|
-
this.activeProjectionRowsAtStage.clear();
|
|
1829
|
-
this.projectionBuffers.clear();
|
|
1830
|
-
this.pendingProjectionRetirements.clear();
|
|
1831
|
-
this.device.destroyBuffer(this.instanceBuffer);
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
private retireProjectionBuffer(buffer: Buffer): void {
|
|
1835
|
-
if (this.destroyedProjectionBuffers.has(buffer)) return;
|
|
1836
|
-
this.projectionBuffers.delete(buffer);
|
|
1837
|
-
if (this.projectionDisposed) {
|
|
1838
|
-
this.destroyProjectionBuffer(buffer);
|
|
1839
|
-
return;
|
|
1840
|
-
}
|
|
1841
|
-
if (!this.pendingProjectionRetirements.add(buffer)) return;
|
|
1842
|
-
void this.device.queue.onSubmittedWorkDone().then(
|
|
1843
|
-
() => {
|
|
1844
|
-
this.pendingProjectionRetirements.delete(buffer);
|
|
1845
|
-
this.destroyProjectionBuffer(buffer);
|
|
1846
|
-
},
|
|
1847
|
-
() => {
|
|
1848
|
-
this.pendingProjectionRetirements.delete(buffer);
|
|
1849
|
-
this.destroyProjectionBuffer(buffer);
|
|
1850
|
-
},
|
|
1851
|
-
);
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
private destroyProjectionBuffer(buffer: Buffer): void {
|
|
1855
|
-
if (this.destroyedProjectionBuffers.has(buffer)) return;
|
|
1856
|
-
this.destroyedProjectionBuffers.add(buffer);
|
|
1857
|
-
this.projectionBuffers.delete(buffer);
|
|
1858
|
-
this.device.destroyBuffer(buffer);
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
get instancesFrameGroup(): BindGroup {
|
|
1862
|
-
return this.instancesBindGroup;
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
get kind(): 'rigid' | 'skin' {
|
|
1866
|
-
return this.deformation;
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
1332
|
|
|
1870
1333
|
export interface PreparedGpuDrivenFrame {
|
|
1871
1334
|
readonly topologySignature: string;
|
|
1335
|
+
/** Composition-local world indices mapped to the stable identities used by draw keys. */
|
|
1336
|
+
readonly worldKeys: readonly number[];
|
|
1872
1337
|
/** True when every source draw item/instance is claimed by the GPU lane. */
|
|
1873
1338
|
readonly ownsAllDrawItems: boolean;
|
|
1874
1339
|
/** True only when the extracted ShadowCaster channel is fully represented. */
|
|
@@ -1876,6 +1341,8 @@ export interface PreparedGpuDrivenFrame {
|
|
|
1876
1341
|
/** Concrete main-pass draw identities claimed by the GPU projection. */
|
|
1877
1342
|
readonly drawKeys?: ReadonlySet<string>;
|
|
1878
1343
|
readonly standardPbrFrameResources: GpuDrivenStandardPbrFrameResources;
|
|
1344
|
+
/** Published Surface admission and its pass lane decision. */
|
|
1345
|
+
readonly surfaceSubmission?: SurfaceGpuSubmissionAdmission;
|
|
1879
1346
|
readonly occlusion?: OcclusionFrameProjection;
|
|
1880
1347
|
/** @internal Commits a replacement generation only after graph promotion. */
|
|
1881
1348
|
readonly _commitResourceReplacement: () => void;
|
|
@@ -1883,6 +1350,8 @@ export interface PreparedGpuDrivenFrame {
|
|
|
1883
1350
|
readonly _commitGpuResourceReplacement?: () => void;
|
|
1884
1351
|
/** @internal Discards staged shadow cache publication after a failed frame. */
|
|
1885
1352
|
readonly _abortResourceReplacement?: () => void;
|
|
1353
|
+
/** @internal Marks validated dynamic ranges consumed only after queue submit. */
|
|
1354
|
+
readonly _consumeSurfaceDynamicInput?: (frameNumber: number) => Result<void, RhiError>;
|
|
1886
1355
|
/** Concrete ShadowCaster keys claimed by the GPU subset for this frame. */
|
|
1887
1356
|
readonly shadowDrawKeys?: ReadonlySet<string>;
|
|
1888
1357
|
/** View-scoped ShadowCaster claims consumed by each shadow record pass. */
|
|
@@ -1903,6 +1372,7 @@ export interface PreparedGpuDrivenFrame {
|
|
|
1903
1372
|
graph: RenderGraphBuilder<RenderPipelineFrame>,
|
|
1904
1373
|
format: TextureFormat,
|
|
1905
1374
|
sampleCount: 1 | 4,
|
|
1375
|
+
surfaceSubmissionObservation?: () => SurfaceSubmissionCandidate | undefined,
|
|
1906
1376
|
additionalColorFormats?: readonly TextureFormat[],
|
|
1907
1377
|
): Result<RenderPipelineGpuDrivenProjection, RenderGraphError | RhiError>;
|
|
1908
1378
|
}
|
|
@@ -1936,6 +1406,7 @@ interface GpuDrivenCpuValidationTelemetryCache {
|
|
|
1936
1406
|
readonly cacheKey: string;
|
|
1937
1407
|
readonly validated: readonly { readonly source: RenderableSnapshot }[];
|
|
1938
1408
|
readonly gpuOwnedDrawKeys: ReadonlySet<string>;
|
|
1409
|
+
readonly worldKeys: readonly number[] | undefined;
|
|
1939
1410
|
readonly gpuOwnedDrawCount: number;
|
|
1940
1411
|
readonly totalDrawItems: number;
|
|
1941
1412
|
readonly gpuOwnedRows: number;
|
|
@@ -2047,6 +1518,26 @@ export class GpuDrivenProduction {
|
|
|
2047
1518
|
readonly value: FilteredProductionPlan;
|
|
2048
1519
|
}
|
|
2049
1520
|
| undefined;
|
|
1521
|
+
private surfaceFrameRangeCache:
|
|
1522
|
+
| {
|
|
1523
|
+
readonly filtered: FilteredProductionPlan;
|
|
1524
|
+
readonly slots: PersistentGpuDrivenState['slots'];
|
|
1525
|
+
readonly page: ReadonlyDynamicInputPage | undefined;
|
|
1526
|
+
readonly projectionRevision: number;
|
|
1527
|
+
readonly value: SurfaceFrameProjection;
|
|
1528
|
+
}
|
|
1529
|
+
| undefined;
|
|
1530
|
+
private consumedSurfaceInput:
|
|
1531
|
+
| {
|
|
1532
|
+
readonly page: ReadonlyDynamicInputPage;
|
|
1533
|
+
readonly projectionRevision: number;
|
|
1534
|
+
readonly contentRevision: number;
|
|
1535
|
+
readonly bufferGeneration: number;
|
|
1536
|
+
readonly deviceGeneration: number;
|
|
1537
|
+
}
|
|
1538
|
+
| undefined;
|
|
1539
|
+
private surfaceFrameRangeBuilds = 0;
|
|
1540
|
+
private surfaceFrameRangeMemberScans = 0;
|
|
2050
1541
|
private shadowFilteredCache:
|
|
2051
1542
|
| {
|
|
2052
1543
|
readonly source: SubmissionPlan;
|
|
@@ -2099,6 +1590,8 @@ export class GpuDrivenProduction {
|
|
|
2099
1590
|
>();
|
|
2100
1591
|
private shadowDrawKeysByView = new Map<string, ReadonlySet<string>>();
|
|
2101
1592
|
private shadowExpectedDrawKeys = new Set<string>();
|
|
1593
|
+
private surfaceSubmission: SurfaceGpuSubmissionAdmission | undefined;
|
|
1594
|
+
private surfaceArtifactInspection: GpuDrivenProductionInspection['surfaceArtifact'];
|
|
2102
1595
|
private shadowBatchProjectionSource: SubmissionPlan | undefined;
|
|
2103
1596
|
private skinPaletteBuffer: Buffer | undefined;
|
|
2104
1597
|
private committedPaletteContentRevision: number | undefined;
|
|
@@ -2138,6 +1631,110 @@ export class GpuDrivenProduction {
|
|
|
2138
1631
|
private readonly recoveryCount = 0,
|
|
2139
1632
|
) {}
|
|
2140
1633
|
|
|
1634
|
+
private resolveSurfaceFrameRanges(
|
|
1635
|
+
filtered: FilteredProductionPlan,
|
|
1636
|
+
frame: SurfaceDynamicInputFrame | undefined,
|
|
1637
|
+
scene: PersistentGpuDrivenState,
|
|
1638
|
+
): Result<SurfaceFrameProjection, RhiError> {
|
|
1639
|
+
const ranges = frame?.ranges ?? [];
|
|
1640
|
+
const projectionRevision = frame?.projectionRevision ?? 0;
|
|
1641
|
+
if (!Number.isSafeInteger(projectionRevision) || projectionRevision < 0) {
|
|
1642
|
+
return err(
|
|
1643
|
+
surfaceDynamicInputError(
|
|
1644
|
+
'the Surface projection revision is a non-negative safe integer',
|
|
1645
|
+
'advance the producer-owned projection revision only when membership or addresses change',
|
|
1646
|
+
),
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
const cached = this.surfaceFrameRangeCache;
|
|
1650
|
+
if (
|
|
1651
|
+
cached?.filtered === filtered &&
|
|
1652
|
+
cached.slots === scene.slots &&
|
|
1653
|
+
cached.page === frame?.page &&
|
|
1654
|
+
cached.projectionRevision === projectionRevision
|
|
1655
|
+
) {
|
|
1656
|
+
return ok(cached.value);
|
|
1657
|
+
}
|
|
1658
|
+
this.surfaceFrameRangeBuilds += 1;
|
|
1659
|
+
for (const prepared of filtered.batches) {
|
|
1660
|
+
if (!isSingleLayerMediumBatch(prepared)) continue;
|
|
1661
|
+
for (const candidate of prepared.batch.candidates) {
|
|
1662
|
+
if (
|
|
1663
|
+
(candidate as GpuDrivenCandidate & { readonly submitAdmission?: boolean })
|
|
1664
|
+
.submitAdmission !== false
|
|
1665
|
+
) {
|
|
1666
|
+
this.surfaceFrameRangeMemberScans += 1;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
const built = buildSurfaceFrameRanges(filtered, ranges, scene);
|
|
1671
|
+
if (!built.ok) return built;
|
|
1672
|
+
let capacity = 1;
|
|
1673
|
+
const consumptionRanges: DynamicInputRange[] = [];
|
|
1674
|
+
for (const entry of built.value) {
|
|
1675
|
+
capacity = Math.max(capacity, entry.frameIndex + 1);
|
|
1676
|
+
if (entry.range !== undefined) consumptionRanges.push(entry.range);
|
|
1677
|
+
}
|
|
1678
|
+
const value = {
|
|
1679
|
+
ranges: built.value,
|
|
1680
|
+
consumptionRanges: Object.freeze(consumptionRanges),
|
|
1681
|
+
capacity,
|
|
1682
|
+
};
|
|
1683
|
+
return ok(value);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
private rememberSurfaceFrameProjection(
|
|
1687
|
+
filtered: FilteredProductionPlan,
|
|
1688
|
+
frame: SurfaceDynamicInputFrame | undefined,
|
|
1689
|
+
scene: PersistentGpuDrivenState,
|
|
1690
|
+
value: SurfaceFrameProjection,
|
|
1691
|
+
): void {
|
|
1692
|
+
this.surfaceFrameRangeCache = {
|
|
1693
|
+
filtered,
|
|
1694
|
+
slots: scene.slots,
|
|
1695
|
+
page: frame?.page,
|
|
1696
|
+
projectionRevision: frame?.projectionRevision ?? 0,
|
|
1697
|
+
value,
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
private consumeSurfaceDynamicInput(
|
|
1702
|
+
page: ReadonlyDynamicInputPage,
|
|
1703
|
+
ranges: readonly DynamicInputRange[],
|
|
1704
|
+
projectionRevision: number,
|
|
1705
|
+
frameNumber: number,
|
|
1706
|
+
): Result<void, RhiError> {
|
|
1707
|
+
const consumed = this.consumedSurfaceInput;
|
|
1708
|
+
if (
|
|
1709
|
+
consumed?.page === page &&
|
|
1710
|
+
consumed.projectionRevision === projectionRevision &&
|
|
1711
|
+
consumed.contentRevision === page.contentRevision &&
|
|
1712
|
+
consumed.bufferGeneration === page.bufferGeneration &&
|
|
1713
|
+
consumed.deviceGeneration === page.deviceGeneration
|
|
1714
|
+
) {
|
|
1715
|
+
return ok(undefined);
|
|
1716
|
+
}
|
|
1717
|
+
for (const range of ranges) {
|
|
1718
|
+
const receipt = page.consume(range, frameNumber);
|
|
1719
|
+
if (!receipt.ok) {
|
|
1720
|
+
return err(
|
|
1721
|
+
surfaceDynamicInputError(
|
|
1722
|
+
'each submitted Surface dynamic range was uploaded before graph consumption',
|
|
1723
|
+
receipt.error.hint,
|
|
1724
|
+
),
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
this.consumedSurfaceInput = {
|
|
1729
|
+
page,
|
|
1730
|
+
projectionRevision,
|
|
1731
|
+
contentRevision: page.contentRevision,
|
|
1732
|
+
bufferGeneration: page.bufferGeneration,
|
|
1733
|
+
deviceGeneration: page.deviceGeneration,
|
|
1734
|
+
};
|
|
1735
|
+
return ok(undefined);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
2141
1738
|
/**
|
|
2142
1739
|
* Reuse the last producer-resolved residency rows when all source identity
|
|
2143
1740
|
* and generation fences still match. Renderables and dispatch entries are
|
|
@@ -2224,6 +1821,10 @@ export class GpuDrivenProduction {
|
|
|
2224
1821
|
readonly meshBySlot: ReadonlyMap<number, MeshGpuHandles>;
|
|
2225
1822
|
readonly viewBindGroupLayout: BindGroupLayout;
|
|
2226
1823
|
readonly meshResidencyEpoch: number;
|
|
1824
|
+
/** Optional for legacy Standard callers; omitted means non-HDRP. */
|
|
1825
|
+
readonly hdrp?: boolean;
|
|
1826
|
+
/** Explicit Standard topology; cluster resources are bound by main-pass. */
|
|
1827
|
+
readonly clustered?: boolean;
|
|
2227
1828
|
readonly materialBindingClasses?: ReadonlyMap<string, string>;
|
|
2228
1829
|
/** Cooked producer artifact selected from the frame material catalog. */
|
|
2229
1830
|
readonly materialArtifact?: MaterialShaderArtifact;
|
|
@@ -2237,6 +1838,8 @@ export class GpuDrivenProduction {
|
|
|
2237
1838
|
readonly materialArtifacts?: ReadonlyMap<string, MaterialShaderArtifact>;
|
|
2238
1839
|
/** Per-draw shadow-pass artifacts keyed by the same prepared identity. */
|
|
2239
1840
|
readonly shadowMaterialArtifacts?: ReadonlyMap<string, MaterialShaderArtifact>;
|
|
1841
|
+
/** Retained ProbeBlend record page; slot N is addressed at aligned lane N+1. */
|
|
1842
|
+
readonly probeBlendRecordBuffer?: Buffer;
|
|
2240
1843
|
readonly materialPipelineState?: PipelineState;
|
|
2241
1844
|
readonly standardPbrPipelineState?: PipelineState;
|
|
2242
1845
|
/** Existing renderer owner that builds the selected artifact PSO/BGL. */
|
|
@@ -2256,7 +1859,14 @@ export class GpuDrivenProduction {
|
|
|
2256
1859
|
readonly occlusion?: OcclusionFrameProjection;
|
|
2257
1860
|
/** Per-frame deltas from the existing RenderScene/GPU Scene/palette owners. */
|
|
2258
1861
|
readonly structureMetrics?: GpuDrivenStructureMetrics;
|
|
1862
|
+
/** World/App frame-time snapshot consumed by authored Surface code. */
|
|
1863
|
+
readonly frameTime?: number;
|
|
1864
|
+
/** Current RHI device generation used for Surface admission. */
|
|
1865
|
+
readonly deviceGeneration?: number;
|
|
1866
|
+
/** Renderer-owned generic read-only Surface page and instance ranges. */
|
|
1867
|
+
readonly surfaceDynamicInput?: SurfaceDynamicInputFrame;
|
|
2259
1868
|
}): Result<PreparedGpuDrivenFrame | undefined, RhiError | GpuDrivenPreparationError> {
|
|
1869
|
+
this.surfaceArtifactInspection = undefined;
|
|
2260
1870
|
this.gpuOwnedSnapshotsMaterialized = 0;
|
|
2261
1871
|
this.filteredPlanBuilds = 0;
|
|
2262
1872
|
this.batchBindGroupCreates = 0;
|
|
@@ -2285,6 +1895,7 @@ export class GpuDrivenProduction {
|
|
|
2285
1895
|
this.laneReason = 'capability';
|
|
2286
1896
|
this.submissionState = 'idle';
|
|
2287
1897
|
this.shadowExpectedDrawKeys = new Set();
|
|
1898
|
+
this.surfaceSubmission = undefined;
|
|
2288
1899
|
const capability = classifyRecoveryCapability({
|
|
2289
1900
|
generation: 0,
|
|
2290
1901
|
shaderCompilation: true,
|
|
@@ -2295,6 +1906,7 @@ export class GpuDrivenProduction {
|
|
|
2295
1906
|
});
|
|
2296
1907
|
if (
|
|
2297
1908
|
input.scene === undefined ||
|
|
1909
|
+
(input.hdrp === true && input.clustered !== true) ||
|
|
2298
1910
|
selectGpuLodLane(this.device.caps) === 'cpu' ||
|
|
2299
1911
|
capability.status === 'disabled'
|
|
2300
1912
|
) {
|
|
@@ -2332,6 +1944,57 @@ export class GpuDrivenProduction {
|
|
|
2332
1944
|
const materialArtifact = input.materialArtifact ?? input.standardPbrArtifact;
|
|
2333
1945
|
const skinArtifact = input.materialSkinArtifact ?? input.standardPbrSkinArtifact;
|
|
2334
1946
|
const materialPipelineState = input.materialPipelineState ?? input.standardPbrPipelineState;
|
|
1947
|
+
const surfaceArtifact = [
|
|
1948
|
+
materialArtifact,
|
|
1949
|
+
skinArtifact,
|
|
1950
|
+
...(input.materialArtifacts === undefined ? [] : input.materialArtifacts.values()),
|
|
1951
|
+
].find((artifact) => artifact?.receipt?.surface?.model === 'single-layer-medium');
|
|
1952
|
+
this.surfaceArtifactInspection =
|
|
1953
|
+
surfaceArtifact?.receipt === undefined
|
|
1954
|
+
? undefined
|
|
1955
|
+
: Object.freeze({
|
|
1956
|
+
material: surfaceArtifact.material,
|
|
1957
|
+
specializationKey: surfaceArtifact.specializationKey,
|
|
1958
|
+
variantSet: surfaceArtifact.variantSet,
|
|
1959
|
+
layoutIdentity: surfaceArtifact.layoutIdentity,
|
|
1960
|
+
receiptIdentity: surfaceArtifact.receipt.receiptIdentity,
|
|
1961
|
+
receiptGeneration: surfaceArtifact.receipt.generation,
|
|
1962
|
+
directEntry: surfaceArtifact.receipt.directEntry,
|
|
1963
|
+
sceneIndexEntry: surfaceArtifact.receipt.sceneIndexEntry,
|
|
1964
|
+
});
|
|
1965
|
+
const surfaceDynamicInputLayout = surfaceArtifact?.receipt?.surface?.dynamicInput?.layout;
|
|
1966
|
+
if (surfaceArtifact?.receipt?.surface !== undefined) {
|
|
1967
|
+
const deviceGeneration = input.deviceGeneration ?? 1;
|
|
1968
|
+
const admitted = admitSingleLayerMediumSubmission({
|
|
1969
|
+
abi: surfaceArtifact.receipt,
|
|
1970
|
+
caps: {
|
|
1971
|
+
compute: this.device.caps.compute,
|
|
1972
|
+
storageBuffer: this.device.caps.storageBuffer,
|
|
1973
|
+
indirectDrawing: this.device.caps.indirectDrawing,
|
|
1974
|
+
},
|
|
1975
|
+
sceneIndexReady: surfaceArtifact.receipt.sceneIndexEntry.length > 0,
|
|
1976
|
+
resourcesReady:
|
|
1977
|
+
surfaceArtifact.receipt.reflection.layoutIdentity === surfaceArtifact.layoutIdentity,
|
|
1978
|
+
dynamicInputReady:
|
|
1979
|
+
surfaceArtifact.receipt.surface.dynamicInput === undefined ||
|
|
1980
|
+
input.surfaceDynamicInput !== undefined,
|
|
1981
|
+
deviceGeneration,
|
|
1982
|
+
preparedDeviceGeneration: deviceGeneration,
|
|
1983
|
+
});
|
|
1984
|
+
if (!admitted.ok) {
|
|
1985
|
+
this.lane = 'blocked';
|
|
1986
|
+
this.laneReason = 'resource-not-ready';
|
|
1987
|
+
const failure = new GpuDrivenPreparationError('resource-not-ready', {
|
|
1988
|
+
reason: 'material-resource-missing',
|
|
1989
|
+
owner: 'material',
|
|
1990
|
+
expected: admitted.error.expected,
|
|
1991
|
+
actual: JSON.stringify(admitted.error.actual),
|
|
1992
|
+
});
|
|
1993
|
+
this.preparationFailure = preparationFailureInspection(failure);
|
|
1994
|
+
return err(failure);
|
|
1995
|
+
}
|
|
1996
|
+
this.surfaceSubmission = admitted.value;
|
|
1997
|
+
}
|
|
2335
1998
|
const artifactSignature = `${materialArtifactSignature(
|
|
2336
1999
|
input.materialArtifacts,
|
|
2337
2000
|
materialArtifact,
|
|
@@ -2590,6 +2253,20 @@ export class GpuDrivenProduction {
|
|
|
2590
2253
|
}),
|
|
2591
2254
|
);
|
|
2592
2255
|
}
|
|
2256
|
+
const probeBlendRequested = filtered.batches.some(({ artifact }) =>
|
|
2257
|
+
requiresProbeBlendRecord(artifact),
|
|
2258
|
+
);
|
|
2259
|
+
if (probeBlendRequested && input.probeBlendRecordBuffer === undefined) {
|
|
2260
|
+
return err(
|
|
2261
|
+
new GpuDrivenPreparationError('resource-not-ready', {
|
|
2262
|
+
reason: 'material-resource-missing',
|
|
2263
|
+
owner: 'material',
|
|
2264
|
+
expected: 'the retained ProbeBlend record page for every scene-index probe draw',
|
|
2265
|
+
actual: 'the prepared frame did not publish its ProbeBlend record page',
|
|
2266
|
+
recovery: 'retry-same-draw',
|
|
2267
|
+
}),
|
|
2268
|
+
);
|
|
2269
|
+
}
|
|
2593
2270
|
const committedArtifactChanged =
|
|
2594
2271
|
this.committedProjectionSource !== undefined &&
|
|
2595
2272
|
this.committedProjectionSource.materialArtifactSignature !== artifactSignature;
|
|
@@ -2615,10 +2292,15 @@ export class GpuDrivenProduction {
|
|
|
2615
2292
|
...(input.materialPipelineFactory === undefined
|
|
2616
2293
|
? {}
|
|
2617
2294
|
: { pipelineFactory: input.materialPipelineFactory }),
|
|
2295
|
+
...(input.probeBlendRecordBuffer === undefined
|
|
2296
|
+
? {}
|
|
2297
|
+
: { probeBlendRecordBuffer: input.probeBlendRecordBuffer }),
|
|
2618
2298
|
});
|
|
2619
2299
|
if (!created.ok) return created;
|
|
2620
2300
|
this.materialRaster = created.value;
|
|
2621
2301
|
} else {
|
|
2302
|
+
const probe = this.materialRaster.ensureProbeBlendRecordBuffer(input.probeBlendRecordBuffer);
|
|
2303
|
+
if (!probe.ok) return probe;
|
|
2622
2304
|
const rebound = this.materialRaster.ensureVisibleBuffer(visibleBuffer);
|
|
2623
2305
|
if (!rebound.ok) return rebound;
|
|
2624
2306
|
}
|
|
@@ -2664,6 +2346,10 @@ export class GpuDrivenProduction {
|
|
|
2664
2346
|
deformation: 'skin',
|
|
2665
2347
|
paletteBuffer: skinSnapshot.buffer,
|
|
2666
2348
|
paletteBindingWindowBytes: skinAllocator.bindingWindowBytes,
|
|
2349
|
+
sharedSurfaceFrame: materialRaster.surfaceDynamicSharedFrameState,
|
|
2350
|
+
...(input.probeBlendRecordBuffer === undefined
|
|
2351
|
+
? {}
|
|
2352
|
+
: { probeBlendRecordBuffer: input.probeBlendRecordBuffer }),
|
|
2667
2353
|
...(input.materialPipelineFactory === undefined
|
|
2668
2354
|
? {}
|
|
2669
2355
|
: { pipelineFactory: input.materialPipelineFactory }),
|
|
@@ -2671,6 +2357,8 @@ export class GpuDrivenProduction {
|
|
|
2671
2357
|
if (!created.ok) return created;
|
|
2672
2358
|
this.skinRaster = created.value;
|
|
2673
2359
|
} else {
|
|
2360
|
+
const probe = this.skinRaster.ensureProbeBlendRecordBuffer(input.probeBlendRecordBuffer);
|
|
2361
|
+
if (!probe.ok) return probe;
|
|
2674
2362
|
const rebound = this.skinRaster.ensureVisibleBuffer(visibleBuffer);
|
|
2675
2363
|
if (!rebound.ok) return rebound;
|
|
2676
2364
|
const palette = this.skinRaster.ensurePaletteBuffer(
|
|
@@ -2687,6 +2375,52 @@ export class GpuDrivenProduction {
|
|
|
2687
2375
|
}
|
|
2688
2376
|
}
|
|
2689
2377
|
const skinRaster = this.skinRaster;
|
|
2378
|
+
if (this.surfaceSubmission !== undefined) {
|
|
2379
|
+
const surfaceFrameRangesResult = this.resolveSurfaceFrameRanges(
|
|
2380
|
+
filtered,
|
|
2381
|
+
input.surfaceDynamicInput,
|
|
2382
|
+
input.scene,
|
|
2383
|
+
);
|
|
2384
|
+
if (!surfaceFrameRangesResult.ok) return surfaceFrameRangesResult;
|
|
2385
|
+
const surfaceFrameProjection = surfaceFrameRangesResult.value;
|
|
2386
|
+
const surfaceFrameRanges = surfaceFrameProjection.ranges;
|
|
2387
|
+
const surfaceFrameCapacity = Math.max(
|
|
2388
|
+
filtered.plan.visibleCapacity,
|
|
2389
|
+
surfaceFrameProjection.capacity,
|
|
2390
|
+
);
|
|
2391
|
+
const surfaceInput = materialRaster.ensureSurfaceDynamicInput(
|
|
2392
|
+
input.surfaceDynamicInput,
|
|
2393
|
+
surfaceFrameCapacity,
|
|
2394
|
+
input.surfaceDynamicInput?.frameTime ?? input.frameTime ?? 0,
|
|
2395
|
+
surfaceDynamicInputLayout,
|
|
2396
|
+
input.deviceGeneration ?? 1,
|
|
2397
|
+
surfaceFrameRanges,
|
|
2398
|
+
);
|
|
2399
|
+
if (!surfaceInput.ok) return surfaceInput;
|
|
2400
|
+
if (skinRaster !== undefined) {
|
|
2401
|
+
const skinSurfaceInput = skinRaster.ensureSurfaceDynamicInput(
|
|
2402
|
+
input.surfaceDynamicInput,
|
|
2403
|
+
surfaceFrameCapacity,
|
|
2404
|
+
input.surfaceDynamicInput?.frameTime ?? input.frameTime ?? 0,
|
|
2405
|
+
surfaceDynamicInputLayout,
|
|
2406
|
+
input.deviceGeneration ?? 1,
|
|
2407
|
+
surfaceFrameRanges,
|
|
2408
|
+
);
|
|
2409
|
+
if (!skinSurfaceInput.ok) return skinSurfaceInput;
|
|
2410
|
+
}
|
|
2411
|
+
const committedSurfaceInput = materialRaster.commitSurfaceDynamicInput();
|
|
2412
|
+
if (!committedSurfaceInput.ok) return committedSurfaceInput;
|
|
2413
|
+
if (skinRaster !== undefined) {
|
|
2414
|
+
const committedSkinSurfaceInput = skinRaster.commitSurfaceDynamicInput();
|
|
2415
|
+
if (!committedSkinSurfaceInput.ok) return committedSkinSurfaceInput;
|
|
2416
|
+
}
|
|
2417
|
+
this.rememberSurfaceFrameProjection(
|
|
2418
|
+
filtered,
|
|
2419
|
+
input.surfaceDynamicInput,
|
|
2420
|
+
input.scene,
|
|
2421
|
+
surfaceFrameProjection,
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2690
2424
|
const shadowViewPool = this.shadowViews;
|
|
2691
2425
|
if (shadowViewPool === undefined) {
|
|
2692
2426
|
return err(
|
|
@@ -2842,25 +2576,83 @@ export class GpuDrivenProduction {
|
|
|
2842
2576
|
)
|
|
2843
2577
|
.join('|') ?? '';
|
|
2844
2578
|
const production = this;
|
|
2579
|
+
const surfaceDynamicInputForCommit =
|
|
2580
|
+
this.surfaceSubmission !== undefined && input.surfaceDynamicInput !== undefined
|
|
2581
|
+
? {
|
|
2582
|
+
page: input.surfaceDynamicInput.page,
|
|
2583
|
+
ranges: this.surfaceFrameRangeCache?.value.consumptionRanges ?? [],
|
|
2584
|
+
projectionRevision: input.surfaceDynamicInput.projectionRevision,
|
|
2585
|
+
}
|
|
2586
|
+
: undefined;
|
|
2845
2587
|
return ok({
|
|
2846
2588
|
get topologySignature() {
|
|
2847
2589
|
const shadow = shadowTopologySignature();
|
|
2848
2590
|
return shadow.length === 0 ? signature : `${signature}|shadow=${shadow}`;
|
|
2849
2591
|
},
|
|
2850
2592
|
ownsAllDrawItems: filtered.ownsAllDrawItems,
|
|
2593
|
+
worldKeys: scene.worldKeys ?? [],
|
|
2851
2594
|
...(input.occlusion === undefined ? {} : { occlusion: input.occlusion }),
|
|
2852
2595
|
ownsAllShadowCasters,
|
|
2853
2596
|
drawKeys: filtered.drawKeys,
|
|
2854
2597
|
shadowDrawKeys: shadowFiltered.shadowDrawKeys,
|
|
2598
|
+
...(this.surfaceSubmission === undefined
|
|
2599
|
+
? {}
|
|
2600
|
+
: { surfaceSubmission: this.surfaceSubmission }),
|
|
2855
2601
|
get shadowDrawKeysByView() {
|
|
2856
2602
|
return new Map(production.shadowDrawKeysByView);
|
|
2857
2603
|
},
|
|
2858
2604
|
standardPbrFrameResources: {
|
|
2605
|
+
resourceGeneration: viewInspection.resourceGeneration,
|
|
2859
2606
|
materialBindGroups: [],
|
|
2860
2607
|
materialSlotIndicesByEntity: new Map(),
|
|
2861
2608
|
instancesBindGroup: materialRaster.instancesFrameGroup,
|
|
2862
2609
|
materialStride: MATERIAL_PER_ENTITY_STRIDE,
|
|
2863
2610
|
sceneMaterialBuffer: scene.scene.materialBuffer,
|
|
2611
|
+
...(this.surfaceSubmission === undefined
|
|
2612
|
+
? {}
|
|
2613
|
+
: {
|
|
2614
|
+
surfaceDynamicInput: {
|
|
2615
|
+
page: materialRaster.surfaceDynamicInputBuffer,
|
|
2616
|
+
frame: materialRaster.surfaceDynamicFrameBuffer,
|
|
2617
|
+
sharedFrame: materialRaster.surfaceDynamicSharedFrameBuffer,
|
|
2618
|
+
pageBytes: materialRaster.surfaceDynamicInputBufferSize,
|
|
2619
|
+
frameBytes: materialRaster.surfaceDynamicFrameBufferSize,
|
|
2620
|
+
sharedFrameBytes: materialRaster.surfaceDynamicSharedFrameBufferSize,
|
|
2621
|
+
},
|
|
2622
|
+
surfaceDirectInstances: (directInput) => {
|
|
2623
|
+
const resolved = materialRaster.directSurfaceInstances(directInput);
|
|
2624
|
+
if (!resolved.ok) return resolved;
|
|
2625
|
+
const firstInstanceOrdinal = directInput.firstInstanceOrdinal ?? 0;
|
|
2626
|
+
const memberIds: string[] = [];
|
|
2627
|
+
for (let index = 0; index < directInput.instanceCount; index += 1) {
|
|
2628
|
+
const instanceOrdinal = firstInstanceOrdinal + index;
|
|
2629
|
+
const frameRange = this.surfaceFrameRangeCache?.value.ranges.find(
|
|
2630
|
+
({ directMember }) =>
|
|
2631
|
+
directMember.worldId === directInput.worldId &&
|
|
2632
|
+
directMember.entityKey === directInput.entityKey &&
|
|
2633
|
+
directMember.drawItemIndex === directInput.drawItemIndex &&
|
|
2634
|
+
directMember.instanceOrdinal === instanceOrdinal,
|
|
2635
|
+
);
|
|
2636
|
+
if (frameRange === undefined) {
|
|
2637
|
+
return err(
|
|
2638
|
+
surfaceDynamicInputError(
|
|
2639
|
+
'every direct Surface command resolves the retained public member identity',
|
|
2640
|
+
`rebuild the Surface projection for world=${directInput.worldId}, entity=${directInput.entityKey}, draw=${directInput.drawItemIndex}, instance=${instanceOrdinal}`,
|
|
2641
|
+
),
|
|
2642
|
+
);
|
|
2643
|
+
}
|
|
2644
|
+
memberIds.push(
|
|
2645
|
+
JSON.stringify([
|
|
2646
|
+
frameRange.directMember.worldIdentity,
|
|
2647
|
+
frameRange.directMember.entityKey,
|
|
2648
|
+
frameRange.directMember.drawItemIndex,
|
|
2649
|
+
frameRange.directMember.instanceOrdinal,
|
|
2650
|
+
]),
|
|
2651
|
+
);
|
|
2652
|
+
}
|
|
2653
|
+
return ok({ ...resolved.value, memberIds: Object.freeze(memberIds) });
|
|
2654
|
+
},
|
|
2655
|
+
}),
|
|
2864
2656
|
},
|
|
2865
2657
|
_commitGpuResourceReplacement: () => {
|
|
2866
2658
|
view._commitResourceReplacement();
|
|
@@ -2876,6 +2668,8 @@ export class GpuDrivenProduction {
|
|
|
2876
2668
|
this.lastKnownGoodGeneration = view.inspect().resourceGeneration;
|
|
2877
2669
|
},
|
|
2878
2670
|
_abortResourceReplacement: () => {
|
|
2671
|
+
materialRaster.abortSurfaceDynamicInput();
|
|
2672
|
+
skinRaster?.abortSurfaceDynamicInput();
|
|
2879
2673
|
shadowViewPool._abortResourceReplacement();
|
|
2880
2674
|
materialRaster.abortProjectionBindings();
|
|
2881
2675
|
skinRaster?.abortProjectionBindings();
|
|
@@ -2883,6 +2677,18 @@ export class GpuDrivenProduction {
|
|
|
2883
2677
|
this.submissionState = 'aborted';
|
|
2884
2678
|
this.retryCount += 1;
|
|
2885
2679
|
},
|
|
2680
|
+
...(surfaceDynamicInputForCommit === undefined ||
|
|
2681
|
+
surfaceDynamicInputForCommit.ranges.length === 0
|
|
2682
|
+
? {}
|
|
2683
|
+
: {
|
|
2684
|
+
_consumeSurfaceDynamicInput: (frameNumber: number) =>
|
|
2685
|
+
this.consumeSurfaceDynamicInput(
|
|
2686
|
+
surfaceDynamicInputForCommit.page,
|
|
2687
|
+
surfaceDynamicInputForCommit.ranges,
|
|
2688
|
+
surfaceDynamicInputForCommit.projectionRevision,
|
|
2689
|
+
frameNumber,
|
|
2690
|
+
),
|
|
2691
|
+
}),
|
|
2886
2692
|
projectShadow: (graph, identity) => {
|
|
2887
2693
|
if (!shadowViewPool.isActive(identity) || shadowViewPool.submission(identity) === undefined)
|
|
2888
2694
|
return ok(undefined);
|
|
@@ -2910,25 +2716,75 @@ export class GpuDrivenProduction {
|
|
|
2910
2716
|
),
|
|
2911
2717
|
);
|
|
2912
2718
|
},
|
|
2913
|
-
project: (
|
|
2719
|
+
project: (
|
|
2720
|
+
graph,
|
|
2721
|
+
format,
|
|
2722
|
+
sampleCount,
|
|
2723
|
+
surfaceSubmissionObservation,
|
|
2724
|
+
additionalColorFormats,
|
|
2725
|
+
) => {
|
|
2914
2726
|
this.indirectDrawCount = admittedRasterBatchCount(filtered.plan);
|
|
2915
|
-
const outputs = view.addPasses(graph);
|
|
2727
|
+
const outputs = view.addPasses(graph, 'gpu-driven', true, surfaceSubmissionObservation);
|
|
2916
2728
|
if (!outputs.ok) return outputs;
|
|
2917
2729
|
if (materialRaster !== undefined && filtered.batches.length > 0) {
|
|
2730
|
+
let surfacePage: GraphBuffer | undefined;
|
|
2731
|
+
let surfaceFrame: GraphBuffer | undefined;
|
|
2732
|
+
let surfaceSharedFrame: GraphBuffer | undefined;
|
|
2733
|
+
if (this.surfaceSubmission !== undefined) {
|
|
2734
|
+
const importedPage = graph.importBuffer(
|
|
2735
|
+
'gpu-driven.surface.dynamic-input',
|
|
2736
|
+
{
|
|
2737
|
+
size: materialRaster.surfaceDynamicInputBufferSize,
|
|
2738
|
+
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
2739
|
+
},
|
|
2740
|
+
() => materialRaster.surfaceDynamicInputBuffer,
|
|
2741
|
+
);
|
|
2742
|
+
if (!importedPage.ok) return importedPage;
|
|
2743
|
+
const importedFrame = graph.importBuffer(
|
|
2744
|
+
'gpu-driven.surface.frame-input',
|
|
2745
|
+
{
|
|
2746
|
+
size: materialRaster.surfaceDynamicFrameBufferSize,
|
|
2747
|
+
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
2748
|
+
},
|
|
2749
|
+
() => materialRaster.surfaceDynamicFrameBuffer,
|
|
2750
|
+
);
|
|
2751
|
+
if (!importedFrame.ok) return importedFrame;
|
|
2752
|
+
const importedSharedFrame = graph.importBuffer(
|
|
2753
|
+
'gpu-driven.surface.shared-frame-input',
|
|
2754
|
+
{
|
|
2755
|
+
size: materialRaster.surfaceDynamicSharedFrameBufferSize,
|
|
2756
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
2757
|
+
},
|
|
2758
|
+
() => materialRaster.surfaceDynamicSharedFrameBuffer,
|
|
2759
|
+
);
|
|
2760
|
+
if (!importedSharedFrame.ok) return importedSharedFrame;
|
|
2761
|
+
surfacePage = importedPage.value;
|
|
2762
|
+
surfaceFrame = importedFrame.value;
|
|
2763
|
+
surfaceSharedFrame = importedSharedFrame.value;
|
|
2764
|
+
}
|
|
2918
2765
|
const standardProjected: Array<{
|
|
2919
2766
|
readonly batch: GpuDrivenBatch;
|
|
2767
|
+
readonly artifact: MaterialShaderArtifact;
|
|
2920
2768
|
readonly mesh: MeshGpuHandles;
|
|
2921
2769
|
readonly materialEntityKey: number;
|
|
2922
2770
|
readonly vertex: GraphBuffer;
|
|
2923
2771
|
readonly index?: GraphBuffer;
|
|
2924
|
-
readonly pipelineFor: (
|
|
2925
|
-
formats: readonly TextureFormat[],
|
|
2926
|
-
) => Result<RenderPipeline, RhiError>;
|
|
2927
2772
|
readonly meshBindGroup: BindGroup;
|
|
2773
|
+
readonly meshProjectionBuffer: Buffer;
|
|
2774
|
+
readonly meshProjectionBytes: number;
|
|
2928
2775
|
readonly raster: MaterialAbiRasterAdapter;
|
|
2929
2776
|
readonly deformation: 'rigid' | 'skin';
|
|
2930
2777
|
readonly clustered: boolean;
|
|
2931
2778
|
readonly projectionKey: string;
|
|
2779
|
+
readonly pipelineFor: (
|
|
2780
|
+
formats: readonly TextureFormat[],
|
|
2781
|
+
) => Result<RenderPipeline, RhiError>;
|
|
2782
|
+
readonly nearestPipelineFor?: (
|
|
2783
|
+
formats: readonly TextureFormat[],
|
|
2784
|
+
) => Result<RenderPipeline, RhiError>;
|
|
2785
|
+
readonly colorPipelineFor?: (
|
|
2786
|
+
formats: readonly TextureFormat[],
|
|
2787
|
+
) => Result<RenderPipeline, RhiError>;
|
|
2932
2788
|
}> = [];
|
|
2933
2789
|
const standardAccesses: GraphAccess[] = [
|
|
2934
2790
|
{ resource: outputs.value.primitive, usage: 'storage-read' },
|
|
@@ -2937,6 +2793,15 @@ export class GpuDrivenProduction {
|
|
|
2937
2793
|
{ resource: outputs.value.visible, usage: 'storage-read' },
|
|
2938
2794
|
{ resource: outputs.value.transform, usage: 'storage-read' },
|
|
2939
2795
|
{ resource: outputs.value.indirect, usage: 'indirect-read' },
|
|
2796
|
+
...(surfacePage === undefined
|
|
2797
|
+
? []
|
|
2798
|
+
: [{ resource: surfacePage, usage: 'storage-read' as const }]),
|
|
2799
|
+
...(surfaceFrame === undefined
|
|
2800
|
+
? []
|
|
2801
|
+
: [{ resource: surfaceFrame, usage: 'storage-read' as const }]),
|
|
2802
|
+
...(surfaceSharedFrame === undefined
|
|
2803
|
+
? []
|
|
2804
|
+
: [{ resource: surfaceSharedFrame, usage: 'uniform-read' as const }]),
|
|
2940
2805
|
];
|
|
2941
2806
|
const meshBuffers = new Map<
|
|
2942
2807
|
MeshGpuHandles,
|
|
@@ -2963,10 +2828,10 @@ export class GpuDrivenProduction {
|
|
|
2963
2828
|
);
|
|
2964
2829
|
}
|
|
2965
2830
|
// The visible stream is compacted after the persistent visibility
|
|
2966
|
-
// facet applies its active-entity admission. Project
|
|
2967
|
-
// from
|
|
2968
|
-
// batch-local
|
|
2969
|
-
//
|
|
2831
|
+
// facet applies its active-entity admission. Project mesh rows and
|
|
2832
|
+
// frame rows from the same admitted sequence; visible.x is the
|
|
2833
|
+
// batch-local candidate identity, while the atomic slot is only
|
|
2834
|
+
// the draw order chosen by the GPU culler.
|
|
2970
2835
|
const candidates = admittedCandidates.flatMap((candidate) => {
|
|
2971
2836
|
const slot = projectionSlot(candidate.primitiveIndex);
|
|
2972
2837
|
return slot === undefined ? [] : [sceneCandidate(slot, candidate)];
|
|
@@ -2984,12 +2849,23 @@ export class GpuDrivenProduction {
|
|
|
2984
2849
|
}),
|
|
2985
2850
|
);
|
|
2986
2851
|
}
|
|
2852
|
+
const projectionKey = gpuDrivenProjectionKey(prepared.batch, deformation, artifact);
|
|
2987
2853
|
const meshBindGroup = raster.projectionBindGroup(
|
|
2988
2854
|
projection.value.rows,
|
|
2989
|
-
|
|
2855
|
+
projectionKey,
|
|
2990
2856
|
prepared.batch.candidates.length,
|
|
2991
2857
|
);
|
|
2992
2858
|
if (!meshBindGroup.ok) return meshBindGroup;
|
|
2859
|
+
const meshProjection = raster.projectionBuffer(projectionKey);
|
|
2860
|
+
if (meshProjection === undefined) {
|
|
2861
|
+
return err(
|
|
2862
|
+
new RhiError({
|
|
2863
|
+
code: 'internal-error',
|
|
2864
|
+
expected: 'retained batch-local mesh projection buffer',
|
|
2865
|
+
hint: 'keep the projected BindGroup and its buffer on one retained identity',
|
|
2866
|
+
}),
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2993
2869
|
const materialSlot = projectionSlot(admittedCandidates[0]?.primitiveIndex ?? -1);
|
|
2994
2870
|
if (materialSlot === undefined) {
|
|
2995
2871
|
return err(
|
|
@@ -3032,8 +2908,18 @@ export class GpuDrivenProduction {
|
|
|
3032
2908
|
standardAccesses.push({ resource: buffers.index, usage: 'index-read' });
|
|
3033
2909
|
}
|
|
3034
2910
|
}
|
|
2911
|
+
const shaderUvSetCount = isCanonicalStandardPbrMaterialShader(artifact.material)
|
|
2912
|
+
? materialPipelineState.standardPbrShaderUvSetCount
|
|
2913
|
+
: (artifact.uvSetCount ?? materialPipelineState.standardPbrShaderUvSetCount);
|
|
2914
|
+
const stripIndexFormat =
|
|
2915
|
+
prepared.batch.key.drawKind === 'indexed' &&
|
|
2916
|
+
(prepared.batch.key.topology === 'line-strip' ||
|
|
2917
|
+
prepared.batch.key.topology === 'triangle-strip')
|
|
2918
|
+
? prepared.mesh.indexFormat
|
|
2919
|
+
: undefined;
|
|
3035
2920
|
const pipelineFor = (
|
|
3036
2921
|
formats: readonly TextureFormat[],
|
|
2922
|
+
selectedArtifact: MaterialShaderArtifact = artifact,
|
|
3037
2923
|
): Result<RenderPipeline, RhiError> => {
|
|
3038
2924
|
return raster.pipeline(
|
|
3039
2925
|
formats[0] ?? format,
|
|
@@ -3043,22 +2929,31 @@ export class GpuDrivenProduction {
|
|
|
3043
2929
|
// Standard count is only a fallback for legacy synthetic
|
|
3044
2930
|
// fixtures; using it for a custom program would add aliases
|
|
3045
2931
|
// the shader never declares (or omit a declared custom set).
|
|
3046
|
-
|
|
2932
|
+
shaderUvSetCount,
|
|
3047
2933
|
prepared.batch.key.topology,
|
|
3048
|
-
|
|
3049
|
-
(prepared.batch.key.topology === 'line-strip' ||
|
|
3050
|
-
prepared.batch.key.topology === 'triangle-strip')
|
|
3051
|
-
? prepared.mesh.indexFormat
|
|
3052
|
-
: undefined,
|
|
2934
|
+
stripIndexFormat,
|
|
3053
2935
|
prepared.renderState,
|
|
3054
|
-
|
|
2936
|
+
selectedArtifact,
|
|
3055
2937
|
formats.slice(1),
|
|
3056
2938
|
);
|
|
3057
2939
|
};
|
|
3058
2940
|
const initialPipeline = pipelineFor([format, ...(additionalColorFormats ?? [])]);
|
|
3059
2941
|
if (!initialPipeline.ok) return initialPipeline;
|
|
2942
|
+
let nearestPipelineFor:
|
|
2943
|
+
| ((formats: readonly TextureFormat[]) => Result<RenderPipeline, RhiError>)
|
|
2944
|
+
| undefined;
|
|
2945
|
+
let colorPipelineFor:
|
|
2946
|
+
| ((formats: readonly TextureFormat[]) => Result<RenderPipeline, RhiError>)
|
|
2947
|
+
| undefined;
|
|
2948
|
+
if (isSingleLayerMediumArtifact(artifact)) {
|
|
2949
|
+
nearestPipelineFor = (formats) =>
|
|
2950
|
+
pipelineFor(formats, { ...artifact, fragmentEntry: 'fs_nearest_layer' });
|
|
2951
|
+
colorPipelineFor = (formats) =>
|
|
2952
|
+
pipelineFor(formats, { ...artifact, fragmentEntry: 'fs_color' });
|
|
2953
|
+
}
|
|
3060
2954
|
standardProjected.push({
|
|
3061
2955
|
batch: prepared.batch,
|
|
2956
|
+
artifact,
|
|
3062
2957
|
mesh: prepared.mesh,
|
|
3063
2958
|
materialEntityKey: worldEntityKey(
|
|
3064
2959
|
materialSlot.snapshot.worldId,
|
|
@@ -3066,7 +2961,11 @@ export class GpuDrivenProduction {
|
|
|
3066
2961
|
),
|
|
3067
2962
|
...buffers,
|
|
3068
2963
|
pipelineFor,
|
|
2964
|
+
...(nearestPipelineFor === undefined ? {} : { nearestPipelineFor }),
|
|
2965
|
+
...(colorPipelineFor === undefined ? {} : { colorPipelineFor }),
|
|
3069
2966
|
meshBindGroup: meshBindGroup.value,
|
|
2967
|
+
meshProjectionBuffer: meshProjection.buffer,
|
|
2968
|
+
meshProjectionBytes: meshProjection.bindingBytes,
|
|
3070
2969
|
raster,
|
|
3071
2970
|
deformation,
|
|
3072
2971
|
clustered: ['cluster', 'skin-cluster'].includes(
|
|
@@ -3095,6 +2994,8 @@ export class GpuDrivenProduction {
|
|
|
3095
2994
|
pass,
|
|
3096
2995
|
resources: GraphResourceResolver,
|
|
3097
2996
|
frameResources: GpuDrivenStandardPbrFrameResources,
|
|
2997
|
+
filter,
|
|
2998
|
+
fragmentEntryPoint,
|
|
3098
2999
|
) => {
|
|
3099
3000
|
// The shared Standard view BGL carries two dynamic buffers:
|
|
3100
3001
|
// the frame view UBO and the per-frame shadow/light table. GPU
|
|
@@ -3106,6 +3007,18 @@ export class GpuDrivenProduction {
|
|
|
3106
3007
|
let currentVertex: GraphBuffer | undefined;
|
|
3107
3008
|
let currentIndex: GraphBuffer | undefined;
|
|
3108
3009
|
for (const batch of rasterDispatches) {
|
|
3010
|
+
const isMedium = isSingleLayerMediumArtifact(batch.artifact);
|
|
3011
|
+
if (
|
|
3012
|
+
(filter === 'single-layer-medium' && !isMedium) ||
|
|
3013
|
+
((filter === 'opaque' ||
|
|
3014
|
+
filter === 'deferred-opaque' ||
|
|
3015
|
+
filter === 'forward-only-opaque') &&
|
|
3016
|
+
isMedium) ||
|
|
3017
|
+
(filter === 'deferred-opaque' && batch.batch.key.materialPass !== 'deferred') ||
|
|
3018
|
+
(filter === 'forward-only-opaque' && batch.batch.key.materialPass === 'deferred')
|
|
3019
|
+
) {
|
|
3020
|
+
continue;
|
|
3021
|
+
}
|
|
3109
3022
|
const globalMaterialSlots = frameResources.materialSlotIndicesByEntity.get(
|
|
3110
3023
|
batch.materialEntityKey,
|
|
3111
3024
|
);
|
|
@@ -3126,14 +3039,19 @@ export class GpuDrivenProduction {
|
|
|
3126
3039
|
!frameResources.selectedMaterialSlots.has(globalMaterialSlot)
|
|
3127
3040
|
)
|
|
3128
3041
|
continue;
|
|
3129
|
-
const
|
|
3042
|
+
const pipelineFor =
|
|
3043
|
+
fragmentEntryPoint === 'fs_nearest_layer'
|
|
3044
|
+
? (batch.nearestPipelineFor ?? batch.pipelineFor)
|
|
3045
|
+
: fragmentEntryPoint === 'fs_color'
|
|
3046
|
+
? (batch.colorPipelineFor ?? batch.pipelineFor)
|
|
3047
|
+
: batch.pipelineFor;
|
|
3048
|
+
const selectedPipeline = pipelineFor(
|
|
3130
3049
|
frameResources.colorFormats ?? [format, ...(additionalColorFormats ?? [])],
|
|
3131
3050
|
);
|
|
3132
3051
|
if (!selectedPipeline.ok) throw selectedPipeline.error;
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
currentPipeline = pipeline;
|
|
3052
|
+
if (currentPipeline !== selectedPipeline.value) {
|
|
3053
|
+
pass.setPipeline(selectedPipeline.value);
|
|
3054
|
+
currentPipeline = selectedPipeline.value;
|
|
3137
3055
|
}
|
|
3138
3056
|
const materialBindGroup = frameResources.materialBindGroups[globalMaterialSlot];
|
|
3139
3057
|
if (materialBindGroup === undefined) {
|
|
@@ -3146,21 +3064,30 @@ export class GpuDrivenProduction {
|
|
|
3146
3064
|
pass.setBindGroup(1, materialBindGroup, [
|
|
3147
3065
|
globalMaterialSlot * frameResources.materialStride,
|
|
3148
3066
|
]);
|
|
3149
|
-
const
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
: batch.
|
|
3154
|
-
|
|
3067
|
+
const group2 = resolveGpuDrivenMeshGroup({
|
|
3068
|
+
clustered: batch.clustered,
|
|
3069
|
+
deformation: batch.deformation,
|
|
3070
|
+
meshBindGroup: batch.meshBindGroup,
|
|
3071
|
+
meshProjectionBuffer: batch.meshProjectionBuffer,
|
|
3072
|
+
meshProjectionBytes: batch.meshProjectionBytes,
|
|
3073
|
+
frameResources,
|
|
3074
|
+
skinPaletteBinding: batch.raster.skinPaletteBinding(),
|
|
3075
|
+
});
|
|
3076
|
+
if (group2 === undefined) {
|
|
3155
3077
|
throw new RhiError({
|
|
3156
3078
|
code: 'rhi-not-available',
|
|
3157
|
-
expected: '
|
|
3158
|
-
hint: '
|
|
3079
|
+
expected: 'clustered Standard group(2) BindGroup for GPU-driven batch',
|
|
3080
|
+
hint: 'publish the frame-owned cluster lighting resources before indirect encode',
|
|
3159
3081
|
});
|
|
3160
|
-
|
|
3082
|
+
}
|
|
3083
|
+
pass.setBindGroup(2, group2, batch.deformation === 'skin' ? [0, 0, 0] : [0]);
|
|
3161
3084
|
const visibleWindow = batch.raster.visibleWindowBindGroup(
|
|
3162
3085
|
batch.batch.visibleBase,
|
|
3163
3086
|
batch.batch.visibleCapacity,
|
|
3087
|
+
isSingleLayerMediumArtifact(batch.artifact) ? batch.batch.visibleBase : undefined,
|
|
3088
|
+
isSingleLayerMediumArtifact(batch.artifact)
|
|
3089
|
+
? batch.batch.visibleCapacity
|
|
3090
|
+
: undefined,
|
|
3164
3091
|
);
|
|
3165
3092
|
if (!visibleWindow.ok) throw visibleWindow.error;
|
|
3166
3093
|
pass.setBindGroup(3, visibleWindow.value);
|
|
@@ -3183,6 +3110,29 @@ export class GpuDrivenProduction {
|
|
|
3183
3110
|
} else {
|
|
3184
3111
|
pass.drawIndirect(indirect.value, batch.batch.indirectOffset);
|
|
3185
3112
|
}
|
|
3113
|
+
const surfacePass =
|
|
3114
|
+
isMedium && fragmentEntryPoint === 'fs_nearest_layer'
|
|
3115
|
+
? 'nearest-layer'
|
|
3116
|
+
: isMedium && fragmentEntryPoint === 'fs_color'
|
|
3117
|
+
? 'color'
|
|
3118
|
+
: undefined;
|
|
3119
|
+
if (surfacePass !== undefined) {
|
|
3120
|
+
frameResources.surfaceSubmissionObservation?.record(surfacePass, {
|
|
3121
|
+
kind:
|
|
3122
|
+
batch.batch.key.drawKind === 'indexed'
|
|
3123
|
+
? 'draw-indexed-indirect'
|
|
3124
|
+
: 'draw-indirect',
|
|
3125
|
+
indirectBufferIdentity: getOpaqueResourceIdentity(indirect.value as object),
|
|
3126
|
+
indirectOffset: batch.batch.indirectOffset,
|
|
3127
|
+
pipelineIdentity: getOpaqueResourceIdentity(selectedPipeline as object),
|
|
3128
|
+
...(batch.artifact.receipt === undefined
|
|
3129
|
+
? {}
|
|
3130
|
+
: {
|
|
3131
|
+
receiptIdentity: batch.artifact.receipt.receiptIdentity,
|
|
3132
|
+
receiptGeneration: batch.artifact.receipt.generation,
|
|
3133
|
+
}),
|
|
3134
|
+
});
|
|
3135
|
+
}
|
|
3186
3136
|
this.encodedIndirectDraws += 1;
|
|
3187
3137
|
}
|
|
3188
3138
|
},
|
|
@@ -3371,7 +3321,7 @@ export class GpuDrivenProduction {
|
|
|
3371
3321
|
batch.candidates.length,
|
|
3372
3322
|
);
|
|
3373
3323
|
if (!meshBindGroup.ok) return meshBindGroup;
|
|
3374
|
-
const visibleBindGroup = raster.
|
|
3324
|
+
const visibleBindGroup = raster.shadowVisibleWindowBindGroupForBuffer(
|
|
3375
3325
|
visibleBuffer,
|
|
3376
3326
|
batch.visibleBase,
|
|
3377
3327
|
batch.visibleCapacity,
|
|
@@ -3451,6 +3401,8 @@ export class GpuDrivenProduction {
|
|
|
3451
3401
|
this.skinRaster?.dispose();
|
|
3452
3402
|
this.skinRaster = undefined;
|
|
3453
3403
|
this.filteredCache = undefined;
|
|
3404
|
+
this.surfaceFrameRangeCache = undefined;
|
|
3405
|
+
this.consumedSurfaceInput = undefined;
|
|
3454
3406
|
this.signatureCache = undefined;
|
|
3455
3407
|
this.validatedResidencyCache = undefined;
|
|
3456
3408
|
this.cpuValidationTelemetryCache = undefined;
|
|
@@ -3465,11 +3417,22 @@ export class GpuDrivenProduction {
|
|
|
3465
3417
|
this.skinPaletteBuffer = undefined;
|
|
3466
3418
|
this.committedPaletteContentRevision = undefined;
|
|
3467
3419
|
this.preparationFailure = undefined;
|
|
3420
|
+
this.surfaceArtifactInspection = undefined;
|
|
3468
3421
|
this.overflowRecoveryRequired = false;
|
|
3469
3422
|
}
|
|
3470
3423
|
|
|
3471
3424
|
inspect(): GpuDrivenProductionInspection {
|
|
3472
3425
|
const view = this.view?.inspect();
|
|
3426
|
+
const filtered = this.filteredCache?.value;
|
|
3427
|
+
const resourceAllocation = combineGpuResourceAllocationInspections([
|
|
3428
|
+
...(view === undefined ? [] : [view.resourceAllocation]),
|
|
3429
|
+
...(this.materialRaster === undefined ? [] : [this.materialRaster.resourceAllocation]),
|
|
3430
|
+
...(this.skinRaster === undefined ? [] : [this.skinRaster.resourceAllocation]),
|
|
3431
|
+
]);
|
|
3432
|
+
const resourceClasses =
|
|
3433
|
+
filtered === undefined
|
|
3434
|
+
? undefined
|
|
3435
|
+
: inspectResourceClassSplits({ batches: filtered.batches.map(({ batch }) => batch) });
|
|
3473
3436
|
const channels: GpuDrivenLaneSummary[] = [];
|
|
3474
3437
|
const mainTotalDrawCount = this.mainTotalDrawItems;
|
|
3475
3438
|
const mainClaimedDrawCount = this.telemetryOverflow
|
|
@@ -3575,11 +3538,23 @@ export class GpuDrivenProduction {
|
|
|
3575
3538
|
}
|
|
3576
3539
|
}
|
|
3577
3540
|
return {
|
|
3541
|
+
...(this.surfaceArtifactInspection === undefined
|
|
3542
|
+
? {}
|
|
3543
|
+
: { surfaceArtifact: this.surfaceArtifactInspection }),
|
|
3578
3544
|
residencyValidationScans: this.residencyValidationScans,
|
|
3579
3545
|
residencyValidationCacheHits: this.residencyValidationCacheHits,
|
|
3580
3546
|
cpuValidationScans: this.cpuValidationScans,
|
|
3581
3547
|
cpuValidationCacheHits: this.cpuValidationCacheHits,
|
|
3582
3548
|
...this.structureMetrics,
|
|
3549
|
+
surfaceFrameRangeBuilds: this.surfaceFrameRangeBuilds,
|
|
3550
|
+
surfaceFrameMemberScans:
|
|
3551
|
+
this.surfaceFrameRangeMemberScans +
|
|
3552
|
+
(this.materialRaster?.surfaceDynamicFrameMemberScans ?? 0) +
|
|
3553
|
+
(this.skinRaster?.surfaceDynamicFrameMemberScans ?? 0),
|
|
3554
|
+
surfaceFrameRowAllocations:
|
|
3555
|
+
(this.materialRaster?.surfaceDynamicFrameRowAllocations ?? 0) +
|
|
3556
|
+
(this.skinRaster?.surfaceDynamicFrameRowAllocations ?? 0),
|
|
3557
|
+
surfaceFrameTimeWrites: this.materialRaster?.surfaceDynamicFrameTimeWrites ?? 0,
|
|
3583
3558
|
gpuOwnedSnapshotsMaterialized: this.gpuOwnedSnapshotsMaterialized,
|
|
3584
3559
|
filteredPlanBuilds: this.filteredPlanBuilds,
|
|
3585
3560
|
gpuOwnedEntityCount: this.gpuOwnedEntityCount,
|
|
@@ -3610,6 +3585,23 @@ export class GpuDrivenProduction {
|
|
|
3610
3585
|
recoveryCount: this.recoveryCount,
|
|
3611
3586
|
retryCount: this.retryCount,
|
|
3612
3587
|
lastKnownGoodGeneration: this.lastKnownGoodGeneration,
|
|
3588
|
+
...(resourceAllocation === undefined ? {} : { resourceAllocation }),
|
|
3589
|
+
resourceAllocationOwners: Object.freeze({
|
|
3590
|
+
...(view === undefined ? {} : { gpuDrivenView: view.resourceAllocation }),
|
|
3591
|
+
...(this.materialRaster === undefined
|
|
3592
|
+
? {}
|
|
3593
|
+
: { materialRaster: this.materialRaster.resourceAllocation }),
|
|
3594
|
+
...(this.skinRaster === undefined
|
|
3595
|
+
? {}
|
|
3596
|
+
: { skinRaster: this.skinRaster.resourceAllocation }),
|
|
3597
|
+
}),
|
|
3598
|
+
...(resourceClasses === undefined
|
|
3599
|
+
? {}
|
|
3600
|
+
: {
|
|
3601
|
+
resourceClassCount: resourceClasses.resourceClassCount,
|
|
3602
|
+
resourceClassSplits: resourceClasses.resourceClassSplits,
|
|
3603
|
+
resourceClassSplitReasons: resourceClasses.resourceClassSplitReasons,
|
|
3604
|
+
}),
|
|
3613
3605
|
};
|
|
3614
3606
|
}
|
|
3615
3607
|
|
|
@@ -3653,6 +3645,7 @@ export class GpuDrivenProduction {
|
|
|
3653
3645
|
readonly worldKey: number;
|
|
3654
3646
|
readonly primitiveSlot: number;
|
|
3655
3647
|
readonly slotGeneration: number;
|
|
3648
|
+
readonly artifact?: MaterialShaderArtifact;
|
|
3656
3649
|
};
|
|
3657
3650
|
const preparedByBatchId = new Map<number, LodBatchAttribution>(
|
|
3658
3651
|
filtered.batches.map((prepared) => [
|
|
@@ -3662,9 +3655,12 @@ export class GpuDrivenProduction {
|
|
|
3662
3655
|
worldKey: prepared.worldKey,
|
|
3663
3656
|
primitiveSlot: prepared.primitiveSlot,
|
|
3664
3657
|
slotGeneration: prepared.slotGeneration,
|
|
3658
|
+
artifact: prepared.artifact,
|
|
3665
3659
|
},
|
|
3666
3660
|
]),
|
|
3667
3661
|
);
|
|
3662
|
+
const projectedCandidateCount =
|
|
3663
|
+
telemetryCandidateCount > 0 ? telemetryCandidateCount : selection.candidateCount;
|
|
3668
3664
|
// Selector-only batches remain in filtered.plan so their GPU counters
|
|
3669
3665
|
// can describe candidates suppressed from the raster admission lane.
|
|
3670
3666
|
// They have no PreparedBatch entry because no draw is encoded, but they
|
|
@@ -3710,7 +3706,10 @@ export class GpuDrivenProduction {
|
|
|
3710
3706
|
let lodVisible = 0;
|
|
3711
3707
|
for (const batchSelection of selection.batches) {
|
|
3712
3708
|
const prepared = preparedByBatchId.get(batchSelection.batchId);
|
|
3713
|
-
|
|
3709
|
+
// Selector-only or otherwise non-raster batches remain in the GPU
|
|
3710
|
+
// telemetry plan. They have no material producer row and therefore do
|
|
3711
|
+
// not invalidate readback evidence for the prepared Surface batches.
|
|
3712
|
+
if (prepared === undefined) continue;
|
|
3714
3713
|
// The GPU selector also visits ordinary GPU-driven meshes so one
|
|
3715
3714
|
// compact compute path can populate their indirect args. Those rows
|
|
3716
3715
|
// are level-0 by construction, but they are not LOD candidates and
|
|
@@ -3753,14 +3752,50 @@ export class GpuDrivenProduction {
|
|
|
3753
3752
|
}
|
|
3754
3753
|
return {
|
|
3755
3754
|
...selection,
|
|
3756
|
-
candidateCount: lodCandidateCount,
|
|
3757
|
-
visible: lodVisible,
|
|
3758
|
-
occluded: Math.max(
|
|
3755
|
+
candidateCount: lodCandidateCount > 0 ? lodCandidateCount : projectedCandidateCount,
|
|
3756
|
+
visible: lodCandidateCount > 0 ? lodVisible : selection.visible,
|
|
3757
|
+
occluded: Math.max(
|
|
3758
|
+
0,
|
|
3759
|
+
(lodCandidateCount > 0 ? lodCandidateCount : projectedCandidateCount) -
|
|
3760
|
+
(lodCandidateCount > 0 ? lodVisible : selection.visible),
|
|
3761
|
+
),
|
|
3759
3762
|
lodHistogram: Object.freeze(
|
|
3760
3763
|
[...lodHistogram.entries()]
|
|
3761
3764
|
.sort(([left], [right]) => left - right)
|
|
3762
3765
|
.map(([level, count]) => Object.freeze({ level, count })),
|
|
3763
3766
|
),
|
|
3767
|
+
surfaceActualMemberIds: Object.freeze(
|
|
3768
|
+
selection.actualMembers.flatMap((member) => {
|
|
3769
|
+
const prepared = preparedByBatchId.get(member.batchId);
|
|
3770
|
+
if (
|
|
3771
|
+
prepared === undefined ||
|
|
3772
|
+
prepared.artifact === undefined ||
|
|
3773
|
+
!isSingleLayerMediumArtifact(prepared.artifact)
|
|
3774
|
+
) {
|
|
3775
|
+
return [];
|
|
3776
|
+
}
|
|
3777
|
+
const slot = this.filteredCache?.slots.find(
|
|
3778
|
+
(candidate) => candidate.slot === member.primitiveIndex,
|
|
3779
|
+
);
|
|
3780
|
+
if (slot === undefined) return [];
|
|
3781
|
+
const frameRange = this.surfaceFrameRangeCache?.value.ranges.find(
|
|
3782
|
+
({ directMember }) =>
|
|
3783
|
+
directMember.worldId === slot.worldId &&
|
|
3784
|
+
directMember.entityKey === slot.entityKey &&
|
|
3785
|
+
directMember.drawItemIndex === member.drawItemIndex &&
|
|
3786
|
+
directMember.instanceOrdinal === member.instanceOrdinal,
|
|
3787
|
+
);
|
|
3788
|
+
if (frameRange === undefined) return [];
|
|
3789
|
+
return [
|
|
3790
|
+
JSON.stringify([
|
|
3791
|
+
frameRange.directMember.worldIdentity,
|
|
3792
|
+
frameRange.directMember.entityKey,
|
|
3793
|
+
frameRange.directMember.drawItemIndex,
|
|
3794
|
+
frameRange.directMember.instanceOrdinal,
|
|
3795
|
+
]),
|
|
3796
|
+
];
|
|
3797
|
+
}),
|
|
3798
|
+
),
|
|
3764
3799
|
worldSelections: Object.freeze(
|
|
3765
3800
|
[...worldSelections.values()].map((aggregate) =>
|
|
3766
3801
|
Object.freeze({
|
|
@@ -3786,6 +3821,7 @@ export class GpuDrivenProduction {
|
|
|
3786
3821
|
validated: readonly { readonly source: RenderableSnapshot }[],
|
|
3787
3822
|
gpuOwnedDrawKeys: ReadonlySet<string>,
|
|
3788
3823
|
cacheKey?: string,
|
|
3824
|
+
worldKeys?: readonly number[],
|
|
3789
3825
|
): void {
|
|
3790
3826
|
const cached = this.cpuValidationTelemetryCache;
|
|
3791
3827
|
if (
|
|
@@ -3793,7 +3829,8 @@ export class GpuDrivenProduction {
|
|
|
3793
3829
|
cached !== undefined &&
|
|
3794
3830
|
cached.cacheKey === cacheKey &&
|
|
3795
3831
|
cached.validated === validated &&
|
|
3796
|
-
cached.gpuOwnedDrawKeys === gpuOwnedDrawKeys
|
|
3832
|
+
cached.gpuOwnedDrawKeys === gpuOwnedDrawKeys &&
|
|
3833
|
+
cached.worldKeys === worldKeys
|
|
3797
3834
|
) {
|
|
3798
3835
|
this.applyCpuValidationTelemetry(cached);
|
|
3799
3836
|
this.cpuValidationCacheHits += 1;
|
|
@@ -3831,7 +3868,10 @@ export class GpuDrivenProduction {
|
|
|
3831
3868
|
for (const [compactIndex, draw] of draws.entries()) {
|
|
3832
3869
|
const material = row.source.materials[draw.materialSlot] ?? row.source.material;
|
|
3833
3870
|
const key = gpuDrivenDrawKey(
|
|
3834
|
-
worldEntityKey(
|
|
3871
|
+
worldEntityKey(
|
|
3872
|
+
worldKeys?.[row.source.worldId] ?? row.source.worldId,
|
|
3873
|
+
row.source.entityKey,
|
|
3874
|
+
),
|
|
3835
3875
|
material.materialHandle ?? -1,
|
|
3836
3876
|
gpuDrivenSourceDrawItemIndex(draw, compactIndex),
|
|
3837
3877
|
);
|
|
@@ -3876,6 +3916,7 @@ export class GpuDrivenProduction {
|
|
|
3876
3916
|
cacheKey,
|
|
3877
3917
|
validated,
|
|
3878
3918
|
gpuOwnedDrawKeys,
|
|
3919
|
+
worldKeys,
|
|
3879
3920
|
gpuOwnedDrawCount: this.gpuOwnedDrawItems,
|
|
3880
3921
|
totalDrawItems: this.mainTotalDrawItems,
|
|
3881
3922
|
gpuOwnedRows: this.validatedGpuOwnedRows,
|