@forgeax/engine-render 0.1.32 → 0.1.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1374 -142
- package/dist/assembly/analytic-fog-registration.d.ts +3 -0
- package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
- package/dist/assembly/backend-contract.d.ts +2 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -1
- package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
- package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
- package/dist/assembly/canvas-draw-guard.d.ts +3 -0
- package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
- package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
- package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
- package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
- package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
- package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
- package/dist/assembly/factory.d.ts +1 -1
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/fxaa-registration.d.ts +4 -0
- package/dist/assembly/fxaa-registration.d.ts.map +1 -0
- package/dist/assembly/host-contract.d.ts +3 -3
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/lod-observation.d.ts +4 -0
- package/dist/assembly/lod-observation.d.ts.map +1 -0
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
- package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
- package/dist/assembly/material-shader-policy.d.ts +16 -2
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
- package/dist/assembly/motion-blur-registration.d.ts +3 -0
- package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
- package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
- package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/render-feature-post-processes.d.ts +6 -0
- package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
- package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
- package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-inspection.d.ts +11 -0
- package/dist/assembly/renderer-inspection.d.ts.map +1 -1
- package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
- package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
- package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
- package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +2 -2
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
- package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +1 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/authoring.mjs +1 -1
- package/dist/barrel-distortion.d.ts +47 -0
- package/dist/barrel-distortion.d.ts.map +1 -0
- package/dist/bloom-admission.d.ts +13 -0
- package/dist/bloom-admission.d.ts.map +1 -0
- package/dist/{chunk-PA5DRJNI.mjs → chunk-ELJFJ2BU.mjs} +2 -2
- package/dist/chunk-ELJFJ2BU.mjs.map +1 -0
- package/dist/{chunk-JCIRYQ4V.mjs → chunk-HEB564WF.mjs} +10 -4
- package/dist/chunk-HEB564WF.mjs.map +1 -0
- package/dist/{chunk-JRIPCMYC.mjs → chunk-MKDIT2EM.mjs} +1584 -375
- package/dist/chunk-MKDIT2EM.mjs.map +1 -0
- package/dist/{chunk-S665NJNF.mjs → chunk-OCDLEI6I.mjs} +3 -3
- package/dist/{chunk-S665NJNF.mjs.map → chunk-OCDLEI6I.mjs.map} +1 -1
- package/dist/chunk-ODZWR6JQ.mjs +4927 -0
- package/dist/chunk-ODZWR6JQ.mjs.map +1 -0
- package/dist/{chunk-DRR7D3KX.mjs → chunk-SHFUFJ5N.mjs} +192 -18
- package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
- package/dist/{chunk-4NNZQCL3.mjs → chunk-UI5SFODX.mjs} +223 -277
- package/dist/chunk-UI5SFODX.mjs.map +1 -0
- package/dist/{chunk-GWCEMDVR.mjs → chunk-UINHLCZA.mjs} +787 -80
- package/dist/chunk-UINHLCZA.mjs.map +1 -0
- package/dist/{chunk-6TFBXLRJ.mjs → chunk-WEX45HKH.mjs} +222 -337
- package/dist/chunk-WEX45HKH.mjs.map +1 -0
- package/dist/cloud/density.d.ts +55 -0
- package/dist/cloud/density.d.ts.map +1 -0
- package/dist/cloud/extract.d.ts +25 -0
- package/dist/cloud/extract.d.ts.map +1 -0
- package/dist/cloud/feature.d.ts +57 -0
- package/dist/cloud/feature.d.ts.map +1 -0
- package/dist/cloud/frame-context.d.ts +8 -0
- package/dist/cloud/frame-context.d.ts.map +1 -0
- package/dist/cloud/inspection.d.ts +59 -0
- package/dist/cloud/inspection.d.ts.map +1 -0
- package/dist/cloud/optics.d.ts +56 -0
- package/dist/cloud/optics.d.ts.map +1 -0
- package/dist/cloud/parameters.d.ts +73 -0
- package/dist/cloud/parameters.d.ts.map +1 -0
- package/dist/cloud/resources.d.ts +60 -0
- package/dist/cloud/resources.d.ts.map +1 -0
- package/dist/cloud/shadow.d.ts +42 -0
- package/dist/cloud/shadow.d.ts.map +1 -0
- package/dist/cloud/temporal.d.ts +74 -0
- package/dist/cloud/temporal.d.ts.map +1 -0
- package/dist/components/atmosphere.d.ts +2 -0
- package/dist/components/atmosphere.d.ts.map +1 -1
- package/dist/components/barrel-distortion.d.ts +13 -0
- package/dist/components/barrel-distortion.d.ts.map +1 -0
- package/dist/components/camera.d.ts +20 -4
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/cloud-layer.d.ts +29 -0
- package/dist/components/cloud-layer.d.ts.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/instances.d.ts +20 -6
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/components/mesh-filter.d.ts +6 -5
- package/dist/components/mesh-filter.d.ts.map +1 -1
- package/dist/components/motion-blur.d.ts +1 -0
- package/dist/components/motion-blur.d.ts.map +1 -1
- package/dist/construct-renderer.d.ts +1 -1
- package/dist/construct-renderer.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +9751 -3528
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +6 -0
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/dynamic-geometry.d.ts +5 -1
- package/dist/dynamic-geometry.d.ts.map +1 -1
- package/dist/environment/analytic-fog.d.ts +6 -0
- package/dist/environment/analytic-fog.d.ts.map +1 -0
- package/dist/errors/cloud.d.ts +59 -0
- package/dist/errors/cloud.d.ts.map +1 -0
- package/dist/errors/render.d.ts +23 -5
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/examples/single-layer-medium-surface.d.ts +47 -0
- package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +12 -1
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/extract/environment.d.ts +2 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/features/barrel-distortion.d.ts +29 -0
- package/dist/features/barrel-distortion.d.ts.map +1 -0
- package/dist/features/error-reporting.d.ts +17 -0
- package/dist/features/error-reporting.d.ts.map +1 -0
- package/dist/features/host.d.ts +15 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
- package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
- package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
- package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
- package/dist/features/plan.d.ts +15 -4
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +9 -5
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/render-graph-compute.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts +5 -0
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/features/render-graph-raster.d.ts +1 -0
- package/dist/features/render-graph-raster.d.ts.map +1 -1
- package/dist/features/targets.d.ts +2 -0
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +44 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +15 -7
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-dirty-ranges.d.ts +11 -0
- package/dist/gpu-dirty-ranges.d.ts.map +1 -0
- package/dist/gpu-driven/batch-topology.d.ts +17 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/material-artifacts.d.ts +6 -0
- package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +7 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster-material.d.ts +178 -0
- package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +50 -4
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/resource-allocation.d.ts +31 -0
- package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
- package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
- package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
- package/dist/gpu-driven/view-gpu.d.ts +35 -2
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +11 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +2 -2
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +10 -1
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +31 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +258 -8
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +178 -3
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instance-buffer-cache.d.ts +4 -4
- package/dist/instance-buffer-cache.d.ts.map +1 -1
- package/dist/instances.d.ts +49 -94
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +7 -7
- package/dist/pbr-pipeline.d.ts +19 -1
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
- package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
- package/dist/pipeline/standard-output/types.d.ts +4 -2
- package/dist/pipeline/standard-output/types.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +28 -19
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +1 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +2 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
- package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
- package/dist/record/barrel-distortion-frame.d.ts +15 -0
- package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
- package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
- package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
- package/dist/record/fold-buckets.d.ts +95 -0
- package/dist/record/fold-buckets.d.ts.map +1 -0
- package/dist/record/fold-instance-buffer.d.ts +6 -0
- package/dist/record/fold-instance-buffer.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +1 -0
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +40 -3
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +14 -62
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-draw-receipts.d.ts +11 -0
- package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
- package/dist/record/gpu-timing.d.ts +8 -0
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +21 -5
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +7 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +13 -2
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +6 -5
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/motion-blur-receipt.d.ts +11 -0
- package/dist/record/motion-blur-receipt.d.ts.map +1 -0
- package/dist/record/probe-blend-buffer.d.ts +16 -0
- package/dist/record/probe-blend-buffer.d.ts.map +1 -0
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +34 -31
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +2 -0
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +6 -4
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +22 -2
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +11 -3
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/record/volume-params.d.ts +9 -0
- package/dist/record/volume-params.d.ts.map +1 -0
- package/dist/recovery/render-system-candidate.d.ts +10 -0
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +66 -8
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +106 -3
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +3 -2
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +24 -10
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system-presentation.d.ts +23 -0
- package/dist/render-system-presentation.d.ts.map +1 -0
- package/dist/render-system-producer-roots.d.ts +10 -0
- package/dist/render-system-producer-roots.d.ts.map +1 -0
- package/dist/render-system-residency.d.ts +12 -0
- package/dist/render-system-residency.d.ts.map +1 -0
- package/dist/render-system.d.ts +26 -15
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/probe-blend.d.ts +27 -0
- package/dist/scene/probe-blend.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +14 -5
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/ssao-buffers.d.ts +9 -0
- package/dist/ssao-buffers.d.ts.map +1 -1
- package/dist/surface/dynamic-input.d.ts +133 -0
- package/dist/surface/dynamic-input.d.ts.map +1 -0
- package/dist/surface/gpu-driven.d.ts +41 -0
- package/dist/surface/gpu-driven.d.ts.map +1 -0
- package/dist/surface/submission-observation.d.ts +136 -0
- package/dist/surface/submission-observation.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +7 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/gpu.d.ts +13 -1
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- package/dist/temporal/inspection.d.ts.map +1 -1
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
- package/dist/temporal/view.d.ts +6 -1
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/transmission/single-layer-medium.d.ts +89 -0
- package/dist/transmission/single-layer-medium.d.ts.map +1 -0
- package/dist/typed-render-graph-primitives.d.ts +24 -9
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/component.d.ts +9 -5
- package/dist/volume/component.d.ts.map +1 -1
- package/dist/volume/extract.d.ts +1 -1
- package/dist/volume/extract.d.ts.map +1 -1
- package/dist/volume/inspection.d.ts +2 -0
- package/dist/volume/inspection.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +4 -1
- package/dist/volume/passes.d.ts.map +1 -1
- package/dist/volume/resources.d.ts.map +1 -1
- package/dist/volume/temporal.d.ts +3 -1
- package/dist/volume/temporal.d.ts.map +1 -1
- package/package.json +22 -21
- package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
- package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
- package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
- package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
- package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
- package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
- package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
- package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
- package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
- package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
- package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
- package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
- package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
- package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
- package/src/__tests__/environment-selection.unit.test.ts +12 -0
- package/src/__tests__/factory-contract.integration.test.ts +626 -113
- package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
- package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
- package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
- package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
- package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +2 -2
- package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +7 -6
- package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +103 -4
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
- package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
- package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
- package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
- package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
- package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
- package/src/__tests__/gpu-scene.unit.test.ts +210 -9
- package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
- package/src/__tests__/instances-culling.integration.test.ts +10 -36
- package/src/__tests__/instances-store.unit.test.ts +105 -238
- package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
- package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +146 -0
- package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
- package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
- package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
- package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
- package/src/__tests__/motion-blur-compute.dawn.test.ts +589 -0
- package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
- package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
- package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +58 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +21 -0
- package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
- package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
- package/src/__tests__/probe-bindings.unit.test.ts +582 -1
- package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
- package/src/__tests__/public-surface.test-d.ts +8 -0
- package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +9 -0
- package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
- package/src/__tests__/render-feature-error-reporting.unit.test.ts +44 -0
- package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
- package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
- package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
- package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
- package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +37 -6
- package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
- package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
- package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +41 -7
- package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +33 -11
- package/src/__tests__/resource-allocation.unit.test.ts +123 -0
- package/src/__tests__/shader-manifest-fixture.ts +4 -4
- package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
- package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
- package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +17 -0
- package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
- package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
- package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
- package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
- package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
- package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
- package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
- package/src/__tests__/standard-pipeline.integration.test.ts +200 -9
- package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
- package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
- package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
- package/src/__tests__/temporal-performance.integration.test.ts +3 -3
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
- package/src/__tests__/temporal-view.unit.test.ts +2 -1
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
- package/src/__tests__/visibility-instances.integration.test.ts +7 -19
- package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
- package/src/__tests__/volume-optics-oracle.unit.test.ts +7 -3
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +189 -15
- package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
- package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
- package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +558 -68
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +23 -5
- package/src/assembly/analytic-fog-registration.ts +14 -0
- package/src/assembly/backend-contract.ts +30 -0
- package/src/assembly/barrel-distortion-inspection.ts +22 -0
- package/src/assembly/canvas-draw-guard.ts +11 -0
- package/src/assembly/cloud-shadow-entry.ts +22 -0
- package/src/assembly/dynamic-geometry-host.ts +65 -37
- package/src/assembly/dynamic-geometry-runtime.ts +7 -1
- package/src/assembly/factory.ts +2 -30
- package/src/assembly/fxaa-registration.ts +17 -0
- package/src/assembly/host-contract.ts +3 -3
- package/src/assembly/lod-observation.ts +12 -0
- package/src/assembly/material/assembly.ts +25 -13
- package/src/assembly/material/pipeline-helpers.ts +53 -12
- package/src/assembly/material-shader-policy.ts +55 -0
- package/src/assembly/material-shader-prewarm.ts +21 -10
- package/src/assembly/motion-blur-registration.ts +17 -0
- package/src/assembly/recovery/device-loss-fanout.ts +13 -0
- package/src/assembly/recovery/renderer-recover.ts +41 -18
- package/src/assembly/render-feature-post-processes.ts +54 -0
- package/src/assembly/renderer-facade.ts +6 -1
- package/src/assembly/renderer-feature-inspection.ts +8 -2
- package/src/assembly/renderer-frame-transaction.ts +23 -0
- package/src/assembly/renderer-inspection.ts +28 -0
- package/src/assembly/skin-palette-owner.ts +1 -1
- package/src/assembly/temporal-post-process-prewarm.ts +40 -0
- package/src/assembly/webgpu-pbr-ready.ts +9 -1
- package/src/assembly/webgpu-ready-contract.ts +8 -3
- package/src/assembly/webgpu-ready-per-pass.ts +104 -0
- package/src/assembly/webgpu-ready.ts +296 -306
- package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
- package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
- package/src/assembly/webgpu-renderer.ts +228 -281
- package/src/assembly/webgpu-vertex-layouts.ts +1 -1
- package/src/barrel-distortion.ts +247 -0
- package/src/bloom-admission.ts +21 -0
- package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
- package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
- package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
- package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
- package/src/cloud/density.ts +600 -0
- package/src/cloud/extract.ts +110 -0
- package/src/cloud/feature.ts +1810 -0
- package/src/cloud/frame-context.ts +42 -0
- package/src/cloud/inspection.ts +130 -0
- package/src/cloud/optics.ts +285 -0
- package/src/cloud/parameters.ts +255 -0
- package/src/cloud/resources.ts +122 -0
- package/src/cloud/shadow.ts +167 -0
- package/src/cloud/temporal.ts +226 -0
- package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
- package/src/components/atmosphere.ts +4 -0
- package/src/components/barrel-distortion.ts +46 -0
- package/src/components/camera.ts +66 -10
- package/src/components/cloud-layer.ts +47 -0
- package/src/components/index.ts +2 -0
- package/src/components/instances.ts +18 -45
- package/src/components/mesh-filter.ts +11 -9
- package/src/components/motion-blur.ts +1 -0
- package/src/components/sprite-instances.ts +1 -1
- package/src/construct-renderer.ts +1 -1
- package/src/device/gpu-residency.ts +56 -12
- package/src/dynamic-geometry.ts +12 -1
- package/src/environment/analytic-fog.ts +44 -0
- package/src/environment/background.ts +2 -2
- package/src/errors/cloud.ts +103 -0
- package/src/errors/render.ts +58 -8
- package/src/examples/single-layer-medium-surface.ts +59 -0
- package/src/extract/camera.ts +35 -8
- package/src/extract/contracts.ts +12 -0
- package/src/extract/environment.ts +4 -0
- package/src/features/barrel-distortion.ts +172 -0
- package/src/features/error-reporting.ts +98 -0
- package/src/features/host.ts +138 -24
- package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
- package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
- package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
- package/src/features/motion-blur/motion-blur-params.ts +70 -2
- package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
- package/src/features/plan.ts +33 -8
- package/src/features/prepared-gpu-work.ts +86 -35
- package/src/features/render-graph-compute.ts +13 -5
- package/src/features/render-graph-contribution.ts +11 -0
- package/src/features/render-graph-raster.ts +45 -3
- package/src/features/targets.ts +66 -0
- package/src/features/types.ts +47 -0
- package/src/fullscreen-post-process-pass.ts +41 -9
- package/src/gpu-dirty-ranges.ts +48 -0
- package/src/gpu-driven/batch-topology.ts +53 -0
- package/src/gpu-driven/material-artifacts.ts +35 -32
- package/src/gpu-driven/prepared-draw.ts +27 -14
- package/src/gpu-driven/production-raster-material.ts +1894 -0
- package/src/gpu-driven/production-raster.ts +904 -787
- package/src/gpu-driven/resource-allocation.ts +142 -0
- package/src/gpu-driven/shadow-ownership.ts +71 -0
- package/src/gpu-driven/view-gpu.ts +181 -10
- package/src/gpu-scene.ts +259 -101
- package/src/hdrp-buffers.ts +4 -3
- package/src/ibl/skylight-bind-group.ts +49 -7
- package/src/index.ts +228 -9
- package/src/inspection-types.ts +222 -2
- package/src/instance-buffer-cache.ts +4 -4
- package/src/instances.ts +151 -492
- package/src/pbr-pipeline.ts +219 -13
- package/src/pipeline/single-layer-medium-passes.ts +267 -0
- package/src/pipeline/standard-forward-lane.ts +313 -57
- package/src/pipeline/standard-output/graph.ts +8 -2
- package/src/pipeline/standard-output/types.ts +12 -1
- package/src/pipeline/standard-pipeline.ts +291 -18
- package/src/pipeline/standard-post.ts +397 -102
- package/src/pipeline-builder.ts +1 -1
- package/src/pipeline-spec.ts +5 -5
- package/src/plugin.ts +4 -0
- package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
- package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
- package/src/prepare/prepared-graphics-resolver.ts +2 -0
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +1 -1
- package/src/record/__tests__/temporal-payload.unit.test.ts +3 -2
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
- package/src/record/barrel-distortion-frame.ts +110 -0
- package/src/record/dynamic-geometry-consumption.ts +39 -9
- package/src/record/fold-buckets.ts +852 -0
- package/src/record/fold-instance-buffer.ts +51 -0
- package/src/record/frame-lighting.ts +36 -2
- package/src/record/frame-snapshot.ts +44 -4
- package/src/record/frame-targets.ts +10 -6
- package/src/record/frame.ts +373 -1099
- package/src/record/gpu-draw-receipts.ts +38 -0
- package/src/record/gpu-timing.ts +48 -5
- package/src/record/main-pass-geometry.ts +281 -83
- package/src/record/main-pass-material.ts +66 -4
- package/src/record/main-pass-sprite-draws.ts +13 -66
- package/src/record/main-pass.ts +156 -20
- package/src/record/mesh-ssbo.ts +43 -6
- package/src/record/motion-blur-receipt.ts +91 -0
- package/src/record/probe-blend-buffer.ts +180 -0
- package/src/record/recovery-pipeline.ts +35 -3
- package/src/record/render-context.ts +51 -31
- package/src/record/shadow-pass.ts +36 -2
- package/src/record/skybox-post-pass.ts +150 -200
- package/src/record/typed-frame-graph.ts +824 -44
- package/src/record/view-ubo.ts +26 -3
- package/src/record/volume-params.ts +225 -0
- package/src/recovery/render-system-candidate.ts +257 -52
- package/src/reflection/record-owner.ts +7 -3
- package/src/render-contract.ts +71 -8
- package/src/render-graph-primitives.ts +7 -1
- package/src/render-pipeline.ts +136 -1
- package/src/render-system-extract-tail.ts +72 -60
- package/src/render-system-extract.ts +117 -87
- package/src/render-system-presentation.ts +56 -0
- package/src/render-system-producer-roots.ts +96 -0
- package/src/render-system-residency.ts +159 -0
- package/src/render-system.ts +601 -482
- package/src/scene/probe-blend.ts +96 -4
- package/src/scene/render-scene.ts +431 -281
- package/src/ssao-buffers.ts +95 -2
- package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
- package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
- package/src/surface/dynamic-input.ts +658 -0
- package/src/surface/gpu-driven.ts +147 -0
- package/src/surface/submission-observation.ts +517 -0
- package/src/systems/skin-palette-allocator.ts +4 -1
- package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
- package/src/temporal/frame-coordinator.ts +8 -1
- package/src/temporal/frame.ts +20 -0
- package/src/temporal/gpu.ts +101 -17
- package/src/temporal/inspection.ts +7 -1
- package/src/temporal/standard-scene-data.ts +5 -0
- package/src/temporal/target-coverage-attachment.ts +4 -0
- package/src/temporal/view.ts +13 -2
- package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
- package/src/transmission/single-layer-medium.ts +322 -0
- package/src/typed-render-graph-primitives.ts +191 -50
- package/src/volume/component.ts +10 -10
- package/src/volume/extract.ts +11 -5
- package/src/volume/inspection.ts +3 -0
- package/src/volume/passes.ts +95 -8
- package/src/volume/resources.ts +2 -1
- package/src/volume/temporal.ts +11 -0
- package/dist/chunk-4NNZQCL3.mjs.map +0 -1
- package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
- package/dist/chunk-DRR7D3KX.mjs.map +0 -1
- package/dist/chunk-GWCEMDVR.mjs.map +0 -1
- package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
- package/dist/chunk-JRIPCMYC.mjs.map +0 -1
- package/dist/chunk-LULCA3OF.mjs +0 -1713
- package/dist/chunk-LULCA3OF.mjs.map +0 -1
- package/dist/chunk-PA5DRJNI.mjs.map +0 -1
|
@@ -19,6 +19,7 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
19
19
|
import type { RhiBackendPack } from '../assembly/backend-contract';
|
|
20
20
|
import { createRenderer as constructRenderer } from '../assembly/factory';
|
|
21
21
|
import {
|
|
22
|
+
BarrelDistortion,
|
|
22
23
|
DirectionalLight,
|
|
23
24
|
DynamicResolution,
|
|
24
25
|
MeshFilter,
|
|
@@ -83,6 +84,10 @@ async function createBloomScenario() {
|
|
|
83
84
|
antialias: ANTIALIAS_NONE,
|
|
84
85
|
tonemap: TONEMAP_ACES_FILMIC,
|
|
85
86
|
bloom: BLOOM_ENABLED,
|
|
87
|
+
bloomThreshold: 1,
|
|
88
|
+
bloomIntensity: 1,
|
|
89
|
+
bloomSoftKnee: 0.5,
|
|
90
|
+
bloomScatter: 0.7,
|
|
86
91
|
},
|
|
87
92
|
},
|
|
88
93
|
)
|
|
@@ -103,13 +108,132 @@ async function createBloomScenario() {
|
|
|
103
108
|
const updated = world.update();
|
|
104
109
|
if (!updated.ok) throw updated.error;
|
|
105
110
|
};
|
|
111
|
+
const setBloomIntensity = (bloomIntensity: number) => {
|
|
112
|
+
world.set(camera, LocalCamera, { bloomIntensity }).unwrap();
|
|
113
|
+
const updated = world.update();
|
|
114
|
+
if (!updated.ok) throw updated.error;
|
|
115
|
+
};
|
|
106
116
|
const dispose = async () => {
|
|
107
117
|
await renderer.dispose();
|
|
108
118
|
};
|
|
109
|
-
return { renderer, draw, setBloom, dispose, canvas };
|
|
119
|
+
return { renderer, draw, setBloom, setBloomIntensity, dispose, canvas };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function createMotionBlurScenario() {
|
|
123
|
+
const renderer = await constructRenderer(
|
|
124
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
125
|
+
{ rhi },
|
|
126
|
+
{ shaderManifestUrl: renderLifecycleManifestUrl() },
|
|
127
|
+
);
|
|
128
|
+
if (!(await renderer.initialization).ok)
|
|
129
|
+
throw new Error('Motion Blur renderer initialization failed');
|
|
130
|
+
const world = new World();
|
|
131
|
+
const attached = renderer.attach(world);
|
|
132
|
+
if (!attached.ok) throw attached.error;
|
|
133
|
+
registerPropagateTransforms(world);
|
|
134
|
+
const camera = world
|
|
135
|
+
.spawn(
|
|
136
|
+
{ component: Transform, data: { pos: [0, 0, 4] } },
|
|
137
|
+
{
|
|
138
|
+
component: LocalCamera,
|
|
139
|
+
data: {
|
|
140
|
+
fov: 1,
|
|
141
|
+
aspect: 1,
|
|
142
|
+
near: 0.1,
|
|
143
|
+
far: 100,
|
|
144
|
+
antialias: ANTIALIAS_NONE,
|
|
145
|
+
tonemap: TONEMAP_ACES_FILMIC,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
.unwrap();
|
|
150
|
+
world
|
|
151
|
+
.addComponent(camera, {
|
|
152
|
+
component: MotionBlur,
|
|
153
|
+
data: { shutterAngle: 180, maxRadiusPixels: 8, sampleCount: 4, targetFps: 60 },
|
|
154
|
+
})
|
|
155
|
+
.unwrap();
|
|
156
|
+
const frameInput = () => ({
|
|
157
|
+
leases: [attached.value],
|
|
158
|
+
camera: { lease: attached.value },
|
|
159
|
+
environment: { lease: attached.value },
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
renderer,
|
|
163
|
+
draw: () => renderer.draw(frameInput()),
|
|
164
|
+
dispose: () => renderer.dispose(),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function createGenericPostProbeFeature(): RenderFeature<undefined> {
|
|
169
|
+
return {
|
|
170
|
+
identity: 'synthetic.generic-post-probe',
|
|
171
|
+
placement: 'post',
|
|
172
|
+
shaderModuleMode: 'immediate',
|
|
173
|
+
extract: () => ok(undefined),
|
|
174
|
+
plan: () =>
|
|
175
|
+
ok({
|
|
176
|
+
resources: [
|
|
177
|
+
{
|
|
178
|
+
kind: 'compute-program' as const,
|
|
179
|
+
name: 'generic-post-probe-program',
|
|
180
|
+
program: {
|
|
181
|
+
wgsl: '@compute @workgroup_size(1) fn main() {}',
|
|
182
|
+
entryPoints: ['main'],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
kind: 'compute-bindings' as const,
|
|
187
|
+
name: 'generic-post-probe-bindings',
|
|
188
|
+
program: 'generic-post-probe-program',
|
|
189
|
+
entries: [],
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
passes: [
|
|
193
|
+
{
|
|
194
|
+
kind: 'compute' as const,
|
|
195
|
+
name: 'generic-post-probe',
|
|
196
|
+
program: 'generic-post-probe-program',
|
|
197
|
+
bindings: 'generic-post-probe-bindings',
|
|
198
|
+
dispatches: [{ kind: 'direct' as const, entryPoint: 'main', workgroups: [1] }],
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
}),
|
|
202
|
+
};
|
|
110
203
|
}
|
|
111
204
|
|
|
112
205
|
describe('factory contract', () => {
|
|
206
|
+
it('does not publish renderer initialization before boot queue work settles', async () => {
|
|
207
|
+
const fence = deferred<undefined>();
|
|
208
|
+
const onSubmittedWorkDone = vi
|
|
209
|
+
.spyOn(RhiNullQueue.prototype, 'onSubmittedWorkDone')
|
|
210
|
+
.mockReturnValue(fence.promise);
|
|
211
|
+
const canvas = { width: 64, height: 64, getContext: () => null };
|
|
212
|
+
let renderer: Awaited<ReturnType<typeof constructRenderer>> | undefined;
|
|
213
|
+
try {
|
|
214
|
+
renderer = await constructRenderer(
|
|
215
|
+
canvas,
|
|
216
|
+
{ rhi },
|
|
217
|
+
{ shaderManifestUrl: renderLifecycleManifestUrl() },
|
|
218
|
+
);
|
|
219
|
+
let settled = false;
|
|
220
|
+
const initialization = renderer.initialization.then(() => {
|
|
221
|
+
settled = true;
|
|
222
|
+
});
|
|
223
|
+
await vi.waitFor(() => expect(onSubmittedWorkDone).toHaveBeenCalled(), {
|
|
224
|
+
timeout: 2_000,
|
|
225
|
+
});
|
|
226
|
+
expect(settled).toBe(false);
|
|
227
|
+
fence.resolve(undefined);
|
|
228
|
+
await initialization;
|
|
229
|
+
expect(settled).toBe(true);
|
|
230
|
+
} finally {
|
|
231
|
+
fence.resolve(undefined);
|
|
232
|
+
onSubmittedWorkDone.mockRestore();
|
|
233
|
+
await renderer?.dispose();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
113
237
|
it('requires one Renderer-owned target lifecycle seam', () => {
|
|
114
238
|
expect(existsSync(renderTargetHostSourcePath)).toBe(true);
|
|
115
239
|
const factorySource = readFileSync(factorySourcePath, 'utf8');
|
|
@@ -144,7 +268,7 @@ describe('factory contract', () => {
|
|
|
144
268
|
it('releases its profiler catalog contribution on dispose', async () => {
|
|
145
269
|
const profiler = createProfiler();
|
|
146
270
|
const renderer = await constructRenderer(
|
|
147
|
-
{ getContext: () => null },
|
|
271
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
148
272
|
{ profiler, rhi },
|
|
149
273
|
{ shaderManifestUrl: manifestUrl() },
|
|
150
274
|
);
|
|
@@ -172,7 +296,7 @@ describe('factory contract', () => {
|
|
|
172
296
|
|
|
173
297
|
try {
|
|
174
298
|
const renderer = await constructRenderer(
|
|
175
|
-
{ getContext: () => null },
|
|
299
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
176
300
|
{ rhi },
|
|
177
301
|
{ shaderManifestUrl: renderLifecycleManifestUrl() },
|
|
178
302
|
);
|
|
@@ -218,7 +342,7 @@ describe('factory contract', () => {
|
|
|
218
342
|
}),
|
|
219
343
|
} as never;
|
|
220
344
|
const renderer = await constructRenderer(
|
|
221
|
-
{ getContext: () => null },
|
|
345
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
222
346
|
{
|
|
223
347
|
rhi: timingRhi,
|
|
224
348
|
gpuPassTiming: { maxPassesPerFrame: 4, maxFramesInFlight: 1, retentionFrames: 2 },
|
|
@@ -231,7 +355,7 @@ describe('factory contract', () => {
|
|
|
231
355
|
|
|
232
356
|
it('completes the receipt-bound create, attach, draw, observe, recover chain', async () => {
|
|
233
357
|
const renderer = await constructRenderer(
|
|
234
|
-
{ getContext: () => null },
|
|
358
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
235
359
|
{ rhi },
|
|
236
360
|
{ shaderManifestUrl: manifestUrl() },
|
|
237
361
|
);
|
|
@@ -248,7 +372,7 @@ describe('factory contract', () => {
|
|
|
248
372
|
camera: { lease },
|
|
249
373
|
environment: { lease },
|
|
250
374
|
});
|
|
251
|
-
expect(frame.ok).toBe(true);
|
|
375
|
+
expect(frame.ok, frame.ok ? '' : JSON.stringify(frame.error)).toBe(true);
|
|
252
376
|
if (!frame.ok || frame.value === undefined) return;
|
|
253
377
|
|
|
254
378
|
const observed = await renderer.observe(frame.value, { include: ['timings'] });
|
|
@@ -261,7 +385,7 @@ describe('factory contract', () => {
|
|
|
261
385
|
|
|
262
386
|
it('rejects the removed Standard reflection-probe profile field at the Renderer boundary', async () => {
|
|
263
387
|
const renderer = await constructRenderer(
|
|
264
|
-
{ getContext: () => null },
|
|
388
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
265
389
|
{ rhi },
|
|
266
390
|
{ shaderManifestUrl: manifestUrl() },
|
|
267
391
|
);
|
|
@@ -277,7 +401,7 @@ describe('factory contract', () => {
|
|
|
277
401
|
|
|
278
402
|
it('projects stable output and observation identity without pixel payloads', async () => {
|
|
279
403
|
const renderer = await constructRenderer(
|
|
280
|
-
{ getContext: () => null },
|
|
404
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
281
405
|
{ rhi },
|
|
282
406
|
{ shaderManifestUrl: manifestUrl() },
|
|
283
407
|
);
|
|
@@ -306,7 +430,7 @@ describe('factory contract', () => {
|
|
|
306
430
|
|
|
307
431
|
it('delivers a target readback only through its matching receipt', async () => {
|
|
308
432
|
const renderer = await constructRenderer(
|
|
309
|
-
{ getContext: () => null },
|
|
433
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
310
434
|
{ rhi },
|
|
311
435
|
{ shaderManifestUrl: manifestUrl() },
|
|
312
436
|
);
|
|
@@ -335,7 +459,7 @@ describe('factory contract', () => {
|
|
|
335
459
|
camera: { lease: attached.value },
|
|
336
460
|
environment: { lease: attached.value },
|
|
337
461
|
});
|
|
338
|
-
expect(frame.ok).toBe(true);
|
|
462
|
+
expect(frame.ok, frame.ok ? '' : JSON.stringify(frame.error)).toBe(true);
|
|
339
463
|
if (!frame.ok || frame.value === undefined) return;
|
|
340
464
|
const observed = await renderer.observe(frame.value, {
|
|
341
465
|
include: ['target-readbacks'],
|
|
@@ -384,11 +508,65 @@ describe('factory contract', () => {
|
|
|
384
508
|
camera: { lease: attached.value },
|
|
385
509
|
environment: { lease: attached.value },
|
|
386
510
|
});
|
|
387
|
-
expect(frame.ok).toBe(true);
|
|
511
|
+
expect(frame.ok, frame.ok ? '' : JSON.stringify(frame.error)).toBe(true);
|
|
388
512
|
expect(planCalls).toBe(1);
|
|
389
513
|
renderer.dispose();
|
|
390
514
|
});
|
|
391
515
|
|
|
516
|
+
it('projects the active barrel stage through the ordinary feature host once', async () => {
|
|
517
|
+
const renderer = await constructRenderer(
|
|
518
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
519
|
+
{ rhi },
|
|
520
|
+
{ shaderManifestUrl: renderLifecycleManifestUrl() },
|
|
521
|
+
);
|
|
522
|
+
expect((await renderer.initialization).ok).toBe(true);
|
|
523
|
+
const world = new World();
|
|
524
|
+
const attached = renderer.attach(world);
|
|
525
|
+
expect(attached.ok).toBe(true);
|
|
526
|
+
if (!attached.ok) return;
|
|
527
|
+
registerPropagateTransforms(world);
|
|
528
|
+
world
|
|
529
|
+
.spawn(
|
|
530
|
+
{ component: Transform, data: { pos: [0, 0, 4] } },
|
|
531
|
+
{
|
|
532
|
+
component: LocalCamera,
|
|
533
|
+
data: {
|
|
534
|
+
fov: 1,
|
|
535
|
+
aspect: 1,
|
|
536
|
+
near: 0.1,
|
|
537
|
+
far: 100,
|
|
538
|
+
antialias: ANTIALIAS_NONE,
|
|
539
|
+
tonemap: TONEMAP_ACES_FILMIC,
|
|
540
|
+
bloom: BLOOM_DISABLED,
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
{ component: BarrelDistortion, data: { strength: 0.2, centerX: 0.5, centerY: 0.5 } },
|
|
544
|
+
)
|
|
545
|
+
.unwrap();
|
|
546
|
+
expect(world.update().ok).toBe(true);
|
|
547
|
+
const frame = renderer.draw({
|
|
548
|
+
leases: [attached.value],
|
|
549
|
+
camera: { lease: attached.value },
|
|
550
|
+
environment: { lease: attached.value },
|
|
551
|
+
});
|
|
552
|
+
expect(frame.ok, frame.ok ? '' : JSON.stringify(frame.error)).toBe(true);
|
|
553
|
+
if (frame.ok && frame.value !== undefined) {
|
|
554
|
+
expect(frame.value.barrelDistortion).toMatchObject({
|
|
555
|
+
width: 64,
|
|
556
|
+
height: 64,
|
|
557
|
+
camera: { projection: 'perspective' },
|
|
558
|
+
});
|
|
559
|
+
expect(frame.value.barrelDistortion?.strength).toBeCloseTo(0.2, 6);
|
|
560
|
+
expect(
|
|
561
|
+
renderer.perFramePassNames.filter((name) => name === 'barrel-distortion'),
|
|
562
|
+
).toHaveLength(1);
|
|
563
|
+
expect(
|
|
564
|
+
renderer.perFramePassNames.filter((name) => name.startsWith('post-effect-')),
|
|
565
|
+
).toHaveLength(0);
|
|
566
|
+
}
|
|
567
|
+
await renderer.dispose();
|
|
568
|
+
});
|
|
569
|
+
|
|
392
570
|
it('keeps the active graph as LKG while a feature candidate is pending', async () => {
|
|
393
571
|
let invalid = false;
|
|
394
572
|
let featurePlanCalls = 0;
|
|
@@ -624,7 +802,7 @@ describe('factory contract', () => {
|
|
|
624
802
|
|
|
625
803
|
it('attaches derived-state systems once without putting writes in draw', async () => {
|
|
626
804
|
const renderer = await constructRenderer(
|
|
627
|
-
{ getContext: () => null },
|
|
805
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
628
806
|
{ rhi },
|
|
629
807
|
{ shaderManifestUrl: manifestUrl() },
|
|
630
808
|
);
|
|
@@ -640,12 +818,12 @@ describe('factory contract', () => {
|
|
|
640
818
|
|
|
641
819
|
it('releases one World without disposing the shared Renderer', async () => {
|
|
642
820
|
const first = await constructRenderer(
|
|
643
|
-
{ getContext: () => null },
|
|
821
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
644
822
|
{ rhi },
|
|
645
823
|
{ shaderManifestUrl: manifestUrl() },
|
|
646
824
|
);
|
|
647
825
|
const second = await constructRenderer(
|
|
648
|
-
{ getContext: () => null },
|
|
826
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
649
827
|
{ rhi },
|
|
650
828
|
{ shaderManifestUrl: manifestUrl() },
|
|
651
829
|
);
|
|
@@ -662,7 +840,11 @@ describe('factory contract', () => {
|
|
|
662
840
|
it('resolves a renderer for a host canvas and rejects missing input', async () => {
|
|
663
841
|
const manifest = `data:application/json,${encodeURIComponent(JSON.stringify({ schemaVersion: '1.0.0', entries: [] }))}`;
|
|
664
842
|
await expect(
|
|
665
|
-
constructRenderer(
|
|
843
|
+
constructRenderer(
|
|
844
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
845
|
+
{ rhi },
|
|
846
|
+
{ shaderManifestUrl: manifest },
|
|
847
|
+
),
|
|
666
848
|
).resolves.toMatchObject({
|
|
667
849
|
attach: expect.any(Function),
|
|
668
850
|
draw: expect.any(Function),
|
|
@@ -673,7 +855,7 @@ describe('factory contract', () => {
|
|
|
673
855
|
it('temporarily releases presentation while preserving the Renderer identity', async () => {
|
|
674
856
|
const manifest = `data:application/json,${encodeURIComponent(JSON.stringify({ schemaVersion: '1.0.0', entries: [] }))}`;
|
|
675
857
|
const renderer = await constructRenderer(
|
|
676
|
-
{ getContext: () => null },
|
|
858
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
677
859
|
{ rhi },
|
|
678
860
|
{ shaderManifestUrl: manifest },
|
|
679
861
|
);
|
|
@@ -689,7 +871,7 @@ describe('factory contract', () => {
|
|
|
689
871
|
it('does not unconfigure a surface after its ownership was released', async () => {
|
|
690
872
|
let unconfigureCalls = 0;
|
|
691
873
|
const renderer = await constructRenderer(
|
|
692
|
-
{ getContext: () => null },
|
|
874
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
693
875
|
{
|
|
694
876
|
rhi: {
|
|
695
877
|
...rhi,
|
|
@@ -718,19 +900,70 @@ describe('factory contract', () => {
|
|
|
718
900
|
expect(unconfigureCalls).toBe(1);
|
|
719
901
|
});
|
|
720
902
|
|
|
903
|
+
it('keeps direct RHI instrumentation explicit-only at the factory boundary', async () => {
|
|
904
|
+
const drawOneFrame = (renderer: Awaited<ReturnType<typeof constructRenderer>>) => {
|
|
905
|
+
const world = new World();
|
|
906
|
+
const attached = renderer.attach(world);
|
|
907
|
+
expect(attached.ok).toBe(true);
|
|
908
|
+
if (!attached.ok) return;
|
|
909
|
+
expect(world.update().ok).toBe(true);
|
|
910
|
+
const frame = renderer.draw({
|
|
911
|
+
leases: [attached.value],
|
|
912
|
+
camera: { lease: attached.value },
|
|
913
|
+
environment: { lease: attached.value },
|
|
914
|
+
});
|
|
915
|
+
expect(frame.ok, frame.ok ? '' : JSON.stringify(frame.error)).toBe(true);
|
|
916
|
+
};
|
|
917
|
+
const moduleBoundary = vi.fn();
|
|
918
|
+
const implicit = await constructRenderer(
|
|
919
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
920
|
+
{
|
|
921
|
+
rhi: {
|
|
922
|
+
...rhi,
|
|
923
|
+
instrumentation: { onFrameBoundary: moduleBoundary },
|
|
924
|
+
} as never,
|
|
925
|
+
},
|
|
926
|
+
{ shaderManifestUrl: manifestUrl() },
|
|
927
|
+
);
|
|
928
|
+
expect((await implicit.initialization).ok).toBe(true);
|
|
929
|
+
drawOneFrame(implicit);
|
|
930
|
+
expect(moduleBoundary).not.toHaveBeenCalled();
|
|
931
|
+
expect((await implicit.dispose()).ok).toBe(true);
|
|
932
|
+
|
|
933
|
+
const explicitBoundary = vi.fn();
|
|
934
|
+
const explicit = await constructRenderer(
|
|
935
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
936
|
+
{
|
|
937
|
+
rhi: {
|
|
938
|
+
...rhi,
|
|
939
|
+
instrumentation: { onFrameBoundary: moduleBoundary },
|
|
940
|
+
} as never,
|
|
941
|
+
rhiInstrumentation: { onFrameBoundary: explicitBoundary },
|
|
942
|
+
},
|
|
943
|
+
{ shaderManifestUrl: manifestUrl() },
|
|
944
|
+
);
|
|
945
|
+
expect((await explicit.initialization).ok).toBe(true);
|
|
946
|
+
drawOneFrame(explicit);
|
|
947
|
+
expect(explicitBoundary).toHaveBeenCalled();
|
|
948
|
+
expect(moduleBoundary).not.toHaveBeenCalled();
|
|
949
|
+
expect((await explicit.dispose()).ok).toBe(true);
|
|
950
|
+
});
|
|
951
|
+
|
|
721
952
|
it('retains the device-owned prewarmed TAA shader across off and on', async () => {
|
|
722
953
|
const pending: Array<{
|
|
723
954
|
readonly label: string;
|
|
724
955
|
readonly promise: Promise<ReturnType<typeof ok<ShaderModule>>>;
|
|
725
956
|
readonly resolve: (value: ReturnType<typeof ok<ShaderModule>>) => void;
|
|
726
957
|
}> = [];
|
|
727
|
-
const
|
|
958
|
+
const moduleSources: string[] = [];
|
|
959
|
+
const frameBoundary = vi.fn();
|
|
728
960
|
const backend: RhiBackendPack = {
|
|
729
961
|
rhi,
|
|
962
|
+
instrumentation: { onFrameBoundary: frameBoundary },
|
|
730
963
|
createShaderModule: (_device, desc) => {
|
|
731
964
|
const label = desc.label ?? '<unlabeled>';
|
|
732
|
-
|
|
733
|
-
if (!
|
|
965
|
+
moduleSources.push(desc.code);
|
|
966
|
+
if (!desc.code.includes('fs_taa_resolve')) {
|
|
734
967
|
return Promise.resolve(ok({} as ShaderModule));
|
|
735
968
|
}
|
|
736
969
|
let resolve!: (value: ReturnType<typeof ok<ShaderModule>>) => void;
|
|
@@ -781,7 +1014,8 @@ describe('factory contract', () => {
|
|
|
781
1014
|
});
|
|
782
1015
|
|
|
783
1016
|
const first = renderer.draw(frameInput());
|
|
784
|
-
expect(first.ok).toBe(true);
|
|
1017
|
+
expect(first.ok, first.ok ? '' : JSON.stringify(first.error)).toBe(true);
|
|
1018
|
+
expect(frameBoundary).toHaveBeenCalled();
|
|
785
1019
|
expect(pending).toHaveLength(1);
|
|
786
1020
|
|
|
787
1021
|
world.set(camera, LocalCamera, { antialias: ANTIALIAS_NONE }).unwrap();
|
|
@@ -789,14 +1023,12 @@ describe('factory contract', () => {
|
|
|
789
1023
|
const off = renderer.draw(frameInput());
|
|
790
1024
|
expect(off.ok).toBe(true);
|
|
791
1025
|
|
|
792
|
-
expect(
|
|
793
|
-
moduleCalls.filter((label) => label.startsWith('post-process-forgeax.taa-resolve-pso-')),
|
|
794
|
-
).toHaveLength(1);
|
|
1026
|
+
expect(moduleSources.filter((source) => source.includes('fs_taa_resolve'))).toHaveLength(1);
|
|
795
1027
|
|
|
796
1028
|
world.set(camera, LocalCamera, { antialias: ANTIALIAS_TAA }).unwrap();
|
|
797
1029
|
expect(world.update().ok).toBe(true);
|
|
798
1030
|
const second = renderer.draw(frameInput());
|
|
799
|
-
expect(second.ok).toBe(true);
|
|
1031
|
+
expect(second.ok, second.ok ? '' : JSON.stringify(second.error)).toBe(true);
|
|
800
1032
|
expect(pending).toHaveLength(1);
|
|
801
1033
|
expect(renderer.draw(frameInput()).ok).toBe(true);
|
|
802
1034
|
world.set(camera, LocalCamera, { antialias: ANTIALIAS_NONE }).unwrap();
|
|
@@ -810,13 +1042,11 @@ describe('factory contract', () => {
|
|
|
810
1042
|
|
|
811
1043
|
expect((await renderer.dispose()).ok).toBe(true);
|
|
812
1044
|
expect((await renderer.dispose()).ok).toBe(true);
|
|
813
|
-
expect(
|
|
814
|
-
moduleCalls.filter((label) => label.startsWith('post-process-forgeax.taa-resolve-pso-')),
|
|
815
|
-
).toHaveLength(1);
|
|
1045
|
+
expect(moduleSources.filter((source) => source.includes('fs_taa_resolve'))).toHaveLength(1);
|
|
816
1046
|
});
|
|
817
1047
|
|
|
818
1048
|
it('rebuilds a TAA graph for a same-mode target resize', async () => {
|
|
819
|
-
const
|
|
1049
|
+
const moduleSources: string[] = [];
|
|
820
1050
|
const created = new Map<object, string>();
|
|
821
1051
|
const destroyed = new Set<object>();
|
|
822
1052
|
let wrappedDevice: object | undefined;
|
|
@@ -859,7 +1089,7 @@ describe('factory contract', () => {
|
|
|
859
1089
|
const backend: RhiBackendPack = {
|
|
860
1090
|
rhi: instrumentedRhi,
|
|
861
1091
|
createShaderModule: async (_device, desc) => {
|
|
862
|
-
|
|
1092
|
+
moduleSources.push(desc.code);
|
|
863
1093
|
return ok({} as ShaderModule);
|
|
864
1094
|
},
|
|
865
1095
|
};
|
|
@@ -898,14 +1128,12 @@ describe('factory contract', () => {
|
|
|
898
1128
|
await Promise.resolve();
|
|
899
1129
|
first = renderer.draw(frameInput());
|
|
900
1130
|
}
|
|
901
|
-
expect(first.ok).toBe(true);
|
|
1131
|
+
expect(first.ok, first.ok ? '' : JSON.stringify(first.error)).toBe(true);
|
|
902
1132
|
const firstGeneration = [...created.entries()]
|
|
903
1133
|
.filter(([_, label]) => label.startsWith('taa-history-'))
|
|
904
1134
|
.slice(-4);
|
|
905
1135
|
expect(firstGeneration).toHaveLength(4);
|
|
906
|
-
expect(
|
|
907
|
-
moduleCalls.filter((label) => label.startsWith('post-process-forgeax.taa-resolve-pso-')),
|
|
908
|
-
).toHaveLength(1);
|
|
1136
|
+
expect(moduleSources.filter((source) => source.includes('fs_taa_resolve'))).toHaveLength(1);
|
|
909
1137
|
const firstFrame = renderer.inspect().frame.frameId;
|
|
910
1138
|
|
|
911
1139
|
canvas.width = 96;
|
|
@@ -927,9 +1155,7 @@ describe('factory contract', () => {
|
|
|
927
1155
|
expect(replacementGeneration.every(([handle]) => !destroyed.has(handle))).toBe(true);
|
|
928
1156
|
expect(renderer.inspect().frame.frameId).toBe(firstFrame + 1);
|
|
929
1157
|
expect(renderer.inspect().state).toBe('alive');
|
|
930
|
-
expect(
|
|
931
|
-
moduleCalls.filter((label) => label.startsWith('post-process-forgeax.taa-resolve-pso-')),
|
|
932
|
-
).toHaveLength(1);
|
|
1158
|
+
expect(moduleSources.filter((source) => source.includes('fs_taa_resolve'))).toHaveLength(1);
|
|
933
1159
|
world.set(camera, LocalCamera, { antialias: ANTIALIAS_NONE }).unwrap();
|
|
934
1160
|
expect(world.update().ok).toBe(true);
|
|
935
1161
|
expect(renderer.draw(frameInput()).ok).toBe(true);
|
|
@@ -1086,12 +1312,189 @@ describe('factory contract', () => {
|
|
|
1086
1312
|
}
|
|
1087
1313
|
const inspection = renderer.inspect();
|
|
1088
1314
|
expect(inspection.perFramePassNames).toContain('motion-blur');
|
|
1315
|
+
// The same Rhi device/frame admitted the compute plan, graph, and both
|
|
1316
|
+
// production dispatches. This is the application-side admission proof;
|
|
1317
|
+
// Dawn/physical timing remains a separate backend gate.
|
|
1318
|
+
expect(inspection.capabilities).toMatchObject({
|
|
1319
|
+
compute: true,
|
|
1320
|
+
storageBuffer: true,
|
|
1321
|
+
storageTexture: true,
|
|
1322
|
+
rgba16floatRenderable: true,
|
|
1323
|
+
});
|
|
1324
|
+
expect(inspection.motionBlur).toMatchObject({ lane: 'compute', passCount: 2 });
|
|
1325
|
+
expect(inspection.perFramePassNames).toContain('motion-blur');
|
|
1326
|
+
expect(inspection.recoveryEvidence.graph).toMatchObject({ ready: true });
|
|
1327
|
+
expect(inspection.featureGraph?.compileSuccesses ?? 0).toBeGreaterThan(0);
|
|
1328
|
+
// The graph is present, but this is the first accepted temporal pair
|
|
1329
|
+
// after the effect was added. Inspection must expose the reset boundary
|
|
1330
|
+
// until a subsequent successful pair can provide a valid baseline.
|
|
1089
1331
|
expect(inspection.motionBlur).toMatchObject({
|
|
1090
1332
|
enabled: true,
|
|
1091
|
-
status: '
|
|
1333
|
+
status: 'reset',
|
|
1334
|
+
temporalDemand: 'scene-data-temporal-v1',
|
|
1335
|
+
});
|
|
1336
|
+
await renderer.dispose();
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
it('projects generic post work once and keeps Motion Blur independent across Standard lanes', async () => {
|
|
1340
|
+
for (const renderPath of ['forward', 'deferred'] as const) {
|
|
1341
|
+
for (const taa of [false, true]) {
|
|
1342
|
+
const renderer = await constructRenderer(
|
|
1343
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
1344
|
+
{ rhi, features: [createGenericPostProbeFeature()] },
|
|
1345
|
+
{ shaderManifestUrl: renderLifecycleManifestUrl() },
|
|
1346
|
+
);
|
|
1347
|
+
try {
|
|
1348
|
+
expect((await renderer.initialization).ok).toBe(true);
|
|
1349
|
+
expect(renderer.setProfile({ ...DEFAULT_STANDARD_PROFILE, renderPath }).ok).toBe(true);
|
|
1350
|
+
const world = new World();
|
|
1351
|
+
const attached = renderer.attach(world);
|
|
1352
|
+
expect(attached.ok).toBe(true);
|
|
1353
|
+
if (!attached.ok) continue;
|
|
1354
|
+
registerPropagateTransforms(world);
|
|
1355
|
+
const camera = world
|
|
1356
|
+
.spawn(
|
|
1357
|
+
{ component: Transform, data: { pos: [0, 0, 4] } },
|
|
1358
|
+
{
|
|
1359
|
+
component: LocalCamera,
|
|
1360
|
+
data: {
|
|
1361
|
+
fov: 1,
|
|
1362
|
+
aspect: 1,
|
|
1363
|
+
near: 0.1,
|
|
1364
|
+
far: 100,
|
|
1365
|
+
antialias: taa ? ANTIALIAS_TAA : ANTIALIAS_NONE,
|
|
1366
|
+
tonemap: TONEMAP_ACES_FILMIC,
|
|
1367
|
+
},
|
|
1368
|
+
},
|
|
1369
|
+
)
|
|
1370
|
+
.unwrap();
|
|
1371
|
+
world
|
|
1372
|
+
.addComponent(camera, {
|
|
1373
|
+
component: MotionBlur,
|
|
1374
|
+
data: { shutterAngle: 180, maxRadiusPixels: 8, sampleCount: 4, targetFps: 60 },
|
|
1375
|
+
})
|
|
1376
|
+
.unwrap();
|
|
1377
|
+
expect(world.update().ok).toBe(true);
|
|
1378
|
+
const draw = renderer.draw({
|
|
1379
|
+
leases: [attached.value],
|
|
1380
|
+
camera: { lease: attached.value },
|
|
1381
|
+
environment: { lease: attached.value },
|
|
1382
|
+
});
|
|
1383
|
+
expect(draw.ok).toBe(true);
|
|
1384
|
+
if (!draw.ok || draw.value === undefined) continue;
|
|
1385
|
+
const completed = await draw.value.completed;
|
|
1386
|
+
expect(completed.ok).toBe(true);
|
|
1387
|
+
if (!completed.ok) continue;
|
|
1388
|
+
const inspection = renderer.inspect();
|
|
1389
|
+
const names = inspection.perFramePassNames;
|
|
1390
|
+
const genericIndex = names.indexOf('generic-post-probe');
|
|
1391
|
+
const motionSummaryIndex = names.indexOf('motion-blur-tile-summary');
|
|
1392
|
+
const motionIndex = names.indexOf('motion-blur');
|
|
1393
|
+
const outputIndex = names.indexOf('output-transform');
|
|
1394
|
+
expect(names.filter((name) => name === 'generic-post-probe')).toHaveLength(1);
|
|
1395
|
+
expect(names.filter((name) => name === 'motion-blur-tile-summary')).toHaveLength(1);
|
|
1396
|
+
expect(names.filter((name) => name === 'motion-blur')).toHaveLength(1);
|
|
1397
|
+
expect(genericIndex).toBeGreaterThanOrEqual(0);
|
|
1398
|
+
expect(motionSummaryIndex).toBeGreaterThanOrEqual(0);
|
|
1399
|
+
expect(motionIndex).toBeGreaterThan(motionSummaryIndex);
|
|
1400
|
+
expect(outputIndex).toBeGreaterThan(motionIndex);
|
|
1401
|
+
if (taa) expect(names.indexOf('taa-resolve')).toBeLessThan(motionSummaryIndex);
|
|
1402
|
+
else expect(names).not.toContain('taa-resolve');
|
|
1403
|
+
expect(inspection.motionBlur).toMatchObject({
|
|
1404
|
+
enabled: true,
|
|
1405
|
+
passCount: 2,
|
|
1406
|
+
execution: { passes: [{ name: 'motion-blur-tile-summary' }, { name: 'motion-blur' }] },
|
|
1407
|
+
});
|
|
1408
|
+
// A completed two-pass execution is the public Renderer proof that
|
|
1409
|
+
// the named motion-blurred-color target had a writer and was then
|
|
1410
|
+
// consumed by the shared output chain.
|
|
1411
|
+
expect(inspection.featureGraph?.compileSuccesses ?? 0).toBeGreaterThan(0);
|
|
1412
|
+
} finally {
|
|
1413
|
+
await renderer.dispose();
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
it('publishes invalid MotionBlur parameters through renderer inspection', async () => {
|
|
1420
|
+
const renderer = await constructRenderer(
|
|
1421
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
1422
|
+
{ rhi },
|
|
1423
|
+
{ shaderManifestUrl: renderLifecycleManifestUrl() },
|
|
1424
|
+
);
|
|
1425
|
+
expect((await renderer.initialization).ok).toBe(true);
|
|
1426
|
+
const errors: unknown[] = [];
|
|
1427
|
+
renderer.onError((error) => errors.push(error));
|
|
1428
|
+
const world = new World();
|
|
1429
|
+
const attached = renderer.attach(world);
|
|
1430
|
+
expect(attached.ok).toBe(true);
|
|
1431
|
+
if (!attached.ok) return;
|
|
1432
|
+
registerPropagateTransforms(world);
|
|
1433
|
+
const camera = world
|
|
1434
|
+
.spawn(
|
|
1435
|
+
{ component: Transform, data: { pos: [0, 0, 4] } },
|
|
1436
|
+
{
|
|
1437
|
+
component: LocalCamera,
|
|
1438
|
+
data: { fov: 1, aspect: 1, near: 0.1, far: 100, antialias: ANTIALIAS_TAA },
|
|
1439
|
+
},
|
|
1440
|
+
{ component: MotionBlur, data: { targetFps: 59.5 } },
|
|
1441
|
+
)
|
|
1442
|
+
.unwrap();
|
|
1443
|
+
const frameInput = () => ({
|
|
1444
|
+
leases: [attached.value],
|
|
1445
|
+
camera: { lease: attached.value },
|
|
1446
|
+
environment: { lease: attached.value },
|
|
1447
|
+
});
|
|
1448
|
+
expect(world.update().ok).toBe(true);
|
|
1449
|
+
|
|
1450
|
+
const draw = renderer.draw(frameInput());
|
|
1451
|
+
expect(draw.ok).toBe(false);
|
|
1452
|
+
if (!draw.ok) {
|
|
1453
|
+
expect(draw.error).toMatchObject({
|
|
1454
|
+
code: 'motion-blur-invalid-params',
|
|
1455
|
+
detail: { field: 'targetFps', value: 59.5, integer: true },
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
expect(errors).toEqual(
|
|
1459
|
+
expect.arrayContaining([expect.objectContaining({ code: 'motion-blur-invalid-params' })]),
|
|
1460
|
+
);
|
|
1461
|
+
expect(renderer.inspect().motionBlur).toMatchObject({
|
|
1462
|
+
enabled: true,
|
|
1463
|
+
status: 'invalid',
|
|
1464
|
+
lastFailure: 'invalid-params',
|
|
1092
1465
|
temporalDemand: 'scene-data-temporal-v1',
|
|
1093
1466
|
});
|
|
1094
1467
|
await renderer.dispose();
|
|
1468
|
+
void camera;
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
it('projects a rejected Motion Blur submit while retaining its accepted baseline', async () => {
|
|
1472
|
+
const scenario = await createMotionBlurScenario();
|
|
1473
|
+
const submit = vi.spyOn(RhiNullQueue.prototype, 'submit');
|
|
1474
|
+
const failure = new RhiError({
|
|
1475
|
+
code: 'webgpu-runtime-error',
|
|
1476
|
+
expected: 'Motion Blur queue submission succeeds',
|
|
1477
|
+
hint: 'repair the queue and retry the same frame',
|
|
1478
|
+
});
|
|
1479
|
+
try {
|
|
1480
|
+
expect(scenario.draw().ok).toBe(true);
|
|
1481
|
+
expect(scenario.draw().ok).toBe(true);
|
|
1482
|
+
const accepted = scenario.renderer.inspect().motionBlur;
|
|
1483
|
+
expect(accepted).toMatchObject({ enabled: true });
|
|
1484
|
+
expect(accepted).not.toHaveProperty('lastFailure');
|
|
1485
|
+
submit.mockReturnValueOnce(err(failure));
|
|
1486
|
+
expect(scenario.draw().ok).toBe(false);
|
|
1487
|
+
expect(scenario.renderer.inspect().motionBlur).toMatchObject({
|
|
1488
|
+
enabled: true,
|
|
1489
|
+
lastFailure: 'submit-failure',
|
|
1490
|
+
});
|
|
1491
|
+
submit.mockRestore();
|
|
1492
|
+
expect(scenario.draw().ok).toBe(true);
|
|
1493
|
+
expect(scenario.renderer.inspect().motionBlur?.lastFailure).toBeUndefined();
|
|
1494
|
+
} finally {
|
|
1495
|
+
submit.mockRestore();
|
|
1496
|
+
await scenario.dispose();
|
|
1497
|
+
}
|
|
1095
1498
|
});
|
|
1096
1499
|
|
|
1097
1500
|
it('retires factory-owned TAA textures and params on an off submit', async () => {
|
|
@@ -1168,7 +1571,7 @@ describe('factory contract', () => {
|
|
|
1168
1571
|
await Promise.resolve();
|
|
1169
1572
|
first = renderer.draw(frameInput());
|
|
1170
1573
|
}
|
|
1171
|
-
expect(first.ok).toBe(true);
|
|
1574
|
+
expect(first.ok, first.ok ? '' : JSON.stringify(first.error)).toBe(true);
|
|
1172
1575
|
const historyHandles = [...created.entries()].filter(([_, label]) =>
|
|
1173
1576
|
label.startsWith('taa-history-'),
|
|
1174
1577
|
);
|
|
@@ -1230,12 +1633,12 @@ describe('factory contract', () => {
|
|
|
1230
1633
|
const onInspection = renderer.inspect().bloom;
|
|
1231
1634
|
expect(onInspection.state).toBe('active');
|
|
1232
1635
|
expect(onInspection.graphStatus).toBe('valid');
|
|
1233
|
-
expect(onInspection.targetCount).toBe(
|
|
1234
|
-
expect(onInspection.targetBytes).toBe(
|
|
1235
|
-
expect(onInspection.passCount).toBe(
|
|
1236
|
-
expect(onInspection.uploadCount).toBe(
|
|
1237
|
-
expect(onInspection.bindGroupCount).toBe(
|
|
1238
|
-
expect(onInspection.encodeCount).toBe(
|
|
1636
|
+
expect(onInspection.targetCount).toBe(10);
|
|
1637
|
+
expect(onInspection.targetBytes).toBe(54_560);
|
|
1638
|
+
expect(onInspection.passCount).toBe(10);
|
|
1639
|
+
expect(onInspection.uploadCount).toBe(10);
|
|
1640
|
+
expect(onInspection.bindGroupCount).toBe(10);
|
|
1641
|
+
expect(onInspection.encodeCount).toBe(10);
|
|
1239
1642
|
expect(JSON.parse(JSON.stringify(onInspection))).toEqual(onInspection);
|
|
1240
1643
|
const firstGeneration = onInspection.generation;
|
|
1241
1644
|
expect(firstGeneration).toBeGreaterThan(0);
|
|
@@ -1266,10 +1669,10 @@ describe('factory contract', () => {
|
|
|
1266
1669
|
const recoveredInspection = renderer.inspect().bloom;
|
|
1267
1670
|
expect(recoveredInspection.state).toBe('active');
|
|
1268
1671
|
expect(recoveredInspection.resourceCount).toBeGreaterThan(0);
|
|
1269
|
-
expect(recoveredInspection.passCount).toBe(
|
|
1270
|
-
expect(recoveredInspection.uploadCount).toBe(
|
|
1271
|
-
expect(recoveredInspection.bindGroupCount).toBe(
|
|
1272
|
-
expect(recoveredInspection.encodeCount).toBe(
|
|
1672
|
+
expect(recoveredInspection.passCount).toBe(10);
|
|
1673
|
+
expect(recoveredInspection.uploadCount).toBe(10);
|
|
1674
|
+
expect(recoveredInspection.bindGroupCount).toBe(10);
|
|
1675
|
+
expect(recoveredInspection.encodeCount).toBe(10);
|
|
1273
1676
|
expect(recoveredInspection.generation).toBeGreaterThan(firstGeneration);
|
|
1274
1677
|
await Promise.resolve();
|
|
1275
1678
|
await Promise.resolve();
|
|
@@ -1290,25 +1693,25 @@ describe('factory contract', () => {
|
|
|
1290
1693
|
try {
|
|
1291
1694
|
expect(scenario.draw().ok).toBe(true);
|
|
1292
1695
|
expect(scenario.renderer.inspect().bloom).toMatchObject({
|
|
1293
|
-
uploadCount:
|
|
1294
|
-
bindGroupCount:
|
|
1295
|
-
encodeCount:
|
|
1696
|
+
uploadCount: 10,
|
|
1697
|
+
bindGroupCount: 10,
|
|
1698
|
+
encodeCount: 10,
|
|
1296
1699
|
});
|
|
1297
1700
|
|
|
1298
1701
|
submit.mockReturnValueOnce(err(failure));
|
|
1299
1702
|
expect(scenario.draw().ok).toBe(false);
|
|
1300
1703
|
expect(scenario.renderer.inspect().bloom).toMatchObject({
|
|
1301
1704
|
state: 'active',
|
|
1302
|
-
uploadCount:
|
|
1303
|
-
bindGroupCount:
|
|
1304
|
-
encodeCount:
|
|
1705
|
+
uploadCount: 10,
|
|
1706
|
+
bindGroupCount: 10,
|
|
1707
|
+
encodeCount: 10,
|
|
1305
1708
|
});
|
|
1306
1709
|
|
|
1307
1710
|
expect(scenario.draw().ok).toBe(true);
|
|
1308
1711
|
expect(scenario.renderer.inspect().bloom).toMatchObject({
|
|
1309
|
-
uploadCount:
|
|
1310
|
-
bindGroupCount:
|
|
1311
|
-
encodeCount:
|
|
1712
|
+
uploadCount: 10,
|
|
1713
|
+
bindGroupCount: 10,
|
|
1714
|
+
encodeCount: 10,
|
|
1312
1715
|
});
|
|
1313
1716
|
} finally {
|
|
1314
1717
|
submit.mockRestore();
|
|
@@ -1316,6 +1719,26 @@ describe('factory contract', () => {
|
|
|
1316
1719
|
}
|
|
1317
1720
|
});
|
|
1318
1721
|
|
|
1722
|
+
it('uploads positive Bloom parameter changes without rebuilding the accepted bundle', async () => {
|
|
1723
|
+
const scenario = await createBloomScenario();
|
|
1724
|
+
try {
|
|
1725
|
+
expect(scenario.draw().ok).toBe(true);
|
|
1726
|
+
const first = scenario.renderer.inspect().bloom;
|
|
1727
|
+
scenario.setBloomIntensity(0.35);
|
|
1728
|
+
expect(scenario.draw().ok).toBe(true);
|
|
1729
|
+
const updated = scenario.renderer.inspect().bloom;
|
|
1730
|
+
expect(updated.state).toBe('active');
|
|
1731
|
+
expect(updated.generation).toBe(first.generation);
|
|
1732
|
+
expect(updated.resourceCount).toBe(first.resourceCount);
|
|
1733
|
+
expect(updated.targetCount).toBe(first.targetCount);
|
|
1734
|
+
expect(updated.targetBytes).toBe(first.targetBytes);
|
|
1735
|
+
expect(updated.uploadCount).toBe(10);
|
|
1736
|
+
expect(updated.passCount).toBe(10);
|
|
1737
|
+
} finally {
|
|
1738
|
+
await scenario.dispose();
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1319
1742
|
it('aborts a Bloom pass write failure without publishing partial receipts', async () => {
|
|
1320
1743
|
const originalWriteBuffer = RhiNullQueue.prototype.writeBuffer;
|
|
1321
1744
|
const createBuffer = vi.spyOn(RhiNullDevice.prototype, 'createBuffer');
|
|
@@ -1324,30 +1747,30 @@ describe('factory contract', () => {
|
|
|
1324
1747
|
const scenario = await createBloomScenario();
|
|
1325
1748
|
const failure = new RhiError({
|
|
1326
1749
|
code: 'webgpu-runtime-error',
|
|
1327
|
-
expected: 'Bloom
|
|
1750
|
+
expected: 'Bloom downsample params upload succeeds',
|
|
1328
1751
|
hint: 'repair the Bloom params buffer and retry the frame',
|
|
1329
1752
|
});
|
|
1330
1753
|
const errors: unknown[] = [];
|
|
1331
1754
|
const unsubscribe = scenario.renderer.onError((error) => errors.push(error));
|
|
1332
1755
|
try {
|
|
1333
1756
|
expect(scenario.draw().ok).toBe(true);
|
|
1334
|
-
const
|
|
1757
|
+
const downsampleParams = createBuffer.mock.calls.reduce<object | undefined>(
|
|
1335
1758
|
(found, args, index) => {
|
|
1336
|
-
if (found !== undefined || args[0].label !== 'bloom-
|
|
1759
|
+
if (found !== undefined || args[0].label !== 'bloom-downsample-params-ubo') return found;
|
|
1337
1760
|
const result = createBuffer.mock.results[index];
|
|
1338
1761
|
if (result?.type !== 'return' || !result.value.ok) return undefined;
|
|
1339
1762
|
return result.value.value as object;
|
|
1340
1763
|
},
|
|
1341
1764
|
undefined,
|
|
1342
1765
|
);
|
|
1343
|
-
expect(
|
|
1766
|
+
expect(downsampleParams).toBeDefined();
|
|
1344
1767
|
const lkg = scenario.renderer.inspect().bloom;
|
|
1345
|
-
expect(lkg).toMatchObject({ uploadCount:
|
|
1768
|
+
expect(lkg).toMatchObject({ uploadCount: 10, bindGroupCount: 10, encodeCount: 10 });
|
|
1346
1769
|
const submitCount = submit.mock.calls.length;
|
|
1347
1770
|
|
|
1348
1771
|
let injected = false;
|
|
1349
1772
|
writeBuffer.mockImplementation((buffer, ...args) => {
|
|
1350
|
-
if (buffer ===
|
|
1773
|
+
if (buffer === downsampleParams && !injected) {
|
|
1351
1774
|
injected = true;
|
|
1352
1775
|
return err(failure);
|
|
1353
1776
|
}
|
|
@@ -1359,23 +1782,23 @@ describe('factory contract', () => {
|
|
|
1359
1782
|
expect(errors).toHaveLength(1);
|
|
1360
1783
|
expect(errors[0]).toMatchObject({
|
|
1361
1784
|
code: 'pass-encode-failed',
|
|
1362
|
-
detail: { passName: 'bloom-
|
|
1785
|
+
detail: { passName: 'bloom-downsample-0', cause: failure },
|
|
1363
1786
|
});
|
|
1364
1787
|
expect(scenario.renderer.inspect().bloom).toMatchObject({
|
|
1365
1788
|
state: 'active',
|
|
1366
1789
|
generation: lkg.generation,
|
|
1367
|
-
uploadCount:
|
|
1368
|
-
bindGroupCount:
|
|
1369
|
-
encodeCount:
|
|
1790
|
+
uploadCount: 10,
|
|
1791
|
+
bindGroupCount: 10,
|
|
1792
|
+
encodeCount: 10,
|
|
1370
1793
|
});
|
|
1371
1794
|
|
|
1372
1795
|
writeBuffer.mockRestore();
|
|
1373
1796
|
expect(scenario.draw().ok).toBe(true);
|
|
1374
1797
|
expect(submit).toHaveBeenCalledTimes(submitCount + 1);
|
|
1375
1798
|
expect(scenario.renderer.inspect().bloom).toMatchObject({
|
|
1376
|
-
uploadCount:
|
|
1377
|
-
bindGroupCount:
|
|
1378
|
-
encodeCount:
|
|
1799
|
+
uploadCount: 10,
|
|
1800
|
+
bindGroupCount: 10,
|
|
1801
|
+
encodeCount: 10,
|
|
1379
1802
|
});
|
|
1380
1803
|
} finally {
|
|
1381
1804
|
writeBuffer.mockRestore();
|
|
@@ -1388,18 +1811,17 @@ describe('factory contract', () => {
|
|
|
1388
1811
|
|
|
1389
1812
|
const bloomConstructionFailures = [
|
|
1390
1813
|
['createSampler', 'bloom-sampler'],
|
|
1391
|
-
['createBindGroupLayout', 'bloom-
|
|
1392
|
-
['createBindGroupLayout', 'bloom-
|
|
1814
|
+
['createBindGroupLayout', 'bloom-downsample-bgl'],
|
|
1815
|
+
['createBindGroupLayout', 'bloom-upsample-bgl'],
|
|
1393
1816
|
['createBindGroupLayout', 'bloom-composite-bgl'],
|
|
1394
|
-
['createPipelineLayout', 'bloom-
|
|
1395
|
-
['createPipelineLayout', 'bloom-
|
|
1817
|
+
['createPipelineLayout', 'bloom-downsample-pl'],
|
|
1818
|
+
['createPipelineLayout', 'bloom-upsample-pl'],
|
|
1396
1819
|
['createPipelineLayout', 'bloom-composite-pl'],
|
|
1397
|
-
['createRenderPipeline', 'bloom-
|
|
1398
|
-
['createRenderPipeline', 'bloom-
|
|
1820
|
+
['createRenderPipeline', 'bloom-downsample-pipeline'],
|
|
1821
|
+
['createRenderPipeline', 'bloom-upsample-pipeline'],
|
|
1399
1822
|
['createRenderPipeline', 'bloom-composite-pipeline'],
|
|
1400
|
-
['createBuffer', 'bloom-
|
|
1401
|
-
['createBuffer', 'bloom-
|
|
1402
|
-
['createBuffer', 'bloom-blur-v-params-ubo'],
|
|
1823
|
+
['createBuffer', 'bloom-downsample-params-ubo'],
|
|
1824
|
+
['createBuffer', 'bloom-upsample-params-ubo'],
|
|
1403
1825
|
['createBuffer', 'bloom-composite-params-ubo'],
|
|
1404
1826
|
] as const;
|
|
1405
1827
|
|
|
@@ -1464,11 +1886,11 @@ describe('factory contract', () => {
|
|
|
1464
1886
|
expect(scenario.renderer.inspect().bloom).toMatchObject({
|
|
1465
1887
|
state: 'active',
|
|
1466
1888
|
graphStatus: 'valid',
|
|
1467
|
-
targetCount:
|
|
1468
|
-
passCount:
|
|
1469
|
-
uploadCount:
|
|
1470
|
-
bindGroupCount:
|
|
1471
|
-
encodeCount:
|
|
1889
|
+
targetCount: 10,
|
|
1890
|
+
passCount: 10,
|
|
1891
|
+
uploadCount: 10,
|
|
1892
|
+
bindGroupCount: 10,
|
|
1893
|
+
encodeCount: 10,
|
|
1472
1894
|
});
|
|
1473
1895
|
} finally {
|
|
1474
1896
|
operation.mockRestore();
|
|
@@ -1485,7 +1907,7 @@ describe('factory contract', () => {
|
|
|
1485
1907
|
const submit = vi.spyOn(RhiNullQueue.prototype, 'submit');
|
|
1486
1908
|
const failure = new RhiError({
|
|
1487
1909
|
code: 'webgpu-runtime-error',
|
|
1488
|
-
expected: 'Bloom
|
|
1910
|
+
expected: 'Bloom upsample bind-group creation succeeds',
|
|
1489
1911
|
hint: 'repair the Bloom bind group and retry the frame',
|
|
1490
1912
|
});
|
|
1491
1913
|
let injected = false;
|
|
@@ -1496,7 +1918,7 @@ describe('factory contract', () => {
|
|
|
1496
1918
|
scenario.canvas.width = 96;
|
|
1497
1919
|
scenario.canvas.height = 64;
|
|
1498
1920
|
createBindGroup.mockImplementation(function (this: RhiNullDevice, descriptor) {
|
|
1499
|
-
if (!injected && descriptor.label === 'bloom-
|
|
1921
|
+
if (!injected && descriptor.label === 'bloom-upsample-3-bg') {
|
|
1500
1922
|
injected = true;
|
|
1501
1923
|
return err(failure);
|
|
1502
1924
|
}
|
|
@@ -1508,9 +1930,9 @@ describe('factory contract', () => {
|
|
|
1508
1930
|
expect(scenario.renderer.inspect().bloom).toMatchObject({
|
|
1509
1931
|
state: 'active',
|
|
1510
1932
|
generation: lkg.generation,
|
|
1511
|
-
uploadCount:
|
|
1512
|
-
bindGroupCount:
|
|
1513
|
-
encodeCount:
|
|
1933
|
+
uploadCount: 10,
|
|
1934
|
+
bindGroupCount: 10,
|
|
1935
|
+
encodeCount: 10,
|
|
1514
1936
|
});
|
|
1515
1937
|
|
|
1516
1938
|
createBindGroup.mockRestore();
|
|
@@ -1518,9 +1940,9 @@ describe('factory contract', () => {
|
|
|
1518
1940
|
expect(submit).toHaveBeenCalledTimes(submitCount + 1);
|
|
1519
1941
|
expect(scenario.renderer.inspect().bloom).toMatchObject({
|
|
1520
1942
|
state: 'active',
|
|
1521
|
-
uploadCount:
|
|
1522
|
-
bindGroupCount:
|
|
1523
|
-
encodeCount:
|
|
1943
|
+
uploadCount: 10,
|
|
1944
|
+
bindGroupCount: 10,
|
|
1945
|
+
encodeCount: 10,
|
|
1524
1946
|
});
|
|
1525
1947
|
} finally {
|
|
1526
1948
|
createBindGroup.mockRestore();
|
|
@@ -1530,11 +1952,11 @@ describe('factory contract', () => {
|
|
|
1530
1952
|
});
|
|
1531
1953
|
|
|
1532
1954
|
it('aggregates active and fence-retiring Bloom bundles through Renderer.inspect', async () => {
|
|
1955
|
+
const scenario = await createBloomScenario();
|
|
1533
1956
|
const fence = deferred<undefined>();
|
|
1534
1957
|
const onSubmittedWorkDone = vi
|
|
1535
1958
|
.spyOn(RhiNullQueue.prototype, 'onSubmittedWorkDone')
|
|
1536
1959
|
.mockReturnValue(fence.promise);
|
|
1537
|
-
const scenario = await createBloomScenario();
|
|
1538
1960
|
try {
|
|
1539
1961
|
expect(scenario.draw().ok).toBe(true);
|
|
1540
1962
|
scenario.setBloom('off');
|
|
@@ -1557,10 +1979,10 @@ describe('factory contract', () => {
|
|
|
1557
1979
|
expect(overlap.state).toBe('active');
|
|
1558
1980
|
expect(overlap.graphStatus).toBe('valid');
|
|
1559
1981
|
expect(overlap.enabled).toBe(true);
|
|
1560
|
-
expect(overlap.passCount).toBe(
|
|
1561
|
-
expect(overlap.uploadCount).toBe(
|
|
1562
|
-
expect(overlap.bindGroupCount).toBe(
|
|
1563
|
-
expect(overlap.encodeCount).toBe(
|
|
1982
|
+
expect(overlap.passCount).toBe(10);
|
|
1983
|
+
expect(overlap.uploadCount).toBe(10);
|
|
1984
|
+
expect(overlap.bindGroupCount).toBe(10);
|
|
1985
|
+
expect(overlap.encodeCount).toBe(10);
|
|
1564
1986
|
expect(overlap.resourceCount).toBeGreaterThan(retiring.resourceCount);
|
|
1565
1987
|
|
|
1566
1988
|
fence.resolve(undefined);
|
|
@@ -1569,9 +1991,9 @@ describe('factory contract', () => {
|
|
|
1569
1991
|
const settled = scenario.renderer.inspect().bloom;
|
|
1570
1992
|
expect(settled.state).toBe('active');
|
|
1571
1993
|
expect(settled.graphStatus).toBe('valid');
|
|
1572
|
-
expect(settled.passCount).toBe(
|
|
1573
|
-
expect(settled.targetCount).toBe(
|
|
1574
|
-
expect(settled.targetBytes).toBe(
|
|
1994
|
+
expect(settled.passCount).toBe(10);
|
|
1995
|
+
expect(settled.targetCount).toBe(10);
|
|
1996
|
+
expect(settled.targetBytes).toBe(54_560);
|
|
1575
1997
|
expect(settled.resourceCount).toBeLessThan(overlap.resourceCount);
|
|
1576
1998
|
} finally {
|
|
1577
1999
|
onSubmittedWorkDone.mockRestore();
|
|
@@ -1580,11 +2002,11 @@ describe('factory contract', () => {
|
|
|
1580
2002
|
});
|
|
1581
2003
|
|
|
1582
2004
|
it('settles Bloom off to exact zero without re-enabling it', async () => {
|
|
2005
|
+
const scenario = await createBloomScenario();
|
|
1583
2006
|
const fence = deferred<undefined>();
|
|
1584
2007
|
const onSubmittedWorkDone = vi
|
|
1585
2008
|
.spyOn(RhiNullQueue.prototype, 'onSubmittedWorkDone')
|
|
1586
2009
|
.mockReturnValue(fence.promise);
|
|
1587
|
-
const scenario = await createBloomScenario();
|
|
1588
2010
|
try {
|
|
1589
2011
|
expect(scenario.draw().ok).toBe(true);
|
|
1590
2012
|
scenario.setBloom('off');
|
|
@@ -1617,11 +2039,11 @@ describe('factory contract', () => {
|
|
|
1617
2039
|
});
|
|
1618
2040
|
|
|
1619
2041
|
it('reports one structured Bloom fence rejection and drains late-retiring resources', async () => {
|
|
2042
|
+
const scenario = await createBloomScenario();
|
|
1620
2043
|
const fence = deferred<undefined>();
|
|
1621
2044
|
const onSubmittedWorkDone = vi
|
|
1622
2045
|
.spyOn(RhiNullQueue.prototype, 'onSubmittedWorkDone')
|
|
1623
2046
|
.mockReturnValue(fence.promise);
|
|
1624
|
-
const scenario = await createBloomScenario();
|
|
1625
2047
|
const errors: unknown[] = [];
|
|
1626
2048
|
let errorSnapshot: ReturnType<typeof scenario.renderer.inspect> | undefined;
|
|
1627
2049
|
const unsubscribe = scenario.renderer.onError((error) => {
|
|
@@ -1667,6 +2089,7 @@ describe('factory contract', () => {
|
|
|
1667
2089
|
});
|
|
1668
2090
|
|
|
1669
2091
|
it('keeps drained Bloom fence rejection callbacks inert', async () => {
|
|
2092
|
+
const scenario = await createBloomScenario();
|
|
1670
2093
|
const callbacks: Array<{
|
|
1671
2094
|
readonly resolve: (value: undefined) => unknown;
|
|
1672
2095
|
readonly reject: ((reason: unknown) => unknown) | undefined;
|
|
@@ -1681,7 +2104,6 @@ describe('factory contract', () => {
|
|
|
1681
2104
|
const onSubmittedWorkDone = vi
|
|
1682
2105
|
.spyOn(RhiNullQueue.prototype, 'onSubmittedWorkDone')
|
|
1683
2106
|
.mockReturnValue(lateFence);
|
|
1684
|
-
const scenario = await createBloomScenario();
|
|
1685
2107
|
const errors: unknown[] = [];
|
|
1686
2108
|
const unsubscribe = scenario.renderer.onError((error) => errors.push(error));
|
|
1687
2109
|
try {
|
|
@@ -1797,7 +2219,7 @@ describe('factory contract', () => {
|
|
|
1797
2219
|
const destroyCounts = new Map<object, number>();
|
|
1798
2220
|
const failure = new RhiError({
|
|
1799
2221
|
code: 'webgpu-runtime-error',
|
|
1800
|
-
expected: 'Bloom
|
|
2222
|
+
expected: 'Bloom upsample params buffer construction succeeds',
|
|
1801
2223
|
hint: 'repair the injected construction failure and retry the same frame',
|
|
1802
2224
|
});
|
|
1803
2225
|
try {
|
|
@@ -1807,7 +2229,7 @@ describe('factory contract', () => {
|
|
|
1807
2229
|
const lkg = scenario.renderer.inspect().bloom;
|
|
1808
2230
|
scenario.setBloom('on');
|
|
1809
2231
|
createBuffer.mockImplementation(function (this: RhiNullDevice, descriptor) {
|
|
1810
|
-
if (descriptor.label === 'bloom-
|
|
2232
|
+
if (descriptor.label === 'bloom-upsample-params-ubo') {
|
|
1811
2233
|
return err(failure);
|
|
1812
2234
|
}
|
|
1813
2235
|
const result = originalCreateBuffer.call(this, descriptor);
|
|
@@ -1832,12 +2254,12 @@ describe('factory contract', () => {
|
|
|
1832
2254
|
expect(scenario.renderer.inspect().bloom.resourceCount).toBe(lkg.resourceCount);
|
|
1833
2255
|
expect(scenario.renderer.inspect().bloom.passCount).toBe(0);
|
|
1834
2256
|
const adoptedBeforeFailure = [...created.entries()].filter(
|
|
1835
|
-
([, label]) => label === 'bloom-
|
|
2257
|
+
([, label]) => label === 'bloom-downsample-params-ubo',
|
|
1836
2258
|
);
|
|
1837
|
-
expect(adoptedBeforeFailure).toHaveLength(
|
|
2259
|
+
expect(adoptedBeforeFailure).toHaveLength(1);
|
|
1838
2260
|
expect(adoptedBeforeFailure.every(([handle]) => destroyCounts.get(handle) === 1)).toBe(true);
|
|
1839
2261
|
expect(
|
|
1840
|
-
[...created.values()].filter((label) => label === 'bloom-
|
|
2262
|
+
[...created.values()].filter((label) => label === 'bloom-upsample-params-ubo'),
|
|
1841
2263
|
).toHaveLength(0);
|
|
1842
2264
|
createBuffer.mockRestore();
|
|
1843
2265
|
destroyBuffer.mockRestore();
|
|
@@ -1902,6 +2324,7 @@ describe('factory contract', () => {
|
|
|
1902
2324
|
near: 0.1,
|
|
1903
2325
|
far: 100,
|
|
1904
2326
|
antialias: ANTIALIAS_NONE,
|
|
2327
|
+
tonemap: TONEMAP_ACES_FILMIC,
|
|
1905
2328
|
bloom: BLOOM_ENABLED,
|
|
1906
2329
|
},
|
|
1907
2330
|
},
|
|
@@ -1957,10 +2380,9 @@ describe('factory contract', () => {
|
|
|
1957
2380
|
staleLossEvents: 0,
|
|
1958
2381
|
});
|
|
1959
2382
|
expect(renderer.inspect().recovery.rehydratedRoots).toBeGreaterThan(0);
|
|
1960
|
-
//
|
|
1961
|
-
// the
|
|
1962
|
-
|
|
1963
|
-
expect(renderer.inspect().perFramePassNames.length).toBeGreaterThan(0);
|
|
2383
|
+
// Publication retires the graph bound to the previous canvas context;
|
|
2384
|
+
// the first recovered draw acquires the replacement presentation target.
|
|
2385
|
+
expect(renderer.inspect().perFramePassNames).toHaveLength(0);
|
|
1964
2386
|
expect(renderer.inspect().standardLighting).toBeDefined();
|
|
1965
2387
|
expect(world.update().ok).toBe(true);
|
|
1966
2388
|
expect(renderer.draw(input()).ok).toBe(true);
|
|
@@ -1977,6 +2399,96 @@ describe('factory contract', () => {
|
|
|
1977
2399
|
}
|
|
1978
2400
|
});
|
|
1979
2401
|
|
|
2402
|
+
it('does not publish Bloom resources for an intensity-zero recovery candidate', async () => {
|
|
2403
|
+
const adapter = new RhiNullAdapter();
|
|
2404
|
+
const lost = deferred<{ readonly reason: 'unknown'; readonly message: string }>();
|
|
2405
|
+
const firstDevice = (await adapter.requestDevice()).unwrap();
|
|
2406
|
+
const replacementDevice = (await adapter.requestDevice()).unwrap();
|
|
2407
|
+
const lostProxy = new Proxy(firstDevice, {
|
|
2408
|
+
get(target, property) {
|
|
2409
|
+
if (property === 'lost') return lost.promise;
|
|
2410
|
+
const value = Reflect.get(target, property, target);
|
|
2411
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
2412
|
+
},
|
|
2413
|
+
});
|
|
2414
|
+
let requestCount = 0;
|
|
2415
|
+
const recoverableRhi = {
|
|
2416
|
+
...rhi,
|
|
2417
|
+
requestAdapter: async () =>
|
|
2418
|
+
ok({
|
|
2419
|
+
features: adapter.features,
|
|
2420
|
+
limits: adapter.limits,
|
|
2421
|
+
requestDevice: async () => {
|
|
2422
|
+
requestCount += 1;
|
|
2423
|
+
return requestCount === 1 ? ok(lostProxy) : ok(replacementDevice);
|
|
2424
|
+
},
|
|
2425
|
+
} as never),
|
|
2426
|
+
} as never;
|
|
2427
|
+
const renderer = await constructRenderer(
|
|
2428
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
2429
|
+
{ rhi: recoverableRhi },
|
|
2430
|
+
{ shaderManifestUrl: renderLifecycleManifestUrl() },
|
|
2431
|
+
);
|
|
2432
|
+
const world = new World();
|
|
2433
|
+
const attached = renderer.attach(world);
|
|
2434
|
+
expect(attached.ok).toBe(true);
|
|
2435
|
+
if (!attached.ok) return;
|
|
2436
|
+
registerPropagateTransforms(world);
|
|
2437
|
+
world.spawn(
|
|
2438
|
+
{ component: Transform, data: { pos: [0, 0, 4] } },
|
|
2439
|
+
{
|
|
2440
|
+
component: LocalCamera,
|
|
2441
|
+
data: {
|
|
2442
|
+
fov: 1,
|
|
2443
|
+
aspect: 1,
|
|
2444
|
+
near: 0.1,
|
|
2445
|
+
far: 100,
|
|
2446
|
+
antialias: ANTIALIAS_NONE,
|
|
2447
|
+
tonemap: TONEMAP_ACES_FILMIC,
|
|
2448
|
+
bloom: BLOOM_ENABLED,
|
|
2449
|
+
bloomIntensity: 0,
|
|
2450
|
+
},
|
|
2451
|
+
},
|
|
2452
|
+
);
|
|
2453
|
+
const input = () => ({
|
|
2454
|
+
leases: [attached.value],
|
|
2455
|
+
camera: { lease: attached.value },
|
|
2456
|
+
environment: { lease: attached.value },
|
|
2457
|
+
});
|
|
2458
|
+
const assertZero = (): void => {
|
|
2459
|
+
expect(renderer.inspect().bloom).toMatchObject({
|
|
2460
|
+
graphStatus: 'empty',
|
|
2461
|
+
enabled: false,
|
|
2462
|
+
targetCount: 0,
|
|
2463
|
+
targetBytes: 0,
|
|
2464
|
+
resourceCount: 0,
|
|
2465
|
+
passCount: 0,
|
|
2466
|
+
encodeCount: 0,
|
|
2467
|
+
bindGroupCount: 0,
|
|
2468
|
+
uploadCount: 0,
|
|
2469
|
+
residentChildBytes: 0,
|
|
2470
|
+
state: 'off',
|
|
2471
|
+
});
|
|
2472
|
+
};
|
|
2473
|
+
try {
|
|
2474
|
+
expect((await renderer.initialization).ok).toBe(true);
|
|
2475
|
+
expect(world.update().ok).toBe(true);
|
|
2476
|
+
expect(renderer.draw(input()).ok).toBe(true);
|
|
2477
|
+
assertZero();
|
|
2478
|
+
lost.reject({ reason: 'unknown', message: 'forced intensity-zero recovery loss' });
|
|
2479
|
+
await Promise.resolve();
|
|
2480
|
+
await Promise.resolve();
|
|
2481
|
+
expect(renderer.health().reason).toBe('device-lost');
|
|
2482
|
+
expect((await renderer.recover()).ok).toBe(true);
|
|
2483
|
+
assertZero();
|
|
2484
|
+
expect(world.update().ok).toBe(true);
|
|
2485
|
+
expect(renderer.draw(input()).ok).toBe(true);
|
|
2486
|
+
assertZero();
|
|
2487
|
+
} finally {
|
|
2488
|
+
await renderer.dispose();
|
|
2489
|
+
}
|
|
2490
|
+
});
|
|
2491
|
+
|
|
1980
2492
|
it('abandons a post-process recovery candidate before promotion and retries once', async () => {
|
|
1981
2493
|
const adapter = new RhiNullAdapter();
|
|
1982
2494
|
const lost = deferred<{ readonly reason: 'unknown'; readonly message: string }>();
|
|
@@ -2092,6 +2604,7 @@ describe('factory contract', () => {
|
|
|
2092
2604
|
near: 0.1,
|
|
2093
2605
|
far: 100,
|
|
2094
2606
|
antialias: ANTIALIAS_NONE,
|
|
2607
|
+
tonemap: TONEMAP_ACES_FILMIC,
|
|
2095
2608
|
bloom: BLOOM_DISABLED,
|
|
2096
2609
|
},
|
|
2097
2610
|
},
|