@forgeax/engine-render 0.1.33 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1062 -127
- package/dist/assembly/backend-contract.d.ts +2 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -1
- package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
- package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
- package/dist/assembly/canvas-draw-guard.d.ts +3 -0
- package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
- package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
- package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
- package/dist/assembly/device-feature-admission.d.ts +2 -2
- package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
- package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
- package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
- package/dist/assembly/factory.d.ts +1 -1
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +6 -5
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
- package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
- package/dist/assembly/material-shader-policy.d.ts +16 -2
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
- package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
- package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/render-feature-post-processes.d.ts +6 -0
- package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
- package/dist/assembly/render-target-host.d.ts +5 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
- package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
- package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-inspection.d.ts +11 -0
- package/dist/assembly/renderer-inspection.d.ts.map +1 -1
- package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
- package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +1 -1
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
- package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +2 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/authoring.mjs +2 -4
- package/dist/authoring.mjs.map +1 -1
- package/dist/barrel-distortion.d.ts +47 -0
- package/dist/barrel-distortion.d.ts.map +1 -0
- package/dist/chunk-3XUBWRY2.mjs +1842 -0
- package/dist/chunk-3XUBWRY2.mjs.map +1 -0
- package/dist/{chunk-OO3DRYK7.mjs → chunk-G6HDNGY7.mjs} +2400 -1767
- package/dist/chunk-G6HDNGY7.mjs.map +1 -0
- package/dist/{chunk-ZDQWGQW7.mjs → chunk-LM5IE7GF.mjs} +4 -4
- package/dist/{chunk-ZDQWGQW7.mjs.map → chunk-LM5IE7GF.mjs.map} +1 -1
- package/dist/{chunk-VOZWDM3L.mjs → chunk-LNIUIKQ7.mjs} +1514 -498
- package/dist/chunk-LNIUIKQ7.mjs.map +1 -0
- package/dist/chunk-MXTWKZO4.mjs +3268 -0
- package/dist/chunk-MXTWKZO4.mjs.map +1 -0
- package/dist/chunk-MZ4C7EHF.mjs +6942 -0
- package/dist/chunk-MZ4C7EHF.mjs.map +1 -0
- package/dist/chunk-RIPAEGI6.mjs +48 -0
- package/dist/chunk-RIPAEGI6.mjs.map +1 -0
- package/dist/{chunk-LEAXOMYJ.mjs → chunk-SGPUJW3F.mjs} +117 -5
- package/dist/chunk-SGPUJW3F.mjs.map +1 -0
- package/dist/{chunk-PA5DRJNI.mjs → chunk-X2KA6WHM.mjs} +29 -4
- package/dist/chunk-X2KA6WHM.mjs.map +1 -0
- package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
- package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
- package/dist/cloud/density.d.ts +55 -0
- package/dist/cloud/density.d.ts.map +1 -0
- package/dist/cloud/extract.d.ts +25 -0
- package/dist/cloud/extract.d.ts.map +1 -0
- package/dist/cloud/feature.d.ts +57 -0
- package/dist/cloud/feature.d.ts.map +1 -0
- package/dist/cloud/frame-context.d.ts +8 -0
- package/dist/cloud/frame-context.d.ts.map +1 -0
- package/dist/cloud/inspection.d.ts +59 -0
- package/dist/cloud/inspection.d.ts.map +1 -0
- package/dist/cloud/optics.d.ts +56 -0
- package/dist/cloud/optics.d.ts.map +1 -0
- package/dist/cloud/parameters.d.ts +73 -0
- package/dist/cloud/parameters.d.ts.map +1 -0
- package/dist/cloud/resources.d.ts +60 -0
- package/dist/cloud/resources.d.ts.map +1 -0
- package/dist/cloud/shadow.d.ts +42 -0
- package/dist/cloud/shadow.d.ts.map +1 -0
- package/dist/cloud/temporal.d.ts +74 -0
- package/dist/cloud/temporal.d.ts.map +1 -0
- package/dist/components/barrel-distortion.d.ts +13 -0
- package/dist/components/barrel-distortion.d.ts.map +1 -0
- package/dist/components/cloud-layer.d.ts +29 -0
- package/dist/components/cloud-layer.d.ts.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/instances.d.ts +20 -6
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/construct-renderer.d.ts +1 -1
- package/dist/construct-renderer.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +8406 -5785
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +19 -19
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/dynamic-geometry.d.ts +5 -1
- package/dist/dynamic-geometry.d.ts.map +1 -1
- package/dist/errors/cloud.d.ts +59 -0
- package/dist/errors/cloud.d.ts.map +1 -0
- package/dist/errors/render.d.ts +20 -5
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/examples/single-layer-medium-surface.d.ts +47 -0
- package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +5 -1
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/extract/material-context.d.ts +7 -0
- package/dist/extract/material-context.d.ts.map +1 -0
- package/dist/features/barrel-distortion.d.ts +29 -0
- package/dist/features/barrel-distortion.d.ts.map +1 -0
- package/dist/features/error-reporting.d.ts +6 -2
- package/dist/features/error-reporting.d.ts.map +1 -1
- package/dist/features/host.d.ts +15 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/plan.d.ts +10 -0
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts +5 -0
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/features/render-graph-raster.d.ts +1 -0
- package/dist/features/render-graph-raster.d.ts.map +1 -1
- package/dist/features/targets.d.ts +2 -0
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +48 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +15 -7
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/glyph-text-layout-system.d.ts +1 -1
- package/dist/glyph-text-layout-system.d.ts.map +1 -1
- package/dist/gpu-dirty-ranges.d.ts +11 -0
- package/dist/gpu-dirty-ranges.d.ts.map +1 -0
- package/dist/gpu-driven/batch-topology.d.ts +17 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/material-artifacts.d.ts +6 -0
- package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster-material.d.ts +178 -0
- package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +47 -3
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/resource-allocation.d.ts +31 -0
- package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
- package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
- package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
- package/dist/gpu-driven/view-gpu.d.ts +26 -2
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +5 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +2 -2
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/frame-resources.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +14 -5
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +30 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +780 -9
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +135 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instance-buffer-cache.d.ts +4 -4
- package/dist/instance-buffer-cache.d.ts.map +1 -1
- package/dist/instances.d.ts +47 -100
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +7 -8
- package/dist/internal.mjs.map +1 -1
- package/dist/pbr-pipeline.d.ts +22 -4
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
- package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
- package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
- package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
- package/dist/pipeline/standard-output/resources.d.ts +2 -2
- package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
- package/dist/pipeline/standard-output/types.d.ts +4 -2
- package/dist/pipeline/standard-output/types.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +1 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
- package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
- package/dist/publication/contract.d.ts +77 -0
- package/dist/publication/contract.d.ts.map +1 -0
- package/dist/publication/dependencies.d.ts +7 -0
- package/dist/publication/dependencies.d.ts.map +1 -0
- package/dist/publication/prepare-geometry.d.ts +9 -0
- package/dist/publication/prepare-geometry.d.ts.map +1 -0
- package/dist/publication/programs.d.ts +5 -0
- package/dist/publication/programs.d.ts.map +1 -0
- package/dist/publication/publisher.d.ts +33 -0
- package/dist/publication/publisher.d.ts.map +1 -0
- package/dist/publication/receiver.d.ts +29 -0
- package/dist/publication/receiver.d.ts.map +1 -0
- package/dist/publication/resource-scope.d.ts +18 -0
- package/dist/publication/resource-scope.d.ts.map +1 -0
- package/dist/publication/targets.d.ts +33 -0
- package/dist/publication/targets.d.ts.map +1 -0
- package/dist/publication/video.d.ts +5 -0
- package/dist/publication/video.d.ts.map +1 -0
- package/dist/record/barrel-distortion-frame.d.ts +15 -0
- package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
- package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
- package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
- package/dist/record/fold-buckets.d.ts +95 -0
- package/dist/record/fold-buckets.d.ts.map +1 -0
- package/dist/record/fold-instance-buffer.d.ts +6 -0
- package/dist/record/fold-instance-buffer.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +1 -0
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +43 -9
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts +15 -65
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-draw-receipts.d.ts +11 -0
- package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
- package/dist/record/gpu-timing.d.ts +8 -0
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/helpers.d.ts +2 -2
- package/dist/record/helpers.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +22 -6
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +14 -10
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +3 -16
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +13 -2
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/motion-blur-receipt.d.ts +11 -0
- package/dist/record/motion-blur-receipt.d.ts.map +1 -0
- package/dist/record/prepared-material-bindings.d.ts +3 -3
- package/dist/record/prepared-material-bindings.d.ts.map +1 -1
- package/dist/record/probe-blend-buffer.d.ts +16 -0
- package/dist/record/probe-blend-buffer.d.ts.map +1 -0
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +24 -5
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +2 -0
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/sprite-instance-buffer.d.ts +31 -0
- package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
- package/dist/record/target-capture-graph.d.ts +19 -0
- package/dist/record/target-capture-graph.d.ts.map +1 -0
- package/dist/record/typed-frame-graph.d.ts +24 -15
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +11 -3
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/record/volume-params.d.ts +9 -0
- package/dist/record/volume-params.d.ts.map +1 -0
- package/dist/recovery/render-system-candidate.d.ts +14 -4
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +46 -5
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +103 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +3 -2
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +18 -9
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system-presentation.d.ts +23 -0
- package/dist/render-system-presentation.d.ts.map +1 -0
- package/dist/render-system-producer-roots.d.ts +10 -0
- package/dist/render-system-producer-roots.d.ts.map +1 -0
- package/dist/render-system-projections.d.ts +3 -2
- package/dist/render-system-projections.d.ts.map +1 -1
- package/dist/render-system-residency.d.ts +12 -0
- package/dist/render-system-residency.d.ts.map +1 -0
- package/dist/render-system.d.ts +22 -21
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/probe-blend.d.ts +27 -0
- package/dist/scene/probe-blend.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +18 -7
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/render-source.d.ts +42 -0
- package/dist/scene/render-source.d.ts.map +1 -0
- package/dist/scene/source-systems.d.ts +6 -0
- package/dist/scene/source-systems.d.ts.map +1 -0
- package/dist/ssao-buffers.d.ts +9 -0
- package/dist/ssao-buffers.d.ts.map +1 -1
- package/dist/surface/dynamic-input.d.ts +133 -0
- package/dist/surface/dynamic-input.d.ts.map +1 -0
- package/dist/surface/gpu-driven.d.ts +41 -0
- package/dist/surface/gpu-driven.d.ts.map +1 -0
- package/dist/surface/submission-observation.d.ts +136 -0
- package/dist/surface/submission-observation.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/systems/skin-palette-types.d.ts +10 -0
- package/dist/systems/skin-palette-types.d.ts.map +1 -1
- package/dist/systems/transparent-dispatch.d.ts +2 -2
- package/dist/systems/transparent-dispatch.d.ts.map +1 -1
- package/dist/systems/transparent-sort-config.d.ts +2 -1
- package/dist/systems/transparent-sort-config.d.ts.map +1 -1
- package/dist/temporal/gpu.d.ts +13 -1
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -7
- package/dist/temporal/inspection.d.ts.map +1 -1
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
- package/dist/transmission/single-layer-medium.d.ts +89 -0
- package/dist/transmission/single-layer-medium.d.ts.map +1 -0
- package/dist/typed-render-graph-primitives.d.ts +14 -2
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/component.d.ts +9 -4
- package/dist/volume/component.d.ts.map +1 -1
- package/dist/volume/extract.d.ts +1 -1
- package/dist/volume/extract.d.ts.map +1 -1
- package/dist/volume/inspection.d.ts +2 -0
- package/dist/volume/inspection.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +4 -1
- package/dist/volume/passes.d.ts.map +1 -1
- package/dist/volume/resources.d.ts.map +1 -1
- package/package.json +22 -21
- package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
- package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
- package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
- package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
- package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
- package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
- package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
- package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
- package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
- package/src/__tests__/factory-contract.integration.test.ts +295 -45
- package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
- package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
- package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +5 -6
- package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +6 -5
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +94 -6
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
- package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
- package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
- package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
- package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
- package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
- package/src/__tests__/gpu-scene.unit.test.ts +119 -9
- package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
- package/src/__tests__/instances-culling.integration.test.ts +10 -36
- package/src/__tests__/instances-store.unit.test.ts +97 -305
- package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
- package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +145 -0
- package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
- package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
- package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
- package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
- package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
- package/src/__tests__/motion-blur-compute.dawn.test.ts +13 -7
- package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
- package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
- package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +66 -8
- package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
- package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
- package/src/__tests__/probe-bindings.unit.test.ts +582 -1
- package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
- package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +8 -0
- package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
- package/src/__tests__/render-feature-error-reporting.unit.test.ts +21 -1
- package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
- package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
- package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
- package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +20 -20
- package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
- package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
- package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +42 -8
- package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +26 -10
- package/src/__tests__/resource-allocation.unit.test.ts +123 -0
- package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
- package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
- package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +15 -0
- package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
- package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
- package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
- package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
- package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
- package/src/__tests__/standard-pipeline.integration.test.ts +182 -1
- package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
- package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
- package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
- package/src/__tests__/temporal-performance.integration.test.ts +3 -3
- package/src/__tests__/visibility-instances.integration.test.ts +7 -19
- package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
- package/src/__tests__/volume-optics-oracle.unit.test.ts +4 -2
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +30 -20
- package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
- package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
- package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +113 -9
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +5 -3
- package/src/assembly/backend-contract.ts +30 -0
- package/src/assembly/barrel-distortion-inspection.ts +22 -0
- package/src/assembly/canvas-draw-guard.ts +11 -0
- package/src/assembly/cloud-shadow-entry.ts +22 -0
- package/src/assembly/device-feature-admission.ts +2 -2
- package/src/assembly/dynamic-geometry-host.ts +65 -37
- package/src/assembly/dynamic-geometry-runtime.ts +7 -1
- package/src/assembly/factory.ts +2 -30
- package/src/assembly/host-contract.ts +8 -5
- package/src/assembly/material/assembly.ts +25 -13
- package/src/assembly/material/pipeline-helpers.ts +53 -12
- package/src/assembly/material-shader-policy.ts +55 -0
- package/src/assembly/material-shader-prewarm.ts +21 -10
- package/src/assembly/recovery/device-loss-fanout.ts +13 -0
- package/src/assembly/recovery/renderer-recover.ts +50 -18
- package/src/assembly/render-feature-post-processes.ts +54 -0
- package/src/assembly/render-target-host.ts +57 -47
- package/src/assembly/renderer-facade.ts +7 -2
- package/src/assembly/renderer-feature-inspection.ts +8 -2
- package/src/assembly/renderer-frame-transaction.ts +23 -0
- package/src/assembly/renderer-inspection.ts +28 -0
- package/src/assembly/skin-palette-owner.ts +1 -1
- package/src/assembly/temporal-post-process-prewarm.ts +3 -3
- package/src/assembly/webgpu-pbr-ready.ts +9 -1
- package/src/assembly/webgpu-ready-per-pass.ts +104 -0
- package/src/assembly/webgpu-ready.ts +116 -62
- package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
- package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
- package/src/assembly/webgpu-renderer.ts +303 -313
- package/src/assembly/webgpu-vertex-layouts.ts +1 -1
- package/src/barrel-distortion.ts +247 -0
- package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
- package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
- package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
- package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
- package/src/cloud/density.ts +600 -0
- package/src/cloud/extract.ts +110 -0
- package/src/cloud/feature.ts +1810 -0
- package/src/cloud/frame-context.ts +42 -0
- package/src/cloud/inspection.ts +130 -0
- package/src/cloud/optics.ts +285 -0
- package/src/cloud/parameters.ts +255 -0
- package/src/cloud/resources.ts +122 -0
- package/src/cloud/shadow.ts +167 -0
- package/src/cloud/temporal.ts +226 -0
- package/src/components/barrel-distortion.ts +46 -0
- package/src/components/cloud-layer.ts +47 -0
- package/src/components/index.ts +2 -0
- package/src/components/instances.ts +18 -45
- package/src/components/sprite-instances.ts +1 -1
- package/src/construct-renderer.ts +1 -1
- package/src/device/gpu-residency.ts +26 -25
- package/src/dynamic-geometry.ts +12 -1
- package/src/errors/cloud.ts +103 -0
- package/src/errors/render.ts +47 -8
- package/src/examples/single-layer-medium-surface.ts +59 -0
- package/src/extract/camera.ts +15 -1
- package/src/extract/contracts.ts +5 -0
- package/src/extract/material-context.ts +21 -0
- package/src/features/barrel-distortion.ts +172 -0
- package/src/features/error-reporting.ts +26 -6
- package/src/features/host.ts +128 -19
- package/src/features/plan.ts +10 -0
- package/src/features/render-graph-contribution.ts +9 -0
- package/src/features/render-graph-raster.ts +45 -3
- package/src/features/targets.ts +66 -0
- package/src/features/types.ts +51 -0
- package/src/fullscreen-post-process-pass.ts +41 -9
- package/src/glyph-text-layout-system.ts +2 -2
- package/src/gpu-dirty-ranges.ts +48 -0
- package/src/gpu-driven/batch-topology.ts +53 -0
- package/src/gpu-driven/material-artifacts.ts +33 -25
- package/src/gpu-driven/prepared-draw.ts +12 -4
- package/src/gpu-driven/production-raster-material.ts +1894 -0
- package/src/gpu-driven/production-raster.ts +825 -784
- package/src/gpu-driven/resource-allocation.ts +142 -0
- package/src/gpu-driven/shadow-ownership.ts +71 -0
- package/src/gpu-driven/view-gpu.ts +164 -10
- package/src/gpu-scene.ts +75 -84
- package/src/hdrp-buffers.ts +4 -3
- package/src/ibl/frame-resources.ts +0 -1
- package/src/ibl/skylight-bind-group.ts +62 -32
- package/src/index.ts +205 -9
- package/src/inspection-types.ts +162 -0
- package/src/instance-buffer-cache.ts +4 -4
- package/src/instances.ts +152 -569
- package/src/pbr-pipeline.ts +230 -30
- package/src/pipeline/single-layer-medium-passes.ts +267 -0
- package/src/pipeline/standard-forward-lane.ts +313 -57
- package/src/pipeline/standard-output/graph.ts +8 -2
- package/src/pipeline/standard-output/lut-gpu.ts +3 -3
- package/src/pipeline/standard-output/resources.ts +2 -2
- package/src/pipeline/standard-output/types.ts +12 -1
- package/src/pipeline/standard-pipeline.ts +291 -18
- package/src/pipeline/standard-post.ts +90 -24
- package/src/pipeline-builder.ts +1 -1
- package/src/plugin.ts +4 -0
- package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
- package/src/prepare/prepared-graphics-resolver.ts +2 -0
- package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
- package/src/publication/__tests__/features.integration.test.ts +122 -0
- package/src/publication/__tests__/programs.unit.test.ts +25 -0
- package/src/publication/__tests__/publication.integration.test.ts +370 -0
- package/src/publication/__tests__/targets.integration.test.ts +120 -0
- package/src/publication/__tests__/video.browser.test.ts +50 -0
- package/src/publication/contract.ts +104 -0
- package/src/publication/dependencies.ts +54 -0
- package/src/publication/prepare-geometry.ts +94 -0
- package/src/publication/programs.ts +45 -0
- package/src/publication/publisher.ts +618 -0
- package/src/publication/receiver.ts +297 -0
- package/src/publication/resource-scope.ts +31 -0
- package/src/publication/targets.ts +182 -0
- package/src/publication/video.ts +33 -0
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
- package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +9 -11
- package/src/record/__tests__/temporal-payload.unit.test.ts +1 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
- package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
- package/src/record/barrel-distortion-frame.ts +110 -0
- package/src/record/dynamic-geometry-consumption.ts +39 -9
- package/src/record/fold-buckets.ts +855 -0
- package/src/record/fold-instance-buffer.ts +51 -0
- package/src/record/frame-lighting.ts +36 -2
- package/src/record/frame-snapshot.ts +45 -9
- package/src/record/frame.ts +258 -1105
- package/src/record/gpu-draw-receipts.ts +38 -0
- package/src/record/gpu-timing.ts +48 -5
- package/src/record/helpers.ts +2 -2
- package/src/record/main-pass-geometry.ts +272 -84
- package/src/record/main-pass-material.ts +102 -36
- package/src/record/main-pass-sprite-draws.ts +13 -217
- package/src/record/main-pass.ts +164 -27
- package/src/record/motion-blur-receipt.ts +91 -0
- package/src/record/prepared-material-bindings.ts +11 -6
- package/src/record/probe-blend-buffer.ts +180 -0
- package/src/record/recovery-pipeline.ts +35 -3
- package/src/record/render-context.ts +41 -5
- package/src/record/shadow-pass.ts +30 -4
- package/src/record/skybox-post-pass.ts +28 -4
- package/src/record/sprite-instance-buffer.ts +155 -0
- package/src/record/target-capture-graph.ts +258 -0
- package/src/record/typed-frame-graph.ts +776 -340
- package/src/record/view-ubo.ts +27 -4
- package/src/record/volume-params.ts +225 -0
- package/src/recovery/render-system-candidate.ts +243 -50
- package/src/reflection/record-owner.ts +7 -3
- package/src/render-contract.ts +54 -5
- package/src/render-graph-primitives.ts +7 -1
- package/src/render-pipeline.ts +130 -0
- package/src/render-system-extract-tail.ts +90 -78
- package/src/render-system-extract.ts +76 -76
- package/src/render-system-presentation.ts +56 -0
- package/src/render-system-producer-roots.ts +96 -0
- package/src/render-system-projections.ts +3 -2
- package/src/render-system-residency.ts +159 -0
- package/src/render-system.ts +421 -408
- package/src/scene/probe-blend.ts +96 -4
- package/src/scene/render-scene.ts +416 -337
- package/src/scene/render-source.ts +90 -0
- package/src/scene/source-systems.ts +33 -0
- package/src/ssao-buffers.ts +95 -2
- package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
- package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
- package/src/surface/dynamic-input.ts +658 -0
- package/src/surface/gpu-driven.ts +147 -0
- package/src/surface/submission-observation.ts +517 -0
- package/src/systems/skin-palette-allocator.ts +5 -1
- package/src/systems/skin-palette-types.ts +11 -0
- package/src/systems/transparent-dispatch.ts +2 -2
- package/src/systems/transparent-sort-config.ts +3 -1
- package/src/temporal/gpu.ts +101 -17
- package/src/temporal/inspection.ts +7 -1
- package/src/temporal/standard-scene-data.ts +5 -0
- package/src/temporal/target-coverage-attachment.ts +4 -0
- package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
- package/src/transmission/single-layer-medium.ts +322 -0
- package/src/typed-render-graph-primitives.ts +70 -2
- package/src/volume/component.ts +10 -9
- package/src/volume/extract.ts +11 -5
- package/src/volume/inspection.ts +3 -0
- package/src/volume/passes.ts +87 -4
- package/src/volume/resources.ts +2 -1
- package/dist/chunk-3RTCJTH4.mjs +0 -1610
- package/dist/chunk-3RTCJTH4.mjs.map +0 -1
- package/dist/chunk-BLXGONR2.mjs.map +0 -1
- package/dist/chunk-ILQ5MEZF.mjs +0 -32
- package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
- package/dist/chunk-LEAXOMYJ.mjs.map +0 -1
- package/dist/chunk-NB2S7ZRX.mjs +0 -1783
- package/dist/chunk-NB2S7ZRX.mjs.map +0 -1
- package/dist/chunk-NNQL7CB3.mjs +0 -1713
- package/dist/chunk-NNQL7CB3.mjs.map +0 -1
- package/dist/chunk-OO3DRYK7.mjs.map +0 -1
- package/dist/chunk-PA5DRJNI.mjs.map +0 -1
- package/dist/chunk-PLAXOOX5.mjs +0 -1907
- package/dist/chunk-PLAXOOX5.mjs.map +0 -1
- package/dist/chunk-RXZ3RGOU.mjs +0 -53
- package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
- package/dist/chunk-VOZWDM3L.mjs.map +0 -1
|
@@ -0,0 +1,1894 @@
|
|
|
1
|
+
import { deriveVertexBufferLayoutFromProjection } from '@forgeax/engine-geometry';
|
|
2
|
+
import type {
|
|
3
|
+
BindGroup,
|
|
4
|
+
BindGroupLayout,
|
|
5
|
+
Buffer,
|
|
6
|
+
RenderPipeline,
|
|
7
|
+
Result,
|
|
8
|
+
RhiDevice,
|
|
9
|
+
TextureFormat,
|
|
10
|
+
} from '@forgeax/engine-rhi';
|
|
11
|
+
import { err, ok, RhiError } from '@forgeax/engine-rhi';
|
|
12
|
+
import type { MaterialShaderArtifact } from '@forgeax/engine-shader';
|
|
13
|
+
import type { MaterialDynamicInputLayout, MaterialRenderState } from '@forgeax/engine-types';
|
|
14
|
+
import type { MeshGpuHandles } from '../device/gpu-residency';
|
|
15
|
+
import { GpuDrivenPreparationError } from '../errors/gpu-driven';
|
|
16
|
+
import { GPU_SCENE_LAYOUTS } from '../gpu-scene-schema';
|
|
17
|
+
import {
|
|
18
|
+
GPU_BUFFER_USAGE_COPY_DST,
|
|
19
|
+
GPU_BUFFER_USAGE_STORAGE,
|
|
20
|
+
GPU_BUFFER_USAGE_UNIFORM,
|
|
21
|
+
} from '../gpu-usage';
|
|
22
|
+
import { isCanonicalStandardPbrMaterialShader, isStandardPbrMaterialShader } from '../pbr-pipeline';
|
|
23
|
+
import {
|
|
24
|
+
MESH_PER_ENTITY_STRIDE,
|
|
25
|
+
MESH_SSBO_BYTES,
|
|
26
|
+
packInstanceStorageBuffer,
|
|
27
|
+
} from '../record/mesh-ssbo';
|
|
28
|
+
import type { PipelineState, RenderSystemInternals } from '../record/render-context';
|
|
29
|
+
import { PROBE_BLEND_RECORD_BYTE_SIZE } from '../scene/probe-blend-record';
|
|
30
|
+
import type {
|
|
31
|
+
DynamicInputRange,
|
|
32
|
+
DynamicInputUploadReceipt,
|
|
33
|
+
ReadonlyDynamicInputPage,
|
|
34
|
+
SurfaceDynamicInputFrame,
|
|
35
|
+
} from '../surface/dynamic-input';
|
|
36
|
+
import type { GpuDrivenBatch } from './batch-topology';
|
|
37
|
+
import { requiresProbeBlendRecord } from './material-artifacts';
|
|
38
|
+
import type { GpuDrivenPbrProgram } from './pbr-program';
|
|
39
|
+
import { resolveStandardPbrProgram } from './pbr-program';
|
|
40
|
+
import {
|
|
41
|
+
GpuResourceAllocationLedger,
|
|
42
|
+
type GpuResourceAllocationToken,
|
|
43
|
+
} from './resource-allocation';
|
|
44
|
+
|
|
45
|
+
const SURFACE_FRAME_RECORD_BYTES = 48;
|
|
46
|
+
const SURFACE_SHARED_FRAME_BYTES = 16;
|
|
47
|
+
const SURFACE_DIRECT_ADDRESS_STRIDE = 256;
|
|
48
|
+
|
|
49
|
+
interface SurfaceFrameRange {
|
|
50
|
+
readonly frameIndex: number;
|
|
51
|
+
readonly range?: DynamicInputRange;
|
|
52
|
+
readonly directMember?: {
|
|
53
|
+
readonly worldId: number;
|
|
54
|
+
readonly entityKey: number;
|
|
55
|
+
readonly drawItemIndex: number;
|
|
56
|
+
readonly instanceOrdinal: number;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface SurfaceFrameRow {
|
|
61
|
+
readonly eventRangeStart: number;
|
|
62
|
+
readonly eventRangeCount: number;
|
|
63
|
+
readonly instanceIndex: number;
|
|
64
|
+
readonly backgroundAvailable: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface SurfaceSharedFrameState {
|
|
68
|
+
readonly buffer: Buffer;
|
|
69
|
+
frameTime: number;
|
|
70
|
+
writes: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sameSurfaceFrameRow(left: SurfaceFrameRow, right: SurfaceFrameRow): boolean {
|
|
74
|
+
return (
|
|
75
|
+
left.eventRangeStart === right.eventRangeStart &&
|
|
76
|
+
left.eventRangeCount === right.eventRangeCount &&
|
|
77
|
+
left.instanceIndex === right.instanceIndex &&
|
|
78
|
+
left.backgroundAvailable === right.backgroundAvailable
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function bufferBinding(buffer: Buffer, offset?: number, size?: number) {
|
|
83
|
+
return {
|
|
84
|
+
kind: 'buffer' as const,
|
|
85
|
+
value: {
|
|
86
|
+
buffer,
|
|
87
|
+
...(offset === undefined ? {} : { offset }),
|
|
88
|
+
...(size === undefined ? {} : { size }),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function writeSurfaceFrameRecord(
|
|
94
|
+
view: DataView,
|
|
95
|
+
offset: number,
|
|
96
|
+
frameTime: number,
|
|
97
|
+
eventRangeStart: number,
|
|
98
|
+
eventRangeCount: number,
|
|
99
|
+
instanceIndex = 0,
|
|
100
|
+
backgroundAvailable = false,
|
|
101
|
+
): void {
|
|
102
|
+
view.setFloat32(offset, frameTime, true);
|
|
103
|
+
view.setUint32(offset + 4, eventRangeStart, true);
|
|
104
|
+
view.setUint32(offset + 8, eventRangeCount, true);
|
|
105
|
+
view.setUint32(offset + 12, instanceIndex, true);
|
|
106
|
+
for (let lane = 0; lane < 4; lane += 1) view.setFloat32(offset + 16 + lane * 4, 0, true);
|
|
107
|
+
view.setFloat32(offset + 32, 0, true);
|
|
108
|
+
view.setUint32(offset + 36, 0, true);
|
|
109
|
+
view.setUint32(offset + 40, 0, true);
|
|
110
|
+
view.setUint32(offset + 44, backgroundAvailable ? 1 : 0, true);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function surfaceDynamicInputError(expected: string, hint: string): RhiError {
|
|
114
|
+
return new RhiError({ code: 'rhi-descriptor-invalid', expected, hint });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function sameDynamicInputLayout(
|
|
118
|
+
actual: MaterialDynamicInputLayout,
|
|
119
|
+
expected: MaterialDynamicInputLayout,
|
|
120
|
+
): boolean {
|
|
121
|
+
return (
|
|
122
|
+
actual.identity === expected.identity &&
|
|
123
|
+
actual.name === expected.name &&
|
|
124
|
+
actual.stride === expected.stride &&
|
|
125
|
+
actual.maxRecords === expected.maxRecords &&
|
|
126
|
+
actual.maxDomains === expected.maxDomains &&
|
|
127
|
+
actual.maxPageBytes === expected.maxPageBytes &&
|
|
128
|
+
actual.maxBindings === expected.maxBindings &&
|
|
129
|
+
actual.maxEventsPerSample === expected.maxEventsPerSample &&
|
|
130
|
+
actual.fields.length === expected.fields.length &&
|
|
131
|
+
actual.fields.every((field, index) => {
|
|
132
|
+
const candidate = expected.fields[index];
|
|
133
|
+
return (
|
|
134
|
+
candidate !== undefined &&
|
|
135
|
+
field.name === candidate.name &&
|
|
136
|
+
field.type === candidate.type &&
|
|
137
|
+
field.offset === candidate.offset &&
|
|
138
|
+
field.size === candidate.size &&
|
|
139
|
+
field.alignment === candidate.alignment
|
|
140
|
+
);
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function invalidSurfaceDynamicRange(
|
|
146
|
+
page: ReadonlyDynamicInputPage,
|
|
147
|
+
range: DynamicInputRange,
|
|
148
|
+
expectedDeviceGeneration: number | undefined,
|
|
149
|
+
): string | undefined {
|
|
150
|
+
if (range.sourceId !== page.sourceId || range.pageId !== page.pageId) {
|
|
151
|
+
return 'each Surface dynamic range belongs to the submitted page source and page id';
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
expectedDeviceGeneration !== undefined &&
|
|
155
|
+
range.deviceGeneration !== expectedDeviceGeneration
|
|
156
|
+
) {
|
|
157
|
+
return 'each Surface dynamic range belongs to the current renderer device generation';
|
|
158
|
+
}
|
|
159
|
+
if (
|
|
160
|
+
range.bufferGeneration !== page.bufferGeneration ||
|
|
161
|
+
range.deviceGeneration !== page.deviceGeneration
|
|
162
|
+
) {
|
|
163
|
+
return 'each Surface dynamic range belongs to the current page buffer and device generation';
|
|
164
|
+
}
|
|
165
|
+
if (
|
|
166
|
+
!Number.isSafeInteger(range.instanceIndex) ||
|
|
167
|
+
range.instanceIndex < 0 ||
|
|
168
|
+
range.instanceIndex > 0xffffffff
|
|
169
|
+
) {
|
|
170
|
+
return 'each Surface dynamic range uses an exact unsigned 32-bit instance index';
|
|
171
|
+
}
|
|
172
|
+
if (
|
|
173
|
+
!Number.isSafeInteger(range.recordStart) ||
|
|
174
|
+
!Number.isSafeInteger(range.recordCount) ||
|
|
175
|
+
range.recordStart < 0 ||
|
|
176
|
+
range.recordCount <= 0 ||
|
|
177
|
+
range.recordStart + range.recordCount > page.schema.maxRecords ||
|
|
178
|
+
range.recordCount > page.schema.maxEventsPerSample
|
|
179
|
+
) {
|
|
180
|
+
return 'each Surface dynamic range stays within the published record and per-sample budgets';
|
|
181
|
+
}
|
|
182
|
+
if (range.byteOffset !== range.recordStart * page.layout.stride) {
|
|
183
|
+
return 'each Surface dynamic range byte offset is derived from the published record stride';
|
|
184
|
+
}
|
|
185
|
+
if (
|
|
186
|
+
!Number.isSafeInteger(range.contentRevision) ||
|
|
187
|
+
range.contentRevision <= 0 ||
|
|
188
|
+
range.contentRevision > page.contentRevision
|
|
189
|
+
) {
|
|
190
|
+
return 'each Surface dynamic range names a published page content revision';
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
type MaterialPipelineFactory = NonNullable<RenderSystemInternals['getMaterialShaderPipelineEntry']>;
|
|
196
|
+
|
|
197
|
+
function materialArtifactProgramIdentity(artifact: MaterialShaderArtifact): string {
|
|
198
|
+
const source = artifact.specializationKey ?? artifact.wgsl;
|
|
199
|
+
let hash = 2166136261;
|
|
200
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
201
|
+
hash ^= source.charCodeAt(index);
|
|
202
|
+
hash = Math.imul(hash, 16777619);
|
|
203
|
+
}
|
|
204
|
+
return `${source.length}:${hash >>> 0}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export class MaterialAbiRasterAdapter {
|
|
208
|
+
private readonly pipelines = new Map<string, RenderPipeline>();
|
|
209
|
+
private readonly projectionBuffers = new Set<Buffer>();
|
|
210
|
+
private readonly projectionBindings = new Map<
|
|
211
|
+
string,
|
|
212
|
+
{
|
|
213
|
+
readonly buffer: Buffer;
|
|
214
|
+
readonly bindGroup: BindGroup;
|
|
215
|
+
readonly capacityBytes: number;
|
|
216
|
+
rows: Float32Array;
|
|
217
|
+
}
|
|
218
|
+
>();
|
|
219
|
+
/**
|
|
220
|
+
* Projection rows are graph-generation resources. A new topology may stage
|
|
221
|
+
* a replacement before the graph is promoted; keeping staged bindings out
|
|
222
|
+
* of the active map lets an aborted graph leave the last submitted rows
|
|
223
|
+
* intact. Superseded buffers are fence-retired on promotion.
|
|
224
|
+
*/
|
|
225
|
+
private readonly stagedProjectionBindings = new Map<
|
|
226
|
+
string,
|
|
227
|
+
{
|
|
228
|
+
readonly buffer: Buffer;
|
|
229
|
+
readonly bindGroup: BindGroup;
|
|
230
|
+
readonly capacityBytes: number;
|
|
231
|
+
rows: Float32Array;
|
|
232
|
+
}
|
|
233
|
+
>();
|
|
234
|
+
private readonly pendingProjectionRetirements = new Set<Buffer>();
|
|
235
|
+
private readonly destroyedProjectionBuffers = new WeakSet<Buffer>();
|
|
236
|
+
private projectionDisposed = false;
|
|
237
|
+
private stagedProjectionKeys = new Set<string>();
|
|
238
|
+
private readonly activeProjectionRowsAtStage = new Map<string, Float32Array>();
|
|
239
|
+
private readonly visibleWindowBindGroups = new Map<Buffer, Map<string, BindGroup>>();
|
|
240
|
+
private readonly surfaceInputBuffers = new Set<Buffer>();
|
|
241
|
+
/**
|
|
242
|
+
* Material-raster-owned buffers only. Visible, probe, palette and mesh
|
|
243
|
+
* buffers are supplied by their respective owners and are deliberately not
|
|
244
|
+
* registered here, so the production projection cannot double-count them.
|
|
245
|
+
*/
|
|
246
|
+
private readonly allocationTokens = new WeakMap<Buffer, GpuResourceAllocationToken>();
|
|
247
|
+
private readonly allocationLedger: GpuResourceAllocationLedger;
|
|
248
|
+
private dynamicInputPage: ReadonlyDynamicInputPage | undefined;
|
|
249
|
+
private dynamicInputPageGeneration = 0;
|
|
250
|
+
private dynamicInputBuffer: Buffer;
|
|
251
|
+
private dynamicInputBufferSize: number;
|
|
252
|
+
private dynamicFrameBuffer: Buffer;
|
|
253
|
+
private dynamicFrameBufferSize: number;
|
|
254
|
+
private readonly dynamicSharedFrame: SurfaceSharedFrameState;
|
|
255
|
+
private directAddressBuffer: Buffer;
|
|
256
|
+
private directAddressBufferSize: number;
|
|
257
|
+
private readonly directAddresses = new Map<
|
|
258
|
+
string,
|
|
259
|
+
{ readonly frameBase: number; readonly instanceCount: number; readonly byteOffset: number }
|
|
260
|
+
>();
|
|
261
|
+
private readonly directBindGroups = new Map<
|
|
262
|
+
string,
|
|
263
|
+
{
|
|
264
|
+
readonly instanceBuffer: Buffer;
|
|
265
|
+
readonly probeBuffer: Buffer;
|
|
266
|
+
readonly addressBuffer: Buffer;
|
|
267
|
+
readonly bindGroup: BindGroup;
|
|
268
|
+
}
|
|
269
|
+
>();
|
|
270
|
+
/** Last successfully queued frame rows, keyed by visible member identity. */
|
|
271
|
+
private readonly dynamicFrameRows = new Map<number, SurfaceFrameRow>();
|
|
272
|
+
private dynamicFrameRangeSource: readonly SurfaceFrameRange[] | undefined;
|
|
273
|
+
private dynamicInputRangeSource: readonly DynamicInputRange[] | undefined;
|
|
274
|
+
private dynamicInputFrameRanges: readonly SurfaceFrameRange[] | undefined;
|
|
275
|
+
private dynamicFrameRangeCapacity = 0;
|
|
276
|
+
private dynamicFrameMemberScans = 0;
|
|
277
|
+
private dynamicFrameRowAllocations = 0;
|
|
278
|
+
private pendingDynamicInputUpload:
|
|
279
|
+
| { readonly page: ReadonlyDynamicInputPage; readonly receipt: DynamicInputUploadReceipt }
|
|
280
|
+
| undefined;
|
|
281
|
+
|
|
282
|
+
private constructor(
|
|
283
|
+
private readonly device: RhiDevice,
|
|
284
|
+
private readonly pipelineLayout: import('@forgeax/engine-rhi').PipelineLayout,
|
|
285
|
+
private readonly meshLayout: BindGroupLayout,
|
|
286
|
+
private readonly instancesBindGroupLayout: BindGroupLayout,
|
|
287
|
+
private readonly surfaceDirectInstancesBindGroupLayout: BindGroupLayout | null,
|
|
288
|
+
private readonly programs: ReadonlyMap<string, GpuDrivenPbrProgram> | undefined,
|
|
289
|
+
private instancesBindGroup: BindGroup,
|
|
290
|
+
private readonly instanceBuffer: Buffer,
|
|
291
|
+
private readonly instanceBufferSize: number,
|
|
292
|
+
private visibleBuffer: Buffer,
|
|
293
|
+
private probeBlendRecordBuffer: Buffer | undefined,
|
|
294
|
+
private readonly artifact: MaterialShaderArtifact,
|
|
295
|
+
private readonly deformation: 'rigid' | 'skin',
|
|
296
|
+
private readonly pipelineFactory: MaterialPipelineFactory | undefined,
|
|
297
|
+
private paletteBuffer: Buffer | undefined,
|
|
298
|
+
private paletteBindingWindowBytes: number | undefined,
|
|
299
|
+
dynamicInputBuffer: Buffer,
|
|
300
|
+
dynamicInputFrameBuffer: Buffer,
|
|
301
|
+
dynamicSharedFrame: SurfaceSharedFrameState,
|
|
302
|
+
directAddressBuffer: Buffer,
|
|
303
|
+
ownsDynamicSharedFrame: boolean,
|
|
304
|
+
allocationLedger: GpuResourceAllocationLedger,
|
|
305
|
+
) {
|
|
306
|
+
this.allocationLedger = allocationLedger;
|
|
307
|
+
this.dynamicInputBuffer = dynamicInputBuffer;
|
|
308
|
+
this.dynamicInputBufferSize = 16;
|
|
309
|
+
this.dynamicFrameBuffer = dynamicInputFrameBuffer;
|
|
310
|
+
this.dynamicFrameBufferSize = SURFACE_FRAME_RECORD_BYTES;
|
|
311
|
+
this.dynamicSharedFrame = dynamicSharedFrame;
|
|
312
|
+
this.directAddressBuffer = directAddressBuffer;
|
|
313
|
+
this.directAddressBufferSize = SURFACE_DIRECT_ADDRESS_STRIDE;
|
|
314
|
+
this.surfaceInputBuffers.add(dynamicInputBuffer);
|
|
315
|
+
this.surfaceInputBuffers.add(dynamicInputFrameBuffer);
|
|
316
|
+
this.surfaceInputBuffers.add(directAddressBuffer);
|
|
317
|
+
if (ownsDynamicSharedFrame) this.surfaceInputBuffers.add(dynamicSharedFrame.buffer);
|
|
318
|
+
this.trackOwnedBuffer(instanceBuffer, instanceBufferSize);
|
|
319
|
+
this.trackOwnedBuffer(dynamicInputBuffer, 16);
|
|
320
|
+
this.trackOwnedBuffer(dynamicInputFrameBuffer, SURFACE_FRAME_RECORD_BYTES);
|
|
321
|
+
this.trackOwnedBuffer(directAddressBuffer, SURFACE_DIRECT_ADDRESS_STRIDE);
|
|
322
|
+
if (ownsDynamicSharedFrame) {
|
|
323
|
+
this.trackOwnedBuffer(dynamicSharedFrame.buffer, SURFACE_SHARED_FRAME_BYTES);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
static create(input: {
|
|
328
|
+
readonly device: RhiDevice;
|
|
329
|
+
readonly pipelineState: PipelineState;
|
|
330
|
+
readonly artifact: MaterialShaderArtifact;
|
|
331
|
+
readonly visibleBuffer: Buffer;
|
|
332
|
+
readonly deformation?: 'rigid' | 'skin';
|
|
333
|
+
readonly paletteBuffer?: Buffer;
|
|
334
|
+
readonly paletteBindingWindowBytes?: number;
|
|
335
|
+
readonly sharedSurfaceFrame?: SurfaceSharedFrameState;
|
|
336
|
+
readonly probeBlendRecordBuffer?: Buffer;
|
|
337
|
+
/** Renderer-owned factory for the selected artifact's actual PSO. */
|
|
338
|
+
readonly pipelineFactory?: MaterialPipelineFactory;
|
|
339
|
+
}): Result<MaterialAbiRasterAdapter, RhiError | GpuDrivenPreparationError> {
|
|
340
|
+
const deformation = input.deformation ?? 'rigid';
|
|
341
|
+
const receipt = input.artifact.receipt;
|
|
342
|
+
if (receipt === undefined) {
|
|
343
|
+
return err(
|
|
344
|
+
new RhiError({
|
|
345
|
+
code: 'rhi-not-available',
|
|
346
|
+
expected: 'published MaterialProgramAbi receipt for the selected material artifact',
|
|
347
|
+
hint: 'recook and publish the material before creating a GPU-driven raster owner',
|
|
348
|
+
}),
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const probeBlendRequested = requiresProbeBlendRecord(input.artifact);
|
|
352
|
+
if (probeBlendRequested && input.probeBlendRecordBuffer === undefined) {
|
|
353
|
+
return err(
|
|
354
|
+
new GpuDrivenPreparationError('resource-not-ready', {
|
|
355
|
+
reason: 'material-resource-missing',
|
|
356
|
+
owner: 'material',
|
|
357
|
+
expected: 'the retained ProbeBlend record page for this scene-index program',
|
|
358
|
+
actual: 'no ProbeBlend record page was published for the prepared frame',
|
|
359
|
+
recovery: 'retry-same-draw',
|
|
360
|
+
}),
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
const materialRowMatchesSceneTable =
|
|
364
|
+
receipt.materialRow.byteLength === GPU_SCENE_LAYOUTS.material.stride &&
|
|
365
|
+
new Set(receipt.materialRow.fields).size === receipt.materialRow.fields.length;
|
|
366
|
+
if (!materialRowMatchesSceneTable) {
|
|
367
|
+
return err(
|
|
368
|
+
new GpuDrivenPreparationError('material-abi-not-executable', {
|
|
369
|
+
reason: 'material-abi-not-executable',
|
|
370
|
+
owner: 'material',
|
|
371
|
+
expected: `scene-index material row ${GPU_SCENE_LAYOUTS.material.stride} bytes with unique producer fields`,
|
|
372
|
+
actual: `row ${receipt.materialRow.byteLength} bytes with fields ${receipt.materialRow.fields.join(',')}`,
|
|
373
|
+
}),
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
if (
|
|
377
|
+
receipt.reflection.layoutIdentity !== input.artifact.layoutIdentity ||
|
|
378
|
+
receipt.directEntry.length === 0 ||
|
|
379
|
+
receipt.sceneIndexEntry.length === 0 ||
|
|
380
|
+
receipt.resourceSlots.some((slot) => slot.group !== 1)
|
|
381
|
+
) {
|
|
382
|
+
return err(
|
|
383
|
+
new RhiError({
|
|
384
|
+
code: 'rhi-descriptor-invalid',
|
|
385
|
+
expected: `complete selected MaterialProgramAbi reflection, scene row stride ${GPU_SCENE_LAYOUTS.material.stride}, and resource slots`,
|
|
386
|
+
hint: 'repair the producer publication or use the owner for its non-scene material layout',
|
|
387
|
+
}),
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
// Scene-index shaders declare the dedicated slot-3 ABI. Keep the legacy
|
|
391
|
+
// fields as a compatibility fallback for synthetic fixtures; the real
|
|
392
|
+
// factory publishes the dedicated handles whenever the capable storage
|
|
393
|
+
// lane is enabled, so production draws never bind the direct-draw BGL.
|
|
394
|
+
const pipelineLayout =
|
|
395
|
+
deformation === 'skin'
|
|
396
|
+
? input.pipelineState.gpuDrivenPbrSkinPipelineLayout
|
|
397
|
+
: input.pipelineState.gpuDrivenPbrPipelineLayout;
|
|
398
|
+
const instancesBindGroupLayout = input.pipelineState.gpuDrivenInstancesBindGroupLayout;
|
|
399
|
+
if (pipelineLayout == null || instancesBindGroupLayout == null) {
|
|
400
|
+
return err(
|
|
401
|
+
new RhiError({
|
|
402
|
+
code: 'rhi-not-available',
|
|
403
|
+
expected: `${deformation === 'skin' ? 'skinned ' : ''}published material pipeline layout`,
|
|
404
|
+
hint: 'initialize the producer material pipeline before enabling GPU-driven raster',
|
|
405
|
+
}),
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
const programs = input.pipelineState.gpuDrivenPbrPrograms;
|
|
409
|
+
if (
|
|
410
|
+
input.pipelineFactory === undefined &&
|
|
411
|
+
(programs === undefined ||
|
|
412
|
+
resolveStandardPbrProgram(
|
|
413
|
+
programs,
|
|
414
|
+
input.artifact.material,
|
|
415
|
+
input.artifact.receipt?.vertexInputs.some((attribute) => attribute.semantic === 'color'),
|
|
416
|
+
) === undefined)
|
|
417
|
+
) {
|
|
418
|
+
return err(
|
|
419
|
+
new RhiError({
|
|
420
|
+
code: 'rhi-not-available',
|
|
421
|
+
expected: `producer-owned ${deformation === 'skin' ? 'skinned ' : ''}material shader module for the current device generation`,
|
|
422
|
+
hint: 'publish the selected material module from buildReadyWebGPU before enabling GPU-driven raster',
|
|
423
|
+
}),
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
const meshLayout =
|
|
427
|
+
deformation === 'skin'
|
|
428
|
+
? input.pipelineState.pbrSkinMeshBindGroupLayout
|
|
429
|
+
: input.pipelineState.meshBindGroupLayout;
|
|
430
|
+
if (meshLayout === null) {
|
|
431
|
+
return err(
|
|
432
|
+
new RhiError({
|
|
433
|
+
code: 'rhi-not-available',
|
|
434
|
+
expected: `${deformation === 'skin' ? 'skinned ' : ''}material mesh bind-group layout`,
|
|
435
|
+
hint: 'publish the exact mesh-array layout selected by the material receipt',
|
|
436
|
+
}),
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
if (
|
|
440
|
+
deformation === 'skin' &&
|
|
441
|
+
(input.paletteBuffer === undefined || input.paletteBindingWindowBytes === undefined)
|
|
442
|
+
) {
|
|
443
|
+
return err(
|
|
444
|
+
new RhiError({
|
|
445
|
+
code: 'rhi-not-available',
|
|
446
|
+
expected: 'persistent skin palette buffer and binding window',
|
|
447
|
+
hint: 'keep the palette receipt and the GPU-driven skinned projection on one frame identity',
|
|
448
|
+
}),
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
const identity = new Float32Array(16);
|
|
452
|
+
identity[0] = 1;
|
|
453
|
+
identity[5] = 1;
|
|
454
|
+
identity[10] = 1;
|
|
455
|
+
identity[15] = 1;
|
|
456
|
+
const instancePayload = packInstanceStorageBuffer(identity);
|
|
457
|
+
const createdBuffers: Buffer[] = [];
|
|
458
|
+
const cleanupCreatedBuffers = (): void => {
|
|
459
|
+
for (const buffer of createdBuffers) input.device.destroyBuffer(buffer);
|
|
460
|
+
createdBuffers.length = 0;
|
|
461
|
+
};
|
|
462
|
+
const instanceBuffer = input.device.createBuffer({
|
|
463
|
+
label: 'standard-pbr-gpu-driven-instance-data',
|
|
464
|
+
size: instancePayload.byteLength,
|
|
465
|
+
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
466
|
+
});
|
|
467
|
+
if (!instanceBuffer.ok) return instanceBuffer;
|
|
468
|
+
createdBuffers.push(instanceBuffer.value);
|
|
469
|
+
const instanceWrite = input.device.queue.writeBuffer(instanceBuffer.value, 0, instancePayload);
|
|
470
|
+
if (!instanceWrite.ok) {
|
|
471
|
+
cleanupCreatedBuffers();
|
|
472
|
+
return instanceWrite;
|
|
473
|
+
}
|
|
474
|
+
const dynamicInputBuffer = input.device.createBuffer({
|
|
475
|
+
label: 'surface-dynamic-input-page',
|
|
476
|
+
size: 16,
|
|
477
|
+
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
478
|
+
});
|
|
479
|
+
if (!dynamicInputBuffer.ok) {
|
|
480
|
+
cleanupCreatedBuffers();
|
|
481
|
+
return dynamicInputBuffer;
|
|
482
|
+
}
|
|
483
|
+
createdBuffers.push(dynamicInputBuffer.value);
|
|
484
|
+
const dynamicInputWrite = input.device.queue.writeBuffer(
|
|
485
|
+
dynamicInputBuffer.value,
|
|
486
|
+
0,
|
|
487
|
+
new Uint32Array(4),
|
|
488
|
+
);
|
|
489
|
+
if (!dynamicInputWrite.ok) {
|
|
490
|
+
cleanupCreatedBuffers();
|
|
491
|
+
return dynamicInputWrite;
|
|
492
|
+
}
|
|
493
|
+
const dynamicFrameBuffer = input.device.createBuffer({
|
|
494
|
+
label: 'surface-dynamic-frame-input',
|
|
495
|
+
size: SURFACE_FRAME_RECORD_BYTES,
|
|
496
|
+
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
497
|
+
});
|
|
498
|
+
if (!dynamicFrameBuffer.ok) {
|
|
499
|
+
cleanupCreatedBuffers();
|
|
500
|
+
return dynamicFrameBuffer;
|
|
501
|
+
}
|
|
502
|
+
createdBuffers.push(dynamicFrameBuffer.value);
|
|
503
|
+
const dynamicFramePayload = new Uint8Array(SURFACE_FRAME_RECORD_BYTES);
|
|
504
|
+
writeSurfaceFrameRecord(new DataView(dynamicFramePayload.buffer), 0, 0, 0, 0);
|
|
505
|
+
const dynamicFrameWrite = input.device.queue.writeBuffer(
|
|
506
|
+
dynamicFrameBuffer.value,
|
|
507
|
+
0,
|
|
508
|
+
dynamicFramePayload,
|
|
509
|
+
);
|
|
510
|
+
if (!dynamicFrameWrite.ok) {
|
|
511
|
+
cleanupCreatedBuffers();
|
|
512
|
+
return dynamicFrameWrite;
|
|
513
|
+
}
|
|
514
|
+
const directAddressBuffer = input.device.createBuffer({
|
|
515
|
+
label: 'surface-direct-addresses',
|
|
516
|
+
size: SURFACE_DIRECT_ADDRESS_STRIDE,
|
|
517
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
518
|
+
});
|
|
519
|
+
if (!directAddressBuffer.ok) {
|
|
520
|
+
cleanupCreatedBuffers();
|
|
521
|
+
return directAddressBuffer;
|
|
522
|
+
}
|
|
523
|
+
createdBuffers.push(directAddressBuffer.value);
|
|
524
|
+
const directAddressWrite = input.device.queue.writeBuffer(
|
|
525
|
+
directAddressBuffer.value,
|
|
526
|
+
0,
|
|
527
|
+
new Uint32Array(4),
|
|
528
|
+
);
|
|
529
|
+
if (!directAddressWrite.ok) {
|
|
530
|
+
cleanupCreatedBuffers();
|
|
531
|
+
return directAddressWrite;
|
|
532
|
+
}
|
|
533
|
+
let ownsDynamicSharedFrame = false;
|
|
534
|
+
let dynamicSharedFrame = input.sharedSurfaceFrame;
|
|
535
|
+
if (dynamicSharedFrame === undefined) {
|
|
536
|
+
const created = input.device.createBuffer({
|
|
537
|
+
label: 'surface-dynamic-shared-frame-input',
|
|
538
|
+
size: SURFACE_SHARED_FRAME_BYTES,
|
|
539
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
540
|
+
});
|
|
541
|
+
if (!created.ok) {
|
|
542
|
+
cleanupCreatedBuffers();
|
|
543
|
+
return created;
|
|
544
|
+
}
|
|
545
|
+
createdBuffers.push(created.value);
|
|
546
|
+
const initialized = input.device.queue.writeBuffer(created.value, 0, new Float32Array(4));
|
|
547
|
+
if (!initialized.ok) {
|
|
548
|
+
cleanupCreatedBuffers();
|
|
549
|
+
return initialized;
|
|
550
|
+
}
|
|
551
|
+
dynamicSharedFrame = { buffer: created.value, frameTime: 0, writes: 0 };
|
|
552
|
+
ownsDynamicSharedFrame = true;
|
|
553
|
+
}
|
|
554
|
+
const instancesBindGroup = input.device.createBindGroup({
|
|
555
|
+
label: 'standard-pbr-gpu-driven-visible-instances-bg',
|
|
556
|
+
layout: instancesBindGroupLayout,
|
|
557
|
+
entries: [
|
|
558
|
+
{
|
|
559
|
+
binding: 0,
|
|
560
|
+
resource: bufferBinding(instanceBuffer.value, 0, instancePayload.byteLength),
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
binding: 1,
|
|
564
|
+
resource: bufferBinding(input.probeBlendRecordBuffer ?? input.visibleBuffer),
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
binding: 2,
|
|
568
|
+
resource: bufferBinding(input.visibleBuffer),
|
|
569
|
+
},
|
|
570
|
+
{ binding: 3, resource: bufferBinding(dynamicInputBuffer.value) },
|
|
571
|
+
{ binding: 4, resource: bufferBinding(dynamicFrameBuffer.value) },
|
|
572
|
+
{ binding: 5, resource: bufferBinding(dynamicSharedFrame.buffer) },
|
|
573
|
+
],
|
|
574
|
+
});
|
|
575
|
+
if (!instancesBindGroup.ok) {
|
|
576
|
+
cleanupCreatedBuffers();
|
|
577
|
+
return instancesBindGroup;
|
|
578
|
+
}
|
|
579
|
+
const allocationLedger = new GpuResourceAllocationLedger();
|
|
580
|
+
return ok(
|
|
581
|
+
new MaterialAbiRasterAdapter(
|
|
582
|
+
input.device,
|
|
583
|
+
pipelineLayout,
|
|
584
|
+
meshLayout,
|
|
585
|
+
instancesBindGroupLayout,
|
|
586
|
+
input.pipelineState.surfaceDirectInstancesBindGroupLayout ?? null,
|
|
587
|
+
programs,
|
|
588
|
+
instancesBindGroup.value,
|
|
589
|
+
instanceBuffer.value,
|
|
590
|
+
instancePayload.byteLength,
|
|
591
|
+
input.visibleBuffer,
|
|
592
|
+
input.probeBlendRecordBuffer,
|
|
593
|
+
input.artifact,
|
|
594
|
+
deformation,
|
|
595
|
+
input.pipelineFactory,
|
|
596
|
+
input.paletteBuffer,
|
|
597
|
+
input.paletteBindingWindowBytes,
|
|
598
|
+
dynamicInputBuffer.value,
|
|
599
|
+
dynamicFrameBuffer.value,
|
|
600
|
+
dynamicSharedFrame,
|
|
601
|
+
directAddressBuffer.value,
|
|
602
|
+
ownsDynamicSharedFrame,
|
|
603
|
+
allocationLedger,
|
|
604
|
+
),
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
ensurePaletteBuffer(
|
|
609
|
+
paletteBuffer: Buffer | undefined,
|
|
610
|
+
paletteBindingWindowBytes: number | undefined,
|
|
611
|
+
): Result<void, RhiError> {
|
|
612
|
+
if (this.deformation !== 'skin') return ok(undefined);
|
|
613
|
+
if (paletteBuffer === undefined || paletteBindingWindowBytes === undefined) {
|
|
614
|
+
return err(
|
|
615
|
+
new RhiError({
|
|
616
|
+
code: 'rhi-not-available',
|
|
617
|
+
expected: 'persistent skin palette buffer and binding window',
|
|
618
|
+
hint: 'publish a storage-backed SkinPaletteReceipt before the GPU-driven skinned lane',
|
|
619
|
+
}),
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
this.paletteBuffer = paletteBuffer;
|
|
623
|
+
this.paletteBindingWindowBytes = paletteBindingWindowBytes;
|
|
624
|
+
return ok(undefined);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
skinPaletteBinding():
|
|
628
|
+
| { readonly buffer: Buffer; readonly bindingWindowBytes: number }
|
|
629
|
+
| undefined {
|
|
630
|
+
return this.deformation === 'skin' &&
|
|
631
|
+
this.paletteBuffer !== undefined &&
|
|
632
|
+
this.paletteBindingWindowBytes !== undefined
|
|
633
|
+
? { buffer: this.paletteBuffer, bindingWindowBytes: this.paletteBindingWindowBytes }
|
|
634
|
+
: undefined;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
ensureVisibleBuffer(visibleBuffer: Buffer): Result<void, RhiError> {
|
|
638
|
+
if (this.visibleBuffer === visibleBuffer) return ok(undefined);
|
|
639
|
+
const created = this.device.createBindGroup({
|
|
640
|
+
label: 'standard-pbr-gpu-driven-visible-instances-bg',
|
|
641
|
+
layout: this.instancesBindGroupLayout,
|
|
642
|
+
entries: [
|
|
643
|
+
{
|
|
644
|
+
binding: 0,
|
|
645
|
+
resource: bufferBinding(this.instanceBuffer, 0, this.instanceBufferSize),
|
|
646
|
+
},
|
|
647
|
+
{ binding: 1, resource: bufferBinding(this.probeBlendRecordBuffer ?? visibleBuffer) },
|
|
648
|
+
{ binding: 2, resource: bufferBinding(visibleBuffer) },
|
|
649
|
+
{ binding: 3, resource: bufferBinding(this.dynamicInputBuffer) },
|
|
650
|
+
{ binding: 4, resource: bufferBinding(this.dynamicFrameBuffer) },
|
|
651
|
+
{ binding: 5, resource: bufferBinding(this.dynamicSharedFrame.buffer) },
|
|
652
|
+
],
|
|
653
|
+
});
|
|
654
|
+
if (!created.ok) return created;
|
|
655
|
+
this.instancesBindGroup = created.value;
|
|
656
|
+
this.visibleBuffer = visibleBuffer;
|
|
657
|
+
return ok(undefined);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
ensureProbeBlendRecordBuffer(buffer: Buffer | undefined): Result<void, RhiError> {
|
|
661
|
+
if (buffer === undefined || buffer === this.probeBlendRecordBuffer) return ok(undefined);
|
|
662
|
+
this.probeBlendRecordBuffer = buffer;
|
|
663
|
+
const rebound = this.rebuildInstancesBindGroup();
|
|
664
|
+
if (!rebound.ok) return rebound;
|
|
665
|
+
this.visibleWindowBindGroups.clear();
|
|
666
|
+
this.directBindGroups.clear();
|
|
667
|
+
return ok(undefined);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
visibleWindowBindGroup(
|
|
671
|
+
visibleBase: number,
|
|
672
|
+
visibleCapacity: number,
|
|
673
|
+
surfaceFrameBase?: number,
|
|
674
|
+
surfaceFrameCapacity?: number,
|
|
675
|
+
): Result<BindGroup, RhiError> {
|
|
676
|
+
return this.visibleWindowBindGroupForBuffer(
|
|
677
|
+
this.visibleBuffer,
|
|
678
|
+
visibleBase,
|
|
679
|
+
visibleCapacity,
|
|
680
|
+
surfaceFrameBase,
|
|
681
|
+
surfaceFrameCapacity,
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
visibleWindowBindGroupForBuffer(
|
|
686
|
+
visibleBuffer: Buffer,
|
|
687
|
+
visibleBase: number,
|
|
688
|
+
visibleCapacity: number,
|
|
689
|
+
surfaceFrameBase?: number,
|
|
690
|
+
surfaceFrameCapacity?: number,
|
|
691
|
+
shadowVisibleAlias = false,
|
|
692
|
+
): Result<BindGroup, RhiError> {
|
|
693
|
+
const offset = visibleBase * 16;
|
|
694
|
+
const size = visibleCapacity * 16;
|
|
695
|
+
if (offset % 256 !== 0 || size <= 0) {
|
|
696
|
+
return err(
|
|
697
|
+
new RhiError({
|
|
698
|
+
code: 'rhi-descriptor-invalid',
|
|
699
|
+
expected: '256-byte-aligned non-empty visible window',
|
|
700
|
+
hint: `allocate each GPU-driven batch visible window on the portable storage-buffer boundary (base=${visibleBase}, capacity=${visibleCapacity}, offset=${offset}, size=${size})`,
|
|
701
|
+
}),
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
const frameBase = surfaceFrameBase === undefined ? undefined : surfaceFrameBase;
|
|
705
|
+
const frameCapacity = surfaceFrameCapacity ?? visibleCapacity;
|
|
706
|
+
const frameOffset =
|
|
707
|
+
frameBase === undefined ? undefined : frameBase * SURFACE_FRAME_RECORD_BYTES;
|
|
708
|
+
const frameSize =
|
|
709
|
+
frameBase === undefined ? undefined : frameCapacity * SURFACE_FRAME_RECORD_BYTES;
|
|
710
|
+
if (
|
|
711
|
+
frameOffset !== undefined &&
|
|
712
|
+
frameSize !== undefined &&
|
|
713
|
+
(frameOffset % 256 !== 0 ||
|
|
714
|
+
frameSize <= 0 ||
|
|
715
|
+
frameOffset + frameSize > this.dynamicFrameBufferSize)
|
|
716
|
+
) {
|
|
717
|
+
return err(
|
|
718
|
+
new RhiError({
|
|
719
|
+
code: 'rhi-descriptor-invalid',
|
|
720
|
+
expected: 'the Surface frame window is aligned and contained by the frame buffer',
|
|
721
|
+
hint: `bind the renderer-owned compact Surface frame range (base=${frameBase}, capacity=${frameCapacity}, bufferBytes=${this.dynamicFrameBufferSize})`,
|
|
722
|
+
}),
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
let groups = this.visibleWindowBindGroups.get(visibleBuffer);
|
|
726
|
+
if (groups === undefined) {
|
|
727
|
+
groups = new Map();
|
|
728
|
+
this.visibleWindowBindGroups.set(visibleBuffer, groups);
|
|
729
|
+
}
|
|
730
|
+
const key = `${visibleBase}:${visibleCapacity}:${frameBase ?? -1}:${frameCapacity}:${shadowVisibleAlias ? 'shadow' : 'main'}`;
|
|
731
|
+
const cached = groups.get(key);
|
|
732
|
+
if (cached !== undefined) return ok(cached);
|
|
733
|
+
const created = this.device.createBindGroup({
|
|
734
|
+
label: `standard-pbr-gpu-driven-visible-window-${visibleBase}`,
|
|
735
|
+
layout: this.instancesBindGroupLayout,
|
|
736
|
+
entries: [
|
|
737
|
+
{
|
|
738
|
+
binding: 0,
|
|
739
|
+
resource: bufferBinding(this.instanceBuffer, 0, this.instanceBufferSize),
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
binding: 1,
|
|
743
|
+
resource:
|
|
744
|
+
shadowVisibleAlias || this.probeBlendRecordBuffer === undefined
|
|
745
|
+
? bufferBinding(visibleBuffer, offset, size)
|
|
746
|
+
: bufferBinding(this.probeBlendRecordBuffer),
|
|
747
|
+
},
|
|
748
|
+
{ binding: 2, resource: bufferBinding(visibleBuffer, offset, size) },
|
|
749
|
+
{ binding: 3, resource: bufferBinding(this.dynamicInputBuffer) },
|
|
750
|
+
{
|
|
751
|
+
binding: 4,
|
|
752
|
+
resource: bufferBinding(this.dynamicFrameBuffer, frameOffset, frameSize),
|
|
753
|
+
},
|
|
754
|
+
{ binding: 5, resource: bufferBinding(this.dynamicSharedFrame.buffer) },
|
|
755
|
+
],
|
|
756
|
+
});
|
|
757
|
+
if (!created.ok) return created;
|
|
758
|
+
groups.set(key, created.value);
|
|
759
|
+
return ok(created.value);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
shadowVisibleWindowBindGroupForBuffer(
|
|
763
|
+
visibleBuffer: Buffer,
|
|
764
|
+
visibleBase: number,
|
|
765
|
+
visibleCapacity: number,
|
|
766
|
+
): Result<BindGroup, RhiError> {
|
|
767
|
+
return this.visibleWindowBindGroupForBuffer(
|
|
768
|
+
visibleBuffer,
|
|
769
|
+
visibleBase,
|
|
770
|
+
visibleCapacity,
|
|
771
|
+
undefined,
|
|
772
|
+
undefined,
|
|
773
|
+
true,
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
private ensureDirectSurfaceAddresses(
|
|
778
|
+
frameRanges: readonly SurfaceFrameRange[],
|
|
779
|
+
): Result<void, RhiError> {
|
|
780
|
+
const members = new Map<string, SurfaceFrameRange[]>();
|
|
781
|
+
for (const entry of frameRanges) {
|
|
782
|
+
const member = entry.directMember;
|
|
783
|
+
if (member === undefined) continue;
|
|
784
|
+
const key = JSON.stringify([member.worldId, member.entityKey, member.drawItemIndex]);
|
|
785
|
+
const group = members.get(key);
|
|
786
|
+
if (group === undefined) members.set(key, [entry]);
|
|
787
|
+
else group.push(entry);
|
|
788
|
+
}
|
|
789
|
+
const addresses = new Map<
|
|
790
|
+
string,
|
|
791
|
+
{ readonly frameBase: number; readonly instanceCount: number; readonly byteOffset: number }
|
|
792
|
+
>();
|
|
793
|
+
const memberCount = [...members.values()].reduce((count, group) => count + group.length, 0);
|
|
794
|
+
const payload = new Uint8Array(Math.max(1, memberCount) * SURFACE_DIRECT_ADDRESS_STRIDE);
|
|
795
|
+
const view = new DataView(payload.buffer);
|
|
796
|
+
let addressIndex = 0;
|
|
797
|
+
for (const [key, group] of members) {
|
|
798
|
+
group.sort(
|
|
799
|
+
(left, right) =>
|
|
800
|
+
(left.directMember?.instanceOrdinal ?? 0) - (right.directMember?.instanceOrdinal ?? 0),
|
|
801
|
+
);
|
|
802
|
+
const first = group[0];
|
|
803
|
+
const firstMember = first?.directMember;
|
|
804
|
+
if (first === undefined || firstMember === undefined) continue;
|
|
805
|
+
const frameBase = first.frameIndex - firstMember.instanceOrdinal;
|
|
806
|
+
if (
|
|
807
|
+
frameBase < 0 ||
|
|
808
|
+
group.some(
|
|
809
|
+
(entry, ordinal) =>
|
|
810
|
+
entry.directMember?.instanceOrdinal !== ordinal ||
|
|
811
|
+
entry.frameIndex !== frameBase + ordinal,
|
|
812
|
+
)
|
|
813
|
+
) {
|
|
814
|
+
return err(
|
|
815
|
+
surfaceDynamicInputError(
|
|
816
|
+
'each direct Surface draw has contiguous local instance ordinals and one frame-row base',
|
|
817
|
+
`rebuild the retained member projection for draw ${key}`,
|
|
818
|
+
),
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
for (let ordinal = 0; ordinal < group.length; ordinal += 1) {
|
|
822
|
+
const byteOffset = addressIndex * SURFACE_DIRECT_ADDRESS_STRIDE;
|
|
823
|
+
view.setUint32(byteOffset, frameBase + ordinal, true);
|
|
824
|
+
view.setUint32(byteOffset + 4, group.length - ordinal, true);
|
|
825
|
+
addresses.set(`${key}:${ordinal}`, {
|
|
826
|
+
frameBase: frameBase + ordinal,
|
|
827
|
+
instanceCount: group.length - ordinal,
|
|
828
|
+
byteOffset,
|
|
829
|
+
});
|
|
830
|
+
addressIndex += 1;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
const replacementSize = Math.max(this.directAddressBufferSize, payload.byteLength);
|
|
834
|
+
const replacement = this.device.createBuffer({
|
|
835
|
+
label: 'surface-direct-addresses',
|
|
836
|
+
size: replacementSize,
|
|
837
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
838
|
+
});
|
|
839
|
+
if (!replacement.ok) return replacement;
|
|
840
|
+
this.trackOwnedBuffer(replacement.value, replacementSize);
|
|
841
|
+
const write = this.device.queue.writeBuffer(replacement.value, 0, payload);
|
|
842
|
+
if (!write.ok) {
|
|
843
|
+
this.rollbackOwnedBuffer(replacement.value);
|
|
844
|
+
return write;
|
|
845
|
+
}
|
|
846
|
+
const previous = this.directAddressBuffer;
|
|
847
|
+
this.directAddressBuffer = replacement.value;
|
|
848
|
+
this.directAddressBufferSize = replacementSize;
|
|
849
|
+
this.directAddresses.clear();
|
|
850
|
+
for (const [key, address] of addresses) this.directAddresses.set(key, address);
|
|
851
|
+
this.directBindGroups.clear();
|
|
852
|
+
this.surfaceInputBuffers.add(replacement.value);
|
|
853
|
+
this.surfaceInputBuffers.delete(previous);
|
|
854
|
+
this.retireProjectionBuffer(previous);
|
|
855
|
+
return ok(undefined);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
directSurfaceInstances(input: {
|
|
859
|
+
readonly worldId: number;
|
|
860
|
+
readonly entityKey: number;
|
|
861
|
+
readonly drawItemIndex: number;
|
|
862
|
+
readonly instanceCount: number;
|
|
863
|
+
readonly firstInstanceOrdinal?: number;
|
|
864
|
+
readonly instanceBuffer: Buffer;
|
|
865
|
+
readonly probeBuffer?: Buffer;
|
|
866
|
+
readonly probeOffset?: number;
|
|
867
|
+
}): Result<
|
|
868
|
+
{
|
|
869
|
+
readonly bindGroup: BindGroup;
|
|
870
|
+
readonly dynamicOffsets: readonly number[];
|
|
871
|
+
readonly frameBase: number;
|
|
872
|
+
},
|
|
873
|
+
RhiError
|
|
874
|
+
> {
|
|
875
|
+
const layout = this.surfaceDirectInstancesBindGroupLayout;
|
|
876
|
+
if (layout === null) {
|
|
877
|
+
return err(
|
|
878
|
+
surfaceDynamicInputError(
|
|
879
|
+
'the current device publishes the direct Surface slot-3 layout',
|
|
880
|
+
'keep the draw on recovery until the storage-backed Surface ABI is ready',
|
|
881
|
+
),
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
const drawKey = JSON.stringify([input.worldId, input.entityKey, input.drawItemIndex]);
|
|
885
|
+
const firstInstanceOrdinal = input.firstInstanceOrdinal ?? 0;
|
|
886
|
+
const key = `${drawKey}:${firstInstanceOrdinal}`;
|
|
887
|
+
const address = this.directAddresses.get(key);
|
|
888
|
+
if (address === undefined || address.instanceCount < input.instanceCount) {
|
|
889
|
+
return err(
|
|
890
|
+
surfaceDynamicInputError(
|
|
891
|
+
'the direct Surface draw resolves one retained frame base with the same instance count',
|
|
892
|
+
`rebuild the producer projection for draw ${drawKey} at ordinal ${firstInstanceOrdinal} (instances=${input.instanceCount})`,
|
|
893
|
+
),
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
const probeOffset = input.probeOffset ?? 0;
|
|
897
|
+
if (!Number.isSafeInteger(probeOffset) || probeOffset < 0) {
|
|
898
|
+
return err(
|
|
899
|
+
surfaceDynamicInputError(
|
|
900
|
+
'the direct Surface Probe offset is a non-negative safe integer',
|
|
901
|
+
'discard the draw and rebuild its producer-owned Probe binding before retrying',
|
|
902
|
+
),
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
const retainedProbeBuffer = input.probeBuffer ?? this.probeBlendRecordBuffer;
|
|
906
|
+
if (probeOffset !== 0 && retainedProbeBuffer === undefined) {
|
|
907
|
+
return err(
|
|
908
|
+
surfaceDynamicInputError(
|
|
909
|
+
'a non-zero direct Surface Probe offset has its matched producer Probe buffer',
|
|
910
|
+
'forward the producer Probe buffer with the direct draw or use a zero Probe offset',
|
|
911
|
+
),
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
const probeBuffer = retainedProbeBuffer ?? this.dynamicInputBuffer;
|
|
915
|
+
const cached = this.directBindGroups.get(key);
|
|
916
|
+
if (
|
|
917
|
+
cached !== undefined &&
|
|
918
|
+
cached.instanceBuffer === input.instanceBuffer &&
|
|
919
|
+
cached.probeBuffer === probeBuffer &&
|
|
920
|
+
cached.addressBuffer === this.directAddressBuffer
|
|
921
|
+
) {
|
|
922
|
+
return ok({
|
|
923
|
+
bindGroup: cached.bindGroup,
|
|
924
|
+
dynamicOffsets: [probeOffset],
|
|
925
|
+
frameBase: address.frameBase,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
const created = this.device.createBindGroup({
|
|
929
|
+
label: `surface-direct-instances-${input.worldId}-${input.entityKey}-${input.drawItemIndex}`,
|
|
930
|
+
layout,
|
|
931
|
+
entries: [
|
|
932
|
+
{ binding: 0, resource: bufferBinding(input.instanceBuffer) },
|
|
933
|
+
{
|
|
934
|
+
binding: 1,
|
|
935
|
+
resource: bufferBinding(
|
|
936
|
+
probeBuffer,
|
|
937
|
+
0,
|
|
938
|
+
retainedProbeBuffer === undefined ? 16 : PROBE_BLEND_RECORD_BYTE_SIZE,
|
|
939
|
+
),
|
|
940
|
+
},
|
|
941
|
+
{ binding: 3, resource: bufferBinding(this.dynamicInputBuffer) },
|
|
942
|
+
{ binding: 4, resource: bufferBinding(this.dynamicFrameBuffer) },
|
|
943
|
+
{ binding: 5, resource: bufferBinding(this.dynamicSharedFrame.buffer) },
|
|
944
|
+
{
|
|
945
|
+
binding: 6,
|
|
946
|
+
resource: bufferBinding(
|
|
947
|
+
this.directAddressBuffer,
|
|
948
|
+
address.byteOffset,
|
|
949
|
+
SURFACE_SHARED_FRAME_BYTES,
|
|
950
|
+
),
|
|
951
|
+
},
|
|
952
|
+
],
|
|
953
|
+
});
|
|
954
|
+
if (!created.ok) return created;
|
|
955
|
+
this.directBindGroups.set(key, {
|
|
956
|
+
instanceBuffer: input.instanceBuffer,
|
|
957
|
+
probeBuffer,
|
|
958
|
+
addressBuffer: this.directAddressBuffer,
|
|
959
|
+
bindGroup: created.value,
|
|
960
|
+
});
|
|
961
|
+
return ok({
|
|
962
|
+
bindGroup: created.value,
|
|
963
|
+
dynamicOffsets: [probeOffset],
|
|
964
|
+
frameBase: address.frameBase,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Upload the generic Surface page and frame/range records through the same
|
|
970
|
+
* prepared GPU-driven owner that binds instances and visible rows.
|
|
971
|
+
*/
|
|
972
|
+
ensureSurfaceDynamicInput(
|
|
973
|
+
frame: SurfaceDynamicInputFrame | undefined,
|
|
974
|
+
recordCapacity: number,
|
|
975
|
+
frameTime = frame?.frameTime ?? 0,
|
|
976
|
+
expectedLayout?: MaterialDynamicInputLayout,
|
|
977
|
+
expectedDeviceGeneration?: number,
|
|
978
|
+
frameRanges?: readonly SurfaceFrameRange[],
|
|
979
|
+
): Result<void, RhiError> {
|
|
980
|
+
this.pendingDynamicInputUpload = undefined;
|
|
981
|
+
if (!Number.isFinite(frameTime)) {
|
|
982
|
+
return err(
|
|
983
|
+
surfaceDynamicInputError(
|
|
984
|
+
'the frame-time snapshot is finite',
|
|
985
|
+
'pass the World/App time snapshot to the Render owner',
|
|
986
|
+
),
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
if (!Number.isSafeInteger(recordCapacity) || recordCapacity <= 0) {
|
|
990
|
+
return err(
|
|
991
|
+
surfaceDynamicInputError(
|
|
992
|
+
'the GPU-driven Surface frame capacity is a positive safe integer',
|
|
993
|
+
'prepare the frame from the bounded RenderScene batch capacity',
|
|
994
|
+
),
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
const page = frame?.page;
|
|
998
|
+
if (expectedLayout === undefined && page !== undefined) {
|
|
999
|
+
return err(
|
|
1000
|
+
surfaceDynamicInputError(
|
|
1001
|
+
'a dynamic input page is supplied only when the published Surface ABI declares one',
|
|
1002
|
+
'remove the page or recook the material with its dynamicInput declaration',
|
|
1003
|
+
),
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
if (expectedLayout !== undefined && page === undefined) {
|
|
1007
|
+
return err(
|
|
1008
|
+
surfaceDynamicInputError(
|
|
1009
|
+
'the published Surface ABI dynamicInput page is present for this draw',
|
|
1010
|
+
'publish the renderer-owned page before preparing the Surface draw',
|
|
1011
|
+
),
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
const ranges = frame?.ranges ?? [];
|
|
1015
|
+
let compactFrameRanges = frameRanges;
|
|
1016
|
+
if (compactFrameRanges === undefined) {
|
|
1017
|
+
if (ranges === this.dynamicInputRangeSource && this.dynamicInputFrameRanges !== undefined) {
|
|
1018
|
+
compactFrameRanges = this.dynamicInputFrameRanges;
|
|
1019
|
+
} else {
|
|
1020
|
+
compactFrameRanges = Object.freeze(
|
|
1021
|
+
ranges.map(
|
|
1022
|
+
(range) => ({ frameIndex: range.instanceIndex, range }) satisfies SurfaceFrameRange,
|
|
1023
|
+
),
|
|
1024
|
+
);
|
|
1025
|
+
this.dynamicInputRangeSource = ranges;
|
|
1026
|
+
this.dynamicInputFrameRanges = compactFrameRanges;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
const frameRangesChanged = compactFrameRanges !== this.dynamicFrameRangeSource;
|
|
1030
|
+
if (frameRangesChanged) {
|
|
1031
|
+
const frameIndices = new Set<number>();
|
|
1032
|
+
for (const entry of compactFrameRanges) {
|
|
1033
|
+
if (!Number.isSafeInteger(entry.frameIndex) || entry.frameIndex < 0) {
|
|
1034
|
+
return err(
|
|
1035
|
+
surfaceDynamicInputError(
|
|
1036
|
+
'every renderer-owned Surface frame index is a non-negative safe integer',
|
|
1037
|
+
'derive the visible-member row from the retained GPU Scene window',
|
|
1038
|
+
),
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
if (frameIndices.has(entry.frameIndex)) {
|
|
1042
|
+
return err(
|
|
1043
|
+
surfaceDynamicInputError(
|
|
1044
|
+
'each Surface frame row has one renderer-owned visible member',
|
|
1045
|
+
'repair the filtered scene-index membership before encoding the frame',
|
|
1046
|
+
),
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
frameIndices.add(entry.frameIndex);
|
|
1050
|
+
if (entry.range !== undefined && !ranges.includes(entry.range)) {
|
|
1051
|
+
return err(
|
|
1052
|
+
surfaceDynamicInputError(
|
|
1053
|
+
'each Surface row references a range from the submitted frame',
|
|
1054
|
+
'derive the visible-member mapping from the same SurfaceDynamicInputFrame instance',
|
|
1055
|
+
),
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
let uploaded: DynamicInputUploadReceipt | undefined;
|
|
1061
|
+
if (page !== undefined) {
|
|
1062
|
+
if (!sameDynamicInputLayout(page.layout, expectedLayout ?? page.layout)) {
|
|
1063
|
+
return err(
|
|
1064
|
+
surfaceDynamicInputError(
|
|
1065
|
+
'the submitted Surface dynamic page matches the cooked MaterialProgramAbi layout',
|
|
1066
|
+
'recreate the page from the published dynamicInput layout before drawing',
|
|
1067
|
+
),
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
if (
|
|
1071
|
+
expectedDeviceGeneration !== undefined &&
|
|
1072
|
+
page.deviceGeneration !== expectedDeviceGeneration
|
|
1073
|
+
) {
|
|
1074
|
+
return err(
|
|
1075
|
+
surfaceDynamicInputError(
|
|
1076
|
+
'the submitted Surface dynamic page belongs to the current renderer device generation',
|
|
1077
|
+
'reconfigure the page and retry after device recovery',
|
|
1078
|
+
),
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
if (
|
|
1082
|
+
page.bytes.byteLength !== page.schema.maxPageBytes ||
|
|
1083
|
+
page.layout.stride * page.schema.maxRecords > page.bytes.byteLength
|
|
1084
|
+
) {
|
|
1085
|
+
return err(
|
|
1086
|
+
surfaceDynamicInputError(
|
|
1087
|
+
'the dynamic page byte length covers its derived bounded record layout',
|
|
1088
|
+
'recreate the page through ReadonlyDynamicInputPage.create()',
|
|
1089
|
+
),
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
const begun = page.beginUpload();
|
|
1093
|
+
if (!begun.ok) {
|
|
1094
|
+
return err(
|
|
1095
|
+
surfaceDynamicInputError(
|
|
1096
|
+
'a live Surface dynamic input page can produce an upload receipt',
|
|
1097
|
+
begun.error.hint,
|
|
1098
|
+
),
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
uploaded = begun.value;
|
|
1102
|
+
}
|
|
1103
|
+
if (frameRangesChanged) {
|
|
1104
|
+
const rangeAddresses = new Set<number>();
|
|
1105
|
+
for (const range of ranges) {
|
|
1106
|
+
if (page === undefined) {
|
|
1107
|
+
return err(
|
|
1108
|
+
surfaceDynamicInputError(
|
|
1109
|
+
'Surface dynamic ranges are paired with their renderer-owned page',
|
|
1110
|
+
'provide the page that owns the submitted ranges',
|
|
1111
|
+
),
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
const invalidRange = invalidSurfaceDynamicRange(page, range, expectedDeviceGeneration);
|
|
1115
|
+
if (invalidRange !== undefined) {
|
|
1116
|
+
return err(
|
|
1117
|
+
surfaceDynamicInputError(
|
|
1118
|
+
'every Surface dynamic range matches the published page schema and generation',
|
|
1119
|
+
invalidRange,
|
|
1120
|
+
),
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
if (rangeAddresses.has(range.instanceIndex)) {
|
|
1124
|
+
return err(
|
|
1125
|
+
surfaceDynamicInputError(
|
|
1126
|
+
'each Surface dynamic instance address has one unambiguous record range',
|
|
1127
|
+
'merge duplicate ranges before publishing the frame projection',
|
|
1128
|
+
),
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
rangeAddresses.add(range.instanceIndex);
|
|
1132
|
+
}
|
|
1133
|
+
const directAddresses = this.ensureDirectSurfaceAddresses(compactFrameRanges);
|
|
1134
|
+
if (!directAddresses.ok) return directAddresses;
|
|
1135
|
+
}
|
|
1136
|
+
const pageChanged = page !== undefined && page !== this.dynamicInputPage;
|
|
1137
|
+
const pageGenerationChanged =
|
|
1138
|
+
page !== undefined && page.bufferGeneration !== this.dynamicInputPageGeneration;
|
|
1139
|
+
if (page !== undefined) {
|
|
1140
|
+
const requiredBytes = Math.max(16, page.bytes.byteLength);
|
|
1141
|
+
const maxStorageBufferBindingSize = this.device.limits.maxStorageBufferBindingSize;
|
|
1142
|
+
if (
|
|
1143
|
+
Number.isFinite(maxStorageBufferBindingSize) &&
|
|
1144
|
+
requiredBytes > maxStorageBufferBindingSize
|
|
1145
|
+
) {
|
|
1146
|
+
return err(
|
|
1147
|
+
new RhiError({
|
|
1148
|
+
code: 'limit-exceeded',
|
|
1149
|
+
expected: 'the Surface dynamic page fits maxStorageBufferBindingSize',
|
|
1150
|
+
hint: 'reduce the published dynamic page budget before entering the GPU lane',
|
|
1151
|
+
detail: { maxStorageBufferBindingSize, requestedBytes: requiredBytes },
|
|
1152
|
+
}),
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
if (requiredBytes > this.dynamicInputBufferSize || pageChanged || pageGenerationChanged) {
|
|
1156
|
+
const replacement = this.device.createBuffer({
|
|
1157
|
+
label: 'surface-dynamic-input-page',
|
|
1158
|
+
size: requiredBytes,
|
|
1159
|
+
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
1160
|
+
});
|
|
1161
|
+
if (!replacement.ok) return replacement;
|
|
1162
|
+
this.trackOwnedBuffer(replacement.value, requiredBytes);
|
|
1163
|
+
const initialWrite = this.device.queue.writeBuffer(replacement.value, 0, page.bytes);
|
|
1164
|
+
if (!initialWrite.ok) {
|
|
1165
|
+
this.rollbackOwnedBuffer(replacement.value);
|
|
1166
|
+
return initialWrite;
|
|
1167
|
+
}
|
|
1168
|
+
const previous = this.dynamicInputBuffer;
|
|
1169
|
+
const previousSize = this.dynamicInputBufferSize;
|
|
1170
|
+
const previousPage = this.dynamicInputPage;
|
|
1171
|
+
const previousGeneration = this.dynamicInputPageGeneration;
|
|
1172
|
+
this.dynamicInputBuffer = replacement.value;
|
|
1173
|
+
this.dynamicInputBufferSize = requiredBytes;
|
|
1174
|
+
this.dynamicInputPage = page;
|
|
1175
|
+
this.dynamicInputPageGeneration = page.bufferGeneration;
|
|
1176
|
+
this.surfaceInputBuffers.add(replacement.value);
|
|
1177
|
+
const rebound = this.rebuildInstancesBindGroup();
|
|
1178
|
+
if (!rebound.ok) {
|
|
1179
|
+
this.dynamicInputBuffer = previous;
|
|
1180
|
+
this.dynamicInputBufferSize = previousSize;
|
|
1181
|
+
this.dynamicInputPage = previousPage;
|
|
1182
|
+
this.dynamicInputPageGeneration = previousGeneration;
|
|
1183
|
+
this.surfaceInputBuffers.delete(replacement.value);
|
|
1184
|
+
this.rollbackOwnedBuffer(replacement.value);
|
|
1185
|
+
return rebound;
|
|
1186
|
+
}
|
|
1187
|
+
this.visibleWindowBindGroups.clear();
|
|
1188
|
+
this.directBindGroups.clear();
|
|
1189
|
+
this.retireProjectionBuffer(previous);
|
|
1190
|
+
} else {
|
|
1191
|
+
for (const range of uploaded?.ranges ?? []) {
|
|
1192
|
+
const write = this.device.queue.writeBuffer(
|
|
1193
|
+
this.dynamicInputBuffer,
|
|
1194
|
+
range.byteStart,
|
|
1195
|
+
page.bytes.slice(range.byteStart, range.byteEnd),
|
|
1196
|
+
);
|
|
1197
|
+
if (!write.ok) return write;
|
|
1198
|
+
}
|
|
1199
|
+
this.dynamicInputPage = page;
|
|
1200
|
+
this.dynamicInputPageGeneration = page.bufferGeneration;
|
|
1201
|
+
}
|
|
1202
|
+
this.pendingDynamicInputUpload =
|
|
1203
|
+
uploaded === undefined ? undefined : { page, receipt: uploaded };
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const compactRangeCapacity = frameRangesChanged
|
|
1207
|
+
? compactFrameRanges.reduce((capacity, entry) => Math.max(capacity, entry.frameIndex + 1), 0)
|
|
1208
|
+
: this.dynamicFrameRangeCapacity;
|
|
1209
|
+
const capacity = Math.max(1, recordCapacity, compactRangeCapacity);
|
|
1210
|
+
if (capacity > Number.MAX_SAFE_INTEGER / SURFACE_FRAME_RECORD_BYTES) {
|
|
1211
|
+
return err(
|
|
1212
|
+
surfaceDynamicInputError(
|
|
1213
|
+
'the Surface frame record allocation fits the JavaScript safe integer range',
|
|
1214
|
+
'reduce the explicit instance address range before preparing the frame',
|
|
1215
|
+
),
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
const requiredFrameBytes = capacity * SURFACE_FRAME_RECORD_BYTES;
|
|
1219
|
+
const maxStorageBufferBindingSize = this.device.limits.maxStorageBufferBindingSize;
|
|
1220
|
+
if (
|
|
1221
|
+
Number.isFinite(maxStorageBufferBindingSize) &&
|
|
1222
|
+
requiredFrameBytes > maxStorageBufferBindingSize
|
|
1223
|
+
) {
|
|
1224
|
+
return err(
|
|
1225
|
+
new RhiError({
|
|
1226
|
+
code: 'limit-exceeded',
|
|
1227
|
+
expected: 'the Surface frame records fit maxStorageBufferBindingSize',
|
|
1228
|
+
hint: 'reduce the explicit instance address range or the batch capacity',
|
|
1229
|
+
detail: { maxStorageBufferBindingSize, requestedBytes: requiredFrameBytes },
|
|
1230
|
+
}),
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
let frameBufferReplaced = false;
|
|
1234
|
+
if (requiredFrameBytes > this.dynamicFrameBufferSize) {
|
|
1235
|
+
const replacement = this.device.createBuffer({
|
|
1236
|
+
label: 'surface-dynamic-frame-input',
|
|
1237
|
+
size: requiredFrameBytes,
|
|
1238
|
+
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
1239
|
+
});
|
|
1240
|
+
if (!replacement.ok) return replacement;
|
|
1241
|
+
this.trackOwnedBuffer(replacement.value, requiredFrameBytes);
|
|
1242
|
+
const previous = this.dynamicFrameBuffer;
|
|
1243
|
+
const previousSize = this.dynamicFrameBufferSize;
|
|
1244
|
+
this.dynamicFrameBuffer = replacement.value;
|
|
1245
|
+
this.dynamicFrameBufferSize = requiredFrameBytes;
|
|
1246
|
+
this.surfaceInputBuffers.add(replacement.value);
|
|
1247
|
+
const rebound = this.rebuildInstancesBindGroup();
|
|
1248
|
+
if (!rebound.ok) {
|
|
1249
|
+
this.dynamicFrameBuffer = previous;
|
|
1250
|
+
this.dynamicFrameBufferSize = previousSize;
|
|
1251
|
+
this.surfaceInputBuffers.delete(replacement.value);
|
|
1252
|
+
this.rollbackOwnedBuffer(replacement.value);
|
|
1253
|
+
return rebound;
|
|
1254
|
+
}
|
|
1255
|
+
this.visibleWindowBindGroups.clear();
|
|
1256
|
+
this.directBindGroups.clear();
|
|
1257
|
+
this.retireProjectionBuffer(previous);
|
|
1258
|
+
this.dynamicFrameRows.clear();
|
|
1259
|
+
this.dynamicFrameRangeSource = undefined;
|
|
1260
|
+
frameBufferReplaced = true;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
if (frameTime !== this.dynamicSharedFrame.frameTime) {
|
|
1264
|
+
const payload = new Float32Array(4);
|
|
1265
|
+
payload[0] = frameTime;
|
|
1266
|
+
const write = this.device.queue.writeBuffer(this.dynamicSharedFrame.buffer, 0, payload);
|
|
1267
|
+
if (!write.ok) return write;
|
|
1268
|
+
this.dynamicSharedFrame.frameTime = frameTime;
|
|
1269
|
+
this.dynamicSharedFrame.writes += 1;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
if (!frameBufferReplaced && !frameRangesChanged) {
|
|
1273
|
+
return ok(undefined);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
const desiredFrameRows = new Map<number, SurfaceFrameRow>();
|
|
1277
|
+
for (const entry of compactFrameRanges) {
|
|
1278
|
+
this.dynamicFrameMemberScans += 1;
|
|
1279
|
+
desiredFrameRows.set(entry.frameIndex, {
|
|
1280
|
+
eventRangeStart: entry.range?.recordStart ?? 0,
|
|
1281
|
+
eventRangeCount: entry.range?.recordCount ?? 0,
|
|
1282
|
+
instanceIndex: entry.range?.instanceIndex ?? 0,
|
|
1283
|
+
// A Surface submission is prepared only for the typed medium graph,
|
|
1284
|
+
// whose Standard producer has already published the sampled raw-depth
|
|
1285
|
+
// pair. Keep this bit in the frame ABI so the shader consumes the
|
|
1286
|
+
// graph-paired resources rather than interpreting zero facts as a
|
|
1287
|
+
// zero optical path.
|
|
1288
|
+
backgroundAvailable: true,
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
// A compact index can disappear when visibility or LOD changes. Clear
|
|
1292
|
+
// the old row while its buffer still exists so a reused visible window
|
|
1293
|
+
// cannot observe the prior event range. The zero row is retained in the
|
|
1294
|
+
// CPU cache and therefore costs one write only at retirement.
|
|
1295
|
+
for (const [frameIndex] of this.dynamicFrameRows) {
|
|
1296
|
+
if (desiredFrameRows.has(frameIndex)) continue;
|
|
1297
|
+
if ((frameIndex + 1) * SURFACE_FRAME_RECORD_BYTES > this.dynamicFrameBufferSize) {
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
desiredFrameRows.set(frameIndex, {
|
|
1301
|
+
eventRangeStart: 0,
|
|
1302
|
+
eventRangeCount: 0,
|
|
1303
|
+
instanceIndex: 0,
|
|
1304
|
+
backgroundAvailable: false,
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
for (const [frameIndex, row] of desiredFrameRows) {
|
|
1308
|
+
const previous = this.dynamicFrameRows.get(frameIndex);
|
|
1309
|
+
if (!frameBufferReplaced && previous !== undefined && sameSurfaceFrameRow(previous, row))
|
|
1310
|
+
continue;
|
|
1311
|
+
const payload = new Uint8Array(SURFACE_FRAME_RECORD_BYTES);
|
|
1312
|
+
this.dynamicFrameRowAllocations += 1;
|
|
1313
|
+
writeSurfaceFrameRecord(
|
|
1314
|
+
new DataView(payload.buffer),
|
|
1315
|
+
0,
|
|
1316
|
+
0,
|
|
1317
|
+
row.eventRangeStart,
|
|
1318
|
+
row.eventRangeCount,
|
|
1319
|
+
row.instanceIndex,
|
|
1320
|
+
row.backgroundAvailable,
|
|
1321
|
+
);
|
|
1322
|
+
const frameWrite = this.device.queue.writeBuffer(
|
|
1323
|
+
this.dynamicFrameBuffer,
|
|
1324
|
+
frameIndex * SURFACE_FRAME_RECORD_BYTES,
|
|
1325
|
+
payload,
|
|
1326
|
+
);
|
|
1327
|
+
if (!frameWrite.ok) return frameWrite;
|
|
1328
|
+
this.dynamicFrameRows.set(frameIndex, row);
|
|
1329
|
+
}
|
|
1330
|
+
this.dynamicFrameRangeSource = compactFrameRanges;
|
|
1331
|
+
this.dynamicFrameRangeCapacity = compactRangeCapacity;
|
|
1332
|
+
return ok(undefined);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
commitSurfaceDynamicInput(): Result<void, RhiError> {
|
|
1336
|
+
if (this.pendingDynamicInputUpload === undefined) return ok(undefined);
|
|
1337
|
+
const { page, receipt } = this.pendingDynamicInputUpload;
|
|
1338
|
+
const committed = page.commitUpload(receipt);
|
|
1339
|
+
if (!committed.ok) {
|
|
1340
|
+
return err(
|
|
1341
|
+
surfaceDynamicInputError(
|
|
1342
|
+
'the Surface dynamic page upload remains current through every queue write',
|
|
1343
|
+
committed.error.hint,
|
|
1344
|
+
),
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
this.pendingDynamicInputUpload = undefined;
|
|
1348
|
+
return ok(undefined);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
abortSurfaceDynamicInput(): void {
|
|
1352
|
+
this.pendingDynamicInputUpload = undefined;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
private rebuildInstancesBindGroup(): Result<void, RhiError> {
|
|
1356
|
+
const created = this.device.createBindGroup({
|
|
1357
|
+
label: 'standard-pbr-gpu-driven-visible-instances-bg',
|
|
1358
|
+
layout: this.instancesBindGroupLayout,
|
|
1359
|
+
entries: [
|
|
1360
|
+
{ binding: 0, resource: bufferBinding(this.instanceBuffer, 0, this.instanceBufferSize) },
|
|
1361
|
+
{
|
|
1362
|
+
binding: 1,
|
|
1363
|
+
resource: bufferBinding(this.probeBlendRecordBuffer ?? this.visibleBuffer),
|
|
1364
|
+
},
|
|
1365
|
+
{ binding: 2, resource: bufferBinding(this.visibleBuffer) },
|
|
1366
|
+
{ binding: 3, resource: bufferBinding(this.dynamicInputBuffer) },
|
|
1367
|
+
{ binding: 4, resource: bufferBinding(this.dynamicFrameBuffer) },
|
|
1368
|
+
{ binding: 5, resource: bufferBinding(this.dynamicSharedFrame.buffer) },
|
|
1369
|
+
],
|
|
1370
|
+
});
|
|
1371
|
+
if (!created.ok) return created;
|
|
1372
|
+
this.instancesBindGroup = created.value;
|
|
1373
|
+
return ok(undefined);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
pipeline(
|
|
1377
|
+
format: TextureFormat,
|
|
1378
|
+
sampleCount: 1 | 4,
|
|
1379
|
+
layoutProjection: MeshGpuHandles['layoutProjection'],
|
|
1380
|
+
shaderUvSetCount: number,
|
|
1381
|
+
topology: GpuDrivenBatch['key']['topology'],
|
|
1382
|
+
stripIndexFormat: 'uint16' | 'uint32' | undefined,
|
|
1383
|
+
renderState: MaterialRenderState | undefined,
|
|
1384
|
+
selectedArtifact: MaterialShaderArtifact = this.artifact,
|
|
1385
|
+
additionalColorFormats: readonly TextureFormat[] = [],
|
|
1386
|
+
): Result<RenderPipeline, RhiError> {
|
|
1387
|
+
const pipelineUvSetCount = isCanonicalStandardPbrMaterialShader(selectedArtifact.material)
|
|
1388
|
+
? shaderUvSetCount
|
|
1389
|
+
: (selectedArtifact.uvSetCount ?? shaderUvSetCount);
|
|
1390
|
+
const vertexLayout = deriveVertexBufferLayoutFromProjection(layoutProjection, {
|
|
1391
|
+
shaderUvSetCount: pipelineUvSetCount,
|
|
1392
|
+
})[0];
|
|
1393
|
+
if (vertexLayout === undefined) {
|
|
1394
|
+
return err(
|
|
1395
|
+
new RhiError({
|
|
1396
|
+
code: 'shader-compile-failed',
|
|
1397
|
+
expected: 'non-empty geometry VertexLayoutProjection',
|
|
1398
|
+
hint: 'prepare a receipt-compatible mesh projection before Standard PBR raster',
|
|
1399
|
+
}),
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
const receipt = selectedArtifact.receipt;
|
|
1403
|
+
if (receipt === undefined) {
|
|
1404
|
+
return err(
|
|
1405
|
+
new RhiError({
|
|
1406
|
+
code: 'rhi-not-available',
|
|
1407
|
+
expected: 'selected MaterialProgramAbi receipt for GPU-driven raster',
|
|
1408
|
+
hint: 'keep the draw on the producer recovery path until its ABI is published',
|
|
1409
|
+
}),
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
const program =
|
|
1413
|
+
this.programs === undefined
|
|
1414
|
+
? undefined
|
|
1415
|
+
: resolveStandardPbrProgram(
|
|
1416
|
+
this.programs,
|
|
1417
|
+
this.artifact.material,
|
|
1418
|
+
layoutProjection.attributes.some((attribute) => attribute.key === 'color'),
|
|
1419
|
+
);
|
|
1420
|
+
if (this.pipelineFactory === undefined && program === undefined) {
|
|
1421
|
+
return err(
|
|
1422
|
+
new RhiError({
|
|
1423
|
+
code: 'rhi-not-available',
|
|
1424
|
+
expected: 'producer-owned Standard PBR program matching the geometry vertex layout',
|
|
1425
|
+
hint: 'publish both vertex-color variants for the current device generation',
|
|
1426
|
+
}),
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
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${pipelineUvSetCount}`;
|
|
1430
|
+
const cached = this.pipelines.get(key);
|
|
1431
|
+
if (cached !== undefined) return ok(cached);
|
|
1432
|
+
if (this.pipelineFactory !== undefined) {
|
|
1433
|
+
const expectsClusteredGroup =
|
|
1434
|
+
isStandardPbrMaterialShader(selectedArtifact.material) &&
|
|
1435
|
+
(selectedArtifact.variantSet === '' ||
|
|
1436
|
+
selectedArtifact.variantSet?.includes('CLUSTER_FORWARD_AVAILABLE=true') === true);
|
|
1437
|
+
const layoutKind = expectsClusteredGroup
|
|
1438
|
+
? this.deformation === 'skin'
|
|
1439
|
+
? 'gpu-driven-cluster-skin'
|
|
1440
|
+
: 'gpu-driven-cluster-pbr'
|
|
1441
|
+
: this.deformation === 'skin'
|
|
1442
|
+
? 'gpu-driven-skin'
|
|
1443
|
+
: 'gpu-driven-pbr';
|
|
1444
|
+
const pipelineEntry = this.pipelineFactory(
|
|
1445
|
+
selectedArtifact.specializationKey ?? selectedArtifact.material,
|
|
1446
|
+
false,
|
|
1447
|
+
renderState,
|
|
1448
|
+
topology,
|
|
1449
|
+
stripIndexFormat,
|
|
1450
|
+
selectedArtifact.variantSet,
|
|
1451
|
+
'forward',
|
|
1452
|
+
undefined,
|
|
1453
|
+
sampleCount,
|
|
1454
|
+
format,
|
|
1455
|
+
pipelineUvSetCount,
|
|
1456
|
+
'depth24plus-stencil8',
|
|
1457
|
+
undefined,
|
|
1458
|
+
layoutProjection,
|
|
1459
|
+
// GPU-driven receipts are already producer-validated. Use the
|
|
1460
|
+
// synchronous shader-module adapter for this submission seam so a
|
|
1461
|
+
// newly loaded authored material cannot lose its first draw to the
|
|
1462
|
+
// ordinary async pre-bake warm-up window.
|
|
1463
|
+
'immediate',
|
|
1464
|
+
layoutKind,
|
|
1465
|
+
receipt.sceneIndexEntry,
|
|
1466
|
+
additionalColorFormats,
|
|
1467
|
+
selectedArtifact.vertexEntry,
|
|
1468
|
+
selectedArtifact.fragmentEntry,
|
|
1469
|
+
);
|
|
1470
|
+
if (pipelineEntry === null) {
|
|
1471
|
+
return err(
|
|
1472
|
+
new RhiError({
|
|
1473
|
+
code: 'rhi-not-available',
|
|
1474
|
+
expected: `producer-owned scene-index pipeline for material '${selectedArtifact.material}'`,
|
|
1475
|
+
hint: 'publish a matching program, entry, layout and resource contract before GPU admission',
|
|
1476
|
+
}),
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
const expectedGroup2Contract = expectsClusteredGroup
|
|
1480
|
+
? this.deformation === 'skin'
|
|
1481
|
+
? 'skin-cluster'
|
|
1482
|
+
: 'cluster'
|
|
1483
|
+
: this.deformation === 'skin'
|
|
1484
|
+
? 'skin'
|
|
1485
|
+
: 'mesh';
|
|
1486
|
+
if (pipelineEntry.group2Contract !== expectedGroup2Contract) {
|
|
1487
|
+
return err(
|
|
1488
|
+
new RhiError({
|
|
1489
|
+
code: 'rhi-descriptor-invalid',
|
|
1490
|
+
expected: `GPU-driven ${this.deformation === 'skin' ? 'skinned ' : ''}material pipeline group(2) ${expectedGroup2Contract} contract`,
|
|
1491
|
+
hint: 'custom attachment or incompatible group(2) layouts remain on their specialized lane',
|
|
1492
|
+
}),
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
this.pipelines.set(key, pipelineEntry.pipeline);
|
|
1496
|
+
return ok(pipelineEntry.pipeline);
|
|
1497
|
+
}
|
|
1498
|
+
if (
|
|
1499
|
+
program === undefined ||
|
|
1500
|
+
selectedArtifact.material !== this.artifact.material ||
|
|
1501
|
+
selectedArtifact.layoutIdentity !== this.artifact.layoutIdentity ||
|
|
1502
|
+
selectedArtifact.receipt?.receiptIdentity !== this.artifact.receipt?.receiptIdentity
|
|
1503
|
+
) {
|
|
1504
|
+
return err(
|
|
1505
|
+
new RhiError({
|
|
1506
|
+
code: 'rhi-not-available',
|
|
1507
|
+
expected: 'selected material pipeline factory for the requested ABI artifact',
|
|
1508
|
+
hint: 'do not pair a receipt-backed custom or color variant with the legacy fixed module',
|
|
1509
|
+
}),
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
const pipeline = this.device.createRenderPipeline({
|
|
1513
|
+
label: `standard-pbr-gpu-driven.${key}`,
|
|
1514
|
+
layout: this.pipelineLayout,
|
|
1515
|
+
vertex: {
|
|
1516
|
+
module: program.module,
|
|
1517
|
+
entryPoint: selectedArtifact.vertexEntry ?? receipt.sceneIndexEntry,
|
|
1518
|
+
buffers: [
|
|
1519
|
+
{
|
|
1520
|
+
arrayStride: vertexLayout.arrayStride,
|
|
1521
|
+
stepMode: 'vertex',
|
|
1522
|
+
attributes: vertexLayout.attributes.map(({ shaderLocation, offset, format }) => ({
|
|
1523
|
+
shaderLocation,
|
|
1524
|
+
offset,
|
|
1525
|
+
format: format as GPUVertexFormat,
|
|
1526
|
+
})),
|
|
1527
|
+
},
|
|
1528
|
+
],
|
|
1529
|
+
},
|
|
1530
|
+
fragment: {
|
|
1531
|
+
module: program.module,
|
|
1532
|
+
entryPoint: 'fs_main',
|
|
1533
|
+
targets: [
|
|
1534
|
+
{ format },
|
|
1535
|
+
...additionalColorFormats.map((additionalFormat) => ({ format: additionalFormat })),
|
|
1536
|
+
],
|
|
1537
|
+
},
|
|
1538
|
+
primitive: {
|
|
1539
|
+
topology,
|
|
1540
|
+
cullMode: renderState?.cullMode ?? 'back',
|
|
1541
|
+
frontFace: renderState?.frontFace ?? 'ccw',
|
|
1542
|
+
...(stripIndexFormat === undefined ? {} : { stripIndexFormat }),
|
|
1543
|
+
},
|
|
1544
|
+
depthStencil: {
|
|
1545
|
+
format: 'depth24plus-stencil8',
|
|
1546
|
+
depthWriteEnabled: renderState?.depthWriteEnabled ?? true,
|
|
1547
|
+
depthCompare: renderState?.depthCompare ?? 'less',
|
|
1548
|
+
},
|
|
1549
|
+
...(sampleCount === 4 ? { multisample: { count: 4 } } : {}),
|
|
1550
|
+
});
|
|
1551
|
+
if (!pipeline.ok) return pipeline;
|
|
1552
|
+
this.pipelines.set(key, pipeline.value);
|
|
1553
|
+
return pipeline;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
/**
|
|
1557
|
+
* Drop only program-derived caches when a publication switches. Resource
|
|
1558
|
+
* buffers stay alive until their normal fence retirement, while the next
|
|
1559
|
+
* draw must obtain a PSO for the new artifact rather than reuse the old one.
|
|
1560
|
+
*/
|
|
1561
|
+
invalidatePipelineCache(): void {
|
|
1562
|
+
this.pipelines.clear();
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
projectionBindGroup(
|
|
1566
|
+
rows: Float32Array,
|
|
1567
|
+
key?: string,
|
|
1568
|
+
capacityRows?: number,
|
|
1569
|
+
): Result<BindGroup, RhiError> {
|
|
1570
|
+
if (key !== undefined) {
|
|
1571
|
+
const capacityBytes = Math.max(
|
|
1572
|
+
MESH_PER_ENTITY_STRIDE,
|
|
1573
|
+
(capacityRows ?? Math.ceil(rows.byteLength / MESH_SSBO_BYTES)) * MESH_SSBO_BYTES,
|
|
1574
|
+
);
|
|
1575
|
+
const retained = this.stagedProjectionBindings.get(key) ?? this.projectionBindings.get(key);
|
|
1576
|
+
if (retained !== undefined && retained.capacityBytes >= capacityBytes) {
|
|
1577
|
+
const write = this.writeProjectionRows(key, retained, rows);
|
|
1578
|
+
if (!write.ok) return write;
|
|
1579
|
+
return ok(retained.bindGroup);
|
|
1580
|
+
}
|
|
1581
|
+
const created = this.createProjectionBinding(rows, capacityBytes);
|
|
1582
|
+
if (!created.ok) return created;
|
|
1583
|
+
this.stagedProjectionBindings.set(key, created.value);
|
|
1584
|
+
return ok(created.value.bindGroup);
|
|
1585
|
+
}
|
|
1586
|
+
const created = this.createProjectionBinding(
|
|
1587
|
+
rows,
|
|
1588
|
+
Math.max(MESH_PER_ENTITY_STRIDE, rows.byteLength),
|
|
1589
|
+
);
|
|
1590
|
+
if (!created.ok) return created;
|
|
1591
|
+
return ok(created.value.bindGroup);
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
projectionBuffer(
|
|
1595
|
+
key: string,
|
|
1596
|
+
): { readonly buffer: Buffer; readonly bindingBytes: number } | undefined {
|
|
1597
|
+
const projection = this.stagedProjectionBindings.get(key) ?? this.projectionBindings.get(key);
|
|
1598
|
+
return projection === undefined
|
|
1599
|
+
? undefined
|
|
1600
|
+
: { buffer: projection.buffer, bindingBytes: projection.capacityBytes };
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
refreshProjectionRows(key: string, rows: Float32Array): Result<void, RhiError> {
|
|
1604
|
+
const retained = this.stagedProjectionBindings.get(key) ?? this.projectionBindings.get(key);
|
|
1605
|
+
if (retained === undefined) return ok(undefined);
|
|
1606
|
+
if (rows.byteLength > retained.capacityBytes) {
|
|
1607
|
+
const capacityBytes = Math.max(
|
|
1608
|
+
MESH_PER_ENTITY_STRIDE,
|
|
1609
|
+
Math.ceil(rows.byteLength / MESH_SSBO_BYTES) * MESH_SSBO_BYTES,
|
|
1610
|
+
);
|
|
1611
|
+
const replacement = this.createProjectionBinding(rows, capacityBytes);
|
|
1612
|
+
if (!replacement.ok) return replacement;
|
|
1613
|
+
const previousStaged = this.stagedProjectionBindings.get(key);
|
|
1614
|
+
if (previousStaged !== undefined && previousStaged.buffer !== replacement.value.buffer) {
|
|
1615
|
+
this.retireProjectionBuffer(previousStaged.buffer);
|
|
1616
|
+
}
|
|
1617
|
+
this.stagedProjectionBindings.set(key, replacement.value);
|
|
1618
|
+
return ok(undefined);
|
|
1619
|
+
}
|
|
1620
|
+
return this.writeProjectionRows(key, retained, rows);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
/** Stage the projection keys required by the frame being prepared. */
|
|
1624
|
+
stageProjectionKeys(keys: ReadonlySet<string>): void {
|
|
1625
|
+
this.stagedProjectionKeys = new Set(keys);
|
|
1626
|
+
this.activeProjectionRowsAtStage.clear();
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
/** Add a shadow projection key discovered while updating a view plan. */
|
|
1630
|
+
noteProjectionKey(key: string): void {
|
|
1631
|
+
this.stagedProjectionKeys.add(key);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/** Promote staged projection resources after the command buffer submits. */
|
|
1635
|
+
commitProjectionBindings(): void {
|
|
1636
|
+
const keep = new Set(this.stagedProjectionKeys);
|
|
1637
|
+
for (const [key, staged] of this.stagedProjectionBindings) {
|
|
1638
|
+
if (!keep.has(key)) {
|
|
1639
|
+
this.retireProjectionBuffer(staged.buffer);
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
const previous = this.projectionBindings.get(key);
|
|
1643
|
+
this.projectionBindings.set(key, staged);
|
|
1644
|
+
if (previous !== undefined && previous.buffer !== staged.buffer) {
|
|
1645
|
+
this.retireProjectionBuffer(previous.buffer);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
this.stagedProjectionBindings.clear();
|
|
1649
|
+
for (const [key, active] of this.projectionBindings) {
|
|
1650
|
+
if (!keep.has(key)) {
|
|
1651
|
+
this.projectionBindings.delete(key);
|
|
1652
|
+
this.retireProjectionBuffer(active.buffer);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
this.stagedProjectionKeys.clear();
|
|
1656
|
+
this.activeProjectionRowsAtStage.clear();
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
/** Discard resources created for an aborted graph while retaining LKG rows. */
|
|
1660
|
+
abortProjectionBindings(): void {
|
|
1661
|
+
for (const [key, rows] of this.activeProjectionRowsAtStage) {
|
|
1662
|
+
const active = this.projectionBindings.get(key);
|
|
1663
|
+
if (active === undefined) continue;
|
|
1664
|
+
const restored = this.device.queue.writeBuffer(active.buffer, 0, rows);
|
|
1665
|
+
if (restored.ok) active.rows = new Float32Array(rows);
|
|
1666
|
+
}
|
|
1667
|
+
for (const staged of this.stagedProjectionBindings.values()) {
|
|
1668
|
+
this.retireProjectionBuffer(staged.buffer);
|
|
1669
|
+
}
|
|
1670
|
+
this.stagedProjectionBindings.clear();
|
|
1671
|
+
this.stagedProjectionKeys.clear();
|
|
1672
|
+
this.activeProjectionRowsAtStage.clear();
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
private createProjectionBinding(
|
|
1676
|
+
rows: Float32Array,
|
|
1677
|
+
capacityBytes: number,
|
|
1678
|
+
): Result<
|
|
1679
|
+
{
|
|
1680
|
+
readonly buffer: Buffer;
|
|
1681
|
+
readonly bindGroup: BindGroup;
|
|
1682
|
+
readonly capacityBytes: number;
|
|
1683
|
+
rows: Float32Array;
|
|
1684
|
+
},
|
|
1685
|
+
RhiError
|
|
1686
|
+
> {
|
|
1687
|
+
const buffer = this.device.createBuffer({
|
|
1688
|
+
label: 'standard-pbr-gpu-driven-scene-meshes',
|
|
1689
|
+
size: capacityBytes,
|
|
1690
|
+
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
1691
|
+
});
|
|
1692
|
+
if (!buffer.ok) return buffer;
|
|
1693
|
+
this.trackOwnedBuffer(buffer.value, capacityBytes);
|
|
1694
|
+
this.projectionBuffers.add(buffer.value);
|
|
1695
|
+
const write = this.device.queue.writeBuffer(buffer.value, 0, rows);
|
|
1696
|
+
if (!write.ok) {
|
|
1697
|
+
this.rollbackOwnedBuffer(buffer.value);
|
|
1698
|
+
return write;
|
|
1699
|
+
}
|
|
1700
|
+
const entries = [
|
|
1701
|
+
{ binding: 0, resource: bufferBinding(buffer.value, 0, capacityBytes) },
|
|
1702
|
+
...(this.deformation === 'skin' &&
|
|
1703
|
+
this.paletteBuffer !== undefined &&
|
|
1704
|
+
this.paletteBindingWindowBytes !== undefined
|
|
1705
|
+
? [
|
|
1706
|
+
{
|
|
1707
|
+
binding: 1,
|
|
1708
|
+
// The scene-index second lane is a global palette row. Bind the
|
|
1709
|
+
// complete arena so rows beyond the legacy 255-joint window
|
|
1710
|
+
// remain addressable; the shader performs the row selection.
|
|
1711
|
+
resource: bufferBinding(this.paletteBuffer),
|
|
1712
|
+
},
|
|
1713
|
+
{
|
|
1714
|
+
binding: 2,
|
|
1715
|
+
// The dedicated GPU-driven skin layout keeps the current /
|
|
1716
|
+
// previous palette pair used by the direct skin layout. The
|
|
1717
|
+
// scene-index shader currently reads the current arena, but
|
|
1718
|
+
// the shared layout still requires a complete binding shape.
|
|
1719
|
+
resource: bufferBinding(this.paletteBuffer),
|
|
1720
|
+
},
|
|
1721
|
+
]
|
|
1722
|
+
: []),
|
|
1723
|
+
];
|
|
1724
|
+
const bindGroup = this.device.createBindGroup({
|
|
1725
|
+
label: 'standard-pbr-gpu-driven-scene-mesh-bg',
|
|
1726
|
+
layout: this.meshLayout,
|
|
1727
|
+
entries,
|
|
1728
|
+
});
|
|
1729
|
+
if (!bindGroup.ok) {
|
|
1730
|
+
this.rollbackOwnedBuffer(buffer.value);
|
|
1731
|
+
return bindGroup;
|
|
1732
|
+
}
|
|
1733
|
+
return ok({
|
|
1734
|
+
buffer: buffer.value,
|
|
1735
|
+
bindGroup: bindGroup.value,
|
|
1736
|
+
capacityBytes,
|
|
1737
|
+
rows: new Float32Array(rows),
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
private writeProjectionRows(
|
|
1742
|
+
key: string,
|
|
1743
|
+
binding: {
|
|
1744
|
+
readonly buffer: Buffer;
|
|
1745
|
+
readonly bindGroup: BindGroup;
|
|
1746
|
+
readonly capacityBytes: number;
|
|
1747
|
+
rows: Float32Array;
|
|
1748
|
+
},
|
|
1749
|
+
rows: Float32Array,
|
|
1750
|
+
): Result<void, RhiError> {
|
|
1751
|
+
// Active bindings are the last-known-good graph resources. Keep one CPU
|
|
1752
|
+
// snapshot so an aborted candidate can restore their exact contents
|
|
1753
|
+
// without allocating a second GPU buffer on every stable frame.
|
|
1754
|
+
if (
|
|
1755
|
+
this.stagedProjectionBindings.get(key) === undefined &&
|
|
1756
|
+
!this.activeProjectionRowsAtStage.has(key)
|
|
1757
|
+
) {
|
|
1758
|
+
this.activeProjectionRowsAtStage.set(key, new Float32Array(binding.rows));
|
|
1759
|
+
}
|
|
1760
|
+
const write = this.device.queue.writeBuffer(binding.buffer, 0, rows);
|
|
1761
|
+
if (!write.ok) return write;
|
|
1762
|
+
binding.rows = new Float32Array(rows);
|
|
1763
|
+
return ok(undefined);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
dispose(): void {
|
|
1767
|
+
if (this.projectionDisposed) return;
|
|
1768
|
+
this.projectionDisposed = true;
|
|
1769
|
+
for (const binding of this.projectionBindings.values()) {
|
|
1770
|
+
this.destroyProjectionBuffer(binding.buffer);
|
|
1771
|
+
}
|
|
1772
|
+
for (const binding of this.stagedProjectionBindings.values()) {
|
|
1773
|
+
this.destroyProjectionBuffer(binding.buffer);
|
|
1774
|
+
}
|
|
1775
|
+
for (const buffer of this.projectionBuffers) this.destroyProjectionBuffer(buffer);
|
|
1776
|
+
for (const buffer of this.pendingProjectionRetirements) {
|
|
1777
|
+
this.destroyProjectionBuffer(buffer);
|
|
1778
|
+
}
|
|
1779
|
+
this.projectionBindings.clear();
|
|
1780
|
+
this.stagedProjectionBindings.clear();
|
|
1781
|
+
this.activeProjectionRowsAtStage.clear();
|
|
1782
|
+
this.projectionBuffers.clear();
|
|
1783
|
+
this.pendingProjectionRetirements.clear();
|
|
1784
|
+
this.dynamicFrameRows.clear();
|
|
1785
|
+
this.dynamicFrameRangeSource = undefined;
|
|
1786
|
+
this.dynamicInputRangeSource = undefined;
|
|
1787
|
+
this.dynamicInputFrameRanges = undefined;
|
|
1788
|
+
this.dynamicFrameRangeCapacity = 0;
|
|
1789
|
+
this.destroyProjectionBuffer(this.instanceBuffer);
|
|
1790
|
+
for (const buffer of this.surfaceInputBuffers) this.destroyProjectionBuffer(buffer);
|
|
1791
|
+
this.surfaceInputBuffers.clear();
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
private retireProjectionBuffer(buffer: Buffer): void {
|
|
1795
|
+
if (this.destroyedProjectionBuffers.has(buffer)) return;
|
|
1796
|
+
this.projectionBuffers.delete(buffer);
|
|
1797
|
+
if (this.projectionDisposed) {
|
|
1798
|
+
this.destroyProjectionBuffer(buffer);
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
if (!this.pendingProjectionRetirements.add(buffer)) return;
|
|
1802
|
+
const token = this.allocationTokens.get(buffer);
|
|
1803
|
+
if (token !== undefined) this.allocationLedger.retire(token);
|
|
1804
|
+
void this.device.queue.onSubmittedWorkDone().then(
|
|
1805
|
+
() => {
|
|
1806
|
+
this.pendingProjectionRetirements.delete(buffer);
|
|
1807
|
+
this.destroyProjectionBuffer(buffer);
|
|
1808
|
+
},
|
|
1809
|
+
() => {
|
|
1810
|
+
this.pendingProjectionRetirements.delete(buffer);
|
|
1811
|
+
this.destroyProjectionBuffer(buffer);
|
|
1812
|
+
},
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
private destroyProjectionBuffer(buffer: Buffer): void {
|
|
1817
|
+
if (this.destroyedProjectionBuffers.has(buffer)) return;
|
|
1818
|
+
this.destroyedProjectionBuffers.add(buffer);
|
|
1819
|
+
this.projectionBuffers.delete(buffer);
|
|
1820
|
+
this.surfaceInputBuffers.delete(buffer);
|
|
1821
|
+
const token = this.allocationTokens.get(buffer);
|
|
1822
|
+
if (token !== undefined) this.allocationLedger.release(token);
|
|
1823
|
+
this.device.destroyBuffer(buffer);
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
private trackOwnedBuffer(buffer: Buffer, bytes: number): void {
|
|
1827
|
+
if (this.allocationTokens.has(buffer)) return;
|
|
1828
|
+
this.allocationTokens.set(buffer, this.allocationLedger.allocate(bytes));
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
private rollbackOwnedBuffer(buffer: Buffer): void {
|
|
1832
|
+
if (this.destroyedProjectionBuffers.has(buffer)) return;
|
|
1833
|
+
this.destroyedProjectionBuffers.add(buffer);
|
|
1834
|
+
this.projectionBuffers.delete(buffer);
|
|
1835
|
+
this.surfaceInputBuffers.delete(buffer);
|
|
1836
|
+
this.pendingProjectionRetirements.delete(buffer);
|
|
1837
|
+
const token = this.allocationTokens.get(buffer);
|
|
1838
|
+
if (token !== undefined) this.allocationLedger.rollback(token);
|
|
1839
|
+
this.device.destroyBuffer(buffer);
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
get instancesFrameGroup(): BindGroup {
|
|
1843
|
+
return this.instancesBindGroup;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
get surfaceDynamicInputBuffer(): Buffer {
|
|
1847
|
+
return this.dynamicInputBuffer;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
get surfaceDynamicFrameBuffer(): Buffer {
|
|
1851
|
+
return this.dynamicFrameBuffer;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
get surfaceDynamicSharedFrameBuffer(): Buffer {
|
|
1855
|
+
return this.dynamicSharedFrame.buffer;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
get surfaceDynamicInputBufferSize(): number {
|
|
1859
|
+
return this.dynamicInputBufferSize;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
get surfaceDynamicFrameBufferSize(): number {
|
|
1863
|
+
return this.dynamicFrameBufferSize;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
get surfaceDynamicSharedFrameBufferSize(): number {
|
|
1867
|
+
return SURFACE_SHARED_FRAME_BYTES;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
get surfaceDynamicFrameRowAllocations(): number {
|
|
1871
|
+
return this.dynamicFrameRowAllocations;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
get surfaceDynamicFrameMemberScans(): number {
|
|
1875
|
+
return this.dynamicFrameMemberScans;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
/** Logical bytes owned by this adapter; external scene buffers are excluded. */
|
|
1879
|
+
get resourceAllocation(): import('../inspection-types').GpuResourceAllocationInspection {
|
|
1880
|
+
return this.allocationLedger.inspect();
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
get surfaceDynamicFrameTimeWrites(): number {
|
|
1884
|
+
return this.dynamicSharedFrame.writes;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
get surfaceDynamicSharedFrameState(): SurfaceSharedFrameState {
|
|
1888
|
+
return this.dynamicSharedFrame;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
get kind(): 'rigid' | 'skin' {
|
|
1892
|
+
return this.deformation;
|
|
1893
|
+
}
|
|
1894
|
+
}
|