@forgeax/engine-render 0.1.7 → 0.1.20
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 +140 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/assembly/backend-contract.d.ts +12 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -1
- package/dist/assembly/factory.d.ts +26 -5
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +4 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/renderer-frame-transaction.d.ts +39 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -0
- package/dist/assembly/renderer-inspection.d.ts +22 -0
- package/dist/assembly/renderer-inspection.d.ts.map +1 -0
- package/dist/assembly/renderer-lifecycle.d.ts +4 -0
- package/dist/assembly/renderer-lifecycle.d.ts.map +1 -0
- package/dist/authoring.mjs +4 -2
- package/dist/authoring.mjs.map +1 -1
- package/dist/chunk-4XINDLAY.mjs +9404 -0
- package/dist/chunk-4XINDLAY.mjs.map +1 -0
- package/dist/chunk-CDTWTHZR.mjs +752 -0
- package/dist/chunk-CDTWTHZR.mjs.map +1 -0
- package/dist/chunk-FJ6P52EE.mjs +46 -0
- package/dist/chunk-FJ6P52EE.mjs.map +1 -0
- package/dist/chunk-JDWARUOI.mjs +44 -0
- package/dist/chunk-JDWARUOI.mjs.map +1 -0
- package/dist/chunk-M2MP4ZOU.mjs +73 -0
- package/dist/chunk-M2MP4ZOU.mjs.map +1 -0
- package/dist/chunk-PCHBPYML.mjs +385 -0
- package/dist/chunk-PCHBPYML.mjs.map +1 -0
- package/dist/chunk-RXZ3RGOU.mjs +53 -0
- package/dist/chunk-RXZ3RGOU.mjs.map +1 -0
- package/dist/{chunk-YEJIUW7D.mjs → chunk-TBQJOK2L.mjs} +356 -543
- package/dist/chunk-TBQJOK2L.mjs.map +1 -0
- package/dist/{chunk-CQXZC5XI.mjs → chunk-VFPLTKCU.mjs} +79 -69
- package/dist/chunk-VFPLTKCU.mjs.map +1 -0
- package/dist/components/atmosphere.d.ts +9 -0
- package/dist/components/atmosphere.d.ts.map +1 -0
- package/dist/components/camera.d.ts +2 -1
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/fog.d.ts +8 -0
- package/dist/components/fog.d.ts.map +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/motion-blur.d.ts +7 -0
- package/dist/components/motion-blur.d.ts.map +1 -0
- package/dist/components/spot-light.d.ts +12 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.d.ts +2 -2
- package/dist/construct-renderer.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +11146 -14507
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/device-scope.d.ts +4 -0
- package/dist/device/device-scope.d.ts.map +1 -1
- package/dist/device/gpu-residency.d.ts +19 -2
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/frame.d.ts +60 -0
- package/dist/environment/frame.d.ts.map +1 -0
- package/dist/environment/generation.d.ts +21 -0
- package/dist/environment/generation.d.ts.map +1 -0
- package/dist/environment/graph.d.ts +4 -0
- package/dist/environment/graph.d.ts.map +1 -0
- package/dist/environment/inspection.d.ts +48 -0
- package/dist/environment/inspection.d.ts.map +1 -0
- package/dist/environment/lifecycle.d.ts +39 -0
- package/dist/environment/lifecycle.d.ts.map +1 -0
- package/dist/environment/signature.d.ts +25 -0
- package/dist/environment/signature.d.ts.map +1 -0
- package/dist/errors/render.d.ts +204 -1
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts +5 -0
- package/dist/extract/camera.d.ts.map +1 -0
- package/dist/extract/environment.d.ts +74 -0
- package/dist/extract/environment.d.ts.map +1 -0
- package/dist/extract/visibility.d.ts +1 -1
- package/dist/extract/visibility.d.ts.map +1 -1
- package/dist/features/host.d.ts +9 -2
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-feature.d.ts +16 -0
- package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -0
- package/dist/features/motion-blur/motion-blur-params.d.ts +25 -0
- package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -0
- package/dist/features/plan.d.ts +17 -2
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +3 -1
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/prepared-graphics.d.ts +2 -1
- package/dist/features/prepared-graphics.d.ts.map +1 -1
- package/dist/features/render-graph-raster.d.ts +2 -1
- package/dist/features/render-graph-raster.d.ts.map +1 -1
- package/dist/features/targets.d.ts +3 -0
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +12 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +7 -1
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +2 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +2 -0
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +5 -6
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +24 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +6 -4
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +43 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +2 -0
- package/dist/light-buffer-layout.d.ts +20 -12
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts +22 -0
- package/dist/materials.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +16 -9
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts +3 -0
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +48 -0
- package/dist/pipeline/standard-post.d.ts.map +1 -0
- package/dist/pipeline/standard-profile.d.ts +12 -2
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +18 -0
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/record/frame-lighting.d.ts +2 -0
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +137 -6
- 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 +6 -3
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-timing.d.ts +32 -0
- package/dist/record/gpu-timing.d.ts.map +1 -0
- package/dist/record/main-pass-geometry.d.ts +11 -0
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +9 -6
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +8 -1
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +2 -0
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +81 -19
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +1 -11
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +36 -4
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +4 -1
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/render-contract.d.ts +157 -8
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-data.d.ts +10 -6
- package/dist/render-data.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +86 -6
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +84 -8
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +30 -5
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer-inspect.d.ts +29 -0
- package/dist/renderer-inspect.d.ts.map +1 -0
- package/dist/scene/render-scene.d.ts +36 -2
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/temporal/coverage.d.ts +42 -0
- package/dist/temporal/coverage.d.ts.map +1 -0
- package/dist/temporal/frame-coordinator.d.ts +99 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -0
- package/dist/temporal/frame.d.ts +44 -0
- package/dist/temporal/frame.d.ts.map +1 -0
- package/dist/temporal/gpu.d.ts +39 -0
- package/dist/temporal/gpu.d.ts.map +1 -0
- package/dist/temporal/history.d.ts +37 -0
- package/dist/temporal/history.d.ts.map +1 -0
- package/dist/temporal/index.d.ts +7 -0
- package/dist/temporal/index.d.ts.map +1 -0
- package/dist/temporal/index.mjs +8 -0
- package/dist/temporal/index.mjs.map +1 -0
- package/dist/temporal/inspection.d.ts +36 -0
- package/dist/temporal/inspection.d.ts.map +1 -0
- package/dist/temporal/scene-data-catalog.d.ts +48 -0
- package/dist/temporal/scene-data-catalog.d.ts.map +1 -0
- package/dist/temporal/scene-data.d.ts +43 -0
- package/dist/temporal/scene-data.d.ts.map +1 -0
- package/dist/temporal/standard-scene-data.d.ts +61 -0
- package/dist/temporal/standard-scene-data.d.ts.map +1 -0
- package/dist/temporal/taa-history-resources.d.ts +22 -0
- package/dist/temporal/taa-history-resources.d.ts.map +1 -0
- package/dist/temporal/taa-history-store.d.ts +27 -0
- package/dist/temporal/taa-history-store.d.ts.map +1 -0
- package/dist/temporal/taa-resolve.d.ts +15 -0
- package/dist/temporal/taa-resolve.d.ts.map +1 -0
- package/dist/temporal/temporal-view.d.ts +40 -0
- package/dist/temporal/temporal-view.d.ts.map +1 -0
- package/dist/temporal/transaction.d.ts +9 -0
- package/dist/temporal/transaction.d.ts.map +1 -0
- package/dist/temporal/view.d.ts +56 -0
- package/dist/temporal/view.d.ts.map +1 -0
- package/dist/transmission/backdrop.d.ts +147 -0
- package/dist/transmission/backdrop.d.ts.map +1 -0
- package/dist/transmission/inspection.d.ts +57 -0
- package/dist/transmission/inspection.d.ts.map +1 -0
- package/dist/transmission/oracle.d.ts +42 -0
- package/dist/transmission/oracle.d.ts.map +1 -0
- package/dist/transmission/projection.d.ts +37 -0
- package/dist/transmission/projection.d.ts.map +1 -0
- package/dist/typed-render-graph-primitives.d.ts +30 -2
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-atlas.d.ts +10 -0
- package/dist/typed-shadow-atlas.d.ts.map +1 -0
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/capability.d.ts +57 -0
- package/dist/volume/capability.d.ts.map +1 -0
- package/dist/volume/component.d.ts +61 -0
- package/dist/volume/component.d.ts.map +1 -0
- package/dist/volume/extract.d.ts +11 -0
- package/dist/volume/extract.d.ts.map +1 -0
- package/dist/volume/inspection.d.ts +38 -0
- package/dist/volume/inspection.d.ts.map +1 -0
- package/dist/volume/passes.d.ts +38 -0
- package/dist/volume/passes.d.ts.map +1 -0
- package/dist/volume/recovery.d.ts +38 -0
- package/dist/volume/recovery.d.ts.map +1 -0
- package/dist/volume/resources.d.ts +57 -0
- package/dist/volume/resources.d.ts.map +1 -0
- package/dist/volume/temporal.d.ts +35 -0
- package/dist/volume/temporal.d.ts.map +1 -0
- package/package.json +24 -19
- package/src/__tests__/ai-user-cold-start.integration.test.ts +59 -0
- package/src/__tests__/ai-user-public-surface.test-d.ts +45 -0
- package/src/__tests__/bloom-exact-zero.unit.test.ts +202 -0
- package/src/__tests__/bloom-lifecycle.integration.test.ts +125 -0
- package/src/__tests__/construct-renderer-identity.unit.test.ts +4 -3
- package/src/__tests__/detached-inspection.integration.test.ts +177 -0
- package/src/__tests__/environment-fog-lanes.integration.test.ts +219 -0
- package/src/__tests__/environment-generation.integration.test.ts +141 -0
- package/src/__tests__/environment-selection.unit.test.ts +245 -0
- package/src/__tests__/factory-contract.integration.test.ts +1380 -9
- package/src/__tests__/frame-plan-contract.unit.test.ts +90 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +7 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +74 -22
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +37 -0
- package/src/__tests__/gpu-scene.unit.test.ts +10 -0
- package/src/__tests__/injection-kind-owner.test-d.ts +2 -1
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
- package/src/__tests__/material-contract-inventory.unit.test.ts +62 -0
- package/src/__tests__/material-cooked-projection.unit.test.ts +18 -146
- package/src/__tests__/materials-transmission-validation.unit.test.ts +36 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
- package/src/__tests__/morph-feature-plan.unit.test.ts +7 -0
- package/src/__tests__/motion-blur.pixel.integration.test.ts +121 -0
- package/src/__tests__/public-surface.test-d.ts +15 -1
- package/src/__tests__/render-error-code-owner.test-d.ts +14 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +11 -2
- package/src/__tests__/render-error-exhaustive.test-d.ts +3 -0
- package/src/__tests__/render-feature-errors.unit.test.ts +33 -3
- package/src/__tests__/render-feature-gpu-work.integration.test.ts +44 -0
- package/src/__tests__/render-feature-plan.test-d.ts +22 -89
- package/src/__tests__/render-scene-projection.unit.test.ts +28 -0
- package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +26 -0
- package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +21 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +423 -1
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +405 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +22 -0
- package/src/__tests__/scene-data-catalog.unit.test.ts +59 -0
- package/src/__tests__/scene-data-consumer.integration.test.ts +67 -0
- package/src/__tests__/scene-data-contract.test-d.ts +35 -0
- package/src/__tests__/scene-data-inspection.unit.test.ts +41 -0
- package/src/__tests__/scene-data-public-surface.test-d.ts +34 -0
- package/src/__tests__/shader-manifest-fixture.ts +111 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +123 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +24 -3
- package/src/__tests__/standard-pipeline.dawn.test.ts +15 -0
- package/src/__tests__/standard-pipeline.integration.test.ts +543 -1
- package/src/__tests__/standard-profile-oracle.unit.test.ts +12 -2
- package/src/__tests__/standard-temporal-producer.integration.test.ts +35 -0
- package/src/__tests__/surface-configuration.unit.test.ts +211 -12
- package/src/__tests__/taa-transaction.integration.test.ts +30 -0
- package/src/__tests__/temporal-admission.integration.test.ts +40 -0
- package/src/__tests__/temporal-coverage.unit.test.ts +35 -0
- package/src/__tests__/temporal-demand.unit.test.ts +44 -0
- package/src/__tests__/temporal-frame-coordinator.unit.test.ts +64 -0
- package/src/__tests__/temporal-graph-topology.unit.test.ts +51 -0
- package/src/__tests__/temporal-history-lifecycle.unit.test.ts +170 -0
- package/src/__tests__/temporal-performance.integration.test.ts +615 -0
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +97 -0
- package/src/__tests__/temporal-transaction.integration.test.ts +60 -0
- package/src/__tests__/temporal-view.unit.test.ts +152 -0
- package/src/__tests__/transmission-prewarm.unit.test.ts +93 -0
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +230 -1
- package/src/__tests__/urp-pipeline-targets.unit.test.ts +25 -1
- package/src/__tests__/urp-pipeline.unit.test.ts +4 -3
- package/src/__tests__/visibility-extract.integration.test.ts +27 -1
- package/src/__tests__/visibility-producer-matrix.integration.test.ts +10 -0
- package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
- package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
- package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
- package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
- package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
- package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
- package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
- package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
- package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
- package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
- package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
- package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
- package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
- package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
- package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
- package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
- package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
- package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
- package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
- package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
- package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
- package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
- package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
- package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
- package/src/assembly/backend-contract.ts +47 -1
- package/src/assembly/factory.ts +1480 -505
- package/src/assembly/host-contract.ts +4 -0
- package/src/assembly/renderer-frame-transaction.ts +75 -0
- package/src/assembly/renderer-inspection.ts +112 -0
- package/src/assembly/renderer-lifecycle.ts +6 -0
- package/src/components/__tests__/motion-blur.unit.test.ts +14 -0
- package/src/components/__tests__/schema-contract.unit.test.ts +6 -1
- package/src/components/atmosphere.ts +10 -0
- package/src/components/camera.ts +2 -1
- package/src/components/fog.ts +9 -0
- package/src/components/index.ts +3 -0
- package/src/components/light-helpers.ts +4 -0
- package/src/components/motion-blur.ts +8 -0
- package/src/components/spot-light.ts +22 -3
- package/src/construct-renderer.ts +40 -3
- package/src/device/device-scope.ts +15 -1
- package/src/device/gpu-residency.ts +168 -58
- package/src/environment/frame.ts +185 -0
- package/src/environment/generation.ts +23 -0
- package/src/environment/graph.ts +9 -0
- package/src/environment/inspection.ts +87 -0
- package/src/environment/lifecycle.ts +250 -0
- package/src/environment/signature.ts +55 -0
- package/src/errors/render.ts +404 -1
- package/src/extract/camera.ts +78 -0
- package/src/extract/environment.ts +171 -0
- package/src/extract/visibility.ts +38 -19
- package/src/features/host.ts +36 -5
- package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +67 -0
- package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +53 -0
- package/src/features/motion-blur/motion-blur-feature.ts +119 -0
- package/src/features/motion-blur/motion-blur-params.ts +89 -0
- package/src/features/plan.ts +32 -3
- package/src/features/prepared-gpu-work.ts +21 -6
- package/src/features/prepared-graphics.ts +2 -1
- package/src/features/render-graph-raster.ts +2 -1
- package/src/features/targets.ts +24 -2
- package/src/features/types.ts +13 -0
- package/src/fullscreen-post-process-pass.ts +34 -4
- package/src/gpu-scene.ts +24 -0
- package/src/gpu-usage.ts +2 -0
- package/src/ibl/skylight-bind-group.ts +30 -38
- package/src/index.ts +104 -7
- package/src/inspection-types.ts +48 -0
- package/src/internal.ts +5 -0
- package/src/light-buffer-layout.ts +59 -26
- package/src/materials.ts +119 -24
- package/src/pbr-pipeline.ts +91 -29
- package/src/pipeline/standard-forward-lane.ts +178 -101
- package/src/pipeline/standard-pipeline.ts +176 -105
- package/src/pipeline/standard-post.ts +373 -0
- package/src/pipeline/standard-profile.ts +26 -3
- package/src/pipeline-builder.ts +102 -12
- package/src/pipeline-spec.ts +9 -0
- package/src/plugin.ts +2 -0
- package/src/record/__tests__/fullscreen-temporal-routing.unit.test.ts +25 -0
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +47 -0
- package/src/record/__tests__/material-bg-cache.unit.test.ts +12 -1
- package/src/record/__tests__/material-pipeline-pending-cache.unit.test.ts +22 -0
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +75 -0
- package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +67 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +164 -0
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +127 -0
- package/src/record/frame-lighting.ts +103 -89
- package/src/record/frame-snapshot.ts +170 -6
- package/src/record/frame-targets.ts +21 -8
- package/src/record/frame.ts +1014 -7
- package/src/record/gpu-timing.ts +224 -0
- package/src/record/main-pass-geometry.ts +86 -40
- package/src/record/main-pass-material.ts +120 -68
- package/src/record/main-pass-sprite-draws.ts +78 -101
- package/src/record/main-pass.ts +77 -4
- package/src/record/mesh-ssbo.ts +22 -3
- package/src/record/render-context.ts +194 -23
- package/src/record/shadow-pass.ts +197 -58
- package/src/record/skybox-post-pass.ts +97 -41
- package/src/record/typed-frame-graph.ts +704 -30
- package/src/record/view-ubo.ts +52 -4
- package/src/render-contract.ts +181 -6
- package/src/render-data.ts +50 -22
- package/src/render-graph-primitives.ts +18 -1
- package/src/render-pipeline.ts +144 -27
- package/src/render-system-extract.ts +522 -72
- package/src/render-system.ts +561 -41
- package/src/renderer-inspect.ts +66 -0
- package/src/scene/render-scene.ts +252 -7
- package/src/temporal/__tests__/taa-history-resources.unit.test.ts +45 -0
- package/src/temporal/coverage.ts +76 -0
- package/src/temporal/frame-coordinator.ts +311 -0
- package/src/temporal/frame.ts +128 -0
- package/src/temporal/gpu.ts +287 -0
- package/src/temporal/history.ts +92 -0
- package/src/temporal/index.ts +6 -0
- package/src/temporal/inspection.ts +97 -0
- package/src/temporal/scene-data-catalog.ts +200 -0
- package/src/temporal/scene-data.ts +149 -0
- package/src/temporal/standard-scene-data.ts +182 -0
- package/src/temporal/taa-history-resources.ts +128 -0
- package/src/temporal/taa-history-store.ts +79 -0
- package/src/temporal/taa-resolve.ts +34 -0
- package/src/temporal/temporal-view.ts +169 -0
- package/src/temporal/transaction.ts +17 -0
- package/src/temporal/view.ts +161 -0
- package/src/transmission/__tests__/capability.unit.test.ts +83 -0
- package/src/transmission/__tests__/composition.integration.test.ts +161 -0
- package/src/transmission/__tests__/inspection.unit.test.ts +241 -0
- package/src/transmission/__tests__/lifecycle.integration.test.ts +96 -0
- package/src/transmission/__tests__/oracle.unit.test.ts +131 -0
- package/src/transmission/__tests__/projection.unit.test.ts +71 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +437 -0
- package/src/transmission/__tests__/transmission-backdrop-topology.unit.test.ts +109 -0
- package/src/transmission/__tests__/transmission-backdrop.dawn.test.ts +105 -0
- package/src/transmission/backdrop.ts +572 -0
- package/src/transmission/inspection.ts +190 -0
- package/src/transmission/oracle.ts +141 -0
- package/src/transmission/projection.ts +83 -0
- package/src/typed-render-graph-primitives.ts +194 -11
- package/src/typed-shadow-atlas.ts +17 -0
- package/src/typed-shadow-passes.ts +6 -5
- package/src/volume/capability.ts +173 -0
- package/src/volume/component.ts +110 -0
- package/src/volume/extract.ts +21 -0
- package/src/volume/inspection.ts +78 -0
- package/src/volume/passes.ts +680 -0
- package/src/volume/recovery.ts +86 -0
- package/src/volume/resources.ts +236 -0
- package/src/volume/temporal.ts +89 -0
- package/dist/__tests__/authoring-path.unit.test.d.ts +0 -2
- package/dist/__tests__/authoring-path.unit.test.d.ts.map +0 -1
- package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts +0 -2
- package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts.map +0 -1
- package/dist/__tests__/callback-inference.test-d.d.ts +0 -2
- package/dist/__tests__/callback-inference.test-d.d.ts.map +0 -1
- package/dist/__tests__/cluster-binner.test.d.ts +0 -2
- package/dist/__tests__/cluster-binner.test.d.ts.map +0 -1
- package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/construct-renderer-identity.unit.test.d.ts +0 -2
- package/dist/__tests__/construct-renderer-identity.unit.test.d.ts.map +0 -1
- package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts +0 -2
- package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts.map +0 -1
- package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts +0 -2
- package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts.map +0 -1
- package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts +0 -2
- package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts.map +0 -1
- package/dist/__tests__/direct-light-snapshot.unit.test.d.ts +0 -2
- package/dist/__tests__/direct-light-snapshot.unit.test.d.ts.map +0 -1
- package/dist/__tests__/factory-contract.integration.test.d.ts +0 -2
- package/dist/__tests__/factory-contract.integration.test.d.ts.map +0 -1
- package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/frame-observation.contract.test.d.ts +0 -2
- package/dist/__tests__/frame-observation.contract.test.d.ts.map +0 -1
- package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts +0 -2
- package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts.map +0 -1
- package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts +0 -2
- package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts +0 -6
- package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts.map +0 -1
- package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts +0 -2
- package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts +0 -2
- package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-driven-production.integration.test.d.ts +0 -2
- package/dist/__tests__/gpu-driven-production.integration.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts +0 -2
- package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts +0 -18
- package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts.map +0 -1
- package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts +0 -2
- package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-driven-view-reference.d.ts +0 -15
- package/dist/__tests__/gpu-driven-view-reference.d.ts.map +0 -1
- package/dist/__tests__/gpu-driven-view.browser.test.d.ts +0 -2
- package/dist/__tests__/gpu-driven-view.browser.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-driven-view.dawn.test.d.ts +0 -2
- package/dist/__tests__/gpu-driven-view.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-driven-view.unit.test.d.ts +0 -2
- package/dist/__tests__/gpu-driven-view.unit.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts +0 -2
- package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts +0 -7
- package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts.map +0 -1
- package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts +0 -2
- package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts +0 -2
- package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-scene-schema.unit.test.d.ts +0 -2
- package/dist/__tests__/gpu-scene-schema.unit.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-scene.dawn.test.d.ts +0 -2
- package/dist/__tests__/gpu-scene.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-scene.unit.test.d.ts +0 -2
- package/dist/__tests__/gpu-scene.unit.test.d.ts.map +0 -1
- package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts +0 -2
- package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts.map +0 -1
- package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts +0 -2
- package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts.map +0 -1
- package/dist/__tests__/injection-kind-owner.test-d.d.ts +0 -2
- package/dist/__tests__/injection-kind-owner.test-d.d.ts.map +0 -1
- package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts +0 -2
- package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/light-buffer-layout.unit.test.d.ts +0 -2
- package/dist/__tests__/light-buffer-layout.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-contract-inventory.unit.test.d.ts +0 -2
- package/dist/__tests__/material-contract-inventory.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-cooked-projection.unit.test.d.ts +0 -2
- package/dist/__tests__/material-cooked-projection.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts +0 -2
- package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts.map +0 -1
- package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts +0 -2
- package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-stride-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/material-stride-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/mesh-material-bindings.unit.test.d.ts +0 -2
- package/dist/__tests__/mesh-material-bindings.unit.test.d.ts.map +0 -1
- package/dist/__tests__/mesh-stride-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/mesh-stride-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts +0 -2
- package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts +0 -2
- package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts.map +0 -1
- package/dist/__tests__/morph-feature-plan.unit.test.d.ts +0 -2
- package/dist/__tests__/morph-feature-plan.unit.test.d.ts.map +0 -1
- package/dist/__tests__/morph-visual-evidence.integration.test.d.ts +0 -2
- package/dist/__tests__/morph-visual-evidence.integration.test.d.ts.map +0 -1
- package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts +0 -2
- package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts.map +0 -1
- package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts +0 -2
- package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts.map +0 -1
- package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts +0 -2
- package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts.map +0 -1
- package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts +0 -2
- package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts.map +0 -1
- package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts +0 -2
- package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts.map +0 -1
- package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts +0 -2
- package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts.map +0 -1
- package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts +0 -2
- package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts.map +0 -1
- package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts +0 -2
- package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts.map +0 -1
- package/dist/__tests__/prepared-graphics-store.unit.test.d.ts +0 -2
- package/dist/__tests__/prepared-graphics-store.unit.test.d.ts.map +0 -1
- package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts +0 -2
- package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts.map +0 -1
- package/dist/__tests__/profiler-default-off.test.d.ts +0 -2
- package/dist/__tests__/profiler-default-off.test.d.ts.map +0 -1
- package/dist/__tests__/profiler-phase-catalog.test.d.ts +0 -2
- package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +0 -1
- package/dist/__tests__/profiler-phase-integration.test.d.ts +0 -2
- package/dist/__tests__/profiler-phase-integration.test.d.ts.map +0 -1
- package/dist/__tests__/profiler-phase-token.test.d.ts +0 -2
- package/dist/__tests__/profiler-phase-token.test.d.ts.map +0 -1
- package/dist/__tests__/public-surface.test-d.d.ts +0 -2
- package/dist/__tests__/public-surface.test-d.d.ts.map +0 -1
- package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts +0 -2
- package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-error-code-owner.test-d.d.ts +0 -2
- package/dist/__tests__/render-error-code-owner.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-error-detail-ssot.test-d.d.ts +0 -2
- package/dist/__tests__/render-error-detail-ssot.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts +0 -2
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-feature-assembly.test-d.d.ts +0 -2
- package/dist/__tests__/render-feature-assembly.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-feature-context.test-d.d.ts +0 -2
- package/dist/__tests__/render-feature-context.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-feature-contract.test-d.d.ts +0 -2
- package/dist/__tests__/render-feature-contract.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-docs.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-docs.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-errors.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-errors.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts +0 -2
- package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-plan.test-d.d.ts +0 -2
- package/dist/__tests__/render-feature-plan.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-feature-plan.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-plan.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts +0 -2
- package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts +0 -2
- package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts +0 -2
- package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-public-surface.test-d.d.ts +0 -2
- package/dist/__tests__/render-feature-public-surface.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-feature-retry.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-retry.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-scheduling.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-scheduling.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-visibility-context.test-d.d.ts +0 -2
- package/dist/__tests__/render-feature-visibility-context.test-d.d.ts.map +0 -1
- package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-zero-work.unit.test.d.ts +0 -2
- package/dist/__tests__/render-feature-zero-work.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-graph-compute-raster-gpu.d.ts +0 -7
- package/dist/__tests__/render-graph-compute-raster-gpu.d.ts.map +0 -1
- package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts +0 -2
- package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts.map +0 -1
- package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts +0 -2
- package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/render-graph-hzb-gpu.d.ts +0 -7
- package/dist/__tests__/render-graph-hzb-gpu.d.ts.map +0 -1
- package/dist/__tests__/render-graph-hzb.browser.test.d.ts +0 -2
- package/dist/__tests__/render-graph-hzb.browser.test.d.ts.map +0 -1
- package/dist/__tests__/render-graph-hzb.dawn.test.d.ts +0 -2
- package/dist/__tests__/render-graph-hzb.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/render-graph-primitives.unit.test.d.ts +0 -2
- package/dist/__tests__/render-graph-primitives.unit.test.d.ts.map +0 -1
- package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts +0 -12
- package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts.map +0 -1
- package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts +0 -2
- package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts.map +0 -1
- package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts +0 -2
- package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/render-scene-projection.unit.test.d.ts +0 -2
- package/dist/__tests__/render-scene-projection.unit.test.d.ts.map +0 -1
- package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts +0 -2
- package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts.map +0 -1
- package/dist/__tests__/renderer-lifecycle.integration.test.d.ts +0 -2
- package/dist/__tests__/renderer-lifecycle.integration.test.d.ts.map +0 -1
- package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts +0 -2
- package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts.map +0 -1
- package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts +0 -2
- package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts.map +0 -1
- package/dist/__tests__/shader-stage-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/shader-stage-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/ssao-config.unit.test.d.ts +0 -2
- package/dist/__tests__/ssao-config.unit.test.d.ts.map +0 -1
- package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts +0 -2
- package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts.map +0 -1
- package/dist/__tests__/standard-descriptors.test-d.d.ts +0 -2
- package/dist/__tests__/standard-descriptors.test-d.d.ts.map +0 -1
- package/dist/__tests__/standard-pipeline.dawn.test.d.ts +0 -2
- package/dist/__tests__/standard-pipeline.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/standard-pipeline.integration.test.d.ts +0 -2
- package/dist/__tests__/standard-pipeline.integration.test.d.ts.map +0 -1
- package/dist/__tests__/standard-profile-oracle.unit.test.d.ts +0 -2
- package/dist/__tests__/standard-profile-oracle.unit.test.d.ts.map +0 -1
- package/dist/__tests__/surface-configuration.unit.test.d.ts +0 -2
- package/dist/__tests__/surface-configuration.unit.test.d.ts.map +0 -1
- package/dist/__tests__/texture-usage-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/texture-usage-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts +0 -2
- package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts.map +0 -1
- package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts +0 -2
- package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts.map +0 -1
- package/dist/__tests__/urp-pipeline.unit.test.d.ts +0 -2
- package/dist/__tests__/urp-pipeline.unit.test.d.ts.map +0 -1
- package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts +0 -2
- package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts.map +0 -1
- package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts +0 -2
- package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-diagnostics.integration.test.d.ts +0 -2
- package/dist/__tests__/visibility-diagnostics.integration.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-extract.integration.test.d.ts +0 -2
- package/dist/__tests__/visibility-extract.integration.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-extract.perf.test.d.ts +0 -2
- package/dist/__tests__/visibility-extract.perf.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts +0 -2
- package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-hidden-report.unit.test.d.ts +0 -2
- package/dist/__tests__/visibility-hidden-report.unit.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-instances.integration.test.d.ts +0 -2
- package/dist/__tests__/visibility-instances.integration.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-multi-world.integration.test.d.ts +0 -2
- package/dist/__tests__/visibility-multi-world.integration.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts +0 -2
- package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts +0 -2
- package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-public-surface.test-d.d.ts +0 -2
- package/dist/__tests__/visibility-public-surface.test-d.d.ts.map +0 -1
- package/dist/__tests__/visibility-resolver.unit.test.d.ts +0 -2
- package/dist/__tests__/visibility-resolver.unit.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts +0 -2
- package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts.map +0 -1
- package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts +0 -2
- package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts.map +0 -1
- package/dist/__tests__/vocabulary.unit.test.d.ts +0 -2
- package/dist/__tests__/vocabulary.unit.test.d.ts.map +0 -1
- package/dist/chunk-CQXZC5XI.mjs.map +0 -1
- package/dist/chunk-HCOGPHKS.mjs +0 -93
- package/dist/chunk-HCOGPHKS.mjs.map +0 -1
- package/dist/chunk-YEJIUW7D.mjs.map +0 -1
- package/dist/components/__tests__/points-lines-admission.unit.test.d.ts +0 -2
- package/dist/components/__tests__/points-lines-admission.unit.test.d.ts.map +0 -1
- package/dist/components/__tests__/points-lines-schema.unit.test.d.ts +0 -2
- package/dist/components/__tests__/points-lines-schema.unit.test.d.ts.map +0 -1
- package/dist/components/__tests__/schema-contract.unit.test.d.ts +0 -2
- package/dist/components/__tests__/schema-contract.unit.test.d.ts.map +0 -1
- package/dist/components/__tests__/schema-parity.unit.test.d.ts +0 -2
- package/dist/components/__tests__/schema-parity.unit.test.d.ts.map +0 -1
- package/dist/components/__tests__/visibility-schema.unit.test.d.ts +0 -2
- package/dist/components/__tests__/visibility-schema.unit.test.d.ts.map +0 -1
- package/dist/points-lines/__tests__/bounds.unit.test.d.ts +0 -2
- package/dist/points-lines/__tests__/bounds.unit.test.d.ts.map +0 -1
- package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts +0 -2
- package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts.map +0 -1
- package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts +0 -2
- package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts.map +0 -1
- package/dist/points-lines/__tests__/inspection.unit.test.d.ts +0 -2
- package/dist/points-lines/__tests__/inspection.unit.test.d.ts.map +0 -1
- package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts +0 -2
- package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts.map +0 -1
- package/dist/points-lines/__tests__/points-lines.bench.d.ts +0 -2
- package/dist/points-lines/__tests__/points-lines.bench.d.ts.map +0 -1
- package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts +0 -2
- package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts.map +0 -1
- package/dist/points-lines/__tests__/snapshot.unit.test.d.ts +0 -2
- package/dist/points-lines/__tests__/snapshot.unit.test.d.ts.map +0 -1
- package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts +0 -2
- package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts.map +0 -1
- package/dist/record/__tests__/material-bg-cache.unit.test.d.ts +0 -2
- package/dist/record/__tests__/material-bg-cache.unit.test.d.ts.map +0 -1
- package/dist/record/__tests__/material-slot-plan.unit.test.d.ts +0 -2
- package/dist/record/__tests__/material-slot-plan.unit.test.d.ts.map +0 -1
- package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts +0 -2
- package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts.map +0 -1
- package/dist/record/__tests__/video-texture-view.unit.test.d.ts +0 -2
- package/dist/record/__tests__/video-texture-view.unit.test.d.ts.map +0 -1
package/src/assembly/factory.ts
CHANGED
|
@@ -59,6 +59,7 @@ import type {
|
|
|
59
59
|
import { err, ok, RhiError, validateDrawArgs } from '@forgeax/engine-rhi';
|
|
60
60
|
import { registerPropagateTransforms } from '@forgeax/engine-scene';
|
|
61
61
|
import {
|
|
62
|
+
DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
|
|
62
63
|
findVariantByKey,
|
|
63
64
|
type MaterialShaderEntry,
|
|
64
65
|
type MaterialShaderManifestEntry,
|
|
@@ -77,9 +78,12 @@ import type {
|
|
|
77
78
|
VertexAttributeMap,
|
|
78
79
|
} from '@forgeax/engine-types';
|
|
79
80
|
import { derive, handleSlot } from '@forgeax/engine-types';
|
|
81
|
+
|
|
82
|
+
export type { MaterialShaderManifestEntry } from '@forgeax/engine-shader';
|
|
83
|
+
|
|
80
84
|
import { GpuResidencyCache, type MeshGpuHandles } from '../device/gpu-residency';
|
|
81
85
|
import { createEngineMetrics } from '../engine-metrics';
|
|
82
|
-
import { RecoverError, type RecoverFailure
|
|
86
|
+
import { RecoverError, type RecoverFailure } from '../errors/recover';
|
|
83
87
|
import {
|
|
84
88
|
FrameReceiptStaleError,
|
|
85
89
|
type RenderError,
|
|
@@ -90,8 +94,9 @@ import {
|
|
|
90
94
|
RenderFeatureStageFailedError,
|
|
91
95
|
} from '../errors/render';
|
|
92
96
|
import { createRenderFeatureHost, type RenderFeatureHost } from '../features/host';
|
|
97
|
+
import type { RenderFeatureMaterialShaderBindingContract } from '../features/plan';
|
|
93
98
|
import { RENDER_FEATURE_VERTEX_LAYOUTS } from '../features/prepared-graphics';
|
|
94
|
-
import type { RenderFeature } from '../features/types';
|
|
99
|
+
import type { RenderFeature, RenderFeatureShaderModuleMode } from '../features/types';
|
|
95
100
|
import type { PostProcessShaderEntry } from '../fullscreen-post-process-pass';
|
|
96
101
|
import { glyphTextLayoutSystem } from '../glyph-text-layout-system';
|
|
97
102
|
import { GpuBuffer } from '../gpu-resource';
|
|
@@ -115,6 +120,7 @@ import {
|
|
|
115
120
|
FALLBACK_BYTES_PER_ROW,
|
|
116
121
|
type SkylightFallback,
|
|
117
122
|
} from '../ibl/skylight-bind-group';
|
|
123
|
+
import type { BloomInspection } from '../inspection-types';
|
|
118
124
|
import {
|
|
119
125
|
DeviceScope,
|
|
120
126
|
HealthListenerRegistry,
|
|
@@ -129,15 +135,19 @@ import {
|
|
|
129
135
|
SPOT_LIGHT_STD430_BYTES,
|
|
130
136
|
} from '../light-buffer-layout';
|
|
131
137
|
import {
|
|
138
|
+
buildPbrMaterialUserRegionEntries,
|
|
132
139
|
buildPbrPipelineLayouts,
|
|
133
140
|
buildPbrSkinLayouts,
|
|
141
|
+
isSkinnedShadowCasterVariant,
|
|
134
142
|
SKIN_MATERIAL_SHADER_ID,
|
|
135
143
|
} from '../pbr-pipeline';
|
|
136
144
|
import { STANDARD_CLUSTER_MEMBERSHIP_WGSL, standardPipeline } from '../pipeline/standard-pipeline';
|
|
145
|
+
import { inspectStandardBloomGraph } from '../pipeline/standard-post';
|
|
137
146
|
import {
|
|
138
147
|
DEFAULT_STANDARD_PROFILE,
|
|
139
148
|
STANDARD_LIGHT_COUNTS,
|
|
140
149
|
STANDARD_PIPELINE_ID,
|
|
150
|
+
STANDARD_POST_STAGE_NAMES,
|
|
141
151
|
} from '../pipeline/standard-profile';
|
|
142
152
|
import { buildPipelineForMaterialShader } from '../pipeline-builder';
|
|
143
153
|
import {
|
|
@@ -155,6 +165,8 @@ import {
|
|
|
155
165
|
} from '../pipeline-spec';
|
|
156
166
|
import { POINTS_LINES_MATERIAL_SHADER_ID } from '../points-lines/record';
|
|
157
167
|
import type { FrameObservationOptions } from '../record/frame';
|
|
168
|
+
import { GpuTimingCapture, type VolumeTimingObservation } from '../record/gpu-timing';
|
|
169
|
+
import type { BloomPersistentBundle } from '../record/render-context';
|
|
158
170
|
import { SHADOW_CASTER_BUFFER_SIZE } from '../record/shadow-pass';
|
|
159
171
|
import { POINTS_LINES_VIEW_BUFFER_SIZE, VIEW_UNIFORM_BUFFER_SIZE } from '../record/view-ubo';
|
|
160
172
|
import type {
|
|
@@ -176,8 +188,9 @@ import type {
|
|
|
176
188
|
RenderProfile,
|
|
177
189
|
RenderResult,
|
|
178
190
|
RenderWorldLease,
|
|
191
|
+
VolumetricFogShaderSources,
|
|
179
192
|
} from '../render-contract';
|
|
180
|
-
import {
|
|
193
|
+
import { STANDARD_OUTPUT_TRANSFORM_FEATURE_ID } from '../render-contract';
|
|
181
194
|
import {
|
|
182
195
|
configureSurface,
|
|
183
196
|
createRenderSystem,
|
|
@@ -194,6 +207,8 @@ import {
|
|
|
194
207
|
import { tilemapChunkExtractSystem } from '../tilemap-chunk-extract-system';
|
|
195
208
|
import { EngineEnvironmentError, type RhiBackendPack } from './backend-contract';
|
|
196
209
|
import type { RendererAssemblyImplementation, RendererHostImplementation } from './host-contract';
|
|
210
|
+
import { projectRendererOutputInspection } from './renderer-inspection';
|
|
211
|
+
import { isRendererStateOperational } from './renderer-lifecycle';
|
|
197
212
|
|
|
198
213
|
export { assembleMaterialProjection } from './material/assembly';
|
|
199
214
|
export {
|
|
@@ -232,6 +247,9 @@ function loadRhiPack(
|
|
|
232
247
|
const createShaderModule = mod.createShaderModule as
|
|
233
248
|
| RhiBackendPack['createShaderModule']
|
|
234
249
|
| undefined;
|
|
250
|
+
const createShaderModuleImmediate = mod.createShaderModuleImmediate as
|
|
251
|
+
| RhiBackendPack['createShaderModuleImmediate']
|
|
252
|
+
| undefined;
|
|
235
253
|
const translateErrorEventToRhiError = mod.translateErrorEventToRhiError as
|
|
236
254
|
| RhiBackendPack['translateErrorEventToRhiError']
|
|
237
255
|
| undefined;
|
|
@@ -241,6 +259,7 @@ function loadRhiPack(
|
|
|
241
259
|
return {
|
|
242
260
|
rhi,
|
|
243
261
|
...(createShaderModule === undefined ? {} : { createShaderModule }),
|
|
262
|
+
...(createShaderModuleImmediate === undefined ? {} : { createShaderModuleImmediate }),
|
|
244
263
|
...(translateErrorEventToRhiError === undefined ? {} : { translateErrorEventToRhiError }),
|
|
245
264
|
...(rawDeviceAccessor === undefined ? {} : { _internal_getRawDevice: rawDeviceAccessor }),
|
|
246
265
|
...(instrumentation === undefined ? {} : { instrumentation }),
|
|
@@ -477,20 +496,67 @@ export type LayoutKind = 'pbr' | 'pbr-skin' | 'hdrp-pbr' | 'sprite-urp' | 'unlit
|
|
|
477
496
|
* Return every device-capability HDRP PBR variant that needs a lazy module
|
|
478
497
|
* label seeded during renderer boot. Color availability is intentionally not
|
|
479
498
|
* filtered: colored and no-color meshes use distinct composed WGSL modules.
|
|
499
|
+
* Transmission is filtered by the same sampled-texture capability gate as
|
|
500
|
+
* Standard transmission because HDRP shares the material texture ABI.
|
|
480
501
|
*/
|
|
481
502
|
export function selectHdrpPbrPrewarmVariants(
|
|
482
503
|
manifestEntry: MaterialShaderManifestEntry | undefined,
|
|
483
504
|
storageBufferCapable: boolean,
|
|
505
|
+
transmissionCapable: boolean,
|
|
506
|
+
projectorAvailable: boolean = true,
|
|
484
507
|
): readonly MaterialShaderManifestEntry['variants'][number][] {
|
|
485
508
|
return (
|
|
486
509
|
manifestEntry?.variants.filter(
|
|
487
510
|
(variant) =>
|
|
488
511
|
variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable &&
|
|
489
|
-
variant.defines.CLUSTER_FORWARD_AVAILABLE === true
|
|
512
|
+
variant.defines.CLUSTER_FORWARD_AVAILABLE === true &&
|
|
513
|
+
(!('PROJECTOR_AVAILABLE' in variant.defines) ||
|
|
514
|
+
variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) &&
|
|
515
|
+
(transmissionCapable || variant.defines.TRANSMISSION_AVAILABLE !== true),
|
|
490
516
|
) ?? []
|
|
491
517
|
);
|
|
492
518
|
}
|
|
493
519
|
|
|
520
|
+
/**
|
|
521
|
+
* Select both Standard transmission states from the manifest for exact-label
|
|
522
|
+
* prewarming. The requested keys come from declared variants; no key is
|
|
523
|
+
* synthesized and no source fallback is permitted.
|
|
524
|
+
*/
|
|
525
|
+
export function selectStandardPbrTransmissionPrewarmVariants(
|
|
526
|
+
manifestEntry: MaterialShaderManifestEntry | undefined,
|
|
527
|
+
storageBufferCapable: boolean,
|
|
528
|
+
projectorAvailable: boolean = true,
|
|
529
|
+
): readonly MaterialShaderManifestEntry['variants'][number][] {
|
|
530
|
+
if (manifestEntry === undefined) {
|
|
531
|
+
throw new Error('Standard material shader manifest row is missing');
|
|
532
|
+
}
|
|
533
|
+
const selected: MaterialShaderManifestEntry['variants'][number][] = [];
|
|
534
|
+
for (const transmissionAvailable of [false, true]) {
|
|
535
|
+
const declared = manifestEntry.variants.find(
|
|
536
|
+
(variant) =>
|
|
537
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable &&
|
|
538
|
+
variant.defines.CLUSTER_FORWARD_AVAILABLE === false &&
|
|
539
|
+
variant.defines.VERTEX_COLOR_AVAILABLE === false &&
|
|
540
|
+
(!('PROJECTOR_AVAILABLE' in variant.defines) ||
|
|
541
|
+
variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) &&
|
|
542
|
+
variant.defines.TRANSMISSION_AVAILABLE === transmissionAvailable,
|
|
543
|
+
);
|
|
544
|
+
if (declared === undefined) {
|
|
545
|
+
throw new Error(
|
|
546
|
+
`Standard material shader manifest lacks exact TRANSMISSION_AVAILABLE=${transmissionAvailable} prewarm variant`,
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
const exact = findVariantByKey(manifestEntry, declared.definesKey);
|
|
550
|
+
if (exact === undefined) {
|
|
551
|
+
throw new Error(
|
|
552
|
+
`Standard material shader manifest exact lookup failed for TRANSMISSION_AVAILABLE=${transmissionAvailable}`,
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
selected.push(exact);
|
|
556
|
+
}
|
|
557
|
+
return selected;
|
|
558
|
+
}
|
|
559
|
+
|
|
494
560
|
/**
|
|
495
561
|
* Select the PBR variant that matches a fallback mesh with no COLOR_0 input.
|
|
496
562
|
* Unknown/custom material IDs may still use the legacy PBR fallback, so that
|
|
@@ -500,24 +566,24 @@ export function selectNoColorPbrVariant(
|
|
|
500
566
|
manifestEntry: MaterialShaderManifestEntry | undefined,
|
|
501
567
|
storageBufferCapable: boolean,
|
|
502
568
|
variantSet: string | undefined,
|
|
569
|
+
projectorAvailable: boolean = true,
|
|
503
570
|
): MaterialShaderManifestEntry['variants'][number] | undefined {
|
|
504
571
|
const hdrpRequested =
|
|
505
|
-
variantSet === '' ||
|
|
572
|
+
variantSet === '' ||
|
|
573
|
+
// biome-ignore lint/complexity/useOptionalChain: G-14 requires an explicit undefined guard so no variant request becomes false.
|
|
574
|
+
(variantSet !== undefined && variantSet.includes('CLUSTER_FORWARD_AVAILABLE=true'));
|
|
506
575
|
return manifestEntry?.variants.find(
|
|
507
576
|
(variant) =>
|
|
508
577
|
variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable &&
|
|
509
578
|
variant.defines.VERTEX_COLOR_AVAILABLE === false &&
|
|
579
|
+
(!('PROJECTOR_AVAILABLE' in variant.defines) ||
|
|
580
|
+
variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) &&
|
|
510
581
|
(!('CLUSTER_FORWARD_AVAILABLE' in variant.defines) ||
|
|
511
582
|
variant.defines.CLUSTER_FORWARD_AVAILABLE === hdrpRequested),
|
|
512
583
|
);
|
|
513
584
|
}
|
|
514
585
|
|
|
515
|
-
export type MaterialShaderBindingContract =
|
|
516
|
-
| 'group-0'
|
|
517
|
-
| 'group-0-resource'
|
|
518
|
-
| 'view-only'
|
|
519
|
-
| 'view-and-scene-depth'
|
|
520
|
-
| 'render-material';
|
|
586
|
+
export type MaterialShaderBindingContract = RenderFeatureMaterialShaderBindingContract;
|
|
521
587
|
export type MaterialShaderVertexInputContract = 'none' | 'render-material';
|
|
522
588
|
|
|
523
589
|
export function resolveMaterialShaderBindingContract(
|
|
@@ -570,6 +636,46 @@ export function resolveMaterialShaderVertexInputContract(
|
|
|
570
636
|
: 'none';
|
|
571
637
|
}
|
|
572
638
|
|
|
639
|
+
/**
|
|
640
|
+
* Decide whether a custom material's user-region can use the shared PBR
|
|
641
|
+
* bind-group layout. Resource names are not part of a WebGPU layout contract:
|
|
642
|
+
* the only safe reuse test is that every derived binding in the custom schema
|
|
643
|
+
* is an identical prefix of the canonical user-region descriptor, including
|
|
644
|
+
* binding kind and its full shape (sample type, view dimension, sampler mode,
|
|
645
|
+
* dynamic-offset flags, and visibility). A texture3d/array or comparison /
|
|
646
|
+
* storage resource must therefore take the per-shader layout path even when
|
|
647
|
+
* its field count is small.
|
|
648
|
+
*/
|
|
649
|
+
export function isSharedMaterialUserRegionCompatible(
|
|
650
|
+
paramSchema: readonly ParamSchemaEntry[],
|
|
651
|
+
): boolean {
|
|
652
|
+
const shared = buildPbrMaterialUserRegionEntries(DEFAULT_STANDARD_PBR_PARAM_SCHEMA);
|
|
653
|
+
const candidate = buildPbrMaterialUserRegionEntries(paramSchema);
|
|
654
|
+
if (candidate.length > shared.length) return false;
|
|
655
|
+
return candidate.every((entry, index) => {
|
|
656
|
+
const canonical = shared[index];
|
|
657
|
+
if (canonical === undefined) return false;
|
|
658
|
+
return (
|
|
659
|
+
JSON.stringify({
|
|
660
|
+
binding: entry.binding,
|
|
661
|
+
visibility: entry.visibility,
|
|
662
|
+
buffer: entry.buffer ?? null,
|
|
663
|
+
sampler: entry.sampler ?? null,
|
|
664
|
+
texture: entry.texture ?? null,
|
|
665
|
+
storageTexture: entry.storageTexture ?? null,
|
|
666
|
+
}) ===
|
|
667
|
+
JSON.stringify({
|
|
668
|
+
binding: canonical.binding,
|
|
669
|
+
visibility: canonical.visibility,
|
|
670
|
+
buffer: canonical.buffer ?? null,
|
|
671
|
+
sampler: canonical.sampler ?? null,
|
|
672
|
+
texture: canonical.texture ?? null,
|
|
673
|
+
storageTexture: canonical.storageTexture ?? null,
|
|
674
|
+
})
|
|
675
|
+
);
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
573
679
|
export function allowsUnlitPreparedFallback(
|
|
574
680
|
depthFormatOverride: GPUTextureFormat | null | undefined,
|
|
575
681
|
vertexLayout: string | undefined,
|
|
@@ -632,6 +738,13 @@ export function selectPipelineLayoutForVariant(
|
|
|
632
738
|
if (layoutKind === 'unlit-urp') {
|
|
633
739
|
return state.pbrPipelineLayout;
|
|
634
740
|
}
|
|
741
|
+
// Static PBR callers can use the canonical all-true variant key (`''`)
|
|
742
|
+
// without becoming HDRP. Keep the shader variant identity independent from
|
|
743
|
+
// the pipeline-layout identity; shadow-caster static draws use this explicit
|
|
744
|
+
// kind so their ordinary mesh bind group remains compatible with the PSO.
|
|
745
|
+
if (layoutKind === 'pbr') {
|
|
746
|
+
return state.pbrPipelineLayout;
|
|
747
|
+
}
|
|
635
748
|
// HDRP variant matches when:
|
|
636
749
|
// - layoutKind === 'hdrp-pbr' (caller-driven, future-proof), OR
|
|
637
750
|
// - variantSet is the canonical all-true key '' (boot-time HDRP
|
|
@@ -667,10 +780,13 @@ export function resolveMaterialShaderVariantSet(
|
|
|
667
780
|
}>,
|
|
668
781
|
backendKind: RhiDevice['caps']['backendKind'],
|
|
669
782
|
storageBuffer: boolean,
|
|
783
|
+
projectorAvailable: boolean = true,
|
|
670
784
|
): string | undefined {
|
|
671
785
|
const declaredAxes = new Set(variants.flatMap((variant) => Object.keys(variant.defines)));
|
|
672
786
|
const declaresCapabilityVariants =
|
|
673
|
-
declaredAxes.has('WEBGL2_COMPAT') ||
|
|
787
|
+
declaredAxes.has('WEBGL2_COMPAT') ||
|
|
788
|
+
declaredAxes.has('STORAGE_BUFFER_AVAILABLE') ||
|
|
789
|
+
declaredAxes.has('PROJECTOR_AVAILABLE');
|
|
674
790
|
if (!declaresCapabilityVariants) return requestedVariantSet;
|
|
675
791
|
|
|
676
792
|
const requested: Record<string, boolean> = {};
|
|
@@ -685,7 +801,19 @@ export function resolveMaterialShaderVariantSet(
|
|
|
685
801
|
? backendKind === 'wgpu-webgl2'
|
|
686
802
|
: axis === 'STORAGE_BUFFER_AVAILABLE'
|
|
687
803
|
? backendKind !== 'wgpu-webgl2' && storageBuffer
|
|
688
|
-
:
|
|
804
|
+
: axis === 'SKINNING_DISABLED'
|
|
805
|
+
? (requested[axis] ?? true)
|
|
806
|
+
: axis === 'CLUSTER_FORWARD_AVAILABLE'
|
|
807
|
+
? backendKind !== 'wgpu-webgl2' && (requested[axis] ?? false)
|
|
808
|
+
: axis === 'PER_INSTANCE_REGION'
|
|
809
|
+
? requestedVariantSet === '' || (requested[axis] ?? false)
|
|
810
|
+
: axis === 'VERTEX_COLOR_AVAILABLE'
|
|
811
|
+
? requestedVariantSet === '' || (requested[axis] ?? false)
|
|
812
|
+
: axis === 'TRANSMISSION_AVAILABLE'
|
|
813
|
+
? (requested[axis] ?? false)
|
|
814
|
+
: axis === 'PROJECTOR_AVAILABLE'
|
|
815
|
+
? projectorAvailable
|
|
816
|
+
: (requested[axis] ?? true);
|
|
689
817
|
}
|
|
690
818
|
const sorted = Object.entries(filtered).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
691
819
|
return sorted.every(([, value]) => value)
|
|
@@ -875,11 +1003,38 @@ const COMPRESSION_FEATURES: GPUFeatureName[] = [
|
|
|
875
1003
|
'texture-compression-astc',
|
|
876
1004
|
];
|
|
877
1005
|
|
|
1006
|
+
// The Standard transmission fragment ABI needs 17 sampled textures across its
|
|
1007
|
+
// material, IBL, clustered-light, and SSAO bindings. Ordinary Standard draws
|
|
1008
|
+
// use the non-transmission variant and remain valid on the WebGPU minimum of
|
|
1009
|
+
// 16; only request the elevated limit when the adapter can provide it.
|
|
1010
|
+
const STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES = 17;
|
|
1011
|
+
|
|
878
1012
|
function deviceOptionsForAdapter(
|
|
879
|
-
adapter: Pick<RhiAdapter, 'features'>,
|
|
880
|
-
|
|
1013
|
+
adapter: Pick<RhiAdapter, 'features' | 'limits'>,
|
|
1014
|
+
options?: Pick<RendererOptions, 'captureGpuTimings'>,
|
|
1015
|
+
):
|
|
1016
|
+
| {
|
|
1017
|
+
requiredFeatures?: GPUFeatureName[];
|
|
1018
|
+
requiredLimits?: GPUDeviceDescriptor['requiredLimits'];
|
|
1019
|
+
}
|
|
1020
|
+
| undefined {
|
|
881
1021
|
const requiredFeatures = COMPRESSION_FEATURES.filter((feature) => adapter.features.has(feature));
|
|
882
|
-
|
|
1022
|
+
const supportsTransmission =
|
|
1023
|
+
(adapter.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
1024
|
+
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
1025
|
+
if (options?.captureGpuTimings === true && adapter.features.has('timestamp-query')) {
|
|
1026
|
+
requiredFeatures.push('timestamp-query');
|
|
1027
|
+
}
|
|
1028
|
+
return {
|
|
1029
|
+
...(requiredFeatures.length > 0 ? { requiredFeatures } : {}),
|
|
1030
|
+
...(supportsTransmission
|
|
1031
|
+
? {
|
|
1032
|
+
requiredLimits: {
|
|
1033
|
+
maxSampledTexturesPerShaderStage: STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES,
|
|
1034
|
+
},
|
|
1035
|
+
}
|
|
1036
|
+
: {}),
|
|
1037
|
+
};
|
|
883
1038
|
}
|
|
884
1039
|
|
|
885
1040
|
/**
|
|
@@ -929,7 +1084,7 @@ async function tryCreateWebGPURenderer(
|
|
|
929
1084
|
const adapter = adapterResult.value;
|
|
930
1085
|
// M4 w28: filter compression features from adapter.features (AC-07). The
|
|
931
1086
|
// same helper is used by recover() below so both paths share one profile.
|
|
932
|
-
const deviceOpts = deviceOptionsForAdapter(adapter);
|
|
1087
|
+
const deviceOpts = deviceOptionsForAdapter(adapter, options);
|
|
933
1088
|
const result = await adapter.requestDevice(deviceOpts);
|
|
934
1089
|
if (!result.ok) {
|
|
935
1090
|
return { kind: 'rhi-err', error: result.error };
|
|
@@ -1059,6 +1214,8 @@ interface WebGPURendererInternals {
|
|
|
1059
1214
|
* has wired the controller; identity stable across grow events.
|
|
1060
1215
|
*/
|
|
1061
1216
|
meshSsboState?: MeshSsboState;
|
|
1217
|
+
/** Device-bound, manifest-owned volumetric utility shader sources. */
|
|
1218
|
+
volumetricFogShaders?: VolumetricFogShaderSources | undefined;
|
|
1062
1219
|
/** Host-owned producer features; created before device-bound assembly starts. */
|
|
1063
1220
|
featureHost?: RenderFeatureHost;
|
|
1064
1221
|
/** App-owned overlay capability read by the typed graph at frame time. */
|
|
@@ -1097,16 +1254,34 @@ async function makeWebGPURenderer(
|
|
|
1097
1254
|
// compile across both paths (charter P4 consistent abstraction +
|
|
1098
1255
|
// architecture-principles #6 idempotency).
|
|
1099
1256
|
let sharedShaderModuleAdapter: ShaderDeviceAdapterInternal | null = null;
|
|
1257
|
+
// Generated feature programs (currently VFX) opt into the handle-first
|
|
1258
|
+
// adapter. Keep it separate from the validated adapter so ordinary render
|
|
1259
|
+
// pipelines retain the compiler-readiness barrier that prevents first-frame
|
|
1260
|
+
// black output on Dawn.
|
|
1261
|
+
let sharedImmediateShaderModuleAdapter: ShaderDeviceAdapterInternal | null = null;
|
|
1100
1262
|
const getShaderModuleAdapter = (): ShaderDeviceAdapterInternal => {
|
|
1101
1263
|
if (sharedShaderModuleAdapter === null) {
|
|
1102
1264
|
sharedShaderModuleAdapter = makeShaderDeviceAdapter(
|
|
1103
1265
|
internals.device,
|
|
1104
1266
|
internals.errorRegistry,
|
|
1105
1267
|
internals.pack.createShaderModule,
|
|
1268
|
+
internals.pack.createShaderModuleImmediate,
|
|
1106
1269
|
);
|
|
1107
1270
|
}
|
|
1108
1271
|
return sharedShaderModuleAdapter;
|
|
1109
1272
|
};
|
|
1273
|
+
const getImmediateShaderModuleAdapter = (): ShaderDeviceAdapterInternal => {
|
|
1274
|
+
if (sharedImmediateShaderModuleAdapter === null) {
|
|
1275
|
+
sharedImmediateShaderModuleAdapter = makeShaderDeviceAdapter(
|
|
1276
|
+
internals.device,
|
|
1277
|
+
internals.errorRegistry,
|
|
1278
|
+
internals.pack.createShaderModule,
|
|
1279
|
+
internals.pack.createShaderModuleImmediate,
|
|
1280
|
+
'immediate',
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
return sharedImmediateShaderModuleAdapter;
|
|
1284
|
+
};
|
|
1110
1285
|
const getShader = (): ShaderCatalog => {
|
|
1111
1286
|
if (shaderInstance === null) {
|
|
1112
1287
|
// feat-20260608-create-app-param-surface-trim / M2 / D-2 q5-A:
|
|
@@ -1326,13 +1501,13 @@ async function makeWebGPURenderer(
|
|
|
1326
1501
|
>();
|
|
1327
1502
|
const preparedMaterialPipelineLayoutCache = new Map<string, PipelineLayout>();
|
|
1328
1503
|
// feat-20260621-learn-render-5-5-parallax M2 / w6 (D-1): per-shader material
|
|
1329
|
-
// BGL + pipeline layout cache.
|
|
1330
|
-
// user-region
|
|
1331
|
-
//
|
|
1332
|
-
//
|
|
1333
|
-
//
|
|
1334
|
-
//
|
|
1335
|
-
//
|
|
1504
|
+
// BGL + pipeline layout cache. Every authored material shader owns the
|
|
1505
|
+
// user-region binding order emitted by its paramSchema, plus the shared
|
|
1506
|
+
// engine-injection suffix. A custom shader with four-or-fewer textures can
|
|
1507
|
+
// still differ from the standard PBR order (for example, baseColorTexture
|
|
1508
|
+
// followed directly by normalTexture), so it cannot safely reuse the
|
|
1509
|
+
// standard material BGL. Built lazily on first request and cached by
|
|
1510
|
+
// shaderId; built-in shader IDs continue to use the boot-time shared layout.
|
|
1336
1511
|
type PerShaderMaterialLayout = {
|
|
1337
1512
|
materialBgl: BindGroupLayout;
|
|
1338
1513
|
pipelineLayout: PipelineLayout;
|
|
@@ -1343,6 +1518,16 @@ async function makeWebGPURenderer(
|
|
|
1343
1518
|
readonly layout: PerShaderMaterialLayout | null;
|
|
1344
1519
|
};
|
|
1345
1520
|
const perShaderMaterialLayoutCache = new Map<string, PerShaderMaterialLayoutCacheEntry>();
|
|
1521
|
+
const isEngineOwnedMaterialShader = (materialShaderId: string): boolean =>
|
|
1522
|
+
materialShaderId === 'forgeax::default-unlit' ||
|
|
1523
|
+
materialShaderId === 'forgeax::default-shadow-caster' ||
|
|
1524
|
+
materialShaderId === 'forgeax::sprite' ||
|
|
1525
|
+
materialShaderId === 'forgeax::sprite-lit' ||
|
|
1526
|
+
materialShaderId === 'forgeax::default-sprite' ||
|
|
1527
|
+
materialShaderId === 'forgeax::msdf-text' ||
|
|
1528
|
+
materialShaderId === 'forgeax::default-standard-pbr' ||
|
|
1529
|
+
materialShaderId === 'forgeax::pbr-skin' ||
|
|
1530
|
+
materialShaderId === 'forgeax::default-standard-pbr-skin';
|
|
1346
1531
|
// Material binding contracts are derived from WGSL source, but the lookup
|
|
1347
1532
|
// is also used by the per-submesh bind-group path. Cache the derived value
|
|
1348
1533
|
// per renderer so a frame does not re-run the comment stripping and regex
|
|
@@ -1477,7 +1662,9 @@ async function makeWebGPURenderer(
|
|
|
1477
1662
|
if (cached !== undefined) return cached;
|
|
1478
1663
|
if (pipelineState === null) return null;
|
|
1479
1664
|
const materialBgl =
|
|
1480
|
-
|
|
1665
|
+
(isEngineOwnedMaterialShader(materialShaderId)
|
|
1666
|
+
? undefined
|
|
1667
|
+
: getOrBuildPerShaderMaterialLayout(materialShaderId)?.materialBgl) ??
|
|
1481
1668
|
pipelineState.materialBindGroupLayout;
|
|
1482
1669
|
const result = internals.device.createPipelineLayout({
|
|
1483
1670
|
label: `prepared-material-pl-${materialShaderId}`,
|
|
@@ -1490,15 +1677,15 @@ async function makeWebGPURenderer(
|
|
|
1490
1677
|
preparedMaterialPipelineLayoutCache.set(materialShaderId, result.value);
|
|
1491
1678
|
return result.value;
|
|
1492
1679
|
};
|
|
1493
|
-
// Returns the per-shader { materialBgl, pipelineLayout } for
|
|
1494
|
-
//
|
|
1495
|
-
//
|
|
1496
|
-
//
|
|
1497
|
-
//
|
|
1498
|
-
// 'pbr-material-merged' + materialParamSchema) used by buildPbrPipelineLayouts.
|
|
1680
|
+
// Returns the per-shader { materialBgl, pipelineLayout } for an authored
|
|
1681
|
+
// shader, or null when it is unregistered / pipelineState is not ready.
|
|
1682
|
+
// Pure-ish: builds + caches on miss via the same
|
|
1683
|
+
// buildBindGroupLayoutDescriptor SSOT (kind 'pbr-material-merged' +
|
|
1684
|
+
// materialParamSchema) used by buildPbrPipelineLayouts.
|
|
1499
1685
|
const getOrBuildPerShaderMaterialLayout = (
|
|
1500
1686
|
materialShaderId: string,
|
|
1501
1687
|
): PerShaderMaterialLayout | null => {
|
|
1688
|
+
if (isEngineOwnedMaterialShader(materialShaderId)) return null;
|
|
1502
1689
|
const lookup = getShader().findMaterialArtifact(materialShaderId);
|
|
1503
1690
|
if (!lookup.ok) return null;
|
|
1504
1691
|
const cached = perShaderMaterialLayoutCache.get(materialShaderId);
|
|
@@ -1507,9 +1694,11 @@ async function makeWebGPURenderer(
|
|
|
1507
1694
|
}
|
|
1508
1695
|
if (pipelineState === null) return null;
|
|
1509
1696
|
const paramSchema = lookup.value.paramSchema;
|
|
1510
|
-
//
|
|
1511
|
-
//
|
|
1512
|
-
|
|
1697
|
+
// Reuse the shared BGL only when the complete derived resource shape is a
|
|
1698
|
+
// canonical prefix. Field names alone are not a layout contract: a
|
|
1699
|
+
// texture3d/array, comparison sampler, or storage buffer must never be
|
|
1700
|
+
// paired with the PBR texture2d/filtering descriptor by accident.
|
|
1701
|
+
if (isSharedMaterialUserRegionCompatible(paramSchema)) {
|
|
1513
1702
|
perShaderMaterialLayoutCache.set(materialShaderId, {
|
|
1514
1703
|
source: lookup.value.source,
|
|
1515
1704
|
paramSchema,
|
|
@@ -1563,6 +1752,8 @@ async function makeWebGPURenderer(
|
|
|
1563
1752
|
[],
|
|
1564
1753
|
internals.device.caps.backendKind,
|
|
1565
1754
|
internals.device.caps.storageBuffer,
|
|
1755
|
+
(internals.device.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
1756
|
+
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES,
|
|
1566
1757
|
);
|
|
1567
1758
|
}
|
|
1568
1759
|
const variants = manifestEntry.variants;
|
|
@@ -1571,13 +1762,17 @@ async function makeWebGPURenderer(
|
|
|
1571
1762
|
byRequest = new Map();
|
|
1572
1763
|
materialShaderVariantResolutionCache.set(variants, byRequest);
|
|
1573
1764
|
}
|
|
1574
|
-
const
|
|
1765
|
+
const projectorAvailable =
|
|
1766
|
+
(internals.device.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
1767
|
+
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
1768
|
+
const cacheKey = `${requestedVariantSet ?? '\u0000'}|${internals.device.caps.backendKind}|${internals.device.caps.storageBuffer ? '1' : '0'}|${projectorAvailable ? '1' : '0'}`;
|
|
1575
1769
|
if (byRequest.has(cacheKey)) return byRequest.get(cacheKey);
|
|
1576
1770
|
const resolved = resolveMaterialShaderVariantSet(
|
|
1577
1771
|
requestedVariantSet,
|
|
1578
1772
|
variants,
|
|
1579
1773
|
internals.device.caps.backendKind,
|
|
1580
1774
|
internals.device.caps.storageBuffer,
|
|
1775
|
+
projectorAvailable,
|
|
1581
1776
|
);
|
|
1582
1777
|
byRequest.set(cacheKey, resolved);
|
|
1583
1778
|
return resolved;
|
|
@@ -1603,9 +1798,11 @@ async function makeWebGPURenderer(
|
|
|
1603
1798
|
// `internals.device` / `internals.pack` are read live, so a rebuild after a
|
|
1604
1799
|
// device swap compiles against the new device.
|
|
1605
1800
|
let tonemapRegistered = false;
|
|
1606
|
-
|
|
1801
|
+
let temporalPostProcessesRegistered = false;
|
|
1802
|
+
const buildPipeline = (scope: DeviceScope = activeDeviceScope): Promise<PipelineState> =>
|
|
1607
1803
|
buildReadyWebGPU(
|
|
1608
1804
|
internals.device,
|
|
1805
|
+
scope,
|
|
1609
1806
|
getShader,
|
|
1610
1807
|
gpuStore,
|
|
1611
1808
|
internals.pack.createShaderModule,
|
|
@@ -1644,13 +1841,34 @@ async function makeWebGPURenderer(
|
|
|
1644
1841
|
// tonemap into this channel each frame (render-system-extract.ts w13).
|
|
1645
1842
|
(source: string) => {
|
|
1646
1843
|
if (tonemapRegistered) return;
|
|
1647
|
-
renderSystem.registerBuiltinPostProcess(
|
|
1844
|
+
renderSystem.registerBuiltinPostProcess(STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, {
|
|
1648
1845
|
source,
|
|
1649
1846
|
params: { byteSize: 16, defaultValue: new Uint8Array(16) },
|
|
1650
1847
|
reads: ['hdrColor'],
|
|
1651
1848
|
});
|
|
1652
1849
|
tonemapRegistered = true;
|
|
1653
1850
|
},
|
|
1851
|
+
(entries: { readonly motionBlur?: string; readonly taaResolve?: string }) => {
|
|
1852
|
+
if (temporalPostProcessesRegistered) return;
|
|
1853
|
+
if (entries.motionBlur !== undefined) {
|
|
1854
|
+
renderSystem.registerBuiltinPostProcess('forgeax.motion-blur', {
|
|
1855
|
+
source: entries.motionBlur,
|
|
1856
|
+
params: { byteSize: 16, defaultValue: new Uint8Array(16) },
|
|
1857
|
+
reads: ['scene-color', 'scene-temporal'],
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
if (entries.taaResolve !== undefined) {
|
|
1861
|
+
renderSystem.registerBuiltinPostProcess('forgeax.taa-resolve', {
|
|
1862
|
+
source: entries.taaResolve,
|
|
1863
|
+
params: { byteSize: 16, defaultValue: new Uint8Array(16) },
|
|
1864
|
+
reads: ['scene-color', 'scene-temporal', 'taa-history-color', 'taa-history-temporal'],
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
temporalPostProcessesRegistered = true;
|
|
1868
|
+
},
|
|
1869
|
+
(sources: VolumetricFogShaderSources) => {
|
|
1870
|
+
internals.volumetricFogShaders = sources;
|
|
1871
|
+
},
|
|
1654
1872
|
);
|
|
1655
1873
|
const ready: Promise<Result<void, RhiError>> = buildPipeline().then(
|
|
1656
1874
|
(state): Result<void, RhiError> => {
|
|
@@ -1696,6 +1914,8 @@ async function makeWebGPURenderer(
|
|
|
1696
1914
|
pbrManifestEntry,
|
|
1697
1915
|
internals.device.caps.storageBuffer,
|
|
1698
1916
|
variantSet,
|
|
1917
|
+
(internals.device.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
1918
|
+
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES,
|
|
1699
1919
|
);
|
|
1700
1920
|
if (noColorVariant !== undefined && noColorVariant.composedWgsl !== standardEntry.wgsl) {
|
|
1701
1921
|
return { ...standardEntry, wgsl: noColorVariant.composedWgsl };
|
|
@@ -1730,6 +1950,8 @@ async function makeWebGPURenderer(
|
|
|
1730
1950
|
meshAttributes?: VertexAttributeMap,
|
|
1731
1951
|
vertexLayout?: string,
|
|
1732
1952
|
vertexLayoutProjection?: VertexLayoutProjection,
|
|
1953
|
+
shaderModuleMode?: RenderFeatureShaderModuleMode,
|
|
1954
|
+
layoutKindOverride?: LayoutKind,
|
|
1733
1955
|
) => {
|
|
1734
1956
|
// bug-20260611: derive LayoutKind from materialShaderId (caller-driven)
|
|
1735
1957
|
// so the selector body stays free of literal shader-id strings (AC-09).
|
|
@@ -1741,17 +1963,17 @@ async function makeWebGPURenderer(
|
|
|
1741
1963
|
// through the HDRP layout branch of the selector — sprite shaders have
|
|
1742
1964
|
// no HDRP variant, plan-strategy R-1' guard.
|
|
1743
1965
|
const layoutKind: LayoutKind | undefined =
|
|
1744
|
-
|
|
1966
|
+
layoutKindOverride ??
|
|
1967
|
+
(materialShaderId === SKIN_MATERIAL_SHADER_ID ||
|
|
1968
|
+
isSkinnedShadowCasterVariant(materialShaderId, variantSet)
|
|
1745
1969
|
? 'pbr-skin'
|
|
1746
1970
|
: materialShaderId === 'forgeax::sprite' || materialShaderId === 'forgeax::sprite-lit'
|
|
1747
1971
|
? 'sprite-urp'
|
|
1748
1972
|
: materialShaderId === 'forgeax::default-unlit'
|
|
1749
1973
|
? 'unlit-urp'
|
|
1750
|
-
: undefined;
|
|
1974
|
+
: undefined);
|
|
1751
1975
|
const usesSharedBootMaterialLayout =
|
|
1752
|
-
materialShaderId
|
|
1753
|
-
materialShaderId === 'forgeax::default-unlit' ||
|
|
1754
|
-
materialShaderId === SKIN_MATERIAL_SHADER_ID;
|
|
1976
|
+
materialShaderId !== undefined && isEngineOwnedMaterialShader(materialShaderId);
|
|
1755
1977
|
// Built-in PBR/unlit/skin layouts are assembled at boot. Re-deriving a
|
|
1756
1978
|
// per-shader layout from the expanded PBR schema would bypass the HDRP
|
|
1757
1979
|
// group(2) selector and bind the URP mesh BGL to the cluster pipeline.
|
|
@@ -1759,12 +1981,11 @@ async function makeWebGPURenderer(
|
|
|
1759
1981
|
!usesSharedBootMaterialLayout && layoutKind !== 'pbr-skin' && materialShaderId !== undefined
|
|
1760
1982
|
? getOrBuildPerShaderMaterialLayout(materialShaderId)
|
|
1761
1983
|
: null;
|
|
1762
|
-
// feat-20260621-learn-render-5-5-parallax M2 / w6 (D-1):
|
|
1763
|
-
//
|
|
1764
|
-
//
|
|
1765
|
-
//
|
|
1766
|
-
//
|
|
1767
|
-
// (D-2 byte-for-byte).
|
|
1984
|
+
// feat-20260621-learn-render-5-5-parallax M2 / w6 (D-1): an authored
|
|
1985
|
+
// shader owns a per-shader pipeline layout derived from its paramSchema,
|
|
1986
|
+
// because its compact texture order may differ from standard PBR even
|
|
1987
|
+
// when it declares only a few textures. Skin / HDRP keep their dedicated
|
|
1988
|
+
// layouts; built-in material IDs remain on the shared boot-time layout.
|
|
1768
1989
|
const bindingContract =
|
|
1769
1990
|
materialShaderId === undefined
|
|
1770
1991
|
? 'render-material'
|
|
@@ -1900,9 +2121,13 @@ async function makeWebGPURenderer(
|
|
|
1900
2121
|
] satisfies readonly GPUVertexBufferLayout[]);
|
|
1901
2122
|
return {
|
|
1902
2123
|
device: internals.device,
|
|
1903
|
-
shaderModuleFactory:
|
|
2124
|
+
shaderModuleFactory:
|
|
2125
|
+
shaderModuleMode === 'immediate'
|
|
2126
|
+
? getImmediateShaderModuleAdapter()
|
|
2127
|
+
: getShaderModuleAdapter(),
|
|
1904
2128
|
pipelineLayout,
|
|
1905
2129
|
vertexBuffers,
|
|
2130
|
+
...(layoutKind === 'pbr-skin' ? { layoutKind } : {}),
|
|
1906
2131
|
};
|
|
1907
2132
|
};
|
|
1908
2133
|
/**
|
|
@@ -1960,6 +2185,8 @@ async function makeWebGPURenderer(
|
|
|
1960
2185
|
depthFormatOverride?: GPUTextureFormat | null,
|
|
1961
2186
|
vertexLayout?: string,
|
|
1962
2187
|
vertexLayoutProjection?: VertexLayoutProjection,
|
|
2188
|
+
shaderModuleMode?: RenderFeatureShaderModuleMode,
|
|
2189
|
+
layoutKindOverride?: LayoutKind,
|
|
1963
2190
|
): RenderPipeline | null => {
|
|
1964
2191
|
const ctx = buildPipelineContext(
|
|
1965
2192
|
variantSet,
|
|
@@ -1967,6 +2194,8 @@ async function makeWebGPURenderer(
|
|
|
1967
2194
|
meshAttributes,
|
|
1968
2195
|
vertexLayout,
|
|
1969
2196
|
vertexLayoutProjection,
|
|
2197
|
+
shaderModuleMode,
|
|
2198
|
+
layoutKindOverride,
|
|
1970
2199
|
);
|
|
1971
2200
|
if (ctx === null) return null;
|
|
1972
2201
|
// bug-20260612: route the LDR color format through pipelineState so the
|
|
@@ -1976,6 +2205,11 @@ async function makeWebGPURenderer(
|
|
|
1976
2205
|
// the explicit null check).
|
|
1977
2206
|
if (pipelineState === null) return null;
|
|
1978
2207
|
const ldrColorFormat = colorFormatOverride ?? pipelineState.colorAttachmentFormat;
|
|
2208
|
+
const fragmentEntry =
|
|
2209
|
+
isHdr &&
|
|
2210
|
+
(materialShaderId === 'forgeax::sprite' || materialShaderId === 'forgeax::sprite-lit')
|
|
2211
|
+
? 'fs_main_hdr'
|
|
2212
|
+
: undefined;
|
|
1979
2213
|
const built = buildPipelineForMaterialShader(
|
|
1980
2214
|
cacheKey,
|
|
1981
2215
|
// The catalog entry is projected into the material-shader entry shape.
|
|
@@ -2009,7 +2243,7 @@ async function makeWebGPURenderer(
|
|
|
2009
2243
|
}
|
|
2010
2244
|
: undefined,
|
|
2011
2245
|
undefined, // vertexEntry — default vs_main
|
|
2012
|
-
|
|
2246
|
+
fragmentEntry,
|
|
2013
2247
|
undefined, // defines — none
|
|
2014
2248
|
passKind,
|
|
2015
2249
|
sampleCount,
|
|
@@ -2068,6 +2302,8 @@ async function makeWebGPURenderer(
|
|
|
2068
2302
|
depthFormatOverride?: GPUTextureFormat | null,
|
|
2069
2303
|
vertexLayout?: string,
|
|
2070
2304
|
vertexLayoutProjection?: VertexLayoutProjection,
|
|
2305
|
+
shaderModuleMode: RenderFeatureShaderModuleMode = 'validated',
|
|
2306
|
+
layoutKindOverride?: LayoutKind,
|
|
2071
2307
|
): RenderPipeline | null => {
|
|
2072
2308
|
// WebGL2 is a downlevel backend even when the record stage asks for the
|
|
2073
2309
|
// native-capability variant string. Resolve the variant axes at this
|
|
@@ -2082,7 +2318,14 @@ async function makeWebGPURenderer(
|
|
|
2082
2318
|
// (stored in materialShaderUvSetCounts during prepareMaterialShaders).
|
|
2083
2319
|
const resolvedUvSetCount = shaderUvSetCount ?? materialShaderUvSetCounts.get(materialShaderId);
|
|
2084
2320
|
const manifestEntry = findMaterialShaderManifestEntry(materialShaderId);
|
|
2085
|
-
|
|
2321
|
+
// Sprite's omitted request is the boot-selected default artifact. The
|
|
2322
|
+
// explicit empty key remains the PER_INSTANCE_REGION=true request used by
|
|
2323
|
+
// SpriteInstances, so only that path is capability-rewritten here.
|
|
2324
|
+
const resolvedVariantSet =
|
|
2325
|
+
variantSet === undefined &&
|
|
2326
|
+
(materialShaderId === 'forgeax::sprite' || materialShaderId === 'forgeax::sprite-lit')
|
|
2327
|
+
? undefined
|
|
2328
|
+
: resolveCachedMaterialShaderVariantSet(variantSet, manifestEntry);
|
|
2086
2329
|
// The record stage also composes engine capability axes for user shaders.
|
|
2087
2330
|
// Keep only manifest-declared axes for non-engine material IDs: a plain
|
|
2088
2331
|
// custom shader must not collapse that request to '' because '' is the
|
|
@@ -2227,6 +2470,7 @@ async function makeWebGPURenderer(
|
|
|
2227
2470
|
depthFormatOverride,
|
|
2228
2471
|
vertexLayout,
|
|
2229
2472
|
vertexLayoutProjection,
|
|
2473
|
+
shaderModuleMode,
|
|
2230
2474
|
);
|
|
2231
2475
|
}
|
|
2232
2476
|
const pbrEntry = findStandardPbrEntry(effectiveVariantSet);
|
|
@@ -2280,6 +2524,7 @@ async function makeWebGPURenderer(
|
|
|
2280
2524
|
depthFormatOverride,
|
|
2281
2525
|
vertexLayout,
|
|
2282
2526
|
vertexLayoutProjection,
|
|
2527
|
+
shaderModuleMode,
|
|
2283
2528
|
);
|
|
2284
2529
|
}
|
|
2285
2530
|
// feat-20260609 M4 / w31: resolve variant WGSL from manifest when
|
|
@@ -2370,6 +2615,8 @@ async function makeWebGPURenderer(
|
|
|
2370
2615
|
depthFormatOverride,
|
|
2371
2616
|
vertexLayout,
|
|
2372
2617
|
vertexLayoutProjection,
|
|
2618
|
+
shaderModuleMode,
|
|
2619
|
+
layoutKindOverride,
|
|
2373
2620
|
);
|
|
2374
2621
|
};
|
|
2375
2622
|
const getParamSchema = (materialShaderId: string) => {
|
|
@@ -2378,10 +2625,11 @@ async function makeWebGPURenderer(
|
|
|
2378
2625
|
};
|
|
2379
2626
|
// feat-20260621-learn-render-5-5-parallax M2 / w6 (D-1): expose the per-shader
|
|
2380
2627
|
// material BGL so the record stage creates the material bind group against
|
|
2381
|
-
// the matching layout
|
|
2382
|
-
//
|
|
2383
|
-
//
|
|
2628
|
+
// the matching paramSchema-derived layout. Returns undefined only for
|
|
2629
|
+
// group-0 / view-only contracts or an unavailable shader layout; built-in
|
|
2630
|
+
// material IDs continue to resolve through the shared layout path.
|
|
2384
2631
|
const getMaterialBindGroupLayout = (materialShaderId: string): BindGroupLayout | undefined => {
|
|
2632
|
+
if (isEngineOwnedMaterialShader(materialShaderId)) return undefined;
|
|
2385
2633
|
const contract = getCachedMaterialShaderBindingContract(materialShaderId);
|
|
2386
2634
|
if (contract === 'group-0') {
|
|
2387
2635
|
return getOrBuildGroup0MaterialLayout()?.materialBgl;
|
|
@@ -2458,7 +2706,11 @@ async function makeWebGPURenderer(
|
|
|
2458
2706
|
fragment: {
|
|
2459
2707
|
module: moduleResult.value,
|
|
2460
2708
|
entryPoint: 'fs_main',
|
|
2461
|
-
targets:
|
|
2709
|
+
targets:
|
|
2710
|
+
entry.source.includes('@location(1) temporal') &&
|
|
2711
|
+
entry.source.includes('TaaResolveOutput')
|
|
2712
|
+
? [{ format: colorFormat }, { format: colorFormat }]
|
|
2713
|
+
: [{ format: colorFormat }],
|
|
2462
2714
|
},
|
|
2463
2715
|
primitive: { topology: 'triangle-list', cullMode: 'none', frontFace: 'ccw' },
|
|
2464
2716
|
depthStencil: undefined,
|
|
@@ -2487,6 +2739,10 @@ async function makeWebGPURenderer(
|
|
|
2487
2739
|
shaderModuleFactory: {
|
|
2488
2740
|
createShaderModule: (descriptor) => getShaderModuleAdapter().createShaderModule(descriptor),
|
|
2489
2741
|
},
|
|
2742
|
+
immediateShaderModuleFactory: {
|
|
2743
|
+
createShaderModule: (descriptor) =>
|
|
2744
|
+
getImmediateShaderModuleAdapter().createShaderModule(descriptor),
|
|
2745
|
+
},
|
|
2490
2746
|
profiler: internals.options?.profiler,
|
|
2491
2747
|
// feat-20260622-s5 M3 / w17: device + context read live off `internals` via
|
|
2492
2748
|
// getters so the recover() rebuild (which swaps internals.device /
|
|
@@ -2499,6 +2755,9 @@ async function makeWebGPURenderer(
|
|
|
2499
2755
|
get deviceScope() {
|
|
2500
2756
|
return activeDeviceScope;
|
|
2501
2757
|
},
|
|
2758
|
+
invalidateShaderModule: (label: string) => {
|
|
2759
|
+
sharedShaderModuleAdapter?.invalidateModule(label);
|
|
2760
|
+
},
|
|
2502
2761
|
get context() {
|
|
2503
2762
|
return internals.context;
|
|
2504
2763
|
},
|
|
@@ -2535,7 +2794,22 @@ async function makeWebGPURenderer(
|
|
|
2535
2794
|
get meshSsboState() {
|
|
2536
2795
|
return internals.meshSsboState;
|
|
2537
2796
|
},
|
|
2797
|
+
get volumetricFogShaders() {
|
|
2798
|
+
return internals.volumetricFogShaders;
|
|
2799
|
+
},
|
|
2538
2800
|
});
|
|
2801
|
+
if (
|
|
2802
|
+
internals.device.caps.backendKind === 'null' &&
|
|
2803
|
+
Array.from(getShader().entries()).length === 0
|
|
2804
|
+
) {
|
|
2805
|
+
// RhiNull validates graph topology only; keep the built-in identity
|
|
2806
|
+
// registered so structural frames do not fail before graph inspection.
|
|
2807
|
+
renderSystem.registerBuiltinPostProcess(STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, {
|
|
2808
|
+
source: '',
|
|
2809
|
+
params: { byteSize: 16, defaultValue: new Uint8Array(16) },
|
|
2810
|
+
reads: ['hdrColor'],
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2539
2813
|
// The Standard pipeline is the sole built-in graph owner. Feature plans are
|
|
2540
2814
|
// projected into that graph directly; no second post-process registry is
|
|
2541
2815
|
// installed for fullscreen producers.
|
|
@@ -2548,7 +2822,8 @@ async function makeWebGPURenderer(
|
|
|
2548
2822
|
const transformReleases = new Map<World, () => void>();
|
|
2549
2823
|
const attachmentOwner = {};
|
|
2550
2824
|
let frameId = 0;
|
|
2551
|
-
|
|
2825
|
+
const issuedReceipts = new WeakSet<FrameReceipt>();
|
|
2826
|
+
const receiptTimings = new WeakMap<FrameReceipt, Promise<VolumeTimingObservation>>();
|
|
2552
2827
|
let surfaceReleased = false;
|
|
2553
2828
|
|
|
2554
2829
|
const renderer: RendererAssemblyImplementation = {
|
|
@@ -2596,6 +2871,23 @@ async function makeWebGPURenderer(
|
|
|
2596
2871
|
: health.reason === 'internal-fault' || !readySettled
|
|
2597
2872
|
? 'faulted'
|
|
2598
2873
|
: 'alive';
|
|
2874
|
+
const surfaceStorage = pipelineState?.format ?? 'rgba8unorm';
|
|
2875
|
+
const surfaceDisplay = pipelineState?.colorAttachmentFormat ?? 'rgba8unorm';
|
|
2876
|
+
const observationId = `${STANDARD_OUTPUT_TRANSFORM_FEATURE_ID}:frame-${frameId}`;
|
|
2877
|
+
const outputInspection = projectRendererOutputInspection({
|
|
2878
|
+
graph: renderSystem.perFrameGraphInfo,
|
|
2879
|
+
surfaceAvailable: !surfaceReleased && pipelineState !== null,
|
|
2880
|
+
surfaceStorage,
|
|
2881
|
+
surfaceDisplay,
|
|
2882
|
+
...(pipelineState?.surfaceProfile === undefined
|
|
2883
|
+
? {}
|
|
2884
|
+
: { surfaceProfile: pipelineState.surfaceProfile }),
|
|
2885
|
+
rgba16floatRenderable: internals.device.caps.rgba16floatRenderable,
|
|
2886
|
+
...(internals.context?.presentationProof === undefined
|
|
2887
|
+
? {}
|
|
2888
|
+
: { presentationProof: internals.context.presentationProof }),
|
|
2889
|
+
});
|
|
2890
|
+
const volumetricFog = renderSystem.volumetricFog;
|
|
2599
2891
|
return Object.freeze({
|
|
2600
2892
|
state,
|
|
2601
2893
|
surface: surfaceReleased ? 'released' : 'available',
|
|
@@ -2609,9 +2901,22 @@ async function makeWebGPURenderer(
|
|
|
2609
2901
|
(internals.featureHost?.features ?? []).map((feature) => feature.identity),
|
|
2610
2902
|
),
|
|
2611
2903
|
featureDiagnostics: internals.featureHost?.diagnostics() ?? Object.freeze([]),
|
|
2904
|
+
volumetricFog,
|
|
2612
2905
|
frustumStats: Object.freeze({ ...renderSystem.frustumStats }),
|
|
2613
2906
|
visibilityStats: Object.freeze({ ...renderSystem.visibilityStats }),
|
|
2614
2907
|
renderScene: renderSystem.renderScene,
|
|
2908
|
+
environment: renderSystem.environment,
|
|
2909
|
+
temporal: renderSystem.temporal,
|
|
2910
|
+
bloom: renderSystem.bloom,
|
|
2911
|
+
...(renderSystem.temporalTargetInspection === undefined
|
|
2912
|
+
? {}
|
|
2913
|
+
: { temporalTarget: Object.freeze(renderSystem.temporalTargetInspection) }),
|
|
2914
|
+
...(renderSystem.motionBlurInspection === undefined
|
|
2915
|
+
? {}
|
|
2916
|
+
: { motionBlur: Object.freeze(renderSystem.motionBlurInspection) }),
|
|
2917
|
+
...(renderSystem.transmission === undefined
|
|
2918
|
+
? {}
|
|
2919
|
+
: { transmission: renderSystem.transmission }),
|
|
2615
2920
|
meshMaterialBindings: Object.freeze(
|
|
2616
2921
|
renderSystem.meshMaterialBindings.map((observation) =>
|
|
2617
2922
|
Object.freeze({
|
|
@@ -2630,6 +2935,22 @@ async function makeWebGPURenderer(
|
|
|
2630
2935
|
createBindGroup: renderSystem.bindGroupCounts.createBindGroup,
|
|
2631
2936
|
keys: Object.freeze([...renderSystem.bindGroupCounts.keys]),
|
|
2632
2937
|
}),
|
|
2938
|
+
...outputInspection,
|
|
2939
|
+
observation: Object.freeze({
|
|
2940
|
+
observationId,
|
|
2941
|
+
frameId,
|
|
2942
|
+
...(renderSystem.lastSuccessfulCameraAntialias === undefined
|
|
2943
|
+
? {}
|
|
2944
|
+
: { antialias: renderSystem.lastSuccessfulCameraAntialias }),
|
|
2945
|
+
...(outputInspection.surfaceProfile === undefined
|
|
2946
|
+
? {}
|
|
2947
|
+
: { surfaceProfile: outputInspection.surfaceProfile }),
|
|
2948
|
+
rgba16floatRenderable: internals.device.caps.rgba16floatRenderable,
|
|
2949
|
+
passNames: Object.freeze([...outputInspection.graphPassNames]),
|
|
2950
|
+
...(outputInspection.standardOutputColor === undefined
|
|
2951
|
+
? {}
|
|
2952
|
+
: { standardOutputColor: Object.freeze({ ...outputInspection.standardOutputColor }) }),
|
|
2953
|
+
}),
|
|
2633
2954
|
});
|
|
2634
2955
|
},
|
|
2635
2956
|
get device(): RhiDevice {
|
|
@@ -2720,6 +3041,12 @@ async function makeWebGPURenderer(
|
|
|
2720
3041
|
observeCurrentFrame(options: FrameObservationOptions) {
|
|
2721
3042
|
return renderSystem.observeCurrentFrame(options);
|
|
2722
3043
|
},
|
|
3044
|
+
getCurrentGraphTarget(name: string) {
|
|
3045
|
+
return renderSystem.getCurrentGraphTarget(name);
|
|
3046
|
+
},
|
|
3047
|
+
requestGraphTargetCapture(request) {
|
|
3048
|
+
renderSystem.requestGraphTargetCapture(request);
|
|
3049
|
+
},
|
|
2723
3050
|
get frustumStats() {
|
|
2724
3051
|
return renderSystem.frustumStats;
|
|
2725
3052
|
},
|
|
@@ -2991,14 +3318,34 @@ async function makeWebGPURenderer(
|
|
|
2991
3318
|
// Configure context lazily on first draw (D-S1 single-point
|
|
2992
3319
|
// exemption): GPUCanvasContext.configure({device}) needs a raw
|
|
2993
3320
|
// The canvas context is configured through the RHI device scope.
|
|
2994
|
-
ensureContextConfigured(
|
|
3321
|
+
const contextConfigured = ensureContextConfigured(
|
|
3322
|
+
internals,
|
|
3323
|
+
pipelineState,
|
|
3324
|
+
internals.errorRegistry,
|
|
3325
|
+
);
|
|
3326
|
+
if (!contextConfigured.ok) return contextConfigured;
|
|
2995
3327
|
// w24 — facade-level try/catch produces Result.err on unexpected throw
|
|
2996
3328
|
// (D-P6 dual-channel preserved: per-stage RhiError continues to fan out
|
|
2997
3329
|
// through onError separately; the facade Result is the synchronous
|
|
2998
3330
|
// summary AI users can ignore or branch on).
|
|
2999
3331
|
try {
|
|
3000
|
-
const
|
|
3332
|
+
const timingRequested =
|
|
3333
|
+
isFrameRequest &&
|
|
3334
|
+
(frameRequest?.profileFrame !== undefined ||
|
|
3335
|
+
internals.options?.captureGpuTimings === true);
|
|
3336
|
+
let timingCapture: GpuTimingCapture | undefined;
|
|
3337
|
+
let timingUnavailableReason = timingRequested
|
|
3338
|
+
? 'timestamp capture is unavailable on this device'
|
|
3339
|
+
: 'timestamp capture was not requested for this frame';
|
|
3340
|
+
if (timingRequested) {
|
|
3341
|
+
const createdTiming = GpuTimingCapture.create(internals.device);
|
|
3342
|
+
if (createdTiming.ok) timingCapture = createdTiming.value;
|
|
3343
|
+
else
|
|
3344
|
+
timingUnavailableReason = `timestamp capture setup failed: ${createdTiming.error.code}`;
|
|
3345
|
+
}
|
|
3346
|
+
const submitted = renderSystem.draw(worlds, drawOptions, renderReadLeases, timingCapture);
|
|
3001
3347
|
if (!submitted) {
|
|
3348
|
+
timingCapture?.discard();
|
|
3002
3349
|
return err(
|
|
3003
3350
|
new RendererContractFailureError(
|
|
3004
3351
|
'draw',
|
|
@@ -3028,7 +3375,15 @@ async function makeWebGPURenderer(
|
|
|
3028
3375
|
deviceGeneration: receiptGeneration,
|
|
3029
3376
|
completed,
|
|
3030
3377
|
});
|
|
3031
|
-
|
|
3378
|
+
receiptTimings.set(
|
|
3379
|
+
receipt,
|
|
3380
|
+
timingCapture?.observation() ??
|
|
3381
|
+
Promise.resolve({
|
|
3382
|
+
status: 'unavailable',
|
|
3383
|
+
reason: timingUnavailableReason,
|
|
3384
|
+
}),
|
|
3385
|
+
);
|
|
3386
|
+
issuedReceipts.add(receipt);
|
|
3032
3387
|
return ok(receipt);
|
|
3033
3388
|
}
|
|
3034
3389
|
return ok(undefined);
|
|
@@ -3053,10 +3408,8 @@ async function makeWebGPURenderer(
|
|
|
3053
3408
|
request: FrameObservationRequest,
|
|
3054
3409
|
): Promise<RenderResult<FrameReceiptObservation, RenderError>> {
|
|
3055
3410
|
if (
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
latestReceipt.frameId !== receipt.frameId ||
|
|
3059
|
-
latestReceipt.deviceGeneration !== receipt.deviceGeneration
|
|
3411
|
+
!issuedReceipts.has(receipt) ||
|
|
3412
|
+
receipt.deviceGeneration !== activeDeviceScope.generation
|
|
3060
3413
|
) {
|
|
3061
3414
|
const currentGeneration = activeDeviceScope.generation;
|
|
3062
3415
|
return err(
|
|
@@ -3079,11 +3432,19 @@ async function makeWebGPURenderer(
|
|
|
3079
3432
|
}),
|
|
3080
3433
|
);
|
|
3081
3434
|
}
|
|
3435
|
+
const timings = request.include.includes('timings')
|
|
3436
|
+
? await (receiptTimings.get(receipt) ??
|
|
3437
|
+
Promise.resolve({
|
|
3438
|
+
status: 'unavailable',
|
|
3439
|
+
reason: 'timing receipt was not retained',
|
|
3440
|
+
}))
|
|
3441
|
+
: undefined;
|
|
3082
3442
|
return ok(
|
|
3083
3443
|
Object.freeze({
|
|
3084
3444
|
frameId: receipt.frameId,
|
|
3085
3445
|
deviceGeneration: receipt.deviceGeneration,
|
|
3086
3446
|
include: Object.freeze([...request.include]),
|
|
3447
|
+
...(timings === undefined ? {} : { timings }),
|
|
3087
3448
|
}),
|
|
3088
3449
|
);
|
|
3089
3450
|
},
|
|
@@ -3159,13 +3520,6 @@ async function makeWebGPURenderer(
|
|
|
3159
3520
|
if (disposed) return ok(undefined);
|
|
3160
3521
|
disposed = true;
|
|
3161
3522
|
const cleanupFailures: RendererOperationCause[] = [];
|
|
3162
|
-
try {
|
|
3163
|
-
activeDeviceScope.dispose();
|
|
3164
|
-
} catch (cause) {
|
|
3165
|
-
const error = wrapDisposeError(cause, 'activeDeviceScope.dispose');
|
|
3166
|
-
cleanupFailures.push(error);
|
|
3167
|
-
internals.errorRegistry.fire(error);
|
|
3168
|
-
}
|
|
3169
3523
|
for (const world of attachedWorlds) {
|
|
3170
3524
|
try {
|
|
3171
3525
|
const derivedSystemName = derivedSystemNames.get(world);
|
|
@@ -3247,6 +3601,18 @@ async function makeWebGPURenderer(
|
|
|
3247
3601
|
cleanupFailures.push(error);
|
|
3248
3602
|
internals.errorRegistry.fire(error);
|
|
3249
3603
|
}
|
|
3604
|
+
// Retire assembly-owned device resources only after the surface and
|
|
3605
|
+
// renderer-owned GPU pools have been released. The wgpu WebGL2 surface
|
|
3606
|
+
// still needs a live device while `unconfigure()` presents its pending
|
|
3607
|
+
// image; disposing the scope first lets wasm finalizers race that
|
|
3608
|
+
// presentation and can surface a parking_lot panic in WebKit.
|
|
3609
|
+
try {
|
|
3610
|
+
activeDeviceScope.dispose();
|
|
3611
|
+
} catch (cause) {
|
|
3612
|
+
const error = wrapDisposeError(cause, 'activeDeviceScope.dispose');
|
|
3613
|
+
cleanupFailures.push(error);
|
|
3614
|
+
internals.errorRegistry.fire(error);
|
|
3615
|
+
}
|
|
3250
3616
|
// Step 5: the IBL cache is keyed by the retired DeviceScope generation.
|
|
3251
3617
|
// It is no longer reachable through the active renderer scope, while
|
|
3252
3618
|
// the device teardown owns the child GPU handles.
|
|
@@ -3326,6 +3692,7 @@ async function makeWebGPURenderer(
|
|
|
3326
3692
|
// buffer (or crash the browser GPU process). Keep the ShaderCatalog's
|
|
3327
3693
|
// CPU material/source catalogue, but rebuild this device-bound adapter.
|
|
3328
3694
|
sharedShaderModuleAdapter = null;
|
|
3695
|
+
sharedImmediateShaderModuleAdapter = null;
|
|
3329
3696
|
// The fullscreen post-process builder keeps one empty group-0 layout
|
|
3330
3697
|
// outside RenderSystem's frame cache. It is also device-bound, so retain
|
|
3331
3698
|
// the source/registration but force the next post-process pass to derive
|
|
@@ -3352,7 +3719,7 @@ async function makeWebGPURenderer(
|
|
|
3352
3719
|
let device: RhiDevice;
|
|
3353
3720
|
try {
|
|
3354
3721
|
const deviceResult = await adapterResult.value.requestDevice(
|
|
3355
|
-
deviceOptionsForAdapter(adapterResult.value),
|
|
3722
|
+
deviceOptionsForAdapter(adapterResult.value, internals.options),
|
|
3356
3723
|
);
|
|
3357
3724
|
if (!deviceResult.ok) {
|
|
3358
3725
|
return err(new RecoverError('recover-device-unavailable'));
|
|
@@ -3372,6 +3739,13 @@ async function makeWebGPURenderer(
|
|
|
3372
3739
|
// swap is observed without reconstructing the RenderSystem.
|
|
3373
3740
|
internals.device = device;
|
|
3374
3741
|
internals.context = ctxResult.value;
|
|
3742
|
+
// Build the replacement graph under a fresh renderer-owned scope. The
|
|
3743
|
+
// old committed scope remains authoritative until every rebuild step
|
|
3744
|
+
// succeeds, so a failed recovery cannot publish a half-built scope.
|
|
3745
|
+
const recoveryScope = DeviceScope.create(
|
|
3746
|
+
activeDeviceScope.generation + 1,
|
|
3747
|
+
activeDeviceScope.owner,
|
|
3748
|
+
);
|
|
3375
3749
|
// Re-attach the device.lost fan-out to the new device's lost Promise,
|
|
3376
3750
|
// reusing the SAME registries the host already subscribed to (SSOT
|
|
3377
3751
|
// helper). When this device is lost again, health() flips to
|
|
@@ -3419,10 +3793,13 @@ async function makeWebGPURenderer(
|
|
|
3419
3793
|
// the first post-recovery upload; unlike CPU asset payloads, these
|
|
3420
3794
|
// handles cannot survive a device replacement.
|
|
3421
3795
|
dynamicTextureStore.configureGpuDevice(adaptDynamicTextureDevice(internals.device));
|
|
3796
|
+
let candidatePipelineState: PipelineState | undefined;
|
|
3422
3797
|
const abortDisposedRecovery = (): Result<void, RecoverFailure> | undefined => {
|
|
3423
3798
|
if (!disposed) return undefined;
|
|
3424
3799
|
gpuStore.destroyAll();
|
|
3800
|
+
candidatePipelineState?.perPassResources.drainBloomResources?.();
|
|
3425
3801
|
renderSystem.disposeFrameState();
|
|
3802
|
+
recoveryScope.abandon();
|
|
3426
3803
|
materialShaderPipelineCache.clear();
|
|
3427
3804
|
perShaderMaterialLayoutCache.clear();
|
|
3428
3805
|
group0ResourceLayouts.clear();
|
|
@@ -3441,7 +3818,7 @@ async function makeWebGPURenderer(
|
|
|
3441
3818
|
);
|
|
3442
3819
|
const abortedAfterShaders = abortDisposedRecovery();
|
|
3443
3820
|
if (abortedAfterShaders !== undefined) return abortedAfterShaders;
|
|
3444
|
-
|
|
3821
|
+
candidatePipelineState = await buildPipeline(recoveryScope);
|
|
3445
3822
|
const abortedAfterPipeline = abortDisposedRecovery();
|
|
3446
3823
|
if (abortedAfterPipeline !== undefined) return abortedAfterPipeline;
|
|
3447
3824
|
renderSystem.restorePostProcessResources();
|
|
@@ -3449,6 +3826,8 @@ async function makeWebGPURenderer(
|
|
|
3449
3826
|
// Pipeline rebuild failed against the new device. Treat as a device
|
|
3450
3827
|
// unavailability (the device was acquired but is not usable); health
|
|
3451
3828
|
// stays `device-lost` so the host can retry.
|
|
3829
|
+
candidatePipelineState?.perPassResources.drainBloomResources?.();
|
|
3830
|
+
recoveryScope.abandon();
|
|
3452
3831
|
const aborted = abortDisposedRecovery();
|
|
3453
3832
|
if (aborted !== undefined) return aborted;
|
|
3454
3833
|
return err(new RecoverError('recover-device-unavailable'));
|
|
@@ -3456,27 +3835,18 @@ async function makeWebGPURenderer(
|
|
|
3456
3835
|
const abortedAfterPostProcess = abortDisposedRecovery();
|
|
3457
3836
|
if (abortedAfterPostProcess !== undefined) return abortedAfterPostProcess;
|
|
3458
3837
|
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
)
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
return err(
|
|
3466
|
-
new RenderRecoveryError(
|
|
3467
|
-
disposed ? 'recover-disposed-during-rebuild' : 'recover-lifecycle-failed',
|
|
3468
|
-
{
|
|
3469
|
-
owner: detail.owner,
|
|
3470
|
-
generation: detail.generation,
|
|
3471
|
-
recovery: disposed ? 'stop' : 'recover',
|
|
3472
|
-
receipt: replacementResult.error.receipt,
|
|
3473
|
-
cause: replacementResult.error,
|
|
3474
|
-
},
|
|
3475
|
-
),
|
|
3476
|
-
);
|
|
3838
|
+
// Promote only after the complete replacement build is ready. The old
|
|
3839
|
+
// scope is then retired exactly once; a failed attempt abandons its
|
|
3840
|
+
// candidate in the catch path and can be retried with a new generation.
|
|
3841
|
+
if (candidatePipelineState === undefined) {
|
|
3842
|
+
recoveryScope.abandon();
|
|
3843
|
+
return err(new RecoverError('recover-device-unavailable'));
|
|
3477
3844
|
}
|
|
3478
|
-
|
|
3845
|
+
const previousDeviceScope = activeDeviceScope;
|
|
3846
|
+
activeDeviceScope = recoveryScope;
|
|
3847
|
+
pipelineState = candidatePipelineState;
|
|
3479
3848
|
gpuStore.bindDeviceScope(activeDeviceScope);
|
|
3849
|
+
previousDeviceScope.retire();
|
|
3480
3850
|
|
|
3481
3851
|
// Re-evaluate feature capability gates against the replacement device.
|
|
3482
3852
|
// Feature recovery is isolated from renderer recovery: a feature that is
|
|
@@ -3574,7 +3944,7 @@ export function exposeRenderer(renderer: RendererHostImplementation): Renderer {
|
|
|
3574
3944
|
);
|
|
3575
3945
|
},
|
|
3576
3946
|
setProfile: (profile) => {
|
|
3577
|
-
if (currentState
|
|
3947
|
+
if (!isRendererStateOperational(currentState)) {
|
|
3578
3948
|
return err(
|
|
3579
3949
|
new RendererOperationError('renderer-state-invalid', {
|
|
3580
3950
|
operation: 'set-profile',
|
|
@@ -3647,7 +4017,7 @@ export function exposeRenderer(renderer: RendererHostImplementation): Renderer {
|
|
|
3647
4017
|
const error = new RendererOperationError('recovery-failed', {
|
|
3648
4018
|
operation: 'recover',
|
|
3649
4019
|
oldGeneration,
|
|
3650
|
-
cause: result.error,
|
|
4020
|
+
cause: structuredRendererCause(result.error, 'recover'),
|
|
3651
4021
|
});
|
|
3652
4022
|
emit(Object.freeze({ kind: 'error', error }));
|
|
3653
4023
|
return err(error);
|
|
@@ -3690,13 +4060,24 @@ function structuredRendererCause(cause: unknown, operation: string): RendererOpe
|
|
|
3690
4060
|
typeof candidate.expected === 'string' &&
|
|
3691
4061
|
typeof candidate.hint === 'string'
|
|
3692
4062
|
) {
|
|
3693
|
-
return
|
|
4063
|
+
return {
|
|
4064
|
+
code: candidate.code,
|
|
4065
|
+
expected: candidate.expected,
|
|
4066
|
+
hint: candidate.hint,
|
|
4067
|
+
...(candidate.detail === undefined ? {} : { detail: candidate.detail }),
|
|
4068
|
+
};
|
|
3694
4069
|
}
|
|
3695
4070
|
}
|
|
3696
|
-
|
|
4071
|
+
const contractFailure = new RendererContractFailureError(
|
|
3697
4072
|
'draw',
|
|
3698
4073
|
`${operation}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
3699
4074
|
);
|
|
4075
|
+
return {
|
|
4076
|
+
code: contractFailure.code,
|
|
4077
|
+
expected: contractFailure.expected,
|
|
4078
|
+
hint: contractFailure.hint,
|
|
4079
|
+
detail: contractFailure.detail,
|
|
4080
|
+
};
|
|
3700
4081
|
}
|
|
3701
4082
|
|
|
3702
4083
|
const RENDER_PROFILE_KEYS = new Set([
|
|
@@ -3711,6 +4092,7 @@ const RENDER_PROFILE_KEYS = new Set([
|
|
|
3711
4092
|
'tone',
|
|
3712
4093
|
'antialias',
|
|
3713
4094
|
'sky',
|
|
4095
|
+
'volumetricFog',
|
|
3714
4096
|
'fallback',
|
|
3715
4097
|
'postStages',
|
|
3716
4098
|
]);
|
|
@@ -3731,32 +4113,31 @@ function validateRenderProfile(profile: RenderProfile): string | undefined {
|
|
|
3731
4113
|
if (profile.shadows !== 'off' && profile.shadows !== 'hard' && profile.shadows !== 'filtered') {
|
|
3732
4114
|
return "RenderProfile.shadows must be 'off', 'hard', or 'filtered'";
|
|
3733
4115
|
}
|
|
3734
|
-
if (
|
|
3735
|
-
profile.tone !== 'none' &&
|
|
3736
|
-
profile.tone !== 'aces-filmic' &&
|
|
3737
|
-
profile.tone !== 'agx' &&
|
|
3738
|
-
profile.tone !== 'cineon' &&
|
|
3739
|
-
profile.tone !== 'linear' &&
|
|
3740
|
-
profile.tone !== 'neutral' &&
|
|
3741
|
-
profile.tone !== 'reinhard' &&
|
|
3742
|
-
profile.tone !== 'reinhard-extended'
|
|
3743
|
-
) {
|
|
3744
|
-
return 'RenderProfile.tone is not a supported Standard tone';
|
|
3745
|
-
}
|
|
3746
|
-
if (
|
|
3747
|
-
profile.antialias !== 'none' &&
|
|
3748
|
-
profile.antialias !== 'fxaa' &&
|
|
3749
|
-
profile.antialias !== 'msaa'
|
|
3750
|
-
) {
|
|
3751
|
-
return "RenderProfile.antialias must be 'none', 'fxaa', or 'msaa'";
|
|
3752
|
-
}
|
|
3753
4116
|
if (profile.fallback !== 'native' && profile.fallback !== 'cpu-webgl2') {
|
|
3754
4117
|
return "RenderProfile.fallback must be 'native' or 'cpu-webgl2'";
|
|
3755
4118
|
}
|
|
3756
|
-
for (const field of ['pbr', 'ibl', 'ssao'
|
|
4119
|
+
for (const field of ['pbr', 'ibl', 'ssao'] as const) {
|
|
3757
4120
|
if (typeof profile[field] !== 'boolean') return `RenderProfile.${field} must be boolean`;
|
|
3758
4121
|
}
|
|
3759
|
-
|
|
4122
|
+
if (profile.volumetricFog !== undefined) {
|
|
4123
|
+
if (profile.volumetricFog.quality !== 'low' && profile.volumetricFog.quality !== 'high')
|
|
4124
|
+
return "RenderProfile.volumetricFog.quality must be 'low' or 'high'";
|
|
4125
|
+
if (profile.volumetricFog.depth !== 48 && profile.volumetricFog.depth !== 64) {
|
|
4126
|
+
return 'RenderProfile.volumetricFog.depth must be 48 or 64';
|
|
4127
|
+
}
|
|
4128
|
+
if (profile.volumetricFog.tileSize !== 4 && profile.volumetricFog.tileSize !== 16) {
|
|
4129
|
+
return 'RenderProfile.volumetricFog.tileSize must be 4 or 16';
|
|
4130
|
+
}
|
|
4131
|
+
if (profile.volumetricFog.quality === 'low' && profile.volumetricFog.depth !== 48)
|
|
4132
|
+
return 'RenderProfile.volumetricFog.low must use depth 48';
|
|
4133
|
+
if (profile.volumetricFog.quality === 'high' && profile.volumetricFog.depth !== 64)
|
|
4134
|
+
return 'RenderProfile.volumetricFog.high must use depth 64';
|
|
4135
|
+
if (profile.volumetricFog.quality === 'low' && profile.volumetricFog.tileSize !== 16)
|
|
4136
|
+
return 'RenderProfile.volumetricFog.low must use tileSize 16';
|
|
4137
|
+
if (profile.volumetricFog.quality === 'high' && profile.volumetricFog.tileSize !== 4)
|
|
4138
|
+
return 'RenderProfile.volumetricFog.high must use tileSize 4';
|
|
4139
|
+
}
|
|
4140
|
+
const stages = STANDARD_POST_STAGE_NAMES;
|
|
3760
4141
|
if (
|
|
3761
4142
|
!Array.isArray(profile.postStages) ||
|
|
3762
4143
|
profile.postStages.length !== stages.length ||
|
|
@@ -3807,8 +4188,11 @@ interface ShaderDeviceAdapterInternal extends ShaderCatalogDevice {
|
|
|
3807
4188
|
* `module-forgeax::default-shadow-caster` returns OK on frame 1.
|
|
3808
4189
|
*/
|
|
3809
4190
|
seedModule(label: string, module: ShaderModule): void;
|
|
4191
|
+
invalidateModule(label: string): void;
|
|
3810
4192
|
}
|
|
3811
4193
|
|
|
4194
|
+
type ShaderModuleAdapterMode = 'validated' | 'immediate';
|
|
4195
|
+
|
|
3812
4196
|
function makeShaderDeviceAdapter(
|
|
3813
4197
|
rhiDevice: RhiDevice,
|
|
3814
4198
|
_errorRegistry: RhiErrorListenerRegistry,
|
|
@@ -3818,10 +4202,18 @@ function makeShaderDeviceAdapter(
|
|
|
3818
4202
|
desc: { code: string; label?: string | undefined },
|
|
3819
4203
|
) => Promise<Result<ShaderModule, RhiError>>)
|
|
3820
4204
|
| undefined,
|
|
4205
|
+
immediateCreateShaderModule:
|
|
4206
|
+
| ((
|
|
4207
|
+
device: RhiDevice,
|
|
4208
|
+
desc: { code: string; label?: string | undefined },
|
|
4209
|
+
) => Result<ShaderModule, RhiError>)
|
|
4210
|
+
| undefined,
|
|
4211
|
+
mode: ShaderModuleAdapterMode = 'validated',
|
|
3821
4212
|
): ShaderDeviceAdapterInternal {
|
|
3822
4213
|
const moduleCache = new Map<string, ShaderModule>();
|
|
3823
4214
|
const errorCache = new Map<string, RhiError>();
|
|
3824
|
-
const pending = new
|
|
4215
|
+
const pending = new Map<string, number>();
|
|
4216
|
+
const epochs = new Map<string, number>();
|
|
3825
4217
|
|
|
3826
4218
|
return {
|
|
3827
4219
|
createShaderModule(desc): Result<ShaderModule, RhiError> {
|
|
@@ -3831,18 +4223,39 @@ function makeShaderDeviceAdapter(
|
|
|
3831
4223
|
const cachedError = errorCache.get(key);
|
|
3832
4224
|
if (cachedError !== undefined) return err(cachedError);
|
|
3833
4225
|
|
|
4226
|
+
// WebGPU's raw createShaderModule is synchronous. Only the explicitly
|
|
4227
|
+
// selected immediate adapter uses that entry: generated feature programs
|
|
4228
|
+
// can avoid a slow getCompilationInfo() round trip, while the default
|
|
4229
|
+
// validated adapter keeps the compiler-readiness barrier for ordinary
|
|
4230
|
+
// render pipelines. The public async factory still owns diagnostics;
|
|
4231
|
+
// pipeline creation remains the validation point for the fast path.
|
|
4232
|
+
if (mode === 'immediate' && immediateCreateShaderModule !== undefined) {
|
|
4233
|
+
const immediateResult = immediateCreateShaderModule(rhiDevice, {
|
|
4234
|
+
code: desc.code,
|
|
4235
|
+
...(desc.label === undefined ? {} : { label: desc.label }),
|
|
4236
|
+
});
|
|
4237
|
+
if (immediateResult.ok) {
|
|
4238
|
+
moduleCache.set(key, immediateResult.value);
|
|
4239
|
+
return immediateResult;
|
|
4240
|
+
}
|
|
4241
|
+
errorCache.set(key, immediateResult.error);
|
|
4242
|
+
return immediateResult;
|
|
4243
|
+
}
|
|
4244
|
+
|
|
3834
4245
|
// Fire async creation; first sync call returns pending error (the AI consumer retry pattern).
|
|
3835
4246
|
// M3 D-P4: rhi-webgpu supplies the async factory; rhi-wgpu (no top-level
|
|
3836
4247
|
// async factory) falls back to the synchronous device.createShaderModule
|
|
3837
4248
|
// entry which already returns Result<ShaderModule, RhiError>.
|
|
3838
4249
|
if (!pending.has(key)) {
|
|
3839
|
-
|
|
4250
|
+
const epoch = epochs.get(key) ?? 0;
|
|
4251
|
+
pending.set(key, epoch);
|
|
3840
4252
|
const desc2: { code: string; label?: string | undefined } = { code: desc.code };
|
|
3841
4253
|
if (desc.label !== undefined) desc2.label = desc.label;
|
|
3842
4254
|
const asyncResult: Promise<Result<ShaderModule, RhiError>> = asyncCreateShaderModule
|
|
3843
4255
|
? asyncCreateShaderModule(rhiDevice, desc2)
|
|
3844
4256
|
: invokeDeviceCreateShaderModule(rhiDevice, desc2);
|
|
3845
4257
|
void asyncResult.then((result) => {
|
|
4258
|
+
if (pending.get(key) !== epoch || (epochs.get(key) ?? 0) !== epoch) return;
|
|
3846
4259
|
if (result.ok) {
|
|
3847
4260
|
moduleCache.set(key, result.value);
|
|
3848
4261
|
} else {
|
|
@@ -3857,6 +4270,12 @@ function makeShaderDeviceAdapter(
|
|
|
3857
4270
|
seedModule(label: string, module: ShaderModule): void {
|
|
3858
4271
|
moduleCache.set(label, module);
|
|
3859
4272
|
},
|
|
4273
|
+
invalidateModule(label: string): void {
|
|
4274
|
+
epochs.set(label, (epochs.get(label) ?? 0) + 1);
|
|
4275
|
+
moduleCache.delete(label);
|
|
4276
|
+
errorCache.delete(label);
|
|
4277
|
+
pending.delete(label);
|
|
4278
|
+
},
|
|
3860
4279
|
};
|
|
3861
4280
|
}
|
|
3862
4281
|
|
|
@@ -4102,7 +4521,11 @@ const HDR_COLOR_ATTACHMENT_FORMAT: GPUTextureFormat = 'rgba16float';
|
|
|
4102
4521
|
// so it is intentionally absent here. A `mipmap:true` texture in an
|
|
4103
4522
|
// un-prewarmed format surfaces a structured RhiError at record (never an
|
|
4104
4523
|
// async stall) -- the falsifiable anchor for an incomplete prewarm list.
|
|
4105
|
-
const MIPMAP_PREWARM_FORMATS: readonly GPUTextureFormat[] = [
|
|
4524
|
+
const MIPMAP_PREWARM_FORMATS: readonly GPUTextureFormat[] = [
|
|
4525
|
+
'rgba8unorm-srgb',
|
|
4526
|
+
'rgba8unorm',
|
|
4527
|
+
'rgba16float',
|
|
4528
|
+
];
|
|
4106
4529
|
|
|
4107
4530
|
// feat-20260531-bloom-first-declarative-render-graph-pass / w13:
|
|
4108
4531
|
// UBO sizes for bloom params (std140 layout).
|
|
@@ -4491,6 +4914,14 @@ async function prepareMaterialShaders(
|
|
|
4491
4914
|
if (!capCheck.ok) throw capCheck.error;
|
|
4492
4915
|
storageBufferCapable = !webgl2Downlevel && capCheck.value;
|
|
4493
4916
|
}
|
|
4917
|
+
// The shared PBR view/material ABI consumes the WebGPU minimum of 16
|
|
4918
|
+
// fragment sampled textures. The authored SpotLight projector is the
|
|
4919
|
+
// optional 17th texture; select its shader/BGL pair from the same limit so
|
|
4920
|
+
// low-limit adapters keep analytic lighting instead of failing layout
|
|
4921
|
+
// creation at boot.
|
|
4922
|
+
const projectorAvailable =
|
|
4923
|
+
(rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
4924
|
+
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
4494
4925
|
|
|
4495
4926
|
// ── Step 1: manifest load ─────────────────────────────────────────────────
|
|
4496
4927
|
const registry = getShader();
|
|
@@ -4516,6 +4947,13 @@ async function prepareMaterialShaders(
|
|
|
4516
4947
|
const variantDefines: Record<string, boolean> = {
|
|
4517
4948
|
STORAGE_BUFFER_AVAILABLE: storageBufferCapable,
|
|
4518
4949
|
};
|
|
4950
|
+
// User material manifests may declare WEBGL2_COMPAT alongside the shared
|
|
4951
|
+
// mesh-storage axis. The fallback backend must select that authored
|
|
4952
|
+
// branch at boot; otherwise it asks for the impossible bare
|
|
4953
|
+
// `STORAGE_BUFFER_AVAILABLE=false` key and fails before the first frame.
|
|
4954
|
+
if (msEntry.variants.some((v) => 'WEBGL2_COMPAT' in v.defines)) {
|
|
4955
|
+
variantDefines.WEBGL2_COMPAT = webgl2Downlevel;
|
|
4956
|
+
}
|
|
4519
4957
|
// Only include CLUSTER_FORWARD_AVAILABLE when the material shader declares
|
|
4520
4958
|
// the axis (has a variant with this key), preserving backward compat for
|
|
4521
4959
|
// entries without the pragma.
|
|
@@ -4525,6 +4963,18 @@ async function prepareMaterialShaders(
|
|
|
4525
4963
|
if (msEntry.variants.some((v) => 'VERTEX_COLOR_AVAILABLE' in v.defines)) {
|
|
4526
4964
|
variantDefines.VERTEX_COLOR_AVAILABLE = false;
|
|
4527
4965
|
}
|
|
4966
|
+
if (
|
|
4967
|
+
msEntry.identifier === 'forgeax::default-standard-pbr' &&
|
|
4968
|
+
msEntry.variants.some((v) => 'PROJECTOR_AVAILABLE' in v.defines)
|
|
4969
|
+
) {
|
|
4970
|
+
variantDefines.PROJECTOR_AVAILABLE = projectorAvailable;
|
|
4971
|
+
}
|
|
4972
|
+
if (
|
|
4973
|
+
msEntry.identifier === 'forgeax::default-standard-pbr' &&
|
|
4974
|
+
msEntry.variants.some((v) => 'TRANSMISSION_AVAILABLE' in v.defines)
|
|
4975
|
+
) {
|
|
4976
|
+
variantDefines.TRANSMISSION_AVAILABLE = false;
|
|
4977
|
+
}
|
|
4528
4978
|
// bug-20260708 M2 (a): PER_INSTANCE_REGION axis default = false at boot.
|
|
4529
4979
|
// Per-entity spritePH path (`main-pass-sprite-draws.ts:511-513`) expects
|
|
4530
4980
|
// `variantSet=undefined` to resolve to the PIR=false shader source (64B
|
|
@@ -4539,6 +4989,9 @@ async function prepareMaterialShaders(
|
|
|
4539
4989
|
if (msEntry.variants.some((v) => 'PER_INSTANCE_REGION' in v.defines)) {
|
|
4540
4990
|
variantDefines.PER_INSTANCE_REGION = false;
|
|
4541
4991
|
}
|
|
4992
|
+
if (msEntry.variants.some((v) => 'SKINNING_DISABLED' in v.defines)) {
|
|
4993
|
+
variantDefines.SKINNING_DISABLED = true;
|
|
4994
|
+
}
|
|
4542
4995
|
const sortedEntries = Object.entries(variantDefines).sort(([a], [b]) =>
|
|
4543
4996
|
a < b ? -1 : a > b ? 1 : 0,
|
|
4544
4997
|
);
|
|
@@ -4546,6 +4999,13 @@ async function prepareMaterialShaders(
|
|
|
4546
4999
|
? ''
|
|
4547
5000
|
: sortedEntries.map(([k, v]) => `${k}=${v}`).join('+');
|
|
4548
5001
|
const chosen = findVariantByKey(msEntry, definesKey);
|
|
5002
|
+
if (msEntry.variants.length > 0 && chosen === undefined) {
|
|
5003
|
+
throw new RhiError({
|
|
5004
|
+
code: 'shader-compile-failed',
|
|
5005
|
+
expected: `material shader '${msEntry.identifier}' declares the exact boot variant '${definesKey}'`,
|
|
5006
|
+
hint: 'regenerate the shader manifest with the complete variant Cartesian product',
|
|
5007
|
+
});
|
|
5008
|
+
}
|
|
4549
5009
|
const wgsl = chosen?.composedWgsl ?? msEntry.composedWgsl;
|
|
4550
5010
|
if (wgsl.length > 0) {
|
|
4551
5011
|
const existing = registry.findMaterialArtifact(msEntry.identifier);
|
|
@@ -4627,6 +5087,7 @@ async function prepareMaterialShaders(
|
|
|
4627
5087
|
|
|
4628
5088
|
async function buildReadyWebGPU(
|
|
4629
5089
|
rhiDevice: RhiDevice,
|
|
5090
|
+
rendererScope: DeviceScope,
|
|
4630
5091
|
getShader: () => ShaderCatalog,
|
|
4631
5092
|
gpuStore: GpuResidencyCache,
|
|
4632
5093
|
asyncCreateShaderModule:
|
|
@@ -4680,6 +5141,12 @@ async function buildReadyWebGPU(
|
|
|
4680
5141
|
// tonemap source resolves inside this async function (after the manifest-load
|
|
4681
5142
|
// await), by which point the synchronous `renderSystem` const is defined.
|
|
4682
5143
|
registerBuiltinTonemap: (source: string) => void,
|
|
5144
|
+
registerBuiltinTemporalPostProcesses: (entries: {
|
|
5145
|
+
readonly motionBlur?: string;
|
|
5146
|
+
readonly taaResolve?: string;
|
|
5147
|
+
}) => void,
|
|
5148
|
+
/** Install manifest-owned volume utility sources only after all three modules compile. */
|
|
5149
|
+
setVolumetricFogShaderSources: (sources: VolumetricFogShaderSources) => void,
|
|
4683
5150
|
): Promise<PipelineState> {
|
|
4684
5151
|
// bug-20260610: WebGL2 fallback path — read storage-buffer capability up
|
|
4685
5152
|
// front so both Step 2 (shader-module compile, where we patch engine
|
|
@@ -4697,6 +5164,9 @@ async function buildReadyWebGPU(
|
|
|
4697
5164
|
const capCheck = assertStorageBufferCap(capValue);
|
|
4698
5165
|
storageBufferCapable = capCheck.ok ? !webgl2Downlevel && capCheck.value : !webgl2Downlevel;
|
|
4699
5166
|
}
|
|
5167
|
+
const projectorAvailable =
|
|
5168
|
+
(rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
5169
|
+
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
4700
5170
|
|
|
4701
5171
|
// bug-20260612: choose swap-chain storage / view formats by backend.
|
|
4702
5172
|
// Channel 2 (native WebGPU) follows navigator.gpu.getPreferredCanvasFormat();
|
|
@@ -4706,7 +5176,10 @@ async function buildReadyWebGPU(
|
|
|
4706
5176
|
// pipeline target, configure() call, and color-attachment format —
|
|
4707
5177
|
// single SSOT, no scattered branches. See selectSwapChainFormat
|
|
4708
5178
|
// (above the SWAP_CHAIN_*_FORMAT historical constants).
|
|
4709
|
-
const
|
|
5179
|
+
const surfaceViewFormats =
|
|
5180
|
+
(rhiDevice as unknown as { readonly surfaceViewFormats?: boolean }).surfaceViewFormats ??
|
|
5181
|
+
!webgl2Downlevel;
|
|
5182
|
+
const swapChainFormats = selectSwapChainFormat(storageBufferCapable, surfaceViewFormats);
|
|
4710
5183
|
// The 'null' (headless RhiNull) backend has no UA preferred-canvas-format by
|
|
4711
5184
|
// design; the rgba8unorm fallback is its intended steady state, not a
|
|
4712
5185
|
// degraded one — firing 'rhi-not-available' there is territorially wrong
|
|
@@ -4811,6 +5284,7 @@ async function buildReadyWebGPU(
|
|
|
4811
5284
|
let pbrEntry = findEngineManifestEntry('forgeax::default-standard-pbr');
|
|
4812
5285
|
let unlitEntry = findEngineManifestEntry('forgeax::default-unlit');
|
|
4813
5286
|
let tonemapEntry: ManifestEntry | undefined;
|
|
5287
|
+
let motionBlurEntry: ManifestEntry | undefined;
|
|
4814
5288
|
let spriteEntry: ManifestEntry | undefined;
|
|
4815
5289
|
// sprite-lit identification marker is the `spriteLitShadeAccum`
|
|
4816
5290
|
// helper (defined in sprite-lit.wgsl, absent from sprite.wgsl). The
|
|
@@ -4820,6 +5294,7 @@ async function buildReadyWebGPU(
|
|
|
4820
5294
|
// their paramSchema mirror) does not mis-classify sprite-lit as
|
|
4821
5295
|
// sprite.
|
|
4822
5296
|
let spriteLitEntry: ManifestEntry | undefined;
|
|
5297
|
+
let taaResolveEntry: ManifestEntry | undefined;
|
|
4823
5298
|
let iblEquirectEntry: ManifestEntry | undefined;
|
|
4824
5299
|
let iblIrradianceEntry: ManifestEntry | undefined;
|
|
4825
5300
|
let iblPrefilterEntry: ManifestEntry | undefined;
|
|
@@ -4844,6 +5319,14 @@ async function buildReadyWebGPU(
|
|
|
4844
5319
|
tonemapEntry ??= entry;
|
|
4845
5320
|
continue;
|
|
4846
5321
|
}
|
|
5322
|
+
if (entry.wgsl.includes('MotionBlurParams')) {
|
|
5323
|
+
motionBlurEntry ??= entry;
|
|
5324
|
+
continue;
|
|
5325
|
+
}
|
|
5326
|
+
if (entry.wgsl.includes('fs_taa_resolve')) {
|
|
5327
|
+
taaResolveEntry ??= entry;
|
|
5328
|
+
continue;
|
|
5329
|
+
}
|
|
4847
5330
|
if (entry.wgsl.includes('spriteLitShadeAccum')) {
|
|
4848
5331
|
// sprite-lit identification — must precede the `pivotAndSize`
|
|
4849
5332
|
// marker since sprite-lit also carries that field (paramSchema
|
|
@@ -4985,10 +5468,12 @@ async function buildReadyWebGPU(
|
|
|
4985
5468
|
(!('CLUSTER_FORWARD_AVAILABLE' in v.defines) ||
|
|
4986
5469
|
v.defines.CLUSTER_FORWARD_AVAILABLE === false) &&
|
|
4987
5470
|
(!('VERTEX_COLOR_AVAILABLE' in v.defines) ||
|
|
4988
|
-
v.defines.VERTEX_COLOR_AVAILABLE === false)
|
|
5471
|
+
v.defines.VERTEX_COLOR_AVAILABLE === false) &&
|
|
5472
|
+
(!('PROJECTOR_AVAILABLE' in v.defines) ||
|
|
5473
|
+
v.defines.PROJECTOR_AVAILABLE === projectorAvailable) &&
|
|
5474
|
+
(!('SKINNING_DISABLED' in v.defines) || v.defines.SKINNING_DISABLED === true),
|
|
4989
5475
|
);
|
|
4990
|
-
const v =
|
|
4991
|
-
exact ?? ms.variants.find((cand) => cand.defines.STORAGE_BUFFER_AVAILABLE === false);
|
|
5476
|
+
const v = exact === undefined ? undefined : findVariantByKey(ms, exact.definesKey);
|
|
4992
5477
|
if (v !== undefined) {
|
|
4993
5478
|
idToVariantWgsl.set(ms.identifier, v.composedWgsl);
|
|
4994
5479
|
}
|
|
@@ -5028,13 +5513,15 @@ async function buildReadyWebGPU(
|
|
|
5028
5513
|
(!('CLUSTER_FORWARD_AVAILABLE' in variant.defines) ||
|
|
5029
5514
|
variant.defines.CLUSTER_FORWARD_AVAILABLE === false) &&
|
|
5030
5515
|
(!('VERTEX_COLOR_AVAILABLE' in variant.defines) ||
|
|
5031
|
-
variant.defines.VERTEX_COLOR_AVAILABLE === false)
|
|
5516
|
+
variant.defines.VERTEX_COLOR_AVAILABLE === false) &&
|
|
5517
|
+
(!('PROJECTOR_AVAILABLE' in variant.defines) ||
|
|
5518
|
+
variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) &&
|
|
5519
|
+
(!('SKINNING_DISABLED' in variant.defines) ||
|
|
5520
|
+
variant.defines.SKINNING_DISABLED === true) &&
|
|
5521
|
+
(!('TRANSMISSION_AVAILABLE' in variant.defines) ||
|
|
5522
|
+
variant.defines.TRANSMISSION_AVAILABLE === false),
|
|
5032
5523
|
);
|
|
5033
|
-
const variant =
|
|
5034
|
-
exact ??
|
|
5035
|
-
ms.variants.find(
|
|
5036
|
-
(candidate) => candidate.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable,
|
|
5037
|
-
);
|
|
5524
|
+
const variant = exact === undefined ? undefined : findVariantByKey(ms, exact.definesKey);
|
|
5038
5525
|
if (variant !== undefined) {
|
|
5039
5526
|
idToRuntimeVariantWgsl.set(ms.identifier, variant.composedWgsl);
|
|
5040
5527
|
}
|
|
@@ -5163,6 +5650,80 @@ async function buildReadyWebGPU(
|
|
|
5163
5650
|
seedShaderModule(label, shaderResult.value);
|
|
5164
5651
|
}
|
|
5165
5652
|
|
|
5653
|
+
// The volume graph is authored from ECS state and therefore cannot be
|
|
5654
|
+
// compiled while the static material manifest is assembled. Its pass
|
|
5655
|
+
// encoder nevertheless uses the same shared lazy shader adapter, so the
|
|
5656
|
+
// four utility modules must be prewarmed before the first synchronous
|
|
5657
|
+
// draw. The sources come from the shader manifest (the package-owned WGSL
|
|
5658
|
+
// files are the sole source of truth); compile all four first and only
|
|
5659
|
+
// then publish the labels and source bundle. A partial bundle would let a
|
|
5660
|
+
// later frame mix generations after a device recovery.
|
|
5661
|
+
if (rhiDevice.caps.compute && rhiDevice.caps.storageTexture) {
|
|
5662
|
+
const volumeSources = [
|
|
5663
|
+
['inject', 'volume_inject', (source: string) => source.includes('fn volume_inject(')],
|
|
5664
|
+
['temporal', 'volume_temporal', (source: string) => source.includes('fn volume_temporal(')],
|
|
5665
|
+
[
|
|
5666
|
+
'integrate',
|
|
5667
|
+
'volume_integrate',
|
|
5668
|
+
(source: string) => source.includes('fn volume_integrate('),
|
|
5669
|
+
],
|
|
5670
|
+
['composite', 'volume_composite', (source: string) => source.includes('fn volume_fs(')],
|
|
5671
|
+
] as const;
|
|
5672
|
+
const resolved = volumeSources.map(([stage, label, matches]) => {
|
|
5673
|
+
const entry = manifestEntries.find((candidate) => matches(candidate.wgsl));
|
|
5674
|
+
return { stage, label, entry };
|
|
5675
|
+
});
|
|
5676
|
+
// Volumetric fog is an optional graph feature. Minimal hosts/tests may
|
|
5677
|
+
// intentionally ship only material/post-process entries; keep those
|
|
5678
|
+
// renderers bootable while still rejecting a partially declared volume
|
|
5679
|
+
// bundle (which would otherwise fail later as a lazy pending pipeline).
|
|
5680
|
+
const present = resolved.filter((candidate) => candidate.entry !== undefined).length;
|
|
5681
|
+
if (present === 0) {
|
|
5682
|
+
// No volume manifest entries: leave the source/module bundle unset and
|
|
5683
|
+
// let the topology capability gate keep volume work disabled.
|
|
5684
|
+
} else {
|
|
5685
|
+
const missing = resolved.find((candidate) => candidate.entry === undefined);
|
|
5686
|
+
if (missing !== undefined) {
|
|
5687
|
+
throw new RhiError({
|
|
5688
|
+
code: 'shader-compile-failed',
|
|
5689
|
+
expected: 'shader manifest contains all four volumetric fog utility entries',
|
|
5690
|
+
hint: `add the package-owned WGSL entry for '${missing.label}' to the engine shader manifest`,
|
|
5691
|
+
});
|
|
5692
|
+
}
|
|
5693
|
+
const compiled = [] as Array<{
|
|
5694
|
+
readonly stage: (typeof volumeSources)[number][0];
|
|
5695
|
+
readonly label: string;
|
|
5696
|
+
readonly source: string;
|
|
5697
|
+
readonly module: ShaderModule;
|
|
5698
|
+
}>;
|
|
5699
|
+
for (const { stage, label, entry } of resolved) {
|
|
5700
|
+
// `missing` above proves the narrowing for this loop; keep the guard
|
|
5701
|
+
// explicit so a future refactor cannot publish an undefined source.
|
|
5702
|
+
if (entry === undefined) throw new Error(`missing volumetric shader ${label}`);
|
|
5703
|
+
const shaderResult = await runShimStep(
|
|
5704
|
+
() =>
|
|
5705
|
+
asyncCreateShaderModule
|
|
5706
|
+
? asyncCreateShaderModule(rhiDevice, { code: entry.wgsl, label })
|
|
5707
|
+
: invokeDeviceCreateShaderModule(rhiDevice, { code: entry.wgsl, label }),
|
|
5708
|
+
'shader-compile-failed',
|
|
5709
|
+
`volumetric fog shader module '${label}' compiled`,
|
|
5710
|
+
`inspect manifest WGSL for '${label}' and check device.features`,
|
|
5711
|
+
);
|
|
5712
|
+
if (!shaderResult.ok) throw shaderResult.error;
|
|
5713
|
+
compiled.push({ stage, label, source: entry.wgsl, module: shaderResult.value });
|
|
5714
|
+
}
|
|
5715
|
+
for (const { label, module } of compiled) seedShaderModule(label, module);
|
|
5716
|
+
setVolumetricFogShaderSources({
|
|
5717
|
+
inject: compiled.find((candidate) => candidate.stage === 'inject')?.source as string,
|
|
5718
|
+
temporal: compiled.find((candidate) => candidate.stage === 'temporal')?.source as string,
|
|
5719
|
+
integrate: compiled.find((candidate) => candidate.stage === 'integrate')
|
|
5720
|
+
?.source as string,
|
|
5721
|
+
composite: compiled.find((candidate) => candidate.stage === 'composite')
|
|
5722
|
+
?.source as string,
|
|
5723
|
+
});
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
5726
|
+
|
|
5166
5727
|
const pbrShaderResult = await runShimStep(
|
|
5167
5728
|
() =>
|
|
5168
5729
|
asyncCreateShaderModule
|
|
@@ -5190,6 +5751,16 @@ async function buildReadyWebGPU(
|
|
|
5190
5751
|
if (pbrVariant !== undefined) {
|
|
5191
5752
|
seedShaderModule(`module-forgeax::default-standard-pbr#${pbrVariant.definesKey}`, pbrModule);
|
|
5192
5753
|
}
|
|
5754
|
+
const transmissionCapable =
|
|
5755
|
+
(rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
5756
|
+
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
5757
|
+
const transmissionVariants = transmissionCapable
|
|
5758
|
+
? selectStandardPbrTransmissionPrewarmVariants(
|
|
5759
|
+
pbrManifestEntry,
|
|
5760
|
+
storageBufferCapable,
|
|
5761
|
+
projectorAvailable,
|
|
5762
|
+
)
|
|
5763
|
+
: [];
|
|
5193
5764
|
|
|
5194
5765
|
// HDRP uses the canonical all-true variant key (`''`) and the lazy
|
|
5195
5766
|
// material pipeline adapter therefore requests a distinct module label.
|
|
@@ -5197,9 +5768,36 @@ async function buildReadyWebGPU(
|
|
|
5197
5768
|
// label. Color availability is part of the geometry-owned variant axis, so
|
|
5198
5769
|
// both colored and no-color HDRP sources must be retained.
|
|
5199
5770
|
const prewarmedHdrpModules = new Map<string, ShaderModule>([[pbrEntry.wgsl, pbrModule]]);
|
|
5771
|
+
for (const transmissionVariant of transmissionVariants) {
|
|
5772
|
+
const moduleLabel = `module-forgeax::default-standard-pbr#${transmissionVariant.definesKey}`;
|
|
5773
|
+
let transmissionModule = prewarmedHdrpModules.get(transmissionVariant.composedWgsl);
|
|
5774
|
+
if (transmissionModule === undefined) {
|
|
5775
|
+
const transmissionModuleResult = await runShimStep(
|
|
5776
|
+
() =>
|
|
5777
|
+
asyncCreateShaderModule
|
|
5778
|
+
? asyncCreateShaderModule(rhiDevice, {
|
|
5779
|
+
code: transmissionVariant.composedWgsl,
|
|
5780
|
+
label: moduleLabel,
|
|
5781
|
+
})
|
|
5782
|
+
: invokeDeviceCreateShaderModule(rhiDevice, {
|
|
5783
|
+
code: transmissionVariant.composedWgsl,
|
|
5784
|
+
label: moduleLabel,
|
|
5785
|
+
}),
|
|
5786
|
+
'shader-compile-failed',
|
|
5787
|
+
'Standard transmission shader module compiled',
|
|
5788
|
+
'inspect the composed Standard transmission variant WGSL and check device.features',
|
|
5789
|
+
);
|
|
5790
|
+
if (!transmissionModuleResult.ok) throw transmissionModuleResult.error;
|
|
5791
|
+
transmissionModule = transmissionModuleResult.value;
|
|
5792
|
+
prewarmedHdrpModules.set(transmissionVariant.composedWgsl, transmissionModule);
|
|
5793
|
+
}
|
|
5794
|
+
seedShaderModule(moduleLabel, transmissionModule);
|
|
5795
|
+
}
|
|
5200
5796
|
for (const hdrpVariant of selectHdrpPbrPrewarmVariants(
|
|
5201
5797
|
pbrManifestEntry,
|
|
5202
5798
|
storageBufferCapable,
|
|
5799
|
+
transmissionCapable,
|
|
5800
|
+
projectorAvailable,
|
|
5203
5801
|
)) {
|
|
5204
5802
|
const moduleLabel = `module-forgeax::default-standard-pbr#${hdrpVariant.definesKey}`;
|
|
5205
5803
|
let hdrpModule = prewarmedHdrpModules.get(hdrpVariant.composedWgsl);
|
|
@@ -5335,6 +5933,49 @@ async function buildReadyWebGPU(
|
|
|
5335
5933
|
// uses so the lazy build's createShaderModule call hits OK on
|
|
5336
5934
|
// frame 1.
|
|
5337
5935
|
seedShaderModule('module-forgeax::default-shadow-caster', shadowCasterShaderResult.value);
|
|
5936
|
+
|
|
5937
|
+
// The shadow caster now has independent storage and skinning axes. The
|
|
5938
|
+
// canonical all-true entry remains the ordinary static source through
|
|
5939
|
+
// SKINNING_DISABLED=true; skinned meshes request the explicit false
|
|
5940
|
+
// variant. Prewarm both for the active storage capability so neither
|
|
5941
|
+
// path loses its first shadow frame to the lazy module retry contract.
|
|
5942
|
+
const shadowCasterManifestEntry = Array.from(registry.materialShaderManifestEntries()).find(
|
|
5943
|
+
(candidate) => candidate.identifier === 'forgeax::default-shadow-caster',
|
|
5944
|
+
);
|
|
5945
|
+
const activeShadowVariants = shadowCasterManifestEntry?.variants.filter(
|
|
5946
|
+
(variant) =>
|
|
5947
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable &&
|
|
5948
|
+
typeof variant.defines.SKINNING_DISABLED === 'boolean',
|
|
5949
|
+
);
|
|
5950
|
+
for (const variant of activeShadowVariants ?? []) {
|
|
5951
|
+
if (variant.definesKey === '') {
|
|
5952
|
+
seedShaderModule(
|
|
5953
|
+
'module-forgeax::default-shadow-caster#',
|
|
5954
|
+
shadowCasterShaderResult.value,
|
|
5955
|
+
);
|
|
5956
|
+
continue;
|
|
5957
|
+
}
|
|
5958
|
+
const variantShaderResult = await runShimStep(
|
|
5959
|
+
() =>
|
|
5960
|
+
asyncCreateShaderModule
|
|
5961
|
+
? asyncCreateShaderModule(rhiDevice, {
|
|
5962
|
+
code: variant.composedWgsl,
|
|
5963
|
+
label: `shadow_caster#${variant.definesKey}`,
|
|
5964
|
+
})
|
|
5965
|
+
: invokeDeviceCreateShaderModule(rhiDevice, {
|
|
5966
|
+
code: variant.composedWgsl,
|
|
5967
|
+
label: `shadow_caster#${variant.definesKey}`,
|
|
5968
|
+
}),
|
|
5969
|
+
'shader-compile-failed',
|
|
5970
|
+
`shadow_caster variant ${variant.definesKey || '<default>'} compiled`,
|
|
5971
|
+
'inspect the selected shadow_caster storage/skinning variant and device.features',
|
|
5972
|
+
);
|
|
5973
|
+
if (!variantShaderResult.ok) throw variantShaderResult.error;
|
|
5974
|
+
seedShaderModule(
|
|
5975
|
+
`module-forgeax::default-shadow-caster#${variant.definesKey}`,
|
|
5976
|
+
variantShaderResult.value,
|
|
5977
|
+
);
|
|
5978
|
+
}
|
|
5338
5979
|
}
|
|
5339
5980
|
|
|
5340
5981
|
// feat-20260621 M-A3 (D-5): register the built-in tonemap onto the unified
|
|
@@ -5354,27 +5995,62 @@ async function buildReadyWebGPU(
|
|
|
5354
5995
|
// forever. Mirror the shadow_caster prewarm: await-compile the tonemap module
|
|
5355
5996
|
// here and `seedShaderModule` it under the exact label
|
|
5356
5997
|
// `buildPostProcessPipeline` requests (`post-process-${id}-module`), so the
|
|
5357
|
-
// first `getPostProcessPipeline(
|
|
5998
|
+
// first `getPostProcessPipeline(STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, …)` hits the module cache
|
|
5358
5999
|
// synchronously and builds the pipeline on frame 1.
|
|
5359
6000
|
const tonemapPrewarm = await runShimStep(
|
|
5360
6001
|
() =>
|
|
5361
6002
|
asyncCreateShaderModule
|
|
5362
6003
|
? asyncCreateShaderModule(rhiDevice, {
|
|
5363
6004
|
code: tonemapEntry.wgsl,
|
|
5364
|
-
label: `post-process-${
|
|
6005
|
+
label: `post-process-${STANDARD_OUTPUT_TRANSFORM_FEATURE_ID}-module`,
|
|
5365
6006
|
})
|
|
5366
6007
|
: invokeDeviceCreateShaderModule(rhiDevice, {
|
|
5367
6008
|
code: tonemapEntry.wgsl,
|
|
5368
|
-
label: `post-process-${
|
|
6009
|
+
label: `post-process-${STANDARD_OUTPUT_TRANSFORM_FEATURE_ID}-module`,
|
|
5369
6010
|
}),
|
|
5370
6011
|
'shader-compile-failed',
|
|
5371
6012
|
'tonemap shader module compiled (unified post-process prewarm)',
|
|
5372
6013
|
'inspect manifest tonemap entry composed wgsl; check device.features',
|
|
5373
6014
|
);
|
|
5374
6015
|
if (!tonemapPrewarm.ok) throw tonemapPrewarm.error;
|
|
5375
|
-
seedShaderModule(
|
|
6016
|
+
seedShaderModule(
|
|
6017
|
+
`post-process-${STANDARD_OUTPUT_TRANSFORM_FEATURE_ID}-module`,
|
|
6018
|
+
tonemapPrewarm.value,
|
|
6019
|
+
);
|
|
5376
6020
|
registerBuiltinTonemap(tonemapEntry.wgsl);
|
|
5377
6021
|
|
|
6022
|
+
// Temporal fullscreen passes execute synchronously from the compiled graph,
|
|
6023
|
+
// so their shader modules must be warmed before the initialization barrier
|
|
6024
|
+
// resolves. Otherwise the first tight draw loop sees an asynchronous
|
|
6025
|
+
// `rhi-not-available` result and leaves the temporal targets cleared even
|
|
6026
|
+
// though the graph topology is present.
|
|
6027
|
+
const prewarmTemporal = async (entry: ManifestEntry | undefined, id: string) => {
|
|
6028
|
+
if (entry === undefined) return;
|
|
6029
|
+
const prewarm = await runShimStep(
|
|
6030
|
+
() =>
|
|
6031
|
+
asyncCreateShaderModule
|
|
6032
|
+
? asyncCreateShaderModule(rhiDevice, {
|
|
6033
|
+
code: entry.wgsl,
|
|
6034
|
+
label: `post-process-${id}-module`,
|
|
6035
|
+
})
|
|
6036
|
+
: invokeDeviceCreateShaderModule(rhiDevice, {
|
|
6037
|
+
code: entry.wgsl,
|
|
6038
|
+
label: `post-process-${id}-module`,
|
|
6039
|
+
}),
|
|
6040
|
+
'shader-compile-failed',
|
|
6041
|
+
`${id} shader module compiled (unified post-process prewarm)`,
|
|
6042
|
+
`inspect manifest ${id} entry composed wgsl; check device.features`,
|
|
6043
|
+
);
|
|
6044
|
+
if (!prewarm.ok) throw prewarm.error;
|
|
6045
|
+
seedShaderModule(`post-process-${id}-module`, prewarm.value);
|
|
6046
|
+
};
|
|
6047
|
+
await prewarmTemporal(motionBlurEntry, 'forgeax.motion-blur');
|
|
6048
|
+
await prewarmTemporal(taaResolveEntry, 'forgeax.taa-resolve');
|
|
6049
|
+
registerBuiltinTemporalPostProcesses({
|
|
6050
|
+
...(motionBlurEntry === undefined ? {} : { motionBlur: motionBlurEntry.wgsl }),
|
|
6051
|
+
...(taaResolveEntry === undefined ? {} : { taaResolve: taaResolveEntry.wgsl }),
|
|
6052
|
+
});
|
|
6053
|
+
|
|
5378
6054
|
// feat-20260520-2d-sprite-layer-mvp M-3 / w24: sprite shader module
|
|
5379
6055
|
// is optional in the 3-tuple legacy manifest (back-compat for apps
|
|
5380
6056
|
// that locked their manifest URL before this feat). With the
|
|
@@ -5410,6 +6086,36 @@ async function buildReadyWebGPU(
|
|
|
5410
6086
|
// shares the generic per-MaterialShader lazy build path, which now
|
|
5411
6087
|
// needs the explicit seed to preserve frame-1 readiness.
|
|
5412
6088
|
seedShaderModule('module-forgeax::sprite', spriteShaderResult.value);
|
|
6089
|
+
const spriteManifestEntry = Array.from(registry.materialShaderManifestEntries()).find(
|
|
6090
|
+
(entry) => entry.identifier === 'forgeax::sprite',
|
|
6091
|
+
);
|
|
6092
|
+
const regionVariant = spriteManifestEntry?.variants.find(
|
|
6093
|
+
(variant) =>
|
|
6094
|
+
variant.defines.PER_INSTANCE_REGION === true &&
|
|
6095
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable,
|
|
6096
|
+
);
|
|
6097
|
+
if (regionVariant !== undefined) {
|
|
6098
|
+
const regionShaderResult = await runShimStep(
|
|
6099
|
+
() =>
|
|
6100
|
+
asyncCreateShaderModule
|
|
6101
|
+
? asyncCreateShaderModule(rhiDevice, {
|
|
6102
|
+
code: regionVariant.composedWgsl,
|
|
6103
|
+
label: `sprite#${regionVariant.definesKey}`,
|
|
6104
|
+
})
|
|
6105
|
+
: invokeDeviceCreateShaderModule(rhiDevice, {
|
|
6106
|
+
code: regionVariant.composedWgsl,
|
|
6107
|
+
label: `sprite#${regionVariant.definesKey}`,
|
|
6108
|
+
}),
|
|
6109
|
+
'shader-compile-failed',
|
|
6110
|
+
`sprite variant ${regionVariant.definesKey || '<default>'} compiled`,
|
|
6111
|
+
'inspect the selected sprite PER_INSTANCE_REGION variant and device.features',
|
|
6112
|
+
);
|
|
6113
|
+
if (!regionShaderResult.ok) throw regionShaderResult.error;
|
|
6114
|
+
seedShaderModule(
|
|
6115
|
+
`module-forgeax::sprite#${regionVariant.definesKey}`,
|
|
6116
|
+
regionShaderResult.value,
|
|
6117
|
+
);
|
|
6118
|
+
}
|
|
5413
6119
|
}
|
|
5414
6120
|
|
|
5415
6121
|
// feat-20260624-sprite-lit-shading-model-pure-2d-lighting M1' / t7:
|
|
@@ -5482,15 +6188,12 @@ async function buildReadyWebGPU(
|
|
|
5482
6188
|
skyboxModule = skyboxShaderResult.value;
|
|
5483
6189
|
}
|
|
5484
6190
|
|
|
5485
|
-
//
|
|
5486
|
-
//
|
|
5487
|
-
//
|
|
5488
|
-
//
|
|
5489
|
-
// (see triage above; struct names survive naga_oil composition). When
|
|
5490
|
-
// present, each module is compiled here; pipeline construction happens
|
|
5491
|
-
// alongside the FXAA / tonemap pipelines below.
|
|
6191
|
+
// Bloom shader modules remain available to the lazy Bloom pipeline owner.
|
|
6192
|
+
// Their construction is still part of renderer readiness because the
|
|
6193
|
+
// backend may expose only an asynchronous module factory; the expensive
|
|
6194
|
+
// pipeline, binding, and parameter resources stay deferred until admission.
|
|
5492
6195
|
if (bloomBrightEntry !== undefined) {
|
|
5493
|
-
const
|
|
6196
|
+
const result = await runShimStep(
|
|
5494
6197
|
() =>
|
|
5495
6198
|
asyncCreateShaderModule
|
|
5496
6199
|
? asyncCreateShaderModule(rhiDevice, {
|
|
@@ -5505,17 +6208,14 @@ async function buildReadyWebGPU(
|
|
|
5505
6208
|
'bloom-bright shader module compiled',
|
|
5506
6209
|
'inspect manifest bloom-bright entry composed wgsl; check device.features',
|
|
5507
6210
|
);
|
|
5508
|
-
if (!
|
|
5509
|
-
bloomBrightModule =
|
|
6211
|
+
if (!result.ok) throw result.error;
|
|
6212
|
+
bloomBrightModule = result.value;
|
|
5510
6213
|
}
|
|
5511
6214
|
if (bloomBlurEntry !== undefined) {
|
|
5512
|
-
const
|
|
6215
|
+
const result = await runShimStep(
|
|
5513
6216
|
() =>
|
|
5514
6217
|
asyncCreateShaderModule
|
|
5515
|
-
? asyncCreateShaderModule(rhiDevice, {
|
|
5516
|
-
code: bloomBlurEntry.wgsl,
|
|
5517
|
-
label: 'bloom-blur',
|
|
5518
|
-
})
|
|
6218
|
+
? asyncCreateShaderModule(rhiDevice, { code: bloomBlurEntry.wgsl, label: 'bloom-blur' })
|
|
5519
6219
|
: invokeDeviceCreateShaderModule(rhiDevice, {
|
|
5520
6220
|
code: bloomBlurEntry.wgsl,
|
|
5521
6221
|
label: 'bloom-blur',
|
|
@@ -5524,11 +6224,11 @@ async function buildReadyWebGPU(
|
|
|
5524
6224
|
'bloom-blur shader module compiled',
|
|
5525
6225
|
'inspect manifest bloom-blur entry composed wgsl; check device.features',
|
|
5526
6226
|
);
|
|
5527
|
-
if (!
|
|
5528
|
-
bloomBlurModule =
|
|
6227
|
+
if (!result.ok) throw result.error;
|
|
6228
|
+
bloomBlurModule = result.value;
|
|
5529
6229
|
}
|
|
5530
6230
|
if (bloomCompositeEntry !== undefined) {
|
|
5531
|
-
const
|
|
6231
|
+
const result = await runShimStep(
|
|
5532
6232
|
() =>
|
|
5533
6233
|
asyncCreateShaderModule
|
|
5534
6234
|
? asyncCreateShaderModule(rhiDevice, {
|
|
@@ -5543,8 +6243,8 @@ async function buildReadyWebGPU(
|
|
|
5543
6243
|
'bloom-composite shader module compiled',
|
|
5544
6244
|
'inspect manifest bloom-composite entry composed wgsl; check device.features',
|
|
5545
6245
|
);
|
|
5546
|
-
if (!
|
|
5547
|
-
bloomCompositeModule =
|
|
6246
|
+
if (!result.ok) throw result.error;
|
|
6247
|
+
bloomCompositeModule = result.value;
|
|
5548
6248
|
}
|
|
5549
6249
|
// feat-20260612-hdrp-ssao M6 / w27: SSAO shader module compilation.
|
|
5550
6250
|
// Optional manifest entry — absent on legacy manifests (zero-overhead
|
|
@@ -5576,11 +6276,11 @@ async function buildReadyWebGPU(
|
|
|
5576
6276
|
// The factory builds 4 BindGroupLayouts (view + material + mesh-array +
|
|
5577
6277
|
// instances) and the 4-slot pipeline layout in one call, throwing on each
|
|
5578
6278
|
// device.createBindGroupLayout / device.createPipelineLayout failure.
|
|
5579
|
-
// material BGL now carries
|
|
6279
|
+
// material BGL now carries 24 entries (material 0..16 + Skylight 17..23 via
|
|
5580
6280
|
// mergeSkylightIntoMaterialBgl) -- pipeline layout itself stays at 4 slots,
|
|
5581
6281
|
// no @group(4) allocated (D-5 round-4 fix for the round-2 maxBindGroups=4
|
|
5582
6282
|
// BLOCKER). The unlit pipeline currently shares this 14-entry layout
|
|
5583
|
-
// (carrying fallback identity at binding
|
|
6283
|
+
// (carrying fallback identity at binding 17..23 from the record stage);
|
|
5584
6284
|
// future split via `buildUnlitMaterialBgl` is exposed for tests.
|
|
5585
6285
|
//
|
|
5586
6286
|
// feat-20260520-directional-light-shadow-mapping merge: view BGL entries
|
|
@@ -5591,7 +6291,13 @@ async function buildReadyWebGPU(
|
|
|
5591
6291
|
// buildReadyWebGPU (above) so Step 2 (variant patch) and this layout
|
|
5592
6292
|
// build read the same value.
|
|
5593
6293
|
const pbrLayouts = runShimSyncStep(
|
|
5594
|
-
() =>
|
|
6294
|
+
() =>
|
|
6295
|
+
ok(
|
|
6296
|
+
buildPbrPipelineLayouts(rhiDevice, {
|
|
6297
|
+
storageBuffer: storageBufferCapable,
|
|
6298
|
+
projectorAvailable,
|
|
6299
|
+
}),
|
|
6300
|
+
),
|
|
5595
6301
|
'webgpu-runtime-error',
|
|
5596
6302
|
'buildPbrPipelineLayouts succeeded',
|
|
5597
6303
|
'check device.limits.maxBindingsPerBindGroup (need >=14) and maxBindGroupsPerPipelineLayout',
|
|
@@ -5614,7 +6320,10 @@ async function buildReadyWebGPU(
|
|
|
5614
6320
|
// (URP layout) so the manifest entry's URP variant WGSL still builds a
|
|
5615
6321
|
// valid PSO instead of hard-disabling the entire HDRP-variant build path.
|
|
5616
6322
|
let hdrpPbrPipelineLayoutHandle: PipelineLayout | null = null;
|
|
5617
|
-
|
|
6323
|
+
// The cluster layout is storage-buffer backed. On the WebGL2/fallback
|
|
6324
|
+
// capability tier its uniform-buffer projection exceeds the backend's
|
|
6325
|
+
// fragment-stage limit, and HDRP is not a valid route there anyway.
|
|
6326
|
+
if (storageBufferCapable) {
|
|
5618
6327
|
const hdrpUnifiedBglDesc = createHdrpBindGroupLayoutDescriptor(storageBufferCapable);
|
|
5619
6328
|
const hdrpUnifiedBglRes = rhiDevice.createBindGroupLayout(hdrpUnifiedBglDesc);
|
|
5620
6329
|
if (hdrpUnifiedBglRes.ok) {
|
|
@@ -6922,6 +7631,59 @@ async function buildReadyWebGPU(
|
|
|
6922
7631
|
let bloomBlurHParamsBufferHandle: Buffer | null = null;
|
|
6923
7632
|
let bloomBlurVParamsBufferHandle: Buffer | null = null;
|
|
6924
7633
|
let bloomCompositeParamsBufferHandle: Buffer | null = null;
|
|
7634
|
+
let bloomResourcesReady = false;
|
|
7635
|
+
let ensureBloomResources: (() => void) | undefined;
|
|
7636
|
+
let bloomActiveBundle: BloomPersistentBundle | undefined;
|
|
7637
|
+
let bloomCandidateBundle: BloomPersistentBundle | undefined;
|
|
7638
|
+
const retiringBloomBundles = new Set<BloomPersistentBundle>();
|
|
7639
|
+
let bloomGeneration = 0;
|
|
7640
|
+
let lastSuccessfulBloomReceipts:
|
|
7641
|
+
| import('../record/frame-snapshot').BloomFrameReceipts
|
|
7642
|
+
| undefined;
|
|
7643
|
+
const getBloomResourcesStable = (): BloomPersistentBundle | null =>
|
|
7644
|
+
bloomCandidateBundle ?? bloomActiveBundle ?? null;
|
|
7645
|
+
let commitBloomResources: (() => void) | undefined;
|
|
7646
|
+
let discardBloomResources: (() => void) | undefined;
|
|
7647
|
+
let retireBloomResources: ((completion: Promise<unknown>) => void) | undefined;
|
|
7648
|
+
let drainBloomResources: (() => void) | undefined;
|
|
7649
|
+
let inspectBloomResources:
|
|
7650
|
+
| ((graph?: import('@forgeax/engine-render-graph').CompiledRenderGraphInfo) => BloomInspection)
|
|
7651
|
+
| undefined;
|
|
7652
|
+
const ensureBloomResourcesStable = (): void => {
|
|
7653
|
+
ensureBloomResources?.();
|
|
7654
|
+
};
|
|
7655
|
+
const retireBloomResourcesStable = (completion: Promise<unknown>): void => {
|
|
7656
|
+
retireBloomResources?.(completion);
|
|
7657
|
+
};
|
|
7658
|
+
const commitBloomResourcesStable = (): void => {
|
|
7659
|
+
commitBloomResources?.();
|
|
7660
|
+
};
|
|
7661
|
+
const discardBloomResourcesStable = (): void => {
|
|
7662
|
+
discardBloomResources?.();
|
|
7663
|
+
};
|
|
7664
|
+
const drainBloomResourcesStable = (): void => {
|
|
7665
|
+
drainBloomResources?.();
|
|
7666
|
+
};
|
|
7667
|
+
const inspectBloomResourcesStable = (
|
|
7668
|
+
graph?: import('@forgeax/engine-render-graph').CompiledRenderGraphInfo,
|
|
7669
|
+
): BloomInspection => {
|
|
7670
|
+
return (
|
|
7671
|
+
inspectBloomResources?.(graph) ?? {
|
|
7672
|
+
graphStatus: 'empty',
|
|
7673
|
+
enabled: false,
|
|
7674
|
+
targetCount: 0,
|
|
7675
|
+
targetBytes: 0,
|
|
7676
|
+
resourceCount: 0,
|
|
7677
|
+
passCount: 0,
|
|
7678
|
+
encodeCount: 0,
|
|
7679
|
+
bindGroupCount: 0,
|
|
7680
|
+
uploadCount: 0,
|
|
7681
|
+
residentChildBytes: 0,
|
|
7682
|
+
generation: 0,
|
|
7683
|
+
state: 'off',
|
|
7684
|
+
}
|
|
7685
|
+
);
|
|
7686
|
+
};
|
|
6925
7687
|
// feat-20260612-hdrp-ssao M6 / w26 + w43: SSAO pipeline handles (D-A).
|
|
6926
7688
|
// calc + blur RenderPipeline pair sharing a dedicated 6-entry BGL.
|
|
6927
7689
|
// Optional — null when manifest lacks hdrp-ssao entry.
|
|
@@ -7222,361 +7984,560 @@ async function buildReadyWebGPU(
|
|
|
7222
7984
|
// bloom-blur-v : same BGL, same module as blur-h, V-axis texelSize
|
|
7223
7985
|
// bloom-composite: 2-tex + sampler + UBO@3 BGL, rgba16float target
|
|
7224
7986
|
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
const
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7987
|
+
ensureBloomResources = (): void => {
|
|
7988
|
+
if (bloomResourcesReady) return;
|
|
7989
|
+
const candidateScope = rendererScope.createChild(`${rendererScope.owner}:standard-bloom`);
|
|
7990
|
+
const adopt = <T>(
|
|
7991
|
+
kind: Parameters<DeviceScope['_adopt']>[0],
|
|
7992
|
+
value: T,
|
|
7993
|
+
cleanup: (value: T) => void,
|
|
7994
|
+
): T => {
|
|
7995
|
+
candidateScope._adopt(kind, value, cleanup);
|
|
7996
|
+
return value;
|
|
7997
|
+
};
|
|
7998
|
+
try {
|
|
7999
|
+
// Shared bloom sampler: linear filter + clamp-to-edge (all 4 passes
|
|
8000
|
+
// sample from textures using fullscreen triangle UVs).
|
|
8001
|
+
if (
|
|
8002
|
+
bloomBrightModule !== null ||
|
|
8003
|
+
bloomBlurModule !== null ||
|
|
8004
|
+
bloomCompositeModule !== null
|
|
8005
|
+
) {
|
|
8006
|
+
const bloomSamplerResult = runShimSyncStep(
|
|
8007
|
+
() =>
|
|
8008
|
+
rhiDevice.createSampler({
|
|
8009
|
+
label: 'bloom-sampler',
|
|
8010
|
+
magFilter: 'linear',
|
|
8011
|
+
minFilter: 'linear',
|
|
8012
|
+
mipmapFilter: 'linear',
|
|
8013
|
+
addressModeU: 'clamp-to-edge',
|
|
8014
|
+
addressModeV: 'clamp-to-edge',
|
|
8015
|
+
}),
|
|
8016
|
+
'webgpu-runtime-error',
|
|
8017
|
+
'createSampler (bloom) succeeded',
|
|
8018
|
+
'check device.limits.maxSamplersPerShaderStage',
|
|
8019
|
+
);
|
|
8020
|
+
if (!bloomSamplerResult.ok) throw bloomSamplerResult.error;
|
|
8021
|
+
bloomSamplerHandle = adopt('binding', bloomSamplerResult.value, () => undefined);
|
|
8022
|
+
}
|
|
7245
8023
|
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
8024
|
+
// Bloom bright: 1-tex + sampler + UBO@2 BGL (D-4).
|
|
8025
|
+
if (bloomBrightModule !== null && bloomSamplerHandle !== null) {
|
|
8026
|
+
const brightBglResult = runShimSyncStep(
|
|
8027
|
+
() =>
|
|
8028
|
+
rhiDevice.createBindGroupLayout({
|
|
8029
|
+
label: 'bloom-bright-bgl',
|
|
8030
|
+
entries: [
|
|
8031
|
+
{
|
|
8032
|
+
binding: 0,
|
|
8033
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
8034
|
+
texture: { sampleType: 'float', viewDimension: '2d' },
|
|
8035
|
+
},
|
|
8036
|
+
{
|
|
8037
|
+
binding: 1,
|
|
8038
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
8039
|
+
sampler: { type: 'filtering' },
|
|
8040
|
+
},
|
|
8041
|
+
{
|
|
8042
|
+
binding: 2,
|
|
8043
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
8044
|
+
buffer: { type: 'uniform' },
|
|
8045
|
+
},
|
|
8046
|
+
],
|
|
8047
|
+
}),
|
|
8048
|
+
'webgpu-runtime-error',
|
|
8049
|
+
'createBindGroupLayout(bloom-bright) succeeded',
|
|
8050
|
+
'check device.limits.maxBindGroupsPerPipelineLayout',
|
|
8051
|
+
);
|
|
8052
|
+
if (!brightBglResult.ok) throw brightBglResult.error;
|
|
8053
|
+
bloomBrightBglHandle = adopt('binding', brightBglResult.value, () => undefined);
|
|
8054
|
+
|
|
8055
|
+
const brightPlResult = runShimSyncStep(
|
|
8056
|
+
() =>
|
|
8057
|
+
rhiDevice.createPipelineLayout({
|
|
8058
|
+
label: 'bloom-bright-pl',
|
|
8059
|
+
bindGroupLayouts: [brightBglResult.value],
|
|
8060
|
+
}),
|
|
8061
|
+
'webgpu-runtime-error',
|
|
8062
|
+
'createPipelineLayout(bloom-bright) succeeded',
|
|
8063
|
+
'verify bloom-bright BindGroupLayout matches shader @group(0) bindings',
|
|
8064
|
+
);
|
|
8065
|
+
if (!brightPlResult.ok) throw brightPlResult.error;
|
|
8066
|
+
adopt('binding', brightPlResult.value, () => undefined);
|
|
8067
|
+
|
|
8068
|
+
// M2-T4: bloom-bright pipeline via getOrBuildPipeline (lazy-build).
|
|
8069
|
+
{
|
|
8070
|
+
const brightSpec: PipelineSpec = {
|
|
8071
|
+
shader: {
|
|
8072
|
+
id: 'forgeax::post::bloom-bright',
|
|
8073
|
+
passKind: 'post-process',
|
|
8074
|
+
variantSet: undefined,
|
|
7257
8075
|
},
|
|
7258
|
-
{
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
8076
|
+
attachments: {
|
|
8077
|
+
colorFormats: [HDR_COLOR_ATTACHMENT_FORMAT],
|
|
8078
|
+
depthFormat: undefined,
|
|
8079
|
+
sampleCount: 1,
|
|
7262
8080
|
},
|
|
7263
|
-
{
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
8081
|
+
geometry: {
|
|
8082
|
+
topology: 'triangle-list',
|
|
8083
|
+
stripIndexFormat: undefined,
|
|
8084
|
+
vertexLayout: {},
|
|
7267
8085
|
},
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
8086
|
+
renderState: { cullMode: 'none' },
|
|
8087
|
+
};
|
|
8088
|
+
const modules = {
|
|
8089
|
+
vertex: bloomBrightModule,
|
|
8090
|
+
fragment: bloomBrightModule,
|
|
8091
|
+
layout: brightPlResult.value,
|
|
8092
|
+
};
|
|
8093
|
+
try {
|
|
8094
|
+
bloomBrightPipelineHandle = getOrBuildPipeline(
|
|
8095
|
+
brightSpec,
|
|
8096
|
+
pipelineDeviceProvider,
|
|
8097
|
+
pipelineCache,
|
|
8098
|
+
modules,
|
|
8099
|
+
) as RenderPipeline;
|
|
8100
|
+
bloomBrightPipelineHandle = adopt(
|
|
8101
|
+
'pipeline',
|
|
8102
|
+
bloomBrightPipelineHandle,
|
|
8103
|
+
() => undefined,
|
|
8104
|
+
);
|
|
8105
|
+
} catch (err) {
|
|
8106
|
+
if (err instanceof PipelineSpecError) throw err;
|
|
8107
|
+
throw new PipelineSpecError({
|
|
8108
|
+
code: 'pipeline-build-failed',
|
|
8109
|
+
detail: { cause: err },
|
|
8110
|
+
hint: 'createRenderPipeline (bloom-bright fullscreen) failed; inspect gpuMessage',
|
|
8111
|
+
});
|
|
8112
|
+
}
|
|
8113
|
+
}
|
|
7288
8114
|
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
vertexLayout: {},
|
|
7306
|
-
},
|
|
7307
|
-
renderState: { cullMode: 'none' },
|
|
7308
|
-
};
|
|
7309
|
-
const modules = {
|
|
7310
|
-
vertex: bloomBrightModule,
|
|
7311
|
-
fragment: bloomBrightModule,
|
|
7312
|
-
layout: brightPlResult.value,
|
|
7313
|
-
};
|
|
7314
|
-
try {
|
|
7315
|
-
bloomBrightPipelineHandle = getOrBuildPipeline(
|
|
7316
|
-
brightSpec,
|
|
7317
|
-
pipelineDeviceProvider,
|
|
7318
|
-
pipelineCache,
|
|
7319
|
-
modules,
|
|
7320
|
-
) as RenderPipeline;
|
|
7321
|
-
} catch (err) {
|
|
7322
|
-
if (err instanceof PipelineSpecError) throw err;
|
|
7323
|
-
throw new PipelineSpecError({
|
|
7324
|
-
code: 'pipeline-build-failed',
|
|
7325
|
-
detail: { cause: err },
|
|
7326
|
-
hint: 'createRenderPipeline (bloom-bright fullscreen) failed; inspect gpuMessage',
|
|
8115
|
+
// Bright params UBO: 16 B std140 (threshold f32 + 12 B pad).
|
|
8116
|
+
const brightParamsResult = runShimSyncStep(
|
|
8117
|
+
() =>
|
|
8118
|
+
rhiDevice.createBuffer({
|
|
8119
|
+
label: 'bloom-bright-params-ubo',
|
|
8120
|
+
size: BRIGHT_PARAMS_BYTES,
|
|
8121
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
8122
|
+
mappedAtCreation: false,
|
|
8123
|
+
}),
|
|
8124
|
+
'webgpu-runtime-error',
|
|
8125
|
+
'createBuffer (bloom-bright params UBO) succeeded',
|
|
8126
|
+
'check device.limits.maxUniformBufferBindingSize',
|
|
8127
|
+
);
|
|
8128
|
+
if (!brightParamsResult.ok) throw brightParamsResult.error;
|
|
8129
|
+
bloomBrightParamsBufferHandle = adopt('buffer', brightParamsResult.value, (value) => {
|
|
8130
|
+
rhiDevice.destroyBuffer(value);
|
|
7327
8131
|
});
|
|
7328
8132
|
}
|
|
7329
|
-
}
|
|
7330
|
-
|
|
7331
|
-
// Bright params UBO: 16 B std140 (threshold f32 + 12 B pad).
|
|
7332
|
-
const brightParamsResult = runShimSyncStep(
|
|
7333
|
-
() =>
|
|
7334
|
-
rhiDevice.createBuffer({
|
|
7335
|
-
label: 'bloom-bright-params-ubo',
|
|
7336
|
-
size: BRIGHT_PARAMS_BYTES,
|
|
7337
|
-
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
7338
|
-
mappedAtCreation: false,
|
|
7339
|
-
}),
|
|
7340
|
-
'webgpu-runtime-error',
|
|
7341
|
-
'createBuffer (bloom-bright params UBO) succeeded',
|
|
7342
|
-
'check device.limits.maxUniformBufferBindingSize',
|
|
7343
|
-
);
|
|
7344
|
-
if (!brightParamsResult.ok) throw brightParamsResult.error;
|
|
7345
|
-
bloomBrightParamsBufferHandle = brightParamsResult.value;
|
|
7346
|
-
}
|
|
7347
8133
|
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
8134
|
+
// Bloom blur H/V: same BGL (1-tex + sampler + UBO@2), same module,
|
|
8135
|
+
// two separate pipelines with per-axis texelSize (D-1, D-4).
|
|
8136
|
+
if (bloomBlurModule !== null && bloomSamplerHandle !== null) {
|
|
8137
|
+
const blurBglResult = runShimSyncStep(
|
|
8138
|
+
() =>
|
|
8139
|
+
rhiDevice.createBindGroupLayout({
|
|
8140
|
+
label: 'bloom-blur-bgl',
|
|
8141
|
+
entries: [
|
|
8142
|
+
{
|
|
8143
|
+
binding: 0,
|
|
8144
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
8145
|
+
texture: { sampleType: 'float', viewDimension: '2d' },
|
|
8146
|
+
},
|
|
8147
|
+
{
|
|
8148
|
+
binding: 1,
|
|
8149
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
8150
|
+
sampler: { type: 'filtering' },
|
|
8151
|
+
},
|
|
8152
|
+
{
|
|
8153
|
+
binding: 2,
|
|
8154
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
8155
|
+
buffer: { type: 'uniform' },
|
|
8156
|
+
},
|
|
8157
|
+
],
|
|
8158
|
+
}),
|
|
8159
|
+
'webgpu-runtime-error',
|
|
8160
|
+
'createBindGroupLayout(bloom-blur) succeeded',
|
|
8161
|
+
'check device.limits.maxBindGroupsPerPipelineLayout',
|
|
8162
|
+
);
|
|
8163
|
+
if (!blurBglResult.ok) throw blurBglResult.error;
|
|
8164
|
+
bloomBlurBglHandle = adopt('binding', blurBglResult.value, () => undefined);
|
|
8165
|
+
|
|
8166
|
+
const blurPlResult = runShimSyncStep(
|
|
8167
|
+
() =>
|
|
8168
|
+
rhiDevice.createPipelineLayout({
|
|
8169
|
+
label: 'bloom-blur-pl',
|
|
8170
|
+
bindGroupLayouts: [blurBglResult.value],
|
|
8171
|
+
}),
|
|
8172
|
+
'webgpu-runtime-error',
|
|
8173
|
+
'createPipelineLayout(bloom-blur) succeeded',
|
|
8174
|
+
'verify bloom-blur BindGroupLayout matches shader @group(0) bindings',
|
|
8175
|
+
);
|
|
8176
|
+
if (!blurPlResult.ok) throw blurPlResult.error;
|
|
8177
|
+
adopt('binding', blurPlResult.value, () => undefined);
|
|
8178
|
+
|
|
8179
|
+
// M2-T4: bloom-blur H/V pipelines via getOrBuildPipeline (lazy-build).
|
|
8180
|
+
// H and V share the same PSO descriptor — only per-axis texelSize UBO
|
|
8181
|
+
// distinguishes them at record time. getOrBuildPipeline cache-hit on the
|
|
8182
|
+
// second call returns the same handle (identical spec, identical PSO).
|
|
8183
|
+
{
|
|
8184
|
+
const blurSpec: PipelineSpec = {
|
|
8185
|
+
shader: {
|
|
8186
|
+
id: 'forgeax::post::bloom-blur',
|
|
8187
|
+
passKind: 'post-process',
|
|
8188
|
+
variantSet: undefined,
|
|
7360
8189
|
},
|
|
7361
|
-
{
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
8190
|
+
attachments: {
|
|
8191
|
+
colorFormats: [HDR_COLOR_ATTACHMENT_FORMAT],
|
|
8192
|
+
depthFormat: undefined,
|
|
8193
|
+
sampleCount: 1,
|
|
7365
8194
|
},
|
|
7366
|
-
{
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
8195
|
+
geometry: {
|
|
8196
|
+
topology: 'triangle-list',
|
|
8197
|
+
stripIndexFormat: undefined,
|
|
8198
|
+
vertexLayout: {},
|
|
7370
8199
|
},
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
8200
|
+
renderState: { cullMode: 'none' },
|
|
8201
|
+
};
|
|
8202
|
+
const modules = {
|
|
8203
|
+
vertex: bloomBlurModule,
|
|
8204
|
+
fragment: bloomBlurModule,
|
|
8205
|
+
layout: blurPlResult.value,
|
|
8206
|
+
label: 'bloom-blur-h-pipeline',
|
|
8207
|
+
};
|
|
8208
|
+
try {
|
|
8209
|
+
bloomBlurHPipelineHandle = getOrBuildPipeline(
|
|
8210
|
+
blurSpec,
|
|
8211
|
+
pipelineDeviceProvider,
|
|
8212
|
+
pipelineCache,
|
|
8213
|
+
modules,
|
|
8214
|
+
) as RenderPipeline;
|
|
8215
|
+
bloomBlurHPipelineHandle = adopt(
|
|
8216
|
+
'pipeline',
|
|
8217
|
+
bloomBlurHPipelineHandle,
|
|
8218
|
+
() => undefined,
|
|
8219
|
+
);
|
|
8220
|
+
} catch (err) {
|
|
8221
|
+
if (err instanceof PipelineSpecError) throw err;
|
|
8222
|
+
throw new PipelineSpecError({
|
|
8223
|
+
code: 'pipeline-build-failed',
|
|
8224
|
+
detail: { cause: err },
|
|
8225
|
+
hint: 'createRenderPipeline (bloom-blur-h fullscreen) failed; inspect gpuMessage',
|
|
8226
|
+
});
|
|
8227
|
+
}
|
|
8228
|
+
// V is cache-hit on the same spec (identical PSO; per-axis UBO differentiates at record time).
|
|
8229
|
+
bloomBlurVPipelineHandle = bloomBlurHPipelineHandle;
|
|
8230
|
+
}
|
|
7379
8231
|
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
8232
|
+
// Blur params UBOs: 16 B std140 (texelSize.xy + radius + pad) each.
|
|
8233
|
+
// bug-20260625: one per axis -- H and V must not share a buffer (the
|
|
8234
|
+
// shared-buffer writeBuffer race made both passes blur vertically).
|
|
8235
|
+
const blurHParamsResult = runShimSyncStep(
|
|
8236
|
+
() =>
|
|
8237
|
+
rhiDevice.createBuffer({
|
|
8238
|
+
label: 'bloom-blur-h-params-ubo',
|
|
8239
|
+
size: BLUR_PARAMS_BYTES,
|
|
8240
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
8241
|
+
mappedAtCreation: false,
|
|
8242
|
+
}),
|
|
8243
|
+
'webgpu-runtime-error',
|
|
8244
|
+
'createBuffer (bloom-blur-h params UBO) succeeded',
|
|
8245
|
+
'check device.limits.maxUniformBufferBindingSize',
|
|
8246
|
+
);
|
|
8247
|
+
if (!blurHParamsResult.ok) throw blurHParamsResult.error;
|
|
8248
|
+
bloomBlurHParamsBufferHandle = adopt('buffer', blurHParamsResult.value, (value) => {
|
|
8249
|
+
rhiDevice.destroyBuffer(value);
|
|
8250
|
+
});
|
|
7391
8251
|
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
},
|
|
7408
|
-
geometry: {
|
|
7409
|
-
topology: 'triangle-list',
|
|
7410
|
-
stripIndexFormat: undefined,
|
|
7411
|
-
vertexLayout: {},
|
|
7412
|
-
},
|
|
7413
|
-
renderState: { cullMode: 'none' },
|
|
7414
|
-
};
|
|
7415
|
-
const modules = {
|
|
7416
|
-
vertex: bloomBlurModule,
|
|
7417
|
-
fragment: bloomBlurModule,
|
|
7418
|
-
layout: blurPlResult.value,
|
|
7419
|
-
label: 'bloom-blur-h-pipeline',
|
|
7420
|
-
};
|
|
7421
|
-
try {
|
|
7422
|
-
bloomBlurHPipelineHandle = getOrBuildPipeline(
|
|
7423
|
-
blurSpec,
|
|
7424
|
-
pipelineDeviceProvider,
|
|
7425
|
-
pipelineCache,
|
|
7426
|
-
modules,
|
|
7427
|
-
) as RenderPipeline;
|
|
7428
|
-
} catch (err) {
|
|
7429
|
-
if (err instanceof PipelineSpecError) throw err;
|
|
7430
|
-
throw new PipelineSpecError({
|
|
7431
|
-
code: 'pipeline-build-failed',
|
|
7432
|
-
detail: { cause: err },
|
|
7433
|
-
hint: 'createRenderPipeline (bloom-blur-h fullscreen) failed; inspect gpuMessage',
|
|
8252
|
+
const blurVParamsResult = runShimSyncStep(
|
|
8253
|
+
() =>
|
|
8254
|
+
rhiDevice.createBuffer({
|
|
8255
|
+
label: 'bloom-blur-v-params-ubo',
|
|
8256
|
+
size: BLUR_PARAMS_BYTES,
|
|
8257
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
8258
|
+
mappedAtCreation: false,
|
|
8259
|
+
}),
|
|
8260
|
+
'webgpu-runtime-error',
|
|
8261
|
+
'createBuffer (bloom-blur-v params UBO) succeeded',
|
|
8262
|
+
'check device.limits.maxUniformBufferBindingSize',
|
|
8263
|
+
);
|
|
8264
|
+
if (!blurVParamsResult.ok) throw blurVParamsResult.error;
|
|
8265
|
+
bloomBlurVParamsBufferHandle = adopt('buffer', blurVParamsResult.value, (value) => {
|
|
8266
|
+
rhiDevice.destroyBuffer(value);
|
|
7434
8267
|
});
|
|
7435
8268
|
}
|
|
7436
|
-
// V is cache-hit on the same spec (identical PSO; per-axis UBO differentiates at record time).
|
|
7437
|
-
bloomBlurVPipelineHandle = bloomBlurHPipelineHandle;
|
|
7438
|
-
}
|
|
7439
|
-
|
|
7440
|
-
// Blur params UBOs: 16 B std140 (texelSize.xy + radius + pad) each.
|
|
7441
|
-
// bug-20260625: one per axis -- H and V must not share a buffer (the
|
|
7442
|
-
// shared-buffer writeBuffer race made both passes blur vertically).
|
|
7443
|
-
const blurHParamsResult = runShimSyncStep(
|
|
7444
|
-
() =>
|
|
7445
|
-
rhiDevice.createBuffer({
|
|
7446
|
-
label: 'bloom-blur-h-params-ubo',
|
|
7447
|
-
size: BLUR_PARAMS_BYTES,
|
|
7448
|
-
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
7449
|
-
mappedAtCreation: false,
|
|
7450
|
-
}),
|
|
7451
|
-
'webgpu-runtime-error',
|
|
7452
|
-
'createBuffer (bloom-blur-h params UBO) succeeded',
|
|
7453
|
-
'check device.limits.maxUniformBufferBindingSize',
|
|
7454
|
-
);
|
|
7455
|
-
if (!blurHParamsResult.ok) throw blurHParamsResult.error;
|
|
7456
|
-
bloomBlurHParamsBufferHandle = blurHParamsResult.value;
|
|
7457
|
-
|
|
7458
|
-
const blurVParamsResult = runShimSyncStep(
|
|
7459
|
-
() =>
|
|
7460
|
-
rhiDevice.createBuffer({
|
|
7461
|
-
label: 'bloom-blur-v-params-ubo',
|
|
7462
|
-
size: BLUR_PARAMS_BYTES,
|
|
7463
|
-
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
7464
|
-
mappedAtCreation: false,
|
|
7465
|
-
}),
|
|
7466
|
-
'webgpu-runtime-error',
|
|
7467
|
-
'createBuffer (bloom-blur-v params UBO) succeeded',
|
|
7468
|
-
'check device.limits.maxUniformBufferBindingSize',
|
|
7469
|
-
);
|
|
7470
|
-
if (!blurVParamsResult.ok) throw blurVParamsResult.error;
|
|
7471
|
-
bloomBlurVParamsBufferHandle = blurVParamsResult.value;
|
|
7472
|
-
}
|
|
7473
8269
|
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
8270
|
+
// Bloom composite: 2-tex + sampler + UBO@3 BGL (D-4, D-5).
|
|
8271
|
+
if (bloomCompositeModule !== null && bloomSamplerHandle !== null) {
|
|
8272
|
+
const compositeBglResult = runShimSyncStep(
|
|
8273
|
+
() =>
|
|
8274
|
+
rhiDevice.createBindGroupLayout({
|
|
8275
|
+
label: 'bloom-composite-bgl',
|
|
8276
|
+
entries: [
|
|
8277
|
+
{
|
|
8278
|
+
binding: 0,
|
|
8279
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
8280
|
+
texture: { sampleType: 'float', viewDimension: '2d' },
|
|
8281
|
+
},
|
|
8282
|
+
{
|
|
8283
|
+
binding: 1,
|
|
8284
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
8285
|
+
texture: { sampleType: 'float', viewDimension: '2d' },
|
|
8286
|
+
},
|
|
8287
|
+
{
|
|
8288
|
+
binding: 2,
|
|
8289
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
8290
|
+
sampler: { type: 'filtering' },
|
|
8291
|
+
},
|
|
8292
|
+
{
|
|
8293
|
+
binding: 3,
|
|
8294
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
8295
|
+
buffer: { type: 'uniform' },
|
|
8296
|
+
},
|
|
8297
|
+
],
|
|
8298
|
+
}),
|
|
8299
|
+
'webgpu-runtime-error',
|
|
8300
|
+
'createBindGroupLayout(bloom-composite) succeeded',
|
|
8301
|
+
'check device.limits.maxBindGroupsPerPipelineLayout',
|
|
8302
|
+
);
|
|
8303
|
+
if (!compositeBglResult.ok) throw compositeBglResult.error;
|
|
8304
|
+
bloomCompositeBglHandle = adopt('binding', compositeBglResult.value, () => undefined);
|
|
8305
|
+
|
|
8306
|
+
const compositePlResult = runShimSyncStep(
|
|
8307
|
+
() =>
|
|
8308
|
+
rhiDevice.createPipelineLayout({
|
|
8309
|
+
label: 'bloom-composite-pl',
|
|
8310
|
+
bindGroupLayouts: [compositeBglResult.value],
|
|
8311
|
+
}),
|
|
8312
|
+
'webgpu-runtime-error',
|
|
8313
|
+
'createPipelineLayout(bloom-composite) succeeded',
|
|
8314
|
+
'verify bloom-composite BindGroupLayout matches shader @group(0) bindings',
|
|
8315
|
+
);
|
|
8316
|
+
if (!compositePlResult.ok) throw compositePlResult.error;
|
|
8317
|
+
adopt('binding', compositePlResult.value, () => undefined);
|
|
8318
|
+
|
|
8319
|
+
// M2-T4: bloom-composite pipeline via getOrBuildPipeline (lazy-build).
|
|
8320
|
+
{
|
|
8321
|
+
const compositeSpec: PipelineSpec = {
|
|
8322
|
+
shader: {
|
|
8323
|
+
id: 'forgeax::post::bloom-composite',
|
|
8324
|
+
passKind: 'post-process',
|
|
8325
|
+
variantSet: undefined,
|
|
7490
8326
|
},
|
|
7491
|
-
{
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
8327
|
+
attachments: {
|
|
8328
|
+
colorFormats: [HDR_COLOR_ATTACHMENT_FORMAT],
|
|
8329
|
+
depthFormat: undefined,
|
|
8330
|
+
sampleCount: 1,
|
|
7495
8331
|
},
|
|
7496
|
-
{
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
8332
|
+
geometry: {
|
|
8333
|
+
topology: 'triangle-list',
|
|
8334
|
+
stripIndexFormat: undefined,
|
|
8335
|
+
vertexLayout: {},
|
|
7500
8336
|
},
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
8337
|
+
renderState: { cullMode: 'none' },
|
|
8338
|
+
};
|
|
8339
|
+
const modules = {
|
|
8340
|
+
vertex: bloomCompositeModule,
|
|
8341
|
+
fragment: bloomCompositeModule,
|
|
8342
|
+
layout: compositePlResult.value,
|
|
8343
|
+
};
|
|
8344
|
+
try {
|
|
8345
|
+
bloomCompositePipelineHandle = getOrBuildPipeline(
|
|
8346
|
+
compositeSpec,
|
|
8347
|
+
pipelineDeviceProvider,
|
|
8348
|
+
pipelineCache,
|
|
8349
|
+
modules,
|
|
8350
|
+
) as RenderPipeline;
|
|
8351
|
+
bloomCompositePipelineHandle = adopt(
|
|
8352
|
+
'pipeline',
|
|
8353
|
+
bloomCompositePipelineHandle,
|
|
8354
|
+
() => undefined,
|
|
8355
|
+
);
|
|
8356
|
+
} catch (err) {
|
|
8357
|
+
if (err instanceof PipelineSpecError) throw err;
|
|
8358
|
+
throw new PipelineSpecError({
|
|
8359
|
+
code: 'pipeline-build-failed',
|
|
8360
|
+
detail: { cause: err },
|
|
8361
|
+
hint: 'createRenderPipeline (bloom-composite fullscreen) failed; inspect gpuMessage',
|
|
8362
|
+
});
|
|
8363
|
+
}
|
|
8364
|
+
}
|
|
7509
8365
|
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
8366
|
+
// Composite params UBO: 16 B std140 (intensity f32 + 12 B pad).
|
|
8367
|
+
const compositeParamsResult = runShimSyncStep(
|
|
8368
|
+
() =>
|
|
8369
|
+
rhiDevice.createBuffer({
|
|
8370
|
+
label: 'bloom-composite-params-ubo',
|
|
8371
|
+
size: COMPOSITE_PARAMS_BYTES,
|
|
8372
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
8373
|
+
mappedAtCreation: false,
|
|
8374
|
+
}),
|
|
8375
|
+
'webgpu-runtime-error',
|
|
8376
|
+
'createBuffer (bloom-composite params UBO) succeeded',
|
|
8377
|
+
'check device.limits.maxUniformBufferBindingSize',
|
|
8378
|
+
);
|
|
8379
|
+
if (!compositeParamsResult.ok) throw compositeParamsResult.error;
|
|
8380
|
+
bloomCompositeParamsBufferHandle = adopt(
|
|
8381
|
+
'buffer',
|
|
8382
|
+
compositeParamsResult.value,
|
|
8383
|
+
(value) => {
|
|
8384
|
+
rhiDevice.destroyBuffer(value);
|
|
8385
|
+
},
|
|
8386
|
+
);
|
|
8387
|
+
}
|
|
7521
8388
|
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
stripIndexFormat: undefined,
|
|
7538
|
-
vertexLayout: {},
|
|
7539
|
-
},
|
|
7540
|
-
renderState: { cullMode: 'none' },
|
|
8389
|
+
bloomCandidateBundle = {
|
|
8390
|
+
scope: candidateScope,
|
|
8391
|
+
generation: bloomGeneration + 1,
|
|
8392
|
+
bloomBrightPipeline: bloomBrightPipelineHandle,
|
|
8393
|
+
bloomBlurHPipeline: bloomBlurHPipelineHandle,
|
|
8394
|
+
bloomBlurVPipeline: bloomBlurVPipelineHandle,
|
|
8395
|
+
bloomCompositePipeline: bloomCompositePipelineHandle,
|
|
8396
|
+
bloomBrightBindGroupLayout: bloomBrightBglHandle,
|
|
8397
|
+
bloomBlurBindGroupLayout: bloomBlurBglHandle,
|
|
8398
|
+
bloomCompositeBindGroupLayout: bloomCompositeBglHandle,
|
|
8399
|
+
bloomSampler: bloomSamplerHandle,
|
|
8400
|
+
bloomBrightParamsBuffer: bloomBrightParamsBufferHandle,
|
|
8401
|
+
bloomBlurHParamsBuffer: bloomBlurHParamsBufferHandle,
|
|
8402
|
+
bloomBlurVParamsBuffer: bloomBlurVParamsBufferHandle,
|
|
8403
|
+
bloomCompositeParamsBuffer: bloomCompositeParamsBufferHandle,
|
|
7541
8404
|
};
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
8405
|
+
inspectBloomResources = (graph): BloomInspection => {
|
|
8406
|
+
const retiring = [...retiringBloomBundles].sort(
|
|
8407
|
+
(left, right) => left.generation - right.generation,
|
|
8408
|
+
);
|
|
8409
|
+
const active = bloomActiveBundle?.scope.isAlive() ? bloomActiveBundle : undefined;
|
|
8410
|
+
const liveRetiring = retiring.filter((bundle) => bundle.scope.state === 'retiring');
|
|
8411
|
+
const selected = active ?? liveRetiring[liveRetiring.length - 1];
|
|
8412
|
+
if (selected === undefined) {
|
|
8413
|
+
return {
|
|
8414
|
+
graphStatus: 'empty',
|
|
8415
|
+
enabled: false,
|
|
8416
|
+
targetCount: 0,
|
|
8417
|
+
targetBytes: 0,
|
|
8418
|
+
resourceCount: 0,
|
|
8419
|
+
passCount: 0,
|
|
8420
|
+
encodeCount: 0,
|
|
8421
|
+
bindGroupCount: 0,
|
|
8422
|
+
uploadCount: 0,
|
|
8423
|
+
residentChildBytes: 0,
|
|
8424
|
+
generation: 0,
|
|
8425
|
+
state: 'off',
|
|
8426
|
+
};
|
|
8427
|
+
}
|
|
8428
|
+
const state = active === undefined ? 'retiring' : 'active';
|
|
8429
|
+
const ownedBundles = [...(active === undefined ? [] : [active]), ...liveRetiring];
|
|
8430
|
+
const graphInspection = inspectStandardBloomGraph(graph);
|
|
8431
|
+
const receipts = lastSuccessfulBloomReceipts ?? {
|
|
8432
|
+
uploadCount: 0,
|
|
8433
|
+
bindGroupCount: 0,
|
|
8434
|
+
encodeCount: 0,
|
|
8435
|
+
};
|
|
8436
|
+
return {
|
|
8437
|
+
graphStatus: active === undefined ? 'empty' : graphInspection.status,
|
|
8438
|
+
enabled: active !== undefined,
|
|
8439
|
+
targetCount: active === undefined ? 0 : graphInspection.targetCount,
|
|
8440
|
+
targetBytes: active === undefined ? 0 : graphInspection.targetBytes,
|
|
8441
|
+
resourceCount: ownedBundles.reduce(
|
|
8442
|
+
(count, bundle) => count + bundle.scope.resourceDelta(),
|
|
8443
|
+
0,
|
|
8444
|
+
),
|
|
8445
|
+
passCount: active === undefined ? 0 : graphInspection.passCount,
|
|
8446
|
+
encodeCount: active === undefined ? 0 : receipts.encodeCount,
|
|
8447
|
+
bindGroupCount: active === undefined ? 0 : receipts.bindGroupCount,
|
|
8448
|
+
uploadCount: active === undefined ? 0 : receipts.uploadCount,
|
|
8449
|
+
residentChildBytes: 0,
|
|
8450
|
+
generation: selected?.generation ?? 0,
|
|
8451
|
+
state,
|
|
8452
|
+
};
|
|
7546
8453
|
};
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
if (
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
8454
|
+
commitBloomResources = () => {
|
|
8455
|
+
if (bloomCandidateBundle?.scope !== candidateScope) return;
|
|
8456
|
+
bloomActiveBundle = bloomCandidateBundle;
|
|
8457
|
+
bloomCandidateBundle = undefined;
|
|
8458
|
+
bloomGeneration += 1;
|
|
8459
|
+
bloomResourcesReady = true;
|
|
8460
|
+
};
|
|
8461
|
+
discardBloomResources = () => {
|
|
8462
|
+
if (bloomCandidateBundle?.scope !== candidateScope) return;
|
|
8463
|
+
candidateScope.abandon();
|
|
8464
|
+
bloomCandidateBundle = undefined;
|
|
8465
|
+
bloomResourcesReady = false;
|
|
8466
|
+
};
|
|
8467
|
+
const reportBloomFenceFailure = (cause: unknown): void => {
|
|
8468
|
+
if (cause instanceof RhiError) {
|
|
8469
|
+
errorRegistry.fire(cause);
|
|
8470
|
+
return;
|
|
8471
|
+
}
|
|
8472
|
+
const detailError = {
|
|
8473
|
+
code: 'bloom-fence-rejected',
|
|
8474
|
+
message: String(cause),
|
|
8475
|
+
...(cause instanceof Error ? { name: cause.name } : {}),
|
|
8476
|
+
};
|
|
8477
|
+
errorRegistry.fire(
|
|
8478
|
+
new RhiError({
|
|
8479
|
+
code: 'webgpu-runtime-error',
|
|
8480
|
+
expected: 'Bloom queue completion resolves after the submitted frame retires',
|
|
8481
|
+
hint: 'inspect renderer errors for the queue completion cause',
|
|
8482
|
+
detail: { error: detailError },
|
|
8483
|
+
}),
|
|
8484
|
+
);
|
|
8485
|
+
};
|
|
8486
|
+
retireBloomResources = (completion) => {
|
|
8487
|
+
const retiring = bloomActiveBundle;
|
|
8488
|
+
if (retiring === undefined) return;
|
|
8489
|
+
bloomActiveBundle = undefined;
|
|
8490
|
+
bloomResourcesReady = false;
|
|
8491
|
+
retiringBloomBundles.add(retiring);
|
|
8492
|
+
retiring.scope.beginRetire();
|
|
8493
|
+
void completion.then(
|
|
8494
|
+
() => {
|
|
8495
|
+
if (!retiringBloomBundles.has(retiring)) return;
|
|
8496
|
+
retiring.scope.retire();
|
|
8497
|
+
retiringBloomBundles.delete(retiring);
|
|
8498
|
+
},
|
|
8499
|
+
(cause) => {
|
|
8500
|
+
if (!retiringBloomBundles.has(retiring)) return;
|
|
8501
|
+
reportBloomFenceFailure(cause);
|
|
8502
|
+
retiring.scope.retire();
|
|
8503
|
+
retiringBloomBundles.delete(retiring);
|
|
8504
|
+
},
|
|
8505
|
+
);
|
|
8506
|
+
};
|
|
8507
|
+
drainBloomResources = () => {
|
|
8508
|
+
if (bloomCandidateBundle !== undefined) {
|
|
8509
|
+
bloomCandidateBundle.scope.abandon();
|
|
8510
|
+
bloomCandidateBundle = undefined;
|
|
8511
|
+
}
|
|
8512
|
+
if (bloomActiveBundle !== undefined) {
|
|
8513
|
+
bloomActiveBundle.scope.retire();
|
|
8514
|
+
bloomActiveBundle = undefined;
|
|
8515
|
+
}
|
|
8516
|
+
for (const retiring of retiringBloomBundles) retiring.scope.retire();
|
|
8517
|
+
retiringBloomBundles.clear();
|
|
8518
|
+
bloomResourcesReady = false;
|
|
8519
|
+
};
|
|
8520
|
+
} catch (cause) {
|
|
8521
|
+
candidateScope.abandon();
|
|
8522
|
+
if (bloomCandidateBundle?.scope === candidateScope) bloomCandidateBundle = undefined;
|
|
8523
|
+
commitBloomResources = undefined;
|
|
8524
|
+
discardBloomResources = undefined;
|
|
8525
|
+
drainBloomResources = undefined;
|
|
8526
|
+
bloomBrightPipelineHandle = null;
|
|
8527
|
+
bloomBlurHPipelineHandle = null;
|
|
8528
|
+
bloomBlurVPipelineHandle = null;
|
|
8529
|
+
bloomCompositePipelineHandle = null;
|
|
8530
|
+
bloomBrightBglHandle = null;
|
|
8531
|
+
bloomBlurBglHandle = null;
|
|
8532
|
+
bloomCompositeBglHandle = null;
|
|
8533
|
+
bloomSamplerHandle = null;
|
|
8534
|
+
bloomBrightParamsBufferHandle = null;
|
|
8535
|
+
bloomBlurHParamsBufferHandle = null;
|
|
8536
|
+
bloomBlurVParamsBufferHandle = null;
|
|
8537
|
+
bloomCompositeParamsBufferHandle = null;
|
|
8538
|
+
throw cause;
|
|
7562
8539
|
}
|
|
7563
|
-
|
|
7564
|
-
// Composite params UBO: 16 B std140 (intensity f32 + 12 B pad).
|
|
7565
|
-
const compositeParamsResult = runShimSyncStep(
|
|
7566
|
-
() =>
|
|
7567
|
-
rhiDevice.createBuffer({
|
|
7568
|
-
label: 'bloom-composite-params-ubo',
|
|
7569
|
-
size: COMPOSITE_PARAMS_BYTES,
|
|
7570
|
-
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
7571
|
-
mappedAtCreation: false,
|
|
7572
|
-
}),
|
|
7573
|
-
'webgpu-runtime-error',
|
|
7574
|
-
'createBuffer (bloom-composite params UBO) succeeded',
|
|
7575
|
-
'check device.limits.maxUniformBufferBindingSize',
|
|
7576
|
-
);
|
|
7577
|
-
if (!compositeParamsResult.ok) throw compositeParamsResult.error;
|
|
7578
|
-
bloomCompositeParamsBufferHandle = compositeParamsResult.value;
|
|
7579
|
-
}
|
|
8540
|
+
};
|
|
7580
8541
|
|
|
7581
8542
|
// ── feat-20260612-hdrp-ssao M6 / w26 + w43 + M8 / w37 ───────────────────
|
|
7582
8543
|
//
|
|
@@ -7798,7 +8759,7 @@ async function buildReadyWebGPU(
|
|
|
7798
8759
|
if (!prewarmRes.ok) throw prewarmRes.error;
|
|
7799
8760
|
}
|
|
7800
8761
|
|
|
7801
|
-
|
|
8762
|
+
const pipelineState: PipelineState = {
|
|
7802
8763
|
// feat-20260518-pbr-direct-lighting-mvp M5 / w22.10 (AC-06 dual->triple
|
|
7803
8764
|
// pipeline + D-2 + D-10): three distinct GPU render-pipeline handles
|
|
7804
8765
|
// backed by 2 distinct shader modules (unlit + pbr) x 2 vertex stride
|
|
@@ -7881,7 +8842,9 @@ async function buildReadyWebGPU(
|
|
|
7881
8842
|
meshes: meshHandles,
|
|
7882
8843
|
format: swapChainFormats.storage,
|
|
7883
8844
|
colorAttachmentFormat: swapChainFormats.view,
|
|
8845
|
+
surfaceProfile: surfaceViewFormats ? 'dual-view' : 'raw-only',
|
|
7884
8846
|
viewBindGroupLayout: viewBglResult.value,
|
|
8847
|
+
projectorAvailable,
|
|
7885
8848
|
materialBindGroupLayout: materialBglResult.value,
|
|
7886
8849
|
meshBindGroupLayout: meshArrayBglResult.value,
|
|
7887
8850
|
viewUniformBuffer: viewUboResult.value,
|
|
@@ -7990,18 +8953,28 @@ async function buildReadyWebGPU(
|
|
|
7990
8953
|
// (ensureLazyTexture, slot width/height tracking for size-drift rebuild).
|
|
7991
8954
|
// BindGroup caches survive until the intermediate view is invalidated
|
|
7992
8955
|
// by a resize (width/height drift forces null).
|
|
7993
|
-
bloomBrightPipeline:
|
|
7994
|
-
bloomBlurHPipeline:
|
|
7995
|
-
bloomBlurVPipeline:
|
|
7996
|
-
bloomCompositePipeline:
|
|
7997
|
-
bloomBrightBindGroupLayout:
|
|
7998
|
-
bloomBlurBindGroupLayout:
|
|
7999
|
-
bloomCompositeBindGroupLayout:
|
|
8000
|
-
bloomSampler:
|
|
8001
|
-
bloomBrightParamsBuffer:
|
|
8002
|
-
bloomBlurHParamsBuffer:
|
|
8003
|
-
bloomBlurVParamsBuffer:
|
|
8004
|
-
bloomCompositeParamsBuffer:
|
|
8956
|
+
bloomBrightPipeline: null,
|
|
8957
|
+
bloomBlurHPipeline: null,
|
|
8958
|
+
bloomBlurVPipeline: null,
|
|
8959
|
+
bloomCompositePipeline: null,
|
|
8960
|
+
bloomBrightBindGroupLayout: null,
|
|
8961
|
+
bloomBlurBindGroupLayout: null,
|
|
8962
|
+
bloomCompositeBindGroupLayout: null,
|
|
8963
|
+
bloomSampler: null,
|
|
8964
|
+
bloomBrightParamsBuffer: null,
|
|
8965
|
+
bloomBlurHParamsBuffer: null,
|
|
8966
|
+
bloomBlurVParamsBuffer: null,
|
|
8967
|
+
bloomCompositeParamsBuffer: null,
|
|
8968
|
+
ensureBloomResources: ensureBloomResourcesStable,
|
|
8969
|
+
getBloomResources: getBloomResourcesStable,
|
|
8970
|
+
commitBloomResources: commitBloomResourcesStable,
|
|
8971
|
+
commitBloomFrameReceipts: (receipts) => {
|
|
8972
|
+
lastSuccessfulBloomReceipts = receipts;
|
|
8973
|
+
},
|
|
8974
|
+
discardBloomResources: discardBloomResourcesStable,
|
|
8975
|
+
retireBloomResources: retireBloomResourcesStable,
|
|
8976
|
+
drainBloomResources: drainBloomResourcesStable,
|
|
8977
|
+
inspectBloomResources: inspectBloomResourcesStable,
|
|
8005
8978
|
bloomBrightTexture: null,
|
|
8006
8979
|
bloomBrightView: null,
|
|
8007
8980
|
bloomBrightWidth: 0,
|
|
@@ -8026,6 +8999,7 @@ async function buildReadyWebGPU(
|
|
|
8026
8999
|
ssaoFallbackRawView: null,
|
|
8027
9000
|
},
|
|
8028
9001
|
};
|
|
9002
|
+
return pipelineState;
|
|
8029
9003
|
}
|
|
8030
9004
|
|
|
8031
9005
|
/**
|
|
@@ -8050,8 +9024,8 @@ function ensureContextConfigured(
|
|
|
8050
9024
|
internals: WebGPURendererInternals,
|
|
8051
9025
|
state: PipelineState,
|
|
8052
9026
|
errorRegistry: RhiErrorListenerRegistry,
|
|
8053
|
-
): void {
|
|
8054
|
-
if (state.perPassResources.configured) return;
|
|
9027
|
+
): Result<void, RhiError> {
|
|
9028
|
+
if (state.perPassResources.configured) return ok(undefined);
|
|
8055
9029
|
// M6 / w41 (feat-20260510-rhi-resource-creation): configure goes through
|
|
8056
9030
|
// the forgeax `RhiCanvasContext` surface; the shim resolves the forgeax
|
|
8057
9031
|
// RhiDevice back to the underlying raw GPUDevice via RAW_DEVICE_MAP
|
|
@@ -8064,7 +9038,7 @@ function ensureContextConfigured(
|
|
|
8064
9038
|
const configuredDevice = internals.pack.instrumentation?.resolveSurfaceDevice?.(internals.device);
|
|
8065
9039
|
if (configuredDevice !== undefined && !configuredDevice.ok) {
|
|
8066
9040
|
errorRegistry.fire(configuredDevice.error);
|
|
8067
|
-
return;
|
|
9041
|
+
return err(configuredDevice.error);
|
|
8068
9042
|
}
|
|
8069
9043
|
const cfgResult = configureSurface(
|
|
8070
9044
|
internals.context,
|
|
@@ -8074,7 +9048,7 @@ function ensureContextConfigured(
|
|
|
8074
9048
|
);
|
|
8075
9049
|
if (!cfgResult.ok) {
|
|
8076
9050
|
errorRegistry.fire(cfgResult.error);
|
|
8077
|
-
return;
|
|
9051
|
+
return err(cfgResult.error);
|
|
8078
9052
|
}
|
|
8079
9053
|
state.perPassResources.configured = true;
|
|
8080
9054
|
// bug-20260612: write the actually-configured swap-chain storage format
|
|
@@ -8087,6 +9061,7 @@ function ensureContextConfigured(
|
|
|
8087
9061
|
// `${storage}-srgb` partner — derived, not duplicated.
|
|
8088
9062
|
(globalThis as { __forgeaxSwapChainFormat?: GPUTextureFormat }).__forgeaxSwapChainFormat =
|
|
8089
9063
|
state.format;
|
|
9064
|
+
return ok(undefined);
|
|
8090
9065
|
}
|
|
8091
9066
|
|
|
8092
9067
|
/**
|