@forgeax/engine-render 0.1.20 → 0.1.23
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 +365 -29
- package/dist/assembly/backend-contract.d.ts +13 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -1
- package/dist/assembly/device-feature-admission.d.ts +18 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -0
- package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
- package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +30 -19
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +19 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/render-target-host.d.ts +46 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -0
- package/dist/assets/render-asset-port.d.ts.map +1 -1
- package/dist/authoring.mjs +4 -1
- package/dist/authoring.mjs.map +1 -1
- package/dist/capture/cube-views.d.ts +21 -0
- package/dist/capture/cube-views.d.ts.map +1 -0
- package/dist/capture/scheduler.d.ts +54 -0
- package/dist/capture/scheduler.d.ts.map +1 -0
- package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
- package/dist/chunk-4L3P6DT5.mjs.map +1 -0
- package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
- package/dist/chunk-4NPUASZR.mjs.map +1 -0
- package/dist/chunk-DIYW3M5D.mjs +5085 -0
- package/dist/chunk-DIYW3M5D.mjs.map +1 -0
- package/dist/chunk-FBC4R44J.mjs +1003 -0
- package/dist/chunk-FBC4R44J.mjs.map +1 -0
- package/dist/{chunk-CDTWTHZR.mjs → chunk-FF7K3FSO.mjs} +474 -168
- package/dist/chunk-FF7K3FSO.mjs.map +1 -0
- package/dist/chunk-HZUPSYW7.mjs +1104 -0
- package/dist/chunk-HZUPSYW7.mjs.map +1 -0
- package/dist/{chunk-TBQJOK2L.mjs → chunk-QODA5UKU.mjs} +321 -269
- package/dist/chunk-QODA5UKU.mjs.map +1 -0
- package/dist/chunk-YALPS44A.mjs +296 -0
- package/dist/chunk-YALPS44A.mjs.map +1 -0
- package/dist/{chunk-4XINDLAY.mjs → chunk-ZUWKAZ2I.mjs} +1963 -2178
- package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
- package/dist/cluster-binner.d.ts +5 -0
- package/dist/cluster-binner.d.ts.map +1 -1
- package/dist/components/camera.d.ts +1 -0
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/cube-camera.d.ts +20 -0
- package/dist/components/cube-camera.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +11 -2
- package/dist/components/directional-light.d.ts.map +1 -1
- package/dist/components/directional-shadow-filter.d.ts +25 -0
- package/dist/components/directional-shadow-filter.d.ts.map +1 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts +4 -0
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/light-probe.d.ts +9 -0
- package/dist/components/light-probe.d.ts.map +1 -0
- package/dist/components/rect-area-light.d.ts +12 -0
- package/dist/components/rect-area-light.d.ts.map +1 -0
- package/dist/components/reflection-probe.d.ts +17 -0
- package/dist/components/reflection-probe.d.ts.map +1 -0
- package/dist/components/scene-instance.d.ts +4 -0
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/spot-light.d.ts +3 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +14836 -9620
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +8 -1
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/lifecycle-transaction.d.ts +2 -0
- package/dist/device/lifecycle-transaction.d.ts.map +1 -0
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +5 -0
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/gpu-driven.d.ts +26 -0
- package/dist/errors/gpu-driven.d.ts.map +1 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +9 -1
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +166 -93
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/directional-shadow-projection.d.ts +23 -0
- package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
- package/dist/extract/environment.d.ts +14 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +50 -0
- package/dist/extract/gpu-driven.d.ts.map +1 -0
- package/dist/extract/visibility.d.ts.map +1 -1
- package/dist/features/fullscreen.d.ts.map +1 -1
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +10 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +13 -2
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts +34 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +63 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +97 -2
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +64 -1
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +14 -0
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +8 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +0 -1
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +21 -19
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +4 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/kernel-cache.d.ts +36 -0
- package/dist/ibl/kernel-cache.d.ts.map +1 -0
- package/dist/index.d.ts +39 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +91 -6
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +214 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +50 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -0
- package/dist/internal.d.ts +9 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +23 -2
- package/dist/internal.mjs.map +1 -1
- package/dist/light-buffer-layout.d.ts +58 -140
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +54 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +38 -14
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts +2 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
- package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
- package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
- package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts +4 -13
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +5 -26
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -0
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +40 -7
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/point-shadow-inspection.d.ts +21 -0
- package/dist/point-shadow-inspection.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
- package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/resources.d.ts +72 -0
- package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/state.d.ts +24 -0
- package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +33 -36
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +78 -24
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts +5 -0
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +16 -6
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
- package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
- package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/index.d.ts +8 -0
- package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
- package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/session.d.ts +43 -0
- package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
- package/dist/record/gpu-timing.d.ts +20 -3
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/helpers.d.ts +0 -20
- package/dist/record/helpers.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +6 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +25 -2
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +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 +98 -6
- 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.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +119 -4
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +7 -5
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/reflection/filter.d.ts +22 -0
- package/dist/reflection/filter.d.ts.map +1 -0
- package/dist/reflection/gpu-table.d.ts +25 -0
- package/dist/reflection/gpu-table.d.ts.map +1 -0
- package/dist/reflection/inspection.d.ts +10 -0
- package/dist/reflection/inspection.d.ts.map +1 -0
- package/dist/reflection/projection.d.ts +140 -0
- package/dist/reflection/projection.d.ts.map +1 -0
- package/dist/reflection/record-owner.d.ts +60 -0
- package/dist/reflection/record-owner.d.ts.map +1 -0
- package/dist/render-contract.d.ts +62 -14
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +49 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +160 -31
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +28 -2
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer.d.ts +43 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/scene/probe-blend-record.d.ts +22 -0
- package/dist/scene/probe-blend-record.d.ts.map +1 -0
- package/dist/scene/probe-blend.d.ts +142 -0
- package/dist/scene/probe-blend.d.ts.map +1 -0
- package/dist/scene/render-scene-types.d.ts +0 -6
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +117 -9
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/visibility/bounds.d.ts +20 -0
- package/dist/scene/visibility/bounds.d.ts.map +1 -0
- package/dist/scene/visibility/budget.d.ts +16 -0
- package/dist/scene/visibility/budget.d.ts.map +1 -0
- package/dist/scene/visibility/facet.d.ts +73 -0
- package/dist/scene/visibility/facet.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-lod.d.ts +46 -0
- package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
- package/dist/scene/visibility/inspection.d.ts +132 -0
- package/dist/scene/visibility/inspection.d.ts.map +1 -0
- package/dist/scene/visibility/lod-selector.d.ts +25 -0
- package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
- package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
- package/dist/scene/visibility/types.d.ts +34 -0
- package/dist/scene/visibility/types.d.ts.map +1 -0
- package/dist/shadow-atlas.d.ts +3 -3
- package/dist/shadow-atlas.d.ts.map +1 -1
- package/dist/ssao-buffers.d.ts +3 -0
- package/dist/ssao-buffers.d.ts.map +1 -1
- package/dist/ssr/admission.d.ts +122 -0
- package/dist/ssr/admission.d.ts.map +1 -0
- package/dist/ssr/composition.d.ts +32 -0
- package/dist/ssr/composition.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +39 -0
- package/dist/ssr/errors.d.ts.map +1 -0
- package/dist/ssr/identity.d.ts +13 -0
- package/dist/ssr/identity.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +1 -1
- package/dist/targets/contracts.d.ts +66 -0
- package/dist/targets/contracts.d.ts.map +1 -0
- package/dist/targets/graph-projection.d.ts +29 -0
- package/dist/targets/graph-projection.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +34 -0
- package/dist/targets/material-source.d.ts.map +1 -0
- package/dist/targets/owner.d.ts +32 -0
- package/dist/targets/owner.d.ts.map +1 -0
- package/dist/targets/physical.d.ts +18 -0
- package/dist/targets/physical.d.ts.map +1 -0
- package/dist/targets/readback.d.ts +39 -0
- package/dist/targets/readback.d.ts.map +1 -0
- package/dist/temporal/frame-coordinator.d.ts +13 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +2 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -5
- package/dist/temporal/view.d.ts +7 -0
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/tilemap-chunk-extract-system.d.ts +1 -1
- package/dist/typed-render-graph-primitives.d.ts +5 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +1 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +9 -5
- package/dist/volume/passes.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
- package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
- package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
- package/src/__tests__/cluster-binner.unit.test.ts +35 -0
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +12 -2
- package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
- package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
- package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
- package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
- package/src/__tests__/cube-camera.integration.test.ts +42 -0
- package/src/__tests__/detached-inspection.integration.test.ts +6 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +85 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +62 -6
- package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
- package/src/__tests__/environment-generation.integration.test.ts +16 -0
- package/src/__tests__/evidence-identity.ts +13 -0
- package/src/__tests__/evidence-identity.unit.test.ts +24 -0
- package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
- package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
- package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
- package/src/__tests__/factory-contract.integration.test.ts +288 -29
- package/src/__tests__/fog-component.unit.test.ts +67 -0
- package/src/__tests__/fog.integration.test.ts +86 -0
- package/src/__tests__/frame-observation.contract.test.ts +15 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -8
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +18 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +579 -17
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +101 -6
- package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
- package/src/__tests__/gpu-lod-evidence.ts +188 -0
- package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
- package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
- package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
- package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
- package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
- package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
- package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
- package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
- package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
- package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
- package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
- package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
- package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
- package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
- package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
- package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
- package/src/__tests__/gpu-scene.unit.test.ts +49 -3
- package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
- package/src/__tests__/instances-culling.integration.test.ts +94 -0
- package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
- package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
- package/src/__tests__/light-inspection.unit.test.ts +55 -0
- package/src/__tests__/light-resource-error.unit.test.ts +43 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
- package/src/__tests__/lod-bounds.unit.test.ts +42 -0
- package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
- package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
- package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
- package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
- package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
- package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
- package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
- package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
- package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
- package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
- package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
- package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
- package/src/__tests__/lod-selector.profile.test.ts +35 -0
- package/src/__tests__/lod-selector.unit.test.ts +68 -0
- package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
- package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
- package/src/__tests__/ltc-resources.unit.test.ts +32 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
- package/src/__tests__/material-observation.integration.test.ts +101 -0
- package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
- package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
- package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
- package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
- package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
- package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
- package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
- package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
- package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
- package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
- package/src/__tests__/occlusion-query.evidence.ts +178 -0
- package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
- package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
- package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
- package/src/__tests__/probe-bindings.unit.test.ts +200 -0
- package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
- package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
- package/src/__tests__/probe-capacity.integration.test.ts +97 -0
- package/src/__tests__/probe-recovery.integration.test.ts +104 -0
- package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
- package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
- package/src/__tests__/profiler-phase-token.test.ts +1 -0
- package/src/__tests__/public-surface.test-d.ts +10 -0
- package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
- package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
- package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
- package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
- package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
- package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
- package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
- package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
- package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
- package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
- package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
- package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
- package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +19 -3
- package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
- package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
- package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
- package/src/__tests__/render-target-admission.unit.test.ts +80 -0
- package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
- package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
- package/src/__tests__/render-target-material-source.test-d.ts +30 -0
- package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
- package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
- package/src/__tests__/render-target-owner.unit.test.ts +122 -0
- package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
- package/src/__tests__/render-target-public-schema.json +107 -0
- package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
- package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
- package/src/__tests__/render-target-readback.integration.test.ts +90 -0
- package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +124 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
- package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
- package/src/__tests__/skin-motion-regression.unit.test.ts +154 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +13 -4
- package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
- package/src/__tests__/ssr-composition.unit.test.ts +46 -0
- package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
- package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
- package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
- package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +299 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +15 -5
- package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
- package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +441 -0
- package/src/__tests__/standard-lighting.integration.test.ts +67 -0
- package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
- package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
- package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
- package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
- package/src/__tests__/temporal-view.unit.test.ts +3 -3
- package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
- package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
- package/src/__tests__/visibility-budget.integration.test.ts +140 -0
- package/src/__tests__/visibility-budget.unit.test.ts +70 -0
- package/src/__tests__/visibility-extract.integration.test.ts +11 -6
- package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
- package/src/assembly/backend-contract.ts +13 -0
- package/src/assembly/device-feature-admission.ts +80 -0
- package/src/assembly/directional-shadow-inspection.ts +122 -0
- package/src/assembly/factory.ts +1411 -366
- package/src/assembly/host-contract.ts +35 -0
- package/src/assembly/render-target-host.ts +847 -0
- package/src/assets/asset-decoders.ts +1 -1
- package/src/assets/render-asset-port.ts +7 -1
- package/src/capture/cube-views.ts +69 -0
- package/src/capture/scheduler.ts +265 -0
- package/src/cluster-binner.ts +31 -0
- package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
- package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
- package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
- package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
- package/src/components/camera.ts +4 -0
- package/src/components/cube-camera.ts +52 -0
- package/src/components/directional-light.ts +22 -2
- package/src/components/directional-shadow-filter.ts +57 -0
- package/src/components/index.ts +5 -0
- package/src/components/light-helpers.ts +133 -8
- package/src/components/light-probe.ts +13 -0
- package/src/components/rect-area-light.ts +15 -0
- package/src/components/reflection-probe.ts +49 -0
- package/src/components/scene-instance.ts +6 -0
- package/src/components/spot-light.ts +3 -0
- package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
- package/src/device/gpu-residency.ts +140 -9
- package/src/device/lifecycle-transaction.ts +12 -0
- package/src/environment/frame.ts +6 -24
- package/src/environment/lifecycle.ts +20 -1
- package/src/errors/gpu-driven.ts +92 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/recover.ts +32 -0
- package/src/errors/render.ts +341 -143
- package/src/extract/camera.ts +3 -3
- package/src/extract/directional-shadow-projection.ts +61 -0
- package/src/extract/environment.ts +57 -0
- package/src/extract/gpu-driven.ts +108 -0
- package/src/extract/visibility.ts +2 -1
- package/src/features/fullscreen.ts +3 -0
- package/src/features/targets.ts +4 -1
- package/src/features/types.ts +10 -0
- package/src/fullscreen-post-process-pass.ts +43 -2
- package/src/gpu-driven/batch-topology.ts +207 -28
- package/src/gpu-driven/prepared-draw.ts +208 -0
- package/src/gpu-driven/production-raster.ts +953 -43
- package/src/gpu-driven/view-gpu.ts +411 -29
- package/src/gpu-scene-schema.ts +13 -0
- package/src/gpu-scene.ts +140 -64
- package/src/gpu-usage.ts +0 -1
- package/src/hdrp-buffers.ts +169 -63
- package/src/ibl/IblPipelineCache.ts +11 -0
- package/src/ibl/kernel-cache.ts +88 -0
- package/src/index.ts +222 -0
- package/src/inspection-types.ts +276 -1
- package/src/instances-derived-bounds.ts +171 -0
- package/src/internal.ts +38 -0
- package/src/light-buffer-layout.ts +155 -310
- package/src/materials.ts +16 -2
- package/src/mesh-material-bindings.ts +132 -0
- package/src/pbr-pipeline.ts +255 -60
- package/src/pipeline/standard-forward-lane.ts +93 -16
- package/src/pipeline/standard-lighting/errors.ts +6 -0
- package/src/pipeline/standard-lighting/graph.ts +158 -0
- package/src/pipeline/standard-lighting/inspection.ts +88 -0
- package/src/pipeline/standard-lighting/layout.ts +31 -0
- package/src/pipeline/standard-lighting/light-frame.ts +35 -0
- package/src/pipeline/standard-lighting/membership.ts +43 -0
- package/src/pipeline/standard-lighting/prepare.ts +37 -0
- package/src/pipeline/standard-lighting/topology.ts +120 -0
- package/src/pipeline/standard-lighting/transport.ts +78 -0
- package/src/pipeline/standard-pipeline.ts +124 -193
- package/src/pipeline/standard-post.ts +26 -7
- package/src/pipeline/standard-profile.ts +17 -47
- package/src/pipeline-builder.ts +5 -2
- package/src/pipeline-spec.ts +95 -21
- package/src/plugin.ts +4 -0
- package/src/point-shadow-inspection.ts +55 -0
- package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
- package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
- package/src/prepare/extended-lighting/resources.ts +389 -0
- package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
- package/src/prepare/extended-lighting/state.ts +92 -0
- package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +64 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
- package/src/record/frame-lighting.ts +591 -452
- package/src/record/frame-snapshot.ts +92 -39
- package/src/record/frame-targets.ts +17 -0
- package/src/record/frame.ts +662 -158
- package/src/record/gpu-pass-timing/contract.ts +180 -0
- package/src/record/gpu-pass-timing/errors.ts +34 -0
- package/src/record/gpu-pass-timing/index.ts +37 -0
- package/src/record/gpu-pass-timing/parser.ts +97 -0
- package/src/record/gpu-pass-timing/session.ts +938 -0
- package/src/record/gpu-timing.ts +112 -39
- package/src/record/helpers.ts +0 -58
- package/src/record/main-pass-geometry.ts +324 -119
- package/src/record/main-pass-material.ts +103 -10
- package/src/record/main-pass-sprite-draws.ts +99 -103
- package/src/record/main-pass.ts +71 -15
- package/src/record/mesh-ssbo.ts +47 -36
- package/src/record/render-context.ts +136 -3
- package/src/record/shadow-pass.ts +83 -68
- package/src/record/skybox-post-pass.ts +20 -4
- package/src/record/typed-frame-graph.ts +1034 -43
- package/src/record/view-ubo.ts +72 -47
- package/src/reflection/filter.ts +81 -0
- package/src/reflection/gpu-table.ts +44 -0
- package/src/reflection/inspection.ts +67 -0
- package/src/reflection/projection.ts +476 -0
- package/src/reflection/record-owner.ts +1283 -0
- package/src/render-contract.ts +93 -10
- package/src/render-graph-primitives.ts +9 -4
- package/src/render-pipeline.ts +122 -2
- package/src/render-system-extract.ts +1041 -225
- package/src/render-system.ts +1310 -85
- package/src/renderer.ts +222 -0
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
- package/src/scene/probe-blend-record.ts +54 -0
- package/src/scene/probe-blend.ts +813 -0
- package/src/scene/render-scene-types.ts +0 -7
- package/src/scene/render-scene.ts +942 -171
- package/src/scene/visibility/bounds.ts +57 -0
- package/src/scene/visibility/budget.ts +40 -0
- package/src/scene/visibility/facet.ts +400 -0
- package/src/scene/visibility/gpu-frame-timing.ts +101 -0
- package/src/scene/visibility/gpu-lod.ts +119 -0
- package/src/scene/visibility/inspection.ts +313 -0
- package/src/scene/visibility/lod-selector.ts +84 -0
- package/src/scene/visibility/occlusion-confidence.ts +478 -0
- package/src/scene/visibility/occlusion-pass.ts +165 -0
- package/src/scene/visibility/occlusion-query-pool.ts +178 -0
- package/src/scene/visibility/occlusion-runtime.ts +672 -0
- package/src/scene/visibility/types.ts +50 -0
- package/src/shadow-atlas.ts +5 -5
- package/src/ssao-buffers.ts +9 -2
- package/src/ssr/admission.ts +451 -0
- package/src/ssr/composition.ts +73 -0
- package/src/ssr/errors.ts +76 -0
- package/src/ssr/identity.ts +12 -0
- package/src/systems/skin-palette-allocator.ts +1 -1
- package/src/targets/contracts.ts +182 -0
- package/src/targets/graph-projection.ts +169 -0
- package/src/targets/material-source.ts +143 -0
- package/src/targets/owner.ts +214 -0
- package/src/targets/physical.ts +18 -0
- package/src/targets/readback.ts +153 -0
- package/src/temporal/frame-coordinator.ts +26 -0
- package/src/temporal/frame.ts +14 -1
- package/src/temporal/view.ts +11 -0
- package/src/tilemap-chunk-extract-system.ts +4 -4
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
- package/src/typed-render-graph-primitives.ts +53 -3
- package/src/typed-shadow-passes.ts +46 -40
- package/src/volume/passes.ts +61 -58
- package/dist/.tsbuildinfo +0 -1
- package/dist/chunk-4XINDLAY.mjs.map +0 -1
- package/dist/chunk-CDTWTHZR.mjs.map +0 -1
- package/dist/chunk-M2MP4ZOU.mjs +0 -73
- package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
- package/dist/chunk-PCHBPYML.mjs.map +0 -1
- package/dist/chunk-TBQJOK2L.mjs.map +0 -1
- package/dist/chunk-VFPLTKCU.mjs.map +0 -1
package/src/assembly/factory.ts
CHANGED
|
@@ -50,17 +50,21 @@ import type {
|
|
|
50
50
|
PipelineLayout,
|
|
51
51
|
RenderPipeline,
|
|
52
52
|
Result,
|
|
53
|
-
RhiAdapter,
|
|
54
53
|
RhiCanvasContext,
|
|
55
54
|
RhiDevice,
|
|
56
55
|
Sampler,
|
|
57
56
|
ShaderModule,
|
|
57
|
+
TextureView,
|
|
58
58
|
} from '@forgeax/engine-rhi';
|
|
59
59
|
import { err, ok, RhiError, validateDrawArgs } from '@forgeax/engine-rhi';
|
|
60
60
|
import { registerPropagateTransforms } from '@forgeax/engine-scene';
|
|
61
61
|
import {
|
|
62
|
+
createStandardPbrArtifactReceipt,
|
|
62
63
|
DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
|
|
63
64
|
findVariantByKey,
|
|
65
|
+
LTC_TABLE_HEIGHT,
|
|
66
|
+
LTC_TABLE_WIDTH,
|
|
67
|
+
LTC_TABLES,
|
|
64
68
|
type MaterialShaderEntry,
|
|
65
69
|
type MaterialShaderManifestEntry,
|
|
66
70
|
ShaderCatalog,
|
|
@@ -92,12 +96,16 @@ import {
|
|
|
92
96
|
RendererOperationError,
|
|
93
97
|
RenderFeatureCapabilityMissingError,
|
|
94
98
|
RenderFeatureStageFailedError,
|
|
99
|
+
StandardProfileInvalidError,
|
|
95
100
|
} from '../errors/render';
|
|
96
101
|
import { createRenderFeatureHost, type RenderFeatureHost } from '../features/host';
|
|
97
102
|
import type { RenderFeatureMaterialShaderBindingContract } from '../features/plan';
|
|
98
103
|
import { RENDER_FEATURE_VERTEX_LAYOUTS } from '../features/prepared-graphics';
|
|
99
104
|
import type { RenderFeature, RenderFeatureShaderModuleMode } from '../features/types';
|
|
100
|
-
import
|
|
105
|
+
import {
|
|
106
|
+
type PostProcessShaderEntry,
|
|
107
|
+
postProcessShaderModuleLabel,
|
|
108
|
+
} from '../fullscreen-post-process-pass';
|
|
101
109
|
import { glyphTextLayoutSystem } from '../glyph-text-layout-system';
|
|
102
110
|
import { GpuBuffer } from '../gpu-resource';
|
|
103
111
|
import { GPU_SHADER_STAGE_FRAGMENT, GPU_SHADER_STAGE_VERTEX } from '../gpu-stage';
|
|
@@ -127,18 +135,14 @@ import {
|
|
|
127
135
|
LostListenerRegistry,
|
|
128
136
|
RhiErrorListenerRegistry,
|
|
129
137
|
} from '../lifecycle';
|
|
130
|
-
import {
|
|
131
|
-
assertStorageBufferCap,
|
|
132
|
-
LIGHT_ARRAY_HEADER_BYTES,
|
|
133
|
-
LIGHT_ARRAY_MAX_SLOTS,
|
|
134
|
-
POINT_LIGHT_STD430_BYTES,
|
|
135
|
-
SPOT_LIGHT_STD430_BYTES,
|
|
136
|
-
} from '../light-buffer-layout';
|
|
138
|
+
import { assertStorageBufferCap } from '../light-buffer-layout';
|
|
137
139
|
import {
|
|
138
140
|
buildPbrMaterialUserRegionEntries,
|
|
139
141
|
buildPbrPipelineLayouts,
|
|
140
142
|
buildPbrSkinLayouts,
|
|
141
|
-
|
|
143
|
+
createHdrpSkinBindGroupLayoutDescriptor,
|
|
144
|
+
type PipelineGroup2Contract,
|
|
145
|
+
resolvePipelineGroup2Contract,
|
|
142
146
|
SKIN_MATERIAL_SHADER_ID,
|
|
143
147
|
} from '../pbr-pipeline';
|
|
144
148
|
import { STANDARD_CLUSTER_MEMBERSHIP_WGSL, standardPipeline } from '../pipeline/standard-pipeline';
|
|
@@ -164,7 +168,31 @@ import {
|
|
|
164
168
|
PipelineSpecError,
|
|
165
169
|
} from '../pipeline-spec';
|
|
166
170
|
import { POINTS_LINES_MATERIAL_SHADER_ID } from '../points-lines/record';
|
|
171
|
+
import type { ExtendedLightingResourceCandidate } from '../prepare/extended-lighting/resources';
|
|
172
|
+
import {
|
|
173
|
+
COOKIE_MATRIX_BYTES,
|
|
174
|
+
COOKIE_SLICE_SIZE,
|
|
175
|
+
deriveExtendedLightingCapability,
|
|
176
|
+
EXTENDED_LIGHTING_TOPOLOGY,
|
|
177
|
+
extendedLightingSampledTextureCapacityAvailable,
|
|
178
|
+
IES_SLICE_HEIGHT,
|
|
179
|
+
IES_SLICE_WIDTH,
|
|
180
|
+
} from '../prepare/extended-lighting/resources';
|
|
181
|
+
import { createCookieProjectionMatrixData } from '../prepare/extended-lighting/spot-modifiers';
|
|
182
|
+
import {
|
|
183
|
+
createExtendedLightingState,
|
|
184
|
+
projectExtendedLightingInspection,
|
|
185
|
+
promoteExtendedLightingCandidate,
|
|
186
|
+
} from '../prepare/extended-lighting/state';
|
|
167
187
|
import type { FrameObservationOptions } from '../record/frame';
|
|
188
|
+
import type { GpuPassTimingReason } from '../record/gpu-pass-timing/errors.js';
|
|
189
|
+
import {
|
|
190
|
+
createGpuPassTimingSession,
|
|
191
|
+
DEFAULT_GPU_PASS_TIMING_OPTIONS,
|
|
192
|
+
type GpuPassTimingCapture,
|
|
193
|
+
type GpuPassTimingObservation,
|
|
194
|
+
type GpuPassTimingSession,
|
|
195
|
+
} from '../record/gpu-pass-timing/index.js';
|
|
168
196
|
import { GpuTimingCapture, type VolumeTimingObservation } from '../record/gpu-timing';
|
|
169
197
|
import type { BloomPersistentBundle } from '../record/render-context';
|
|
170
198
|
import { SHADOW_CASTER_BUFFER_SIZE } from '../record/shadow-pass';
|
|
@@ -190,8 +218,10 @@ import type {
|
|
|
190
218
|
RenderWorldLease,
|
|
191
219
|
VolumetricFogShaderSources,
|
|
192
220
|
} from '../render-contract';
|
|
193
|
-
import { STANDARD_OUTPUT_TRANSFORM_FEATURE_ID } from '../render-contract';
|
|
221
|
+
import { FXAA_POST_PROCESS_ID, STANDARD_OUTPUT_TRANSFORM_FEATURE_ID } from '../render-contract';
|
|
222
|
+
|
|
194
223
|
import {
|
|
224
|
+
attachGpuPassTimingSession,
|
|
195
225
|
configureSurface,
|
|
196
226
|
createRenderSystem,
|
|
197
227
|
MATERIAL_PER_ENTITY_STRIDE,
|
|
@@ -199,6 +229,7 @@ import {
|
|
|
199
229
|
type RenderSystem,
|
|
200
230
|
selectSwapChainFormat,
|
|
201
231
|
} from '../render-system';
|
|
232
|
+
import { createGpuPassTimingObservationStore, observeGpuPassTimingDisabled } from '../renderer.js';
|
|
202
233
|
import { postSpawnResolveJoints } from '../scene-instances/post-spawn-resolve-joints';
|
|
203
234
|
import {
|
|
204
235
|
createSkinPaletteAllocator,
|
|
@@ -206,7 +237,12 @@ import {
|
|
|
206
237
|
} from '../systems/skin-palette-allocator';
|
|
207
238
|
import { tilemapChunkExtractSystem } from '../tilemap-chunk-extract-system';
|
|
208
239
|
import { EngineEnvironmentError, type RhiBackendPack } from './backend-contract';
|
|
240
|
+
import {
|
|
241
|
+
deviceOptionsForAdapter,
|
|
242
|
+
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES,
|
|
243
|
+
} from './device-feature-admission';
|
|
209
244
|
import type { RendererAssemblyImplementation, RendererHostImplementation } from './host-contract';
|
|
245
|
+
import { createRenderTargetHost } from './render-target-host';
|
|
210
246
|
import { projectRendererOutputInspection } from './renderer-inspection';
|
|
211
247
|
import { isRendererStateOperational } from './renderer-lifecycle';
|
|
212
248
|
|
|
@@ -226,6 +262,19 @@ const GPU_ENVIRONMENT_ERROR_CODES = new Set([
|
|
|
226
262
|
'oom',
|
|
227
263
|
]);
|
|
228
264
|
|
|
265
|
+
/**
|
|
266
|
+
* The boot artifact is only a registry/prewarm seed. It is deliberately not
|
|
267
|
+
* an active lighting decision: every frame derives its Standard variant from
|
|
268
|
+
* `RenderPipelineFrame.standardLighting.kind` at record time. Keeping this
|
|
269
|
+
* boot-only value named as such prevents the eager module from becoming a
|
|
270
|
+
* second topology authority.
|
|
271
|
+
*/
|
|
272
|
+
const STANDARD_BOOT_TOPOLOGY_KIND: 'no-local-lights' | 'clustered' = 'no-local-lights';
|
|
273
|
+
|
|
274
|
+
function standardBootClusterAxis(): boolean {
|
|
275
|
+
return STANDARD_BOOT_TOPOLOGY_KIND === 'clustered';
|
|
276
|
+
}
|
|
277
|
+
|
|
229
278
|
function classifyEnvErrorReason(
|
|
230
279
|
base: string,
|
|
231
280
|
primary: { code?: unknown; name?: unknown } | undefined,
|
|
@@ -275,8 +324,8 @@ function adaptMipmapShaderModuleFactory(
|
|
|
275
324
|
|
|
276
325
|
// Re-export registerAdvanceAnimationPlayer so consumers can wire it.
|
|
277
326
|
// Re-export registerPropagateTransforms so consumers can wire the
|
|
278
|
-
//
|
|
279
|
-
// derived
|
|
327
|
+
// GlobalTransform.world mat4 derivation (audio listener sync + picking read the
|
|
328
|
+
// derived GlobalTransform.world from scripts).
|
|
280
329
|
|
|
281
330
|
// feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4): a single
|
|
282
331
|
// shared empty ArrayBuffer used by `buildPipelineContext` to synthesize the
|
|
@@ -415,7 +464,7 @@ function toGpuVertexBufferLayouts(
|
|
|
415
464
|
* Bundler-layer injection accepted by `createRenderer` (and proxied by
|
|
416
465
|
* `createApp`) as the optional third argument.
|
|
417
466
|
*
|
|
418
|
-
* feat-20260608-create-app-param-surface-trim / M2 / D-3: aggregates the
|
|
467
|
+
* feat-20260608-create-app-param-surface-trim / M2 / D-3: aggregates the three
|
|
419
468
|
* host-injected build-tool channels:
|
|
420
469
|
*
|
|
421
470
|
* - shaderManifestUrl: the URL the host's vite-plugin-shader emit step
|
|
@@ -429,6 +478,9 @@ function toGpuVertexBufferLayouts(
|
|
|
429
478
|
* AssetRegistry third ctor slot so DDC-miss assets can lazy-import.
|
|
430
479
|
* Absent => shipped form (a DDC miss fails fast with `asset-not-imported`).
|
|
431
480
|
*
|
|
481
|
+
* - build: exact checkout revision emitted by the build-tool adapter and
|
|
482
|
+
* carried into renderer-owned inspection identity.
|
|
483
|
+
*
|
|
432
484
|
* The interface is duplicated structurally in `@forgeax/engine-app`
|
|
433
485
|
* (BundlerOptions exported from packages/app); demos consume through the app
|
|
434
486
|
* package, the engine accepts via this local minimal shape so packages/runtime
|
|
@@ -439,6 +491,8 @@ function toGpuVertexBufferLayouts(
|
|
|
439
491
|
export interface BundlerOptions {
|
|
440
492
|
readonly importTransport?: ImportTransport | undefined;
|
|
441
493
|
readonly shaderManifestUrl?: string | undefined;
|
|
494
|
+
/** Exact checkout revision emitted by the build-tool adapter. */
|
|
495
|
+
readonly build?: string | undefined;
|
|
442
496
|
}
|
|
443
497
|
|
|
444
498
|
// ─── Backend pack — M3 auto-select internal shape ───────────────────────────
|
|
@@ -450,18 +504,14 @@ export interface BundlerOptions {
|
|
|
450
504
|
/**
|
|
451
505
|
* Per-variant PipelineLayout selector (M4.5 / D-10 option A).
|
|
452
506
|
*
|
|
453
|
-
* The HDRP
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
* 1-slot mesh-array BGL chain (`pbrPipelineLayout`).
|
|
507
|
+
* The resolved HDRP group(2) contract needs the 7-slot BGL chain
|
|
508
|
+
* (`hdrpPbrPipelineLayout`); ordinary material contracts keep the 1-slot
|
|
509
|
+
* mesh-array BGL chain (`pbrPipelineLayout`). Variant keys select source
|
|
510
|
+
* artifacts only and never override this contract.
|
|
458
511
|
*
|
|
459
|
-
* Returns `null`
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
* resolved upstream by `findVariantByKey` is the URP variant in that
|
|
463
|
-
* case (manifest entry with all-true definesKey === '' was registered
|
|
464
|
-
* with isHdrpActive=false at boot when storage-buffer caps are absent).
|
|
512
|
+
* Returns `null` when the selected layout is unavailable. An HDRP variant
|
|
513
|
+
* never falls back to the URP layout: its clustered WGSL and group(2) ABI
|
|
514
|
+
* must be admitted together or the draw fails closed.
|
|
465
515
|
*
|
|
466
516
|
* Pure function — exported for unit-test access (M4.5 / w35).
|
|
467
517
|
*/
|
|
@@ -469,8 +519,9 @@ export interface BundlerOptions {
|
|
|
469
519
|
* Closed enum of pipeline-layout kinds the selector can dispatch to.
|
|
470
520
|
*
|
|
471
521
|
* `pbr` — standard PBR (1-entry mesh-array BGL).
|
|
472
|
-
* `pbr-skin` — bug-20260611-skin-pipeline-layout:
|
|
473
|
-
* (binding 0 meshes + binding 1 palette
|
|
522
|
+
* `pbr-skin` — bug-20260611-skin-pipeline-layout: 3-entry mesh-array BGL
|
|
523
|
+
* (binding 0 meshes + binding 1 current palette + binding 2 previous
|
|
524
|
+
* palette) for the `forgeax::pbr-skin`
|
|
474
525
|
* material shader.
|
|
475
526
|
* `hdrp-pbr` — feat-20260609 HDRP cluster-forward variant (7-slot group(2)
|
|
476
527
|
* BGL substituted at slot 2).
|
|
@@ -490,7 +541,15 @@ export interface BundlerOptions {
|
|
|
490
541
|
* material-shader layouts add a `LayoutKind` member + a PipelineState slot
|
|
491
542
|
* + a switch arm here in one cut (charter P3 extensibility).
|
|
492
543
|
*/
|
|
493
|
-
export type LayoutKind = 'pbr' | 'pbr-skin' | 'hdrp-pbr' | 'sprite-urp' | 'unlit-urp';
|
|
544
|
+
export type LayoutKind = 'pbr' | 'pbr-skin' | 'hdrp-pbr' | 'hdrp-skin' | 'sprite-urp' | 'unlit-urp';
|
|
545
|
+
|
|
546
|
+
/** The closed group(2) contract resolved from the selected WGSL artifact. */
|
|
547
|
+
export type { PipelineGroup2Contract } from '../pbr-pipeline';
|
|
548
|
+
export { resolvePipelineGroup2Contract } from '../pbr-pipeline';
|
|
549
|
+
|
|
550
|
+
function directionalPcssCapable(backendKind: RhiDevice['caps']['backendKind']): boolean {
|
|
551
|
+
return backendKind === 'webgpu' || backendKind === 'wgpu-native';
|
|
552
|
+
}
|
|
494
553
|
|
|
495
554
|
/**
|
|
496
555
|
* Return every device-capability HDRP PBR variant that needs a lazy module
|
|
@@ -502,17 +561,60 @@ export type LayoutKind = 'pbr' | 'pbr-skin' | 'hdrp-pbr' | 'sprite-urp' | 'unlit
|
|
|
502
561
|
export function selectHdrpPbrPrewarmVariants(
|
|
503
562
|
manifestEntry: MaterialShaderManifestEntry | undefined,
|
|
504
563
|
storageBufferCapable: boolean,
|
|
505
|
-
|
|
564
|
+
extendedLightingShaderAvailableOrTransmissionCapable = true,
|
|
565
|
+
transmissionCapable = true,
|
|
566
|
+
directionalPcssAvailable = true,
|
|
506
567
|
projectorAvailable: boolean = true,
|
|
507
568
|
): readonly MaterialShaderManifestEntry['variants'][number][] {
|
|
569
|
+
const variants = manifestEntry?.variants ?? [];
|
|
570
|
+
const hasExtendedLightingAxis = variants.some(
|
|
571
|
+
(variant) => 'EXTENDED_LIGHTING_AVAILABLE' in variant.defines,
|
|
572
|
+
);
|
|
573
|
+
const extendedLightingShaderAvailable = hasExtendedLightingAxis
|
|
574
|
+
? extendedLightingShaderAvailableOrTransmissionCapable
|
|
575
|
+
: true;
|
|
576
|
+
const effectiveTransmissionCapable = hasExtendedLightingAxis
|
|
577
|
+
? transmissionCapable
|
|
578
|
+
: extendedLightingShaderAvailableOrTransmissionCapable;
|
|
579
|
+
// HDRP's clustered group(2) ABI is storage-buffer backed. A downlevel
|
|
580
|
+
// WebGL2 device must stay on the URP variant; prewarming an HDRP source
|
|
581
|
+
// here would advertise an inactive route and can exceed its fragment
|
|
582
|
+
// uniform-buffer budget before the first frame.
|
|
583
|
+
if (!storageBufferCapable) return [];
|
|
508
584
|
return (
|
|
509
|
-
|
|
585
|
+
variants.filter(
|
|
510
586
|
(variant) =>
|
|
511
587
|
variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable &&
|
|
588
|
+
(!('EXTENDED_LIGHTING_AVAILABLE' in variant.defines) ||
|
|
589
|
+
variant.defines.EXTENDED_LIGHTING_AVAILABLE === extendedLightingShaderAvailable) &&
|
|
512
590
|
variant.defines.CLUSTER_FORWARD_AVAILABLE === true &&
|
|
591
|
+
(!('DIRECTIONAL_PCSS_AVAILABLE' in variant.defines) ||
|
|
592
|
+
variant.defines.DIRECTIONAL_PCSS_AVAILABLE === directionalPcssAvailable) &&
|
|
513
593
|
(!('PROJECTOR_AVAILABLE' in variant.defines) ||
|
|
514
594
|
variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) &&
|
|
515
|
-
|
|
595
|
+
// ProbeBlend is an object-level opt-in. Boot/prewarm modules must
|
|
596
|
+
// stay on the no-probe ABI; a probe record selects its true variant
|
|
597
|
+
// later together with the probe-enabled slot-3 BGL.
|
|
598
|
+
variant.defines.PROBE_BLEND_AVAILABLE !== true &&
|
|
599
|
+
(effectiveTransmissionCapable || variant.defines.TRANSMISSION_AVAILABLE !== true),
|
|
600
|
+
) ?? []
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** Return every active-capability pbr-skin variant used by URP and HDRP draws. */
|
|
605
|
+
export function selectSkinPrewarmVariants(
|
|
606
|
+
manifestEntry: MaterialShaderManifestEntry | undefined,
|
|
607
|
+
storageBufferCapable: boolean,
|
|
608
|
+
): readonly MaterialShaderManifestEntry['variants'][number][] {
|
|
609
|
+
return (
|
|
610
|
+
manifestEntry?.variants.filter(
|
|
611
|
+
(variant) =>
|
|
612
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable &&
|
|
613
|
+
// The clustered skin ABI is only active on the storage-backed tier;
|
|
614
|
+
// storage=false must never prewarm or expose it as a usable variant.
|
|
615
|
+
(storageBufferCapable || variant.defines.CLUSTER_FORWARD_AVAILABLE !== true) &&
|
|
616
|
+
// ProbeBlend is selected per object, never by the boot skin module.
|
|
617
|
+
variant.defines.PROBE_BLEND_AVAILABLE !== true,
|
|
516
618
|
) ?? []
|
|
517
619
|
);
|
|
518
620
|
}
|
|
@@ -525,7 +627,9 @@ export function selectHdrpPbrPrewarmVariants(
|
|
|
525
627
|
export function selectStandardPbrTransmissionPrewarmVariants(
|
|
526
628
|
manifestEntry: MaterialShaderManifestEntry | undefined,
|
|
527
629
|
storageBufferCapable: boolean,
|
|
630
|
+
directionalPcssAvailable = true,
|
|
528
631
|
projectorAvailable: boolean = true,
|
|
632
|
+
extendedLightingShaderAvailable = true,
|
|
529
633
|
): readonly MaterialShaderManifestEntry['variants'][number][] {
|
|
530
634
|
if (manifestEntry === undefined) {
|
|
531
635
|
throw new Error('Standard material shader manifest row is missing');
|
|
@@ -535,10 +639,15 @@ export function selectStandardPbrTransmissionPrewarmVariants(
|
|
|
535
639
|
const declared = manifestEntry.variants.find(
|
|
536
640
|
(variant) =>
|
|
537
641
|
variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable &&
|
|
538
|
-
variant.defines.CLUSTER_FORWARD_AVAILABLE ===
|
|
642
|
+
variant.defines.CLUSTER_FORWARD_AVAILABLE === standardBootClusterAxis() &&
|
|
539
643
|
variant.defines.VERTEX_COLOR_AVAILABLE === false &&
|
|
644
|
+
(!('DIRECTIONAL_PCSS_AVAILABLE' in variant.defines) ||
|
|
645
|
+
variant.defines.DIRECTIONAL_PCSS_AVAILABLE === directionalPcssAvailable) &&
|
|
540
646
|
(!('PROJECTOR_AVAILABLE' in variant.defines) ||
|
|
541
647
|
variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) &&
|
|
648
|
+
(!('EXTENDED_LIGHTING_AVAILABLE' in variant.defines) ||
|
|
649
|
+
variant.defines.EXTENDED_LIGHTING_AVAILABLE === extendedLightingShaderAvailable) &&
|
|
650
|
+
variant.defines.PROBE_BLEND_AVAILABLE !== true &&
|
|
542
651
|
variant.defines.TRANSMISSION_AVAILABLE === transmissionAvailable,
|
|
543
652
|
);
|
|
544
653
|
if (declared === undefined) {
|
|
@@ -566,23 +675,39 @@ export function selectNoColorPbrVariant(
|
|
|
566
675
|
manifestEntry: MaterialShaderManifestEntry | undefined,
|
|
567
676
|
storageBufferCapable: boolean,
|
|
568
677
|
variantSet: string | undefined,
|
|
678
|
+
extendedLightingShaderAvailable = true,
|
|
679
|
+
directionalPcssAvailable = true,
|
|
569
680
|
projectorAvailable: boolean = true,
|
|
570
681
|
): MaterialShaderManifestEntry['variants'][number] | undefined {
|
|
571
|
-
const hdrpRequested =
|
|
572
|
-
|
|
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'));
|
|
682
|
+
const hdrpRequested = variantSet === '' || requestsClusterVariant(variantSet);
|
|
683
|
+
const probeBlendRequested = requestsProbeBlendVariant(variantSet);
|
|
575
684
|
return manifestEntry?.variants.find(
|
|
576
685
|
(variant) =>
|
|
577
686
|
variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable &&
|
|
687
|
+
(!('EXTENDED_LIGHTING_AVAILABLE' in variant.defines) ||
|
|
688
|
+
variant.defines.EXTENDED_LIGHTING_AVAILABLE === extendedLightingShaderAvailable) &&
|
|
578
689
|
variant.defines.VERTEX_COLOR_AVAILABLE === false &&
|
|
690
|
+
(!('DIRECTIONAL_PCSS_AVAILABLE' in variant.defines) ||
|
|
691
|
+
variant.defines.DIRECTIONAL_PCSS_AVAILABLE === directionalPcssAvailable) &&
|
|
579
692
|
(!('PROJECTOR_AVAILABLE' in variant.defines) ||
|
|
580
693
|
variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) &&
|
|
694
|
+
(!('PROBE_BLEND_AVAILABLE' in variant.defines) ||
|
|
695
|
+
variant.defines.PROBE_BLEND_AVAILABLE === probeBlendRequested) &&
|
|
581
696
|
(!('CLUSTER_FORWARD_AVAILABLE' in variant.defines) ||
|
|
582
697
|
variant.defines.CLUSTER_FORWARD_AVAILABLE === hdrpRequested),
|
|
583
698
|
);
|
|
584
699
|
}
|
|
585
700
|
|
|
701
|
+
function requestsClusterVariant(variantSet: string | undefined): boolean {
|
|
702
|
+
if (variantSet === undefined) return false;
|
|
703
|
+
return variantSet.includes('CLUSTER_FORWARD_AVAILABLE=true');
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function requestsProbeBlendVariant(variantSet: string | undefined): boolean {
|
|
707
|
+
if (variantSet === undefined) return false;
|
|
708
|
+
return variantSet.includes('PROBE_BLEND_AVAILABLE=true');
|
|
709
|
+
}
|
|
710
|
+
|
|
586
711
|
export type MaterialShaderBindingContract = RenderFeatureMaterialShaderBindingContract;
|
|
587
712
|
export type MaterialShaderVertexInputContract = 'none' | 'render-material';
|
|
588
713
|
|
|
@@ -707,13 +832,18 @@ export function shouldDeferMissingPreparedMaterialShader(
|
|
|
707
832
|
export function selectPipelineLayoutForVariant(
|
|
708
833
|
state: {
|
|
709
834
|
readonly pbrPipelineLayout: PipelineLayout | null;
|
|
835
|
+
readonly pbrProbePipelineLayout?: PipelineLayout | null;
|
|
710
836
|
readonly hdrpPbrPipelineLayout: PipelineLayout | null;
|
|
837
|
+
readonly hdrpProbePbrPipelineLayout?: PipelineLayout | null;
|
|
711
838
|
readonly pbrSkinPipelineLayout: PipelineLayout | null;
|
|
839
|
+
readonly pbrSkinProbePipelineLayout?: PipelineLayout | null;
|
|
840
|
+
readonly hdrpSkinPipelineLayout?: PipelineLayout | null;
|
|
712
841
|
} | null,
|
|
713
842
|
variantSet: string | undefined,
|
|
714
843
|
layoutKind?: LayoutKind,
|
|
715
844
|
): PipelineLayout | null {
|
|
716
845
|
if (state === null) return null;
|
|
846
|
+
const probeBlendVariant = variantSet?.includes('PROBE_BLEND_AVAILABLE=true') === true;
|
|
717
847
|
// bug-20260611: skin layout selection takes precedence over HDRP variant
|
|
718
848
|
// resolution. HDRP × skin is OOS-1 (plan-strategy R-2 — left for a
|
|
719
849
|
// dedicated feat); when an HDRP-variant skin call ever lands, we fail
|
|
@@ -721,7 +851,12 @@ export function selectPipelineLayoutForVariant(
|
|
|
721
851
|
// (charter P3 explicit failure, mirrors memory anchor
|
|
722
852
|
// `hdrp-active-must-not-fallback-to-urp-pipeline`).
|
|
723
853
|
if (layoutKind === 'pbr-skin') {
|
|
724
|
-
return
|
|
854
|
+
return probeBlendVariant
|
|
855
|
+
? (state.pbrSkinProbePipelineLayout ?? null)
|
|
856
|
+
: state.pbrSkinPipelineLayout;
|
|
857
|
+
}
|
|
858
|
+
if (layoutKind === 'hdrp-skin') {
|
|
859
|
+
return state.hdrpSkinPipelineLayout ?? null;
|
|
725
860
|
}
|
|
726
861
|
// bug-20260708 M2 (c): sprite / sprite-lit shaders reuse the URP 1-slot
|
|
727
862
|
// mesh-array pipeline layout but their canonical all-true variant key
|
|
@@ -738,33 +873,24 @@ export function selectPipelineLayoutForVariant(
|
|
|
738
873
|
if (layoutKind === 'unlit-urp') {
|
|
739
874
|
return state.pbrPipelineLayout;
|
|
740
875
|
}
|
|
741
|
-
//
|
|
742
|
-
//
|
|
743
|
-
// the
|
|
744
|
-
//
|
|
876
|
+
// The resolved group(2) contract is authoritative for custom material
|
|
877
|
+
// shaders. A mesh-only composed WGSL uses the one-entry mesh BGL even when
|
|
878
|
+
// the renderer is otherwise running its HDRP lighting variant; allowing the
|
|
879
|
+
// variant key to re-select HDRP here recreates the mesh-vs-cluster mismatch.
|
|
745
880
|
if (layoutKind === 'pbr') {
|
|
746
|
-
return state.pbrPipelineLayout;
|
|
881
|
+
return probeBlendVariant ? (state.pbrProbePipelineLayout ?? null) : state.pbrPipelineLayout;
|
|
747
882
|
}
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
//
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
// explicitly distinguish '' vs undefined rather than using an optional-chain
|
|
758
|
-
// (charter P3: explicit failure / no implicit empty-vs-missing collapse).
|
|
759
|
-
const isHdrpVariant =
|
|
760
|
-
layoutKind === 'hdrp-pbr' ||
|
|
761
|
-
variantSet === '' ||
|
|
762
|
-
// biome-ignore lint/complexity/useOptionalChain: G-14 grep gate forbids the optional-chain falsy collapse on variantSet.
|
|
763
|
-
(variantSet !== undefined && variantSet.includes('CLUSTER_FORWARD_AVAILABLE=true'));
|
|
764
|
-
if (isHdrpVariant && state.hdrpPbrPipelineLayout !== null) {
|
|
765
|
-
return state.hdrpPbrPipelineLayout;
|
|
883
|
+
// Group(2) contract/layout kind is the sole layout authority. Variant keys
|
|
884
|
+
// identify WGSL artifacts, but must not independently select a bind-group
|
|
885
|
+
// layout; this prevents an empty/expanded key from bypassing the resolved
|
|
886
|
+
// storage contract.
|
|
887
|
+
const isHdrpVariant = layoutKind === 'hdrp-pbr';
|
|
888
|
+
if (isHdrpVariant) {
|
|
889
|
+
return probeBlendVariant
|
|
890
|
+
? (state.hdrpProbePbrPipelineLayout ?? null)
|
|
891
|
+
: state.hdrpPbrPipelineLayout;
|
|
766
892
|
}
|
|
767
|
-
return state.pbrPipelineLayout;
|
|
893
|
+
return probeBlendVariant ? (state.pbrProbePipelineLayout ?? null) : state.pbrPipelineLayout;
|
|
768
894
|
}
|
|
769
895
|
|
|
770
896
|
/**
|
|
@@ -780,12 +906,14 @@ export function resolveMaterialShaderVariantSet(
|
|
|
780
906
|
}>,
|
|
781
907
|
backendKind: RhiDevice['caps']['backendKind'],
|
|
782
908
|
storageBuffer: boolean,
|
|
783
|
-
|
|
909
|
+
maxSampledTexturesPerShaderStageOrProjectorAvailable?: number | boolean,
|
|
784
910
|
): string | undefined {
|
|
785
911
|
const declaredAxes = new Set(variants.flatMap((variant) => Object.keys(variant.defines)));
|
|
786
912
|
const declaresCapabilityVariants =
|
|
787
913
|
declaredAxes.has('WEBGL2_COMPAT') ||
|
|
788
914
|
declaredAxes.has('STORAGE_BUFFER_AVAILABLE') ||
|
|
915
|
+
declaredAxes.has('EXTENDED_LIGHTING_AVAILABLE') ||
|
|
916
|
+
declaredAxes.has('DIRECTIONAL_PCSS_AVAILABLE') ||
|
|
789
917
|
declaredAxes.has('PROJECTOR_AVAILABLE');
|
|
790
918
|
if (!declaresCapabilityVariants) return requestedVariantSet;
|
|
791
919
|
|
|
@@ -794,6 +922,22 @@ export function resolveMaterialShaderVariantSet(
|
|
|
794
922
|
const separator = part.indexOf('=');
|
|
795
923
|
if (separator > 0) requested[part.slice(0, separator)] = part.slice(separator + 1) === 'true';
|
|
796
924
|
}
|
|
925
|
+
const sampledTextureLimit =
|
|
926
|
+
typeof maxSampledTexturesPerShaderStageOrProjectorAvailable === 'number'
|
|
927
|
+
? maxSampledTexturesPerShaderStageOrProjectorAvailable
|
|
928
|
+
: undefined;
|
|
929
|
+
const extendedLightingAvailable =
|
|
930
|
+
sampledTextureLimit === undefined
|
|
931
|
+
? true
|
|
932
|
+
: backendKind !== 'wgpu-webgl2' &&
|
|
933
|
+
storageBuffer &&
|
|
934
|
+
extendedLightingSampledTextureCapacityAvailable(sampledTextureLimit);
|
|
935
|
+
const projectorAvailable =
|
|
936
|
+
typeof maxSampledTexturesPerShaderStageOrProjectorAvailable === 'boolean'
|
|
937
|
+
? maxSampledTexturesPerShaderStageOrProjectorAvailable
|
|
938
|
+
: sampledTextureLimit === undefined
|
|
939
|
+
? true
|
|
940
|
+
: sampledTextureLimit >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
797
941
|
const filtered: Record<string, boolean> = {};
|
|
798
942
|
for (const axis of declaredAxes) {
|
|
799
943
|
filtered[axis] =
|
|
@@ -804,22 +948,33 @@ export function resolveMaterialShaderVariantSet(
|
|
|
804
948
|
: axis === 'SKINNING_DISABLED'
|
|
805
949
|
? (requested[axis] ?? true)
|
|
806
950
|
: axis === 'CLUSTER_FORWARD_AVAILABLE'
|
|
807
|
-
? backendKind !== 'wgpu-webgl2' && (requested[axis] ?? false)
|
|
951
|
+
? storageBuffer && backendKind !== 'wgpu-webgl2' && (requested[axis] ?? false)
|
|
808
952
|
: axis === 'PER_INSTANCE_REGION'
|
|
809
953
|
? requestedVariantSet === '' || (requested[axis] ?? false)
|
|
810
954
|
: axis === 'VERTEX_COLOR_AVAILABLE'
|
|
811
955
|
? requestedVariantSet === '' || (requested[axis] ?? false)
|
|
812
|
-
: axis === '
|
|
813
|
-
?
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
956
|
+
: axis === 'EXTENDED_LIGHTING_AVAILABLE'
|
|
957
|
+
? // This is a backend-owned ABI axis, just like projector
|
|
958
|
+
// and directional PCSS. An omitted caller request must
|
|
959
|
+
// inherit the device-admitted view layout; defaulting it
|
|
960
|
+
// to false pairs an extended view BGL with the projector
|
|
961
|
+
// WGSL and fails WebGPU validation at binding 11.
|
|
962
|
+
extendedLightingAvailable
|
|
963
|
+
: axis === 'PROBE_BLEND_AVAILABLE'
|
|
964
|
+
? storageBuffer && (requested[axis] ?? false)
|
|
965
|
+
: axis === 'TRANSMISSION_AVAILABLE'
|
|
966
|
+
? (requested[axis] ?? false)
|
|
967
|
+
: axis === 'DIRECTIONAL_PCSS_AVAILABLE'
|
|
968
|
+
? directionalPcssCapable(backendKind)
|
|
969
|
+
: axis === 'PROJECTOR_AVAILABLE'
|
|
970
|
+
? projectorAvailable
|
|
971
|
+
: (requested[axis] ?? false);
|
|
817
972
|
}
|
|
818
973
|
const sorted = Object.entries(filtered).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
819
974
|
return sorted.every(([, value]) => value)
|
|
820
|
-
? requestedVariantSet ===
|
|
821
|
-
?
|
|
822
|
-
:
|
|
975
|
+
? requestedVariantSet === undefined
|
|
976
|
+
? undefined
|
|
977
|
+
: ''
|
|
823
978
|
: sorted.map(([name, value]) => `${name}=${value}`).join('+');
|
|
824
979
|
}
|
|
825
980
|
|
|
@@ -843,8 +998,9 @@ export async function createRenderer(
|
|
|
843
998
|
options?: RendererOptions,
|
|
844
999
|
// feat-20260608-create-app-param-surface-trim / M2 / D-3: the third arg is
|
|
845
1000
|
// now BundlerOptions -- the build-tool injection channel that aggregates
|
|
846
|
-
// (a) shaderManifestUrl (vite-plugin-shader emit URL)
|
|
847
|
-
// (dev-only AssetRegistry transport from feat-20260604)
|
|
1001
|
+
// (a) shaderManifestUrl (vite-plugin-shader emit URL), (b) importTransport,
|
|
1002
|
+
// (dev-only AssetRegistry transport from feat-20260604), and (c) build
|
|
1003
|
+
// identity for renderer-owned inspection evidence. All fields are
|
|
848
1004
|
// optional so {} is a valid call shape. Absent => both shipped-form
|
|
849
1005
|
// fallbacks fire: shaderManifestUrl falls back to '/shaders/manifest.json'
|
|
850
1006
|
// (D-2 q5-A, ~30 lines below); importTransport stays undefined (AssetRegistry
|
|
@@ -883,6 +1039,28 @@ export async function createRenderer(
|
|
|
883
1039
|
});
|
|
884
1040
|
}
|
|
885
1041
|
|
|
1042
|
+
function markRendererBootstrapStage(
|
|
1043
|
+
name: string,
|
|
1044
|
+
detail?: Readonly<Record<string, unknown>>,
|
|
1045
|
+
): void {
|
|
1046
|
+
const host = globalThis as {
|
|
1047
|
+
__forgeaxRendererBootstrap?: {
|
|
1048
|
+
readonly name: string;
|
|
1049
|
+
readonly startedAt: number;
|
|
1050
|
+
readonly previousElapsedMs: number;
|
|
1051
|
+
readonly detail?: Readonly<Record<string, unknown>>;
|
|
1052
|
+
};
|
|
1053
|
+
};
|
|
1054
|
+
const previous = host.__forgeaxRendererBootstrap;
|
|
1055
|
+
const now = Date.now();
|
|
1056
|
+
host.__forgeaxRendererBootstrap = {
|
|
1057
|
+
name,
|
|
1058
|
+
startedAt: now,
|
|
1059
|
+
previousElapsedMs: previous === undefined ? 0 : now - previous.startedAt,
|
|
1060
|
+
...(detail === undefined ? {} : { detail }),
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
|
|
886
1064
|
// ─── WebGPU branch ──────────────────────────────────────────────────────────
|
|
887
1065
|
|
|
888
1066
|
/**
|
|
@@ -909,7 +1087,11 @@ function attachDeviceLostFanout(
|
|
|
909
1087
|
},
|
|
910
1088
|
): void {
|
|
911
1089
|
const { lostRegistry, errorRegistry, healthRegistry, onDeviceLost } = registries;
|
|
912
|
-
|
|
1090
|
+
// Backend fixtures may compose a deterministic loss signal with the native
|
|
1091
|
+
// promise so the real recovery path can be exercised without manufacturing
|
|
1092
|
+
// a second renderer health state. Production packs leave this hook absent.
|
|
1093
|
+
const lost = pack.instrumentation?.deviceLost?.(device) ?? device.lost;
|
|
1094
|
+
lost
|
|
913
1095
|
.then((info) => {
|
|
914
1096
|
const safe = {
|
|
915
1097
|
reason: info?.reason ?? 'unknown',
|
|
@@ -993,50 +1175,6 @@ type WebGPUOutcome =
|
|
|
993
1175
|
| { kind: 'rhi-err'; error: RhiError }
|
|
994
1176
|
| { kind: 'throw'; error: Error };
|
|
995
1177
|
|
|
996
|
-
// Optional compression is exposed on a device only when it is requested from
|
|
997
|
-
// the adapter. Keep this capability projection shared by initial construction
|
|
998
|
-
// and device-loss recovery; otherwise recovery silently drops BC/ETC2/ASTC
|
|
999
|
-
// and already-catalogued compressed textures fail on the first new frame.
|
|
1000
|
-
const COMPRESSION_FEATURES: GPUFeatureName[] = [
|
|
1001
|
-
'texture-compression-bc',
|
|
1002
|
-
'texture-compression-etc2',
|
|
1003
|
-
'texture-compression-astc',
|
|
1004
|
-
];
|
|
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
|
-
|
|
1012
|
-
function deviceOptionsForAdapter(
|
|
1013
|
-
adapter: Pick<RhiAdapter, 'features' | 'limits'>,
|
|
1014
|
-
options?: Pick<RendererOptions, 'captureGpuTimings'>,
|
|
1015
|
-
):
|
|
1016
|
-
| {
|
|
1017
|
-
requiredFeatures?: GPUFeatureName[];
|
|
1018
|
-
requiredLimits?: GPUDeviceDescriptor['requiredLimits'];
|
|
1019
|
-
}
|
|
1020
|
-
| undefined {
|
|
1021
|
-
const requiredFeatures = COMPRESSION_FEATURES.filter((feature) => adapter.features.has(feature));
|
|
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
|
-
};
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
1178
|
/**
|
|
1041
1179
|
* Goes through the strict two-step path exposed by the injected backend pack:
|
|
1042
1180
|
* `rhi.requestAdapter()` -> `adapter.requestDevice()`
|
|
@@ -1077,7 +1215,12 @@ async function tryCreateWebGPURenderer(
|
|
|
1077
1215
|
// backend singleton (rhi-webgpu / rhi-wgpu / explicit escape hatch).
|
|
1078
1216
|
// w19: thread canvas as compatibleSurface so the wgpu GL backend can
|
|
1079
1217
|
// enumerate adapters against it (escape hatch, capability-gated).
|
|
1218
|
+
markRendererBootstrapStage('adapter-request-start');
|
|
1080
1219
|
const adapterResult = await pack.rhi.requestAdapter(undefined, canvas);
|
|
1220
|
+
markRendererBootstrapStage('adapter-request-end', {
|
|
1221
|
+
ok: adapterResult.ok,
|
|
1222
|
+
...(adapterResult.ok ? {} : { code: adapterResult.error.code }),
|
|
1223
|
+
});
|
|
1081
1224
|
if (!adapterResult.ok) {
|
|
1082
1225
|
return { kind: 'rhi-err', error: adapterResult.error };
|
|
1083
1226
|
}
|
|
@@ -1085,11 +1228,19 @@ async function tryCreateWebGPURenderer(
|
|
|
1085
1228
|
// M4 w28: filter compression features from adapter.features (AC-07). The
|
|
1086
1229
|
// same helper is used by recover() below so both paths share one profile.
|
|
1087
1230
|
const deviceOpts = deviceOptionsForAdapter(adapter, options);
|
|
1231
|
+
markRendererBootstrapStage('device-request-start');
|
|
1088
1232
|
const result = await adapter.requestDevice(deviceOpts);
|
|
1233
|
+
markRendererBootstrapStage('device-request-end', {
|
|
1234
|
+
ok: result.ok,
|
|
1235
|
+
...(result.ok ? {} : { code: result.error.code }),
|
|
1236
|
+
});
|
|
1089
1237
|
if (!result.ok) {
|
|
1090
1238
|
return { kind: 'rhi-err', error: result.error };
|
|
1091
1239
|
}
|
|
1092
1240
|
const device: RhiDevice = result.value;
|
|
1241
|
+
markRendererBootstrapStage('device-acquired', {
|
|
1242
|
+
backendKind: device.caps.backendKind,
|
|
1243
|
+
});
|
|
1093
1244
|
|
|
1094
1245
|
// Acquire the canvas context through the backend pack (M3 / w16).
|
|
1095
1246
|
// rhi-webgpu: internally calls canvas.getContext('webgpu') + branded wrap.
|
|
@@ -1172,10 +1323,8 @@ interface WebGPURendererInternals {
|
|
|
1172
1323
|
options: RendererOptions | undefined;
|
|
1173
1324
|
/**
|
|
1174
1325
|
* feat-20260608-create-app-param-surface-trim / M2 / D-3: BundlerOptions
|
|
1175
|
-
* forwarded verbatim from createRenderer's third arg. Carries
|
|
1176
|
-
*
|
|
1177
|
-
* createRenderer falls back to '/shaders/manifest.json') and the
|
|
1178
|
-
* importTransport that previously rode a dedicated internal slot.
|
|
1326
|
+
* forwarded verbatim from createRenderer's third arg. Carries the
|
|
1327
|
+
* host-injected shaderManifestUrl, importTransport, and exact build identity.
|
|
1179
1328
|
*/
|
|
1180
1329
|
bundler: BundlerOptions | undefined;
|
|
1181
1330
|
lostRegistry: LostListenerRegistry;
|
|
@@ -1220,12 +1369,24 @@ interface WebGPURendererInternals {
|
|
|
1220
1369
|
featureHost?: RenderFeatureHost;
|
|
1221
1370
|
/** App-owned overlay capability read by the typed graph at frame time. */
|
|
1222
1371
|
debugOverlay?: RenderDebugOverlay;
|
|
1372
|
+
gpuPassTimingSubmittedWork?: Promise<void> | undefined;
|
|
1223
1373
|
}
|
|
1224
1374
|
|
|
1225
1375
|
async function makeWebGPURenderer(
|
|
1226
1376
|
internals: WebGPURendererInternals,
|
|
1227
1377
|
): Promise<RendererAssemblyImplementation> {
|
|
1228
1378
|
let disposed = false;
|
|
1379
|
+
let renderSystemForTargetPromotion: RenderSystem | undefined;
|
|
1380
|
+
// The host keeps the zero-option createRenderTargetHost() seam compatible
|
|
1381
|
+
// for package-local lifecycle tests while this assembly supplies the live
|
|
1382
|
+
// DeviceScope generation callback.
|
|
1383
|
+
const renderTargetHost = createRenderTargetHost({
|
|
1384
|
+
rendererId: Symbol('renderer'),
|
|
1385
|
+
getGeneration: () => activeDeviceScope.generation,
|
|
1386
|
+
getDevice: () => internals.device,
|
|
1387
|
+
canPromoteTarget: (target) =>
|
|
1388
|
+
renderSystemForTargetPromotion?.isCubeCapturePending(target) !== true,
|
|
1389
|
+
});
|
|
1229
1390
|
const featureHostResult = createRenderFeatureHost(internals.options?.features ?? []);
|
|
1230
1391
|
if (!featureHostResult.ok) throw featureHostResult.error;
|
|
1231
1392
|
internals.featureHost = featureHostResult.value;
|
|
@@ -1240,6 +1401,28 @@ async function makeWebGPURenderer(
|
|
|
1240
1401
|
featureHostResult.value.features.flatMap((feature) => feature.requiredMaterialShaders ?? []),
|
|
1241
1402
|
);
|
|
1242
1403
|
let requiredMaterialShaders = Object.freeze([...requiredMaterialShaderSet]);
|
|
1404
|
+
const collectRequiredFullscreenPostProcesses = (
|
|
1405
|
+
features: readonly RenderFeature<unknown>[],
|
|
1406
|
+
): readonly { readonly identity: string; readonly source: string }[] => {
|
|
1407
|
+
const entries = new Map<string, { readonly identity: string; readonly source: string }>();
|
|
1408
|
+
for (const feature of features) {
|
|
1409
|
+
for (const entry of feature.requiredFullscreenPostProcesses ?? []) {
|
|
1410
|
+
const existing = entries.get(entry.identity);
|
|
1411
|
+
if (existing !== undefined && existing.source !== entry.source) {
|
|
1412
|
+
throw new RhiError({
|
|
1413
|
+
code: 'internal-error',
|
|
1414
|
+
expected: `fullscreen identity '${entry.identity}' has one source across render features`,
|
|
1415
|
+
hint: 'rename the conflicting fullscreen identity or make its WGSL source identical',
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
entries.set(entry.identity, entry);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return Object.freeze([...entries.values()]);
|
|
1422
|
+
};
|
|
1423
|
+
let requiredFullscreenPostProcesses = collectRequiredFullscreenPostProcesses(
|
|
1424
|
+
featureHostResult.value.features,
|
|
1425
|
+
);
|
|
1243
1426
|
// Lazy ShaderCatalog instance (plan-strategy section S-10 / D-R10 / OQ-5
|
|
1244
1427
|
// close): constructed on first access; subsequent accesses return the
|
|
1245
1428
|
// same instance.
|
|
@@ -1282,6 +1465,28 @@ async function makeWebGPURenderer(
|
|
|
1282
1465
|
}
|
|
1283
1466
|
return sharedImmediateShaderModuleAdapter;
|
|
1284
1467
|
};
|
|
1468
|
+
const prewarmFullscreenFeatureModules = async (
|
|
1469
|
+
feature: RenderFeature<unknown>,
|
|
1470
|
+
): Promise<Result<void, RhiError>> => {
|
|
1471
|
+
for (const postProcess of feature.requiredFullscreenPostProcesses ?? []) {
|
|
1472
|
+
const label = postProcessShaderModuleLabel(postProcess.identity, postProcess.source);
|
|
1473
|
+
const shaderResult = internals.pack.createShaderModule
|
|
1474
|
+
? await internals.pack.createShaderModule(internals.device, {
|
|
1475
|
+
code: postProcess.source,
|
|
1476
|
+
label,
|
|
1477
|
+
})
|
|
1478
|
+
: await invokeDeviceCreateShaderModule(internals.device, {
|
|
1479
|
+
code: postProcess.source,
|
|
1480
|
+
label,
|
|
1481
|
+
});
|
|
1482
|
+
if (!shaderResult.ok) {
|
|
1483
|
+
internals.errorRegistry.fire(shaderResult.error);
|
|
1484
|
+
return err(shaderResult.error);
|
|
1485
|
+
}
|
|
1486
|
+
getShaderModuleAdapter().seedModule(label, shaderResult.value);
|
|
1487
|
+
}
|
|
1488
|
+
return ok(undefined);
|
|
1489
|
+
};
|
|
1285
1490
|
const getShader = (): ShaderCatalog => {
|
|
1286
1491
|
if (shaderInstance === null) {
|
|
1287
1492
|
// feat-20260608-create-app-param-surface-trim / M2 / D-2 q5-A:
|
|
@@ -1346,6 +1551,57 @@ async function makeWebGPURenderer(
|
|
|
1346
1551
|
// lives in a standalone store; `assets` keeps the CPU POD registry only.
|
|
1347
1552
|
const gpuStore = new GpuResidencyCache();
|
|
1348
1553
|
let activeDeviceScope = DeviceScope.create(0, 'renderer');
|
|
1554
|
+
let extendedLightingState = createExtendedLightingState(activeDeviceScope.generation);
|
|
1555
|
+
const gpuPassTimingOptions = internals.options?.gpuPassTiming;
|
|
1556
|
+
const timingRetentionFrames =
|
|
1557
|
+
gpuPassTimingOptions?.retentionFrames !== undefined &&
|
|
1558
|
+
Number.isInteger(gpuPassTimingOptions.retentionFrames) &&
|
|
1559
|
+
gpuPassTimingOptions.retentionFrames >= 1 &&
|
|
1560
|
+
gpuPassTimingOptions.retentionFrames <= 8
|
|
1561
|
+
? gpuPassTimingOptions.retentionFrames
|
|
1562
|
+
: DEFAULT_GPU_PASS_TIMING_OPTIONS.retentionFrames;
|
|
1563
|
+
let gpuPassTimingSession: GpuPassTimingSession | undefined;
|
|
1564
|
+
let gpuPassTimingUnavailable: GpuPassTimingObservation | undefined;
|
|
1565
|
+
const timingObservationStore =
|
|
1566
|
+
gpuPassTimingOptions === undefined
|
|
1567
|
+
? undefined
|
|
1568
|
+
: createGpuPassTimingObservationStore({
|
|
1569
|
+
retentionFrames: timingRetentionFrames,
|
|
1570
|
+
currentDeviceGeneration: () => activeDeviceScope.generation,
|
|
1571
|
+
});
|
|
1572
|
+
const retireGpuPassTimingSession = (): void => {
|
|
1573
|
+
const retired = gpuPassTimingSession;
|
|
1574
|
+
gpuPassTimingSession = undefined;
|
|
1575
|
+
retired?.dispose();
|
|
1576
|
+
};
|
|
1577
|
+
const failedGpuPassTimingObservation = (error: GpuPassTimingReason): GpuPassTimingObservation => {
|
|
1578
|
+
const latestKnownGood = timingObservationStore?.inspect().latestKnownGood;
|
|
1579
|
+
return {
|
|
1580
|
+
status: 'failed',
|
|
1581
|
+
error,
|
|
1582
|
+
...(latestKnownGood === undefined ? {} : { latestKnownGood }),
|
|
1583
|
+
};
|
|
1584
|
+
};
|
|
1585
|
+
const establishGpuPassTimingSession = (): void => {
|
|
1586
|
+
retireGpuPassTimingSession();
|
|
1587
|
+
gpuPassTimingUnavailable = undefined;
|
|
1588
|
+
const timingOptions = gpuPassTimingOptions;
|
|
1589
|
+
if (timingOptions === undefined) return;
|
|
1590
|
+
const created = createGpuPassTimingSession(internals.device, timingOptions);
|
|
1591
|
+
if (created.ok) {
|
|
1592
|
+
gpuPassTimingSession = created.value;
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
gpuPassTimingUnavailable = {
|
|
1596
|
+
status: 'unavailable',
|
|
1597
|
+
reason: created.error,
|
|
1598
|
+
capability: {
|
|
1599
|
+
timestampQuery: internals.device.caps.timestampQuery,
|
|
1600
|
+
timestampPeriodNanoseconds: internals.device.caps.timestampPeriodNanoseconds,
|
|
1601
|
+
},
|
|
1602
|
+
};
|
|
1603
|
+
};
|
|
1604
|
+
establishGpuPassTimingSession();
|
|
1349
1605
|
// feat-20260623-world-space-video-asset M4 / w16 (D-3): transient per-frame
|
|
1350
1606
|
// video texture store, fully independent of gpuStore (AC-08). Configured with
|
|
1351
1607
|
// the device alongside gpuStore below; threaded into the record stage via the
|
|
@@ -1479,7 +1735,10 @@ async function makeWebGPURenderer(
|
|
|
1479
1735
|
// (the seeding closure captures this Map by reference; ordering is safe
|
|
1480
1736
|
// because the prewarm step runs strictly before the first frame's
|
|
1481
1737
|
// `getMaterialShaderPipeline` lookup that would consume it).
|
|
1482
|
-
const materialShaderPipelineCache = new Map<
|
|
1738
|
+
const materialShaderPipelineCache = new Map<
|
|
1739
|
+
string,
|
|
1740
|
+
{ readonly pipeline: RenderPipeline; readonly group2Contract: PipelineGroup2Contract }
|
|
1741
|
+
>();
|
|
1483
1742
|
const materialShaderManifestEntryCache = new Map<string, MaterialShaderManifestEntry>();
|
|
1484
1743
|
// Variant resolution only depends on the requested axes, device capability
|
|
1485
1744
|
// axes, and the manifest entry that owns the declarations. Cache it by the
|
|
@@ -1752,8 +2011,7 @@ async function makeWebGPURenderer(
|
|
|
1752
2011
|
[],
|
|
1753
2012
|
internals.device.caps.backendKind,
|
|
1754
2013
|
internals.device.caps.storageBuffer,
|
|
1755
|
-
|
|
1756
|
-
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES,
|
|
2014
|
+
internals.device.limits.maxSampledTexturesPerShaderStage,
|
|
1757
2015
|
);
|
|
1758
2016
|
}
|
|
1759
2017
|
const variants = manifestEntry.variants;
|
|
@@ -1765,14 +2023,14 @@ async function makeWebGPURenderer(
|
|
|
1765
2023
|
const projectorAvailable =
|
|
1766
2024
|
(internals.device.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
1767
2025
|
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
1768
|
-
const cacheKey = `${requestedVariantSet ?? '\u0000'}|${internals.device.caps.backendKind}|${internals.device.caps.storageBuffer ? '1' : '0'}|${projectorAvailable ? '1' : '0'}`;
|
|
2026
|
+
const cacheKey = `${requestedVariantSet ?? '\u0000'}|${internals.device.caps.backendKind}|${internals.device.caps.storageBuffer ? '1' : '0'}|${internals.device.limits.maxSampledTexturesPerShaderStage ?? 'unknown'}|${projectorAvailable ? '1' : '0'}`;
|
|
1769
2027
|
if (byRequest.has(cacheKey)) return byRequest.get(cacheKey);
|
|
1770
2028
|
const resolved = resolveMaterialShaderVariantSet(
|
|
1771
2029
|
requestedVariantSet,
|
|
1772
2030
|
variants,
|
|
1773
2031
|
internals.device.caps.backendKind,
|
|
1774
2032
|
internals.device.caps.storageBuffer,
|
|
1775
|
-
|
|
2033
|
+
internals.device.limits.maxSampledTexturesPerShaderStage,
|
|
1776
2034
|
);
|
|
1777
2035
|
byRequest.set(cacheKey, resolved);
|
|
1778
2036
|
return resolved;
|
|
@@ -1798,6 +2056,44 @@ async function makeWebGPURenderer(
|
|
|
1798
2056
|
// `internals.device` / `internals.pack` are read live, so a rebuild after a
|
|
1799
2057
|
// device swap compiles against the new device.
|
|
1800
2058
|
let tonemapRegistered = false;
|
|
2059
|
+
const promotePipelineExtendedLighting = (state: PipelineState, scope: DeviceScope): void => {
|
|
2060
|
+
if (
|
|
2061
|
+
state.extendedLightingAvailable !== true ||
|
|
2062
|
+
state.iesProfileTexture === undefined ||
|
|
2063
|
+
state.cookieTexture === undefined ||
|
|
2064
|
+
state.cookieMatrixBuffer === undefined ||
|
|
2065
|
+
state.iesProfileTextureView === undefined ||
|
|
2066
|
+
state.cookieTextureView === undefined ||
|
|
2067
|
+
state.ltcLambertTextureView === undefined ||
|
|
2068
|
+
state.ltcGgxTextureView === undefined
|
|
2069
|
+
) {
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
// The pipeline builder owns these already-resident GPU handles. Project
|
|
2073
|
+
// that completed bundle into the inspection state so the public receipt
|
|
2074
|
+
// cannot report an empty candidate while the record path is consuming the
|
|
2075
|
+
// same IES/Cookie/LTC resources. The candidate is metadata-only here; no
|
|
2076
|
+
// second GPU allocation or lifecycle owner is introduced.
|
|
2077
|
+
const candidate: ExtendedLightingResourceCandidate = {
|
|
2078
|
+
topology: EXTENDED_LIGHTING_TOPOLOGY,
|
|
2079
|
+
generation: scope.generation,
|
|
2080
|
+
scope,
|
|
2081
|
+
iesSliceCount: 0,
|
|
2082
|
+
cookieSliceCount: 0,
|
|
2083
|
+
cookieMatrices: 0,
|
|
2084
|
+
sampler: state.defaultSampler,
|
|
2085
|
+
iesTexture: state.iesProfileTexture,
|
|
2086
|
+
cookieTexture: state.cookieTexture,
|
|
2087
|
+
cookieMatrixBuffer: state.cookieMatrixBuffer,
|
|
2088
|
+
descriptorBytes:
|
|
2089
|
+
IES_SLICE_WIDTH * IES_SLICE_HEIGHT * 2 * 32 +
|
|
2090
|
+
COOKIE_SLICE_SIZE * COOKIE_SLICE_SIZE * 4 * 32 +
|
|
2091
|
+
COOKIE_MATRIX_BYTES,
|
|
2092
|
+
uploadCount: 5,
|
|
2093
|
+
};
|
|
2094
|
+
extendedLightingState = promoteExtendedLightingCandidate(extendedLightingState, candidate);
|
|
2095
|
+
};
|
|
2096
|
+
let fxaaRegistered = false;
|
|
1801
2097
|
let temporalPostProcessesRegistered = false;
|
|
1802
2098
|
const buildPipeline = (scope: DeviceScope = activeDeviceScope): Promise<PipelineState> =>
|
|
1803
2099
|
buildReadyWebGPU(
|
|
@@ -1808,6 +2104,7 @@ async function makeWebGPURenderer(
|
|
|
1808
2104
|
internals.pack.createShaderModule,
|
|
1809
2105
|
internals.errorRegistry,
|
|
1810
2106
|
requiredMaterialShaders,
|
|
2107
|
+
requiredFullscreenPostProcesses,
|
|
1811
2108
|
// feat-20260608-mesh-ssbo-dynamic-grow-l1-lift-1024-entity-cap M2 /
|
|
1812
2109
|
// T-M2-05 + M3 / T-M3-04: callback set by buildReadyWebGPU once
|
|
1813
2110
|
// meshSsboController is wired. Both the grow hook AND the read-only
|
|
@@ -1827,9 +2124,12 @@ async function makeWebGPURenderer(
|
|
|
1827
2124
|
// SPEC_CONST prewarm. Closure captures the Map declared just above so
|
|
1828
2125
|
// the URP-variant prewarmed PSOs become visible to the URP record path
|
|
1829
2126
|
// immediately (no 1-frame async-compile skip-draw window).
|
|
1830
|
-
(key, pso) => {
|
|
2127
|
+
(key, pso, group2Contract) => {
|
|
1831
2128
|
if (!materialShaderPipelineCache.has(key)) {
|
|
1832
|
-
materialShaderPipelineCache.set(key,
|
|
2129
|
+
materialShaderPipelineCache.set(key, {
|
|
2130
|
+
pipeline: pso,
|
|
2131
|
+
group2Contract,
|
|
2132
|
+
});
|
|
1833
2133
|
}
|
|
1834
2134
|
},
|
|
1835
2135
|
// feat-20260621 M-A3 (D-5): register the built-in tonemap on the unified
|
|
@@ -1848,6 +2148,19 @@ async function makeWebGPURenderer(
|
|
|
1848
2148
|
});
|
|
1849
2149
|
tonemapRegistered = true;
|
|
1850
2150
|
},
|
|
2151
|
+
// FXAA is recorded by the special graph primitive, but its final-output
|
|
2152
|
+
// dither switch still uses the renderer-owned post-process params UBO.
|
|
2153
|
+
// Register the declaration through the same lifecycle so recovery
|
|
2154
|
+
// recreates the buffer on the replacement device.
|
|
2155
|
+
(source: string) => {
|
|
2156
|
+
if (fxaaRegistered) return;
|
|
2157
|
+
renderSystem.registerBuiltinPostProcess(FXAA_POST_PROCESS_ID, {
|
|
2158
|
+
source,
|
|
2159
|
+
params: { byteSize: 16, defaultValue: new Uint8Array(16) },
|
|
2160
|
+
reads: ['ldrColor'],
|
|
2161
|
+
});
|
|
2162
|
+
fxaaRegistered = true;
|
|
2163
|
+
},
|
|
1851
2164
|
(entries: { readonly motionBlur?: string; readonly taaResolve?: string }) => {
|
|
1852
2165
|
if (temporalPostProcessesRegistered) return;
|
|
1853
2166
|
if (entries.motionBlur !== undefined) {
|
|
@@ -1870,14 +2183,20 @@ async function makeWebGPURenderer(
|
|
|
1870
2183
|
internals.volumetricFogShaders = sources;
|
|
1871
2184
|
},
|
|
1872
2185
|
);
|
|
2186
|
+
markRendererBootstrapStage('renderer-initialization-start');
|
|
1873
2187
|
const ready: Promise<Result<void, RhiError>> = buildPipeline().then(
|
|
1874
2188
|
(state): Result<void, RhiError> => {
|
|
1875
2189
|
pipelineState = state;
|
|
2190
|
+
promotePipelineExtendedLighting(state, activeDeviceScope);
|
|
1876
2191
|
readySettled = true;
|
|
2192
|
+
markRendererBootstrapStage('renderer-initialized');
|
|
1877
2193
|
return ok(undefined);
|
|
1878
2194
|
},
|
|
1879
2195
|
(e: unknown): Result<void, RhiError> => {
|
|
1880
2196
|
readySettled = true;
|
|
2197
|
+
markRendererBootstrapStage('renderer-initialization-failed', {
|
|
2198
|
+
message: e instanceof Error ? e.message : String(e),
|
|
2199
|
+
});
|
|
1881
2200
|
if (e instanceof RhiError) return err(e);
|
|
1882
2201
|
const message = e instanceof Error ? e.message : String(e);
|
|
1883
2202
|
return err(
|
|
@@ -1914,6 +2233,8 @@ async function makeWebGPURenderer(
|
|
|
1914
2233
|
pbrManifestEntry,
|
|
1915
2234
|
internals.device.caps.storageBuffer,
|
|
1916
2235
|
variantSet,
|
|
2236
|
+
deriveExtendedLightingCapability(internals.device).admitted,
|
|
2237
|
+
directionalPcssCapable(internals.device.caps.backendKind),
|
|
1917
2238
|
(internals.device.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
1918
2239
|
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES,
|
|
1919
2240
|
);
|
|
@@ -1931,10 +2252,10 @@ async function makeWebGPURenderer(
|
|
|
1931
2252
|
// paths (D-3). Null when manifest is empty (Camera-only path).
|
|
1932
2253
|
//
|
|
1933
2254
|
// feat-20260609-hdrp-cluster-fragment-ggx M4.5 / w37 (D-10 option A):
|
|
1934
|
-
// `variantSet`
|
|
1935
|
-
//
|
|
1936
|
-
//
|
|
1937
|
-
//
|
|
2255
|
+
// `variantSet` identifies the shader artifact, while the resolved group(2)
|
|
2256
|
+
// contract selects the matching Standard layout. A missing cluster layout
|
|
2257
|
+
// is an explicit unavailable result; it must not silently bind the mesh-only
|
|
2258
|
+
// layout to a clustered shader.
|
|
1938
2259
|
const buildPipelineContext = (
|
|
1939
2260
|
variantSet?: string,
|
|
1940
2261
|
materialShaderId?: string,
|
|
@@ -1945,40 +2266,31 @@ async function makeWebGPURenderer(
|
|
|
1945
2266
|
// consume. A caller passing a real `MeshAsset.attributes` produces the
|
|
1946
2267
|
// exact 6-attribute / 72-byte layout the skin shader expects; passing
|
|
1947
2268
|
// undefined falls into the synthetic 6-key sentinel below (the skin
|
|
1948
|
-
// path is fully determined by
|
|
1949
|
-
// signal `deriveVertexBufferLayout` reads
|
|
2269
|
+
// path is fully determined by the resolved group-2 contract, so
|
|
2270
|
+
// key-presence is the only signal `deriveVertexBufferLayout` reads).
|
|
1950
2271
|
meshAttributes?: VertexAttributeMap,
|
|
1951
2272
|
vertexLayout?: string,
|
|
1952
2273
|
vertexLayoutProjection?: VertexLayoutProjection,
|
|
1953
2274
|
shaderModuleMode?: RenderFeatureShaderModuleMode,
|
|
2275
|
+
group2Contract: PipelineGroup2Contract = 'mesh',
|
|
1954
2276
|
layoutKindOverride?: LayoutKind,
|
|
1955
2277
|
) => {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
const layoutKind: LayoutKind | undefined =
|
|
1966
|
-
layoutKindOverride ??
|
|
1967
|
-
(materialShaderId === SKIN_MATERIAL_SHADER_ID ||
|
|
1968
|
-
isSkinnedShadowCasterVariant(materialShaderId, variantSet)
|
|
1969
|
-
? 'pbr-skin'
|
|
1970
|
-
: materialShaderId === 'forgeax::sprite' || materialShaderId === 'forgeax::sprite-lit'
|
|
1971
|
-
? 'sprite-urp'
|
|
1972
|
-
: materialShaderId === 'forgeax::default-unlit'
|
|
1973
|
-
? 'unlit-urp'
|
|
1974
|
-
: undefined);
|
|
2278
|
+
const contractLayoutKind: LayoutKind =
|
|
2279
|
+
group2Contract === 'cluster'
|
|
2280
|
+
? 'hdrp-pbr'
|
|
2281
|
+
: group2Contract === 'skin-cluster'
|
|
2282
|
+
? 'hdrp-skin'
|
|
2283
|
+
: group2Contract === 'skin'
|
|
2284
|
+
? 'pbr-skin'
|
|
2285
|
+
: 'pbr';
|
|
2286
|
+
const resolvedLayoutKind = layoutKindOverride ?? contractLayoutKind;
|
|
1975
2287
|
const usesSharedBootMaterialLayout =
|
|
1976
2288
|
materialShaderId !== undefined && isEngineOwnedMaterialShader(materialShaderId);
|
|
1977
2289
|
// Built-in PBR/unlit/skin layouts are assembled at boot. Re-deriving a
|
|
1978
2290
|
// per-shader layout from the expanded PBR schema would bypass the HDRP
|
|
1979
2291
|
// group(2) selector and bind the URP mesh BGL to the cluster pipeline.
|
|
1980
2292
|
const perShaderLayout =
|
|
1981
|
-
!usesSharedBootMaterialLayout &&
|
|
2293
|
+
!usesSharedBootMaterialLayout && materialShaderId !== undefined
|
|
1982
2294
|
? getOrBuildPerShaderMaterialLayout(materialShaderId)
|
|
1983
2295
|
: null;
|
|
1984
2296
|
// feat-20260621-learn-render-5-5-parallax M2 / w6 (D-1): an authored
|
|
@@ -2030,7 +2342,7 @@ async function makeWebGPURenderer(
|
|
|
2030
2342
|
? preparedMaterialLayout
|
|
2031
2343
|
: perShaderLayout !== null
|
|
2032
2344
|
? perShaderLayout.pipelineLayout
|
|
2033
|
-
: selectPipelineLayoutForVariant(pipelineState, variantSet,
|
|
2345
|
+
: selectPipelineLayoutForVariant(pipelineState, variantSet, resolvedLayoutKind);
|
|
2034
2346
|
if (pipelineLayout === null) return null;
|
|
2035
2347
|
// feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4):
|
|
2036
2348
|
// pbr-skin -> deriveVertexBufferLayout (single SSOT for skin/non-skin
|
|
@@ -2081,7 +2393,7 @@ async function makeWebGPURenderer(
|
|
|
2081
2393
|
: undefined,
|
|
2082
2394
|
),
|
|
2083
2395
|
)
|
|
2084
|
-
:
|
|
2396
|
+
: materialShaderId === SKIN_MATERIAL_SHADER_ID
|
|
2085
2397
|
? toGpuVertexBufferLayouts(
|
|
2086
2398
|
deriveVertexBufferLayout(
|
|
2087
2399
|
meshAttributes ??
|
|
@@ -2127,7 +2439,7 @@ async function makeWebGPURenderer(
|
|
|
2127
2439
|
: getShaderModuleAdapter(),
|
|
2128
2440
|
pipelineLayout,
|
|
2129
2441
|
vertexBuffers,
|
|
2130
|
-
...(
|
|
2442
|
+
...(resolvedLayoutKind === 'pbr-skin' ? { layoutKind: 'pbr-skin' as const } : {}),
|
|
2131
2443
|
};
|
|
2132
2444
|
};
|
|
2133
2445
|
/**
|
|
@@ -2151,7 +2463,7 @@ async function makeWebGPURenderer(
|
|
|
2151
2463
|
passKind: PassKind = 'forward',
|
|
2152
2464
|
// bug-20260611-skin-pipeline-layout: thread materialShaderId so
|
|
2153
2465
|
// buildPipelineContext can derive `LayoutKind === 'pbr-skin'` and pick
|
|
2154
|
-
// the
|
|
2466
|
+
// the 3-entry mesh-array BGL chain.
|
|
2155
2467
|
materialShaderId?: string,
|
|
2156
2468
|
// feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4):
|
|
2157
2469
|
// pass the per-mesh `VertexAttributeMap` so the pbr-skin path's
|
|
@@ -2187,7 +2499,12 @@ async function makeWebGPURenderer(
|
|
|
2187
2499
|
vertexLayoutProjection?: VertexLayoutProjection,
|
|
2188
2500
|
shaderModuleMode?: RenderFeatureShaderModuleMode,
|
|
2189
2501
|
layoutKindOverride?: LayoutKind,
|
|
2190
|
-
|
|
2502
|
+
additionalColorFormats?: readonly GPUTextureFormat[],
|
|
2503
|
+
): {
|
|
2504
|
+
readonly pipeline: RenderPipeline;
|
|
2505
|
+
readonly group2Contract: PipelineGroup2Contract;
|
|
2506
|
+
} | null => {
|
|
2507
|
+
const group2Contract = resolvePipelineGroup2Contract(entry.source);
|
|
2191
2508
|
const ctx = buildPipelineContext(
|
|
2192
2509
|
variantSet,
|
|
2193
2510
|
materialShaderId,
|
|
@@ -2195,6 +2512,7 @@ async function makeWebGPURenderer(
|
|
|
2195
2512
|
vertexLayout,
|
|
2196
2513
|
vertexLayoutProjection,
|
|
2197
2514
|
shaderModuleMode,
|
|
2515
|
+
group2Contract,
|
|
2198
2516
|
layoutKindOverride,
|
|
2199
2517
|
);
|
|
2200
2518
|
if (ctx === null) return null;
|
|
@@ -2217,6 +2535,10 @@ async function makeWebGPURenderer(
|
|
|
2217
2535
|
{
|
|
2218
2536
|
...ctx,
|
|
2219
2537
|
colorFormat: isHdr ? HDR_COLOR_ATTACHMENT_FORMAT : ldrColorFormat,
|
|
2538
|
+
colorFormats:
|
|
2539
|
+
additionalColorFormats === undefined
|
|
2540
|
+
? undefined
|
|
2541
|
+
: [isHdr ? HDR_COLOR_ATTACHMENT_FORMAT : ldrColorFormat, ...additionalColorFormats],
|
|
2220
2542
|
depthFormat:
|
|
2221
2543
|
depthFormatOverride === null ? undefined : (depthFormatOverride ?? DEPTH_TEXTURE_FORMAT),
|
|
2222
2544
|
label,
|
|
@@ -2254,10 +2576,14 @@ async function makeWebGPURenderer(
|
|
|
2254
2576
|
}
|
|
2255
2577
|
return null;
|
|
2256
2578
|
}
|
|
2257
|
-
|
|
2258
|
-
|
|
2579
|
+
const pipelineEntry = {
|
|
2580
|
+
pipeline: built.value,
|
|
2581
|
+
group2Contract,
|
|
2582
|
+
} as const;
|
|
2583
|
+
materialShaderPipelineCache.set(cacheKey, pipelineEntry);
|
|
2584
|
+
return pipelineEntry;
|
|
2259
2585
|
};
|
|
2260
|
-
const
|
|
2586
|
+
const resolveMaterialShaderPipelineEntry = (
|
|
2261
2587
|
materialShaderId: string,
|
|
2262
2588
|
isHdr: boolean,
|
|
2263
2589
|
renderState?: MaterialRenderState,
|
|
@@ -2304,7 +2630,11 @@ async function makeWebGPURenderer(
|
|
|
2304
2630
|
vertexLayoutProjection?: VertexLayoutProjection,
|
|
2305
2631
|
shaderModuleMode: RenderFeatureShaderModuleMode = 'validated',
|
|
2306
2632
|
layoutKindOverride?: LayoutKind,
|
|
2307
|
-
|
|
2633
|
+
additionalColorFormats?: readonly GPUTextureFormat[],
|
|
2634
|
+
): {
|
|
2635
|
+
readonly pipeline: RenderPipeline;
|
|
2636
|
+
readonly group2Contract: PipelineGroup2Contract;
|
|
2637
|
+
} | null => {
|
|
2308
2638
|
// WebGL2 is a downlevel backend even when the record stage asks for the
|
|
2309
2639
|
// native-capability variant string. Resolve the variant axes at this
|
|
2310
2640
|
// shared PSO seam so the shader source, BGL layout, and cache key agree on
|
|
@@ -2384,10 +2714,14 @@ async function makeWebGPURenderer(
|
|
|
2384
2714
|
// getPreferredCanvasFormat returns rgba8unorm (dawn-node, lavapipe, wgpu-wasm GLES),
|
|
2385
2715
|
// forcing a redundant second PSO build on first-frame URP record path.
|
|
2386
2716
|
const colorFormat: GPUTextureFormat = isHdr ? 'rgba16float' : ldrColorFormat;
|
|
2717
|
+
const attachmentFormats =
|
|
2718
|
+
additionalColorFormats === undefined
|
|
2719
|
+
? colorFormatsForPassKind(passKind, colorFormat)
|
|
2720
|
+
: [colorFormat, ...additionalColorFormats];
|
|
2387
2721
|
const spec: PipelineSpec = {
|
|
2388
2722
|
shader: { id: materialShaderId, passKind, variantSet: effectiveVariantSet },
|
|
2389
2723
|
attachments: {
|
|
2390
|
-
colorFormats:
|
|
2724
|
+
colorFormats: attachmentFormats,
|
|
2391
2725
|
depthFormat:
|
|
2392
2726
|
passKind === 'shadow-caster' || passKind === 'point-shadow-caster'
|
|
2393
2727
|
? 'depth32float'
|
|
@@ -2424,6 +2758,11 @@ async function makeWebGPURenderer(
|
|
|
2424
2758
|
if (pipelineState === null) return null;
|
|
2425
2759
|
const lookup = getShader().findMaterialArtifact(materialShaderId);
|
|
2426
2760
|
if (!lookup.ok) {
|
|
2761
|
+
// An explicit variant request is an exact artifact contract. There is
|
|
2762
|
+
// no safe boot/PBR substitute: a substitute can pair the requested
|
|
2763
|
+
// source identity with a different group(2) ABI and only fail at GPU
|
|
2764
|
+
// validation. Let the caller retry after the manifest/artifact is fixed.
|
|
2765
|
+
if (effectiveVariantSet !== undefined) return null;
|
|
2427
2766
|
if (shouldDeferMissingPreparedMaterialShader(vertexLayout)) {
|
|
2428
2767
|
// Prepared VFX draws bind only their feature-owned groups. The
|
|
2429
2768
|
// built-in PBR fallback declares mesh/instance groups 2/3 and would
|
|
@@ -2471,6 +2810,8 @@ async function makeWebGPURenderer(
|
|
|
2471
2810
|
vertexLayout,
|
|
2472
2811
|
vertexLayoutProjection,
|
|
2473
2812
|
shaderModuleMode,
|
|
2813
|
+
layoutKindOverride,
|
|
2814
|
+
additionalColorFormats,
|
|
2474
2815
|
);
|
|
2475
2816
|
}
|
|
2476
2817
|
const pbrEntry = findStandardPbrEntry(effectiveVariantSet);
|
|
@@ -2525,6 +2866,8 @@ async function makeWebGPURenderer(
|
|
|
2525
2866
|
vertexLayout,
|
|
2526
2867
|
vertexLayoutProjection,
|
|
2527
2868
|
shaderModuleMode,
|
|
2869
|
+
layoutKindOverride,
|
|
2870
|
+
additionalColorFormats,
|
|
2528
2871
|
);
|
|
2529
2872
|
}
|
|
2530
2873
|
// feat-20260609 M4 / w31: resolve variant WGSL from manifest when
|
|
@@ -2544,24 +2887,26 @@ async function makeWebGPURenderer(
|
|
|
2544
2887
|
// Use `!== undefined` so the empty-string case enters the lookup.
|
|
2545
2888
|
if (effectiveVariantSet !== undefined) {
|
|
2546
2889
|
const registry = getShader();
|
|
2890
|
+
let matchedVariant = false;
|
|
2547
2891
|
for (const msEntry of registry.materialShaderManifestEntries()) {
|
|
2548
2892
|
if (msEntry.identifier === materialShaderId) {
|
|
2549
2893
|
const variant = findVariantByKey(msEntry, effectiveVariantSet);
|
|
2550
|
-
if (variant)
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
}
|
|
2894
|
+
if (variant === undefined) return null;
|
|
2895
|
+
matchedVariant = true;
|
|
2896
|
+
// M3 / w12-w13: variant substitution carries the same source +
|
|
2897
|
+
// paramSchema as the boot-registered entry; the binding layout
|
|
2898
|
+
// is no longer carried on MaterialShaderEntry (deleted in
|
|
2899
|
+
// w13) — buildPbrPipelineLayouts is the BGL SSOT and reads
|
|
2900
|
+
// derive(paramSchema).bglEntries on demand.
|
|
2901
|
+
shaderEntry = {
|
|
2902
|
+
source: variant.composedWgsl,
|
|
2903
|
+
paramSchema: lookup.value.paramSchema,
|
|
2904
|
+
paramSchemaProjection: lookup.value.paramSchemaProjection,
|
|
2905
|
+
};
|
|
2562
2906
|
break;
|
|
2563
2907
|
}
|
|
2564
2908
|
}
|
|
2909
|
+
if (!matchedVariant) return null;
|
|
2565
2910
|
}
|
|
2566
2911
|
// feat-20260609 M4 / R3-fixup: append `-${passKind}` to the PSO label so
|
|
2567
2912
|
// GPU debug captures (and the shadow-caster branch in the builder)
|
|
@@ -2602,7 +2947,7 @@ async function makeWebGPURenderer(
|
|
|
2602
2947
|
passKind,
|
|
2603
2948
|
// bug-20260611-skin-pipeline-layout: thread the registered materialShaderId
|
|
2604
2949
|
// so buildPipelineContext can resolve LayoutKind='pbr-skin' for the skin
|
|
2605
|
-
// shader (
|
|
2950
|
+
// shader (3-entry mesh-array BGL).
|
|
2606
2951
|
materialShaderId,
|
|
2607
2952
|
// feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4):
|
|
2608
2953
|
// forward meshAttributes so the pbr-skin path's vertex buffer layout
|
|
@@ -2617,12 +2962,50 @@ async function makeWebGPURenderer(
|
|
|
2617
2962
|
vertexLayoutProjection,
|
|
2618
2963
|
shaderModuleMode,
|
|
2619
2964
|
layoutKindOverride,
|
|
2965
|
+
additionalColorFormats,
|
|
2620
2966
|
);
|
|
2621
2967
|
};
|
|
2968
|
+
const getMaterialShaderPipelineEntry = (
|
|
2969
|
+
...args: Parameters<typeof resolveMaterialShaderPipelineEntry>
|
|
2970
|
+
) => resolveMaterialShaderPipelineEntry(...args);
|
|
2971
|
+
const getMaterialShaderPipeline = (
|
|
2972
|
+
...args: Parameters<typeof resolveMaterialShaderPipelineEntry>
|
|
2973
|
+
): RenderPipeline | null => getMaterialShaderPipelineEntry(...args)?.pipeline ?? null;
|
|
2622
2974
|
const getParamSchema = (materialShaderId: string) => {
|
|
2623
2975
|
const lookup = getShader().findMaterialArtifact(materialShaderId);
|
|
2624
2976
|
return lookup.ok ? lookup.value.paramSchema : undefined;
|
|
2625
2977
|
};
|
|
2978
|
+
const getMaterialShaderArtifact = (materialShaderId: string) => {
|
|
2979
|
+
if (
|
|
2980
|
+
materialShaderId !== 'forgeax::default-standard-pbr' &&
|
|
2981
|
+
materialShaderId !== 'forgeax::default-standard-pbr-skin'
|
|
2982
|
+
) {
|
|
2983
|
+
return undefined;
|
|
2984
|
+
}
|
|
2985
|
+
const lookup = getShader().findMaterialArtifact(materialShaderId);
|
|
2986
|
+
if (!lookup.ok) return undefined;
|
|
2987
|
+
const receipt = createStandardPbrArtifactReceipt(
|
|
2988
|
+
materialShaderId === 'forgeax::default-standard-pbr-skin',
|
|
2989
|
+
);
|
|
2990
|
+
const uvSetCount = materialShaderUvSetCounts.get(materialShaderId);
|
|
2991
|
+
return {
|
|
2992
|
+
material: materialShaderId,
|
|
2993
|
+
pass: 'forward',
|
|
2994
|
+
wgsl: lookup.value.source,
|
|
2995
|
+
layoutIdentity: receipt.reflection.layoutIdentity,
|
|
2996
|
+
...(uvSetCount === undefined ? {} : { uvSetCount }),
|
|
2997
|
+
bindings: [],
|
|
2998
|
+
deps: [],
|
|
2999
|
+
vertexInputs: receipt.vertexInputs.map(
|
|
3000
|
+
(input): Readonly<Record<string, unknown>> => ({
|
|
3001
|
+
semantic: input.semantic,
|
|
3002
|
+
location: input.location,
|
|
3003
|
+
format: input.format,
|
|
3004
|
+
}),
|
|
3005
|
+
),
|
|
3006
|
+
receipt,
|
|
3007
|
+
};
|
|
3008
|
+
};
|
|
2626
3009
|
// feat-20260621-learn-render-5-5-parallax M2 / w6 (D-1): expose the per-shader
|
|
2627
3010
|
// material BGL so the record stage creates the material bind group against
|
|
2628
3011
|
// the matching paramSchema-derived layout. Returns undefined only for
|
|
@@ -2727,6 +3110,13 @@ async function makeWebGPURenderer(
|
|
|
2727
3110
|
);
|
|
2728
3111
|
const renderSystem: RenderSystem = createRenderSystem({
|
|
2729
3112
|
canvas: internals.canvas,
|
|
3113
|
+
// Keep inspection submits tied to the exact build-tool revision. This
|
|
3114
|
+
// bridge is independent of the SSR identity and is required by
|
|
3115
|
+
// renderer-owned LOD World attribution.
|
|
3116
|
+
get build() {
|
|
3117
|
+
return internals.bundler?.build;
|
|
3118
|
+
},
|
|
3119
|
+
ssrIdentity: internals.options?.ssrIdentity,
|
|
2730
3120
|
get standardProfile() {
|
|
2731
3121
|
return activeProfile;
|
|
2732
3122
|
},
|
|
@@ -2739,6 +3129,10 @@ async function makeWebGPURenderer(
|
|
|
2739
3129
|
shaderModuleFactory: {
|
|
2740
3130
|
createShaderModule: (descriptor) => getShaderModuleAdapter().createShaderModule(descriptor),
|
|
2741
3131
|
},
|
|
3132
|
+
createShaderModule: (device, descriptor) =>
|
|
3133
|
+
internals.pack.createShaderModule === undefined
|
|
3134
|
+
? Promise.resolve(invokeDeviceCreateShaderModule(device, descriptor))
|
|
3135
|
+
: internals.pack.createShaderModule(device, descriptor),
|
|
2742
3136
|
immediateShaderModuleFactory: {
|
|
2743
3137
|
createShaderModule: (descriptor) =>
|
|
2744
3138
|
getImmediateShaderModuleAdapter().createShaderModule(descriptor),
|
|
@@ -2752,6 +3146,9 @@ async function makeWebGPURenderer(
|
|
|
2752
3146
|
get device() {
|
|
2753
3147
|
return internals.device;
|
|
2754
3148
|
},
|
|
3149
|
+
get beforeSubmit() {
|
|
3150
|
+
return internals.pack.instrumentation?.beforeSubmit;
|
|
3151
|
+
},
|
|
2755
3152
|
get deviceScope() {
|
|
2756
3153
|
return activeDeviceScope;
|
|
2757
3154
|
},
|
|
@@ -2775,8 +3172,10 @@ async function makeWebGPURenderer(
|
|
|
2775
3172
|
errorRegistry: internals.errorRegistry,
|
|
2776
3173
|
healthRegistry: internals.healthRegistry,
|
|
2777
3174
|
getMaterialShaderPipeline,
|
|
3175
|
+
getMaterialShaderPipelineEntry,
|
|
2778
3176
|
getMaterialShaderBindingContract: getCachedMaterialShaderBindingContract,
|
|
2779
3177
|
getParamSchema,
|
|
3178
|
+
getMaterialShaderArtifact,
|
|
2780
3179
|
getMaterialBindGroupLayout,
|
|
2781
3180
|
metrics,
|
|
2782
3181
|
// feat-20260609 M4 / T-10-a: post-process pipeline factory (CONCERN-1 fix).
|
|
@@ -2794,10 +3193,93 @@ async function makeWebGPURenderer(
|
|
|
2794
3193
|
get meshSsboState() {
|
|
2795
3194
|
return internals.meshSsboState;
|
|
2796
3195
|
},
|
|
3196
|
+
get gpuPassTimingSession() {
|
|
3197
|
+
return (
|
|
3198
|
+
internals as WebGPURendererInternals & {
|
|
3199
|
+
gpuPassTimingSession?: GpuPassTimingSession | undefined;
|
|
3200
|
+
}
|
|
3201
|
+
).gpuPassTimingSession;
|
|
3202
|
+
},
|
|
3203
|
+
set gpuPassTimingSession(session: GpuPassTimingSession | undefined) {
|
|
3204
|
+
(
|
|
3205
|
+
internals as WebGPURendererInternals & {
|
|
3206
|
+
gpuPassTimingSession?: GpuPassTimingSession | undefined;
|
|
3207
|
+
}
|
|
3208
|
+
).gpuPassTimingSession = session;
|
|
3209
|
+
},
|
|
3210
|
+
get gpuPassTimingCapture() {
|
|
3211
|
+
return (
|
|
3212
|
+
internals as WebGPURendererInternals & {
|
|
3213
|
+
gpuPassTimingCapture?: GpuPassTimingCapture | undefined;
|
|
3214
|
+
}
|
|
3215
|
+
).gpuPassTimingCapture;
|
|
3216
|
+
},
|
|
3217
|
+
set gpuPassTimingCapture(capture: GpuPassTimingCapture | undefined) {
|
|
3218
|
+
(
|
|
3219
|
+
internals as WebGPURendererInternals & {
|
|
3220
|
+
gpuPassTimingCapture?: GpuPassTimingCapture | undefined;
|
|
3221
|
+
}
|
|
3222
|
+
).gpuPassTimingCapture = capture;
|
|
3223
|
+
},
|
|
3224
|
+
get gpuPassTimingSubmittedWork() {
|
|
3225
|
+
return (
|
|
3226
|
+
internals as WebGPURendererInternals & {
|
|
3227
|
+
gpuPassTimingSubmittedWork?: Promise<void> | undefined;
|
|
3228
|
+
}
|
|
3229
|
+
).gpuPassTimingSubmittedWork;
|
|
3230
|
+
},
|
|
3231
|
+
set gpuPassTimingSubmittedWork(completion: Promise<void> | undefined) {
|
|
3232
|
+
(
|
|
3233
|
+
internals as WebGPURendererInternals & {
|
|
3234
|
+
gpuPassTimingSubmittedWork?: Promise<void> | undefined;
|
|
3235
|
+
}
|
|
3236
|
+
).gpuPassTimingSubmittedWork = completion;
|
|
3237
|
+
},
|
|
3238
|
+
get gpuPassTimingFrameIdentity() {
|
|
3239
|
+
return (
|
|
3240
|
+
internals as WebGPURendererInternals & {
|
|
3241
|
+
gpuPassTimingFrameIdentity?:
|
|
3242
|
+
| import('../record/gpu-pass-timing/session.js').GpuPassTimingFrameIdentity
|
|
3243
|
+
| undefined;
|
|
3244
|
+
}
|
|
3245
|
+
).gpuPassTimingFrameIdentity;
|
|
3246
|
+
},
|
|
3247
|
+
set gpuPassTimingFrameIdentity(identity:
|
|
3248
|
+
| import('../record/gpu-pass-timing/session.js').GpuPassTimingFrameIdentity
|
|
3249
|
+
| undefined) {
|
|
3250
|
+
(
|
|
3251
|
+
internals as WebGPURendererInternals & {
|
|
3252
|
+
gpuPassTimingFrameIdentity?:
|
|
3253
|
+
| import('../record/gpu-pass-timing/session.js').GpuPassTimingFrameIdentity
|
|
3254
|
+
| undefined;
|
|
3255
|
+
}
|
|
3256
|
+
).gpuPassTimingFrameIdentity = identity;
|
|
3257
|
+
},
|
|
3258
|
+
get gpuPassTimingBeginReason() {
|
|
3259
|
+
return (
|
|
3260
|
+
internals as WebGPURendererInternals & {
|
|
3261
|
+
gpuPassTimingBeginReason?: GpuPassTimingReason | undefined;
|
|
3262
|
+
}
|
|
3263
|
+
).gpuPassTimingBeginReason;
|
|
3264
|
+
},
|
|
3265
|
+
set gpuPassTimingBeginReason(reason: GpuPassTimingReason | undefined) {
|
|
3266
|
+
(
|
|
3267
|
+
internals as WebGPURendererInternals & {
|
|
3268
|
+
gpuPassTimingBeginReason?: GpuPassTimingReason | undefined;
|
|
3269
|
+
}
|
|
3270
|
+
).gpuPassTimingBeginReason = reason;
|
|
3271
|
+
},
|
|
3272
|
+
getRenderTargetPhysical: (target) => renderTargetHost.getPhysicalTarget(target),
|
|
3273
|
+
resolveRenderTargetTextureSource: (source) =>
|
|
3274
|
+
renderTargetHost.resolveRenderTargetTextureSource(source),
|
|
3275
|
+
encodeRenderTargetReadbacks: (encoder, faces) =>
|
|
3276
|
+
renderTargetHost.encodePendingReadbacks(encoder, faces),
|
|
2797
3277
|
get volumetricFogShaders() {
|
|
2798
3278
|
return internals.volumetricFogShaders;
|
|
2799
3279
|
},
|
|
2800
3280
|
});
|
|
3281
|
+
attachGpuPassTimingSession(internals, gpuPassTimingSession);
|
|
3282
|
+
renderSystemForTargetPromotion = renderSystem;
|
|
2801
3283
|
if (
|
|
2802
3284
|
internals.device.caps.backendKind === 'null' &&
|
|
2803
3285
|
Array.from(getShader().entries()).length === 0
|
|
@@ -2822,6 +3304,10 @@ async function makeWebGPURenderer(
|
|
|
2822
3304
|
const transformReleases = new Map<World, () => void>();
|
|
2823
3305
|
const attachmentOwner = {};
|
|
2824
3306
|
let frameId = 0;
|
|
3307
|
+
// Receipts remain valid for observation until their device generation is
|
|
3308
|
+
// retired. Keeping a renderer-owned registry avoids treating a concurrent
|
|
3309
|
+
// RAF submission as invalidation of an otherwise current receipt.
|
|
3310
|
+
const currentReceipts = new Set<FrameReceipt>();
|
|
2825
3311
|
const issuedReceipts = new WeakSet<FrameReceipt>();
|
|
2826
3312
|
const receiptTimings = new WeakMap<FrameReceipt, Promise<VolumeTimingObservation>>();
|
|
2827
3313
|
let surfaceReleased = false;
|
|
@@ -2837,13 +3323,21 @@ async function makeWebGPURenderer(
|
|
|
2837
3323
|
leasesByWorld.set(world, lease);
|
|
2838
3324
|
return ok(lease);
|
|
2839
3325
|
},
|
|
3326
|
+
createRenderTarget: (descriptor) => renderTargetHost.createRenderTarget(descriptor),
|
|
3327
|
+
resizeRenderTarget: (target, descriptor) =>
|
|
3328
|
+
renderTargetHost.resizeRenderTarget(target, descriptor),
|
|
3329
|
+
createRenderTargetTextureSource: (target, options) =>
|
|
3330
|
+
renderTargetHost.createRenderTargetTextureSource(target, options),
|
|
3331
|
+
requestTargetReadback: (target, request) =>
|
|
3332
|
+
renderTargetHost.requestTargetReadback(target, request),
|
|
3333
|
+
destroyRenderTarget: (target) => renderTargetHost.destroyRenderTarget(target),
|
|
2840
3334
|
setProfile(profile: RenderProfile): RenderResult<void, RenderError> {
|
|
2841
3335
|
const invalid = validateRenderProfile(profile);
|
|
2842
3336
|
if (invalid !== undefined) {
|
|
2843
3337
|
return err(
|
|
2844
3338
|
new RendererOperationError('frame-input-invalid', {
|
|
2845
3339
|
operation: 'set-profile',
|
|
2846
|
-
cause: new
|
|
3340
|
+
cause: new StandardProfileInvalidError(invalid),
|
|
2847
3341
|
}),
|
|
2848
3342
|
);
|
|
2849
3343
|
}
|
|
@@ -2862,6 +3356,12 @@ async function makeWebGPURenderer(
|
|
|
2862
3356
|
);
|
|
2863
3357
|
}
|
|
2864
3358
|
},
|
|
3359
|
+
inspectLodOcclusion() {
|
|
3360
|
+
return {
|
|
3361
|
+
lodOcclusion: renderSystem.lodOcclusionInspection,
|
|
3362
|
+
gpuDriven: renderSystem.gpuDrivenInspection,
|
|
3363
|
+
};
|
|
3364
|
+
},
|
|
2865
3365
|
inspect(): RenderInspection {
|
|
2866
3366
|
const health = internals.healthRegistry.getLastSnapshot();
|
|
2867
3367
|
const state = disposed
|
|
@@ -2888,6 +3388,7 @@ async function makeWebGPURenderer(
|
|
|
2888
3388
|
: { presentationProof: internals.context.presentationProof }),
|
|
2889
3389
|
});
|
|
2890
3390
|
const volumetricFog = renderSystem.volumetricFog;
|
|
3391
|
+
const directionalShadow = renderSystem.directionalShadow;
|
|
2891
3392
|
return Object.freeze({
|
|
2892
3393
|
state,
|
|
2893
3394
|
surface: surfaceReleased ? 'released' : 'available',
|
|
@@ -2905,18 +3406,31 @@ async function makeWebGPURenderer(
|
|
|
2905
3406
|
frustumStats: Object.freeze({ ...renderSystem.frustumStats }),
|
|
2906
3407
|
visibilityStats: Object.freeze({ ...renderSystem.visibilityStats }),
|
|
2907
3408
|
renderScene: renderSystem.renderScene,
|
|
3409
|
+
reflectionProbes: renderSystem.reflectionProbes,
|
|
3410
|
+
ssrDependencies: renderSystem.ssrDependencies,
|
|
2908
3411
|
environment: renderSystem.environment,
|
|
2909
3412
|
temporal: renderSystem.temporal,
|
|
2910
3413
|
bloom: renderSystem.bloom,
|
|
3414
|
+
extendedLighting: projectExtendedLightingInspection(extendedLightingState),
|
|
3415
|
+
directionalShadow,
|
|
2911
3416
|
...(renderSystem.temporalTargetInspection === undefined
|
|
2912
3417
|
? {}
|
|
2913
3418
|
: { temporalTarget: Object.freeze(renderSystem.temporalTargetInspection) }),
|
|
2914
3419
|
...(renderSystem.motionBlurInspection === undefined
|
|
2915
3420
|
? {}
|
|
2916
3421
|
: { motionBlur: Object.freeze(renderSystem.motionBlurInspection) }),
|
|
3422
|
+
...(renderSystem.lodOcclusionInspection === undefined
|
|
3423
|
+
? {}
|
|
3424
|
+
: { lodOcclusion: Object.freeze(renderSystem.lodOcclusionInspection) }),
|
|
2917
3425
|
...(renderSystem.transmission === undefined
|
|
2918
3426
|
? {}
|
|
2919
3427
|
: { transmission: renderSystem.transmission }),
|
|
3428
|
+
...(renderSystem.standardLightingInspection === undefined
|
|
3429
|
+
? {}
|
|
3430
|
+
: { standardLighting: Object.freeze({ ...renderSystem.standardLightingInspection }) }),
|
|
3431
|
+
...(renderSystem.pointShadowInspection === undefined
|
|
3432
|
+
? {}
|
|
3433
|
+
: { pointShadow: Object.freeze({ ...renderSystem.pointShadowInspection }) }),
|
|
2920
3434
|
meshMaterialBindings: Object.freeze(
|
|
2921
3435
|
renderSystem.meshMaterialBindings.map((observation) =>
|
|
2922
3436
|
Object.freeze({
|
|
@@ -3065,6 +3579,9 @@ async function makeWebGPURenderer(
|
|
|
3065
3579
|
get bindGroupCounts() {
|
|
3066
3580
|
return renderSystem.bindGroupCounts;
|
|
3067
3581
|
},
|
|
3582
|
+
configureStandard(config) {
|
|
3583
|
+
renderSystem.configureStandard(config);
|
|
3584
|
+
},
|
|
3068
3585
|
renderFeatureDiagnostics() {
|
|
3069
3586
|
return internals.featureHost?.diagnostics() ?? [];
|
|
3070
3587
|
},
|
|
@@ -3090,6 +3607,35 @@ async function makeWebGPURenderer(
|
|
|
3090
3607
|
new RenderFeatureCapabilityMissingError(feature.identity, host.size, missingCapability),
|
|
3091
3608
|
);
|
|
3092
3609
|
}
|
|
3610
|
+
let nextRequiredFullscreenPostProcesses: readonly {
|
|
3611
|
+
readonly identity: string;
|
|
3612
|
+
readonly source: string;
|
|
3613
|
+
}[];
|
|
3614
|
+
try {
|
|
3615
|
+
nextRequiredFullscreenPostProcesses = collectRequiredFullscreenPostProcesses([
|
|
3616
|
+
...host.features,
|
|
3617
|
+
feature,
|
|
3618
|
+
]);
|
|
3619
|
+
} catch (cause) {
|
|
3620
|
+
const conflict =
|
|
3621
|
+
cause instanceof RhiError
|
|
3622
|
+
? cause
|
|
3623
|
+
: new RhiError({
|
|
3624
|
+
code: 'internal-error',
|
|
3625
|
+
expected: 'fullscreen feature declarations are internally consistent',
|
|
3626
|
+
hint: 'repair the conflicting fullscreen identity/source declarations',
|
|
3627
|
+
});
|
|
3628
|
+
internals.errorRegistry.fire(conflict);
|
|
3629
|
+
return err(
|
|
3630
|
+
new RenderFeatureStageFailedError(feature.identity, -1, 'prepare', 'registration'),
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3633
|
+
const fullscreenPrewarm = await prewarmFullscreenFeatureModules(feature);
|
|
3634
|
+
if (!fullscreenPrewarm.ok) {
|
|
3635
|
+
return err(
|
|
3636
|
+
new RenderFeatureStageFailedError(feature.identity, -1, 'prepare', 'registration'),
|
|
3637
|
+
);
|
|
3638
|
+
}
|
|
3093
3639
|
for (const materialShaderId of feature.requiredMaterialShaders ?? []) {
|
|
3094
3640
|
const lookup = getShader().findMaterialArtifact(materialShaderId);
|
|
3095
3641
|
if (!lookup.ok) {
|
|
@@ -3128,6 +3674,7 @@ async function makeWebGPURenderer(
|
|
|
3128
3674
|
requiredMaterialShaderSet.add(materialShaderId);
|
|
3129
3675
|
}
|
|
3130
3676
|
requiredMaterialShaders = Object.freeze([...requiredMaterialShaderSet]);
|
|
3677
|
+
requiredFullscreenPostProcesses = nextRequiredFullscreenPostProcesses;
|
|
3131
3678
|
}
|
|
3132
3679
|
return installed;
|
|
3133
3680
|
},
|
|
@@ -3156,6 +3703,7 @@ async function makeWebGPURenderer(
|
|
|
3156
3703
|
}
|
|
3157
3704
|
}
|
|
3158
3705
|
requiredMaterialShaders = Object.freeze([...requiredMaterialShaderSet]);
|
|
3706
|
+
requiredFullscreenPostProcesses = collectRequiredFullscreenPostProcesses(host.features);
|
|
3159
3707
|
}
|
|
3160
3708
|
return removed;
|
|
3161
3709
|
},
|
|
@@ -3329,23 +3877,52 @@ async function makeWebGPURenderer(
|
|
|
3329
3877
|
// through onError separately; the facade Result is the synchronous
|
|
3330
3878
|
// summary AI users can ignore or branch on).
|
|
3331
3879
|
try {
|
|
3880
|
+
renderTargetHost.beginFrame();
|
|
3881
|
+
const timingHost = internals as WebGPURendererInternals & {
|
|
3882
|
+
gpuPassTimingFrameIdentity?:
|
|
3883
|
+
| {
|
|
3884
|
+
readonly frameId: number;
|
|
3885
|
+
readonly deviceGeneration: number;
|
|
3886
|
+
readonly graphGeneration: number;
|
|
3887
|
+
}
|
|
3888
|
+
| undefined;
|
|
3889
|
+
gpuPassTimingCapture?:
|
|
3890
|
+
| import('../record/gpu-pass-timing/session.js').GpuPassTimingCapture
|
|
3891
|
+
| undefined;
|
|
3892
|
+
gpuPassTimingBeginReason?: GpuPassTimingReason | undefined;
|
|
3893
|
+
};
|
|
3332
3894
|
const timingRequested =
|
|
3333
3895
|
isFrameRequest &&
|
|
3334
3896
|
(frameRequest?.profileFrame !== undefined ||
|
|
3335
3897
|
internals.options?.captureGpuTimings === true);
|
|
3336
|
-
let
|
|
3337
|
-
|
|
3898
|
+
let volumeTimingCapture: GpuTimingCapture | undefined;
|
|
3899
|
+
const volumeTimingUnavailableReason = timingRequested
|
|
3338
3900
|
? 'timestamp capture is unavailable on this device'
|
|
3339
3901
|
: 'timestamp capture was not requested for this frame';
|
|
3340
|
-
|
|
3902
|
+
// The pass-timing session is authoritative when the feature is
|
|
3903
|
+
// enabled. Keep the volume-specific capture for its existing
|
|
3904
|
+
// benchmark path, but do not allocate two timestamp readbacks for a
|
|
3905
|
+
// single receipt when both options are present.
|
|
3906
|
+
if (timingRequested && gpuPassTimingOptions === undefined) {
|
|
3341
3907
|
const createdTiming = GpuTimingCapture.create(internals.device);
|
|
3342
|
-
if (createdTiming.ok)
|
|
3343
|
-
|
|
3344
|
-
|
|
3908
|
+
if (createdTiming.ok) volumeTimingCapture = createdTiming.value;
|
|
3909
|
+
}
|
|
3910
|
+
if (isFrameRequest) {
|
|
3911
|
+
timingHost.gpuPassTimingFrameIdentity = {
|
|
3912
|
+
frameId: frameId + 1,
|
|
3913
|
+
deviceGeneration: activeDeviceScope.generation,
|
|
3914
|
+
graphGeneration: 0,
|
|
3915
|
+
};
|
|
3345
3916
|
}
|
|
3346
|
-
const submitted = renderSystem.draw(
|
|
3917
|
+
const submitted = renderSystem.draw(
|
|
3918
|
+
worlds,
|
|
3919
|
+
drawOptions,
|
|
3920
|
+
renderReadLeases,
|
|
3921
|
+
volumeTimingCapture,
|
|
3922
|
+
);
|
|
3923
|
+
timingHost.gpuPassTimingFrameIdentity = undefined;
|
|
3347
3924
|
if (!submitted) {
|
|
3348
|
-
|
|
3925
|
+
volumeTimingCapture?.discard();
|
|
3349
3926
|
return err(
|
|
3350
3927
|
new RendererContractFailureError(
|
|
3351
3928
|
'draw',
|
|
@@ -3353,36 +3930,80 @@ async function makeWebGPURenderer(
|
|
|
3353
3930
|
),
|
|
3354
3931
|
);
|
|
3355
3932
|
}
|
|
3933
|
+
const timingCompletion = timingHost.gpuPassTimingSubmittedWork;
|
|
3934
|
+
timingHost.gpuPassTimingSubmittedWork = undefined;
|
|
3935
|
+
const reflectionFallbackCompletion = renderSystem.reflectionFallbackCompletion;
|
|
3356
3936
|
internals.pack.instrumentation?.onFrameBoundary?.();
|
|
3937
|
+
const receiptFrameId = ++frameId;
|
|
3938
|
+
const receiptGeneration = activeDeviceScope.generation;
|
|
3939
|
+
const queueCompletion = timingCompletion ?? internals.device.queue.onSubmittedWorkDone();
|
|
3940
|
+
const completed: FrameReceipt['completed'] = Promise.all([
|
|
3941
|
+
queueCompletion,
|
|
3942
|
+
reflectionFallbackCompletion ?? Promise.resolve(),
|
|
3943
|
+
])
|
|
3944
|
+
.then(() => ok(undefined))
|
|
3945
|
+
.catch((cause: unknown) =>
|
|
3946
|
+
err(
|
|
3947
|
+
new RendererOperationError('device-operation-failed', {
|
|
3948
|
+
operation: 'complete-frame',
|
|
3949
|
+
frameId: receiptFrameId,
|
|
3950
|
+
deviceGeneration: receiptGeneration,
|
|
3951
|
+
cause: structuredRendererCause(cause, 'complete-frame'),
|
|
3952
|
+
}),
|
|
3953
|
+
),
|
|
3954
|
+
);
|
|
3955
|
+
renderTargetHost.onFrameSubmitted(completed);
|
|
3357
3956
|
if (isFrameRequest) {
|
|
3358
|
-
const receiptFrameId = ++frameId;
|
|
3359
|
-
const receiptGeneration = activeDeviceScope.generation;
|
|
3360
|
-
const completed: FrameReceipt['completed'] = internals.device.queue
|
|
3361
|
-
.onSubmittedWorkDone()
|
|
3362
|
-
.then(() => ok(undefined))
|
|
3363
|
-
.catch((cause: unknown) =>
|
|
3364
|
-
err(
|
|
3365
|
-
new RendererOperationError('device-operation-failed', {
|
|
3366
|
-
operation: 'complete-frame',
|
|
3367
|
-
frameId: receiptFrameId,
|
|
3368
|
-
deviceGeneration: receiptGeneration,
|
|
3369
|
-
cause: structuredRendererCause(cause, 'complete-frame'),
|
|
3370
|
-
}),
|
|
3371
|
-
),
|
|
3372
|
-
);
|
|
3373
3957
|
const receipt = Object.freeze({
|
|
3374
3958
|
frameId: receiptFrameId,
|
|
3375
3959
|
deviceGeneration: receiptGeneration,
|
|
3376
3960
|
completed,
|
|
3377
3961
|
});
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3962
|
+
const passTimingCapture = timingHost.gpuPassTimingCapture;
|
|
3963
|
+
const timingBeginReason = timingHost.gpuPassTimingBeginReason;
|
|
3964
|
+
const timingSource =
|
|
3965
|
+
passTimingCapture === undefined
|
|
3966
|
+
? gpuPassTimingUnavailable === undefined && timingBeginReason === undefined
|
|
3967
|
+
? undefined
|
|
3968
|
+
: async (): Promise<GpuPassTimingObservation> =>
|
|
3969
|
+
gpuPassTimingUnavailable ??
|
|
3970
|
+
failedGpuPassTimingObservation(timingBeginReason as GpuPassTimingReason)
|
|
3971
|
+
: async (): Promise<GpuPassTimingObservation> => {
|
|
3972
|
+
const observed = await passTimingCapture.observe();
|
|
3973
|
+
if (!observed.ok) {
|
|
3974
|
+
return failedGpuPassTimingObservation(observed.error);
|
|
3975
|
+
}
|
|
3976
|
+
const frame = observed.value;
|
|
3977
|
+
const unmeasured = frame.passes.find((pass) => pass.status === 'unmeasured');
|
|
3978
|
+
if (unmeasured !== undefined) {
|
|
3979
|
+
return { status: 'partial', frame, reason: unmeasured.reason };
|
|
3980
|
+
}
|
|
3981
|
+
if (frame.droppedPassCount > 0) {
|
|
3982
|
+
return {
|
|
3983
|
+
status: 'partial',
|
|
3984
|
+
frame,
|
|
3985
|
+
reason: {
|
|
3986
|
+
code: 'query-budget-exceeded',
|
|
3987
|
+
expected: 'all executed passes fit within the bounded query budget',
|
|
3988
|
+
hint: 'increase maxPassesPerFrame or simplify the pass graph',
|
|
3989
|
+
detail: { droppedPassCount: frame.droppedPassCount },
|
|
3990
|
+
},
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
return { status: 'complete', frame };
|
|
3994
|
+
};
|
|
3995
|
+
timingObservationStore?.register(receipt, timingSource);
|
|
3996
|
+
currentReceipts.add(receipt);
|
|
3997
|
+
if (timingRequested && gpuPassTimingOptions === undefined) {
|
|
3998
|
+
receiptTimings.set(
|
|
3999
|
+
receipt,
|
|
4000
|
+
volumeTimingCapture?.observation() ??
|
|
4001
|
+
Promise.resolve({
|
|
4002
|
+
status: 'unavailable',
|
|
4003
|
+
reason: volumeTimingUnavailableReason,
|
|
4004
|
+
}),
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
3386
4007
|
issuedReceipts.add(receipt);
|
|
3387
4008
|
return ok(receipt);
|
|
3388
4009
|
}
|
|
@@ -3407,7 +4028,16 @@ async function makeWebGPURenderer(
|
|
|
3407
4028
|
receipt: FrameReceipt,
|
|
3408
4029
|
request: FrameObservationRequest,
|
|
3409
4030
|
): Promise<RenderResult<FrameReceiptObservation, RenderError>> {
|
|
4031
|
+
// The bounded pass contract owns `timings`; the volume timing producer
|
|
4032
|
+
// owns the separate `volumeTimings` field. Keeping the fields distinct
|
|
4033
|
+
// prevents consumers from having to discriminate an untagged union.
|
|
4034
|
+
if (gpuPassTimingOptions !== undefined) {
|
|
4035
|
+
return timingObservationStore === undefined
|
|
4036
|
+
? observeGpuPassTimingDisabled(receipt, request, () => activeDeviceScope.generation)
|
|
4037
|
+
: timingObservationStore.observe(receipt, request);
|
|
4038
|
+
}
|
|
3410
4039
|
if (
|
|
4040
|
+
!currentReceipts.has(receipt) ||
|
|
3411
4041
|
!issuedReceipts.has(receipt) ||
|
|
3412
4042
|
receipt.deviceGeneration !== activeDeviceScope.generation
|
|
3413
4043
|
) {
|
|
@@ -3432,11 +4062,17 @@ async function makeWebGPURenderer(
|
|
|
3432
4062
|
}),
|
|
3433
4063
|
);
|
|
3434
4064
|
}
|
|
3435
|
-
|
|
4065
|
+
await renderSystem.observeLodOcclusion();
|
|
4066
|
+
const targetReadbacks =
|
|
4067
|
+
request.targetReadbacks === undefined || request.targetReadbacks.length === 0
|
|
4068
|
+
? undefined
|
|
4069
|
+
: await renderTargetHost.observeTargetReadbacks(receipt, request.targetReadbacks);
|
|
4070
|
+
if (targetReadbacks !== undefined && !targetReadbacks.ok) return targetReadbacks;
|
|
4071
|
+
const volumeTimings = request.include.includes('timings')
|
|
3436
4072
|
? await (receiptTimings.get(receipt) ??
|
|
3437
|
-
Promise.resolve({
|
|
4073
|
+
Promise.resolve<VolumeTimingObservation>({
|
|
3438
4074
|
status: 'unavailable',
|
|
3439
|
-
reason: '
|
|
4075
|
+
reason: 'timestamp capture was not requested for this frame',
|
|
3440
4076
|
}))
|
|
3441
4077
|
: undefined;
|
|
3442
4078
|
return ok(
|
|
@@ -3444,7 +4080,10 @@ async function makeWebGPURenderer(
|
|
|
3444
4080
|
frameId: receipt.frameId,
|
|
3445
4081
|
deviceGeneration: receipt.deviceGeneration,
|
|
3446
4082
|
include: Object.freeze([...request.include]),
|
|
3447
|
-
...(
|
|
4083
|
+
...(targetReadbacks === undefined
|
|
4084
|
+
? {}
|
|
4085
|
+
: { targetReadbacks: Object.freeze(targetReadbacks.value) }),
|
|
4086
|
+
...(volumeTimings === undefined ? {} : { volumeTimings }),
|
|
3448
4087
|
}),
|
|
3449
4088
|
);
|
|
3450
4089
|
},
|
|
@@ -3519,6 +4158,8 @@ async function makeWebGPURenderer(
|
|
|
3519
4158
|
dispose(): RenderResult<void, RenderError> {
|
|
3520
4159
|
if (disposed) return ok(undefined);
|
|
3521
4160
|
disposed = true;
|
|
4161
|
+
currentReceipts.clear();
|
|
4162
|
+
renderTargetHost.dispose();
|
|
3522
4163
|
const cleanupFailures: RendererOperationCause[] = [];
|
|
3523
4164
|
for (const world of attachedWorlds) {
|
|
3524
4165
|
try {
|
|
@@ -3699,6 +4340,8 @@ async function makeWebGPURenderer(
|
|
|
3699
4340
|
// a layout from the replacement device.
|
|
3700
4341
|
emptyPostProcessBgl = null;
|
|
3701
4342
|
internals.context.unconfigure();
|
|
4343
|
+
retireGpuPassTimingSession();
|
|
4344
|
+
attachGpuPassTimingSession(internals, undefined);
|
|
3702
4345
|
|
|
3703
4346
|
// Step (b) / B-2 / B-AC-02: shed device-bound RenderSystem state minted by
|
|
3704
4347
|
// the lost device — the render-graph pendingDestroy queue (its PooledTextures
|
|
@@ -3845,8 +4488,13 @@ async function makeWebGPURenderer(
|
|
|
3845
4488
|
const previousDeviceScope = activeDeviceScope;
|
|
3846
4489
|
activeDeviceScope = recoveryScope;
|
|
3847
4490
|
pipelineState = candidatePipelineState;
|
|
4491
|
+
promotePipelineExtendedLighting(candidatePipelineState, activeDeviceScope);
|
|
4492
|
+
currentReceipts.clear();
|
|
3848
4493
|
gpuStore.bindDeviceScope(activeDeviceScope);
|
|
4494
|
+
renderTargetHost.recover();
|
|
3849
4495
|
previousDeviceScope.retire();
|
|
4496
|
+
establishGpuPassTimingSession();
|
|
4497
|
+
attachGpuPassTimingSession(internals, gpuPassTimingSession);
|
|
3850
4498
|
|
|
3851
4499
|
// Re-evaluate feature capability gates against the replacement device.
|
|
3852
4500
|
// Feature recovery is isolated from renderer recovery: a feature that is
|
|
@@ -3932,7 +4580,7 @@ export function exposeRenderer(renderer: RendererHostImplementation): Renderer {
|
|
|
3932
4580
|
});
|
|
3933
4581
|
|
|
3934
4582
|
return {
|
|
3935
|
-
attach: (world) => {
|
|
4583
|
+
attach: (world: World) => {
|
|
3936
4584
|
const result = renderer.attach(world);
|
|
3937
4585
|
return result.ok
|
|
3938
4586
|
? result
|
|
@@ -3943,6 +4591,12 @@ export function exposeRenderer(renderer: RendererHostImplementation): Renderer {
|
|
|
3943
4591
|
}),
|
|
3944
4592
|
);
|
|
3945
4593
|
},
|
|
4594
|
+
createRenderTarget: (descriptor) => renderer.createRenderTarget(descriptor),
|
|
4595
|
+
resizeRenderTarget: (target, descriptor) => renderer.resizeRenderTarget(target, descriptor),
|
|
4596
|
+
createRenderTargetTextureSource: (target, options) =>
|
|
4597
|
+
renderer.createRenderTargetTextureSource(target, options),
|
|
4598
|
+
requestTargetReadback: (target, request) => renderer.requestTargetReadback(target, request),
|
|
4599
|
+
destroyRenderTarget: (target) => renderer.destroyRenderTarget(target),
|
|
3946
4600
|
setProfile: (profile) => {
|
|
3947
4601
|
if (!isRendererStateOperational(currentState)) {
|
|
3948
4602
|
return err(
|
|
@@ -3955,7 +4609,7 @@ export function exposeRenderer(renderer: RendererHostImplementation): Renderer {
|
|
|
3955
4609
|
return renderer.setProfile(profile);
|
|
3956
4610
|
},
|
|
3957
4611
|
state: () => currentState,
|
|
3958
|
-
draw: (request) => {
|
|
4612
|
+
draw: (request: RenderFrameInput) => {
|
|
3959
4613
|
const result = renderer.drawFrame(request);
|
|
3960
4614
|
if (!result.ok) return err(drawError(result.error));
|
|
3961
4615
|
emit(
|
|
@@ -3968,8 +4622,9 @@ export function exposeRenderer(renderer: RendererHostImplementation): Renderer {
|
|
|
3968
4622
|
return ok(result.value);
|
|
3969
4623
|
},
|
|
3970
4624
|
inspect: () => renderer.inspect(),
|
|
3971
|
-
observe: (receipt, request) =>
|
|
3972
|
-
|
|
4625
|
+
observe: (receipt: FrameReceipt, request: FrameObservationRequest) =>
|
|
4626
|
+
renderer.observe(receipt, request),
|
|
4627
|
+
subscribe: (listener: RendererEventListener) => {
|
|
3973
4628
|
if (disposed) return () => undefined;
|
|
3974
4629
|
listeners.add(listener);
|
|
3975
4630
|
return () => listeners.delete(listener);
|
|
@@ -4083,17 +4738,11 @@ function structuredRendererCause(cause: unknown, operation: string): RendererOpe
|
|
|
4083
4738
|
const RENDER_PROFILE_KEYS = new Set([
|
|
4084
4739
|
'pipelineId',
|
|
4085
4740
|
'lightCount',
|
|
4086
|
-
'
|
|
4741
|
+
'renderPath',
|
|
4087
4742
|
'shadows',
|
|
4088
4743
|
'pbr',
|
|
4089
4744
|
'ibl',
|
|
4090
4745
|
'ssao',
|
|
4091
|
-
'bloom',
|
|
4092
|
-
'tone',
|
|
4093
|
-
'antialias',
|
|
4094
|
-
'sky',
|
|
4095
|
-
'volumetricFog',
|
|
4096
|
-
'fallback',
|
|
4097
4746
|
'postStages',
|
|
4098
4747
|
]);
|
|
4099
4748
|
|
|
@@ -4107,36 +4756,15 @@ function validateRenderProfile(profile: RenderProfile): string | undefined {
|
|
|
4107
4756
|
if (!STANDARD_LIGHT_COUNTS.some((count) => count === profile.lightCount)) {
|
|
4108
4757
|
return 'RenderProfile.lightCount must be 1, 32, or 256';
|
|
4109
4758
|
}
|
|
4110
|
-
if (profile.
|
|
4111
|
-
return "RenderProfile.
|
|
4759
|
+
if (profile.renderPath !== 'forward' && profile.renderPath !== 'deferred') {
|
|
4760
|
+
return "RenderProfile.renderPath must be 'forward' or 'deferred'";
|
|
4112
4761
|
}
|
|
4113
4762
|
if (profile.shadows !== 'off' && profile.shadows !== 'hard' && profile.shadows !== 'filtered') {
|
|
4114
4763
|
return "RenderProfile.shadows must be 'off', 'hard', or 'filtered'";
|
|
4115
4764
|
}
|
|
4116
|
-
if (profile.fallback !== 'native' && profile.fallback !== 'cpu-webgl2') {
|
|
4117
|
-
return "RenderProfile.fallback must be 'native' or 'cpu-webgl2'";
|
|
4118
|
-
}
|
|
4119
4765
|
for (const field of ['pbr', 'ibl', 'ssao'] as const) {
|
|
4120
4766
|
if (typeof profile[field] !== 'boolean') return `RenderProfile.${field} must be boolean`;
|
|
4121
4767
|
}
|
|
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
4768
|
const stages = STANDARD_POST_STAGE_NAMES;
|
|
4141
4769
|
if (
|
|
4142
4770
|
!Array.isArray(profile.postStages) ||
|
|
@@ -4409,23 +5037,20 @@ function makeRhiNotAvailableError(): RhiError {
|
|
|
4409
5037
|
// inverseViewProj mat4x4<f32> (64 B) appended at tail (offset 176, align 16,
|
|
4410
5038
|
// total 176 + 64 = 240 B). Field added to common.wgsl View struct; host
|
|
4411
5039
|
// writes the tail via queue.writeBuffer in render-system-record.
|
|
4412
|
-
// feat-20260613-csm-cascaded-shadow-maps M4 / w16: extended to
|
|
5040
|
+
// feat-20260613-csm-cascaded-shadow-maps M4 / w16: extended to 960 B.
|
|
4413
5041
|
// lightSpaceMatrix replaced by lightViewProj_A at offset 112; inverseViewProj
|
|
4414
5042
|
// stays at offset 176; lightViewProj_B..D + splitPlanes[4] + cascadeCount +
|
|
4415
|
-
// cascadeBlend + tail padding appended after inverseViewProj.
|
|
4416
|
-
// =
|
|
4417
|
-
//
|
|
4418
|
-
//
|
|
4419
|
-
// append at the formerly-free tail pad (bytes 504/508/512, floats 126/127/128).
|
|
4420
|
-
// VIEW_UBO_BYTES stays 592 (the host tail pad shrinks 88 B -> 64 B); the WGSL
|
|
4421
|
-
// View struct in common.wgsl carries the matching 3 f32 (SSOT comment synced).
|
|
5043
|
+
// cascadeBlend + tail padding appended after inverseViewProj. The directional
|
|
5044
|
+
// payload is 240 f32 = 960 B, placed in a 1024 B dynamic-uniform slot (AC-08).
|
|
5045
|
+
// M1 reserves [126]/[127] for depthBias/normalBias and [128..131] for the
|
|
5046
|
+
// directional filter carrier; the Spot matrix tail begins at float 132.
|
|
4422
5047
|
// feat-20260625-spot-light-shadow-mapping w25 (scope-amend webkit-fallback):
|
|
4423
|
-
// grew
|
|
5048
|
+
// grew from the directional 960 B payload to the 1024 B slot. The per-spot fragment-read perspective lightViewProj matrices
|
|
4424
5049
|
// (`array<mat4x4<f32>, 4>` = 256 B) fold from a standalone @group(0) binding 9
|
|
4425
5050
|
// uniform buffer into the View UBO tail (bytes 528..784, 16 B-aligned after
|
|
4426
|
-
//
|
|
5051
|
+
// the directional filter carrier at bytes 512..528). This removes the 12th fragment-stage uniform
|
|
4427
5052
|
// buffer that overflowed GLES 3.0's max_uniform_buffers_per_shader_stage = 11 on
|
|
4428
|
-
// the WebGL2 fallback path. render-system-record writes the full
|
|
5053
|
+
// the WebGL2 fallback path. render-system-record writes the full 240 f32 payload;
|
|
4429
5054
|
// common.wgsl `View.spotLightViewProj` is the matching SSOT.
|
|
4430
5055
|
const VIEW_UBO_BYTES = VIEW_UNIFORM_BUFFER_SIZE;
|
|
4431
5056
|
|
|
@@ -4914,6 +5539,7 @@ async function prepareMaterialShaders(
|
|
|
4914
5539
|
if (!capCheck.ok) throw capCheck.error;
|
|
4915
5540
|
storageBufferCapable = !webgl2Downlevel && capCheck.value;
|
|
4916
5541
|
}
|
|
5542
|
+
const extendedLightingShaderAvailable = deriveExtendedLightingCapability(rhiDevice).admitted;
|
|
4917
5543
|
// The shared PBR view/material ABI consumes the WebGPU minimum of 16
|
|
4918
5544
|
// fragment sampled textures. The authored SpotLight projector is the
|
|
4919
5545
|
// optional 17th texture; select its shader/BGL pair from the same limit so
|
|
@@ -4922,7 +5548,6 @@ async function prepareMaterialShaders(
|
|
|
4922
5548
|
const projectorAvailable =
|
|
4923
5549
|
(rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
4924
5550
|
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
4925
|
-
|
|
4926
5551
|
// ── Step 1: manifest load ─────────────────────────────────────────────────
|
|
4927
5552
|
const registry = getShader();
|
|
4928
5553
|
const loaded = await registry.loadManifest();
|
|
@@ -4931,12 +5556,11 @@ async function prepareMaterialShaders(
|
|
|
4931
5556
|
}
|
|
4932
5557
|
|
|
4933
5558
|
// ── Step 1b: material shader variant resolution + registration ───────────
|
|
4934
|
-
//
|
|
4935
|
-
//
|
|
4936
|
-
//
|
|
4937
|
-
//
|
|
4938
|
-
|
|
4939
|
-
const isHdrpActive = false; // M4: wire to actual renderSystem.frameState.isHdrpActive
|
|
5559
|
+
// Install the non-clustered artifact as the eager registry seed. This value
|
|
5560
|
+
// only makes the pre-frame registry usable; record-time Standard topology is
|
|
5561
|
+
// derived from the frame's prepared `standardLighting.kind` and never reads
|
|
5562
|
+
// this boot seed as an active lighting decision.
|
|
5563
|
+
const bootClusterAxis = standardBootClusterAxis();
|
|
4940
5564
|
for (const msEntry of registry.materialShaderManifestEntries()) {
|
|
4941
5565
|
// bug-20260610: skip synthetic non-material engine entries piggy-backing
|
|
4942
5566
|
// on the materialShaders channel for variant surfacing (shadow_caster).
|
|
@@ -4947,6 +5571,11 @@ async function prepareMaterialShaders(
|
|
|
4947
5571
|
const variantDefines: Record<string, boolean> = {
|
|
4948
5572
|
STORAGE_BUFFER_AVAILABLE: storageBufferCapable,
|
|
4949
5573
|
};
|
|
5574
|
+
if (msEntry.variants.some((v) => 'DIRECTIONAL_PCSS_AVAILABLE' in v.defines)) {
|
|
5575
|
+
variantDefines.DIRECTIONAL_PCSS_AVAILABLE = directionalPcssCapable(
|
|
5576
|
+
rhiDevice.caps.backendKind,
|
|
5577
|
+
);
|
|
5578
|
+
}
|
|
4950
5579
|
// User material manifests may declare WEBGL2_COMPAT alongside the shared
|
|
4951
5580
|
// mesh-storage axis. The fallback backend must select that authored
|
|
4952
5581
|
// branch at boot; otherwise it asks for the impossible bare
|
|
@@ -4958,11 +5587,20 @@ async function prepareMaterialShaders(
|
|
|
4958
5587
|
// the axis (has a variant with this key), preserving backward compat for
|
|
4959
5588
|
// entries without the pragma.
|
|
4960
5589
|
if (msEntry.variants.some((v) => 'CLUSTER_FORWARD_AVAILABLE' in v.defines)) {
|
|
4961
|
-
variantDefines.CLUSTER_FORWARD_AVAILABLE =
|
|
5590
|
+
variantDefines.CLUSTER_FORWARD_AVAILABLE = bootClusterAxis;
|
|
4962
5591
|
}
|
|
4963
5592
|
if (msEntry.variants.some((v) => 'VERTEX_COLOR_AVAILABLE' in v.defines)) {
|
|
4964
5593
|
variantDefines.VERTEX_COLOR_AVAILABLE = false;
|
|
4965
5594
|
}
|
|
5595
|
+
if (msEntry.variants.some((v) => 'PROBE_BLEND_AVAILABLE' in v.defines)) {
|
|
5596
|
+
// ProbeBlendRecord is an object-level opt-in, not a device capability.
|
|
5597
|
+
// The boot/default module must stay on the no-probe ABI; record-stage
|
|
5598
|
+
// entries with a retained record request the explicit true variant.
|
|
5599
|
+
variantDefines.PROBE_BLEND_AVAILABLE = false;
|
|
5600
|
+
}
|
|
5601
|
+
if (msEntry.variants.some((v) => 'EXTENDED_LIGHTING_AVAILABLE' in v.defines)) {
|
|
5602
|
+
variantDefines.EXTENDED_LIGHTING_AVAILABLE = extendedLightingShaderAvailable;
|
|
5603
|
+
}
|
|
4966
5604
|
if (
|
|
4967
5605
|
msEntry.identifier === 'forgeax::default-standard-pbr' &&
|
|
4968
5606
|
msEntry.variants.some((v) => 'PROJECTOR_AVAILABLE' in v.defines)
|
|
@@ -4975,6 +5613,17 @@ async function prepareMaterialShaders(
|
|
|
4975
5613
|
) {
|
|
4976
5614
|
variantDefines.TRANSMISSION_AVAILABLE = false;
|
|
4977
5615
|
}
|
|
5616
|
+
// The fallback MRT is a record-time opt-in. Boot registration must select
|
|
5617
|
+
// the ordinary single-target Standard artifact when the manifest declares
|
|
5618
|
+
// the axis; record-time topology derives the true branch only after the
|
|
5619
|
+
// fallback attachment has been admitted. Omitting this false value asks
|
|
5620
|
+
// the closed manifest for a non-existent partial key.
|
|
5621
|
+
if (
|
|
5622
|
+
msEntry.identifier === 'forgeax::default-standard-pbr' &&
|
|
5623
|
+
msEntry.variants.some((v) => 'REFLECTION_FALLBACK_AVAILABLE' in v.defines)
|
|
5624
|
+
) {
|
|
5625
|
+
variantDefines.REFLECTION_FALLBACK_AVAILABLE = false;
|
|
5626
|
+
}
|
|
4978
5627
|
// bug-20260708 M2 (a): PER_INSTANCE_REGION axis default = false at boot.
|
|
4979
5628
|
// Per-entity spritePH path (`main-pass-sprite-draws.ts:511-513`) expects
|
|
4980
5629
|
// `variantSet=undefined` to resolve to the PIR=false shader source (64B
|
|
@@ -5103,6 +5752,11 @@ async function buildReadyWebGPU(
|
|
|
5103
5752
|
* frame while the shared async shader adapter is still warming up.
|
|
5104
5753
|
*/
|
|
5105
5754
|
requiredMaterialShaders: readonly string[],
|
|
5755
|
+
/** Fullscreen feature modules compiled before the first synchronous frame. */
|
|
5756
|
+
requiredFullscreenPostProcesses: readonly {
|
|
5757
|
+
readonly identity: string;
|
|
5758
|
+
readonly source: string;
|
|
5759
|
+
}[],
|
|
5106
5760
|
// feat-20260608-mesh-ssbo-dynamic-grow-l1-lift-1024-entity-cap M2 /
|
|
5107
5761
|
// T-M2-05 + M3 / T-M3-04: surface for setting `internals.growMeshSsbo`
|
|
5108
5762
|
// + `internals.meshSsboState` so the record stage's
|
|
@@ -5131,7 +5785,11 @@ async function buildReadyWebGPU(
|
|
|
5131
5785
|
// `getMaterialShaderPipeline` lookup hits a live PSO instead of falling
|
|
5132
5786
|
// into a 1-frame async-compile skip-draw window. Idempotent: caller
|
|
5133
5787
|
// guards against re-seeding when a key already exists.
|
|
5134
|
-
seedMaterialShaderPipelineCache: (
|
|
5788
|
+
seedMaterialShaderPipelineCache: (
|
|
5789
|
+
key: string,
|
|
5790
|
+
pso: RenderPipeline,
|
|
5791
|
+
group2Contract: PipelineGroup2Contract,
|
|
5792
|
+
) => void,
|
|
5135
5793
|
// feat-20260621 M-A3 (D-5): register the engine built-in tonemap onto the
|
|
5136
5794
|
// unified post-process channel once the tonemap manifest entry's composed
|
|
5137
5795
|
// WGSL is resolved. Invoked with the tonemap WGSL source string; the outer
|
|
@@ -5141,6 +5799,8 @@ async function buildReadyWebGPU(
|
|
|
5141
5799
|
// tonemap source resolves inside this async function (after the manifest-load
|
|
5142
5800
|
// await), by which point the synchronous `renderSystem` const is defined.
|
|
5143
5801
|
registerBuiltinTonemap: (source: string) => void,
|
|
5802
|
+
/** Register the special FXAA pass' final-output policy params UBO. */
|
|
5803
|
+
registerBuiltinFxaa: (source: string) => void,
|
|
5144
5804
|
registerBuiltinTemporalPostProcesses: (entries: {
|
|
5145
5805
|
readonly motionBlur?: string;
|
|
5146
5806
|
readonly taaResolve?: string;
|
|
@@ -5164,10 +5824,10 @@ async function buildReadyWebGPU(
|
|
|
5164
5824
|
const capCheck = assertStorageBufferCap(capValue);
|
|
5165
5825
|
storageBufferCapable = capCheck.ok ? !webgl2Downlevel && capCheck.value : !webgl2Downlevel;
|
|
5166
5826
|
}
|
|
5827
|
+
const extendedLightingShaderAvailable = deriveExtendedLightingCapability(rhiDevice).admitted;
|
|
5167
5828
|
const projectorAvailable =
|
|
5168
5829
|
(rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
5169
5830
|
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
5170
|
-
|
|
5171
5831
|
// bug-20260612: choose swap-chain storage / view formats by backend.
|
|
5172
5832
|
// Channel 2 (native WebGPU) follows navigator.gpu.getPreferredCanvasFormat();
|
|
5173
5833
|
// Channel 3 (wgpu-wasm GLES, storageBufferCapable=false) hard-codes
|
|
@@ -5238,6 +5898,10 @@ async function buildReadyWebGPU(
|
|
|
5238
5898
|
let unlitModule: ShaderModule | null = null;
|
|
5239
5899
|
let spriteModule: ShaderModule | null = null;
|
|
5240
5900
|
let spriteLitModule: ShaderModule | null = null;
|
|
5901
|
+
let pbrGroup2Contract: PipelineGroup2Contract | undefined;
|
|
5902
|
+
let unlitGroup2Contract: PipelineGroup2Contract | undefined;
|
|
5903
|
+
let spriteGroup2Contract: PipelineGroup2Contract | undefined;
|
|
5904
|
+
let spriteLitGroup2Contract: PipelineGroup2Contract | undefined;
|
|
5241
5905
|
let fxaaModule: ShaderModule | null = null;
|
|
5242
5906
|
let skyboxModule: ShaderModule | null = null;
|
|
5243
5907
|
let bloomBrightModule: ShaderModule | null = null;
|
|
@@ -5432,7 +6096,7 @@ async function buildReadyWebGPU(
|
|
|
5432
6096
|
// bug-20260610: WebGL2 fallback path — when storageBufferCapable=false
|
|
5433
6097
|
// the variant-aware Step 1b registers material shaders with the
|
|
5434
6098
|
// STORAGE_BUFFER_AVAILABLE=false WGSL (uniform fallback for meshes /
|
|
5435
|
-
//
|
|
6099
|
+
// meshes / instances), but Step 2 above triaged pbr/unlit/sprite/
|
|
5436
6100
|
// pbr-skin/msdf-text from the FLAT `registry.entries()` map which only
|
|
5437
6101
|
// contains the default (storage) variant. Compiling those with
|
|
5438
6102
|
// STORAGE_BUFFER_AVAILABLE=true while the pipeline layout is built
|
|
@@ -5456,22 +6120,28 @@ async function buildReadyWebGPU(
|
|
|
5456
6120
|
// CLUSTER_FORWARD_AVAILABLE in PR #344) only emits compound keys like
|
|
5457
6121
|
// `CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=false`, so
|
|
5458
6122
|
// findVariantByKey('STORAGE_BUFFER_AVAILABLE=false') silently misses.
|
|
5459
|
-
// The
|
|
5460
|
-
//
|
|
5461
|
-
//
|
|
5462
|
-
// otherwise each STORAGE_BUFFER_AVAILABLE=false variant.
|
|
6123
|
+
// The eager registry seed is the non-clustered boot artifact (the active
|
|
6124
|
+
// frame topology is not known here). Pick by defines fields and prefer
|
|
6125
|
+
// the boot cluster axis when present; otherwise use the storage axis.
|
|
5463
6126
|
const idToVariantWgsl = new Map<string, string>();
|
|
5464
6127
|
for (const ms of registry.materialShaderManifestEntries()) {
|
|
5465
6128
|
const exact = ms.variants.find(
|
|
5466
|
-
(
|
|
5467
|
-
|
|
5468
|
-
(!('CLUSTER_FORWARD_AVAILABLE' in
|
|
5469
|
-
|
|
5470
|
-
(!('VERTEX_COLOR_AVAILABLE' in
|
|
5471
|
-
|
|
5472
|
-
(!('
|
|
5473
|
-
|
|
5474
|
-
|
|
6129
|
+
(variant) =>
|
|
6130
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === false &&
|
|
6131
|
+
(!('CLUSTER_FORWARD_AVAILABLE' in variant.defines) ||
|
|
6132
|
+
variant.defines.CLUSTER_FORWARD_AVAILABLE === standardBootClusterAxis()) &&
|
|
6133
|
+
(!('VERTEX_COLOR_AVAILABLE' in variant.defines) ||
|
|
6134
|
+
variant.defines.VERTEX_COLOR_AVAILABLE === false) &&
|
|
6135
|
+
(!('DIRECTIONAL_PCSS_AVAILABLE' in variant.defines) ||
|
|
6136
|
+
variant.defines.DIRECTIONAL_PCSS_AVAILABLE ===
|
|
6137
|
+
directionalPcssCapable(rhiDevice.caps.backendKind)) &&
|
|
6138
|
+
(!('PROJECTOR_AVAILABLE' in variant.defines) ||
|
|
6139
|
+
variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) &&
|
|
6140
|
+
(!('REFLECTION_FALLBACK_AVAILABLE' in variant.defines) ||
|
|
6141
|
+
variant.defines.REFLECTION_FALLBACK_AVAILABLE === false) &&
|
|
6142
|
+
variant.defines.PROBE_BLEND_AVAILABLE !== true &&
|
|
6143
|
+
(!('SKINNING_DISABLED' in variant.defines) ||
|
|
6144
|
+
variant.defines.SKINNING_DISABLED === true),
|
|
5475
6145
|
);
|
|
5476
6146
|
const v = exact === undefined ? undefined : findVariantByKey(ms, exact.definesKey);
|
|
5477
6147
|
if (v !== undefined) {
|
|
@@ -5500,8 +6170,8 @@ async function buildReadyWebGPU(
|
|
|
5500
6170
|
}
|
|
5501
6171
|
}
|
|
5502
6172
|
// The flat `registry.entries()` map carries the canonical composed source,
|
|
5503
|
-
// while
|
|
5504
|
-
// matching
|
|
6173
|
+
// while this eager module uses the boot layout contract. Select the
|
|
6174
|
+
// matching boot variant for every eager engine module, including native
|
|
5505
6175
|
// WebGPU. Without this step the canonical PBR/Unlit source declares the
|
|
5506
6176
|
// optional COLOR_0 vertex input while the boot SPEC_CONST layouts for
|
|
5507
6177
|
// ordinary meshes do not provide location 13.
|
|
@@ -5510,16 +6180,24 @@ async function buildReadyWebGPU(
|
|
|
5510
6180
|
const exact = ms.variants.find(
|
|
5511
6181
|
(variant) =>
|
|
5512
6182
|
variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable &&
|
|
6183
|
+
(!('EXTENDED_LIGHTING_AVAILABLE' in variant.defines) ||
|
|
6184
|
+
variant.defines.EXTENDED_LIGHTING_AVAILABLE === extendedLightingShaderAvailable) &&
|
|
5513
6185
|
(!('CLUSTER_FORWARD_AVAILABLE' in variant.defines) ||
|
|
5514
|
-
variant.defines.CLUSTER_FORWARD_AVAILABLE ===
|
|
6186
|
+
variant.defines.CLUSTER_FORWARD_AVAILABLE === standardBootClusterAxis()) &&
|
|
5515
6187
|
(!('VERTEX_COLOR_AVAILABLE' in variant.defines) ||
|
|
5516
6188
|
variant.defines.VERTEX_COLOR_AVAILABLE === false) &&
|
|
5517
6189
|
(!('PROJECTOR_AVAILABLE' in variant.defines) ||
|
|
5518
6190
|
variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) &&
|
|
6191
|
+
variant.defines.PROBE_BLEND_AVAILABLE !== true &&
|
|
5519
6192
|
(!('SKINNING_DISABLED' in variant.defines) ||
|
|
5520
6193
|
variant.defines.SKINNING_DISABLED === true) &&
|
|
6194
|
+
(!('DIRECTIONAL_PCSS_AVAILABLE' in variant.defines) ||
|
|
6195
|
+
variant.defines.DIRECTIONAL_PCSS_AVAILABLE ===
|
|
6196
|
+
directionalPcssCapable(rhiDevice.caps.backendKind)) &&
|
|
5521
6197
|
(!('TRANSMISSION_AVAILABLE' in variant.defines) ||
|
|
5522
|
-
variant.defines.TRANSMISSION_AVAILABLE === false)
|
|
6198
|
+
variant.defines.TRANSMISSION_AVAILABLE === false) &&
|
|
6199
|
+
(!('REFLECTION_FALLBACK_AVAILABLE' in variant.defines) ||
|
|
6200
|
+
variant.defines.REFLECTION_FALLBACK_AVAILABLE === false),
|
|
5523
6201
|
);
|
|
5524
6202
|
const variant = exact === undefined ? undefined : findVariantByKey(ms, exact.definesKey);
|
|
5525
6203
|
if (variant !== undefined) {
|
|
@@ -5650,6 +6328,27 @@ async function buildReadyWebGPU(
|
|
|
5650
6328
|
seedShaderModule(label, shaderResult.value);
|
|
5651
6329
|
}
|
|
5652
6330
|
|
|
6331
|
+
// A boot-installed fullscreen feature has the same synchronous record
|
|
6332
|
+
// contract as the built-in output transform: its module must be seeded
|
|
6333
|
+
// before the caller can drive a tight frame loop. Otherwise the typed
|
|
6334
|
+
// graph's first post-effect admission is correctly held back, but a
|
|
6335
|
+
// synchronous consumer would only see the base image until it yields.
|
|
6336
|
+
// Late-installed features use the matching registration path below.
|
|
6337
|
+
for (const postProcess of requiredFullscreenPostProcesses) {
|
|
6338
|
+
const label = postProcessShaderModuleLabel(postProcess.identity, postProcess.source);
|
|
6339
|
+
const shaderResult = await runShimStep(
|
|
6340
|
+
() =>
|
|
6341
|
+
asyncCreateShaderModule
|
|
6342
|
+
? asyncCreateShaderModule(rhiDevice, { code: postProcess.source, label })
|
|
6343
|
+
: invokeDeviceCreateShaderModule(rhiDevice, { code: postProcess.source, label }),
|
|
6344
|
+
'shader-compile-failed',
|
|
6345
|
+
`declared fullscreen render feature '${postProcess.identity}' compiled`,
|
|
6346
|
+
`inspect the composed WGSL for '${postProcess.identity}' and check device.features`,
|
|
6347
|
+
);
|
|
6348
|
+
if (!shaderResult.ok) throw shaderResult.error;
|
|
6349
|
+
seedShaderModule(label, shaderResult.value);
|
|
6350
|
+
}
|
|
6351
|
+
|
|
5653
6352
|
// The volume graph is authored from ECS state and therefore cannot be
|
|
5654
6353
|
// compiled while the static material manifest is assembled. Its pass
|
|
5655
6354
|
// encoder nevertheless uses the same shared lazy shader adapter, so the
|
|
@@ -5735,6 +6434,7 @@ async function buildReadyWebGPU(
|
|
|
5735
6434
|
);
|
|
5736
6435
|
if (!pbrShaderResult.ok) throw pbrShaderResult.error;
|
|
5737
6436
|
pbrModule = pbrShaderResult.value;
|
|
6437
|
+
pbrGroup2Contract = resolvePipelineGroup2Contract(pbrEntry.wgsl);
|
|
5738
6438
|
// feat-20260629-multi-uv-set-support: a real extra-UV mesh creates a
|
|
5739
6439
|
// layout-specific material PSO after the boot-time standard-layout PSO
|
|
5740
6440
|
// has been pre-warmed. Seed the shared shader-module adapter under the
|
|
@@ -5758,7 +6458,9 @@ async function buildReadyWebGPU(
|
|
|
5758
6458
|
? selectStandardPbrTransmissionPrewarmVariants(
|
|
5759
6459
|
pbrManifestEntry,
|
|
5760
6460
|
storageBufferCapable,
|
|
6461
|
+
directionalPcssCapable(rhiDevice.caps.backendKind),
|
|
5761
6462
|
projectorAvailable,
|
|
6463
|
+
extendedLightingShaderAvailable,
|
|
5762
6464
|
)
|
|
5763
6465
|
: [];
|
|
5764
6466
|
|
|
@@ -5796,7 +6498,9 @@ async function buildReadyWebGPU(
|
|
|
5796
6498
|
for (const hdrpVariant of selectHdrpPbrPrewarmVariants(
|
|
5797
6499
|
pbrManifestEntry,
|
|
5798
6500
|
storageBufferCapable,
|
|
6501
|
+
extendedLightingShaderAvailable,
|
|
5799
6502
|
transmissionCapable,
|
|
6503
|
+
directionalPcssCapable(rhiDevice.caps.backendKind),
|
|
5800
6504
|
projectorAvailable,
|
|
5801
6505
|
)) {
|
|
5802
6506
|
const moduleLabel = `module-forgeax::default-standard-pbr#${hdrpVariant.definesKey}`;
|
|
@@ -5824,6 +6528,43 @@ async function buildReadyWebGPU(
|
|
|
5824
6528
|
seedShaderModule(moduleLabel, hdrpModule);
|
|
5825
6529
|
}
|
|
5826
6530
|
|
|
6531
|
+
// Skin geometry uses the same lazy material-pipeline adapter as standard
|
|
6532
|
+
// PBR, but its dedicated module was previously never warmed. A record
|
|
6533
|
+
// frame therefore received a pending module result, skipped the skin PSO,
|
|
6534
|
+
// and silently emitted no draw. Compile and seed every active-capability
|
|
6535
|
+
// skin variant so both URP and HDRP resolve their exact PSO consumer on
|
|
6536
|
+
// frame one (including the geometry-owned COLOR_0=false variant).
|
|
6537
|
+
const pbrSkinManifestEntry = [...registry.materialShaderManifestEntries()].find(
|
|
6538
|
+
(entry) => entry.identifier === SKIN_MATERIAL_SHADER_ID,
|
|
6539
|
+
);
|
|
6540
|
+
const activeSkinVariants = selectSkinPrewarmVariants(
|
|
6541
|
+
pbrSkinManifestEntry,
|
|
6542
|
+
storageBufferCapable,
|
|
6543
|
+
);
|
|
6544
|
+
for (const skinVariant of activeSkinVariants) {
|
|
6545
|
+
const moduleLabel = `module-${SKIN_MATERIAL_SHADER_ID}#${skinVariant.definesKey}`;
|
|
6546
|
+
const skinModuleResult = await runShimStep(
|
|
6547
|
+
() =>
|
|
6548
|
+
asyncCreateShaderModule
|
|
6549
|
+
? asyncCreateShaderModule(rhiDevice, {
|
|
6550
|
+
code: skinVariant.composedWgsl,
|
|
6551
|
+
label: moduleLabel,
|
|
6552
|
+
})
|
|
6553
|
+
: invokeDeviceCreateShaderModule(rhiDevice, {
|
|
6554
|
+
code: skinVariant.composedWgsl,
|
|
6555
|
+
label: moduleLabel,
|
|
6556
|
+
}),
|
|
6557
|
+
'shader-compile-failed',
|
|
6558
|
+
`pbr-skin variant ${skinVariant.definesKey || '<default>'} compiled`,
|
|
6559
|
+
'inspect the selected pbr-skin storage/cluster/color variant and device.features',
|
|
6560
|
+
);
|
|
6561
|
+
if (!skinModuleResult.ok) throw skinModuleResult.error;
|
|
6562
|
+
seedShaderModule(moduleLabel, skinModuleResult.value);
|
|
6563
|
+
if (skinVariant.definesKey === '') {
|
|
6564
|
+
seedShaderModule(`module-${SKIN_MATERIAL_SHADER_ID}`, skinModuleResult.value);
|
|
6565
|
+
}
|
|
6566
|
+
}
|
|
6567
|
+
|
|
5827
6568
|
// M2-07-c: Materials.standard with an explicit renderState reaches the
|
|
5828
6569
|
// lazy material pipeline path, whose module label includes the requested
|
|
5829
6570
|
// variant set. Reuse the boot-compiled PBR module only for manifest
|
|
@@ -5848,6 +6589,7 @@ async function buildReadyWebGPU(
|
|
|
5848
6589
|
);
|
|
5849
6590
|
if (!unlitShaderResult.ok) throw unlitShaderResult.error;
|
|
5850
6591
|
unlitModule = unlitShaderResult.value;
|
|
6592
|
+
unlitGroup2Contract = resolvePipelineGroup2Contract(unlitEntry.wgsl);
|
|
5851
6593
|
// Seed the shared lazy adapter with the eagerly compiled module so
|
|
5852
6594
|
// prepared color-only PSOs build synchronously on their first frame.
|
|
5853
6595
|
seedShaderModule('unlit', unlitModule);
|
|
@@ -5994,7 +6736,8 @@ async function buildReadyWebGPU(
|
|
|
5994
6736
|
// frames (e.g. the hello-tonemap dawn smoke) would stall on a black frame
|
|
5995
6737
|
// forever. Mirror the shadow_caster prewarm: await-compile the tonemap module
|
|
5996
6738
|
// here and `seedShaderModule` it under the exact label
|
|
5997
|
-
// `buildPostProcessPipeline` requests
|
|
6739
|
+
// `buildPostProcessPipeline` requests the content-addressed module label,
|
|
6740
|
+
// so the
|
|
5998
6741
|
// first `getPostProcessPipeline(STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, …)` hits the module cache
|
|
5999
6742
|
// synchronously and builds the pipeline on frame 1.
|
|
6000
6743
|
const tonemapPrewarm = await runShimStep(
|
|
@@ -6002,11 +6745,17 @@ async function buildReadyWebGPU(
|
|
|
6002
6745
|
asyncCreateShaderModule
|
|
6003
6746
|
? asyncCreateShaderModule(rhiDevice, {
|
|
6004
6747
|
code: tonemapEntry.wgsl,
|
|
6005
|
-
label:
|
|
6748
|
+
label: postProcessShaderModuleLabel(
|
|
6749
|
+
STANDARD_OUTPUT_TRANSFORM_FEATURE_ID,
|
|
6750
|
+
tonemapEntry.wgsl,
|
|
6751
|
+
),
|
|
6006
6752
|
})
|
|
6007
6753
|
: invokeDeviceCreateShaderModule(rhiDevice, {
|
|
6008
6754
|
code: tonemapEntry.wgsl,
|
|
6009
|
-
label:
|
|
6755
|
+
label: postProcessShaderModuleLabel(
|
|
6756
|
+
STANDARD_OUTPUT_TRANSFORM_FEATURE_ID,
|
|
6757
|
+
tonemapEntry.wgsl,
|
|
6758
|
+
),
|
|
6010
6759
|
}),
|
|
6011
6760
|
'shader-compile-failed',
|
|
6012
6761
|
'tonemap shader module compiled (unified post-process prewarm)',
|
|
@@ -6014,7 +6763,7 @@ async function buildReadyWebGPU(
|
|
|
6014
6763
|
);
|
|
6015
6764
|
if (!tonemapPrewarm.ok) throw tonemapPrewarm.error;
|
|
6016
6765
|
seedShaderModule(
|
|
6017
|
-
|
|
6766
|
+
postProcessShaderModuleLabel(STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, tonemapEntry.wgsl),
|
|
6018
6767
|
tonemapPrewarm.value,
|
|
6019
6768
|
);
|
|
6020
6769
|
registerBuiltinTonemap(tonemapEntry.wgsl);
|
|
@@ -6031,18 +6780,18 @@ async function buildReadyWebGPU(
|
|
|
6031
6780
|
asyncCreateShaderModule
|
|
6032
6781
|
? asyncCreateShaderModule(rhiDevice, {
|
|
6033
6782
|
code: entry.wgsl,
|
|
6034
|
-
label:
|
|
6783
|
+
label: postProcessShaderModuleLabel(id, entry.wgsl),
|
|
6035
6784
|
})
|
|
6036
6785
|
: invokeDeviceCreateShaderModule(rhiDevice, {
|
|
6037
6786
|
code: entry.wgsl,
|
|
6038
|
-
label:
|
|
6787
|
+
label: postProcessShaderModuleLabel(id, entry.wgsl),
|
|
6039
6788
|
}),
|
|
6040
6789
|
'shader-compile-failed',
|
|
6041
6790
|
`${id} shader module compiled (unified post-process prewarm)`,
|
|
6042
6791
|
`inspect manifest ${id} entry composed wgsl; check device.features`,
|
|
6043
6792
|
);
|
|
6044
6793
|
if (!prewarm.ok) throw prewarm.error;
|
|
6045
|
-
seedShaderModule(
|
|
6794
|
+
seedShaderModule(postProcessShaderModuleLabel(id, entry.wgsl), prewarm.value);
|
|
6046
6795
|
};
|
|
6047
6796
|
await prewarmTemporal(motionBlurEntry, 'forgeax.motion-blur');
|
|
6048
6797
|
await prewarmTemporal(taaResolveEntry, 'forgeax.taa-resolve');
|
|
@@ -6071,6 +6820,7 @@ async function buildReadyWebGPU(
|
|
|
6071
6820
|
);
|
|
6072
6821
|
if (!spriteShaderResult.ok) throw spriteShaderResult.error;
|
|
6073
6822
|
spriteModule = spriteShaderResult.value;
|
|
6823
|
+
spriteGroup2Contract = resolvePipelineGroup2Contract(spriteEntry.wgsl);
|
|
6074
6824
|
// feat-20260625-refactor-sprite-as-transparent-mesh CI-fix: seed the
|
|
6075
6825
|
// lazy adapter cache under the same label (`module-forgeax::sprite`)
|
|
6076
6826
|
// that `getMaterialShaderPipeline -> buildAndCachePipeline ->
|
|
@@ -6141,7 +6891,44 @@ async function buildReadyWebGPU(
|
|
|
6141
6891
|
);
|
|
6142
6892
|
if (!spriteLitShaderResult.ok) throw spriteLitShaderResult.error;
|
|
6143
6893
|
spriteLitModule = spriteLitShaderResult.value;
|
|
6894
|
+
spriteLitGroup2Contract = resolvePipelineGroup2Contract(spriteLitEntryConst.wgsl);
|
|
6144
6895
|
seedShaderModule('module-forgeax::sprite-lit', spriteLitShaderResult.value);
|
|
6896
|
+
// Sprite-lit participates in the same Standard capability split as
|
|
6897
|
+
// PBR. Record requests the explicit URP/HDRP variant per frame, so seed
|
|
6898
|
+
// every device-matched artifact during readiness; a tight dawn/browser
|
|
6899
|
+
// frame loop cannot yield to the async module adapter between draws.
|
|
6900
|
+
const spriteLitManifestEntry = Array.from(registry.materialShaderManifestEntries()).find(
|
|
6901
|
+
(entry) => entry.identifier === 'forgeax::sprite-lit',
|
|
6902
|
+
);
|
|
6903
|
+
const prewarmedSpriteLitModules = new Map<string, ShaderModule>([
|
|
6904
|
+
[spriteLitEntryConst.wgsl, spriteLitShaderResult.value],
|
|
6905
|
+
]);
|
|
6906
|
+
for (const variant of spriteLitManifestEntry?.variants ?? []) {
|
|
6907
|
+
if (variant.defines.STORAGE_BUFFER_AVAILABLE !== storageBufferCapable) continue;
|
|
6908
|
+
const moduleLabel = `module-forgeax::sprite-lit#${variant.definesKey}`;
|
|
6909
|
+
let variantModule = prewarmedSpriteLitModules.get(variant.composedWgsl);
|
|
6910
|
+
if (variantModule === undefined) {
|
|
6911
|
+
const variantResult = await runShimStep(
|
|
6912
|
+
() =>
|
|
6913
|
+
asyncCreateShaderModule
|
|
6914
|
+
? asyncCreateShaderModule(rhiDevice, {
|
|
6915
|
+
code: variant.composedWgsl,
|
|
6916
|
+
label: moduleLabel,
|
|
6917
|
+
})
|
|
6918
|
+
: invokeDeviceCreateShaderModule(rhiDevice, {
|
|
6919
|
+
code: variant.composedWgsl,
|
|
6920
|
+
label: moduleLabel,
|
|
6921
|
+
}),
|
|
6922
|
+
'shader-compile-failed',
|
|
6923
|
+
`sprite-lit variant ${variant.definesKey || '<default>'} compiled`,
|
|
6924
|
+
'inspect the selected sprite-lit Standard capability variant and device.features',
|
|
6925
|
+
);
|
|
6926
|
+
if (!variantResult.ok) throw variantResult.error;
|
|
6927
|
+
variantModule = variantResult.value;
|
|
6928
|
+
prewarmedSpriteLitModules.set(variant.composedWgsl, variantModule);
|
|
6929
|
+
}
|
|
6930
|
+
seedShaderModule(moduleLabel, variantModule);
|
|
6931
|
+
}
|
|
6145
6932
|
}
|
|
6146
6933
|
|
|
6147
6934
|
// feat-20260528-fxaa-post-processing: fxaa shader module is optional
|
|
@@ -6164,6 +6951,7 @@ async function buildReadyWebGPU(
|
|
|
6164
6951
|
);
|
|
6165
6952
|
if (!fxaaShaderResult.ok) throw fxaaShaderResult.error;
|
|
6166
6953
|
fxaaModule = fxaaShaderResult.value;
|
|
6954
|
+
registerBuiltinFxaa(fxaaEntry.wgsl);
|
|
6167
6955
|
}
|
|
6168
6956
|
|
|
6169
6957
|
// feat-20260531-skybox-env-background M3 / w15: skybox shader module.
|
|
@@ -6295,6 +7083,7 @@ async function buildReadyWebGPU(
|
|
|
6295
7083
|
ok(
|
|
6296
7084
|
buildPbrPipelineLayouts(rhiDevice, {
|
|
6297
7085
|
storageBuffer: storageBufferCapable,
|
|
7086
|
+
extendedLighting: extendedLightingShaderAvailable,
|
|
6298
7087
|
projectorAvailable,
|
|
6299
7088
|
}),
|
|
6300
7089
|
),
|
|
@@ -6307,6 +7096,7 @@ async function buildReadyWebGPU(
|
|
|
6307
7096
|
const materialBglResult = ok(pbrLayouts.value.materialBgl);
|
|
6308
7097
|
const meshArrayBglResult = ok(pbrLayouts.value.meshArrayBgl);
|
|
6309
7098
|
const instancesBglResult = ok(pbrLayouts.value.instancesBgl);
|
|
7099
|
+
const probeInstancesBglResult = ok(pbrLayouts.value.probeInstancesBgl);
|
|
6310
7100
|
const pipelineLayoutResult = ok(pbrLayouts.value.pipelineLayout);
|
|
6311
7101
|
|
|
6312
7102
|
// feat-20260609-hdrp-cluster-fragment-ggx M4.5 / w36 (D-10 option A):
|
|
@@ -6316,15 +7106,15 @@ async function buildReadyWebGPU(
|
|
|
6316
7106
|
// BGL at group(2) so an HDRP-variant PSO validates against the 7-slot
|
|
6317
7107
|
// group(2) bindGroup that the record stage sets via hdrpClusterBindGroup.
|
|
6318
7108
|
// Stays null when createBindGroupLayout / createPipelineLayout fails;
|
|
6319
|
-
// selectPipelineLayoutForVariant
|
|
6320
|
-
//
|
|
6321
|
-
// valid PSO instead of hard-disabling the entire HDRP-variant build path.
|
|
7109
|
+
// selectPipelineLayoutForVariant propagates that null so the clustered
|
|
7110
|
+
// material remains unavailable instead of binding an incompatible layout.
|
|
6322
7111
|
let hdrpPbrPipelineLayoutHandle: PipelineLayout | null = null;
|
|
7112
|
+
let hdrpProbePbrPipelineLayoutHandle: PipelineLayout | null = null;
|
|
6323
7113
|
// The cluster layout is storage-buffer backed. On the WebGL2/fallback
|
|
6324
7114
|
// capability tier its uniform-buffer projection exceeds the backend's
|
|
6325
7115
|
// fragment-stage limit, and HDRP is not a valid route there anyway.
|
|
6326
7116
|
if (storageBufferCapable) {
|
|
6327
|
-
const hdrpUnifiedBglDesc = createHdrpBindGroupLayoutDescriptor(
|
|
7117
|
+
const hdrpUnifiedBglDesc = createHdrpBindGroupLayoutDescriptor();
|
|
6328
7118
|
const hdrpUnifiedBglRes = rhiDevice.createBindGroupLayout(hdrpUnifiedBglDesc);
|
|
6329
7119
|
if (hdrpUnifiedBglRes.ok) {
|
|
6330
7120
|
const hdrpPlRes = rhiDevice.createPipelineLayout({
|
|
@@ -6339,6 +7129,23 @@ async function buildReadyWebGPU(
|
|
|
6339
7129
|
if (hdrpPlRes.ok) {
|
|
6340
7130
|
hdrpPbrPipelineLayoutHandle = hdrpPlRes.value;
|
|
6341
7131
|
}
|
|
7132
|
+
// ProbeBlendRecord adds a fragment-visible storage binding and is not a
|
|
7133
|
+
// legal downlevel extension of the uniform-fallback/WebGL2 layout. Keep
|
|
7134
|
+
// the HDRP probe PSO on the same capability gate as the standard probe
|
|
7135
|
+
// PSO; otherwise layout construction itself exceeds GLES' uniform
|
|
7136
|
+
// buffer limit before a probe draw can be rejected cleanly.
|
|
7137
|
+
if (storageBufferCapable) {
|
|
7138
|
+
const hdrpProbePlRes = rhiDevice.createPipelineLayout({
|
|
7139
|
+
label: 'hdrp-probe-pbr-pl',
|
|
7140
|
+
bindGroupLayouts: [
|
|
7141
|
+
viewBglResult.value,
|
|
7142
|
+
materialBglResult.value,
|
|
7143
|
+
hdrpUnifiedBglRes.value,
|
|
7144
|
+
probeInstancesBglResult.value,
|
|
7145
|
+
],
|
|
7146
|
+
});
|
|
7147
|
+
if (hdrpProbePlRes.ok) hdrpProbePbrPipelineLayoutHandle = hdrpProbePlRes.value;
|
|
7148
|
+
}
|
|
6342
7149
|
}
|
|
6343
7150
|
}
|
|
6344
7151
|
|
|
@@ -6395,23 +7202,27 @@ async function buildReadyWebGPU(
|
|
|
6395
7202
|
// bug-20260611-skin-pipeline-layout-mesh-array-bgl-2bindings: boot-time
|
|
6396
7203
|
// build of the skin-variant PipelineLayout. Mirrors the HDRP block above
|
|
6397
7204
|
// (D-2 / D-3 in plan-strategy): reuse view / material / instances BGLs
|
|
6398
|
-
// from `pbrLayouts` and only create a
|
|
6399
|
-
// meshes + binding 1 palette) so the
|
|
7205
|
+
// from `pbrLayouts` and only create a 3-entry mesh-array BGL (binding 0
|
|
7206
|
+
// meshes + binding 1 current palette + binding 2 previous palette) so the
|
|
7207
|
+
// `forgeax::pbr-skin` shader's
|
|
6400
7208
|
// `@group(2) @binding(1) palette` declaration validates. Stays null when
|
|
6401
7209
|
// createBindGroupLayout / createPipelineLayout fails;
|
|
6402
7210
|
// selectPipelineLayoutForVariant returns null in that case (charter P3
|
|
6403
7211
|
// explicit failure -- no silent fallback to URP layout, mirroring memory
|
|
6404
7212
|
// anchor `hdrp-active-must-not-fallback-to-urp-pipeline`).
|
|
6405
7213
|
let pbrSkinPipelineLayoutHandle: PipelineLayout | null = null;
|
|
6406
|
-
|
|
7214
|
+
let pbrSkinProbePipelineLayoutHandle: PipelineLayout | null = null;
|
|
7215
|
+
// feat-20260611 R2 / M8 / w28: capture the 3-binding skin mesh-array BGL
|
|
6407
7216
|
// produced by `buildPbrSkinLayouts` so the record stage can build a BG
|
|
6408
|
-
// matching `pbr-skin-pl` (pipeline-layout BGL[2] is this
|
|
7217
|
+
// matching `pbr-skin-pl` (pipeline-layout BGL[2] is this 3-entry skin
|
|
6409
7218
|
// BGL, NOT the 1-entry `pbr-mesh-array-bgl`). The BGL handle stays null
|
|
6410
7219
|
// when the skin pipeline layout itself failed to build, keeping the skin
|
|
6411
7220
|
// path explicitly disabled (charter P3 — record-stage falls back to URP
|
|
6412
7221
|
// path which is correct for non-skin entries; skin entries hit the
|
|
6413
7222
|
// explicit-failure branch).
|
|
6414
7223
|
let pbrSkinMeshBindGroupLayoutHandle: BindGroupLayout | null = null;
|
|
7224
|
+
let hdrpSkinPipelineLayoutHandle: PipelineLayout | null = null;
|
|
7225
|
+
let hdrpSkinMeshBindGroupLayoutHandle: BindGroupLayout | null = null;
|
|
6415
7226
|
{
|
|
6416
7227
|
const skinLayoutsResult = runShimSyncStep(
|
|
6417
7228
|
() =>
|
|
@@ -6424,9 +7235,33 @@ async function buildReadyWebGPU(
|
|
|
6424
7235
|
);
|
|
6425
7236
|
if (skinLayoutsResult.ok) {
|
|
6426
7237
|
pbrSkinPipelineLayoutHandle = skinLayoutsResult.value.pipelineLayout;
|
|
7238
|
+
pbrSkinProbePipelineLayoutHandle = skinLayoutsResult.value.probePipelineLayout;
|
|
6427
7239
|
pbrSkinMeshBindGroupLayoutHandle = skinLayoutsResult.value.meshArrayBgl;
|
|
6428
7240
|
}
|
|
6429
7241
|
}
|
|
7242
|
+
// Keep HDRP skin construction on the same storage-capability gate as the
|
|
7243
|
+
// ordinary Standard cluster layout above. There is no uniform-backed
|
|
7244
|
+
// clustered skin route when storage buffers are unavailable.
|
|
7245
|
+
if (storageBufferCapable) {
|
|
7246
|
+
const skinBglResult = rhiDevice.createBindGroupLayout(
|
|
7247
|
+
createHdrpSkinBindGroupLayoutDescriptor(),
|
|
7248
|
+
);
|
|
7249
|
+
if (skinBglResult.ok) {
|
|
7250
|
+
const skinPlResult = rhiDevice.createPipelineLayout({
|
|
7251
|
+
label: 'hdrp-skin-pl',
|
|
7252
|
+
bindGroupLayouts: [
|
|
7253
|
+
viewBglResult.value,
|
|
7254
|
+
materialBglResult.value,
|
|
7255
|
+
skinBglResult.value,
|
|
7256
|
+
instancesBglResult.value,
|
|
7257
|
+
],
|
|
7258
|
+
});
|
|
7259
|
+
if (skinPlResult.ok) {
|
|
7260
|
+
hdrpSkinPipelineLayoutHandle = skinPlResult.value;
|
|
7261
|
+
hdrpSkinMeshBindGroupLayoutHandle = skinBglResult.value;
|
|
7262
|
+
}
|
|
7263
|
+
}
|
|
7264
|
+
}
|
|
6430
7265
|
|
|
6431
7266
|
// feat-20260612-skin-palette-per-frame-upload M1 / m1-2: skin palette
|
|
6432
7267
|
// allocator. Replaces the prior 16320 B identity-seeded UBO stub (PR #353,
|
|
@@ -6767,54 +7602,6 @@ async function buildReadyWebGPU(
|
|
|
6767
7602
|
// count >= 2 in createRenderer.ts — definition + this exposure).
|
|
6768
7603
|
setGrowMeshSsboHook(meshSsboController.growMeshSsbo, meshSsboController.state);
|
|
6769
7604
|
|
|
6770
|
-
// feat-20260519-light-casters-point-spot-pbr M3 / w20 (D-S1 + D-S2):
|
|
6771
|
-
// PointLight + SpotLight std430 storage buffers. Header 16 B (count u32 +
|
|
6772
|
-
// 12 B pad) + 4-slot first-slice cap (32 B / 48 B per slot). Sized
|
|
6773
|
-
// generously so that the buffer.size never changes once allocated; per-
|
|
6774
|
-
// frame writeBuffer overwrites the entire range (D-S6 full rewrite +
|
|
6775
|
-
// charter P3 explicit failure: stable buffer.size means no orphan slot
|
|
6776
|
-
// reuse).
|
|
6777
|
-
const POINT_LIGHTS_BUFFER_BYTES =
|
|
6778
|
-
LIGHT_ARRAY_HEADER_BYTES + POINT_LIGHT_STD430_BYTES * LIGHT_ARRAY_MAX_SLOTS;
|
|
6779
|
-
const SPOT_LIGHTS_BUFFER_BYTES =
|
|
6780
|
-
LIGHT_ARRAY_HEADER_BYTES + SPOT_LIGHT_STD430_BYTES * LIGHT_ARRAY_MAX_SLOTS;
|
|
6781
|
-
// bug-20260610: WebGL2 fallback — pointLights / spotLights buffers must
|
|
6782
|
-
// be UNIFORM-usage (the storage variant of the shader is gated by
|
|
6783
|
-
// STORAGE_BUFFER_AVAILABLE; the uniform-fallback path expects uniform
|
|
6784
|
-
// buffers). Without this swap, the buffer-creation succeeds (driver still
|
|
6785
|
-
// accepts STORAGE flag), but `createBindGroup` validation rejects the
|
|
6786
|
-
// mismatch: `Usage flags BufferUsages(COPY_DST | STORAGE) ... do not
|
|
6787
|
-
// contain required usage flags BufferUsages(UNIFORM)`.
|
|
6788
|
-
const LIGHTS_BUFFER_USAGE = storageBufferCapable
|
|
6789
|
-
? GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST
|
|
6790
|
-
: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST;
|
|
6791
|
-
const pointLightsBufferResult = runShimSyncStep(
|
|
6792
|
-
() =>
|
|
6793
|
-
rhiDevice.createBuffer({
|
|
6794
|
-
label: 'pbr-point-lights-ssbo',
|
|
6795
|
-
size: POINT_LIGHTS_BUFFER_BYTES,
|
|
6796
|
-
usage: LIGHTS_BUFFER_USAGE,
|
|
6797
|
-
mappedAtCreation: false,
|
|
6798
|
-
}),
|
|
6799
|
-
'webgpu-runtime-error',
|
|
6800
|
-
'createBuffer (point-lights ssbo) succeeded',
|
|
6801
|
-
'check device.limits.maxStorageBufferBindingSize',
|
|
6802
|
-
);
|
|
6803
|
-
if (!pointLightsBufferResult.ok) throw pointLightsBufferResult.error;
|
|
6804
|
-
const spotLightsBufferResult = runShimSyncStep(
|
|
6805
|
-
() =>
|
|
6806
|
-
rhiDevice.createBuffer({
|
|
6807
|
-
label: 'pbr-spot-lights-ssbo',
|
|
6808
|
-
size: SPOT_LIGHTS_BUFFER_BYTES,
|
|
6809
|
-
usage: LIGHTS_BUFFER_USAGE,
|
|
6810
|
-
mappedAtCreation: false,
|
|
6811
|
-
}),
|
|
6812
|
-
'webgpu-runtime-error',
|
|
6813
|
-
'createBuffer (spot-lights ssbo) succeeded',
|
|
6814
|
-
'check device.limits.maxStorageBufferBindingSize',
|
|
6815
|
-
);
|
|
6816
|
-
if (!spotLightsBufferResult.ok) throw spotLightsBufferResult.error;
|
|
6817
|
-
|
|
6818
7605
|
// feat-20260513-instanced-mesh M3 (T-M3-2): identity-mat4 fallback
|
|
6819
7606
|
// storage buffer. Single 64-byte storage buffer carrying one identity
|
|
6820
7607
|
// mat4 column-major. Renderables without an `Instances` component bind
|
|
@@ -6994,6 +7781,225 @@ async function buildReadyWebGPU(
|
|
|
6994
7781
|
);
|
|
6995
7782
|
if (!fallbackTextureViewResult.ok) throw fallbackTextureViewResult.error;
|
|
6996
7783
|
|
|
7784
|
+
let iesModifierFallbackTexture: import('@forgeax/engine-rhi').Texture | undefined;
|
|
7785
|
+
let cookieModifierFallbackTexture: import('@forgeax/engine-rhi').Texture | undefined;
|
|
7786
|
+
let cookieModifierFallbackMatrixBuffer: Buffer | undefined;
|
|
7787
|
+
let iesModifierFallbackView: TextureView | undefined;
|
|
7788
|
+
let cookieModifierFallbackView: TextureView | undefined;
|
|
7789
|
+
let residentLtcLambertView: TextureView | undefined;
|
|
7790
|
+
let residentLtcGgxView: TextureView | undefined;
|
|
7791
|
+
const requireExtendedResource = <T>(value: T | undefined, name: string): T => {
|
|
7792
|
+
if (value === undefined) {
|
|
7793
|
+
throw new Error(`extended-lighting resource missing: ${name}`);
|
|
7794
|
+
}
|
|
7795
|
+
return value;
|
|
7796
|
+
};
|
|
7797
|
+
|
|
7798
|
+
if (extendedLightingShaderAvailable) {
|
|
7799
|
+
// The direct-light modifier ABI is a 32-layer array. It is created only
|
|
7800
|
+
// for the matching extended-lighting topology; the no-extension variant
|
|
7801
|
+
// has no IES/Cookie/LTC declarations or sampled resources at all.
|
|
7802
|
+
const modifierArrayLayers = 32;
|
|
7803
|
+
const iesModifierBytesPerRow = IES_SLICE_WIDTH * 2;
|
|
7804
|
+
const cookieModifierBytesPerRow = COOKIE_SLICE_SIZE * 4;
|
|
7805
|
+
const iesModifierFallbackData = new Uint8Array(
|
|
7806
|
+
iesModifierBytesPerRow * IES_SLICE_HEIGHT * modifierArrayLayers,
|
|
7807
|
+
);
|
|
7808
|
+
for (let offset = 0; offset < iesModifierFallbackData.length; offset += 2) {
|
|
7809
|
+
iesModifierFallbackData[offset] = 0;
|
|
7810
|
+
iesModifierFallbackData[offset + 1] = 0x3c;
|
|
7811
|
+
}
|
|
7812
|
+
const cookieModifierFallbackData = new Uint8Array(
|
|
7813
|
+
cookieModifierBytesPerRow * COOKIE_SLICE_SIZE * modifierArrayLayers,
|
|
7814
|
+
);
|
|
7815
|
+
cookieModifierFallbackData.fill(0xff);
|
|
7816
|
+
const iesModifierFallbackResult = runShimSyncStep(
|
|
7817
|
+
() =>
|
|
7818
|
+
rhiDevice.createTexture({
|
|
7819
|
+
label: 'fallback-ies-profile-array',
|
|
7820
|
+
size: {
|
|
7821
|
+
width: IES_SLICE_WIDTH,
|
|
7822
|
+
height: IES_SLICE_HEIGHT,
|
|
7823
|
+
depthOrArrayLayers: modifierArrayLayers,
|
|
7824
|
+
},
|
|
7825
|
+
mipLevelCount: 1,
|
|
7826
|
+
sampleCount: 1,
|
|
7827
|
+
dimension: '2d',
|
|
7828
|
+
format: 'r16float',
|
|
7829
|
+
usage: GPU_TEXTURE_USAGE_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_DST,
|
|
7830
|
+
viewFormats: [],
|
|
7831
|
+
textureBindingViewDimension: undefined,
|
|
7832
|
+
}),
|
|
7833
|
+
'webgpu-runtime-error',
|
|
7834
|
+
'createTexture (fallback IES array) succeeded',
|
|
7835
|
+
'check array-layer and texture-binding limits',
|
|
7836
|
+
);
|
|
7837
|
+
if (!iesModifierFallbackResult.ok) throw iesModifierFallbackResult.error;
|
|
7838
|
+
const cookieModifierFallbackResult = runShimSyncStep(
|
|
7839
|
+
() =>
|
|
7840
|
+
rhiDevice.createTexture({
|
|
7841
|
+
label: 'fallback-cookie-array',
|
|
7842
|
+
size: {
|
|
7843
|
+
width: COOKIE_SLICE_SIZE,
|
|
7844
|
+
height: COOKIE_SLICE_SIZE,
|
|
7845
|
+
depthOrArrayLayers: modifierArrayLayers,
|
|
7846
|
+
},
|
|
7847
|
+
mipLevelCount: 1,
|
|
7848
|
+
sampleCount: 1,
|
|
7849
|
+
dimension: '2d',
|
|
7850
|
+
format: 'rgba8unorm',
|
|
7851
|
+
usage: GPU_TEXTURE_USAGE_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_DST,
|
|
7852
|
+
viewFormats: [],
|
|
7853
|
+
textureBindingViewDimension: undefined,
|
|
7854
|
+
}),
|
|
7855
|
+
'webgpu-runtime-error',
|
|
7856
|
+
'createTexture (fallback Cookie array) succeeded',
|
|
7857
|
+
'check array-layer and texture-binding limits',
|
|
7858
|
+
);
|
|
7859
|
+
if (!cookieModifierFallbackResult.ok) throw cookieModifierFallbackResult.error;
|
|
7860
|
+
const iesWrite = runShimSyncStep(
|
|
7861
|
+
() =>
|
|
7862
|
+
queue.writeTexture(
|
|
7863
|
+
{ texture: iesModifierFallbackResult.value, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
|
|
7864
|
+
iesModifierFallbackData,
|
|
7865
|
+
{ offset: 0, bytesPerRow: iesModifierBytesPerRow, rowsPerImage: IES_SLICE_HEIGHT },
|
|
7866
|
+
{
|
|
7867
|
+
width: IES_SLICE_WIDTH,
|
|
7868
|
+
height: IES_SLICE_HEIGHT,
|
|
7869
|
+
depthOrArrayLayers: modifierArrayLayers,
|
|
7870
|
+
},
|
|
7871
|
+
),
|
|
7872
|
+
'queue-write-buffer-out-of-bounds',
|
|
7873
|
+
'queue.writeTexture (fallback IES array) succeeded',
|
|
7874
|
+
'verify modifier IES array row alignment',
|
|
7875
|
+
);
|
|
7876
|
+
if (!iesWrite.ok) throw iesWrite.error;
|
|
7877
|
+
const cookieWrite = runShimSyncStep(
|
|
7878
|
+
() =>
|
|
7879
|
+
queue.writeTexture(
|
|
7880
|
+
{
|
|
7881
|
+
texture: cookieModifierFallbackResult.value,
|
|
7882
|
+
mipLevel: 0,
|
|
7883
|
+
origin: { x: 0, y: 0, z: 0 },
|
|
7884
|
+
},
|
|
7885
|
+
cookieModifierFallbackData,
|
|
7886
|
+
{ offset: 0, bytesPerRow: cookieModifierBytesPerRow, rowsPerImage: COOKIE_SLICE_SIZE },
|
|
7887
|
+
{
|
|
7888
|
+
width: COOKIE_SLICE_SIZE,
|
|
7889
|
+
height: COOKIE_SLICE_SIZE,
|
|
7890
|
+
depthOrArrayLayers: modifierArrayLayers,
|
|
7891
|
+
},
|
|
7892
|
+
),
|
|
7893
|
+
'queue-write-buffer-out-of-bounds',
|
|
7894
|
+
'queue.writeTexture (fallback Cookie array) succeeded',
|
|
7895
|
+
'verify modifier Cookie array row alignment',
|
|
7896
|
+
);
|
|
7897
|
+
if (!cookieWrite.ok) throw cookieWrite.error;
|
|
7898
|
+
const iesModifierFallbackViewResult = runShimSyncStep(
|
|
7899
|
+
() =>
|
|
7900
|
+
rhiDevice.createTextureView(iesModifierFallbackResult.value, {
|
|
7901
|
+
label: 'fallback-ies-profile-array-view',
|
|
7902
|
+
dimension: '2d-array',
|
|
7903
|
+
arrayLayerCount: modifierArrayLayers,
|
|
7904
|
+
}),
|
|
7905
|
+
'webgpu-runtime-error',
|
|
7906
|
+
'createTextureView (fallback IES array) succeeded',
|
|
7907
|
+
'verify 2d-array view matches modifier BGL',
|
|
7908
|
+
);
|
|
7909
|
+
if (!iesModifierFallbackViewResult.ok) throw iesModifierFallbackViewResult.error;
|
|
7910
|
+
iesModifierFallbackTexture = iesModifierFallbackResult.value;
|
|
7911
|
+
iesModifierFallbackView = iesModifierFallbackViewResult.value;
|
|
7912
|
+
const cookieModifierFallbackViewResult = runShimSyncStep(
|
|
7913
|
+
() =>
|
|
7914
|
+
rhiDevice.createTextureView(cookieModifierFallbackResult.value, {
|
|
7915
|
+
label: 'fallback-cookie-array-view',
|
|
7916
|
+
dimension: '2d-array',
|
|
7917
|
+
arrayLayerCount: modifierArrayLayers,
|
|
7918
|
+
}),
|
|
7919
|
+
'webgpu-runtime-error',
|
|
7920
|
+
'createTextureView (fallback Cookie array) succeeded',
|
|
7921
|
+
'verify 2d-array view matches modifier BGL',
|
|
7922
|
+
);
|
|
7923
|
+
if (!cookieModifierFallbackViewResult.ok) throw cookieModifierFallbackViewResult.error;
|
|
7924
|
+
cookieModifierFallbackTexture = cookieModifierFallbackResult.value;
|
|
7925
|
+
cookieModifierFallbackView = cookieModifierFallbackViewResult.value;
|
|
7926
|
+
|
|
7927
|
+
const cookieMatrixFallbackData = createCookieProjectionMatrixData(modifierArrayLayers);
|
|
7928
|
+
const cookieMatrixBufferResult = runShimSyncStep(
|
|
7929
|
+
() =>
|
|
7930
|
+
rhiDevice.createBuffer({
|
|
7931
|
+
label: 'fallback-cookie-matrices',
|
|
7932
|
+
size: COOKIE_MATRIX_BYTES,
|
|
7933
|
+
usage: GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_UNIFORM,
|
|
7934
|
+
mappedAtCreation: false,
|
|
7935
|
+
}),
|
|
7936
|
+
'webgpu-runtime-error',
|
|
7937
|
+
'createBuffer (fallback Cookie matrices) succeeded',
|
|
7938
|
+
'check uniform-buffer size and usage support',
|
|
7939
|
+
);
|
|
7940
|
+
if (!cookieMatrixBufferResult.ok) throw cookieMatrixBufferResult.error;
|
|
7941
|
+
const cookieMatrixWrite = runShimSyncStep(
|
|
7942
|
+
() =>
|
|
7943
|
+
rhiDevice.queue.writeBuffer(cookieMatrixBufferResult.value, 0, cookieMatrixFallbackData),
|
|
7944
|
+
'queue-write-buffer-out-of-bounds',
|
|
7945
|
+
'queue.writeBuffer (fallback Cookie matrices) succeeded',
|
|
7946
|
+
'verify Cookie matrix payload alignment',
|
|
7947
|
+
);
|
|
7948
|
+
if (!cookieMatrixWrite.ok) throw cookieMatrixWrite.error;
|
|
7949
|
+
cookieModifierFallbackMatrixBuffer = cookieMatrixBufferResult.value;
|
|
7950
|
+
|
|
7951
|
+
const createResidentLtcView = (label: string, table: Uint16Array): TextureView => {
|
|
7952
|
+
const textureResult = runShimSyncStep(
|
|
7953
|
+
() =>
|
|
7954
|
+
rhiDevice.createTexture({
|
|
7955
|
+
label,
|
|
7956
|
+
size: { width: LTC_TABLE_WIDTH, height: LTC_TABLE_HEIGHT, depthOrArrayLayers: 1 },
|
|
7957
|
+
mipLevelCount: 1,
|
|
7958
|
+
sampleCount: 1,
|
|
7959
|
+
dimension: '2d',
|
|
7960
|
+
format: 'rgba16float',
|
|
7961
|
+
usage: GPU_TEXTURE_USAGE_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_DST,
|
|
7962
|
+
viewFormats: [],
|
|
7963
|
+
textureBindingViewDimension: undefined,
|
|
7964
|
+
}),
|
|
7965
|
+
'webgpu-runtime-error',
|
|
7966
|
+
`createTexture (${label}) succeeded`,
|
|
7967
|
+
'check resident LTC table texture capabilities',
|
|
7968
|
+
);
|
|
7969
|
+
if (!textureResult.ok) throw textureResult.error;
|
|
7970
|
+
const uploadResult = runShimSyncStep(
|
|
7971
|
+
() =>
|
|
7972
|
+
queue.writeTexture(
|
|
7973
|
+
{ texture: textureResult.value, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
|
|
7974
|
+
new Uint8Array(table.buffer, table.byteOffset, table.byteLength),
|
|
7975
|
+
{ offset: 0, bytesPerRow: LTC_TABLE_WIDTH * 8, rowsPerImage: LTC_TABLE_HEIGHT },
|
|
7976
|
+
{ width: LTC_TABLE_WIDTH, height: LTC_TABLE_HEIGHT, depthOrArrayLayers: 1 },
|
|
7977
|
+
),
|
|
7978
|
+
'queue-write-buffer-out-of-bounds',
|
|
7979
|
+
`queue.writeTexture (${label}) succeeded`,
|
|
7980
|
+
'verify resident LTC table row alignment',
|
|
7981
|
+
);
|
|
7982
|
+
if (!uploadResult.ok) throw uploadResult.error;
|
|
7983
|
+
const viewResult = runShimSyncStep(
|
|
7984
|
+
() =>
|
|
7985
|
+
rhiDevice.createTextureView(textureResult.value, {
|
|
7986
|
+
label: `${label}-view`,
|
|
7987
|
+
dimension: '2d',
|
|
7988
|
+
}),
|
|
7989
|
+
'webgpu-runtime-error',
|
|
7990
|
+
`createTextureView (${label}) succeeded`,
|
|
7991
|
+
'verify resident LTC view matches shader binding',
|
|
7992
|
+
);
|
|
7993
|
+
if (!viewResult.ok) throw viewResult.error;
|
|
7994
|
+
return viewResult.value;
|
|
7995
|
+
};
|
|
7996
|
+
residentLtcLambertView = createResidentLtcView(
|
|
7997
|
+
'extended-lighting-ltc-lambert',
|
|
7998
|
+
LTC_TABLES.lambert,
|
|
7999
|
+
);
|
|
8000
|
+
residentLtcGgxView = createResidentLtcView('extended-lighting-ltc-ggx', LTC_TABLES.ggx);
|
|
8001
|
+
}
|
|
8002
|
+
|
|
6997
8003
|
// Normal-slot fallback: 1x1 RGBA8 (128, 128, 255, 255). pbr.wgsl decodes
|
|
6998
8004
|
// sample.rg * 2 - 1 + z = sqrt(1 - x^2 - y^2), so RG=(128,128)=0.5 maps
|
|
6999
8005
|
// to tangent (0, 0, 1) -- zero perturbation when normalTexture is absent.
|
|
@@ -7303,17 +8309,20 @@ async function buildReadyWebGPU(
|
|
|
7303
8309
|
fragmentEntryPoint?: string;
|
|
7304
8310
|
layout?: unknown;
|
|
7305
8311
|
label?: string;
|
|
8312
|
+
group2Contract?: PipelineGroup2Contract;
|
|
7306
8313
|
}
|
|
7307
8314
|
>();
|
|
7308
8315
|
if (unlitModule !== null)
|
|
7309
8316
|
shaderModuleMap.set('forgeax::default-unlit', {
|
|
7310
8317
|
vertex: unlitModule,
|
|
7311
8318
|
fragment: unlitModule,
|
|
8319
|
+
...(unlitGroup2Contract === undefined ? {} : { group2Contract: unlitGroup2Contract }),
|
|
7312
8320
|
});
|
|
7313
8321
|
if (pbrModule !== null)
|
|
7314
8322
|
shaderModuleMap.set('forgeax::default-standard-pbr', {
|
|
7315
8323
|
vertex: pbrModule,
|
|
7316
8324
|
fragment: pbrModule,
|
|
8325
|
+
...(pbrGroup2Contract === undefined ? {} : { group2Contract: pbrGroup2Contract }),
|
|
7317
8326
|
});
|
|
7318
8327
|
if (spriteModule !== null)
|
|
7319
8328
|
// feat-20260625-refactor-sprite-as-transparent-mesh M3 / w14 (D-7):
|
|
@@ -7325,6 +8334,7 @@ async function buildReadyWebGPU(
|
|
|
7325
8334
|
shaderModuleMap.set('forgeax::sprite', {
|
|
7326
8335
|
vertex: spriteModule,
|
|
7327
8336
|
fragment: spriteModule,
|
|
8337
|
+
...(spriteGroup2Contract === undefined ? {} : { group2Contract: spriteGroup2Contract }),
|
|
7328
8338
|
});
|
|
7329
8339
|
// feat-20260624-sprite-lit-shading-model-pure-2d-lighting M1' / t7:
|
|
7330
8340
|
// sprite-lit shader registers under canonical `forgeax::sprite-lit` —
|
|
@@ -7333,6 +8343,7 @@ async function buildReadyWebGPU(
|
|
|
7333
8343
|
shaderModuleMap.set('forgeax::sprite-lit', {
|
|
7334
8344
|
vertex: spriteLitModule,
|
|
7335
8345
|
fragment: spriteLitModule,
|
|
8346
|
+
...(spriteLitGroup2Contract === undefined ? {} : { group2Contract: spriteLitGroup2Contract }),
|
|
7336
8347
|
});
|
|
7337
8348
|
// M2-T4: fullscreen-post shader modules (tonemap + skybox) are registered
|
|
7338
8349
|
// in their respective blocks after pipeline layout creation — layout is
|
|
@@ -7434,7 +8445,6 @@ async function buildReadyWebGPU(
|
|
|
7434
8445
|
...runtimeSpecConstTable,
|
|
7435
8446
|
...runtimeLinearLdrMaterialSpecTable,
|
|
7436
8447
|
];
|
|
7437
|
-
|
|
7438
8448
|
// Boot-time pre-warm: build SPEC_CONST entries whose shader modules are
|
|
7439
8449
|
// compiled. Entries referencing a missing module are silently skipped
|
|
7440
8450
|
// (the empty-manifest path — D-3). Each build failure for an available
|
|
@@ -7467,7 +8477,7 @@ async function buildReadyWebGPU(
|
|
|
7467
8477
|
// `makeWebGPURenderer` scope) from the prewarmed `pipelineCache` for
|
|
7468
8478
|
// SPEC_CONST entries whose `variantSet !== undefined`. The URP record
|
|
7469
8479
|
// path queries `getMaterialShaderPipeline(...)` keyed off
|
|
7470
|
-
// `cacheKeyOf(spec)` with
|
|
8480
|
+
// `cacheKeyOf(spec)` with the non-clustered boot variant; both caches
|
|
7471
8481
|
// generate keys via the same `cacheKeyOf` so the lookup hits the
|
|
7472
8482
|
// boot-time prewarmed PSO instead of triggering a 1-frame async-compile
|
|
7473
8483
|
// skip-draw. Seeding only variantSet-bearing entries keeps the
|
|
@@ -7477,11 +8487,12 @@ async function buildReadyWebGPU(
|
|
|
7477
8487
|
// explicit instead of collapsing into one map (charter P3).
|
|
7478
8488
|
for (const spec of runtimeSpecConstTable) {
|
|
7479
8489
|
if (spec.shader.variantSet === undefined) continue;
|
|
7480
|
-
|
|
8490
|
+
const modules = shaderModuleMap.get(spec.shader.id);
|
|
8491
|
+
if (modules === undefined) continue;
|
|
7481
8492
|
const key = cacheKeyOf(spec);
|
|
7482
8493
|
const built = pipelineCache.get(key);
|
|
7483
|
-
if (built !== undefined) {
|
|
7484
|
-
seedMaterialShaderPipelineCache(key, built as RenderPipeline);
|
|
8494
|
+
if (built !== undefined && modules.group2Contract !== undefined) {
|
|
8495
|
+
seedMaterialShaderPipelineCache(key, built as RenderPipeline, modules.group2Contract);
|
|
7485
8496
|
}
|
|
7486
8497
|
}
|
|
7487
8498
|
|
|
@@ -7489,11 +8500,12 @@ async function buildReadyWebGPU(
|
|
|
7489
8500
|
// swap-chain view target. Seed every material entry, including no-variant
|
|
7490
8501
|
// unlit, because tonemap=none reaches this cache directly on frame one.
|
|
7491
8502
|
for (const spec of runtimeLinearLdrMaterialSpecTable) {
|
|
7492
|
-
|
|
8503
|
+
const modules = shaderModuleMap.get(spec.shader.id);
|
|
8504
|
+
if (modules === undefined) continue;
|
|
7493
8505
|
const key = cacheKeyOf(spec);
|
|
7494
8506
|
const built = pipelineCache.get(key);
|
|
7495
|
-
if (built !== undefined) {
|
|
7496
|
-
seedMaterialShaderPipelineCache(key, built as RenderPipeline);
|
|
8507
|
+
if (built !== undefined && modules.group2Contract !== undefined) {
|
|
8508
|
+
seedMaterialShaderPipelineCache(key, built as RenderPipeline, modules.group2Contract);
|
|
7497
8509
|
}
|
|
7498
8510
|
}
|
|
7499
8511
|
}
|
|
@@ -7701,15 +8713,15 @@ async function buildReadyWebGPU(
|
|
|
7701
8713
|
|
|
7702
8714
|
// feat-20260528-fxaa-post-processing M2 / w10: FXAA pipeline prebuilt.
|
|
7703
8715
|
// When the manifest contains the fxaa entry (rgb2luma marker, D-5),
|
|
7704
|
-
// construct the
|
|
8716
|
+
// construct the 3-entry BGL (texture + sampler + output-policy UBO),
|
|
7705
8717
|
// pipeline layout, fullscreen render pipeline (vertex = fullscreen
|
|
7706
8718
|
// triangle from fxaa.wgsl, fragment = FXAA 3.11 algorithm), and
|
|
7707
8719
|
// linear clamp-to-edge sampler. Mirrors the tonemap pipeline
|
|
7708
8720
|
// construction pattern directly above.
|
|
7709
8721
|
if (fxaaModule !== null) {
|
|
7710
|
-
// FXAA BindGroupLayout:
|
|
7711
|
-
//
|
|
7712
|
-
//
|
|
8722
|
+
// FXAA BindGroupLayout: 3 entries (texture + sampler + output-policy UBO).
|
|
8723
|
+
// The fxaa.wgsl fragment stage declares @binding(0) texture_2d<f32>,
|
|
8724
|
+
// @binding(1) sampler, and @binding(2) FxaaParams.
|
|
7713
8725
|
const fxaaBglResult = runShimSyncStep(
|
|
7714
8726
|
() =>
|
|
7715
8727
|
rhiDevice.createBindGroupLayout({
|
|
@@ -7725,6 +8737,11 @@ async function buildReadyWebGPU(
|
|
|
7725
8737
|
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
7726
8738
|
sampler: { type: 'filtering' },
|
|
7727
8739
|
},
|
|
8740
|
+
{
|
|
8741
|
+
binding: 2,
|
|
8742
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
8743
|
+
buffer: { type: 'uniform' },
|
|
8744
|
+
},
|
|
7728
8745
|
],
|
|
7729
8746
|
}),
|
|
7730
8747
|
'webgpu-runtime-error',
|
|
@@ -8760,6 +9777,7 @@ async function buildReadyWebGPU(
|
|
|
8760
9777
|
}
|
|
8761
9778
|
|
|
8762
9779
|
const pipelineState: PipelineState = {
|
|
9780
|
+
device: rhiDevice,
|
|
8763
9781
|
// feat-20260518-pbr-direct-lighting-mvp M5 / w22.10 (AC-06 dual->triple
|
|
8764
9782
|
// pipeline + D-2 + D-10): three distinct GPU render-pipeline handles
|
|
8765
9783
|
// backed by 2 distinct shader modules (unlit + pbr) x 2 vertex stride
|
|
@@ -8802,6 +9820,8 @@ async function buildReadyWebGPU(
|
|
|
8802
9820
|
// standard pipeline fields above).
|
|
8803
9821
|
pbrPipelineLayout:
|
|
8804
9822
|
unlitModule !== null && pbrModule !== null ? pipelineLayoutResult.value : null,
|
|
9823
|
+
pbrProbePipelineLayout:
|
|
9824
|
+
unlitModule !== null && pbrModule !== null ? pbrLayouts.value.probePipelineLayout : null,
|
|
8805
9825
|
// feat-20260609-hdrp-cluster-fragment-ggx M4.5 / w36 (D-10 option A):
|
|
8806
9826
|
// HDRP-variant PipelineLayout (4-BGL chain with HDRP unified 7-slot
|
|
8807
9827
|
// group(2) BGL substituted for the URP 1-slot mesh-array BGL). Built
|
|
@@ -8811,10 +9831,12 @@ async function buildReadyWebGPU(
|
|
|
8811
9831
|
// `selectPipelineLayoutForVariant` based on the caller's variantSet.
|
|
8812
9832
|
hdrpPbrPipelineLayout:
|
|
8813
9833
|
unlitModule !== null && pbrModule !== null ? hdrpPbrPipelineLayoutHandle : null,
|
|
9834
|
+
hdrpProbePbrPipelineLayout:
|
|
9835
|
+
unlitModule !== null && pbrModule !== null ? hdrpProbePbrPipelineLayoutHandle : null,
|
|
8814
9836
|
hdrpClusterMembershipPipeline,
|
|
8815
9837
|
hdrpClusterMembershipBindGroupLayout,
|
|
8816
9838
|
// bug-20260611-skin-pipeline-layout: skin-variant PipelineLayout (4-BGL
|
|
8817
|
-
// chain with
|
|
9839
|
+
// chain with 3-entry mesh-array BGL substituted for the standard 1-entry
|
|
8818
9840
|
// mesh-array BGL). Built at boot above (see pbrSkinPipelineLayoutHandle);
|
|
8819
9841
|
// gated on the same unlit/pbr module presence so Camera-only and empty-
|
|
8820
9842
|
// manifest paths see null. Selected per call by
|
|
@@ -8822,17 +9844,23 @@ async function buildReadyWebGPU(
|
|
|
8822
9844
|
// `LayoutKind = 'pbr-skin'`.
|
|
8823
9845
|
pbrSkinPipelineLayout:
|
|
8824
9846
|
unlitModule !== null && pbrModule !== null ? pbrSkinPipelineLayoutHandle : null,
|
|
9847
|
+
pbrSkinProbePipelineLayout:
|
|
9848
|
+
unlitModule !== null && pbrModule !== null ? pbrSkinProbePipelineLayoutHandle : null,
|
|
8825
9849
|
// feat-20260611 R2 / M8 / w28 (IS-14): record-stage skin BG plumbing.
|
|
8826
9850
|
// Mirror the gating used by `pbrSkinPipelineLayout` -- both fields
|
|
8827
9851
|
// come from the same `buildPbrSkinLayouts` call so they live or die
|
|
8828
9852
|
// together. Surfaced on PipelineState so render-system-record can
|
|
8829
|
-
// build a
|
|
9853
|
+
// build a 3-binding BG matching `pbr-skin-pl` (was: 1-binding
|
|
8830
9854
|
// `pbr-mesh-bg` + `pbr-mesh-array-bgl`, which the device rejected
|
|
8831
9855
|
// every frame with `Bind group layout pbr-skin-mesh-array-bgl ... does
|
|
8832
9856
|
// not match layout pbr-mesh-array-bgl` + Invalid CommandBuffer +
|
|
8833
9857
|
// queue-submit-failed).
|
|
8834
9858
|
pbrSkinMeshBindGroupLayout:
|
|
8835
9859
|
unlitModule !== null && pbrModule !== null ? pbrSkinMeshBindGroupLayoutHandle : null,
|
|
9860
|
+
hdrpSkinPipelineLayout:
|
|
9861
|
+
unlitModule !== null && pbrModule !== null ? hdrpSkinPipelineLayoutHandle : null,
|
|
9862
|
+
hdrpSkinMeshBindGroupLayout:
|
|
9863
|
+
unlitModule !== null && pbrModule !== null ? hdrpSkinMeshBindGroupLayoutHandle : null,
|
|
8836
9864
|
// feat-20260612-skin-palette-per-frame-upload M1 / m1-2: animator-ready
|
|
8837
9865
|
// skin-palette allocator (replaces the prior identity-buffer stub).
|
|
8838
9866
|
// Same gating as `pbrSkinPipelineLayout` -- `null` when the skin
|
|
@@ -8844,6 +9872,7 @@ async function buildReadyWebGPU(
|
|
|
8844
9872
|
colorAttachmentFormat: swapChainFormats.view,
|
|
8845
9873
|
surfaceProfile: surfaceViewFormats ? 'dual-view' : 'raw-only',
|
|
8846
9874
|
viewBindGroupLayout: viewBglResult.value,
|
|
9875
|
+
extendedLightingAvailable: extendedLightingShaderAvailable,
|
|
8847
9876
|
projectorAvailable,
|
|
8848
9877
|
materialBindGroupLayout: materialBglResult.value,
|
|
8849
9878
|
meshBindGroupLayout: meshArrayBglResult.value,
|
|
@@ -8859,9 +9888,8 @@ async function buildReadyWebGPU(
|
|
|
8859
9888
|
// `{ buffer: Buffer; sizeInBytes: number }` in T-M2-06 to match.
|
|
8860
9889
|
materialUniformBuffer: meshSsboState.material,
|
|
8861
9890
|
meshStorageBuffer: meshSsboState.mesh,
|
|
8862
|
-
pointLightsBuffer: pointLightsBufferResult.value,
|
|
8863
|
-
spotLightsBuffer: spotLightsBufferResult.value,
|
|
8864
9891
|
instancesBindGroupLayout: instancesBglResult.value,
|
|
9892
|
+
probeInstancesBindGroupLayout: probeInstancesBglResult.value,
|
|
8865
9893
|
identityInstanceBuffer: identityInstanceResult.value,
|
|
8866
9894
|
defaultSampler: defaultSamplerResult.value,
|
|
8867
9895
|
nearestSampler: nearestSamplerResult.value,
|
|
@@ -8884,6 +9912,23 @@ async function buildReadyWebGPU(
|
|
|
8884
9912
|
// feat-20260520-directional-light-shadow-mapping M2 / w14 (D-1):
|
|
8885
9913
|
// 1x1 depth32float fallback bound at viewBindGroup binding(3).
|
|
8886
9914
|
shadowFallbackTextureView: shadowFallbackViewResult.value,
|
|
9915
|
+
...(extendedLightingShaderAvailable
|
|
9916
|
+
? {
|
|
9917
|
+
iesProfileTexture: requireExtendedResource(iesModifierFallbackTexture, 'ies texture'),
|
|
9918
|
+
cookieTexture: requireExtendedResource(cookieModifierFallbackTexture, 'cookie texture'),
|
|
9919
|
+
cookieMatrixBuffer: requireExtendedResource(
|
|
9920
|
+
cookieModifierFallbackMatrixBuffer,
|
|
9921
|
+
'cookie matrix buffer',
|
|
9922
|
+
),
|
|
9923
|
+
iesProfileTextureView: requireExtendedResource(iesModifierFallbackView, 'ies view'),
|
|
9924
|
+
cookieTextureView: requireExtendedResource(cookieModifierFallbackView, 'cookie view'),
|
|
9925
|
+
ltcLambertTextureView: requireExtendedResource(
|
|
9926
|
+
residentLtcLambertView,
|
|
9927
|
+
'ltc lambert view',
|
|
9928
|
+
),
|
|
9929
|
+
ltcGgxTextureView: requireExtendedResource(residentLtcGgxView, 'ltc ggx view'),
|
|
9930
|
+
}
|
|
9931
|
+
: {}),
|
|
8887
9932
|
// feat-20260612-point-light-shadows-urp-hdrp Round-2 F-1: 1x1x6
|
|
8888
9933
|
// depth32float cube_array fallback bound at viewBindGroup binding(5)
|
|
8889
9934
|
// when no PointLightShadow snapshots are active. Always-present
|