@forgeax/engine-render 0.1.32 → 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 +1374 -142
- package/dist/assembly/analytic-fog-registration.d.ts +3 -0
- package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
- 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/fxaa-registration.d.ts +4 -0
- package/dist/assembly/fxaa-registration.d.ts.map +1 -0
- package/dist/assembly/host-contract.d.ts +3 -3
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/lod-observation.d.ts +4 -0
- package/dist/assembly/lod-observation.d.ts.map +1 -0
- 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/motion-blur-registration.d.ts +3 -0
- package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
- 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/temporal-post-process-prewarm.d.ts +13 -0
- package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
- 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-contract.d.ts +4 -3
- package/dist/assembly/webgpu-ready-contract.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 +2 -2
- 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/bloom-admission.d.ts +13 -0
- package/dist/bloom-admission.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-JCIRYQ4V.mjs → chunk-HEB564WF.mjs} +10 -4
- package/dist/chunk-HEB564WF.mjs.map +1 -0
- package/dist/{chunk-JRIPCMYC.mjs → chunk-MKDIT2EM.mjs} +1584 -375
- package/dist/chunk-MKDIT2EM.mjs.map +1 -0
- package/dist/{chunk-S665NJNF.mjs → chunk-OCDLEI6I.mjs} +3 -3
- package/dist/{chunk-S665NJNF.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-DRR7D3KX.mjs → chunk-SHFUFJ5N.mjs} +192 -18
- package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
- package/dist/{chunk-4NNZQCL3.mjs → chunk-UI5SFODX.mjs} +223 -277
- package/dist/chunk-UI5SFODX.mjs.map +1 -0
- package/dist/{chunk-GWCEMDVR.mjs → chunk-UINHLCZA.mjs} +787 -80
- package/dist/chunk-UINHLCZA.mjs.map +1 -0
- package/dist/{chunk-6TFBXLRJ.mjs → chunk-WEX45HKH.mjs} +222 -337
- 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/atmosphere.d.ts +2 -0
- package/dist/components/atmosphere.d.ts.map +1 -1
- package/dist/components/barrel-distortion.d.ts +13 -0
- package/dist/components/barrel-distortion.d.ts.map +1 -0
- package/dist/components/camera.d.ts +20 -4
- package/dist/components/camera.d.ts.map +1 -1
- 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/components/mesh-filter.d.ts +6 -5
- package/dist/components/mesh-filter.d.ts.map +1 -1
- package/dist/components/motion-blur.d.ts +1 -0
- package/dist/components/motion-blur.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 +9751 -3528
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +6 -0
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/dynamic-geometry.d.ts +5 -1
- package/dist/dynamic-geometry.d.ts.map +1 -1
- package/dist/environment/analytic-fog.d.ts +6 -0
- package/dist/environment/analytic-fog.d.ts.map +1 -0
- package/dist/errors/cloud.d.ts +59 -0
- package/dist/errors/cloud.d.ts.map +1 -0
- package/dist/errors/render.d.ts +23 -5
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/examples/single-layer-medium-surface.d.ts +47 -0
- package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +12 -1
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/extract/environment.d.ts +2 -0
- package/dist/extract/environment.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 +17 -0
- package/dist/features/error-reporting.d.ts.map +1 -0
- package/dist/features/host.d.ts +15 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
- package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
- package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
- package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
- package/dist/features/plan.d.ts +15 -4
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +9 -5
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/render-graph-compute.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 +7 -0
- 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 +50 -4
- 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 +35 -2
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +11 -1
- 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 +31 -8
- 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 +178 -3
- 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 +49 -94
- 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 +28 -19
- 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/pipeline-spec.d.ts +2 -2
- 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 +40 -3
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +14 -62
- 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/mesh-ssbo.d.ts +6 -5
- package/dist/record/mesh-ssbo.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 +34 -31
- 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 +6 -4
- 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 +66 -8
- 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 +106 -3
- 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 +24 -10
- 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 +26 -15
- 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 +14 -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/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +7 -0
- package/dist/temporal/frame.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/temporal/view.d.ts +6 -1
- package/dist/temporal/view.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 +24 -9
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/component.d.ts +9 -5
- 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/dist/volume/temporal.d.ts +3 -1
- package/dist/volume/temporal.d.ts.map +1 -1
- package/package.json +22 -21
- package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
- package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
- 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__/bloom-exact-zero.unit.test.ts +112 -18
- package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
- 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__/dynamic-resolution-plugin.integration.test.ts +20 -0
- package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
- package/src/__tests__/environment-selection.unit.test.ts +12 -0
- package/src/__tests__/factory-contract.integration.test.ts +626 -113
- package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
- package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
- package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
- 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-indirect-raster-evidence.ts +2 -1
- 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 +7 -6
- package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +103 -4
- 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-resource-store-stride.unit.test.ts +12 -0
- package/src/__tests__/gpu-scene.unit.test.ts +210 -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 +105 -238
- 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__/mesh-submission-lifetime.unit.test.ts +31 -0
- 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 +589 -0
- 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-gpu-driven-pbr.integration.test.ts +21 -0
- package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
- package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
- package/src/__tests__/probe-bindings.unit.test.ts +582 -1
- package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
- package/src/__tests__/public-surface.test-d.ts +8 -0
- 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 +9 -0
- package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
- package/src/__tests__/render-feature-error-reporting.unit.test.ts +44 -0
- 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-feature-stage-fault-recovery.unit.test.ts +23 -0
- 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 +37 -6
- 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 +33 -11
- package/src/__tests__/resource-allocation.unit.test.ts +123 -0
- package/src/__tests__/shader-manifest-fixture.ts +4 -4
- 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 +17 -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-lighting-write.unit.test.ts +2 -1
- 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.dawn.test.ts +1 -1
- package/src/__tests__/standard-pipeline.integration.test.ts +200 -9
- 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__/temporal-reset-matrix.unit.test.ts +10 -0
- package/src/__tests__/temporal-view.unit.test.ts +2 -1
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
- 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 +7 -3
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +189 -15
- 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 +558 -68
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +23 -5
- package/src/assembly/analytic-fog-registration.ts +14 -0
- 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/fxaa-registration.ts +17 -0
- package/src/assembly/host-contract.ts +3 -3
- package/src/assembly/lod-observation.ts +12 -0
- 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/motion-blur-registration.ts +17 -0
- 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 +6 -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 +40 -0
- package/src/assembly/webgpu-pbr-ready.ts +9 -1
- package/src/assembly/webgpu-ready-contract.ts +8 -3
- package/src/assembly/webgpu-ready-per-pass.ts +104 -0
- package/src/assembly/webgpu-ready.ts +296 -306
- 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 +228 -281
- package/src/assembly/webgpu-vertex-layouts.ts +1 -1
- package/src/barrel-distortion.ts +247 -0
- package/src/bloom-admission.ts +21 -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/__tests__/motion-blur.unit.test.ts +1 -0
- package/src/components/atmosphere.ts +4 -0
- package/src/components/barrel-distortion.ts +46 -0
- package/src/components/camera.ts +66 -10
- 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/mesh-filter.ts +11 -9
- package/src/components/motion-blur.ts +1 -0
- package/src/components/sprite-instances.ts +1 -1
- package/src/construct-renderer.ts +1 -1
- package/src/device/gpu-residency.ts +56 -12
- package/src/dynamic-geometry.ts +12 -1
- package/src/environment/analytic-fog.ts +44 -0
- package/src/environment/background.ts +2 -2
- package/src/errors/cloud.ts +103 -0
- package/src/errors/render.ts +58 -8
- package/src/examples/single-layer-medium-surface.ts +59 -0
- package/src/extract/camera.ts +35 -8
- package/src/extract/contracts.ts +12 -0
- package/src/extract/environment.ts +4 -0
- package/src/features/barrel-distortion.ts +172 -0
- package/src/features/error-reporting.ts +98 -0
- package/src/features/host.ts +138 -24
- package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
- package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
- package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
- package/src/features/motion-blur/motion-blur-params.ts +70 -2
- package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
- package/src/features/plan.ts +33 -8
- package/src/features/prepared-gpu-work.ts +86 -35
- package/src/features/render-graph-compute.ts +13 -5
- package/src/features/render-graph-contribution.ts +11 -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 +35 -32
- package/src/gpu-driven/prepared-draw.ts +27 -14
- package/src/gpu-driven/production-raster-material.ts +1894 -0
- package/src/gpu-driven/production-raster.ts +904 -787
- 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 +181 -10
- package/src/gpu-scene.ts +259 -101
- package/src/hdrp-buffers.ts +4 -3
- package/src/ibl/skylight-bind-group.ts +49 -7
- package/src/index.ts +228 -9
- package/src/inspection-types.ts +222 -2
- package/src/instance-buffer-cache.ts +4 -4
- package/src/instances.ts +151 -492
- 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 +397 -102
- package/src/pipeline-builder.ts +1 -1
- package/src/pipeline-spec.ts +5 -5
- package/src/plugin.ts +4 -0
- package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
- package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
- 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 +3 -2
- 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 +44 -4
- package/src/record/frame-targets.ts +10 -6
- package/src/record/frame.ts +373 -1099
- package/src/record/gpu-draw-receipts.ts +38 -0
- package/src/record/gpu-timing.ts +48 -5
- package/src/record/main-pass-geometry.ts +281 -83
- package/src/record/main-pass-material.ts +66 -4
- package/src/record/main-pass-sprite-draws.ts +13 -66
- package/src/record/main-pass.ts +156 -20
- package/src/record/mesh-ssbo.ts +43 -6
- 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 +51 -31
- package/src/record/shadow-pass.ts +36 -2
- package/src/record/skybox-post-pass.ts +150 -200
- package/src/record/typed-frame-graph.ts +824 -44
- package/src/record/view-ubo.ts +26 -3
- package/src/record/volume-params.ts +225 -0
- package/src/recovery/render-system-candidate.ts +257 -52
- package/src/reflection/record-owner.ts +7 -3
- package/src/render-contract.ts +71 -8
- package/src/render-graph-primitives.ts +7 -1
- package/src/render-pipeline.ts +136 -1
- package/src/render-system-extract-tail.ts +72 -60
- package/src/render-system-extract.ts +117 -87
- 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 +601 -482
- package/src/scene/probe-blend.ts +96 -4
- package/src/scene/render-scene.ts +431 -281
- 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/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
- package/src/temporal/frame-coordinator.ts +8 -1
- package/src/temporal/frame.ts +20 -0
- 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/temporal/view.ts +13 -2
- 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 +191 -50
- package/src/volume/component.ts +10 -10
- package/src/volume/extract.ts +11 -5
- package/src/volume/inspection.ts +3 -0
- package/src/volume/passes.ts +95 -8
- package/src/volume/resources.ts +2 -1
- package/src/volume/temporal.ts +11 -0
- package/dist/chunk-4NNZQCL3.mjs.map +0 -1
- package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
- package/dist/chunk-DRR7D3KX.mjs.map +0 -1
- package/dist/chunk-GWCEMDVR.mjs.map +0 -1
- package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
- package/dist/chunk-JRIPCMYC.mjs.map +0 -1
- package/dist/chunk-LULCA3OF.mjs +0 -1713
- package/dist/chunk-LULCA3OF.mjs.map +0 -1
- package/dist/chunk-PA5DRJNI.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,
|
|
@@ -84,7 +98,12 @@ import {
|
|
|
84
98
|
type ShadowViewUpdateInput,
|
|
85
99
|
shadowViewIdentityKey,
|
|
86
100
|
} from './shadow-views';
|
|
87
|
-
import {
|
|
101
|
+
import {
|
|
102
|
+
type GpuDrivenLodSelectionInspection,
|
|
103
|
+
type GpuDrivenLodSubmitIdentity,
|
|
104
|
+
GpuDrivenView,
|
|
105
|
+
selectGpuLodLane,
|
|
106
|
+
} from './view-gpu';
|
|
88
107
|
|
|
89
108
|
export interface StandardPbrSceneCandidate {
|
|
90
109
|
readonly primitiveIndex: number;
|
|
@@ -95,6 +114,39 @@ export interface StandardPbrSceneCandidate {
|
|
|
95
114
|
readonly materialIndex?: number;
|
|
96
115
|
}
|
|
97
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
|
+
|
|
98
150
|
export interface StandardPbrSceneProjection {
|
|
99
151
|
readonly entryPoint: string;
|
|
100
152
|
readonly layoutIdentity: string;
|
|
@@ -253,21 +305,34 @@ interface PreparedBatch {
|
|
|
253
305
|
readonly slotGeneration: number;
|
|
254
306
|
}
|
|
255
307
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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;
|
|
268
323
|
};
|
|
269
324
|
}
|
|
270
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
|
+
|
|
271
336
|
function activeFrustum(camera: CameraSnapshot): Float32Array {
|
|
272
337
|
const projection = mat4.create();
|
|
273
338
|
if (camera.projection === 'orthographic') {
|
|
@@ -925,6 +990,136 @@ function shadowCasterMembershipSignature(
|
|
|
925
990
|
|
|
926
991
|
type FilteredProductionPlan = ReturnType<typeof filteredPlan>;
|
|
927
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
|
+
|
|
928
1123
|
function sceneRequiresGpuDrivenArtifact(scene: PersistentGpuDrivenState): boolean {
|
|
929
1124
|
return scene.plan.batches.some((batch) =>
|
|
930
1125
|
batch.candidates.some((candidate) => {
|
|
@@ -1134,736 +1329,11 @@ function materialArtifactSignature(
|
|
|
1134
1329
|
}
|
|
1135
1330
|
|
|
1136
1331
|
/** The GPU-driven bridge to the existing four-group Standard PBR pipeline. */
|
|
1137
|
-
class MaterialAbiRasterAdapter {
|
|
1138
|
-
private readonly pipelines = new Map<string, RenderPipeline>();
|
|
1139
|
-
private readonly projectionBuffers = new Set<Buffer>();
|
|
1140
|
-
private readonly projectionBindings = new Map<
|
|
1141
|
-
string,
|
|
1142
|
-
{
|
|
1143
|
-
readonly buffer: Buffer;
|
|
1144
|
-
readonly bindGroup: BindGroup;
|
|
1145
|
-
readonly capacityBytes: number;
|
|
1146
|
-
rows: Float32Array;
|
|
1147
|
-
}
|
|
1148
|
-
>();
|
|
1149
|
-
/**
|
|
1150
|
-
* Projection rows are graph-generation resources. A new topology may stage
|
|
1151
|
-
* a replacement before the graph is promoted; keeping staged bindings out
|
|
1152
|
-
* of the active map lets an aborted graph leave the last submitted rows
|
|
1153
|
-
* intact. Superseded buffers are fence-retired on promotion.
|
|
1154
|
-
*/
|
|
1155
|
-
private readonly stagedProjectionBindings = new Map<
|
|
1156
|
-
string,
|
|
1157
|
-
{
|
|
1158
|
-
readonly buffer: Buffer;
|
|
1159
|
-
readonly bindGroup: BindGroup;
|
|
1160
|
-
readonly capacityBytes: number;
|
|
1161
|
-
rows: Float32Array;
|
|
1162
|
-
}
|
|
1163
|
-
>();
|
|
1164
|
-
private readonly pendingProjectionRetirements = new Set<Buffer>();
|
|
1165
|
-
private readonly destroyedProjectionBuffers = new Set<Buffer>();
|
|
1166
|
-
private projectionDisposed = false;
|
|
1167
|
-
private stagedProjectionKeys = new Set<string>();
|
|
1168
|
-
private readonly activeProjectionRowsAtStage = new Map<string, Float32Array>();
|
|
1169
|
-
private readonly visibleWindowBindGroups = new Map<Buffer, Map<string, BindGroup>>();
|
|
1170
|
-
|
|
1171
|
-
private constructor(
|
|
1172
|
-
private readonly device: RhiDevice,
|
|
1173
|
-
private readonly pipelineLayout: import('@forgeax/engine-rhi').PipelineLayout,
|
|
1174
|
-
private readonly meshLayout: BindGroupLayout,
|
|
1175
|
-
private readonly instancesBindGroupLayout: BindGroupLayout,
|
|
1176
|
-
private readonly programs: ReadonlyMap<string, GpuDrivenPbrProgram> | undefined,
|
|
1177
|
-
private instancesBindGroup: BindGroup,
|
|
1178
|
-
private readonly instanceBuffer: Buffer,
|
|
1179
|
-
private readonly instanceBufferSize: number,
|
|
1180
|
-
private visibleBuffer: Buffer,
|
|
1181
|
-
private readonly artifact: MaterialShaderArtifact,
|
|
1182
|
-
private readonly deformation: 'rigid' | 'skin',
|
|
1183
|
-
private readonly pipelineFactory: MaterialPipelineFactory | undefined,
|
|
1184
|
-
private paletteBuffer: Buffer | undefined,
|
|
1185
|
-
private paletteBindingWindowBytes: number | undefined,
|
|
1186
|
-
) {}
|
|
1187
|
-
|
|
1188
|
-
static create(input: {
|
|
1189
|
-
readonly device: RhiDevice;
|
|
1190
|
-
readonly pipelineState: PipelineState;
|
|
1191
|
-
readonly artifact: MaterialShaderArtifact;
|
|
1192
|
-
readonly visibleBuffer: Buffer;
|
|
1193
|
-
readonly deformation?: 'rigid' | 'skin';
|
|
1194
|
-
readonly paletteBuffer?: Buffer;
|
|
1195
|
-
readonly paletteBindingWindowBytes?: number;
|
|
1196
|
-
/** Renderer-owned factory for the selected artifact's actual PSO. */
|
|
1197
|
-
readonly pipelineFactory?: MaterialPipelineFactory;
|
|
1198
|
-
}): Result<MaterialAbiRasterAdapter, RhiError | GpuDrivenPreparationError> {
|
|
1199
|
-
const deformation = input.deformation ?? 'rigid';
|
|
1200
|
-
const receipt = input.artifact.receipt;
|
|
1201
|
-
if (receipt === undefined) {
|
|
1202
|
-
return err(
|
|
1203
|
-
new RhiError({
|
|
1204
|
-
code: 'rhi-not-available',
|
|
1205
|
-
expected: 'published MaterialProgramAbi receipt for the selected material artifact',
|
|
1206
|
-
hint: 'recook and publish the material before creating a GPU-driven raster owner',
|
|
1207
|
-
}),
|
|
1208
|
-
);
|
|
1209
|
-
}
|
|
1210
|
-
const materialRowMatchesSceneTable =
|
|
1211
|
-
receipt.materialRow.byteLength === GPU_SCENE_LAYOUTS.material.stride &&
|
|
1212
|
-
new Set(receipt.materialRow.fields).size === receipt.materialRow.fields.length;
|
|
1213
|
-
if (!materialRowMatchesSceneTable) {
|
|
1214
|
-
return err(
|
|
1215
|
-
new GpuDrivenPreparationError('material-abi-not-executable', {
|
|
1216
|
-
reason: 'material-abi-not-executable',
|
|
1217
|
-
owner: 'material',
|
|
1218
|
-
expected: `scene-index material row ${GPU_SCENE_LAYOUTS.material.stride} bytes with unique producer fields`,
|
|
1219
|
-
actual: `row ${receipt.materialRow.byteLength} bytes with fields ${receipt.materialRow.fields.join(',')}`,
|
|
1220
|
-
}),
|
|
1221
|
-
);
|
|
1222
|
-
}
|
|
1223
|
-
if (
|
|
1224
|
-
receipt.reflection.layoutIdentity !== input.artifact.layoutIdentity ||
|
|
1225
|
-
receipt.directEntry.length === 0 ||
|
|
1226
|
-
receipt.sceneIndexEntry.length === 0 ||
|
|
1227
|
-
receipt.resourceSlots.some((slot) => slot.group !== 1)
|
|
1228
|
-
) {
|
|
1229
|
-
return err(
|
|
1230
|
-
new RhiError({
|
|
1231
|
-
code: 'rhi-descriptor-invalid',
|
|
1232
|
-
expected: `complete selected MaterialProgramAbi reflection, scene row stride ${GPU_SCENE_LAYOUTS.material.stride}, and resource slots`,
|
|
1233
|
-
hint: 'repair the producer publication or use the owner for its non-scene material layout',
|
|
1234
|
-
}),
|
|
1235
|
-
);
|
|
1236
|
-
}
|
|
1237
|
-
// Scene-index shaders declare the dedicated slot-3 ABI. Keep the legacy
|
|
1238
|
-
// fields as a compatibility fallback for synthetic fixtures; the real
|
|
1239
|
-
// factory publishes the dedicated handles whenever the capable storage
|
|
1240
|
-
// lane is enabled, so production draws never bind the direct-draw BGL.
|
|
1241
|
-
const pipelineLayout =
|
|
1242
|
-
deformation === 'skin'
|
|
1243
|
-
? input.pipelineState.gpuDrivenPbrSkinPipelineLayout
|
|
1244
|
-
: input.pipelineState.gpuDrivenPbrPipelineLayout;
|
|
1245
|
-
const instancesBindGroupLayout = input.pipelineState.gpuDrivenInstancesBindGroupLayout;
|
|
1246
|
-
if (pipelineLayout == null || instancesBindGroupLayout == null) {
|
|
1247
|
-
return err(
|
|
1248
|
-
new RhiError({
|
|
1249
|
-
code: 'rhi-not-available',
|
|
1250
|
-
expected: `${deformation === 'skin' ? 'skinned ' : ''}published material pipeline layout`,
|
|
1251
|
-
hint: 'initialize the producer material pipeline before enabling GPU-driven raster',
|
|
1252
|
-
}),
|
|
1253
|
-
);
|
|
1254
|
-
}
|
|
1255
|
-
const programs = input.pipelineState.gpuDrivenPbrPrograms;
|
|
1256
|
-
if (
|
|
1257
|
-
input.pipelineFactory === undefined &&
|
|
1258
|
-
(programs === undefined ||
|
|
1259
|
-
resolveStandardPbrProgram(
|
|
1260
|
-
programs,
|
|
1261
|
-
input.artifact.material,
|
|
1262
|
-
input.artifact.receipt?.vertexInputs.some((attribute) => attribute.semantic === 'color'),
|
|
1263
|
-
) === undefined)
|
|
1264
|
-
) {
|
|
1265
|
-
return err(
|
|
1266
|
-
new RhiError({
|
|
1267
|
-
code: 'rhi-not-available',
|
|
1268
|
-
expected: `producer-owned ${deformation === 'skin' ? 'skinned ' : ''}material shader module for the current device generation`,
|
|
1269
|
-
hint: 'publish the selected material module from buildReadyWebGPU before enabling GPU-driven raster',
|
|
1270
|
-
}),
|
|
1271
|
-
);
|
|
1272
|
-
}
|
|
1273
|
-
const meshLayout =
|
|
1274
|
-
deformation === 'skin'
|
|
1275
|
-
? input.pipelineState.pbrSkinMeshBindGroupLayout
|
|
1276
|
-
: input.pipelineState.meshBindGroupLayout;
|
|
1277
|
-
if (meshLayout === null) {
|
|
1278
|
-
return err(
|
|
1279
|
-
new RhiError({
|
|
1280
|
-
code: 'rhi-not-available',
|
|
1281
|
-
expected: `${deformation === 'skin' ? 'skinned ' : ''}material mesh bind-group layout`,
|
|
1282
|
-
hint: 'publish the exact mesh-array layout selected by the material receipt',
|
|
1283
|
-
}),
|
|
1284
|
-
);
|
|
1285
|
-
}
|
|
1286
|
-
if (
|
|
1287
|
-
deformation === 'skin' &&
|
|
1288
|
-
(input.paletteBuffer === undefined || input.paletteBindingWindowBytes === undefined)
|
|
1289
|
-
) {
|
|
1290
|
-
return err(
|
|
1291
|
-
new RhiError({
|
|
1292
|
-
code: 'rhi-not-available',
|
|
1293
|
-
expected: 'persistent skin palette buffer and binding window',
|
|
1294
|
-
hint: 'keep the palette receipt and the GPU-driven skinned projection on one frame identity',
|
|
1295
|
-
}),
|
|
1296
|
-
);
|
|
1297
|
-
}
|
|
1298
|
-
const identity = new Float32Array(16);
|
|
1299
|
-
identity[0] = 1;
|
|
1300
|
-
identity[5] = 1;
|
|
1301
|
-
identity[10] = 1;
|
|
1302
|
-
identity[15] = 1;
|
|
1303
|
-
const instancePayload = packInstanceStorageBuffer(identity);
|
|
1304
|
-
const instanceBuffer = input.device.createBuffer({
|
|
1305
|
-
label: 'standard-pbr-gpu-driven-instance-data',
|
|
1306
|
-
size: instancePayload.byteLength,
|
|
1307
|
-
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
1308
|
-
});
|
|
1309
|
-
if (!instanceBuffer.ok) return instanceBuffer;
|
|
1310
|
-
const instanceWrite = input.device.queue.writeBuffer(instanceBuffer.value, 0, instancePayload);
|
|
1311
|
-
if (!instanceWrite.ok) return instanceWrite;
|
|
1312
|
-
const instancesBindGroup = input.device.createBindGroup({
|
|
1313
|
-
label: 'standard-pbr-gpu-driven-visible-instances-bg',
|
|
1314
|
-
layout: instancesBindGroupLayout,
|
|
1315
|
-
entries: [
|
|
1316
|
-
{
|
|
1317
|
-
binding: 0,
|
|
1318
|
-
resource: bufferBinding(instanceBuffer.value, 0, instancePayload.byteLength),
|
|
1319
|
-
},
|
|
1320
|
-
{
|
|
1321
|
-
binding: 1,
|
|
1322
|
-
resource: bufferBinding(input.visibleBuffer),
|
|
1323
|
-
},
|
|
1324
|
-
{
|
|
1325
|
-
binding: 2,
|
|
1326
|
-
resource: bufferBinding(input.visibleBuffer),
|
|
1327
|
-
},
|
|
1328
|
-
],
|
|
1329
|
-
});
|
|
1330
|
-
if (!instancesBindGroup.ok) return instancesBindGroup;
|
|
1331
|
-
return ok(
|
|
1332
|
-
new MaterialAbiRasterAdapter(
|
|
1333
|
-
input.device,
|
|
1334
|
-
pipelineLayout,
|
|
1335
|
-
meshLayout,
|
|
1336
|
-
instancesBindGroupLayout,
|
|
1337
|
-
programs,
|
|
1338
|
-
instancesBindGroup.value,
|
|
1339
|
-
instanceBuffer.value,
|
|
1340
|
-
instancePayload.byteLength,
|
|
1341
|
-
input.visibleBuffer,
|
|
1342
|
-
input.artifact,
|
|
1343
|
-
deformation,
|
|
1344
|
-
input.pipelineFactory,
|
|
1345
|
-
input.paletteBuffer,
|
|
1346
|
-
input.paletteBindingWindowBytes,
|
|
1347
|
-
),
|
|
1348
|
-
);
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
ensurePaletteBuffer(
|
|
1352
|
-
paletteBuffer: Buffer | undefined,
|
|
1353
|
-
paletteBindingWindowBytes: number | undefined,
|
|
1354
|
-
): Result<void, RhiError> {
|
|
1355
|
-
if (this.deformation !== 'skin') return ok(undefined);
|
|
1356
|
-
if (paletteBuffer === undefined || paletteBindingWindowBytes === undefined) {
|
|
1357
|
-
return err(
|
|
1358
|
-
new RhiError({
|
|
1359
|
-
code: 'rhi-not-available',
|
|
1360
|
-
expected: 'persistent skin palette buffer and binding window',
|
|
1361
|
-
hint: 'publish a storage-backed SkinPaletteReceipt before the GPU-driven skinned lane',
|
|
1362
|
-
}),
|
|
1363
|
-
);
|
|
1364
|
-
}
|
|
1365
|
-
this.paletteBuffer = paletteBuffer;
|
|
1366
|
-
this.paletteBindingWindowBytes = paletteBindingWindowBytes;
|
|
1367
|
-
return ok(undefined);
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
ensureVisibleBuffer(visibleBuffer: Buffer): Result<void, RhiError> {
|
|
1371
|
-
if (this.visibleBuffer === visibleBuffer) return ok(undefined);
|
|
1372
|
-
const created = this.device.createBindGroup({
|
|
1373
|
-
label: 'standard-pbr-gpu-driven-visible-instances-bg',
|
|
1374
|
-
layout: this.instancesBindGroupLayout,
|
|
1375
|
-
entries: [
|
|
1376
|
-
{
|
|
1377
|
-
binding: 0,
|
|
1378
|
-
resource: bufferBinding(this.instanceBuffer, 0, this.instanceBufferSize),
|
|
1379
|
-
},
|
|
1380
|
-
{ binding: 1, resource: bufferBinding(visibleBuffer) },
|
|
1381
|
-
{ binding: 2, resource: bufferBinding(visibleBuffer) },
|
|
1382
|
-
],
|
|
1383
|
-
});
|
|
1384
|
-
if (!created.ok) return created;
|
|
1385
|
-
this.instancesBindGroup = created.value;
|
|
1386
|
-
this.visibleBuffer = visibleBuffer;
|
|
1387
|
-
return ok(undefined);
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
visibleWindowBindGroup(
|
|
1391
|
-
visibleBase: number,
|
|
1392
|
-
visibleCapacity: number,
|
|
1393
|
-
): Result<BindGroup, RhiError> {
|
|
1394
|
-
return this.visibleWindowBindGroupForBuffer(this.visibleBuffer, visibleBase, visibleCapacity);
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
visibleWindowBindGroupForBuffer(
|
|
1398
|
-
visibleBuffer: Buffer,
|
|
1399
|
-
visibleBase: number,
|
|
1400
|
-
visibleCapacity: number,
|
|
1401
|
-
): Result<BindGroup, RhiError> {
|
|
1402
|
-
const offset = visibleBase * 8;
|
|
1403
|
-
const size = visibleCapacity * 8;
|
|
1404
|
-
if (offset % 256 !== 0 || size <= 0) {
|
|
1405
|
-
return err(
|
|
1406
|
-
new RhiError({
|
|
1407
|
-
code: 'rhi-descriptor-invalid',
|
|
1408
|
-
expected: '256-byte-aligned non-empty visible window',
|
|
1409
|
-
hint: `allocate each GPU-driven batch visible window on the portable storage-buffer boundary (base=${visibleBase}, capacity=${visibleCapacity}, offset=${offset}, size=${size})`,
|
|
1410
|
-
}),
|
|
1411
|
-
);
|
|
1412
|
-
}
|
|
1413
|
-
let groups = this.visibleWindowBindGroups.get(visibleBuffer);
|
|
1414
|
-
if (groups === undefined) {
|
|
1415
|
-
groups = new Map();
|
|
1416
|
-
this.visibleWindowBindGroups.set(visibleBuffer, groups);
|
|
1417
|
-
}
|
|
1418
|
-
const key = `${visibleBase}:${visibleCapacity}`;
|
|
1419
|
-
const cached = groups.get(key);
|
|
1420
|
-
if (cached !== undefined) return ok(cached);
|
|
1421
|
-
const created = this.device.createBindGroup({
|
|
1422
|
-
label: `standard-pbr-gpu-driven-visible-window-${visibleBase}`,
|
|
1423
|
-
layout: this.instancesBindGroupLayout,
|
|
1424
|
-
entries: [
|
|
1425
|
-
{
|
|
1426
|
-
binding: 0,
|
|
1427
|
-
resource: bufferBinding(this.instanceBuffer, 0, this.instanceBufferSize),
|
|
1428
|
-
},
|
|
1429
|
-
{ binding: 1, resource: bufferBinding(visibleBuffer, offset, size) },
|
|
1430
|
-
{ binding: 2, resource: bufferBinding(visibleBuffer, offset, size) },
|
|
1431
|
-
],
|
|
1432
|
-
});
|
|
1433
|
-
if (!created.ok) return created;
|
|
1434
|
-
groups.set(key, created.value);
|
|
1435
|
-
return ok(created.value);
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
pipeline(
|
|
1439
|
-
format: TextureFormat,
|
|
1440
|
-
sampleCount: 1 | 4,
|
|
1441
|
-
layoutProjection: MeshGpuHandles['layoutProjection'],
|
|
1442
|
-
shaderUvSetCount: number,
|
|
1443
|
-
topology: GpuDrivenBatch['key']['topology'],
|
|
1444
|
-
stripIndexFormat: 'uint16' | 'uint32' | undefined,
|
|
1445
|
-
renderState: MaterialRenderState | undefined,
|
|
1446
|
-
selectedArtifact: MaterialShaderArtifact = this.artifact,
|
|
1447
|
-
additionalColorFormats: readonly TextureFormat[] = [],
|
|
1448
|
-
): Result<RenderPipeline, RhiError> {
|
|
1449
|
-
const vertexLayout = deriveVertexBufferLayoutFromProjection(layoutProjection, {
|
|
1450
|
-
shaderUvSetCount,
|
|
1451
|
-
})[0];
|
|
1452
|
-
if (vertexLayout === undefined) {
|
|
1453
|
-
return err(
|
|
1454
|
-
new RhiError({
|
|
1455
|
-
code: 'shader-compile-failed',
|
|
1456
|
-
expected: 'non-empty geometry VertexLayoutProjection',
|
|
1457
|
-
hint: 'prepare a receipt-compatible mesh projection before Standard PBR raster',
|
|
1458
|
-
}),
|
|
1459
|
-
);
|
|
1460
|
-
}
|
|
1461
|
-
const receipt = selectedArtifact.receipt;
|
|
1462
|
-
if (receipt === undefined) {
|
|
1463
|
-
return err(
|
|
1464
|
-
new RhiError({
|
|
1465
|
-
code: 'rhi-not-available',
|
|
1466
|
-
expected: 'selected MaterialProgramAbi receipt for GPU-driven raster',
|
|
1467
|
-
hint: 'keep the draw on the producer recovery path until its ABI is published',
|
|
1468
|
-
}),
|
|
1469
|
-
);
|
|
1470
|
-
}
|
|
1471
|
-
const program =
|
|
1472
|
-
this.programs === undefined
|
|
1473
|
-
? undefined
|
|
1474
|
-
: resolveStandardPbrProgram(
|
|
1475
|
-
this.programs,
|
|
1476
|
-
this.artifact.material,
|
|
1477
|
-
layoutProjection.attributes.some((attribute) => attribute.key === 'color'),
|
|
1478
|
-
);
|
|
1479
|
-
if (this.pipelineFactory === undefined && program === undefined) {
|
|
1480
|
-
return err(
|
|
1481
|
-
new RhiError({
|
|
1482
|
-
code: 'rhi-not-available',
|
|
1483
|
-
expected: 'producer-owned Standard PBR program matching the geometry vertex layout',
|
|
1484
|
-
hint: 'publish both vertex-color variants for the current device generation',
|
|
1485
|
-
}),
|
|
1486
|
-
);
|
|
1487
|
-
}
|
|
1488
|
-
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}`;
|
|
1489
|
-
const cached = this.pipelines.get(key);
|
|
1490
|
-
if (cached !== undefined) return ok(cached);
|
|
1491
|
-
if (this.pipelineFactory !== undefined) {
|
|
1492
|
-
const pipelineEntry = this.pipelineFactory(
|
|
1493
|
-
selectedArtifact.specializationKey ?? selectedArtifact.material,
|
|
1494
|
-
false,
|
|
1495
|
-
renderState,
|
|
1496
|
-
topology,
|
|
1497
|
-
stripIndexFormat,
|
|
1498
|
-
selectedArtifact.variantSet,
|
|
1499
|
-
'forward',
|
|
1500
|
-
undefined,
|
|
1501
|
-
sampleCount,
|
|
1502
|
-
format,
|
|
1503
|
-
selectedArtifact.uvSetCount ?? shaderUvSetCount,
|
|
1504
|
-
'depth24plus-stencil8',
|
|
1505
|
-
undefined,
|
|
1506
|
-
layoutProjection,
|
|
1507
|
-
// GPU-driven receipts are already producer-validated. Use the
|
|
1508
|
-
// synchronous shader-module adapter for this submission seam so a
|
|
1509
|
-
// newly loaded authored material cannot lose its first draw to the
|
|
1510
|
-
// ordinary async pre-bake warm-up window.
|
|
1511
|
-
'immediate',
|
|
1512
|
-
this.deformation === 'skin' ? 'gpu-driven-skin' : 'gpu-driven-pbr',
|
|
1513
|
-
receipt.sceneIndexEntry,
|
|
1514
|
-
additionalColorFormats,
|
|
1515
|
-
selectedArtifact.vertexEntry,
|
|
1516
|
-
selectedArtifact.fragmentEntry,
|
|
1517
|
-
);
|
|
1518
|
-
if (pipelineEntry === null) {
|
|
1519
|
-
return err(
|
|
1520
|
-
new RhiError({
|
|
1521
|
-
code: 'rhi-not-available',
|
|
1522
|
-
expected: `producer-owned scene-index pipeline for material '${selectedArtifact.material}'`,
|
|
1523
|
-
hint: 'publish a matching program, entry, layout and resource contract before GPU admission',
|
|
1524
|
-
}),
|
|
1525
|
-
);
|
|
1526
|
-
}
|
|
1527
|
-
const expectedGroup2Contract = resolvePipelineGroup2Contract(selectedArtifact.wgsl);
|
|
1528
|
-
if (pipelineEntry.group2Contract !== expectedGroup2Contract) {
|
|
1529
|
-
return err(
|
|
1530
|
-
new RhiError({
|
|
1531
|
-
code: 'rhi-descriptor-invalid',
|
|
1532
|
-
expected: `GPU-driven ${this.deformation === 'skin' ? 'skinned ' : ''}material pipeline group(2) ${expectedGroup2Contract} contract`,
|
|
1533
|
-
hint: 'custom attachment or incompatible group(2) layouts remain on their specialized lane',
|
|
1534
|
-
}),
|
|
1535
|
-
);
|
|
1536
|
-
}
|
|
1537
|
-
this.pipelines.set(key, pipelineEntry.pipeline);
|
|
1538
|
-
return ok(pipelineEntry.pipeline);
|
|
1539
|
-
}
|
|
1540
|
-
if (
|
|
1541
|
-
program === undefined ||
|
|
1542
|
-
selectedArtifact.material !== this.artifact.material ||
|
|
1543
|
-
selectedArtifact.layoutIdentity !== this.artifact.layoutIdentity ||
|
|
1544
|
-
selectedArtifact.receipt?.receiptIdentity !== this.artifact.receipt?.receiptIdentity
|
|
1545
|
-
) {
|
|
1546
|
-
return err(
|
|
1547
|
-
new RhiError({
|
|
1548
|
-
code: 'rhi-not-available',
|
|
1549
|
-
expected: 'selected material pipeline factory for the requested ABI artifact',
|
|
1550
|
-
hint: 'do not pair a receipt-backed custom or color variant with the legacy fixed module',
|
|
1551
|
-
}),
|
|
1552
|
-
);
|
|
1553
|
-
}
|
|
1554
|
-
const pipeline = this.device.createRenderPipeline({
|
|
1555
|
-
label: `standard-pbr-gpu-driven.${key}`,
|
|
1556
|
-
layout: this.pipelineLayout,
|
|
1557
|
-
vertex: {
|
|
1558
|
-
module: program.module,
|
|
1559
|
-
entryPoint: selectedArtifact.vertexEntry ?? receipt.sceneIndexEntry,
|
|
1560
|
-
buffers: [
|
|
1561
|
-
{
|
|
1562
|
-
arrayStride: vertexLayout.arrayStride,
|
|
1563
|
-
stepMode: 'vertex',
|
|
1564
|
-
attributes: vertexLayout.attributes.map(({ shaderLocation, offset, format }) => ({
|
|
1565
|
-
shaderLocation,
|
|
1566
|
-
offset,
|
|
1567
|
-
format: format as GPUVertexFormat,
|
|
1568
|
-
})),
|
|
1569
|
-
},
|
|
1570
|
-
],
|
|
1571
|
-
},
|
|
1572
|
-
fragment: {
|
|
1573
|
-
module: program.module,
|
|
1574
|
-
entryPoint: 'fs_main',
|
|
1575
|
-
targets: [format, ...additionalColorFormats].map((format) => ({ format })),
|
|
1576
|
-
},
|
|
1577
|
-
primitive: {
|
|
1578
|
-
topology,
|
|
1579
|
-
cullMode: renderState?.cullMode ?? 'back',
|
|
1580
|
-
frontFace: renderState?.frontFace ?? 'ccw',
|
|
1581
|
-
...(stripIndexFormat === undefined ? {} : { stripIndexFormat }),
|
|
1582
|
-
},
|
|
1583
|
-
depthStencil: {
|
|
1584
|
-
format: 'depth24plus-stencil8',
|
|
1585
|
-
depthWriteEnabled: renderState?.depthWriteEnabled ?? true,
|
|
1586
|
-
depthCompare: renderState?.depthCompare ?? 'less',
|
|
1587
|
-
},
|
|
1588
|
-
...(sampleCount === 4 ? { multisample: { count: 4 } } : {}),
|
|
1589
|
-
});
|
|
1590
|
-
if (!pipeline.ok) return pipeline;
|
|
1591
|
-
this.pipelines.set(key, pipeline.value);
|
|
1592
|
-
return pipeline;
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
/**
|
|
1596
|
-
* Drop only program-derived caches when a publication switches. Resource
|
|
1597
|
-
* buffers stay alive until their normal fence retirement, while the next
|
|
1598
|
-
* draw must obtain a PSO for the new artifact rather than reuse the old one.
|
|
1599
|
-
*/
|
|
1600
|
-
invalidatePipelineCache(): void {
|
|
1601
|
-
this.pipelines.clear();
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
projectionBindGroup(
|
|
1605
|
-
rows: Float32Array,
|
|
1606
|
-
key?: string,
|
|
1607
|
-
capacityRows?: number,
|
|
1608
|
-
): Result<BindGroup, RhiError> {
|
|
1609
|
-
if (key !== undefined) {
|
|
1610
|
-
const capacityBytes = Math.max(
|
|
1611
|
-
MESH_SSBO_BYTES,
|
|
1612
|
-
(capacityRows ?? Math.ceil(rows.byteLength / MESH_SSBO_BYTES)) * MESH_SSBO_BYTES,
|
|
1613
|
-
);
|
|
1614
|
-
const retained = this.stagedProjectionBindings.get(key) ?? this.projectionBindings.get(key);
|
|
1615
|
-
if (retained !== undefined && retained.capacityBytes >= capacityBytes) {
|
|
1616
|
-
const write = this.writeProjectionRows(key, retained, rows);
|
|
1617
|
-
if (!write.ok) return write;
|
|
1618
|
-
return ok(retained.bindGroup);
|
|
1619
|
-
}
|
|
1620
|
-
const created = this.createProjectionBinding(rows, capacityBytes);
|
|
1621
|
-
if (!created.ok) return created;
|
|
1622
|
-
this.stagedProjectionBindings.set(key, created.value);
|
|
1623
|
-
return ok(created.value.bindGroup);
|
|
1624
|
-
}
|
|
1625
|
-
const created = this.createProjectionBinding(rows, Math.max(MESH_SSBO_BYTES, rows.byteLength));
|
|
1626
|
-
if (!created.ok) return created;
|
|
1627
|
-
return ok(created.value.bindGroup);
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
refreshProjectionRows(key: string, rows: Float32Array): Result<void, RhiError> {
|
|
1631
|
-
const retained = this.stagedProjectionBindings.get(key) ?? this.projectionBindings.get(key);
|
|
1632
|
-
if (retained === undefined) return ok(undefined);
|
|
1633
|
-
if (rows.byteLength > retained.capacityBytes) {
|
|
1634
|
-
const capacityBytes = Math.max(
|
|
1635
|
-
MESH_SSBO_BYTES,
|
|
1636
|
-
Math.ceil(rows.byteLength / MESH_SSBO_BYTES) * MESH_SSBO_BYTES,
|
|
1637
|
-
);
|
|
1638
|
-
const replacement = this.createProjectionBinding(rows, capacityBytes);
|
|
1639
|
-
if (!replacement.ok) return replacement;
|
|
1640
|
-
const previousStaged = this.stagedProjectionBindings.get(key);
|
|
1641
|
-
if (previousStaged !== undefined && previousStaged.buffer !== replacement.value.buffer) {
|
|
1642
|
-
this.retireProjectionBuffer(previousStaged.buffer);
|
|
1643
|
-
}
|
|
1644
|
-
this.stagedProjectionBindings.set(key, replacement.value);
|
|
1645
|
-
return ok(undefined);
|
|
1646
|
-
}
|
|
1647
|
-
return this.writeProjectionRows(key, retained, rows);
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
sceneMeshBinding(key: string): GpuDrivenSceneMeshBinding {
|
|
1651
|
-
const binding = this.stagedProjectionBindings.get(key) ?? this.projectionBindings.get(key);
|
|
1652
|
-
if (binding === undefined)
|
|
1653
|
-
throw new RhiError({
|
|
1654
|
-
code: 'rhi-not-available',
|
|
1655
|
-
expected: 'retained scene mesh projection',
|
|
1656
|
-
hint: 'prepare this batch projection before binding its lighting resources',
|
|
1657
|
-
});
|
|
1658
|
-
return {
|
|
1659
|
-
meshBuffer: binding.buffer,
|
|
1660
|
-
meshBytes: binding.capacityBytes,
|
|
1661
|
-
...(this.deformation === 'skin' && this.paletteBuffer !== undefined
|
|
1662
|
-
? { paletteBuffer: this.paletteBuffer }
|
|
1663
|
-
: {}),
|
|
1664
|
-
};
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
/** Stage the projection keys required by the frame being prepared. */
|
|
1668
|
-
stageProjectionKeys(keys: ReadonlySet<string>): void {
|
|
1669
|
-
this.stagedProjectionKeys = new Set(keys);
|
|
1670
|
-
this.activeProjectionRowsAtStage.clear();
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
/** Add a shadow projection key discovered while updating a view plan. */
|
|
1674
|
-
noteProjectionKey(key: string): void {
|
|
1675
|
-
this.stagedProjectionKeys.add(key);
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
/** Promote staged projection resources after the command buffer submits. */
|
|
1679
|
-
commitProjectionBindings(): void {
|
|
1680
|
-
const keep = new Set(this.stagedProjectionKeys);
|
|
1681
|
-
for (const [key, staged] of this.stagedProjectionBindings) {
|
|
1682
|
-
if (!keep.has(key)) {
|
|
1683
|
-
this.retireProjectionBuffer(staged.buffer);
|
|
1684
|
-
continue;
|
|
1685
|
-
}
|
|
1686
|
-
const previous = this.projectionBindings.get(key);
|
|
1687
|
-
this.projectionBindings.set(key, staged);
|
|
1688
|
-
if (previous !== undefined && previous.buffer !== staged.buffer) {
|
|
1689
|
-
this.retireProjectionBuffer(previous.buffer);
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
this.stagedProjectionBindings.clear();
|
|
1693
|
-
for (const [key, active] of this.projectionBindings) {
|
|
1694
|
-
if (!keep.has(key)) {
|
|
1695
|
-
this.projectionBindings.delete(key);
|
|
1696
|
-
this.retireProjectionBuffer(active.buffer);
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
this.stagedProjectionKeys.clear();
|
|
1700
|
-
this.activeProjectionRowsAtStage.clear();
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
/** Discard resources created for an aborted graph while retaining LKG rows. */
|
|
1704
|
-
abortProjectionBindings(): void {
|
|
1705
|
-
for (const [key, rows] of this.activeProjectionRowsAtStage) {
|
|
1706
|
-
const active = this.projectionBindings.get(key);
|
|
1707
|
-
if (active === undefined) continue;
|
|
1708
|
-
const restored = this.device.queue.writeBuffer(active.buffer, 0, rows);
|
|
1709
|
-
if (restored.ok) active.rows = new Float32Array(rows);
|
|
1710
|
-
}
|
|
1711
|
-
for (const staged of this.stagedProjectionBindings.values()) {
|
|
1712
|
-
this.retireProjectionBuffer(staged.buffer);
|
|
1713
|
-
}
|
|
1714
|
-
this.stagedProjectionBindings.clear();
|
|
1715
|
-
this.stagedProjectionKeys.clear();
|
|
1716
|
-
this.activeProjectionRowsAtStage.clear();
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
private createProjectionBinding(
|
|
1720
|
-
rows: Float32Array,
|
|
1721
|
-
capacityBytes: number,
|
|
1722
|
-
): Result<
|
|
1723
|
-
{
|
|
1724
|
-
readonly buffer: Buffer;
|
|
1725
|
-
readonly bindGroup: BindGroup;
|
|
1726
|
-
readonly capacityBytes: number;
|
|
1727
|
-
rows: Float32Array;
|
|
1728
|
-
},
|
|
1729
|
-
RhiError
|
|
1730
|
-
> {
|
|
1731
|
-
const buffer = this.device.createBuffer({
|
|
1732
|
-
label: 'standard-pbr-gpu-driven-scene-meshes',
|
|
1733
|
-
size: capacityBytes,
|
|
1734
|
-
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
1735
|
-
});
|
|
1736
|
-
if (!buffer.ok) return buffer;
|
|
1737
|
-
const write = this.device.queue.writeBuffer(buffer.value, 0, rows);
|
|
1738
|
-
if (!write.ok) {
|
|
1739
|
-
this.destroyProjectionBuffer(buffer.value);
|
|
1740
|
-
return write;
|
|
1741
|
-
}
|
|
1742
|
-
this.projectionBuffers.add(buffer.value);
|
|
1743
|
-
const entries = [
|
|
1744
|
-
{ binding: 0, resource: bufferBinding(buffer.value, 0, capacityBytes) },
|
|
1745
|
-
...(this.deformation === 'skin' &&
|
|
1746
|
-
this.paletteBuffer !== undefined &&
|
|
1747
|
-
this.paletteBindingWindowBytes !== undefined
|
|
1748
|
-
? [
|
|
1749
|
-
{
|
|
1750
|
-
binding: 1,
|
|
1751
|
-
// The scene-index second lane is a global palette row. Bind the
|
|
1752
|
-
// complete arena so rows beyond the legacy 255-joint window
|
|
1753
|
-
// remain addressable; the shader performs the row selection.
|
|
1754
|
-
resource: bufferBinding(this.paletteBuffer),
|
|
1755
|
-
},
|
|
1756
|
-
{
|
|
1757
|
-
binding: 2,
|
|
1758
|
-
// The dedicated GPU-driven skin layout keeps the current /
|
|
1759
|
-
// previous palette pair used by the direct skin layout. The
|
|
1760
|
-
// scene-index shader currently reads the current arena, but
|
|
1761
|
-
// the shared layout still requires a complete binding shape.
|
|
1762
|
-
resource: bufferBinding(this.paletteBuffer),
|
|
1763
|
-
},
|
|
1764
|
-
]
|
|
1765
|
-
: []),
|
|
1766
|
-
];
|
|
1767
|
-
const bindGroup = this.device.createBindGroup({
|
|
1768
|
-
label: 'standard-pbr-gpu-driven-scene-mesh-bg',
|
|
1769
|
-
layout: this.meshLayout,
|
|
1770
|
-
entries,
|
|
1771
|
-
});
|
|
1772
|
-
if (!bindGroup.ok) {
|
|
1773
|
-
this.destroyProjectionBuffer(buffer.value);
|
|
1774
|
-
return bindGroup;
|
|
1775
|
-
}
|
|
1776
|
-
return ok({
|
|
1777
|
-
buffer: buffer.value,
|
|
1778
|
-
bindGroup: bindGroup.value,
|
|
1779
|
-
capacityBytes,
|
|
1780
|
-
rows: new Float32Array(rows),
|
|
1781
|
-
});
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
private writeProjectionRows(
|
|
1785
|
-
key: string,
|
|
1786
|
-
binding: {
|
|
1787
|
-
readonly buffer: Buffer;
|
|
1788
|
-
readonly bindGroup: BindGroup;
|
|
1789
|
-
readonly capacityBytes: number;
|
|
1790
|
-
rows: Float32Array;
|
|
1791
|
-
},
|
|
1792
|
-
rows: Float32Array,
|
|
1793
|
-
): Result<void, RhiError> {
|
|
1794
|
-
// Active bindings are the last-known-good graph resources. Keep one CPU
|
|
1795
|
-
// snapshot so an aborted candidate can restore their exact contents
|
|
1796
|
-
// without allocating a second GPU buffer on every stable frame.
|
|
1797
|
-
if (
|
|
1798
|
-
this.stagedProjectionBindings.get(key) === undefined &&
|
|
1799
|
-
!this.activeProjectionRowsAtStage.has(key)
|
|
1800
|
-
) {
|
|
1801
|
-
this.activeProjectionRowsAtStage.set(key, new Float32Array(binding.rows));
|
|
1802
|
-
}
|
|
1803
|
-
const write = this.device.queue.writeBuffer(binding.buffer, 0, rows);
|
|
1804
|
-
if (!write.ok) return write;
|
|
1805
|
-
binding.rows = new Float32Array(rows);
|
|
1806
|
-
return ok(undefined);
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
dispose(): void {
|
|
1810
|
-
this.projectionDisposed = true;
|
|
1811
|
-
for (const binding of this.projectionBindings.values()) {
|
|
1812
|
-
this.destroyProjectionBuffer(binding.buffer);
|
|
1813
|
-
}
|
|
1814
|
-
for (const binding of this.stagedProjectionBindings.values()) {
|
|
1815
|
-
this.destroyProjectionBuffer(binding.buffer);
|
|
1816
|
-
}
|
|
1817
|
-
for (const buffer of this.projectionBuffers) this.destroyProjectionBuffer(buffer);
|
|
1818
|
-
for (const buffer of this.pendingProjectionRetirements) {
|
|
1819
|
-
this.destroyProjectionBuffer(buffer);
|
|
1820
|
-
}
|
|
1821
|
-
this.projectionBindings.clear();
|
|
1822
|
-
this.stagedProjectionBindings.clear();
|
|
1823
|
-
this.activeProjectionRowsAtStage.clear();
|
|
1824
|
-
this.projectionBuffers.clear();
|
|
1825
|
-
this.pendingProjectionRetirements.clear();
|
|
1826
|
-
this.device.destroyBuffer(this.instanceBuffer);
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
private retireProjectionBuffer(buffer: Buffer): void {
|
|
1830
|
-
if (this.destroyedProjectionBuffers.has(buffer)) return;
|
|
1831
|
-
this.projectionBuffers.delete(buffer);
|
|
1832
|
-
if (this.projectionDisposed) {
|
|
1833
|
-
this.destroyProjectionBuffer(buffer);
|
|
1834
|
-
return;
|
|
1835
|
-
}
|
|
1836
|
-
if (!this.pendingProjectionRetirements.add(buffer)) return;
|
|
1837
|
-
void this.device.queue.onSubmittedWorkDone().then(
|
|
1838
|
-
() => {
|
|
1839
|
-
this.pendingProjectionRetirements.delete(buffer);
|
|
1840
|
-
this.destroyProjectionBuffer(buffer);
|
|
1841
|
-
},
|
|
1842
|
-
() => {
|
|
1843
|
-
this.pendingProjectionRetirements.delete(buffer);
|
|
1844
|
-
this.destroyProjectionBuffer(buffer);
|
|
1845
|
-
},
|
|
1846
|
-
);
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
private destroyProjectionBuffer(buffer: Buffer): void {
|
|
1850
|
-
if (this.destroyedProjectionBuffers.has(buffer)) return;
|
|
1851
|
-
this.destroyedProjectionBuffers.add(buffer);
|
|
1852
|
-
this.projectionBuffers.delete(buffer);
|
|
1853
|
-
this.device.destroyBuffer(buffer);
|
|
1854
|
-
}
|
|
1855
|
-
|
|
1856
|
-
get instancesFrameGroup(): BindGroup {
|
|
1857
|
-
return this.instancesBindGroup;
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
get kind(): 'rigid' | 'skin' {
|
|
1861
|
-
return this.deformation;
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
1332
|
|
|
1865
1333
|
export interface PreparedGpuDrivenFrame {
|
|
1866
1334
|
readonly topologySignature: string;
|
|
1335
|
+
/** Composition-local world indices mapped to the stable identities used by draw keys. */
|
|
1336
|
+
readonly worldKeys: readonly number[];
|
|
1867
1337
|
/** True when every source draw item/instance is claimed by the GPU lane. */
|
|
1868
1338
|
readonly ownsAllDrawItems: boolean;
|
|
1869
1339
|
/** True only when the extracted ShadowCaster channel is fully represented. */
|
|
@@ -1871,6 +1341,8 @@ export interface PreparedGpuDrivenFrame {
|
|
|
1871
1341
|
/** Concrete main-pass draw identities claimed by the GPU projection. */
|
|
1872
1342
|
readonly drawKeys?: ReadonlySet<string>;
|
|
1873
1343
|
readonly standardPbrFrameResources: GpuDrivenStandardPbrFrameResources;
|
|
1344
|
+
/** Published Surface admission and its pass lane decision. */
|
|
1345
|
+
readonly surfaceSubmission?: SurfaceGpuSubmissionAdmission;
|
|
1874
1346
|
readonly occlusion?: OcclusionFrameProjection;
|
|
1875
1347
|
/** @internal Commits a replacement generation only after graph promotion. */
|
|
1876
1348
|
readonly _commitResourceReplacement: () => void;
|
|
@@ -1878,6 +1350,8 @@ export interface PreparedGpuDrivenFrame {
|
|
|
1878
1350
|
readonly _commitGpuResourceReplacement?: () => void;
|
|
1879
1351
|
/** @internal Discards staged shadow cache publication after a failed frame. */
|
|
1880
1352
|
readonly _abortResourceReplacement?: () => void;
|
|
1353
|
+
/** @internal Marks validated dynamic ranges consumed only after queue submit. */
|
|
1354
|
+
readonly _consumeSurfaceDynamicInput?: (frameNumber: number) => Result<void, RhiError>;
|
|
1881
1355
|
/** Concrete ShadowCaster keys claimed by the GPU subset for this frame. */
|
|
1882
1356
|
readonly shadowDrawKeys?: ReadonlySet<string>;
|
|
1883
1357
|
/** View-scoped ShadowCaster claims consumed by each shadow record pass. */
|
|
@@ -1898,6 +1372,7 @@ export interface PreparedGpuDrivenFrame {
|
|
|
1898
1372
|
graph: RenderGraphBuilder<RenderPipelineFrame>,
|
|
1899
1373
|
format: TextureFormat,
|
|
1900
1374
|
sampleCount: 1 | 4,
|
|
1375
|
+
surfaceSubmissionObservation?: () => SurfaceSubmissionCandidate | undefined,
|
|
1901
1376
|
additionalColorFormats?: readonly TextureFormat[],
|
|
1902
1377
|
): Result<RenderPipelineGpuDrivenProjection, RenderGraphError | RhiError>;
|
|
1903
1378
|
}
|
|
@@ -1931,6 +1406,7 @@ interface GpuDrivenCpuValidationTelemetryCache {
|
|
|
1931
1406
|
readonly cacheKey: string;
|
|
1932
1407
|
readonly validated: readonly { readonly source: RenderableSnapshot }[];
|
|
1933
1408
|
readonly gpuOwnedDrawKeys: ReadonlySet<string>;
|
|
1409
|
+
readonly worldKeys: readonly number[] | undefined;
|
|
1934
1410
|
readonly gpuOwnedDrawCount: number;
|
|
1935
1411
|
readonly totalDrawItems: number;
|
|
1936
1412
|
readonly gpuOwnedRows: number;
|
|
@@ -2042,6 +1518,26 @@ export class GpuDrivenProduction {
|
|
|
2042
1518
|
readonly value: FilteredProductionPlan;
|
|
2043
1519
|
}
|
|
2044
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;
|
|
2045
1541
|
private shadowFilteredCache:
|
|
2046
1542
|
| {
|
|
2047
1543
|
readonly source: SubmissionPlan;
|
|
@@ -2094,6 +1590,8 @@ export class GpuDrivenProduction {
|
|
|
2094
1590
|
>();
|
|
2095
1591
|
private shadowDrawKeysByView = new Map<string, ReadonlySet<string>>();
|
|
2096
1592
|
private shadowExpectedDrawKeys = new Set<string>();
|
|
1593
|
+
private surfaceSubmission: SurfaceGpuSubmissionAdmission | undefined;
|
|
1594
|
+
private surfaceArtifactInspection: GpuDrivenProductionInspection['surfaceArtifact'];
|
|
2097
1595
|
private shadowBatchProjectionSource: SubmissionPlan | undefined;
|
|
2098
1596
|
private skinPaletteBuffer: Buffer | undefined;
|
|
2099
1597
|
private committedPaletteContentRevision: number | undefined;
|
|
@@ -2133,6 +1631,110 @@ export class GpuDrivenProduction {
|
|
|
2133
1631
|
private readonly recoveryCount = 0,
|
|
2134
1632
|
) {}
|
|
2135
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
|
+
|
|
2136
1738
|
/**
|
|
2137
1739
|
* Reuse the last producer-resolved residency rows when all source identity
|
|
2138
1740
|
* and generation fences still match. Renderables and dispatch entries are
|
|
@@ -2219,6 +1821,10 @@ export class GpuDrivenProduction {
|
|
|
2219
1821
|
readonly meshBySlot: ReadonlyMap<number, MeshGpuHandles>;
|
|
2220
1822
|
readonly viewBindGroupLayout: BindGroupLayout;
|
|
2221
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;
|
|
2222
1828
|
readonly materialBindingClasses?: ReadonlyMap<string, string>;
|
|
2223
1829
|
/** Cooked producer artifact selected from the frame material catalog. */
|
|
2224
1830
|
readonly materialArtifact?: MaterialShaderArtifact;
|
|
@@ -2232,6 +1838,8 @@ export class GpuDrivenProduction {
|
|
|
2232
1838
|
readonly materialArtifacts?: ReadonlyMap<string, MaterialShaderArtifact>;
|
|
2233
1839
|
/** Per-draw shadow-pass artifacts keyed by the same prepared identity. */
|
|
2234
1840
|
readonly shadowMaterialArtifacts?: ReadonlyMap<string, MaterialShaderArtifact>;
|
|
1841
|
+
/** Retained ProbeBlend record page; slot N is addressed at aligned lane N+1. */
|
|
1842
|
+
readonly probeBlendRecordBuffer?: Buffer;
|
|
2235
1843
|
readonly materialPipelineState?: PipelineState;
|
|
2236
1844
|
readonly standardPbrPipelineState?: PipelineState;
|
|
2237
1845
|
/** Existing renderer owner that builds the selected artifact PSO/BGL. */
|
|
@@ -2246,10 +1854,19 @@ export class GpuDrivenProduction {
|
|
|
2246
1854
|
readonly activeEntityRevision?: number;
|
|
2247
1855
|
/** Total extracted candidates, including facet-suppressed rows. */
|
|
2248
1856
|
readonly telemetryCandidateCount?: number;
|
|
1857
|
+
/** Public receipt identity for the GPU telemetry copy encoded this frame. */
|
|
1858
|
+
readonly telemetrySubmit?: GpuDrivenLodSubmitIdentity;
|
|
2249
1859
|
readonly occlusion?: OcclusionFrameProjection;
|
|
2250
1860
|
/** Per-frame deltas from the existing RenderScene/GPU Scene/palette owners. */
|
|
2251
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;
|
|
2252
1868
|
}): Result<PreparedGpuDrivenFrame | undefined, RhiError | GpuDrivenPreparationError> {
|
|
1869
|
+
this.surfaceArtifactInspection = undefined;
|
|
2253
1870
|
this.gpuOwnedSnapshotsMaterialized = 0;
|
|
2254
1871
|
this.filteredPlanBuilds = 0;
|
|
2255
1872
|
this.batchBindGroupCreates = 0;
|
|
@@ -2278,6 +1895,7 @@ export class GpuDrivenProduction {
|
|
|
2278
1895
|
this.laneReason = 'capability';
|
|
2279
1896
|
this.submissionState = 'idle';
|
|
2280
1897
|
this.shadowExpectedDrawKeys = new Set();
|
|
1898
|
+
this.surfaceSubmission = undefined;
|
|
2281
1899
|
const capability = classifyRecoveryCapability({
|
|
2282
1900
|
generation: 0,
|
|
2283
1901
|
shaderCompilation: true,
|
|
@@ -2288,6 +1906,7 @@ export class GpuDrivenProduction {
|
|
|
2288
1906
|
});
|
|
2289
1907
|
if (
|
|
2290
1908
|
input.scene === undefined ||
|
|
1909
|
+
(input.hdrp === true && input.clustered !== true) ||
|
|
2291
1910
|
selectGpuLodLane(this.device.caps) === 'cpu' ||
|
|
2292
1911
|
capability.status === 'disabled'
|
|
2293
1912
|
) {
|
|
@@ -2325,6 +1944,57 @@ export class GpuDrivenProduction {
|
|
|
2325
1944
|
const materialArtifact = input.materialArtifact ?? input.standardPbrArtifact;
|
|
2326
1945
|
const skinArtifact = input.materialSkinArtifact ?? input.standardPbrSkinArtifact;
|
|
2327
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
|
+
}
|
|
2328
1998
|
const artifactSignature = `${materialArtifactSignature(
|
|
2329
1999
|
input.materialArtifacts,
|
|
2330
2000
|
materialArtifact,
|
|
@@ -2560,6 +2230,7 @@ export class GpuDrivenProduction {
|
|
|
2560
2230
|
}
|
|
2561
2231
|
const updated = this.view.update(filtered.plan, scene.scene, activeFrustum(input.camera));
|
|
2562
2232
|
if (!updated.ok) return updated;
|
|
2233
|
+
this.view.setTelemetrySubmit(input.telemetrySubmit);
|
|
2563
2234
|
this.telemetryPrepared = true;
|
|
2564
2235
|
const view = this.view;
|
|
2565
2236
|
const visibleBuffer = view.visibleBuffer;
|
|
@@ -2582,6 +2253,20 @@ export class GpuDrivenProduction {
|
|
|
2582
2253
|
}),
|
|
2583
2254
|
);
|
|
2584
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
|
+
}
|
|
2585
2270
|
const committedArtifactChanged =
|
|
2586
2271
|
this.committedProjectionSource !== undefined &&
|
|
2587
2272
|
this.committedProjectionSource.materialArtifactSignature !== artifactSignature;
|
|
@@ -2607,10 +2292,15 @@ export class GpuDrivenProduction {
|
|
|
2607
2292
|
...(input.materialPipelineFactory === undefined
|
|
2608
2293
|
? {}
|
|
2609
2294
|
: { pipelineFactory: input.materialPipelineFactory }),
|
|
2295
|
+
...(input.probeBlendRecordBuffer === undefined
|
|
2296
|
+
? {}
|
|
2297
|
+
: { probeBlendRecordBuffer: input.probeBlendRecordBuffer }),
|
|
2610
2298
|
});
|
|
2611
2299
|
if (!created.ok) return created;
|
|
2612
2300
|
this.materialRaster = created.value;
|
|
2613
2301
|
} else {
|
|
2302
|
+
const probe = this.materialRaster.ensureProbeBlendRecordBuffer(input.probeBlendRecordBuffer);
|
|
2303
|
+
if (!probe.ok) return probe;
|
|
2614
2304
|
const rebound = this.materialRaster.ensureVisibleBuffer(visibleBuffer);
|
|
2615
2305
|
if (!rebound.ok) return rebound;
|
|
2616
2306
|
}
|
|
@@ -2656,6 +2346,10 @@ export class GpuDrivenProduction {
|
|
|
2656
2346
|
deformation: 'skin',
|
|
2657
2347
|
paletteBuffer: skinSnapshot.buffer,
|
|
2658
2348
|
paletteBindingWindowBytes: skinAllocator.bindingWindowBytes,
|
|
2349
|
+
sharedSurfaceFrame: materialRaster.surfaceDynamicSharedFrameState,
|
|
2350
|
+
...(input.probeBlendRecordBuffer === undefined
|
|
2351
|
+
? {}
|
|
2352
|
+
: { probeBlendRecordBuffer: input.probeBlendRecordBuffer }),
|
|
2659
2353
|
...(input.materialPipelineFactory === undefined
|
|
2660
2354
|
? {}
|
|
2661
2355
|
: { pipelineFactory: input.materialPipelineFactory }),
|
|
@@ -2663,6 +2357,8 @@ export class GpuDrivenProduction {
|
|
|
2663
2357
|
if (!created.ok) return created;
|
|
2664
2358
|
this.skinRaster = created.value;
|
|
2665
2359
|
} else {
|
|
2360
|
+
const probe = this.skinRaster.ensureProbeBlendRecordBuffer(input.probeBlendRecordBuffer);
|
|
2361
|
+
if (!probe.ok) return probe;
|
|
2666
2362
|
const rebound = this.skinRaster.ensureVisibleBuffer(visibleBuffer);
|
|
2667
2363
|
if (!rebound.ok) return rebound;
|
|
2668
2364
|
const palette = this.skinRaster.ensurePaletteBuffer(
|
|
@@ -2679,6 +2375,52 @@ export class GpuDrivenProduction {
|
|
|
2679
2375
|
}
|
|
2680
2376
|
}
|
|
2681
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
|
+
}
|
|
2682
2424
|
const shadowViewPool = this.shadowViews;
|
|
2683
2425
|
if (shadowViewPool === undefined) {
|
|
2684
2426
|
return err(
|
|
@@ -2834,25 +2576,83 @@ export class GpuDrivenProduction {
|
|
|
2834
2576
|
)
|
|
2835
2577
|
.join('|') ?? '';
|
|
2836
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;
|
|
2837
2587
|
return ok({
|
|
2838
2588
|
get topologySignature() {
|
|
2839
2589
|
const shadow = shadowTopologySignature();
|
|
2840
2590
|
return shadow.length === 0 ? signature : `${signature}|shadow=${shadow}`;
|
|
2841
2591
|
},
|
|
2842
2592
|
ownsAllDrawItems: filtered.ownsAllDrawItems,
|
|
2593
|
+
worldKeys: scene.worldKeys ?? [],
|
|
2843
2594
|
...(input.occlusion === undefined ? {} : { occlusion: input.occlusion }),
|
|
2844
2595
|
ownsAllShadowCasters,
|
|
2845
2596
|
drawKeys: filtered.drawKeys,
|
|
2846
2597
|
shadowDrawKeys: shadowFiltered.shadowDrawKeys,
|
|
2598
|
+
...(this.surfaceSubmission === undefined
|
|
2599
|
+
? {}
|
|
2600
|
+
: { surfaceSubmission: this.surfaceSubmission }),
|
|
2847
2601
|
get shadowDrawKeysByView() {
|
|
2848
2602
|
return new Map(production.shadowDrawKeysByView);
|
|
2849
2603
|
},
|
|
2850
2604
|
standardPbrFrameResources: {
|
|
2605
|
+
resourceGeneration: viewInspection.resourceGeneration,
|
|
2851
2606
|
materialBindGroups: [],
|
|
2852
2607
|
materialSlotIndicesByEntity: new Map(),
|
|
2853
2608
|
instancesBindGroup: materialRaster.instancesFrameGroup,
|
|
2854
2609
|
materialStride: MATERIAL_PER_ENTITY_STRIDE,
|
|
2855
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
|
+
}),
|
|
2856
2656
|
},
|
|
2857
2657
|
_commitGpuResourceReplacement: () => {
|
|
2858
2658
|
view._commitResourceReplacement();
|
|
@@ -2868,6 +2668,8 @@ export class GpuDrivenProduction {
|
|
|
2868
2668
|
this.lastKnownGoodGeneration = view.inspect().resourceGeneration;
|
|
2869
2669
|
},
|
|
2870
2670
|
_abortResourceReplacement: () => {
|
|
2671
|
+
materialRaster.abortSurfaceDynamicInput();
|
|
2672
|
+
skinRaster?.abortSurfaceDynamicInput();
|
|
2871
2673
|
shadowViewPool._abortResourceReplacement();
|
|
2872
2674
|
materialRaster.abortProjectionBindings();
|
|
2873
2675
|
skinRaster?.abortProjectionBindings();
|
|
@@ -2875,6 +2677,18 @@ export class GpuDrivenProduction {
|
|
|
2875
2677
|
this.submissionState = 'aborted';
|
|
2876
2678
|
this.retryCount += 1;
|
|
2877
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
|
+
}),
|
|
2878
2692
|
projectShadow: (graph, identity) => {
|
|
2879
2693
|
if (!shadowViewPool.isActive(identity) || shadowViewPool.submission(identity) === undefined)
|
|
2880
2694
|
return ok(undefined);
|
|
@@ -2902,25 +2716,75 @@ export class GpuDrivenProduction {
|
|
|
2902
2716
|
),
|
|
2903
2717
|
);
|
|
2904
2718
|
},
|
|
2905
|
-
project: (
|
|
2719
|
+
project: (
|
|
2720
|
+
graph,
|
|
2721
|
+
format,
|
|
2722
|
+
sampleCount,
|
|
2723
|
+
surfaceSubmissionObservation,
|
|
2724
|
+
additionalColorFormats,
|
|
2725
|
+
) => {
|
|
2906
2726
|
this.indirectDrawCount = admittedRasterBatchCount(filtered.plan);
|
|
2907
|
-
const outputs = view.addPasses(graph);
|
|
2727
|
+
const outputs = view.addPasses(graph, 'gpu-driven', true, surfaceSubmissionObservation);
|
|
2908
2728
|
if (!outputs.ok) return outputs;
|
|
2909
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
|
+
}
|
|
2910
2765
|
const standardProjected: Array<{
|
|
2911
2766
|
readonly batch: GpuDrivenBatch;
|
|
2767
|
+
readonly artifact: MaterialShaderArtifact;
|
|
2912
2768
|
readonly mesh: MeshGpuHandles;
|
|
2913
2769
|
readonly materialEntityKey: number;
|
|
2914
2770
|
readonly vertex: GraphBuffer;
|
|
2915
2771
|
readonly index?: GraphBuffer;
|
|
2916
|
-
readonly pipelineFor: (
|
|
2917
|
-
formats: readonly TextureFormat[],
|
|
2918
|
-
) => Result<RenderPipeline, RhiError>;
|
|
2919
2772
|
readonly meshBindGroup: BindGroup;
|
|
2773
|
+
readonly meshProjectionBuffer: Buffer;
|
|
2774
|
+
readonly meshProjectionBytes: number;
|
|
2920
2775
|
readonly raster: MaterialAbiRasterAdapter;
|
|
2921
2776
|
readonly deformation: 'rigid' | 'skin';
|
|
2922
2777
|
readonly clustered: boolean;
|
|
2923
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>;
|
|
2924
2788
|
}> = [];
|
|
2925
2789
|
const standardAccesses: GraphAccess[] = [
|
|
2926
2790
|
{ resource: outputs.value.primitive, usage: 'storage-read' },
|
|
@@ -2929,6 +2793,15 @@ export class GpuDrivenProduction {
|
|
|
2929
2793
|
{ resource: outputs.value.visible, usage: 'storage-read' },
|
|
2930
2794
|
{ resource: outputs.value.transform, usage: 'storage-read' },
|
|
2931
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 }]),
|
|
2932
2805
|
];
|
|
2933
2806
|
const meshBuffers = new Map<
|
|
2934
2807
|
MeshGpuHandles,
|
|
@@ -2955,10 +2828,10 @@ export class GpuDrivenProduction {
|
|
|
2955
2828
|
);
|
|
2956
2829
|
}
|
|
2957
2830
|
// The visible stream is compacted after the persistent visibility
|
|
2958
|
-
// facet applies its active-entity admission. Project
|
|
2959
|
-
// from
|
|
2960
|
-
// batch-local
|
|
2961
|
-
//
|
|
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.
|
|
2962
2835
|
const candidates = admittedCandidates.flatMap((candidate) => {
|
|
2963
2836
|
const slot = projectionSlot(candidate.primitiveIndex);
|
|
2964
2837
|
return slot === undefined ? [] : [sceneCandidate(slot, candidate)];
|
|
@@ -2976,12 +2849,23 @@ export class GpuDrivenProduction {
|
|
|
2976
2849
|
}),
|
|
2977
2850
|
);
|
|
2978
2851
|
}
|
|
2852
|
+
const projectionKey = gpuDrivenProjectionKey(prepared.batch, deformation, artifact);
|
|
2979
2853
|
const meshBindGroup = raster.projectionBindGroup(
|
|
2980
2854
|
projection.value.rows,
|
|
2981
|
-
|
|
2855
|
+
projectionKey,
|
|
2982
2856
|
prepared.batch.candidates.length,
|
|
2983
2857
|
);
|
|
2984
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
|
+
}
|
|
2985
2869
|
const materialSlot = projectionSlot(admittedCandidates[0]?.primitiveIndex ?? -1);
|
|
2986
2870
|
if (materialSlot === undefined) {
|
|
2987
2871
|
return err(
|
|
@@ -3024,8 +2908,18 @@ export class GpuDrivenProduction {
|
|
|
3024
2908
|
standardAccesses.push({ resource: buffers.index, usage: 'index-read' });
|
|
3025
2909
|
}
|
|
3026
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;
|
|
3027
2920
|
const pipelineFor = (
|
|
3028
2921
|
formats: readonly TextureFormat[],
|
|
2922
|
+
selectedArtifact: MaterialShaderArtifact = artifact,
|
|
3029
2923
|
): Result<RenderPipeline, RhiError> => {
|
|
3030
2924
|
return raster.pipeline(
|
|
3031
2925
|
formats[0] ?? format,
|
|
@@ -3035,22 +2929,31 @@ export class GpuDrivenProduction {
|
|
|
3035
2929
|
// Standard count is only a fallback for legacy synthetic
|
|
3036
2930
|
// fixtures; using it for a custom program would add aliases
|
|
3037
2931
|
// the shader never declares (or omit a declared custom set).
|
|
3038
|
-
|
|
2932
|
+
shaderUvSetCount,
|
|
3039
2933
|
prepared.batch.key.topology,
|
|
3040
|
-
|
|
3041
|
-
(prepared.batch.key.topology === 'line-strip' ||
|
|
3042
|
-
prepared.batch.key.topology === 'triangle-strip')
|
|
3043
|
-
? prepared.mesh.indexFormat
|
|
3044
|
-
: undefined,
|
|
2934
|
+
stripIndexFormat,
|
|
3045
2935
|
prepared.renderState,
|
|
3046
|
-
|
|
2936
|
+
selectedArtifact,
|
|
3047
2937
|
formats.slice(1),
|
|
3048
2938
|
);
|
|
3049
2939
|
};
|
|
3050
2940
|
const initialPipeline = pipelineFor([format, ...(additionalColorFormats ?? [])]);
|
|
3051
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
|
+
}
|
|
3052
2954
|
standardProjected.push({
|
|
3053
2955
|
batch: prepared.batch,
|
|
2956
|
+
artifact,
|
|
3054
2957
|
mesh: prepared.mesh,
|
|
3055
2958
|
materialEntityKey: worldEntityKey(
|
|
3056
2959
|
materialSlot.snapshot.worldId,
|
|
@@ -3058,7 +2961,11 @@ export class GpuDrivenProduction {
|
|
|
3058
2961
|
),
|
|
3059
2962
|
...buffers,
|
|
3060
2963
|
pipelineFor,
|
|
2964
|
+
...(nearestPipelineFor === undefined ? {} : { nearestPipelineFor }),
|
|
2965
|
+
...(colorPipelineFor === undefined ? {} : { colorPipelineFor }),
|
|
3061
2966
|
meshBindGroup: meshBindGroup.value,
|
|
2967
|
+
meshProjectionBuffer: meshProjection.buffer,
|
|
2968
|
+
meshProjectionBytes: meshProjection.bindingBytes,
|
|
3062
2969
|
raster,
|
|
3063
2970
|
deformation,
|
|
3064
2971
|
clustered: ['cluster', 'skin-cluster'].includes(
|
|
@@ -3087,6 +2994,8 @@ export class GpuDrivenProduction {
|
|
|
3087
2994
|
pass,
|
|
3088
2995
|
resources: GraphResourceResolver,
|
|
3089
2996
|
frameResources: GpuDrivenStandardPbrFrameResources,
|
|
2997
|
+
filter,
|
|
2998
|
+
fragmentEntryPoint,
|
|
3090
2999
|
) => {
|
|
3091
3000
|
// The shared Standard view BGL carries two dynamic buffers:
|
|
3092
3001
|
// the frame view UBO and the per-frame shadow/light table. GPU
|
|
@@ -3098,6 +3007,18 @@ export class GpuDrivenProduction {
|
|
|
3098
3007
|
let currentVertex: GraphBuffer | undefined;
|
|
3099
3008
|
let currentIndex: GraphBuffer | undefined;
|
|
3100
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
|
+
}
|
|
3101
3022
|
const globalMaterialSlots = frameResources.materialSlotIndicesByEntity.get(
|
|
3102
3023
|
batch.materialEntityKey,
|
|
3103
3024
|
);
|
|
@@ -3118,14 +3039,19 @@ export class GpuDrivenProduction {
|
|
|
3118
3039
|
!frameResources.selectedMaterialSlots.has(globalMaterialSlot)
|
|
3119
3040
|
)
|
|
3120
3041
|
continue;
|
|
3121
|
-
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(
|
|
3122
3049
|
frameResources.colorFormats ?? [format, ...(additionalColorFormats ?? [])],
|
|
3123
3050
|
);
|
|
3124
3051
|
if (!selectedPipeline.ok) throw selectedPipeline.error;
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
currentPipeline = pipeline;
|
|
3052
|
+
if (currentPipeline !== selectedPipeline.value) {
|
|
3053
|
+
pass.setPipeline(selectedPipeline.value);
|
|
3054
|
+
currentPipeline = selectedPipeline.value;
|
|
3129
3055
|
}
|
|
3130
3056
|
const materialBindGroup = frameResources.materialBindGroups[globalMaterialSlot];
|
|
3131
3057
|
if (materialBindGroup === undefined) {
|
|
@@ -3138,21 +3064,30 @@ export class GpuDrivenProduction {
|
|
|
3138
3064
|
pass.setBindGroup(1, materialBindGroup, [
|
|
3139
3065
|
globalMaterialSlot * frameResources.materialStride,
|
|
3140
3066
|
]);
|
|
3141
|
-
const
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
: batch.
|
|
3146
|
-
|
|
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) {
|
|
3147
3077
|
throw new RhiError({
|
|
3148
3078
|
code: 'rhi-not-available',
|
|
3149
|
-
expected: '
|
|
3150
|
-
hint: '
|
|
3079
|
+
expected: 'clustered Standard group(2) BindGroup for GPU-driven batch',
|
|
3080
|
+
hint: 'publish the frame-owned cluster lighting resources before indirect encode',
|
|
3151
3081
|
});
|
|
3152
|
-
|
|
3082
|
+
}
|
|
3083
|
+
pass.setBindGroup(2, group2, batch.deformation === 'skin' ? [0, 0, 0] : [0]);
|
|
3153
3084
|
const visibleWindow = batch.raster.visibleWindowBindGroup(
|
|
3154
3085
|
batch.batch.visibleBase,
|
|
3155
3086
|
batch.batch.visibleCapacity,
|
|
3087
|
+
isSingleLayerMediumArtifact(batch.artifact) ? batch.batch.visibleBase : undefined,
|
|
3088
|
+
isSingleLayerMediumArtifact(batch.artifact)
|
|
3089
|
+
? batch.batch.visibleCapacity
|
|
3090
|
+
: undefined,
|
|
3156
3091
|
);
|
|
3157
3092
|
if (!visibleWindow.ok) throw visibleWindow.error;
|
|
3158
3093
|
pass.setBindGroup(3, visibleWindow.value);
|
|
@@ -3175,6 +3110,29 @@ export class GpuDrivenProduction {
|
|
|
3175
3110
|
} else {
|
|
3176
3111
|
pass.drawIndirect(indirect.value, batch.batch.indirectOffset);
|
|
3177
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
|
+
}
|
|
3178
3136
|
this.encodedIndirectDraws += 1;
|
|
3179
3137
|
}
|
|
3180
3138
|
},
|
|
@@ -3363,7 +3321,7 @@ export class GpuDrivenProduction {
|
|
|
3363
3321
|
batch.candidates.length,
|
|
3364
3322
|
);
|
|
3365
3323
|
if (!meshBindGroup.ok) return meshBindGroup;
|
|
3366
|
-
const visibleBindGroup = raster.
|
|
3324
|
+
const visibleBindGroup = raster.shadowVisibleWindowBindGroupForBuffer(
|
|
3367
3325
|
visibleBuffer,
|
|
3368
3326
|
batch.visibleBase,
|
|
3369
3327
|
batch.visibleCapacity,
|
|
@@ -3443,6 +3401,8 @@ export class GpuDrivenProduction {
|
|
|
3443
3401
|
this.skinRaster?.dispose();
|
|
3444
3402
|
this.skinRaster = undefined;
|
|
3445
3403
|
this.filteredCache = undefined;
|
|
3404
|
+
this.surfaceFrameRangeCache = undefined;
|
|
3405
|
+
this.consumedSurfaceInput = undefined;
|
|
3446
3406
|
this.signatureCache = undefined;
|
|
3447
3407
|
this.validatedResidencyCache = undefined;
|
|
3448
3408
|
this.cpuValidationTelemetryCache = undefined;
|
|
@@ -3457,11 +3417,22 @@ export class GpuDrivenProduction {
|
|
|
3457
3417
|
this.skinPaletteBuffer = undefined;
|
|
3458
3418
|
this.committedPaletteContentRevision = undefined;
|
|
3459
3419
|
this.preparationFailure = undefined;
|
|
3420
|
+
this.surfaceArtifactInspection = undefined;
|
|
3460
3421
|
this.overflowRecoveryRequired = false;
|
|
3461
3422
|
}
|
|
3462
3423
|
|
|
3463
3424
|
inspect(): GpuDrivenProductionInspection {
|
|
3464
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) });
|
|
3465
3436
|
const channels: GpuDrivenLaneSummary[] = [];
|
|
3466
3437
|
const mainTotalDrawCount = this.mainTotalDrawItems;
|
|
3467
3438
|
const mainClaimedDrawCount = this.telemetryOverflow
|
|
@@ -3567,11 +3538,23 @@ export class GpuDrivenProduction {
|
|
|
3567
3538
|
}
|
|
3568
3539
|
}
|
|
3569
3540
|
return {
|
|
3541
|
+
...(this.surfaceArtifactInspection === undefined
|
|
3542
|
+
? {}
|
|
3543
|
+
: { surfaceArtifact: this.surfaceArtifactInspection }),
|
|
3570
3544
|
residencyValidationScans: this.residencyValidationScans,
|
|
3571
3545
|
residencyValidationCacheHits: this.residencyValidationCacheHits,
|
|
3572
3546
|
cpuValidationScans: this.cpuValidationScans,
|
|
3573
3547
|
cpuValidationCacheHits: this.cpuValidationCacheHits,
|
|
3574
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,
|
|
3575
3558
|
gpuOwnedSnapshotsMaterialized: this.gpuOwnedSnapshotsMaterialized,
|
|
3576
3559
|
filteredPlanBuilds: this.filteredPlanBuilds,
|
|
3577
3560
|
gpuOwnedEntityCount: this.gpuOwnedEntityCount,
|
|
@@ -3602,6 +3585,23 @@ export class GpuDrivenProduction {
|
|
|
3602
3585
|
recoveryCount: this.recoveryCount,
|
|
3603
3586
|
retryCount: this.retryCount,
|
|
3604
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
|
+
}),
|
|
3605
3605
|
};
|
|
3606
3606
|
}
|
|
3607
3607
|
|
|
@@ -3638,11 +3638,53 @@ export class GpuDrivenProduction {
|
|
|
3638
3638
|
this.laneReason = 'overflow';
|
|
3639
3639
|
}
|
|
3640
3640
|
}
|
|
3641
|
-
if (selection === undefined
|
|
3641
|
+
if (selection === undefined) return selection;
|
|
3642
3642
|
if (filtered === undefined) return selection;
|
|
3643
|
-
|
|
3644
|
-
|
|
3643
|
+
type LodBatchAttribution = {
|
|
3644
|
+
readonly batch: GpuDrivenBatch;
|
|
3645
|
+
readonly worldKey: number;
|
|
3646
|
+
readonly primitiveSlot: number;
|
|
3647
|
+
readonly slotGeneration: number;
|
|
3648
|
+
readonly artifact?: MaterialShaderArtifact;
|
|
3649
|
+
};
|
|
3650
|
+
const preparedByBatchId = new Map<number, LodBatchAttribution>(
|
|
3651
|
+
filtered.batches.map((prepared) => [
|
|
3652
|
+
prepared.batch.batchId,
|
|
3653
|
+
{
|
|
3654
|
+
batch: prepared.batch,
|
|
3655
|
+
worldKey: prepared.worldKey,
|
|
3656
|
+
primitiveSlot: prepared.primitiveSlot,
|
|
3657
|
+
slotGeneration: prepared.slotGeneration,
|
|
3658
|
+
artifact: prepared.artifact,
|
|
3659
|
+
},
|
|
3660
|
+
]),
|
|
3645
3661
|
);
|
|
3662
|
+
const projectedCandidateCount =
|
|
3663
|
+
telemetryCandidateCount > 0 ? telemetryCandidateCount : selection.candidateCount;
|
|
3664
|
+
// Selector-only batches remain in filtered.plan so their GPU counters
|
|
3665
|
+
// can describe candidates suppressed from the raster admission lane.
|
|
3666
|
+
// They have no PreparedBatch entry because no draw is encoded, but they
|
|
3667
|
+
// still need the same World join for receipt attribution. Recover that
|
|
3668
|
+
// metadata from the stable primitive slot instead of dropping the whole
|
|
3669
|
+
// readback when the first such batch is encountered.
|
|
3670
|
+
const filteredSlots = this.filteredCache?.slots;
|
|
3671
|
+
const filteredWorldKeys = this.filteredCache?.worldKeys;
|
|
3672
|
+
if (filteredSlots !== undefined) {
|
|
3673
|
+
const slotByPrimitive = new Map(filteredSlots.map((slot) => [slot.slot, slot] as const));
|
|
3674
|
+
for (const batch of filtered.plan.batches) {
|
|
3675
|
+
if (preparedByBatchId.has(batch.batchId)) continue;
|
|
3676
|
+
const candidate = batch.candidates[0];
|
|
3677
|
+
const slot =
|
|
3678
|
+
candidate === undefined ? undefined : slotByPrimitive.get(candidate.primitiveIndex);
|
|
3679
|
+
if (slot === undefined) continue;
|
|
3680
|
+
preparedByBatchId.set(batch.batchId, {
|
|
3681
|
+
batch,
|
|
3682
|
+
worldKey: filteredWorldKeys?.[slot.snapshot.worldId] ?? slot.snapshot.worldId,
|
|
3683
|
+
primitiveSlot: slot.slot,
|
|
3684
|
+
slotGeneration: slot.generation,
|
|
3685
|
+
});
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3646
3688
|
const worldSelections = new Map<
|
|
3647
3689
|
number,
|
|
3648
3690
|
{
|
|
@@ -3655,9 +3697,36 @@ export class GpuDrivenProduction {
|
|
|
3655
3697
|
readonly lodHistogram: Map<number, number>;
|
|
3656
3698
|
}
|
|
3657
3699
|
>();
|
|
3700
|
+
const lodHistogram = new Map<number, number>();
|
|
3701
|
+
// The selector visits the full captured plan, including candidates that
|
|
3702
|
+
// the CPU visibility projection suppressed. Use that same plan for the
|
|
3703
|
+
// LOD denominator so World attribution cannot fail merely because the
|
|
3704
|
+
// two lanes have different frustum scopes.
|
|
3705
|
+
let lodCandidateCount = 0;
|
|
3706
|
+
let lodVisible = 0;
|
|
3658
3707
|
for (const batchSelection of selection.batches) {
|
|
3659
3708
|
const prepared = preparedByBatchId.get(batchSelection.batchId);
|
|
3660
|
-
|
|
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;
|
|
3713
|
+
// The GPU selector also visits ordinary GPU-driven meshes so one
|
|
3714
|
+
// compact compute path can populate their indirect args. Those rows
|
|
3715
|
+
// are level-0 by construction, but they are not LOD candidates and
|
|
3716
|
+
// must not enter receipt-bound LOD attribution. Batch topology keeps
|
|
3717
|
+
// LOD coverage/ranges in the grouping key, so a batch is expected to
|
|
3718
|
+
// be homogeneous; fail closed if a producer violates that invariant
|
|
3719
|
+
// instead of guessing a split for aggregate counters.
|
|
3720
|
+
const lodCandidateRows = prepared.batch.candidates.filter(
|
|
3721
|
+
(candidate) => (candidate.lodCoverages?.length ?? 0) > 1,
|
|
3722
|
+
);
|
|
3723
|
+
if (lodCandidateRows.length === 0) continue;
|
|
3724
|
+
if (lodCandidateRows.length !== batchSelection.candidateCount) return undefined;
|
|
3725
|
+
lodCandidateCount += batchSelection.candidateCount;
|
|
3726
|
+
lodVisible += batchSelection.visible;
|
|
3727
|
+
for (const row of batchSelection.lodHistogram) {
|
|
3728
|
+
lodHistogram.set(row.level, (lodHistogram.get(row.level) ?? 0) + row.count);
|
|
3729
|
+
}
|
|
3661
3730
|
let aggregate = worldSelections.get(prepared.worldKey);
|
|
3662
3731
|
if (aggregate === undefined) {
|
|
3663
3732
|
aggregate = {
|
|
@@ -3683,8 +3752,50 @@ export class GpuDrivenProduction {
|
|
|
3683
3752
|
}
|
|
3684
3753
|
return {
|
|
3685
3754
|
...selection,
|
|
3686
|
-
candidateCount:
|
|
3687
|
-
|
|
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
|
+
),
|
|
3762
|
+
lodHistogram: Object.freeze(
|
|
3763
|
+
[...lodHistogram.entries()]
|
|
3764
|
+
.sort(([left], [right]) => left - right)
|
|
3765
|
+
.map(([level, count]) => Object.freeze({ level, count })),
|
|
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
|
+
),
|
|
3688
3799
|
worldSelections: Object.freeze(
|
|
3689
3800
|
[...worldSelections.values()].map((aggregate) =>
|
|
3690
3801
|
Object.freeze({
|
|
@@ -3710,6 +3821,7 @@ export class GpuDrivenProduction {
|
|
|
3710
3821
|
validated: readonly { readonly source: RenderableSnapshot }[],
|
|
3711
3822
|
gpuOwnedDrawKeys: ReadonlySet<string>,
|
|
3712
3823
|
cacheKey?: string,
|
|
3824
|
+
worldKeys?: readonly number[],
|
|
3713
3825
|
): void {
|
|
3714
3826
|
const cached = this.cpuValidationTelemetryCache;
|
|
3715
3827
|
if (
|
|
@@ -3717,7 +3829,8 @@ export class GpuDrivenProduction {
|
|
|
3717
3829
|
cached !== undefined &&
|
|
3718
3830
|
cached.cacheKey === cacheKey &&
|
|
3719
3831
|
cached.validated === validated &&
|
|
3720
|
-
cached.gpuOwnedDrawKeys === gpuOwnedDrawKeys
|
|
3832
|
+
cached.gpuOwnedDrawKeys === gpuOwnedDrawKeys &&
|
|
3833
|
+
cached.worldKeys === worldKeys
|
|
3721
3834
|
) {
|
|
3722
3835
|
this.applyCpuValidationTelemetry(cached);
|
|
3723
3836
|
this.cpuValidationCacheHits += 1;
|
|
@@ -3755,7 +3868,10 @@ export class GpuDrivenProduction {
|
|
|
3755
3868
|
for (const [compactIndex, draw] of draws.entries()) {
|
|
3756
3869
|
const material = row.source.materials[draw.materialSlot] ?? row.source.material;
|
|
3757
3870
|
const key = gpuDrivenDrawKey(
|
|
3758
|
-
worldEntityKey(
|
|
3871
|
+
worldEntityKey(
|
|
3872
|
+
worldKeys?.[row.source.worldId] ?? row.source.worldId,
|
|
3873
|
+
row.source.entityKey,
|
|
3874
|
+
),
|
|
3759
3875
|
material.materialHandle ?? -1,
|
|
3760
3876
|
gpuDrivenSourceDrawItemIndex(draw, compactIndex),
|
|
3761
3877
|
);
|
|
@@ -3800,6 +3916,7 @@ export class GpuDrivenProduction {
|
|
|
3800
3916
|
cacheKey,
|
|
3801
3917
|
validated,
|
|
3802
3918
|
gpuOwnedDrawKeys,
|
|
3919
|
+
worldKeys,
|
|
3803
3920
|
gpuOwnedDrawCount: this.gpuOwnedDrawItems,
|
|
3804
3921
|
totalDrawItems: this.mainTotalDrawItems,
|
|
3805
3922
|
gpuOwnedRows: this.validatedGpuOwnedRows,
|