@forgeax/engine-render 0.1.27 → 0.1.29
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 +842 -124
- package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
- package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/dynamic-geometry-host.d.ts +52 -0
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -0
- package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
- package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +3 -1
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/gpu-driven-owner.d.ts +10 -0
- package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
- package/dist/assembly/host-contract.d.ts +19 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +7 -0
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material-shader-policy.d.ts +3 -1
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/recovery/failure-location.d.ts +12 -0
- package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts +6 -4
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
- package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
- package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-helpers.d.ts +3 -0
- package/dist/assembly/renderer-helpers.d.ts.map +1 -1
- package/dist/assembly/renderer-inspection.d.ts +4 -0
- package/dist/assembly/renderer-inspection.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +6 -0
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
- package/dist/assembly/skin-palette-owner.d.ts +8 -0
- package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
- package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
- package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/webgpu-device-loss.d.ts +15 -0
- package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
- package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +7 -3
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
- package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
- package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
- package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
- package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +7 -141
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
- package/dist/authoring.mjs +3 -3
- package/dist/{chunk-ADHHVYLW.mjs → chunk-33XZFTQU.mjs} +245 -39
- package/dist/chunk-33XZFTQU.mjs.map +1 -0
- package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
- package/dist/chunk-BLXGONR2.mjs.map +1 -0
- package/dist/{chunk-KM2NOX2I.mjs → chunk-BQVQOQJH.mjs} +958 -254
- package/dist/chunk-BQVQOQJH.mjs.map +1 -0
- package/dist/{chunk-HKXTW355.mjs → chunk-BTWH7BP4.mjs} +386 -17
- package/dist/chunk-BTWH7BP4.mjs.map +1 -0
- package/dist/{chunk-TMIASV2N.mjs → chunk-FUODTTIA.mjs} +3934 -676
- package/dist/chunk-FUODTTIA.mjs.map +1 -0
- package/dist/{chunk-FJ6P52EE.mjs → chunk-ILQ5MEZF.mjs} +3 -17
- package/dist/chunk-ILQ5MEZF.mjs.map +1 -0
- package/dist/{chunk-E34VL5VI.mjs → chunk-JQTSEI3Z.mjs} +3 -3
- package/dist/{chunk-E34VL5VI.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
- package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
- package/dist/chunk-NICXSHAT.mjs.map +1 -0
- package/dist/{chunk-ZZ4YQ474.mjs → chunk-NRNXBLIJ.mjs} +577 -7
- package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
- package/dist/{chunk-JDWARUOI.mjs → chunk-PA5DRJNI.mjs} +17 -3
- package/dist/chunk-PA5DRJNI.mjs.map +1 -0
- package/dist/{chunk-XDP6XE5V.mjs → chunk-S4DNKQYQ.mjs} +1118 -446
- package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
- package/dist/components/camera.d.ts +62 -2
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/instances.d.ts +6 -20
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/components/scene-instance.d.ts +2 -1
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/screen-space-reflection.d.ts +14 -0
- package/dist/components/screen-space-reflection.d.ts.map +1 -0
- package/dist/construct-renderer.mjs +13805 -7186
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +11 -17
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/mesh-residency-lifetime.d.ts +21 -0
- package/dist/device/mesh-residency-lifetime.d.ts.map +1 -0
- package/dist/dynamic-geometry.d.ts +125 -0
- package/dist/dynamic-geometry.d.ts.map +1 -0
- package/dist/environment/background.d.ts +6 -0
- package/dist/environment/background.d.ts.map +1 -0
- package/dist/errors/gpu-driven.d.ts +6 -3
- package/dist/errors/gpu-driven.d.ts.map +1 -1
- package/dist/errors/render.d.ts +7 -4
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +33 -3
- package/dist/extract/gpu-driven.d.ts.map +1 -1
- package/dist/features/host.d.ts +23 -1
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/noise-texture.d.ts +17 -0
- package/dist/features/noise-texture.d.ts.map +1 -0
- package/dist/features/plan.d.ts +83 -2
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +37 -4
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/prepared-graphics-store.d.ts.map +1 -1
- package/dist/features/prepared-graphics.d.ts +5 -0
- package/dist/features/prepared-graphics.d.ts.map +1 -1
- package/dist/features/render-graph-compute.d.ts +5 -3
- package/dist/features/render-graph-compute.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts +11 -3
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/features/render-graph-raster.d.ts +19 -3
- package/dist/features/render-graph-raster.d.ts.map +1 -1
- package/dist/features/types.d.ts +11 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +2 -0
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts +34 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/pbr-program.d.ts +10 -0
- package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +144 -23
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/shadow-views.d.ts +126 -0
- package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
- package/dist/gpu-driven/view-gpu.d.ts +18 -2
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +2 -8
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +14 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-texture-usage.d.ts +1 -0
- package/dist/gpu-texture-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +6 -0
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/frame-resources.d.ts +8 -0
- package/dist/ibl/frame-resources.d.ts.map +1 -0
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +21 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +8 -7
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +164 -0
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +11 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -1
- package/dist/instances.d.ts +89 -44
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +118 -10
- package/dist/internal.mjs.map +1 -1
- package/dist/lifecycle.d.ts +2 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/material-row.d.ts +5 -0
- package/dist/material-row.d.ts.map +1 -0
- package/dist/materials.d.ts +15 -0
- package/dist/materials.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +21 -5
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
- package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
- package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
- package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
- package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
- package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
- package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
- package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
- package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
- package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
- package/dist/pipeline/standard-output/graph.d.ts +15 -0
- package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
- package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
- package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
- package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
- package/dist/pipeline/standard-output/resources.d.ts +10 -0
- package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
- package/dist/pipeline/standard-output/types.d.ts +39 -0
- package/dist/pipeline/standard-output/types.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +2 -2
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec-types.d.ts +4 -0
- package/dist/pipeline-spec-types.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +1 -9
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/points-lines/standard-owner.d.ts +1 -1
- package/dist/points-lines/standard-owner.d.ts.map +1 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
- package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
- package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +3 -3
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +106 -5
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +39 -4
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +7 -8
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +38 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +2 -10
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +12 -2
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +15 -6
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/prepared-material-bindings.d.ts +19 -0
- package/dist/record/prepared-material-bindings.d.ts.map +1 -0
- package/dist/record/recovery-pipeline.d.ts +2 -9
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +76 -26
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +62 -7
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +35 -7
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +1 -0
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +10 -2
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/recovery/render-system-roots.d.ts +11 -0
- package/dist/recovery/render-system-roots.d.ts.map +1 -0
- package/dist/recovery/types.d.ts +76 -0
- package/dist/recovery/types.d.ts.map +1 -0
- package/dist/reflection/record-owner.d.ts +0 -2
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +185 -30
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +64 -5
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +7 -5
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +101 -41
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system-projections.d.ts +22 -0
- package/dist/render-system-projections.d.ts.map +1 -0
- package/dist/render-system.d.ts +34 -77
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene-types.d.ts +14 -7
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +29 -11
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/shadow-visibility.d.ts +5 -0
- package/dist/scene/shadow-visibility.d.ts.map +1 -0
- package/dist/ssr/admission.d.ts +70 -1
- package/dist/ssr/admission.d.ts.map +1 -1
- package/dist/ssr/compose.d.ts +13 -0
- package/dist/ssr/compose.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +35 -0
- package/dist/ssr/errors.d.ts.map +1 -1
- package/dist/ssr/graph.d.ts +60 -0
- package/dist/ssr/graph.d.ts.map +1 -0
- package/dist/ssr/history.d.ts +97 -0
- package/dist/ssr/history.d.ts.map +1 -0
- package/dist/ssr/hiz.d.ts +37 -0
- package/dist/ssr/hiz.d.ts.map +1 -0
- package/dist/ssr/inspection.d.ts +42 -0
- package/dist/ssr/inspection.d.ts.map +1 -0
- package/dist/ssr/resources.d.ts +58 -0
- package/dist/ssr/resources.d.ts.map +1 -0
- package/dist/ssr/temporal.d.ts +38 -0
- package/dist/ssr/temporal.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +32 -10
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/systems/skin-palette-types.d.ts +15 -0
- package/dist/systems/skin-palette-types.d.ts.map +1 -1
- package/dist/systems/transparent-dispatch.d.ts +9 -0
- package/dist/systems/transparent-dispatch.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +7 -0
- package/dist/targets/material-source.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts +7 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/gpu.d.ts +8 -0
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/history.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -6
- package/dist/temporal/inspection.d.ts.map +1 -1
- package/dist/temporal/standard-scene-data.d.ts +2 -1
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/temporal-view.d.ts +2 -0
- package/dist/temporal/temporal-view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +35 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +3 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/temporal.d.ts +6 -0
- package/dist/volume/temporal.d.ts.map +1 -1
- package/package.json +21 -20
- package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
- package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
- package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
- package/src/__tests__/auto-exposure.browser.test.ts +33 -0
- package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
- package/src/__tests__/detached-inspection.integration.test.ts +5 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
- package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
- package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
- package/src/__tests__/dynamic-geometry-host.unit.test.ts +1123 -0
- package/src/__tests__/dynamic-geometry.unit.test.ts +233 -0
- package/src/__tests__/factory-contract.integration.test.ts +73 -22
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
- package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
- package/src/__tests__/feature-noise.unit.test.ts +38 -0
- package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
- package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
- package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
- package/src/__tests__/frame-targets.unit.test.ts +24 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
- package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
- package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
- package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
- package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
- package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
- package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
- package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
- package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
- package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
- package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
- package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
- package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
- package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
- package/src/__tests__/gpu-scene.unit.test.ts +559 -33
- package/src/__tests__/instances-culling.integration.test.ts +36 -10
- package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
- package/src/__tests__/instances-store.unit.test.ts +242 -39
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
- package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
- package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
- package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
- package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
- package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
- package/src/__tests__/material-texture-source.unit.test.ts +87 -0
- package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
- package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +53 -0
- package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
- package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
- package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
- package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
- package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
- package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
- package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
- package/src/__tests__/public-surface.test-d.ts +3 -0
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
- package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
- package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
- package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
- package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
- package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
- package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
- package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
- package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
- package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
- package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
- package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
- package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
- package/src/__tests__/render-target-material-source.test-d.ts +27 -3
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +61 -1
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
- package/src/__tests__/shader-manifest-fixture.ts +39 -0
- package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
- package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
- package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
- package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
- package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
- package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
- package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
- package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
- package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
- package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
- package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
- package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
- package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
- package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
- package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
- package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
- package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
- package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
- package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
- package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
- package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
- package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
- package/src/__tests__/ssr-reprojection-probe.ts +90 -0
- package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
- package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
- package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
- package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
- package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
- package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
- package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
- package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
- package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
- package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
- package/src/__tests__/standard-output.browser.test.ts +15 -0
- package/src/__tests__/standard-output.dawn.test.ts +15 -0
- package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
- package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
- package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
- package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
- package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
- package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
- package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
- package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
- package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
- package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
- package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
- package/src/__tests__/taa-stability-feedback.ts +341 -0
- package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
- package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
- package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
- package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
- package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
- package/src/__tests__/visibility-extract.perf.test.ts +20 -8
- package/src/__tests__/visibility-instances.integration.test.ts +19 -7
- package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
- package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
- package/src/assembly/dynamic-geometry-host.ts +887 -0
- package/src/assembly/dynamic-geometry-runtime.ts +71 -0
- package/src/assembly/factory.ts +4 -0
- package/src/assembly/gpu-driven-owner.ts +29 -0
- package/src/assembly/host-contract.ts +36 -0
- package/src/assembly/material/assembly.ts +38 -0
- package/src/assembly/material-shader-policy.ts +71 -28
- package/src/assembly/recovery/failure-location.ts +62 -0
- package/src/assembly/recovery/renderer-recover.ts +19 -17
- package/src/assembly/renderer-facade.ts +29 -0
- package/src/assembly/renderer-feature-inspection.ts +13 -0
- package/src/assembly/renderer-frame-transaction.ts +63 -0
- package/src/assembly/renderer-helpers.ts +26 -0
- package/src/assembly/renderer-inspection.ts +6 -0
- package/src/assembly/shader-prewarm-policy.ts +32 -0
- package/src/assembly/skin-palette-owner.ts +21 -0
- package/src/assembly/ssr-shader-prewarm.ts +41 -0
- package/src/assembly/webgpu-device-loss.ts +75 -0
- package/src/assembly/webgpu-pbr-ready.ts +232 -0
- package/src/assembly/webgpu-ready-mipmap.ts +26 -0
- package/src/assembly/webgpu-ready.ts +142 -121
- package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
- package/src/assembly/webgpu-renderer-contract.ts +69 -0
- package/src/assembly/webgpu-renderer-guards.ts +32 -0
- package/src/assembly/webgpu-renderer-observation.ts +43 -0
- package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
- package/src/assembly/webgpu-renderer.ts +675 -614
- package/src/assembly/webgpu-vertex-layouts.ts +233 -87
- package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
- package/src/components/camera.ts +287 -7
- package/src/components/index.ts +1 -0
- package/src/components/instances.ts +45 -18
- package/src/components/scene-instance.ts +2 -1
- package/src/components/screen-space-reflection.ts +21 -0
- package/src/components/sprite-animation.ts +3 -3
- package/src/components/sprite-instances.ts +1 -1
- package/src/device/gpu-residency.ts +96 -93
- package/src/device/mesh-residency-lifetime.ts +73 -0
- package/src/dynamic-geometry.ts +736 -0
- package/src/environment/background.ts +263 -0
- package/src/errors/gpu-driven.ts +39 -5
- package/src/errors/render.ts +27 -3
- package/src/extract/camera.ts +26 -1
- package/src/extract/gpu-driven.ts +185 -10
- package/src/features/host.ts +237 -19
- package/src/features/noise-texture.ts +91 -0
- package/src/features/plan.ts +553 -24
- package/src/features/prepared-gpu-work.ts +631 -49
- package/src/features/prepared-graphics-store.ts +31 -0
- package/src/features/prepared-graphics.ts +5 -0
- package/src/features/render-graph-compute.ts +41 -8
- package/src/features/render-graph-contribution.ts +141 -3
- package/src/features/render-graph-raster.ts +143 -30
- package/src/features/types.ts +11 -0
- package/src/fullscreen-post-process-pass.ts +3 -0
- package/src/gpu-driven/batch-topology.ts +424 -102
- package/src/gpu-driven/pbr-program.ts +31 -0
- package/src/gpu-driven/prepared-draw.ts +29 -4
- package/src/gpu-driven/production-raster.ts +2267 -694
- package/src/gpu-driven/shadow-views.ts +539 -0
- package/src/gpu-driven/view-gpu.ts +188 -41
- package/src/gpu-scene-schema.ts +52 -11
- package/src/gpu-scene.ts +186 -99
- package/src/gpu-texture-usage.ts +1 -0
- package/src/hdrp-buffers.ts +72 -36
- package/src/ibl/frame-resources.ts +38 -0
- package/src/ibl/skylight-bind-group.ts +5 -4
- package/src/index.ts +125 -1
- package/src/inspection-types.ts +191 -0
- package/src/instances-derived-bounds.ts +258 -34
- package/src/instances.ts +459 -150
- package/src/internal.ts +22 -0
- package/src/lifecycle.ts +2 -0
- package/src/material-row.ts +145 -0
- package/src/materials.ts +44 -8
- package/src/pbr-pipeline.ts +159 -17
- package/src/pipeline/standard-forward-lane.ts +52 -24
- package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
- package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
- package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
- package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
- package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
- package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
- package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
- package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
- package/src/pipeline/standard-output/color-transform.ts +124 -0
- package/src/pipeline/standard-output/graph.ts +139 -0
- package/src/pipeline/standard-output/lut-admission.ts +121 -0
- package/src/pipeline/standard-output/lut-gpu.ts +399 -0
- package/src/pipeline/standard-output/lut-state.ts +176 -0
- package/src/pipeline/standard-output/resources.ts +121 -0
- package/src/pipeline/standard-output/types.ts +58 -0
- package/src/pipeline/standard-pipeline.ts +192 -59
- package/src/pipeline/standard-post.ts +186 -27
- package/src/pipeline-builder.ts +16 -6
- package/src/pipeline-spec-types.ts +4 -0
- package/src/pipeline-spec.ts +37 -29
- package/src/points-lines/standard-owner.ts +6 -14
- package/src/prepare/prepared-graphics-resolver.ts +9 -2
- package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
- package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
- package/src/record/dynamic-geometry-consumption.ts +59 -0
- package/src/record/frame-lighting.ts +12 -11
- package/src/record/frame-snapshot.ts +133 -23
- package/src/record/frame-targets.ts +9 -0
- package/src/record/frame.ts +727 -133
- package/src/record/main-pass-geometry.ts +162 -98
- package/src/record/main-pass-material.ts +115 -26
- package/src/record/main-pass-sprite-draws.ts +18 -107
- package/src/record/main-pass.ts +78 -8
- package/src/record/material-uniforms.ts +1 -1
- package/src/record/mesh-ssbo.ts +59 -15
- package/src/record/prepared-material-bindings.ts +234 -0
- package/src/record/recovery-pipeline.ts +59 -136
- package/src/record/render-context.ts +92 -22
- package/src/record/shadow-pass.ts +1231 -209
- package/src/record/skybox-post-pass.ts +14 -3
- package/src/record/typed-frame-graph.ts +1006 -53
- package/src/record/view-ubo.ts +11 -0
- package/src/recovery/render-system-candidate.ts +90 -66
- package/src/recovery/render-system-roots.ts +113 -0
- package/src/recovery/types.ts +77 -0
- package/src/reflection/record-owner.ts +26 -54
- package/src/render-contract.ts +219 -25
- package/src/render-graph-primitives.ts +6 -2
- package/src/render-pipeline.ts +70 -1
- package/src/render-system-extract-tail.ts +409 -160
- package/src/render-system-extract.ts +376 -102
- package/src/render-system-projections.ts +241 -0
- package/src/render-system.ts +692 -791
- package/src/scene/render-scene-types.ts +13 -4
- package/src/scene/render-scene.ts +569 -355
- package/src/scene/shadow-visibility.ts +21 -0
- package/src/ssr/admission.ts +380 -14
- package/src/ssr/compose.ts +144 -0
- package/src/ssr/errors.ts +48 -0
- package/src/ssr/graph.ts +777 -0
- package/src/ssr/history.ts +436 -0
- package/src/ssr/hiz.ts +124 -0
- package/src/ssr/inspection.ts +124 -0
- package/src/ssr/resources.ts +202 -0
- package/src/ssr/temporal.ts +164 -0
- package/src/systems/skin-palette-allocator.ts +495 -148
- package/src/systems/skin-palette-types.ts +17 -0
- package/src/systems/transparent-dispatch.ts +90 -0
- package/src/targets/material-source.ts +34 -0
- package/src/temporal/coverage.ts +1 -1
- package/src/temporal/frame-coordinator.ts +23 -2
- package/src/temporal/gpu.ts +74 -12
- package/src/temporal/history.ts +3 -1
- package/src/temporal/inspection.ts +2 -1
- package/src/temporal/standard-scene-data.ts +3 -1
- package/src/temporal/temporal-view.ts +3 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
- package/src/typed-render-graph-primitives.ts +266 -74
- package/src/typed-shadow-passes.ts +122 -12
- package/src/volume/temporal.ts +10 -0
- package/dist/chunk-ADHHVYLW.mjs.map +0 -1
- package/dist/chunk-FJ6P52EE.mjs.map +0 -1
- package/dist/chunk-GE3SDD72.mjs.map +0 -1
- package/dist/chunk-GF523LHF.mjs.map +0 -1
- package/dist/chunk-HKXTW355.mjs.map +0 -1
- package/dist/chunk-JDWARUOI.mjs.map +0 -1
- package/dist/chunk-KM2NOX2I.mjs.map +0 -1
- package/dist/chunk-TMIASV2N.mjs.map +0 -1
- package/dist/chunk-XDP6XE5V.mjs.map +0 -1
- package/dist/chunk-ZZ4YQ474.mjs.map +0 -1
- package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
|
@@ -3,20 +3,22 @@ import { World } from '@forgeax/engine-ecs';
|
|
|
3
3
|
import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
|
|
4
4
|
import { vec3 } from '@forgeax/engine-math';
|
|
5
5
|
import { RenderGraphBuilder } from '@forgeax/engine-render-graph';
|
|
6
|
-
import type
|
|
6
|
+
import { type BindGroup, RhiError, type RhiRenderPassEncoder } from '@forgeax/engine-rhi';
|
|
7
7
|
import { type RhiNullDevice, rhi } from '@forgeax/engine-rhi-null';
|
|
8
|
-
import { Transform } from '@forgeax/engine-scene';
|
|
8
|
+
import { propagateTransforms, Transform } from '@forgeax/engine-scene';
|
|
9
9
|
import {
|
|
10
10
|
createStandardPbrArtifactReceipt,
|
|
11
11
|
DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
|
|
12
12
|
DEFAULT_UNLIT_PARAM_SCHEMA,
|
|
13
13
|
} from '@forgeax/engine-shader';
|
|
14
14
|
import { ok } from '@forgeax/engine-types';
|
|
15
|
-
import { describe, expect, it } from 'vitest';
|
|
15
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
16
16
|
import { createRenderer as constructRenderer } from '../assembly/factory';
|
|
17
17
|
import { Camera, MeshFilter, MeshRenderer, PointLight } from '../components';
|
|
18
18
|
import type { MeshGpuHandles } from '../device/gpu-residency';
|
|
19
|
+
import { gpuDrivenDrawKey, gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
|
|
19
20
|
import { BatchTopology } from '../gpu-driven/batch-topology';
|
|
21
|
+
import { standardPbrProgramKey } from '../gpu-driven/pbr-program';
|
|
20
22
|
import {
|
|
21
23
|
adaptStandardPbrFrameResources,
|
|
22
24
|
GpuDrivenProduction,
|
|
@@ -31,10 +33,28 @@ import {
|
|
|
31
33
|
GPU_BUFFER_USAGE_UNIFORM,
|
|
32
34
|
GPU_BUFFER_USAGE_VERTEX,
|
|
33
35
|
} from '../gpu-usage';
|
|
36
|
+
import { buildDispatchPlan } from '../record/frame';
|
|
34
37
|
import { makeZeroCameraFallbackSnapshot, worldEntityKey } from '../record/frame-snapshot';
|
|
38
|
+
import {
|
|
39
|
+
encodeDirectionalShadowPass,
|
|
40
|
+
encodePointShadowPass,
|
|
41
|
+
encodeSpotShadowPass,
|
|
42
|
+
recordShadowCasterDraws,
|
|
43
|
+
shadowShaderMap,
|
|
44
|
+
} from '../record/shadow-pass';
|
|
35
45
|
import type { RenderPipelineFrame } from '../render-pipeline';
|
|
36
|
-
import type {
|
|
37
|
-
|
|
46
|
+
import type {
|
|
47
|
+
DispatchEntry,
|
|
48
|
+
MaterialSnapshot,
|
|
49
|
+
RenderableSnapshot,
|
|
50
|
+
ShadowCasterMembership,
|
|
51
|
+
} from '../render-system-extract';
|
|
52
|
+
import { shadowCasterCpuReason } from '../render-system-extract';
|
|
53
|
+
import { PersistentRenderScene, RenderScene } from '../scene/render-scene';
|
|
54
|
+
import {
|
|
55
|
+
preparedPbrSnapshot as preparedPbrFixture,
|
|
56
|
+
standardPbrInputs,
|
|
57
|
+
} from './gpu-driven-production-fixture';
|
|
38
58
|
|
|
39
59
|
const STANDARD_PBR_RECEIPT = createStandardPbrArtifactReceipt();
|
|
40
60
|
|
|
@@ -136,6 +156,27 @@ function snapshot(entityKey: number, assetHandle: number): RenderableSnapshot {
|
|
|
136
156
|
};
|
|
137
157
|
}
|
|
138
158
|
|
|
159
|
+
function preparedSnapshot(entityKey: number, assetHandle: number): RenderableSnapshot {
|
|
160
|
+
return preparedPbrFixture(snapshot(entityKey, assetHandle));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function withoutPrepared(
|
|
164
|
+
draw: NonNullable<RenderableSnapshot['gpuDrivenDraws']>[number],
|
|
165
|
+
): Omit<typeof draw, 'prepared'> {
|
|
166
|
+
const { prepared, ...rest } = draw;
|
|
167
|
+
void prepared;
|
|
168
|
+
return rest;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function updateSnapshot(value: RenderableSnapshot) {
|
|
172
|
+
return {
|
|
173
|
+
kind: 'update' as const,
|
|
174
|
+
worldId: value.worldId,
|
|
175
|
+
entityKey: value.entityKey,
|
|
176
|
+
snapshot: value,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
139
180
|
function mesh(device: RhiNullDevice, indexed = true, withLod = false): MeshGpuHandles {
|
|
140
181
|
const vertex = device
|
|
141
182
|
.createBuffer({
|
|
@@ -158,7 +199,6 @@ function mesh(device: RhiNullDevice, indexed = true, withLod = false): MeshGpuHa
|
|
|
158
199
|
iboBytes: indexed ? 8 : 0,
|
|
159
200
|
indexCount: indexed ? 3 : 0,
|
|
160
201
|
indexFormat: 'uint16',
|
|
161
|
-
layout: '12F',
|
|
162
202
|
layoutProjection: deriveVertexLayoutProjection({
|
|
163
203
|
position: new Float32Array(9),
|
|
164
204
|
normal: new Float32Array(9),
|
|
@@ -199,10 +239,11 @@ describe('GPU-driven production projection', () => {
|
|
|
199
239
|
entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
|
|
200
240
|
})
|
|
201
241
|
.unwrap();
|
|
242
|
+
const pbrInputs = standardPbrInputs(device, shader, viewLayout);
|
|
202
243
|
const projection = new RenderScene();
|
|
203
244
|
const lodMeshGuid = '00000000-0000-7000-8000-000000000001' as never;
|
|
204
245
|
const makeLodSnapshot = (entityKey: number): RenderableSnapshot => {
|
|
205
|
-
const base = snapshot(entityKey, 3);
|
|
246
|
+
const base = preparedPbrFixture(snapshot(entityKey, 3));
|
|
206
247
|
const draw = base.gpuDrivenDraws?.[0];
|
|
207
248
|
if (draw === undefined) throw new Error('LOD test snapshot has no draw');
|
|
208
249
|
return {
|
|
@@ -212,8 +253,8 @@ describe('GPU-driven production projection', () => {
|
|
|
212
253
|
};
|
|
213
254
|
};
|
|
214
255
|
const delta = projection.apply([
|
|
215
|
-
|
|
216
|
-
|
|
256
|
+
updateSnapshot(makeLodSnapshot(1)),
|
|
257
|
+
updateSnapshot(makeLodSnapshot(2)),
|
|
217
258
|
]);
|
|
218
259
|
const availability = GpuScene.create(device, 2).unwrap();
|
|
219
260
|
expect(availability.status).toBe('available');
|
|
@@ -224,15 +265,20 @@ describe('GPU-driven production projection', () => {
|
|
|
224
265
|
const production = new GpuDrivenProduction(device, {
|
|
225
266
|
createShaderModule: () => ok(shader),
|
|
226
267
|
});
|
|
268
|
+
const lodGpuMesh = mesh(device, true, true);
|
|
227
269
|
const prepared = production
|
|
228
270
|
.prepare({
|
|
271
|
+
...pbrInputs,
|
|
229
272
|
scene: {
|
|
230
273
|
scene: availability.scene,
|
|
231
274
|
plan: topology.plan(),
|
|
232
275
|
slots: projection.slotsSnapshot(),
|
|
233
276
|
},
|
|
234
277
|
camera: makeZeroCameraFallbackSnapshot(),
|
|
235
|
-
|
|
278
|
+
meshBySlot: new Map([
|
|
279
|
+
[0, lodGpuMesh],
|
|
280
|
+
[1, lodGpuMesh],
|
|
281
|
+
]),
|
|
236
282
|
viewBindGroupLayout: viewLayout,
|
|
237
283
|
meshResidencyEpoch: 1,
|
|
238
284
|
hdrp: false,
|
|
@@ -240,7 +286,7 @@ describe('GPU-driven production projection', () => {
|
|
|
240
286
|
activeEntityRevision: 1,
|
|
241
287
|
})
|
|
242
288
|
.unwrap();
|
|
243
|
-
expect(prepared?.
|
|
289
|
+
expect(prepared?.drawKeys).toEqual(new Set(['1:-1:0', '2:-1:0']));
|
|
244
290
|
// Both candidates are uploaded to the selector (2 * 304 bytes), while
|
|
245
291
|
// only the active entity is admitted into the compacted draw stream.
|
|
246
292
|
expect(production.inspect()).toMatchObject({
|
|
@@ -254,7 +300,7 @@ describe('GPU-driven production projection', () => {
|
|
|
254
300
|
.unwrap();
|
|
255
301
|
expect(projected.accesses.length).toBeGreaterThan(0);
|
|
256
302
|
expect(production.inspect()).toMatchObject({
|
|
257
|
-
batchBindGroupCreates:
|
|
303
|
+
batchBindGroupCreates: 0,
|
|
258
304
|
indirectDrawCount: 1,
|
|
259
305
|
});
|
|
260
306
|
expect(production.inspect().indirectDrawCount).toBeGreaterThan(0);
|
|
@@ -262,15 +308,47 @@ describe('GPU-driven production projection', () => {
|
|
|
262
308
|
production.inspect().batchCount,
|
|
263
309
|
);
|
|
264
310
|
|
|
311
|
+
// The scene/view buffers and visible range are unchanged on a warm
|
|
312
|
+
// projection. The raster owner must reuse the physical batch bind group;
|
|
313
|
+
// a new group is only valid after one of those handles or the range
|
|
314
|
+
// changes (resize/recovery/LOD admission).
|
|
315
|
+
const warm = production
|
|
316
|
+
.prepare({
|
|
317
|
+
...pbrInputs,
|
|
318
|
+
scene: {
|
|
319
|
+
scene: availability.scene,
|
|
320
|
+
plan: topology.plan(),
|
|
321
|
+
slots: projection.slotsSnapshot(),
|
|
322
|
+
},
|
|
323
|
+
camera: makeZeroCameraFallbackSnapshot(),
|
|
324
|
+
meshBySlot: new Map([
|
|
325
|
+
[0, lodGpuMesh],
|
|
326
|
+
[1, lodGpuMesh],
|
|
327
|
+
]),
|
|
328
|
+
viewBindGroupLayout: viewLayout,
|
|
329
|
+
meshResidencyEpoch: 1,
|
|
330
|
+
hdrp: false,
|
|
331
|
+
activeEntityKeys: new Set([worldEntityKey(0, 1)]),
|
|
332
|
+
activeEntityRevision: 1,
|
|
333
|
+
})
|
|
334
|
+
.unwrap();
|
|
335
|
+
expect(warm?.topologySignature).toBe(prepared.topologySignature);
|
|
336
|
+
warm?.project(new RenderGraphBuilder<RenderPipelineFrame>(), 'rgba8unorm', 1).unwrap();
|
|
337
|
+
expect(production.inspect()).toMatchObject({ batchBindGroupCreates: 0 });
|
|
338
|
+
|
|
265
339
|
const suppressed = production
|
|
266
340
|
.prepare({
|
|
341
|
+
...pbrInputs,
|
|
267
342
|
scene: {
|
|
268
343
|
scene: availability.scene,
|
|
269
344
|
plan: topology.plan(),
|
|
270
345
|
slots: projection.slotsSnapshot(),
|
|
271
346
|
},
|
|
272
347
|
camera: makeZeroCameraFallbackSnapshot(),
|
|
273
|
-
|
|
348
|
+
meshBySlot: new Map([
|
|
349
|
+
[0, lodGpuMesh],
|
|
350
|
+
[1, lodGpuMesh],
|
|
351
|
+
]),
|
|
274
352
|
viewBindGroupLayout: viewLayout,
|
|
275
353
|
meshResidencyEpoch: 1,
|
|
276
354
|
hdrp: false,
|
|
@@ -286,13 +364,17 @@ describe('GPU-driven production projection', () => {
|
|
|
286
364
|
|
|
287
365
|
const restored = production
|
|
288
366
|
.prepare({
|
|
367
|
+
...pbrInputs,
|
|
289
368
|
scene: {
|
|
290
369
|
scene: availability.scene,
|
|
291
370
|
plan: topology.plan(),
|
|
292
371
|
slots: projection.slotsSnapshot(),
|
|
293
372
|
},
|
|
294
373
|
camera: makeZeroCameraFallbackSnapshot(),
|
|
295
|
-
|
|
374
|
+
meshBySlot: new Map([
|
|
375
|
+
[0, lodGpuMesh],
|
|
376
|
+
[1, lodGpuMesh],
|
|
377
|
+
]),
|
|
296
378
|
viewBindGroupLayout: viewLayout,
|
|
297
379
|
meshResidencyEpoch: 1,
|
|
298
380
|
hdrp: false,
|
|
@@ -306,16 +388,18 @@ describe('GPU-driven production projection', () => {
|
|
|
306
388
|
indirectDrawCount: 1,
|
|
307
389
|
});
|
|
308
390
|
expect(GPU_DRIVEN_VIEW_WGSL.indexOf('atomicAdd(&counters[lodCounterIndex')).toBeLessThan(
|
|
309
|
-
GPU_DRIVEN_VIEW_WGSL.indexOf('if (candidate.submitAdmission == 0u)'),
|
|
391
|
+
GPU_DRIVEN_VIEW_WGSL.indexOf('if ((candidate.submitAdmission & 1u) == 0u)'),
|
|
310
392
|
);
|
|
311
|
-
expect(
|
|
393
|
+
expect(
|
|
394
|
+
GPU_DRIVEN_VIEW_WGSL.indexOf('if ((candidate.submitAdmission & 1u) == 0u)'),
|
|
395
|
+
).toBeLessThan(
|
|
312
396
|
GPU_DRIVEN_VIEW_WGSL.indexOf('atomicAdd(&counters[counterIndex(candidate.batchIndex)], 1u)'),
|
|
313
397
|
);
|
|
314
398
|
production.dispose();
|
|
315
399
|
availability.scene.dispose();
|
|
316
400
|
});
|
|
317
401
|
|
|
318
|
-
it('keeps
|
|
402
|
+
it('keeps an all-suppressed candidate out of the GPU raster frame', async () => {
|
|
319
403
|
const adapter = (await rhi.requestAdapter()).unwrap();
|
|
320
404
|
const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
|
|
321
405
|
const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
|
|
@@ -324,9 +408,10 @@ describe('GPU-driven production projection', () => {
|
|
|
324
408
|
entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
|
|
325
409
|
})
|
|
326
410
|
.unwrap();
|
|
411
|
+
const pbrInputs = standardPbrInputs(device, shader, viewLayout);
|
|
327
412
|
const projection = new RenderScene();
|
|
328
|
-
const candidate = snapshot(1, 3);
|
|
329
|
-
const delta = projection.apply([
|
|
413
|
+
const candidate = preparedPbrFixture(snapshot(1, 3));
|
|
414
|
+
const delta = projection.apply([updateSnapshot(candidate)]);
|
|
330
415
|
const availability = GpuScene.create(device, 1).unwrap();
|
|
331
416
|
expect(availability.status).toBe('available');
|
|
332
417
|
if (availability.status !== 'available') return;
|
|
@@ -338,13 +423,14 @@ describe('GPU-driven production projection', () => {
|
|
|
338
423
|
});
|
|
339
424
|
const prepared = production
|
|
340
425
|
.prepare({
|
|
426
|
+
...pbrInputs,
|
|
341
427
|
scene: {
|
|
342
428
|
scene: availability.scene,
|
|
343
429
|
plan: topology.plan(),
|
|
344
430
|
slots: projection.slotsSnapshot(),
|
|
345
431
|
},
|
|
346
432
|
camera: makeZeroCameraFallbackSnapshot(),
|
|
347
|
-
|
|
433
|
+
meshBySlot: new Map([[0, mesh(device)]]),
|
|
348
434
|
viewBindGroupLayout: viewLayout,
|
|
349
435
|
meshResidencyEpoch: 1,
|
|
350
436
|
hdrp: false,
|
|
@@ -352,17 +438,14 @@ describe('GPU-driven production projection', () => {
|
|
|
352
438
|
activeEntityRevision: 1,
|
|
353
439
|
})
|
|
354
440
|
.unwrap();
|
|
355
|
-
expect(prepared).
|
|
356
|
-
expect(production.requiresSceneRows()).toBe(true);
|
|
441
|
+
expect(prepared).toBeUndefined();
|
|
357
442
|
expect(production.inspect()).toMatchObject({
|
|
358
|
-
batchCount:
|
|
443
|
+
batchCount: 0,
|
|
359
444
|
indirectDrawCount: 0,
|
|
360
445
|
batchBindGroupCreates: 0,
|
|
361
446
|
});
|
|
362
|
-
if (prepared === undefined) return;
|
|
363
|
-
prepared.project(new RenderGraphBuilder<RenderPipelineFrame>(), 'rgba8unorm', 1).unwrap();
|
|
364
447
|
expect(production.inspect()).toMatchObject({
|
|
365
|
-
batchCount:
|
|
448
|
+
batchCount: 0,
|
|
366
449
|
indirectDrawCount: 0,
|
|
367
450
|
batchBindGroupCreates: 0,
|
|
368
451
|
});
|
|
@@ -379,13 +462,11 @@ describe('GPU-driven production projection', () => {
|
|
|
379
462
|
entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
|
|
380
463
|
})
|
|
381
464
|
.unwrap();
|
|
465
|
+
const pbrInputs = standardPbrInputs(device, shader, viewLayout);
|
|
382
466
|
const projection = new RenderScene();
|
|
383
|
-
const first = snapshot(1, 3);
|
|
384
|
-
const second = { ...snapshot(2, 3), worldId: 1 };
|
|
385
|
-
const delta = projection.apply([
|
|
386
|
-
{ kind: 'create', snapshot: first },
|
|
387
|
-
{ kind: 'create', snapshot: second },
|
|
388
|
-
]);
|
|
467
|
+
const first = preparedPbrFixture(snapshot(1, 3));
|
|
468
|
+
const second = { ...preparedPbrFixture(snapshot(2, 3)), worldId: 1 };
|
|
469
|
+
const delta = projection.apply([updateSnapshot(first), updateSnapshot(second)]);
|
|
389
470
|
const availability = GpuScene.create(device, 2).unwrap();
|
|
390
471
|
expect(availability.status).toBe('available');
|
|
391
472
|
if (availability.status !== 'available') return;
|
|
@@ -397,13 +478,17 @@ describe('GPU-driven production projection', () => {
|
|
|
397
478
|
});
|
|
398
479
|
const prepared = production
|
|
399
480
|
.prepare({
|
|
481
|
+
...pbrInputs,
|
|
400
482
|
scene: {
|
|
401
483
|
scene: availability.scene,
|
|
402
484
|
plan: topology.plan(),
|
|
403
485
|
slots: projection.slotsSnapshot(),
|
|
404
486
|
},
|
|
405
487
|
camera: makeZeroCameraFallbackSnapshot(),
|
|
406
|
-
|
|
488
|
+
meshBySlot: new Map([
|
|
489
|
+
[0, mesh(device)],
|
|
490
|
+
[1, mesh(device)],
|
|
491
|
+
]),
|
|
407
492
|
viewBindGroupLayout: viewLayout,
|
|
408
493
|
meshResidencyEpoch: 1,
|
|
409
494
|
hdrp: false,
|
|
@@ -415,7 +500,7 @@ describe('GPU-driven production projection', () => {
|
|
|
415
500
|
availability.scene.dispose();
|
|
416
501
|
});
|
|
417
502
|
|
|
418
|
-
it('uses
|
|
503
|
+
it('uses concrete draw keys for CPU validation telemetry', async () => {
|
|
419
504
|
const adapter = (await rhi.requestAdapter()).unwrap();
|
|
420
505
|
const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
|
|
421
506
|
const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
|
|
@@ -428,24 +513,24 @@ describe('GPU-driven production projection', () => {
|
|
|
428
513
|
lods: [{ mesh: '00000000-0000-7000-8000-000000000001' as never, screenCoverage: 0.5 }],
|
|
429
514
|
};
|
|
430
515
|
const plainFallbackRow = { ...snapshot(2, 3), worldId: 0 };
|
|
431
|
-
const gpuOwned = new Set([
|
|
432
|
-
|
|
516
|
+
const gpuOwned = new Set([
|
|
517
|
+
gpuDrivenDrawKey(worldEntityKey(gpuLodRow.worldId, gpuLodRow.entityKey), -1, 0),
|
|
518
|
+
]);
|
|
433
519
|
|
|
434
520
|
// A GPU-owned row is removed from validation before this telemetry hook;
|
|
435
521
|
// the remaining plain row must still be counted as the one CPU fallback.
|
|
436
|
-
production.recordCpuValidation([{ source: plainFallbackRow }], gpuOwned
|
|
522
|
+
production.recordCpuValidation([{ source: plainFallbackRow }], gpuOwned);
|
|
437
523
|
expect(production.inspect()).toMatchObject({
|
|
438
524
|
cpuFallbackDrawItems: 1,
|
|
439
525
|
validatedGpuOwnedRows: 0,
|
|
440
526
|
});
|
|
441
527
|
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
|
|
445
|
-
production.recordCpuValidation([{ source: gpuLodRow }], gpuOwned, worldKeys);
|
|
528
|
+
// A GPU-only row contributes ownership telemetry without inflating the
|
|
529
|
+
// CPU residual row count.
|
|
530
|
+
production.recordCpuValidation([{ source: gpuLodRow }], gpuOwned);
|
|
446
531
|
expect(production.inspect()).toMatchObject({
|
|
447
|
-
cpuFallbackDrawItems:
|
|
448
|
-
validatedGpuOwnedRows:
|
|
532
|
+
cpuFallbackDrawItems: 0,
|
|
533
|
+
validatedGpuOwnedRows: 0,
|
|
449
534
|
});
|
|
450
535
|
production.dispose();
|
|
451
536
|
});
|
|
@@ -459,10 +544,11 @@ describe('GPU-driven production projection', () => {
|
|
|
459
544
|
entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
|
|
460
545
|
})
|
|
461
546
|
.unwrap();
|
|
547
|
+
const pbrInputs = standardPbrInputs(device, shader, viewLayout);
|
|
462
548
|
const projection = new RenderScene();
|
|
463
549
|
const lodMeshGuid = '00000000-0000-7000-8000-000000000001' as never;
|
|
464
550
|
const makeLodSnapshot = (entityKey: number): RenderableSnapshot => {
|
|
465
|
-
const base = snapshot(entityKey, 3);
|
|
551
|
+
const base = preparedPbrFixture(snapshot(entityKey, 3));
|
|
466
552
|
const draw = base.gpuDrivenDraws?.[0];
|
|
467
553
|
if (draw === undefined) throw new Error('LOD test snapshot has no draw');
|
|
468
554
|
return {
|
|
@@ -472,8 +558,8 @@ describe('GPU-driven production projection', () => {
|
|
|
472
558
|
};
|
|
473
559
|
};
|
|
474
560
|
const delta = projection.apply([
|
|
475
|
-
|
|
476
|
-
|
|
561
|
+
updateSnapshot(makeLodSnapshot(1)),
|
|
562
|
+
updateSnapshot(makeLodSnapshot(2)),
|
|
477
563
|
]);
|
|
478
564
|
const availability = GpuScene.create(device, 2).unwrap();
|
|
479
565
|
expect(availability.status).toBe('available');
|
|
@@ -484,15 +570,20 @@ describe('GPU-driven production projection', () => {
|
|
|
484
570
|
const production = new GpuDrivenProduction(device, {
|
|
485
571
|
createShaderModule: () => ok(shader),
|
|
486
572
|
});
|
|
573
|
+
const lodGpuMesh = mesh(device, true, true);
|
|
487
574
|
const prepared = production
|
|
488
575
|
.prepare({
|
|
576
|
+
...pbrInputs,
|
|
489
577
|
scene: {
|
|
490
578
|
scene: availability.scene,
|
|
491
579
|
plan: topology.plan(),
|
|
492
580
|
slots: projection.slotsSnapshot(),
|
|
493
581
|
},
|
|
494
582
|
camera: { ...makeZeroCameraFallbackSnapshot(), position: vec3.create(0, 0, 5) },
|
|
495
|
-
|
|
583
|
+
meshBySlot: new Map([
|
|
584
|
+
[0, lodGpuMesh],
|
|
585
|
+
[1, lodGpuMesh],
|
|
586
|
+
]),
|
|
496
587
|
viewBindGroupLayout: viewLayout,
|
|
497
588
|
meshResidencyEpoch: 1,
|
|
498
589
|
hdrp: false,
|
|
@@ -582,6 +673,7 @@ describe('GPU-driven production projection', () => {
|
|
|
582
673
|
entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
|
|
583
674
|
})
|
|
584
675
|
.unwrap();
|
|
676
|
+
const pbrInputs = standardPbrInputs(device, shader, viewLayout);
|
|
585
677
|
const viewUniform = device
|
|
586
678
|
.createBuffer({ size: 784, usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST })
|
|
587
679
|
.unwrap();
|
|
@@ -591,11 +683,11 @@ describe('GPU-driven production projection', () => {
|
|
|
591
683
|
entries: [{ binding: 0, resource: { kind: 'buffer', value: { buffer: viewUniform } } }],
|
|
592
684
|
})
|
|
593
685
|
.unwrap();
|
|
594
|
-
const first = snapshot(1, 3);
|
|
686
|
+
const first = preparedPbrFixture(snapshot(1, 3));
|
|
595
687
|
const firstDraw = first.gpuDrivenDraws?.[0];
|
|
596
688
|
expect(firstDraw).toBeDefined();
|
|
597
689
|
if (firstDraw === undefined) return;
|
|
598
|
-
const second = { ...snapshot(2, 4), worldId: 1 };
|
|
690
|
+
const second = { ...preparedPbrFixture(snapshot(2, 4)), worldId: 1 };
|
|
599
691
|
const secondDraw = second.gpuDrivenDraws?.[0];
|
|
600
692
|
expect(secondDraw).toBeDefined();
|
|
601
693
|
if (secondDraw === undefined) return;
|
|
@@ -621,9 +713,7 @@ describe('GPU-driven production projection', () => {
|
|
|
621
713
|
},
|
|
622
714
|
];
|
|
623
715
|
const projection = new RenderScene();
|
|
624
|
-
const delta = projection.apply(
|
|
625
|
-
renderables.map((renderable) => ({ kind: 'create' as const, snapshot: renderable })),
|
|
626
|
-
);
|
|
716
|
+
const delta = projection.apply(renderables.map((renderable) => updateSnapshot(renderable)));
|
|
627
717
|
const availability = GpuScene.create(device, 2).unwrap();
|
|
628
718
|
expect(availability.status).toBe('available');
|
|
629
719
|
if (availability.status !== 'available') return;
|
|
@@ -632,25 +722,26 @@ describe('GPU-driven production projection', () => {
|
|
|
632
722
|
topology.rebuild(projection.slotsSnapshot());
|
|
633
723
|
expect(topology.plan()).toMatchObject({
|
|
634
724
|
candidateCount: 3,
|
|
635
|
-
visibleCapacity:
|
|
636
|
-
batches: [{ visibleBase: 0 }, { visibleBase: 64 }
|
|
725
|
+
visibleCapacity: 65,
|
|
726
|
+
batches: [{ visibleBase: 0 }, { visibleBase: 64 }],
|
|
637
727
|
});
|
|
638
728
|
const production = new GpuDrivenProduction(device, {
|
|
639
729
|
createShaderModule: () => ok(shader),
|
|
640
730
|
});
|
|
641
731
|
const meshes = new Map([
|
|
642
|
-
[
|
|
643
|
-
[
|
|
732
|
+
[0, mesh(device)],
|
|
733
|
+
[1, mesh(device, false)],
|
|
644
734
|
]);
|
|
645
735
|
const prepared = production
|
|
646
736
|
.prepare({
|
|
737
|
+
...pbrInputs,
|
|
647
738
|
scene: {
|
|
648
739
|
scene: availability.scene,
|
|
649
740
|
plan: topology.plan(),
|
|
650
741
|
slots: projection.slotsSnapshot(),
|
|
651
742
|
},
|
|
652
743
|
camera: makeZeroCameraFallbackSnapshot(),
|
|
653
|
-
meshes,
|
|
744
|
+
meshBySlot: meshes,
|
|
654
745
|
viewBindGroupLayout: viewLayout,
|
|
655
746
|
meshResidencyEpoch: 1,
|
|
656
747
|
hdrp: false,
|
|
@@ -658,24 +749,31 @@ describe('GPU-driven production projection', () => {
|
|
|
658
749
|
.unwrap();
|
|
659
750
|
expect(prepared).toBeDefined();
|
|
660
751
|
if (prepared === undefined) return;
|
|
661
|
-
expect(
|
|
752
|
+
expect(prepared.drawKeys).toEqual(
|
|
753
|
+
new Set([
|
|
754
|
+
gpuDrivenDrawKey(worldEntityKey(0, 1), -1, 0),
|
|
755
|
+
gpuDrivenDrawKey(worldEntityKey(0, 1), -1, 1),
|
|
756
|
+
gpuDrivenDrawKey(worldEntityKey(1, 2), 42, 0),
|
|
757
|
+
]),
|
|
758
|
+
);
|
|
662
759
|
expect(production.inspect()).toMatchObject({
|
|
663
760
|
gpuOwnedSnapshotsMaterialized: 2,
|
|
664
761
|
filteredPlanBuilds: 1,
|
|
665
762
|
candidateUploadBytes: 912,
|
|
666
|
-
batchUploadBytes:
|
|
763
|
+
batchUploadBytes: 64,
|
|
667
764
|
viewBindGroupCreates: 1,
|
|
668
765
|
});
|
|
669
766
|
|
|
670
767
|
const stable = production
|
|
671
768
|
.prepare({
|
|
769
|
+
...pbrInputs,
|
|
672
770
|
scene: {
|
|
673
771
|
scene: availability.scene,
|
|
674
772
|
plan: topology.plan(),
|
|
675
773
|
slots: projection.slotsSnapshot(),
|
|
676
774
|
},
|
|
677
775
|
camera: makeZeroCameraFallbackSnapshot(),
|
|
678
|
-
meshes,
|
|
776
|
+
meshBySlot: meshes,
|
|
679
777
|
viewBindGroupLayout: viewLayout,
|
|
680
778
|
meshResidencyEpoch: 1,
|
|
681
779
|
hdrp: false,
|
|
@@ -690,25 +788,25 @@ describe('GPU-driven production projection', () => {
|
|
|
690
788
|
viewConstantsUploadBytes: 112,
|
|
691
789
|
viewBindGroupCreates: 0,
|
|
692
790
|
batchBindGroupCreates: 0,
|
|
693
|
-
indirectDrawCount:
|
|
791
|
+
indirectDrawCount: 2,
|
|
694
792
|
});
|
|
695
793
|
|
|
696
|
-
//
|
|
697
|
-
//
|
|
698
|
-
// candidate plan instead of reusing the previous view's heights.
|
|
794
|
+
// This mixed batch has no LOD relation. Camera movement updates view
|
|
795
|
+
// constants while retaining the scene topology and candidate uploads.
|
|
699
796
|
const movedCamera = {
|
|
700
797
|
...makeZeroCameraFallbackSnapshot(),
|
|
701
798
|
position: vec3.create(0, 0, 6),
|
|
702
799
|
};
|
|
703
800
|
const moved = production
|
|
704
801
|
.prepare({
|
|
802
|
+
...pbrInputs,
|
|
705
803
|
scene: {
|
|
706
804
|
scene: availability.scene,
|
|
707
805
|
plan: topology.plan(),
|
|
708
806
|
slots: projection.slotsSnapshot(),
|
|
709
807
|
},
|
|
710
808
|
camera: movedCamera,
|
|
711
|
-
meshes,
|
|
809
|
+
meshBySlot: meshes,
|
|
712
810
|
viewBindGroupLayout: viewLayout,
|
|
713
811
|
meshResidencyEpoch: 1,
|
|
714
812
|
hdrp: false,
|
|
@@ -716,14 +814,21 @@ describe('GPU-driven production projection', () => {
|
|
|
716
814
|
.unwrap();
|
|
717
815
|
expect(moved).toBeDefined();
|
|
718
816
|
expect(production.inspect()).toMatchObject({
|
|
719
|
-
filteredPlanBuilds:
|
|
720
|
-
candidateUploadBytes:
|
|
721
|
-
batchUploadBytes:
|
|
817
|
+
filteredPlanBuilds: 0,
|
|
818
|
+
candidateUploadBytes: 0,
|
|
819
|
+
batchUploadBytes: 0,
|
|
722
820
|
});
|
|
723
821
|
if (moved === undefined) return;
|
|
724
822
|
|
|
725
823
|
const graph = new RenderGraphBuilder<RenderPipelineFrame>();
|
|
726
824
|
const gpu = moved.project(graph, 'rgba8unorm', 1).unwrap();
|
|
825
|
+
const materialBindGroup = device.createBindGroupLayout({ entries: [] }).unwrap();
|
|
826
|
+
const frameResources = {
|
|
827
|
+
...moved.standardPbrFrameResources,
|
|
828
|
+
materialBindGroups: [
|
|
829
|
+
device.createBindGroup({ layout: materialBindGroup, entries: [] }).unwrap(),
|
|
830
|
+
],
|
|
831
|
+
};
|
|
727
832
|
const color = graph
|
|
728
833
|
.createTexture('gpu-driven-production-color', {
|
|
729
834
|
format: 'rgba8unorm',
|
|
@@ -742,7 +847,7 @@ describe('GPU-driven production projection', () => {
|
|
|
742
847
|
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
743
848
|
},
|
|
744
849
|
],
|
|
745
|
-
encode: ({ pass, resources }) => gpu.encode(viewBindGroup, pass, resources),
|
|
850
|
+
encode: ({ pass, resources }) => gpu.encode(viewBindGroup, pass, resources, frameResources),
|
|
746
851
|
})
|
|
747
852
|
.unwrap();
|
|
748
853
|
const compiled = graph.compile({ device, surfaceSize: { width: 1, height: 1 } }).unwrap();
|
|
@@ -751,7 +856,13 @@ describe('GPU-driven production projection', () => {
|
|
|
751
856
|
dependencies: ['gpu-driven.frustum-compact', 'gpu-driven.finalize-indirect'],
|
|
752
857
|
});
|
|
753
858
|
const encoder = device.createCommandEncoder({ label: 'gpu-driven-production' }).unwrap();
|
|
754
|
-
compiled
|
|
859
|
+
compiled
|
|
860
|
+
.execute({
|
|
861
|
+
viewBindGroup,
|
|
862
|
+
encoder,
|
|
863
|
+
gpuDrivenStandardPbrFrameResources: frameResources,
|
|
864
|
+
} as unknown as RenderPipelineFrame)
|
|
865
|
+
.unwrap();
|
|
755
866
|
encoder.finish().unwrap();
|
|
756
867
|
expect(device.framePassNames).toEqual([
|
|
757
868
|
'gpu-driven.view-reset',
|
|
@@ -760,8 +871,8 @@ describe('GPU-driven production projection', () => {
|
|
|
760
871
|
'main',
|
|
761
872
|
]);
|
|
762
873
|
expect(device.totalDispatchCount).toBe(3);
|
|
763
|
-
expect(device.totalDrawCount).toBe(
|
|
764
|
-
expect(production.inspect().indirectDrawCount).toBe(
|
|
874
|
+
expect(device.totalDrawCount).toBe(2);
|
|
875
|
+
expect(production.inspect().indirectDrawCount).toBe(2);
|
|
765
876
|
expect(production.inspect().indirectDrawCount).toBeLessThanOrEqual(
|
|
766
877
|
production.inspect().batchCount,
|
|
767
878
|
);
|
|
@@ -771,13 +882,14 @@ describe('GPU-driven production projection', () => {
|
|
|
771
882
|
// plan without making the remaining entity fall back to CPU validation.
|
|
772
883
|
const activeOnly = production
|
|
773
884
|
.prepare({
|
|
885
|
+
...pbrInputs,
|
|
774
886
|
scene: {
|
|
775
887
|
scene: availability.scene,
|
|
776
888
|
plan: topology.plan(),
|
|
777
889
|
slots: projection.slotsSnapshot(),
|
|
778
890
|
},
|
|
779
891
|
camera: movedCamera,
|
|
780
|
-
meshes,
|
|
892
|
+
meshBySlot: meshes,
|
|
781
893
|
viewBindGroupLayout: viewLayout,
|
|
782
894
|
meshResidencyEpoch: 1,
|
|
783
895
|
hdrp: false,
|
|
@@ -785,18 +897,19 @@ describe('GPU-driven production projection', () => {
|
|
|
785
897
|
activeEntityRevision: 1,
|
|
786
898
|
})
|
|
787
899
|
.unwrap();
|
|
788
|
-
expect(activeOnly?.
|
|
789
|
-
expect(activeOnly?.
|
|
900
|
+
expect(activeOnly?.drawKeys?.size).toBe(3);
|
|
901
|
+
expect(activeOnly?.ownsAllDrawItems).toBe(true);
|
|
790
902
|
|
|
791
903
|
const changedActive = production
|
|
792
904
|
.prepare({
|
|
905
|
+
...pbrInputs,
|
|
793
906
|
scene: {
|
|
794
907
|
scene: availability.scene,
|
|
795
908
|
plan: topology.plan(),
|
|
796
909
|
slots: projection.slotsSnapshot(),
|
|
797
910
|
},
|
|
798
911
|
camera: movedCamera,
|
|
799
|
-
meshes,
|
|
912
|
+
meshBySlot: meshes,
|
|
800
913
|
viewBindGroupLayout: viewLayout,
|
|
801
914
|
meshResidencyEpoch: 1,
|
|
802
915
|
hdrp: false,
|
|
@@ -804,7 +917,7 @@ describe('GPU-driven production projection', () => {
|
|
|
804
917
|
activeEntityRevision: 2,
|
|
805
918
|
})
|
|
806
919
|
.unwrap();
|
|
807
|
-
expect(changedActive?.
|
|
920
|
+
expect(changedActive?.drawKeys?.size).toBe(3);
|
|
808
921
|
// Production inspection counters are per-prepare-frame facts. The changed
|
|
809
922
|
// revision must trigger exactly one rebuild for this frame, not accumulate
|
|
810
923
|
// the prior frame's count.
|
|
@@ -816,13 +929,14 @@ describe('GPU-driven production projection', () => {
|
|
|
816
929
|
replacement.scene.sync(delta).unwrap();
|
|
817
930
|
const replacementPrepared = production
|
|
818
931
|
.prepare({
|
|
932
|
+
...pbrInputs,
|
|
819
933
|
scene: {
|
|
820
934
|
scene: replacement.scene,
|
|
821
935
|
plan: topology.plan(),
|
|
822
936
|
slots: projection.slotsSnapshot(),
|
|
823
937
|
},
|
|
824
938
|
camera: makeZeroCameraFallbackSnapshot(),
|
|
825
|
-
meshes,
|
|
939
|
+
meshBySlot: meshes,
|
|
826
940
|
viewBindGroupLayout: viewLayout,
|
|
827
941
|
meshResidencyEpoch: 1,
|
|
828
942
|
hdrp: false,
|
|
@@ -850,9 +964,7 @@ describe('GPU-driven production projection', () => {
|
|
|
850
964
|
materialShaderId: 'forgeax::default-standard-pbr',
|
|
851
965
|
};
|
|
852
966
|
const projection = new RenderScene();
|
|
853
|
-
const delta = projection.apply([
|
|
854
|
-
{ kind: 'create', snapshot: { ...base, material, materials: [material] } },
|
|
855
|
-
]);
|
|
967
|
+
const delta = projection.apply([updateSnapshot({ ...base, material, materials: [material] })]);
|
|
856
968
|
const availability = GpuScene.create(device, 1).unwrap();
|
|
857
969
|
expect(availability.status).toBe('available');
|
|
858
970
|
if (availability.status !== 'available') return;
|
|
@@ -868,7 +980,7 @@ describe('GPU-driven production projection', () => {
|
|
|
868
980
|
plan: topology.plan(),
|
|
869
981
|
slots: projection.slotsSnapshot(),
|
|
870
982
|
},
|
|
871
|
-
|
|
983
|
+
meshBySlot: new Map([[0, mesh(device)]]),
|
|
872
984
|
viewBindGroupLayout: viewLayout,
|
|
873
985
|
hdrp: false,
|
|
874
986
|
} as const;
|
|
@@ -882,8 +994,7 @@ describe('GPU-driven production projection', () => {
|
|
|
882
994
|
})
|
|
883
995
|
.unwrap(),
|
|
884
996
|
).toBeUndefined();
|
|
885
|
-
expect(production.
|
|
886
|
-
expect(production.inspect().filteredPlanBuilds).toBe(1);
|
|
997
|
+
expect(production.inspect().filteredPlanBuilds).toBe(0);
|
|
887
998
|
expect(
|
|
888
999
|
production
|
|
889
1000
|
.prepare({
|
|
@@ -893,7 +1004,6 @@ describe('GPU-driven production projection', () => {
|
|
|
893
1004
|
})
|
|
894
1005
|
.unwrap(),
|
|
895
1006
|
).toBeUndefined();
|
|
896
|
-
expect(production.requiresSceneRows()).toBe(false);
|
|
897
1007
|
expect(production.inspect()).toMatchObject({
|
|
898
1008
|
filteredPlanBuilds: 0,
|
|
899
1009
|
gpuOwnedSnapshotsMaterialized: 0,
|
|
@@ -904,7 +1014,7 @@ describe('GPU-driven production projection', () => {
|
|
|
904
1014
|
availability.scene.dispose();
|
|
905
1015
|
});
|
|
906
1016
|
|
|
907
|
-
it('
|
|
1017
|
+
it('keeps an ordinary mesh on the common CPU residual while the Standard graph stays active', async () => {
|
|
908
1018
|
const canvas = {
|
|
909
1019
|
width: 1,
|
|
910
1020
|
height: 1,
|
|
@@ -954,11 +1064,9 @@ describe('GPU-driven production projection', () => {
|
|
|
954
1064
|
);
|
|
955
1065
|
expect(renderer.perFramePassNames).toContain('output-transform');
|
|
956
1066
|
// Storage-buffer LDR frames keep the scene linear until the one output
|
|
957
|
-
// transform writes the encoded surface
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
// path for occlusion while GPU-driven raster + output-transform remain
|
|
961
|
-
// the only draws in this graph.
|
|
1067
|
+
// transform writes the encoded surface. The ordinary unprepared mesh
|
|
1068
|
+
// remains the CPU residual under common visibility projection, while the
|
|
1069
|
+
// clustered Standard graph and output transform stay active.
|
|
962
1070
|
expect(device.totalDrawCount).toBe(2);
|
|
963
1071
|
expect(renderer.inspect().lodOcclusion).toMatchObject({
|
|
964
1072
|
fallback: { active: false },
|
|
@@ -979,7 +1087,7 @@ describe('GPU-driven production projection', () => {
|
|
|
979
1087
|
batchBindGroupCreates: 0,
|
|
980
1088
|
viewBindGroupCreates: 0,
|
|
981
1089
|
validatedGpuOwnedRows: 0,
|
|
982
|
-
cpuFallbackDrawItems:
|
|
1090
|
+
cpuFallbackDrawItems: 1,
|
|
983
1091
|
},
|
|
984
1092
|
});
|
|
985
1093
|
renderer.dispose();
|
|
@@ -1030,7 +1138,7 @@ describe('GPU-driven production projection', () => {
|
|
|
1030
1138
|
projectionRecords: 1,
|
|
1031
1139
|
topology: { batchCount: 1, candidateCount: 1 },
|
|
1032
1140
|
gpu: { status: 'resident' },
|
|
1033
|
-
gpuDriven: { cpuFallbackDrawItems:
|
|
1141
|
+
gpuDriven: { cpuFallbackDrawItems: 1, validatedGpuOwnedRows: 0 },
|
|
1034
1142
|
});
|
|
1035
1143
|
|
|
1036
1144
|
sceneWorld.set(rendered, Transform, { pos: [1, 0, 0] }).unwrap();
|
|
@@ -1047,4 +1155,878 @@ describe('GPU-driven production projection', () => {
|
|
|
1047
1155
|
});
|
|
1048
1156
|
renderer.dispose();
|
|
1049
1157
|
});
|
|
1158
|
+
|
|
1159
|
+
it('routes mixed ordinary camera and light edits through common visibility projection', async () => {
|
|
1160
|
+
const canvas = {
|
|
1161
|
+
width: 1,
|
|
1162
|
+
height: 1,
|
|
1163
|
+
getContext: () => null,
|
|
1164
|
+
addEventListener: () => undefined,
|
|
1165
|
+
removeEventListener: () => undefined,
|
|
1166
|
+
} as unknown as HTMLCanvasElement;
|
|
1167
|
+
const renderer = await constructRenderer(
|
|
1168
|
+
canvas,
|
|
1169
|
+
{ rhi },
|
|
1170
|
+
{ shaderManifestUrl: manifestDataUrl() },
|
|
1171
|
+
);
|
|
1172
|
+
expect((await renderer.initialization).ok).toBe(true);
|
|
1173
|
+
const world = new World();
|
|
1174
|
+
expect(renderer.attach(world).ok).toBe(true);
|
|
1175
|
+
const camera = world
|
|
1176
|
+
.spawn(
|
|
1177
|
+
{ component: Transform, data: { pos: [0, 0, 5] } },
|
|
1178
|
+
{ component: Camera, data: { fov: Math.PI / 4, aspect: 1, near: 0.1, far: 100 } },
|
|
1179
|
+
)
|
|
1180
|
+
.unwrap();
|
|
1181
|
+
world
|
|
1182
|
+
.spawn(
|
|
1183
|
+
{ component: Transform, data: {} },
|
|
1184
|
+
{ component: MeshFilter, data: { assetHandle: HANDLE_CUBE } },
|
|
1185
|
+
{ component: MeshRenderer, data: {} },
|
|
1186
|
+
)
|
|
1187
|
+
.unwrap();
|
|
1188
|
+
const lamp = world
|
|
1189
|
+
.spawn(
|
|
1190
|
+
{ component: Transform, data: { pos: [0, 0, 1] } },
|
|
1191
|
+
{ component: PointLight, data: { intensity: 1, range: 10 } },
|
|
1192
|
+
)
|
|
1193
|
+
.unwrap();
|
|
1194
|
+
const updateVisibility = vi.spyOn(PersistentRenderScene.prototype, 'updateVisibilityFacet');
|
|
1195
|
+
const projectVisibility = vi.spyOn(PersistentRenderScene.prototype, 'projectVisibility');
|
|
1196
|
+
try {
|
|
1197
|
+
world.update(1 / 60).unwrap();
|
|
1198
|
+
expect(renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 }).ok).toBe(true);
|
|
1199
|
+
const warmUpdateCalls = updateVisibility.mock.calls.length;
|
|
1200
|
+
const warmProjectCalls = projectVisibility.mock.calls.length;
|
|
1201
|
+
const before = renderer.inspect().renderScene;
|
|
1202
|
+
|
|
1203
|
+
world.set(camera, Transform, { pos: [0, 1, 5] }).unwrap();
|
|
1204
|
+
world.set(lamp, PointLight, { intensity: 3, range: 8 }).unwrap();
|
|
1205
|
+
propagateTransforms(world).unwrap();
|
|
1206
|
+
world.update(1 / 60).unwrap();
|
|
1207
|
+
expect(renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 }).ok).toBe(true);
|
|
1208
|
+
|
|
1209
|
+
expect(updateVisibility.mock.calls.length).toBeGreaterThan(warmUpdateCalls);
|
|
1210
|
+
expect(projectVisibility.mock.calls.length).toBeGreaterThan(warmProjectCalls);
|
|
1211
|
+
const after = renderer.inspect().renderScene;
|
|
1212
|
+
expect(after).toMatchObject({
|
|
1213
|
+
fullRebuilds: 1,
|
|
1214
|
+
projectionRecords: before.projectionRecords,
|
|
1215
|
+
});
|
|
1216
|
+
expect(after.topology.revision).toBe(before.topology.revision);
|
|
1217
|
+
} finally {
|
|
1218
|
+
updateVisibility.mockRestore();
|
|
1219
|
+
projectVisibility.mockRestore();
|
|
1220
|
+
renderer.dispose();
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
it('admits prepared Alpha Mask draws to the GPU shadow lane', () => {
|
|
1225
|
+
const source = preparedSnapshot(10, 3);
|
|
1226
|
+
const alphaMaterial = {
|
|
1227
|
+
...source.material,
|
|
1228
|
+
paramSnapshot: { alphaCutoff: 0.5 },
|
|
1229
|
+
textureHandles: new Map([['baseColorTexture', 1]]) as never,
|
|
1230
|
+
samplerHandles: new Map([['baseColorTexture', 2]]) as never,
|
|
1231
|
+
} as MaterialSnapshot;
|
|
1232
|
+
const draw = source.gpuDrivenDraws?.[0];
|
|
1233
|
+
expect(draw?.prepared).toBeDefined();
|
|
1234
|
+
if (draw === undefined || draw.prepared === undefined) return;
|
|
1235
|
+
|
|
1236
|
+
// Alpha Mask is a prepared material fact: it keeps the opaque depth
|
|
1237
|
+
// state, carries an authored cutoff, and owns the base-color resource
|
|
1238
|
+
// needed by the shadow fragment discard. Alpha Blend is covered by the
|
|
1239
|
+
// adjacent residual-lane regression and must remain CPU-owned.
|
|
1240
|
+
expect(
|
|
1241
|
+
shadowCasterCpuReason(
|
|
1242
|
+
alphaMaterial,
|
|
1243
|
+
draw,
|
|
1244
|
+
[{ materialShaderId: 'forgeax::default-shadow-caster' } as DispatchEntry],
|
|
1245
|
+
undefined,
|
|
1246
|
+
undefined,
|
|
1247
|
+
),
|
|
1248
|
+
).toBeUndefined();
|
|
1249
|
+
|
|
1250
|
+
const projection = new RenderScene();
|
|
1251
|
+
projection.apply([
|
|
1252
|
+
updateSnapshot({ ...source, material: alphaMaterial, materials: [alphaMaterial] }),
|
|
1253
|
+
]);
|
|
1254
|
+
const topology = new BatchTopology();
|
|
1255
|
+
topology.rebuild(projection.slotsSnapshot());
|
|
1256
|
+
expect(topology.plan()).toMatchObject({
|
|
1257
|
+
candidateCount: 1,
|
|
1258
|
+
batches: [{ key: { admission: 'alpha-mask' } }],
|
|
1259
|
+
});
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
it('does not admit Alpha Blend or CPU transmission draw plans as GPU-owned', async () => {
|
|
1263
|
+
const adapter = (await rhi.requestAdapter()).unwrap();
|
|
1264
|
+
const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
|
|
1265
|
+
const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
|
|
1266
|
+
const viewLayout = device
|
|
1267
|
+
.createBindGroupLayout({
|
|
1268
|
+
entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
|
|
1269
|
+
})
|
|
1270
|
+
.unwrap();
|
|
1271
|
+
const materialLayout = device.createBindGroupLayout({ entries: [] }).unwrap();
|
|
1272
|
+
const meshLayout = device
|
|
1273
|
+
.createBindGroupLayout({
|
|
1274
|
+
entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'read-only-storage' } }],
|
|
1275
|
+
})
|
|
1276
|
+
.unwrap();
|
|
1277
|
+
const instancesLayout = device
|
|
1278
|
+
.createBindGroupLayout({
|
|
1279
|
+
entries: [
|
|
1280
|
+
{ binding: 0, visibility: 0x1, buffer: { type: 'read-only-storage' } },
|
|
1281
|
+
{ binding: 1, visibility: 0x1, buffer: { type: 'read-only-storage' } },
|
|
1282
|
+
],
|
|
1283
|
+
})
|
|
1284
|
+
.unwrap();
|
|
1285
|
+
const pbrPipelineLayout = device
|
|
1286
|
+
.createPipelineLayout({
|
|
1287
|
+
bindGroupLayouts: [viewLayout, materialLayout, meshLayout, instancesLayout],
|
|
1288
|
+
})
|
|
1289
|
+
.unwrap();
|
|
1290
|
+
const opaque = preparedSnapshot(1, 3);
|
|
1291
|
+
const blendState = {
|
|
1292
|
+
color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
1293
|
+
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
1294
|
+
} as const;
|
|
1295
|
+
const alphaMaterial = {
|
|
1296
|
+
...opaque.material,
|
|
1297
|
+
renderState: { cullMode: 'none' as const, blend: blendState, depthWriteEnabled: false },
|
|
1298
|
+
} as MaterialSnapshot;
|
|
1299
|
+
const alphaSnapshots = [2, 3].map(
|
|
1300
|
+
(entityKey): RenderableSnapshot => ({
|
|
1301
|
+
...preparedSnapshot(entityKey, 3),
|
|
1302
|
+
material: alphaMaterial,
|
|
1303
|
+
materials: [alphaMaterial],
|
|
1304
|
+
gpuDrivenDraws: [],
|
|
1305
|
+
}),
|
|
1306
|
+
);
|
|
1307
|
+
const transmissionMaterial = {
|
|
1308
|
+
...opaque.material,
|
|
1309
|
+
paramSnapshot: { transmission: 0.5 },
|
|
1310
|
+
} as MaterialSnapshot;
|
|
1311
|
+
const transmissionSnapshot: RenderableSnapshot = {
|
|
1312
|
+
...preparedSnapshot(4, 3),
|
|
1313
|
+
material: transmissionMaterial,
|
|
1314
|
+
materials: [transmissionMaterial],
|
|
1315
|
+
};
|
|
1316
|
+
const sceneProjection = new RenderScene();
|
|
1317
|
+
const delta = sceneProjection.apply([
|
|
1318
|
+
updateSnapshot(opaque),
|
|
1319
|
+
...alphaSnapshots.map((snapshot) => updateSnapshot(snapshot)),
|
|
1320
|
+
updateSnapshot(transmissionSnapshot),
|
|
1321
|
+
]);
|
|
1322
|
+
const availability = GpuScene.create(device, 3).unwrap();
|
|
1323
|
+
expect(availability.status).toBe('available');
|
|
1324
|
+
if (availability.status !== 'available') return;
|
|
1325
|
+
availability.scene.sync(delta).unwrap();
|
|
1326
|
+
const topology = new BatchTopology();
|
|
1327
|
+
topology.rebuild(sceneProjection.slotsSnapshot());
|
|
1328
|
+
const production = new GpuDrivenProduction(device, {
|
|
1329
|
+
createShaderModule: () => ok(shader),
|
|
1330
|
+
});
|
|
1331
|
+
const artifact = {
|
|
1332
|
+
material: 'forgeax::default-standard-pbr',
|
|
1333
|
+
pass: 'forward',
|
|
1334
|
+
wgsl: 'synthetic',
|
|
1335
|
+
layoutIdentity: STANDARD_PBR_RECEIPT.reflection.layoutIdentity,
|
|
1336
|
+
bindings: [],
|
|
1337
|
+
deps: [],
|
|
1338
|
+
vertexInputs: STANDARD_PBR_RECEIPT.vertexInputs as unknown as readonly Readonly<
|
|
1339
|
+
Record<string, unknown>
|
|
1340
|
+
>[],
|
|
1341
|
+
receipt: STANDARD_PBR_RECEIPT,
|
|
1342
|
+
};
|
|
1343
|
+
const programKey = standardPbrProgramKey(artifact.material);
|
|
1344
|
+
expect(programKey).toBeDefined();
|
|
1345
|
+
if (programKey === undefined) return;
|
|
1346
|
+
const prepared = production.prepare({
|
|
1347
|
+
scene: {
|
|
1348
|
+
scene: availability.scene,
|
|
1349
|
+
plan: topology.plan(),
|
|
1350
|
+
slots: sceneProjection.slotsSnapshot(),
|
|
1351
|
+
},
|
|
1352
|
+
camera: makeZeroCameraFallbackSnapshot(),
|
|
1353
|
+
meshBySlot: new Map([[0, mesh(device)]]),
|
|
1354
|
+
viewBindGroupLayout: viewLayout,
|
|
1355
|
+
meshResidencyEpoch: 1,
|
|
1356
|
+
hdrp: false,
|
|
1357
|
+
standardPbrArtifact: artifact,
|
|
1358
|
+
standardPbrPipelineState: {
|
|
1359
|
+
pbrPipelineLayout,
|
|
1360
|
+
meshBindGroupLayout: meshLayout,
|
|
1361
|
+
instancesBindGroupLayout: instancesLayout,
|
|
1362
|
+
gpuDrivenInstancesBindGroupLayout: instancesLayout,
|
|
1363
|
+
gpuDrivenPbrPipelineLayout: pbrPipelineLayout,
|
|
1364
|
+
standardPbrShaderModule: shader,
|
|
1365
|
+
gpuDrivenPbrPrograms: new Map([[programKey, { module: shader, artifact }]]),
|
|
1366
|
+
} as never,
|
|
1367
|
+
});
|
|
1368
|
+
expect(prepared.ok).toBe(true);
|
|
1369
|
+
if (!prepared.ok) return;
|
|
1370
|
+
expect(prepared.value?.drawKeys).toEqual(new Set(['1:-1:0']));
|
|
1371
|
+
expect(prepared.value?.ownsAllDrawItems).toBe(false);
|
|
1372
|
+
production.dispose();
|
|
1373
|
+
availability.scene.dispose();
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
it('projects concrete ShadowCaster candidates and leaves the CPU residual disjoint', async () => {
|
|
1377
|
+
const adapter = (await rhi.requestAdapter()).unwrap();
|
|
1378
|
+
const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
|
|
1379
|
+
const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
|
|
1380
|
+
const viewLayout = device
|
|
1381
|
+
.createBindGroupLayout({
|
|
1382
|
+
entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
|
|
1383
|
+
})
|
|
1384
|
+
.unwrap();
|
|
1385
|
+
const materialLayout = device.createBindGroupLayout({ entries: [] }).unwrap();
|
|
1386
|
+
const meshLayout = device
|
|
1387
|
+
.createBindGroupLayout({
|
|
1388
|
+
entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'read-only-storage' } }],
|
|
1389
|
+
})
|
|
1390
|
+
.unwrap();
|
|
1391
|
+
const instancesLayout = device
|
|
1392
|
+
.createBindGroupLayout({
|
|
1393
|
+
entries: [
|
|
1394
|
+
{ binding: 0, visibility: 0x1, buffer: { type: 'read-only-storage' } },
|
|
1395
|
+
{ binding: 1, visibility: 0x1, buffer: { type: 'read-only-storage' } },
|
|
1396
|
+
],
|
|
1397
|
+
})
|
|
1398
|
+
.unwrap();
|
|
1399
|
+
const pbrPipelineLayout = device
|
|
1400
|
+
.createPipelineLayout({
|
|
1401
|
+
bindGroupLayouts: [viewLayout, materialLayout, meshLayout, instancesLayout],
|
|
1402
|
+
})
|
|
1403
|
+
.unwrap();
|
|
1404
|
+
const source = preparedSnapshot(1, 3);
|
|
1405
|
+
const firstDraw = source.gpuDrivenDraws?.[0];
|
|
1406
|
+
expect(firstDraw).toBeDefined();
|
|
1407
|
+
if (firstDraw === undefined) return;
|
|
1408
|
+
const firstPrepared = firstDraw.prepared;
|
|
1409
|
+
expect(firstPrepared).toBeDefined();
|
|
1410
|
+
if (firstPrepared === undefined) return;
|
|
1411
|
+
const blendState = {
|
|
1412
|
+
color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
1413
|
+
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
1414
|
+
} as const;
|
|
1415
|
+
const residualMaterial = {
|
|
1416
|
+
...source.material,
|
|
1417
|
+
materialHandle: 22,
|
|
1418
|
+
transparent: true,
|
|
1419
|
+
renderState: { cullMode: 'none' as const, blend: blendState, depthWriteEnabled: false },
|
|
1420
|
+
} as MaterialSnapshot;
|
|
1421
|
+
const residualDraw = {
|
|
1422
|
+
...firstDraw,
|
|
1423
|
+
first: 3,
|
|
1424
|
+
materialSlot: 1,
|
|
1425
|
+
materialResourceClass: 'alpha-blend',
|
|
1426
|
+
prepared: { ...firstPrepared, first: 3 },
|
|
1427
|
+
};
|
|
1428
|
+
const mixed = {
|
|
1429
|
+
...source,
|
|
1430
|
+
materials: [source.material, residualMaterial],
|
|
1431
|
+
gpuDrivenDraws: [firstDraw, residualDraw],
|
|
1432
|
+
} as RenderableSnapshot;
|
|
1433
|
+
const projection = new RenderScene();
|
|
1434
|
+
const delta = projection.apply([updateSnapshot(mixed)]);
|
|
1435
|
+
const availability = GpuScene.create(device, 1).unwrap();
|
|
1436
|
+
expect(availability.status).toBe('available');
|
|
1437
|
+
if (availability.status !== 'available') return;
|
|
1438
|
+
availability.scene.sync(delta).unwrap();
|
|
1439
|
+
const topology = new BatchTopology();
|
|
1440
|
+
topology.rebuild(projection.slotsSnapshot());
|
|
1441
|
+
expect(topology.plan()).toMatchObject({ candidateCount: 1 });
|
|
1442
|
+
const production = new GpuDrivenProduction(device, {
|
|
1443
|
+
createShaderModule: () => ok(shader),
|
|
1444
|
+
});
|
|
1445
|
+
const artifact = {
|
|
1446
|
+
material: 'forgeax::default-standard-pbr',
|
|
1447
|
+
pass: 'forward',
|
|
1448
|
+
wgsl: 'synthetic',
|
|
1449
|
+
layoutIdentity: STANDARD_PBR_RECEIPT.reflection.layoutIdentity,
|
|
1450
|
+
bindings: [],
|
|
1451
|
+
deps: [],
|
|
1452
|
+
vertexInputs: STANDARD_PBR_RECEIPT.vertexInputs as unknown as readonly Readonly<
|
|
1453
|
+
Record<string, unknown>
|
|
1454
|
+
>[],
|
|
1455
|
+
receipt: STANDARD_PBR_RECEIPT,
|
|
1456
|
+
};
|
|
1457
|
+
const programKey = standardPbrProgramKey(artifact.material);
|
|
1458
|
+
expect(programKey).toBeDefined();
|
|
1459
|
+
if (programKey === undefined) return;
|
|
1460
|
+
const entity = worldEntityKey(mixed.worldId, mixed.entityKey);
|
|
1461
|
+
const gpuKey = gpuDrivenShadowDrawKey(entity, -1, 0, 0);
|
|
1462
|
+
const residualKey = gpuDrivenShadowDrawKey(entity, 22, 1, 0);
|
|
1463
|
+
const allShadowKeys = new Set([gpuKey, residualKey]);
|
|
1464
|
+
const prepareFrame = (hdrp: boolean) =>
|
|
1465
|
+
production
|
|
1466
|
+
.prepare({
|
|
1467
|
+
scene: {
|
|
1468
|
+
scene: availability.scene,
|
|
1469
|
+
plan: topology.plan(),
|
|
1470
|
+
slots: projection.slotsSnapshot(),
|
|
1471
|
+
},
|
|
1472
|
+
camera: makeZeroCameraFallbackSnapshot(),
|
|
1473
|
+
meshBySlot: new Map([[0, mesh(device)]]),
|
|
1474
|
+
viewBindGroupLayout: viewLayout,
|
|
1475
|
+
meshResidencyEpoch: 1,
|
|
1476
|
+
hdrp,
|
|
1477
|
+
standardPbrArtifact: artifact,
|
|
1478
|
+
standardPbrPipelineState: {
|
|
1479
|
+
pbrPipelineLayout,
|
|
1480
|
+
meshBindGroupLayout: meshLayout,
|
|
1481
|
+
instancesBindGroupLayout: instancesLayout,
|
|
1482
|
+
gpuDrivenInstancesBindGroupLayout: instancesLayout,
|
|
1483
|
+
gpuDrivenPbrPipelineLayout: pbrPipelineLayout,
|
|
1484
|
+
standardPbrShaderModule: shader,
|
|
1485
|
+
gpuDrivenPbrPrograms: new Map([[programKey, { module: shader, artifact }]]),
|
|
1486
|
+
} as never,
|
|
1487
|
+
shadowCasterDrawKeys: allShadowKeys,
|
|
1488
|
+
})
|
|
1489
|
+
.unwrap();
|
|
1490
|
+
const prepared = prepareFrame(false);
|
|
1491
|
+
expect(prepared).toBeDefined();
|
|
1492
|
+
if (prepared === undefined) return;
|
|
1493
|
+
const claimed = new Set(prepared.shadowDrawKeys ?? []);
|
|
1494
|
+
expect([...claimed]).toEqual([gpuKey]);
|
|
1495
|
+
expect(prepared.ownsAllShadowCasters).toBe(false);
|
|
1496
|
+
const residual = [...allShadowKeys].filter((key) => !claimed.has(key));
|
|
1497
|
+
expect(residual).toEqual([residualKey]);
|
|
1498
|
+
expect([...new Set([...claimed, ...residual])].sort()).toEqual([...allShadowKeys].sort());
|
|
1499
|
+
const identity = { kind: 'directional' as const, index: 0 };
|
|
1500
|
+
const updated = prepared.updateShadowViews?.([
|
|
1501
|
+
{ identity, planes: new Float32Array(24), cacheToken: 'mixed-candidates' },
|
|
1502
|
+
]);
|
|
1503
|
+
expect(updated?.ok).toBe(true);
|
|
1504
|
+
expect(production.inspect().channels).toEqual(
|
|
1505
|
+
expect.arrayContaining([
|
|
1506
|
+
expect.objectContaining({
|
|
1507
|
+
viewPass: 'directional-shadow',
|
|
1508
|
+
lane: 'blocked',
|
|
1509
|
+
reason: 'shadow-ownership',
|
|
1510
|
+
drawCount: 1,
|
|
1511
|
+
failure: expect.objectContaining({
|
|
1512
|
+
code: 'shadow-ownership',
|
|
1513
|
+
detail: expect.objectContaining({
|
|
1514
|
+
owner: 'shadow',
|
|
1515
|
+
reason: 'shadow-ownership-missing',
|
|
1516
|
+
recovery: 'route-cpu-lane',
|
|
1517
|
+
}),
|
|
1518
|
+
}),
|
|
1519
|
+
}),
|
|
1520
|
+
]),
|
|
1521
|
+
);
|
|
1522
|
+
const submission = prepared.shadowViewPool?.submission(identity);
|
|
1523
|
+
expect(submission?.plan.candidateCount).toBe(1);
|
|
1524
|
+
expect(prepareFrame(true)).toBeUndefined();
|
|
1525
|
+
expect(production.inspect().channels.filter((channel) => channel.viewPass !== 'main')).toEqual(
|
|
1526
|
+
[],
|
|
1527
|
+
);
|
|
1528
|
+
const resumed = prepareFrame(false);
|
|
1529
|
+
expect(
|
|
1530
|
+
resumed?.updateShadowViews?.([
|
|
1531
|
+
{ identity, planes: new Float32Array(24), cacheToken: 'mixed-candidates' },
|
|
1532
|
+
]).ok,
|
|
1533
|
+
).toBe(true);
|
|
1534
|
+
expect(
|
|
1535
|
+
production.inspect().channels.some((channel) => channel.viewPass === 'directional-shadow'),
|
|
1536
|
+
).toBe(true);
|
|
1537
|
+
production.dispose();
|
|
1538
|
+
availability.scene.dispose();
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
it('claims exact ShadowCaster passes without swallowing residual or custom passes', async () => {
|
|
1542
|
+
const adapter = (await rhi.requestAdapter()).unwrap();
|
|
1543
|
+
const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
|
|
1544
|
+
const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
|
|
1545
|
+
const viewLayout = device
|
|
1546
|
+
.createBindGroupLayout({
|
|
1547
|
+
entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
|
|
1548
|
+
})
|
|
1549
|
+
.unwrap();
|
|
1550
|
+
const materialLayout = device.createBindGroupLayout({ entries: [] }).unwrap();
|
|
1551
|
+
const meshLayout = device
|
|
1552
|
+
.createBindGroupLayout({
|
|
1553
|
+
entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'read-only-storage' } }],
|
|
1554
|
+
})
|
|
1555
|
+
.unwrap();
|
|
1556
|
+
const instancesLayout = device
|
|
1557
|
+
.createBindGroupLayout({
|
|
1558
|
+
entries: [
|
|
1559
|
+
{ binding: 0, visibility: 0x1, buffer: { type: 'read-only-storage' } },
|
|
1560
|
+
{ binding: 1, visibility: 0x1, buffer: { type: 'read-only-storage' } },
|
|
1561
|
+
],
|
|
1562
|
+
})
|
|
1563
|
+
.unwrap();
|
|
1564
|
+
const pbrPipelineLayout = device
|
|
1565
|
+
.createPipelineLayout({
|
|
1566
|
+
bindGroupLayouts: [viewLayout, materialLayout, meshLayout, instancesLayout],
|
|
1567
|
+
})
|
|
1568
|
+
.unwrap();
|
|
1569
|
+
const source = preparedSnapshot(7, 3);
|
|
1570
|
+
const firstDraw = source.gpuDrivenDraws?.[0];
|
|
1571
|
+
expect(firstDraw?.prepared).toBeDefined();
|
|
1572
|
+
if (firstDraw === undefined || firstDraw.prepared === undefined) return;
|
|
1573
|
+
const materials = [0, 1, 2].map((handle) => ({
|
|
1574
|
+
...source.material,
|
|
1575
|
+
materialHandle: handle + 10,
|
|
1576
|
+
})) as MaterialSnapshot[];
|
|
1577
|
+
const draws = [
|
|
1578
|
+
{ ...firstDraw, drawItemIndex: 0, materialSlot: 0, first: 0, count: 3 },
|
|
1579
|
+
{
|
|
1580
|
+
...withoutPrepared(firstDraw),
|
|
1581
|
+
drawItemIndex: 1,
|
|
1582
|
+
materialSlot: 1,
|
|
1583
|
+
first: 3,
|
|
1584
|
+
count: 3,
|
|
1585
|
+
},
|
|
1586
|
+
{ ...firstDraw, drawItemIndex: 2, materialSlot: 2, first: 6, count: 3 },
|
|
1587
|
+
];
|
|
1588
|
+
const mixed: RenderableSnapshot = {
|
|
1589
|
+
...source,
|
|
1590
|
+
material: materials[0] as MaterialSnapshot,
|
|
1591
|
+
materials,
|
|
1592
|
+
gpuDrivenDraws: draws,
|
|
1593
|
+
};
|
|
1594
|
+
const projection = new RenderScene();
|
|
1595
|
+
const delta = projection.apply([updateSnapshot(mixed)]);
|
|
1596
|
+
const availability = GpuScene.create(device, 1).unwrap();
|
|
1597
|
+
expect(availability.status).toBe('available');
|
|
1598
|
+
if (availability.status !== 'available') return;
|
|
1599
|
+
availability.scene.sync(delta).unwrap();
|
|
1600
|
+
const topology = new BatchTopology();
|
|
1601
|
+
topology.rebuild(projection.slotsSnapshot());
|
|
1602
|
+
expect(topology.plan().candidateCount).toBe(3);
|
|
1603
|
+
const entity = worldEntityKey(mixed.worldId, mixed.entityKey);
|
|
1604
|
+
const membership: ShadowCasterMembership[] = [
|
|
1605
|
+
{
|
|
1606
|
+
worldEntity: entity,
|
|
1607
|
+
renderableIndex: 0,
|
|
1608
|
+
drawItemIndex: 0,
|
|
1609
|
+
materialHandle: 10,
|
|
1610
|
+
passIndex: 0,
|
|
1611
|
+
materialShaderId: 'forgeax::default-shadow-caster',
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
worldEntity: entity,
|
|
1615
|
+
renderableIndex: 0,
|
|
1616
|
+
drawItemIndex: 1,
|
|
1617
|
+
materialHandle: 11,
|
|
1618
|
+
passIndex: 0,
|
|
1619
|
+
materialShaderId: 'forgeax::default-shadow-caster',
|
|
1620
|
+
},
|
|
1621
|
+
{
|
|
1622
|
+
worldEntity: entity,
|
|
1623
|
+
renderableIndex: 0,
|
|
1624
|
+
drawItemIndex: 2,
|
|
1625
|
+
materialHandle: 12,
|
|
1626
|
+
passIndex: 0,
|
|
1627
|
+
materialShaderId: 'forgeax::default-shadow-caster',
|
|
1628
|
+
},
|
|
1629
|
+
{
|
|
1630
|
+
worldEntity: entity,
|
|
1631
|
+
renderableIndex: 0,
|
|
1632
|
+
drawItemIndex: 2,
|
|
1633
|
+
materialHandle: 12,
|
|
1634
|
+
passIndex: 7,
|
|
1635
|
+
materialShaderId: 'custom-shadow-caster',
|
|
1636
|
+
},
|
|
1637
|
+
];
|
|
1638
|
+
const allKeys = new Set(
|
|
1639
|
+
membership.map((entry) =>
|
|
1640
|
+
gpuDrivenShadowDrawKey(
|
|
1641
|
+
entry.worldEntity,
|
|
1642
|
+
entry.materialHandle,
|
|
1643
|
+
entry.drawItemIndex,
|
|
1644
|
+
entry.passIndex,
|
|
1645
|
+
),
|
|
1646
|
+
),
|
|
1647
|
+
);
|
|
1648
|
+
const production = new GpuDrivenProduction(device, {
|
|
1649
|
+
createShaderModule: () => ok(shader),
|
|
1650
|
+
});
|
|
1651
|
+
const artifact = {
|
|
1652
|
+
material: 'forgeax::default-standard-pbr',
|
|
1653
|
+
pass: 'forward',
|
|
1654
|
+
wgsl: 'synthetic',
|
|
1655
|
+
layoutIdentity: STANDARD_PBR_RECEIPT.reflection.layoutIdentity,
|
|
1656
|
+
bindings: [],
|
|
1657
|
+
deps: [],
|
|
1658
|
+
vertexInputs: STANDARD_PBR_RECEIPT.vertexInputs as unknown as readonly Readonly<
|
|
1659
|
+
Record<string, unknown>
|
|
1660
|
+
>[],
|
|
1661
|
+
receipt: STANDARD_PBR_RECEIPT,
|
|
1662
|
+
};
|
|
1663
|
+
const programKey = standardPbrProgramKey(artifact.material);
|
|
1664
|
+
expect(programKey).toBeDefined();
|
|
1665
|
+
if (programKey === undefined) return;
|
|
1666
|
+
const multiMesh = {
|
|
1667
|
+
...mesh(device),
|
|
1668
|
+
submeshes: [0, 1, 2].map((materialSlot) => ({
|
|
1669
|
+
indexOffset: materialSlot * 3,
|
|
1670
|
+
indexCount: 3,
|
|
1671
|
+
vertexCount: 3,
|
|
1672
|
+
materialSlot,
|
|
1673
|
+
topology: 'triangle-list' as const,
|
|
1674
|
+
})),
|
|
1675
|
+
};
|
|
1676
|
+
const prepared = production
|
|
1677
|
+
.prepare({
|
|
1678
|
+
scene: {
|
|
1679
|
+
scene: availability.scene,
|
|
1680
|
+
plan: topology.plan(),
|
|
1681
|
+
slots: projection.slotsSnapshot(),
|
|
1682
|
+
},
|
|
1683
|
+
camera: makeZeroCameraFallbackSnapshot(),
|
|
1684
|
+
meshBySlot: new Map([[0, multiMesh]]),
|
|
1685
|
+
viewBindGroupLayout: viewLayout,
|
|
1686
|
+
meshResidencyEpoch: 1,
|
|
1687
|
+
hdrp: false,
|
|
1688
|
+
standardPbrArtifact: artifact,
|
|
1689
|
+
standardPbrPipelineState: {
|
|
1690
|
+
pbrPipelineLayout,
|
|
1691
|
+
meshBindGroupLayout: meshLayout,
|
|
1692
|
+
instancesBindGroupLayout: instancesLayout,
|
|
1693
|
+
gpuDrivenInstancesBindGroupLayout: instancesLayout,
|
|
1694
|
+
gpuDrivenPbrPipelineLayout: pbrPipelineLayout,
|
|
1695
|
+
standardPbrShaderModule: shader,
|
|
1696
|
+
gpuDrivenPbrPrograms: new Map([[programKey, { module: shader, artifact }]]),
|
|
1697
|
+
} as never,
|
|
1698
|
+
shadowCasterDrawKeys: allKeys,
|
|
1699
|
+
shadowCasterMembership: membership,
|
|
1700
|
+
})
|
|
1701
|
+
.unwrap();
|
|
1702
|
+
expect(prepared).toBeDefined();
|
|
1703
|
+
if (prepared === undefined) return;
|
|
1704
|
+
const gpuKey = gpuDrivenShadowDrawKey(entity, 10, 0, 0);
|
|
1705
|
+
expect([...(prepared.shadowDrawKeys ?? [])]).toEqual([gpuKey]);
|
|
1706
|
+
expect(prepared.ownsAllShadowCasters).toBe(false);
|
|
1707
|
+
const residual = [...allKeys].filter((key) => !prepared.shadowDrawKeys?.has(key));
|
|
1708
|
+
expect(residual).toHaveLength(3);
|
|
1709
|
+
expect(new Set([...(prepared.shadowDrawKeys ?? []), ...residual])).toEqual(allKeys);
|
|
1710
|
+
const identity = { kind: 'directional' as const, index: 0 };
|
|
1711
|
+
expect(
|
|
1712
|
+
prepared.updateShadowViews?.([
|
|
1713
|
+
{ identity, planes: new Float32Array(24), cacheToken: 'exact-shadow-membership' },
|
|
1714
|
+
]).ok,
|
|
1715
|
+
).toBe(true);
|
|
1716
|
+
expect(prepared.shadowViewPool?.submission(identity)?.plan.candidateCount).toBe(1);
|
|
1717
|
+
expect(prepared.shadowDrawKeysByView?.get('directional:0:')).toEqual(new Set([gpuKey]));
|
|
1718
|
+
production.dispose();
|
|
1719
|
+
availability.scene.dispose();
|
|
1720
|
+
});
|
|
1721
|
+
|
|
1722
|
+
it.each([false, true])('prepares off-camera CPU shadow materials (main rows=%s)', (withMain) => {
|
|
1723
|
+
const material = { ...preparedSnapshot(1, 3).material, materialHandle: 42 };
|
|
1724
|
+
const shadow = {
|
|
1725
|
+
source: { ...preparedSnapshot(1, 3), material, materials: [material] },
|
|
1726
|
+
renderableIndex: 0,
|
|
1727
|
+
} as never;
|
|
1728
|
+
const main = { source: preparedSnapshot(2, 3), renderableIndex: 0 } as never;
|
|
1729
|
+
const plan = buildDispatchPlan({} as never, withMain ? [main] : [], [], [], [shadow]);
|
|
1730
|
+
expect(plan.shadowValidatedOrdered).toEqual([shadow]);
|
|
1731
|
+
const slot = plan.materialSlots.indexOf(material);
|
|
1732
|
+
expect(slot).toBeGreaterThanOrEqual(0);
|
|
1733
|
+
expect(plan.materialSlotOwners[slot]).toBe(shadow);
|
|
1734
|
+
const dispatch = {
|
|
1735
|
+
renderableIndex: 0,
|
|
1736
|
+
materialHandle: 42,
|
|
1737
|
+
materialShaderId: 'forgeax::default-standard-pbr',
|
|
1738
|
+
tags: { LightMode: 'ShadowCaster' },
|
|
1739
|
+
};
|
|
1740
|
+
const map = shadowShaderMap({ ...plan, shadowDispatch: [dispatch] } as never);
|
|
1741
|
+
expect(map.get(0)?.get(42)?.[0]?.materialSlot).toBe(slot);
|
|
1742
|
+
});
|
|
1743
|
+
|
|
1744
|
+
it.each([
|
|
1745
|
+
false,
|
|
1746
|
+
true,
|
|
1747
|
+
])('records exact CPU residuals with authored state (skinned=%s)', async (skinned) => {
|
|
1748
|
+
const adapter = (await rhi.requestAdapter()).unwrap();
|
|
1749
|
+
const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
|
|
1750
|
+
const source = preparedSnapshot(8, 3);
|
|
1751
|
+
const firstDraw = source.gpuDrivenDraws?.[0];
|
|
1752
|
+
expect(firstDraw?.prepared).toBeDefined();
|
|
1753
|
+
if (firstDraw === undefined || firstDraw.prepared === undefined) return;
|
|
1754
|
+
const materials = [0, 1, 2].map((handle) => ({
|
|
1755
|
+
...source.material,
|
|
1756
|
+
materialHandle: handle + 20,
|
|
1757
|
+
})) as MaterialSnapshot[];
|
|
1758
|
+
const draws = [
|
|
1759
|
+
{ ...firstDraw, drawItemIndex: 0, materialSlot: 0, first: 0, count: 3 },
|
|
1760
|
+
{ ...firstDraw, drawItemIndex: 1, materialSlot: 1, first: 3, count: 3 },
|
|
1761
|
+
{ ...firstDraw, drawItemIndex: 2, materialSlot: 2, first: 6, count: 3 },
|
|
1762
|
+
];
|
|
1763
|
+
const sourceForRecord: RenderableSnapshot = {
|
|
1764
|
+
...source,
|
|
1765
|
+
...(skinned ? { skin: { buffer: {} as never, byteOffset: 0 } as never } : {}),
|
|
1766
|
+
material: materials[0] as MaterialSnapshot,
|
|
1767
|
+
materials,
|
|
1768
|
+
gpuDrivenDraws: draws,
|
|
1769
|
+
};
|
|
1770
|
+
const geometry = {
|
|
1771
|
+
...mesh(device),
|
|
1772
|
+
submeshes: [0, 1, 2].map((materialSlot) => ({
|
|
1773
|
+
indexOffset: materialSlot * 3,
|
|
1774
|
+
indexCount: 3,
|
|
1775
|
+
vertexCount: 3,
|
|
1776
|
+
materialSlot,
|
|
1777
|
+
topology: 'triangle-list' as const,
|
|
1778
|
+
})),
|
|
1779
|
+
};
|
|
1780
|
+
const identityInstanceBuffer = device
|
|
1781
|
+
.createBuffer({ size: 64, usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST })
|
|
1782
|
+
.unwrap();
|
|
1783
|
+
const instancesBindGroupLayout = device
|
|
1784
|
+
.createBindGroupLayout({
|
|
1785
|
+
entries: [
|
|
1786
|
+
{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } },
|
|
1787
|
+
{ binding: 1, visibility: 0x1, buffer: { type: 'uniform' } },
|
|
1788
|
+
],
|
|
1789
|
+
})
|
|
1790
|
+
.unwrap();
|
|
1791
|
+
const worldEntity = worldEntityKey(sourceForRecord.worldId, sourceForRecord.entityKey);
|
|
1792
|
+
const claimedKey = gpuDrivenShadowDrawKey(worldEntity, 20, 0, 0);
|
|
1793
|
+
const residualKeys = new Set([
|
|
1794
|
+
gpuDrivenShadowDrawKey(worldEntity, 21, 1, 0),
|
|
1795
|
+
gpuDrivenShadowDrawKey(worldEntity, 22, 2, 0),
|
|
1796
|
+
gpuDrivenShadowDrawKey(worldEntity, 22, 2, 7),
|
|
1797
|
+
]);
|
|
1798
|
+
const shadowDispatches = new Map([
|
|
1799
|
+
[
|
|
1800
|
+
0,
|
|
1801
|
+
new Map([
|
|
1802
|
+
[
|
|
1803
|
+
20,
|
|
1804
|
+
[
|
|
1805
|
+
{
|
|
1806
|
+
passIndex: 0,
|
|
1807
|
+
materialHandle: 20,
|
|
1808
|
+
vertexEntry: undefined,
|
|
1809
|
+
fragmentEntry: undefined,
|
|
1810
|
+
materialShaderId: 'forgeax::default-shadow-caster',
|
|
1811
|
+
renderState: undefined,
|
|
1812
|
+
materialSlot: 0,
|
|
1813
|
+
paramSnapshot: materials[0]?.paramSnapshot,
|
|
1814
|
+
},
|
|
1815
|
+
],
|
|
1816
|
+
],
|
|
1817
|
+
[
|
|
1818
|
+
21,
|
|
1819
|
+
[
|
|
1820
|
+
{
|
|
1821
|
+
passIndex: 0,
|
|
1822
|
+
materialHandle: 21,
|
|
1823
|
+
vertexEntry: undefined,
|
|
1824
|
+
fragmentEntry: undefined,
|
|
1825
|
+
materialShaderId: 'forgeax::default-shadow-caster',
|
|
1826
|
+
renderState: undefined,
|
|
1827
|
+
materialSlot: 1,
|
|
1828
|
+
paramSnapshot: materials[1]?.paramSnapshot,
|
|
1829
|
+
},
|
|
1830
|
+
],
|
|
1831
|
+
],
|
|
1832
|
+
[
|
|
1833
|
+
22,
|
|
1834
|
+
[
|
|
1835
|
+
{
|
|
1836
|
+
passIndex: 0,
|
|
1837
|
+
materialHandle: 22,
|
|
1838
|
+
vertexEntry: undefined,
|
|
1839
|
+
fragmentEntry: undefined,
|
|
1840
|
+
materialShaderId: 'forgeax::default-shadow-caster',
|
|
1841
|
+
renderState: undefined,
|
|
1842
|
+
materialSlot: 2,
|
|
1843
|
+
paramSnapshot: materials[2]?.paramSnapshot,
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
passIndex: 7,
|
|
1847
|
+
materialHandle: 22,
|
|
1848
|
+
vertexEntry: undefined,
|
|
1849
|
+
fragmentEntry: undefined,
|
|
1850
|
+
materialShaderId: 'custom-shadow-caster',
|
|
1851
|
+
renderState: { cullMode: 'none' as const, frontFace: 'cw' as const },
|
|
1852
|
+
materialSlot: 2,
|
|
1853
|
+
paramSnapshot: materials[2]?.paramSnapshot,
|
|
1854
|
+
},
|
|
1855
|
+
],
|
|
1856
|
+
],
|
|
1857
|
+
]),
|
|
1858
|
+
],
|
|
1859
|
+
]);
|
|
1860
|
+
const indexedDraws: number[][] = [];
|
|
1861
|
+
const pipelineRequests: unknown[][] = [];
|
|
1862
|
+
const pass = {
|
|
1863
|
+
setPipeline: () => undefined,
|
|
1864
|
+
setVertexBuffer: () => undefined,
|
|
1865
|
+
setIndexBuffer: () => undefined,
|
|
1866
|
+
setBindGroup: () => undefined,
|
|
1867
|
+
drawIndexed: (...args: number[]) => indexedDraws.push(args),
|
|
1868
|
+
draw: () => undefined,
|
|
1869
|
+
} as unknown as RhiRenderPassEncoder;
|
|
1870
|
+
const context = {
|
|
1871
|
+
runtime: {
|
|
1872
|
+
device,
|
|
1873
|
+
getMaterialShaderPipeline: (...args: unknown[]) => {
|
|
1874
|
+
pipelineRequests.push(args);
|
|
1875
|
+
return {} as never;
|
|
1876
|
+
},
|
|
1877
|
+
},
|
|
1878
|
+
pipelineState: {
|
|
1879
|
+
identityInstanceBuffer,
|
|
1880
|
+
pbrSkinMeshBindGroupLayout: {} as never,
|
|
1881
|
+
skinPaletteAllocator: { bindingWindowBytes: 256 },
|
|
1882
|
+
instancesBindGroupLayout,
|
|
1883
|
+
materialBindGroupLayout: {} as never,
|
|
1884
|
+
materialUniformBuffer: { buffer: {} as never, sizeInBytes: 256 },
|
|
1885
|
+
meshStorageBuffer: { buffer: {} as never, sizeInBytes: 256 },
|
|
1886
|
+
defaultSampler: {} as never,
|
|
1887
|
+
fallbackTextureView: {} as never,
|
|
1888
|
+
defaultNormalTextureView: {} as never,
|
|
1889
|
+
defaultWhiteTextureView: {} as never,
|
|
1890
|
+
skylightFallback: {
|
|
1891
|
+
irradianceView: {} as never,
|
|
1892
|
+
prefilterView: {} as never,
|
|
1893
|
+
brdfLutView: {} as never,
|
|
1894
|
+
sampler: {} as never,
|
|
1895
|
+
intensityBuffer: {} as never,
|
|
1896
|
+
},
|
|
1897
|
+
},
|
|
1898
|
+
validatedOrdered: [
|
|
1899
|
+
{
|
|
1900
|
+
source: sourceForRecord,
|
|
1901
|
+
mesh: geometry,
|
|
1902
|
+
renderableIndex: 0,
|
|
1903
|
+
},
|
|
1904
|
+
],
|
|
1905
|
+
frameState: {
|
|
1906
|
+
instanceBuffers: new Map(),
|
|
1907
|
+
meshBindGroupCache: new Map(),
|
|
1908
|
+
instancesBgPerEntity: new Map(),
|
|
1909
|
+
singletonMaterialCache: new Map(),
|
|
1910
|
+
materialBgShared: new Map(),
|
|
1911
|
+
},
|
|
1912
|
+
materialBgAssemblyCache: new Map(),
|
|
1913
|
+
store: { materialResourceEpoch: 0 } as never,
|
|
1914
|
+
world: new World(),
|
|
1915
|
+
bindGroupCounts: { createBindGroup: 0, keys: [] },
|
|
1916
|
+
dispatch: [],
|
|
1917
|
+
} as never;
|
|
1918
|
+
const recorded = recordShadowCasterDraws(
|
|
1919
|
+
context,
|
|
1920
|
+
pass,
|
|
1921
|
+
{} as never,
|
|
1922
|
+
{} as never,
|
|
1923
|
+
new Set([0]),
|
|
1924
|
+
new Map([[0, new Set([20, 21, 22])]]),
|
|
1925
|
+
shadowDispatches,
|
|
1926
|
+
new Set([claimedKey]),
|
|
1927
|
+
);
|
|
1928
|
+
expect(indexedDraws).toHaveLength(3);
|
|
1929
|
+
expect(recorded).toEqual(residualKeys);
|
|
1930
|
+
expect(recorded.has(claimedKey)).toBe(false);
|
|
1931
|
+
const authored = pipelineRequests.find((args) => args[0] === 'custom-shadow-caster');
|
|
1932
|
+
expect(authored).toBeDefined();
|
|
1933
|
+
expect(authored?.[2]).toEqual({ cullMode: 'none', frontFace: 'cw' });
|
|
1934
|
+
expect(authored?.[15]).toBe(skinned ? 'pbr-skin' : 'pbr');
|
|
1935
|
+
if (skinned) expect(authored?.[5]).toContain('SKINNING_DISABLED=false');
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
it('fails closed at every shadow front door when expected casters lack bind groups', () => {
|
|
1939
|
+
const membership: ShadowCasterMembership[] = [
|
|
1940
|
+
{
|
|
1941
|
+
worldEntity: 1,
|
|
1942
|
+
renderableIndex: 0,
|
|
1943
|
+
drawItemIndex: 0,
|
|
1944
|
+
materialHandle: 20,
|
|
1945
|
+
passIndex: 0,
|
|
1946
|
+
materialShaderId: 'forgeax::default-shadow-caster',
|
|
1947
|
+
cpuReason: 'unprepared',
|
|
1948
|
+
},
|
|
1949
|
+
];
|
|
1950
|
+
const context = {
|
|
1951
|
+
runtime: {
|
|
1952
|
+
device: { caps: { storageBuffer: false } },
|
|
1953
|
+
getMaterialShaderPipeline: () => null,
|
|
1954
|
+
},
|
|
1955
|
+
pipelineState: { perPassResources: { shadowSampler: null } },
|
|
1956
|
+
frameState: {
|
|
1957
|
+
pointShadowSnapshots: [{ shadowAtlasLayer: 0 }],
|
|
1958
|
+
spotShadowSnapshots: [{ shadowAtlasTile: 0, lightViewProj: new Float32Array(16) }],
|
|
1959
|
+
},
|
|
1960
|
+
dispatch: [],
|
|
1961
|
+
shadowCasterMembership: membership,
|
|
1962
|
+
} as never;
|
|
1963
|
+
const pass = {} as RhiRenderPassEncoder;
|
|
1964
|
+
const calls = [
|
|
1965
|
+
() =>
|
|
1966
|
+
encodeDirectionalShadowPass(context, pass, 0, {
|
|
1967
|
+
x: 0,
|
|
1968
|
+
y: 0,
|
|
1969
|
+
w: 1,
|
|
1970
|
+
h: 1,
|
|
1971
|
+
}),
|
|
1972
|
+
() => encodePointShadowPass(context, pass, 0, 0),
|
|
1973
|
+
() => encodeSpotShadowPass(context, pass, 0),
|
|
1974
|
+
];
|
|
1975
|
+
for (const call of calls) {
|
|
1976
|
+
expect(call).toThrowError(RhiError);
|
|
1977
|
+
try {
|
|
1978
|
+
call();
|
|
1979
|
+
} catch (error) {
|
|
1980
|
+
expect(error).toBeInstanceOf(RhiError);
|
|
1981
|
+
expect((error as RhiError).code).toBe('rhi-not-available');
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
|
|
1986
|
+
it('labels a prepared-contract admission rejection for the CPU residual', () => {
|
|
1987
|
+
const source = preparedSnapshot(9, 3);
|
|
1988
|
+
const draw = source.gpuDrivenDraws?.[0];
|
|
1989
|
+
expect(draw?.prepared).toBeDefined();
|
|
1990
|
+
if (draw === undefined || draw.prepared === undefined) return;
|
|
1991
|
+
const mismatched = {
|
|
1992
|
+
...draw,
|
|
1993
|
+
prepared: {
|
|
1994
|
+
...draw.prepared,
|
|
1995
|
+
identity: {
|
|
1996
|
+
...draw.prepared.identity,
|
|
1997
|
+
material: 'forgeax::pbr-skin' as const,
|
|
1998
|
+
deformation: 'skin' as const,
|
|
1999
|
+
},
|
|
2000
|
+
},
|
|
2001
|
+
};
|
|
2002
|
+
const material = {
|
|
2003
|
+
...source.material,
|
|
2004
|
+
materialShaderId: 'forgeax::default-standard-pbr',
|
|
2005
|
+
} as MaterialSnapshot;
|
|
2006
|
+
expect(
|
|
2007
|
+
shadowCasterCpuReason(
|
|
2008
|
+
material,
|
|
2009
|
+
mismatched,
|
|
2010
|
+
[
|
|
2011
|
+
{
|
|
2012
|
+
entityIndex: source.entityKey,
|
|
2013
|
+
materialHandle: material.materialHandle ?? -1,
|
|
2014
|
+
renderableIndex: 0,
|
|
2015
|
+
passIndex: 0,
|
|
2016
|
+
queue: 2000,
|
|
2017
|
+
layer: 0,
|
|
2018
|
+
tags: { LightMode: 'ShadowCaster' },
|
|
2019
|
+
renderState: undefined,
|
|
2020
|
+
defines: undefined,
|
|
2021
|
+
vertexEntry: undefined,
|
|
2022
|
+
fragmentEntry: undefined,
|
|
2023
|
+
materialShaderId: 'forgeax::default-shadow-caster',
|
|
2024
|
+
paramSnapshot: undefined,
|
|
2025
|
+
} as DispatchEntry,
|
|
2026
|
+
],
|
|
2027
|
+
undefined,
|
|
2028
|
+
undefined,
|
|
2029
|
+
),
|
|
2030
|
+
).toBe('prepared-contract');
|
|
2031
|
+
});
|
|
1050
2032
|
});
|