@forgeax/engine-render 0.1.29 → 0.1.31
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 +248 -21
- package/dist/assembly/atmosphere-shader-prewarm.d.ts +1 -1
- package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -1
- package/dist/assembly/device-feature-admission.d.ts +4 -3
- package/dist/assembly/device-feature-admission.d.ts.map +1 -1
- package/dist/assembly/dynamic-geometry-host.d.ts +6 -0
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +6 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +26 -3
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/pipeline-helpers.d.ts +24 -0
- package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
- package/dist/assembly/recovery/recovery-attempt.d.ts +1 -6
- package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -1
- package/dist/assembly/recovery/renderer-recover.d.ts +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready.d.ts +1 -1
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/capture/scene-projection.d.ts +41 -0
- package/dist/capture/scene-projection.d.ts.map +1 -0
- package/dist/{chunk-BQVQOQJH.mjs → chunk-7MQ3XBSP.mjs} +20 -14
- package/dist/chunk-7MQ3XBSP.mjs.map +1 -0
- package/dist/{chunk-S4DNKQYQ.mjs → chunk-C3DDSXYK.mjs} +86 -33
- package/dist/chunk-C3DDSXYK.mjs.map +1 -0
- package/dist/{chunk-BTWH7BP4.mjs → chunk-FUUO4UND.mjs} +73 -29
- package/dist/chunk-FUUO4UND.mjs.map +1 -0
- package/dist/{chunk-NRNXBLIJ.mjs → chunk-JRBDO2KB.mjs} +46 -6
- package/dist/chunk-JRBDO2KB.mjs.map +1 -0
- package/dist/{chunk-NICXSHAT.mjs → chunk-MDQJPY3T.mjs} +8 -6
- package/dist/chunk-MDQJPY3T.mjs.map +1 -0
- package/dist/{chunk-JQTSEI3Z.mjs → chunk-NO4WZCAH.mjs} +3 -3
- package/dist/chunk-NO4WZCAH.mjs.map +1 -0
- package/dist/{chunk-FUODTTIA.mjs → chunk-PSEQ6QPH.mjs} +621 -108
- package/dist/chunk-PSEQ6QPH.mjs.map +1 -0
- package/dist/{chunk-33XZFTQU.mjs → chunk-S5UE2T5P.mjs} +48 -6
- package/dist/chunk-S5UE2T5P.mjs.map +1 -0
- package/dist/components/directional-light.d.ts +2 -2
- package/dist/components/dynamic-resolution.d.ts +15 -0
- package/dist/components/dynamic-resolution.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/reflection-probe.d.ts +1 -0
- package/dist/components/reflection-probe.d.ts.map +1 -1
- package/dist/components/skylight.d.ts +4 -1
- package/dist/components/skylight.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +2172 -796
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/device-scope.d.ts +2 -0
- package/dist/device/device-scope.d.ts.map +1 -1
- package/dist/device/gpu-residency.d.ts +2 -2
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/dynamic-geometry.d.ts.map +1 -1
- package/dist/environment/background.d.ts +3 -2
- package/dist/environment/background.d.ts.map +1 -1
- package/dist/environment/ibl.d.ts +11 -0
- package/dist/environment/ibl.d.ts.map +1 -0
- package/dist/environment/storage.d.ts +14 -0
- package/dist/environment/storage.d.ts.map +1 -0
- package/dist/errors/gpu-driven.d.ts +2 -2
- package/dist/errors/gpu-driven.d.ts.map +1 -1
- package/dist/errors/render.d.ts +35 -1
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +7 -0
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +18 -1
- package/dist/extract/gpu-driven.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/material-artifacts.d.ts +24 -0
- package/dist/gpu-driven/material-artifacts.d.ts.map +1 -0
- package/dist/gpu-driven/material-bindings.d.ts +7 -0
- package/dist/gpu-driven/material-bindings.d.ts.map +1 -0
- package/dist/gpu-driven/prepared-draw.d.ts +2 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +27 -6
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-resource.d.ts +5 -2
- package/dist/gpu-resource.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +3 -0
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +2 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +3 -1
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +83 -81
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +7 -5
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +16 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/internal.mjs +6 -6
- package/dist/material-row.d.ts +10 -0
- package/dist/material-row.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +3 -1
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/render-extent.d.ts +51 -0
- package/dist/pipeline/render-extent.d.ts.map +1 -0
- package/dist/pipeline/standard-build-context.d.ts +7 -0
- package/dist/pipeline/standard-build-context.d.ts.map +1 -0
- package/dist/pipeline/standard-forward-lane.d.ts +3 -2
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +2 -1
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +3 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/post-process-errors.d.ts +9 -2
- package/dist/post-process-errors.d.ts.map +1 -1
- package/dist/prepare/extended-lighting/resources.d.ts +3 -3
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +3 -8
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts +3 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +7 -2
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +6 -3
- package/dist/record/main-pass-material.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/prepared-material-bindings.d.ts.map +1 -1
- package/dist/record/recovery-pipeline.d.ts +9 -1
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +34 -3
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +2 -2
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +5 -2
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/reflection/background.d.ts +14 -0
- package/dist/reflection/background.d.ts.map +1 -0
- package/dist/reflection/filter.d.ts.map +1 -1
- package/dist/reflection/projection.d.ts +1 -0
- package/dist/reflection/projection.d.ts.map +1 -1
- package/dist/reflection/record-owner.d.ts +4 -2
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +47 -32
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +17 -1
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +9 -2
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +29 -2
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +2 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/ssao-config.d.ts +8 -6
- package/dist/ssao-config.d.ts.map +1 -1
- package/dist/ssr/graph.d.ts.map +1 -1
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- package/dist/temporal/standard-scene-data.d.ts +2 -1
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/target-coverage-attachment.d.ts +15 -0
- package/dist/temporal/target-coverage-attachment.d.ts.map +1 -0
- package/dist/temporal/temporal-view.d.ts +6 -2
- package/dist/temporal/temporal-view.d.ts.map +1 -1
- package/dist/temporal/view.d.ts +6 -0
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +6 -0
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/component.d.ts +1 -0
- package/dist/volume/component.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/__tests__/capture-scene-projection.unit.test.ts +24 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +15 -24
- package/src/__tests__/device-loss-fanout.unit.test.ts +43 -0
- package/src/__tests__/dynamic-geometry-batch.unit.test.ts +157 -0
- package/src/__tests__/dynamic-geometry.unit.test.ts +36 -0
- package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +81 -0
- package/src/__tests__/factory-contract.integration.test.ts +164 -1
- package/src/__tests__/frame-observation.contract.test.ts +21 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +1 -1
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +20 -1
- package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +0 -3
- package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +0 -3
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +11 -6
- package/src/__tests__/gpu-driven-production.integration.test.ts +13 -25
- package/src/__tests__/gpu-driven-view.unit.test.ts +1 -0
- package/src/__tests__/gpu-pass-timing-browser-runner.ts +15 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +14 -1
- package/src/__tests__/gpu-resource-scope.unit.test.ts +71 -0
- package/src/__tests__/gpu-scene.unit.test.ts +55 -1
- package/src/__tests__/materials-standard-contract.unit.test.ts +5 -1
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +8 -0
- package/src/__tests__/morph-neutral-admission.unit.test.ts +19 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +6 -1
- package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -5
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +216 -5
- package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +68 -1
- package/src/__tests__/reflection-probe-authoring.unit.test.ts +12 -10
- package/src/__tests__/reflection-probe-box.dawn.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +9 -0
- package/src/__tests__/reflection-probe-orientation.dawn.test.ts +294 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +5 -15
- package/src/__tests__/reflection-probe-updates.unit.test.ts +129 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +3 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +5 -1
- package/src/__tests__/render-extent.unit.test.ts +63 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +3 -2
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +173 -9
- package/src/__tests__/shader-manifest-fixture.ts +6 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +8 -1
- package/src/__tests__/specular-environment-scale.dawn.test.ts +4 -2
- package/src/__tests__/ssao-config.unit.test.ts +7 -0
- package/src/__tests__/ssao-surface.dawn.test.ts +252 -0
- package/src/__tests__/ssr-cube-silhouette.dawn.test.ts +270 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +51 -43
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +4 -0
- package/src/__tests__/ssr-gpu-dispatch.ts +10 -5
- package/src/__tests__/ssr-graph-topology.unit.test.ts +2 -2
- package/src/__tests__/ssr-hiz.browser.test.ts +1 -1
- package/src/__tests__/ssr-hiz.dawn.test.ts +1 -1
- package/src/__tests__/ssr-spatial.integration.test.ts +1 -1
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +40 -0
- package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +238 -1
- package/src/__tests__/standard-profile-oracle.unit.test.ts +27 -0
- package/src/__tests__/taa-coverage-edge.dawn.test.ts +6 -0
- package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +5 -1
- package/src/__tests__/taa-stability-feedback.ts +1 -0
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +2 -2
- package/src/__tests__/temporal-view.unit.test.ts +17 -0
- package/src/__tests__/transmission-prewarm.unit.test.ts +57 -0
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +62 -13
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +7 -7
- package/src/assembly/atmosphere-shader-prewarm.ts +3 -2
- package/src/assembly/device-feature-admission.ts +9 -4
- package/src/assembly/dynamic-geometry-host.ts +236 -163
- package/src/assembly/host-contract.ts +9 -0
- package/src/assembly/material/assembly.ts +350 -6
- package/src/assembly/material/pipeline-helpers.ts +100 -0
- package/src/assembly/material-shader-policy.ts +39 -18
- package/src/assembly/recovery/device-loss-fanout.ts +5 -0
- package/src/assembly/recovery/recovery-attempt.ts +2 -29
- package/src/assembly/recovery/renderer-recover.ts +6 -9
- package/src/assembly/renderer-facade.ts +22 -1
- package/src/assembly/shader-prewarm-policy.ts +14 -1
- package/src/assembly/webgpu-ready.ts +28 -19
- package/src/assembly/webgpu-renderer.ts +95 -95
- package/src/capture/scene-projection.ts +37 -0
- package/src/components/__tests__/dynamic-resolution-schema.unit.test.ts +65 -0
- package/src/components/__tests__/light-radius.integration.test.ts +59 -0
- package/src/components/directional-light.ts +3 -3
- package/src/components/dynamic-resolution.ts +67 -0
- package/src/components/index.ts +1 -0
- package/src/components/light-helpers.ts +6 -1
- package/src/components/reflection-probe.ts +1 -0
- package/src/components/skylight.ts +4 -1
- package/src/device/device-scope.ts +13 -7
- package/src/device/gpu-residency.ts +11 -11
- package/src/dynamic-geometry.ts +13 -8
- package/src/environment/background.ts +30 -12
- package/src/environment/ibl.ts +211 -0
- package/src/environment/storage.ts +63 -0
- package/src/errors/gpu-driven.ts +10 -3
- package/src/errors/render.ts +64 -0
- package/src/extract/camera.ts +12 -1
- package/src/extract/contracts.ts +10 -0
- package/src/extract/gpu-driven.ts +72 -17
- package/src/gpu-driven/batch-topology.ts +33 -11
- package/src/gpu-driven/material-artifacts.ts +179 -0
- package/src/gpu-driven/material-bindings.ts +41 -0
- package/src/gpu-driven/prepared-draw.ts +13 -2
- package/src/gpu-driven/production-raster.ts +681 -164
- package/src/gpu-resource.ts +21 -2
- package/src/gpu-scene-schema.ts +11 -2
- package/src/gpu-scene.ts +14 -3
- package/src/hdrp-buffers.ts +69 -0
- package/src/ibl/IblPipelineCache.ts +5 -0
- package/src/ibl/skylight-bind-group.ts +27 -17
- package/src/index.ts +92 -81
- package/src/inspection-types.ts +16 -1
- package/src/material-row.ts +60 -11
- package/src/pbr-pipeline.ts +13 -2
- package/src/pipeline/render-extent.ts +111 -0
- package/src/pipeline/standard-build-context.ts +7 -0
- package/src/pipeline/standard-forward-lane.ts +41 -24
- package/src/pipeline/standard-pipeline.ts +47 -18
- package/src/pipeline/standard-post.ts +4 -0
- package/src/pipeline/standard-profile.ts +2 -1
- package/src/pipeline-builder.ts +12 -3
- package/src/plugin.ts +4 -0
- package/src/post-process-errors.ts +26 -18
- package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +2 -2
- package/src/prepare/extended-lighting/resources.ts +3 -3
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +68 -0
- package/src/record/__tests__/material-bg-cache.unit.test.ts +45 -0
- package/src/record/__tests__/material-slot-plan.unit.test.ts +9 -0
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +47 -1
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +7 -3
- package/src/record/__tests__/temporal-payload.unit.test.ts +2 -0
- package/src/record/frame-lighting.ts +3 -1
- package/src/record/frame-snapshot.ts +13 -11
- package/src/record/frame.ts +150 -74
- package/src/record/main-pass-geometry.ts +60 -9
- package/src/record/main-pass-material.ts +47 -19
- package/src/record/main-pass.ts +63 -4
- package/src/record/prepared-material-bindings.ts +4 -0
- package/src/record/recovery-pipeline.ts +56 -0
- package/src/record/render-context.ts +34 -3
- package/src/record/shadow-pass.ts +187 -50
- package/src/record/typed-frame-graph.ts +60 -3
- package/src/record/view-ubo.ts +1 -1
- package/src/recovery/render-system-candidate.ts +54 -11
- package/src/reflection/background.ts +151 -0
- package/src/reflection/filter.ts +3 -0
- package/src/reflection/projection.ts +16 -6
- package/src/reflection/record-owner.ts +282 -86
- package/src/render-contract.ts +50 -32
- package/src/render-graph-primitives.ts +28 -3
- package/src/render-pipeline.ts +20 -1
- package/src/render-system-extract-tail.ts +23 -3
- package/src/render-system-extract.ts +127 -42
- package/src/render-system.ts +35 -7
- package/src/scene/render-scene.ts +8 -0
- package/src/ssao-config.ts +25 -8
- package/src/ssr/graph.ts +78 -58
- package/src/temporal/gpu.ts +5 -0
- package/src/temporal/standard-scene-data.ts +7 -1
- package/src/temporal/target-coverage-attachment.ts +72 -0
- package/src/temporal/temporal-view.ts +10 -4
- package/src/temporal/view.ts +12 -2
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +24 -9
- package/src/typed-render-graph-primitives.ts +38 -0
- package/src/volume/component.ts +1 -0
- package/src/volume/passes.ts +3 -3
- package/dist/chunk-33XZFTQU.mjs.map +0 -1
- package/dist/chunk-BQVQOQJH.mjs.map +0 -1
- package/dist/chunk-BTWH7BP4.mjs.map +0 -1
- package/dist/chunk-FUODTTIA.mjs.map +0 -1
- package/dist/chunk-JQTSEI3Z.mjs.map +0 -1
- package/dist/chunk-NICXSHAT.mjs.map +0 -1
- package/dist/chunk-NRNXBLIJ.mjs.map +0 -1
- package/dist/chunk-S4DNKQYQ.mjs.map +0 -1
|
@@ -1481,7 +1481,7 @@ describe('projected bounds query', () => {
|
|
|
1481
1481
|
expect(scene.bounds(worldA, 99)).toBeUndefined();
|
|
1482
1482
|
const first = scene.bounds(worldA, 7);
|
|
1483
1483
|
if (first === undefined) throw new Error('expected projected bounds');
|
|
1484
|
-
(first.min
|
|
1484
|
+
Reflect.set(first.min, 0, -999);
|
|
1485
1485
|
expect(scene.bounds(worldA, 7)?.min[0]).toBe(110);
|
|
1486
1486
|
const instanceA = snapshot(1, 0).transform.world,
|
|
1487
1487
|
instanceB = snapshot(2, 20).transform.world;
|
|
@@ -1506,8 +1506,9 @@ describe('projected bounds query', () => {
|
|
|
1506
1506
|
);
|
|
1507
1507
|
expect(scene.bounds(worldA, 7)?.max[0]).toBe(132);
|
|
1508
1508
|
expect(scene.bounds(worldB, 7)?.max[0]).toBe(901);
|
|
1509
|
+
const { localAabb: _localAabb, ...withoutBounds } = a;
|
|
1509
1510
|
scene.extractComposition([worldA], { cameraOwner: 0, resourceOwner: 0 }, 3, () =>
|
|
1510
|
-
frame([
|
|
1511
|
+
frame([withoutBounds]),
|
|
1511
1512
|
);
|
|
1512
1513
|
expect(scene.bounds(worldA, 7)).toBeUndefined();
|
|
1513
1514
|
expect(scene.bounds(worldB, 7)).toBeUndefined();
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { RhiError } from '@forgeax/engine-rhi';
|
|
4
|
-
import type {
|
|
3
|
+
import { type RhiDevice, RhiError } from '@forgeax/engine-rhi';
|
|
4
|
+
import type {
|
|
5
|
+
MaterialShaderManifestEntry,
|
|
6
|
+
MaterialShaderManifestVariant,
|
|
7
|
+
ShaderCatalog,
|
|
8
|
+
} from '@forgeax/engine-shader';
|
|
5
9
|
import { describe, expect, it, vi } from 'vitest';
|
|
6
10
|
import { deviceOptionsForAdapter } from '../assembly/device-feature-admission';
|
|
7
11
|
import {
|
|
@@ -17,6 +21,7 @@ import {
|
|
|
17
21
|
selectPipelineLayoutForVariant,
|
|
18
22
|
shouldDeferMissingPreparedMaterialShader,
|
|
19
23
|
} from '../assembly/factory';
|
|
24
|
+
import { prepareMaterialShaders } from '../assembly/material-shader-policy';
|
|
20
25
|
import {
|
|
21
26
|
buildPipelineForMaterialShader,
|
|
22
27
|
type PipelineBuilderContext,
|
|
@@ -56,6 +61,56 @@ function noColorManifest() {
|
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
describe('material shader variant identity', () => {
|
|
64
|
+
it('defaults a declared coverage-only axis to the opaque boot variant', async () => {
|
|
65
|
+
const entry: MaterialShaderManifestEntry = {
|
|
66
|
+
identifier: 'forgeax::default-standard-pbr',
|
|
67
|
+
sourcePath: 'default-standard-pbr.wgsl',
|
|
68
|
+
composedWgsl: 'coverage-off',
|
|
69
|
+
paramSchema: '[]',
|
|
70
|
+
variants: [
|
|
71
|
+
{
|
|
72
|
+
definesKey: 'COVERAGE_ONLY=false+STORAGE_BUFFER_AVAILABLE=true',
|
|
73
|
+
defines: { COVERAGE_ONLY: false, STORAGE_BUFFER_AVAILABLE: true },
|
|
74
|
+
composedWgsl: 'coverage-off',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
definesKey: 'COVERAGE_ONLY=true+STORAGE_BUFFER_AVAILABLE=true',
|
|
78
|
+
defines: { COVERAGE_ONLY: true, STORAGE_BUFFER_AVAILABLE: true },
|
|
79
|
+
composedWgsl: 'coverage-on',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
const installMaterialArtifact = vi.fn();
|
|
84
|
+
const registry = {
|
|
85
|
+
loadManifest: vi.fn(async () => ({ ok: true, value: undefined })),
|
|
86
|
+
materialShaderManifestEntries: () => [entry][Symbol.iterator](),
|
|
87
|
+
findMaterialArtifact: vi.fn(() => ({ ok: false })),
|
|
88
|
+
installMaterialArtifact,
|
|
89
|
+
} as unknown as ShaderCatalog;
|
|
90
|
+
const assets = { catalog: vi.fn(() => ({ ok: true, value: undefined })) } as never;
|
|
91
|
+
const device = {
|
|
92
|
+
caps: {
|
|
93
|
+
backendKind: 'webgpu',
|
|
94
|
+
storageBuffer: true,
|
|
95
|
+
rgba16floatRenderable: true,
|
|
96
|
+
samplerAliasing: true,
|
|
97
|
+
},
|
|
98
|
+
limits: {
|
|
99
|
+
maxStorageBuffersPerShaderStage: 8,
|
|
100
|
+
maxSampledTexturesPerShaderStage: 32,
|
|
101
|
+
maxTextureArrayLayers: 64,
|
|
102
|
+
maxUniformBuffersPerShaderStage: 4,
|
|
103
|
+
},
|
|
104
|
+
} as unknown as RhiDevice;
|
|
105
|
+
|
|
106
|
+
await prepareMaterialShaders(device, () => registry, assets, new Map());
|
|
107
|
+
|
|
108
|
+
expect(installMaterialArtifact).toHaveBeenCalledWith('forgeax::default-standard-pbr', {
|
|
109
|
+
source: 'coverage-off',
|
|
110
|
+
paramSchema: [],
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
59
114
|
it('requests the extended-lighting sampled-texture limit when the adapter supports it', () => {
|
|
60
115
|
const features = new Set<GPUFeatureName>();
|
|
61
116
|
expect(
|
|
@@ -65,7 +120,7 @@ describe('material shader variant identity', () => {
|
|
|
65
120
|
} as never),
|
|
66
121
|
).toEqual({
|
|
67
122
|
requiredFeatures: [],
|
|
68
|
-
requiredLimits: { maxSampledTexturesPerShaderStage:
|
|
123
|
+
requiredLimits: { maxSampledTexturesPerShaderStage: 22 },
|
|
69
124
|
});
|
|
70
125
|
expect(
|
|
71
126
|
deviceOptionsForAdapter({
|
|
@@ -88,6 +143,29 @@ describe('material shader variant identity', () => {
|
|
|
88
143
|
).toBe(urpVariantKey);
|
|
89
144
|
});
|
|
90
145
|
|
|
146
|
+
it('keeps coverage-only fallback selection explicit', () => {
|
|
147
|
+
const coverageKey = `COVERAGE_ONLY=true+${urpVariantKey}`;
|
|
148
|
+
const manifest = {
|
|
149
|
+
...noColorManifest(),
|
|
150
|
+
variants: [
|
|
151
|
+
{
|
|
152
|
+
...noColorVariant(coverageKey, false),
|
|
153
|
+
definesKey: coverageKey,
|
|
154
|
+
defines: {
|
|
155
|
+
...noColorVariant(coverageKey, false).defines,
|
|
156
|
+
COVERAGE_ONLY: true,
|
|
157
|
+
},
|
|
158
|
+
composedWgsl: 'coverage-on',
|
|
159
|
+
},
|
|
160
|
+
{ ...noColorVariant(urpVariantKey, false), composedWgsl: 'coverage-off' },
|
|
161
|
+
],
|
|
162
|
+
};
|
|
163
|
+
expect(selectNoColorPbrVariant(manifest, true, undefined)?.definesKey).toBe(urpVariantKey);
|
|
164
|
+
expect(
|
|
165
|
+
selectNoColorPbrVariant(manifest, true, `COVERAGE_ONLY=true+${urpVariantKey}`)?.definesKey,
|
|
166
|
+
).toBe(coverageKey);
|
|
167
|
+
});
|
|
168
|
+
|
|
91
169
|
it('keeps physical root declarations out of capability variant selection', () => {
|
|
92
170
|
const manifest = noColorManifest();
|
|
93
171
|
expect(
|
|
@@ -207,6 +285,20 @@ describe('material shader variant identity', () => {
|
|
|
207
285
|
);
|
|
208
286
|
});
|
|
209
287
|
|
|
288
|
+
it('defaults coverage-only variants to the opaque path unless explicitly enabled', () => {
|
|
289
|
+
const variants = [{ defines: { COVERAGE_ONLY: true } }, { defines: { COVERAGE_ONLY: false } }];
|
|
290
|
+
|
|
291
|
+
expect(resolveMaterialShaderVariantSet(undefined, variants, 'webgpu', true)).toBe(
|
|
292
|
+
'COVERAGE_ONLY=false',
|
|
293
|
+
);
|
|
294
|
+
expect(resolveMaterialShaderVariantSet('', variants, 'webgpu', true)).toBe(
|
|
295
|
+
'COVERAGE_ONLY=false',
|
|
296
|
+
);
|
|
297
|
+
expect(resolveMaterialShaderVariantSet('COVERAGE_ONLY=true', variants, 'webgpu', true)).toBe(
|
|
298
|
+
'',
|
|
299
|
+
);
|
|
300
|
+
});
|
|
301
|
+
|
|
210
302
|
it('keeps the negative skinning axis disabled when a shadow request omits it', () => {
|
|
211
303
|
const variants = [
|
|
212
304
|
{
|
|
@@ -260,7 +352,7 @@ describe('material shader variant identity', () => {
|
|
|
260
352
|
variants,
|
|
261
353
|
'webgpu',
|
|
262
354
|
true,
|
|
263
|
-
|
|
355
|
+
20,
|
|
264
356
|
),
|
|
265
357
|
).toBe('EXTENDED_LIGHTING_AVAILABLE=false');
|
|
266
358
|
expect(
|
|
@@ -269,11 +361,11 @@ describe('material shader variant identity', () => {
|
|
|
269
361
|
variants,
|
|
270
362
|
'webgpu',
|
|
271
363
|
true,
|
|
272
|
-
|
|
364
|
+
21,
|
|
273
365
|
),
|
|
274
366
|
).toBe('');
|
|
275
367
|
expect(
|
|
276
|
-
resolveMaterialShaderVariantSet(undefined, variants, 'webgpu', true,
|
|
368
|
+
resolveMaterialShaderVariantSet(undefined, variants, 'webgpu', true, 21),
|
|
277
369
|
).toBeUndefined();
|
|
278
370
|
expect(
|
|
279
371
|
resolveMaterialShaderVariantSet(
|
|
@@ -281,7 +373,7 @@ describe('material shader variant identity', () => {
|
|
|
281
373
|
variants,
|
|
282
374
|
'webgpu',
|
|
283
375
|
true,
|
|
284
|
-
|
|
376
|
+
21,
|
|
285
377
|
),
|
|
286
378
|
).toBe('');
|
|
287
379
|
expect(
|
|
@@ -290,11 +382,31 @@ describe('material shader variant identity', () => {
|
|
|
290
382
|
variants,
|
|
291
383
|
'webgpu',
|
|
292
384
|
false,
|
|
293
|
-
|
|
385
|
+
20,
|
|
294
386
|
),
|
|
295
387
|
).toBe('EXTENDED_LIGHTING_AVAILABLE=false');
|
|
296
388
|
});
|
|
297
389
|
|
|
390
|
+
it('preserves authored default axes while rewriting backend capabilities', () => {
|
|
391
|
+
const variants = [
|
|
392
|
+
{ defines: { AUTHORED_TINT: true, STORAGE_BUFFER_AVAILABLE: true } },
|
|
393
|
+
{ defines: { AUTHORED_TINT: false, STORAGE_BUFFER_AVAILABLE: true } },
|
|
394
|
+
{ defines: { AUTHORED_TINT: true, STORAGE_BUFFER_AVAILABLE: false } },
|
|
395
|
+
{ defines: { AUTHORED_TINT: false, STORAGE_BUFFER_AVAILABLE: false } },
|
|
396
|
+
];
|
|
397
|
+
expect(resolveMaterialShaderVariantSet(undefined, variants, 'webgpu', true)).toBeUndefined();
|
|
398
|
+
expect(resolveMaterialShaderVariantSet('', variants, 'webgpu', true)).toBe('');
|
|
399
|
+
expect(
|
|
400
|
+
resolveMaterialShaderVariantSet('STORAGE_BUFFER_AVAILABLE=true', variants, 'webgpu', true),
|
|
401
|
+
).toBe('');
|
|
402
|
+
expect(resolveMaterialShaderVariantSet(undefined, variants, 'wgpu-webgl2', false)).toBe(
|
|
403
|
+
'AUTHORED_TINT=true+STORAGE_BUFFER_AVAILABLE=false',
|
|
404
|
+
);
|
|
405
|
+
expect(resolveMaterialShaderVariantSet('AUTHORED_TINT=false', variants, 'webgpu', true)).toBe(
|
|
406
|
+
'AUTHORED_TINT=false+STORAGE_BUFFER_AVAILABLE=true',
|
|
407
|
+
);
|
|
408
|
+
});
|
|
409
|
+
|
|
298
410
|
it('keeps sprite region variants on the explicit empty-key contract', () => {
|
|
299
411
|
const variants = [
|
|
300
412
|
{
|
|
@@ -474,6 +586,44 @@ describe('temporal material PSO creation contract', () => {
|
|
|
474
586
|
});
|
|
475
587
|
});
|
|
476
588
|
|
|
589
|
+
it('accepts r8unorm only for the output-domain coverage temporal variant', () => {
|
|
590
|
+
const coverageContext = {
|
|
591
|
+
...context,
|
|
592
|
+
colorFormat: 'r8unorm' as GPUTextureFormat,
|
|
593
|
+
coverageOnly: true,
|
|
594
|
+
} satisfies Pick<
|
|
595
|
+
PipelineBuilderContext,
|
|
596
|
+
'colorFormat' | 'depthFormat' | 'capabilities' | 'coverageOnly'
|
|
597
|
+
>;
|
|
598
|
+
expect(validateTemporalPipelineContract(source, coverageContext).ok).toBe(true);
|
|
599
|
+
expect(
|
|
600
|
+
validateTemporalPipelineContract(source, { ...coverageContext, coverageOnly: false }).ok,
|
|
601
|
+
).toBe(false);
|
|
602
|
+
|
|
603
|
+
const createRenderPipeline = vi.fn((_descriptor: unknown) => ({ ok: true, value: {} }));
|
|
604
|
+
const result = buildPipelineForMaterialShader(
|
|
605
|
+
'default-unlit-temporal-coverage',
|
|
606
|
+
{ source, paramSchema: [] },
|
|
607
|
+
{
|
|
608
|
+
...coverageContext,
|
|
609
|
+
device: { createRenderPipeline },
|
|
610
|
+
shaderModuleFactory: { createShaderModule: () => ({ ok: true, value: {} }) },
|
|
611
|
+
pipelineLayout: {},
|
|
612
|
+
vertexBuffers: [],
|
|
613
|
+
} as unknown as PipelineBuilderContext,
|
|
614
|
+
{ depthWriteEnabled: true, depthCompare: 'less-equal' },
|
|
615
|
+
undefined,
|
|
616
|
+
'vs_temporal',
|
|
617
|
+
'fs_temporal',
|
|
618
|
+
undefined,
|
|
619
|
+
'temporal',
|
|
620
|
+
);
|
|
621
|
+
expect(result.ok).toBe(true);
|
|
622
|
+
expect(createRenderPipeline.mock.calls[0]?.[0]).toMatchObject({
|
|
623
|
+
fragment: { entryPoint: 'fs_temporal', targets: [{ format: 'r8unorm' }] },
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
|
|
477
627
|
it.each([
|
|
478
628
|
{ depthWriteEnabled: false, depthCompare: 'always' as const },
|
|
479
629
|
{ depthWriteEnabled: true, depthCompare: 'never' as const },
|
|
@@ -797,7 +947,7 @@ describe('material shader color-domain contract', () => {
|
|
|
797
947
|
'utf8',
|
|
798
948
|
);
|
|
799
949
|
expect(source).toMatch(
|
|
800
|
-
/if \(\s*!isStandardMapLayout\s*&&\s*isSharedMaterialUserRegionCompatible\(/u,
|
|
950
|
+
/if \(\s*!clustered\s*&&\s*!isStandardMapLayout\s*&&\s*isSharedMaterialUserRegionCompatible\(/u,
|
|
801
951
|
);
|
|
802
952
|
});
|
|
803
953
|
|
|
@@ -813,6 +963,20 @@ describe('material shader color-domain contract', () => {
|
|
|
813
963
|
expect(pipelineSource).toContain('appendTextureInjection(afterTransmission, physicalFields)');
|
|
814
964
|
expect(recordSource).toContain('BUILTIN_USER_REGION_TEXTURE_FIELDS');
|
|
815
965
|
});
|
|
966
|
+
|
|
967
|
+
it('keeps scene-index entries and GPU-driven instance layouts on custom pipelines', () => {
|
|
968
|
+
const source = readFileSync(
|
|
969
|
+
resolve(import.meta.dirname, '../assembly/material/pipeline-helpers.ts'),
|
|
970
|
+
'utf8',
|
|
971
|
+
);
|
|
972
|
+
expect(source).toContain(
|
|
973
|
+
"const sceneIndex = layoutKind === 'gpu-driven-pbr' || layoutKind === 'gpu-driven-skin';",
|
|
974
|
+
);
|
|
975
|
+
expect(source).toContain('? (vertexEntryPoint ?? authoredVertexEntry)');
|
|
976
|
+
expect(source).toMatch(
|
|
977
|
+
/const instancesLayout =\s+layoutKind === 'gpu-driven-pbr' \|\| layoutKind === 'gpu-driven-skin'\s+\? state\.gpuDrivenInstancesBindGroupLayout\s+: state\.instancesBindGroupLayout/u,
|
|
978
|
+
);
|
|
979
|
+
});
|
|
816
980
|
});
|
|
817
981
|
|
|
818
982
|
describe('material shader vertex input contract', () => {
|
|
@@ -30,6 +30,12 @@ const entries: readonly FixtureEntry[] = [
|
|
|
30
30
|
glsl: '',
|
|
31
31
|
bindings: '[]',
|
|
32
32
|
},
|
|
33
|
+
{
|
|
34
|
+
hash: 'fixture-motion-blur',
|
|
35
|
+
wgsl: '// fixture marker: MotionBlurParams\n',
|
|
36
|
+
glsl: '',
|
|
37
|
+
bindings: '[]',
|
|
38
|
+
},
|
|
33
39
|
{
|
|
34
40
|
hash: 'fixture-fxaa',
|
|
35
41
|
wgsl: '// fixture marker: rgb2luma\n',
|
|
@@ -52,6 +52,7 @@ describe('skinned shadow caster', () => {
|
|
|
52
52
|
);
|
|
53
53
|
expect(shader).toContain('#pragma variant_axis SKINNING_DISABLED');
|
|
54
54
|
expect(shader).toContain('#pragma variant_axis ALPHA_MASK');
|
|
55
|
+
expect(shader).toContain('#pragma variant_axis VERTEX_COLOR_AVAILABLE');
|
|
55
56
|
expect(shader).toContain('#pragma material_slot surface');
|
|
56
57
|
expect(shader).toContain('#import forgeax_material::parameters::{material}');
|
|
57
58
|
expect(shader).toContain('@group(1) @binding(46)');
|
|
@@ -59,6 +60,8 @@ describe('skinned shadow caster', () => {
|
|
|
59
60
|
expect(shader).toContain('discard;');
|
|
60
61
|
expect(shader).toContain('@group(2) @binding(1)');
|
|
61
62
|
expect(shader).toContain('let skinMatrix = palette[');
|
|
63
|
+
expect(shader).toContain('@location(13) color : vec4<f32>');
|
|
64
|
+
expect(shader).toContain('out.color = in.color;');
|
|
62
65
|
expect(record).toContain('entry.source.skin.byteOffset');
|
|
63
66
|
expect(record).toContain("'shadow-pbr-skin-mesh'");
|
|
64
67
|
expect(builder).toContain('buffers: [...ctx.vertexBuffers]');
|
|
@@ -76,7 +79,8 @@ describe('skinned shadow caster', () => {
|
|
|
76
79
|
expect(singleton).toContain('if (runtime.device.caps.storageBuffer)');
|
|
77
80
|
expect(singleton).toContain('binding: 46');
|
|
78
81
|
const sceneMaterialEntry = singleton.slice(singleton.lastIndexOf('binding: 46'));
|
|
79
|
-
expect(
|
|
82
|
+
expect(singleton).toContain('c.gpuDrivenStandardPbrFrameResources?.sceneMaterialBuffer');
|
|
83
|
+
expect(singleton).toContain('pipelineState.meshStorageBuffer.buffer');
|
|
80
84
|
expect(sceneMaterialEntry).not.toContain('pipelineState.materialUniformBuffer.buffer');
|
|
81
85
|
expect(singleton.indexOf('if (runtime.device.caps.storageBuffer)')).toBeLessThan(
|
|
82
86
|
singleton.indexOf('binding: 46'),
|
|
@@ -165,6 +169,9 @@ describe('skinned shadow caster', () => {
|
|
|
165
169
|
expect(shadowCasterVariantSet(true, false, true)).toBe(
|
|
166
170
|
'ALPHA_MASK=false+GPU_DRIVEN_SCENE_INDEX_AVAILABLE=true+GPU_DRIVEN_SCENE_INDEX_EXPLICIT=false+SKINNING_DISABLED=true+STORAGE_BUFFER_AVAILABLE=true',
|
|
167
171
|
);
|
|
172
|
+
expect(shadowCasterVariantSet(true, false, true, false, true)).toContain(
|
|
173
|
+
'VERTEX_COLOR_AVAILABLE=true',
|
|
174
|
+
);
|
|
168
175
|
expect(shader).toContain('@group(3) @binding(1) var<storage, read> visibleItems');
|
|
169
176
|
expect(shader).toContain('#pragma variant_axis GPU_DRIVEN_SCENE_INDEX_EXPLICIT');
|
|
170
177
|
expect(shader).toContain('GPU_DRIVEN_SCENE_INDEX_EXPLICIT == false');
|
|
@@ -27,6 +27,8 @@ it('decodes regular and probe-sentinel specular environment scales without tint
|
|
|
27
27
|
@group(0) @binding(2) var<storage, read_write> outputs: array<vec4<f32>>;
|
|
28
28
|
@compute @workgroup_size(4) fn probe_scale(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
29
29
|
let scale = decodeSpecularEnvironmentScale(cases[id.x].xyz, cases[id.x].w);
|
|
30
|
+
// Probe intensity is applied while blending local and global radiance.
|
|
31
|
+
// The outer scale stays one for either sentinel, including zero intensity.
|
|
30
32
|
// RGB is the decoded scale. Alpha is the same lobe after the material AO
|
|
31
33
|
// factor, matching the detached fallback's post-environment multiplication.
|
|
32
34
|
outputs[id.x] = vec4<f32>(scale, scale.x * aoValues[id.x]);
|
|
@@ -139,8 +141,8 @@ it('decodes regular and probe-sentinel specular environment scales without tint
|
|
|
139
141
|
const expected = [
|
|
140
142
|
[0.375, 0.75, 1.125, 0.13125],
|
|
141
143
|
[0.05, 0.1, 0.2, 0.04],
|
|
142
|
-
[
|
|
143
|
-
[
|
|
144
|
+
[1, 1, 1, 0.6],
|
|
145
|
+
[1, 1, 1, 0.25],
|
|
144
146
|
].flat();
|
|
145
147
|
values.forEach((value, index) => {
|
|
146
148
|
const target = expected[index];
|
|
@@ -13,6 +13,7 @@ describe('SSAO parameter authority', () => {
|
|
|
13
13
|
radius: SSAO_DEFAULT_RADIUS,
|
|
14
14
|
bias: SSAO_DEFAULT_BIAS,
|
|
15
15
|
intensity: SSAO_DEFAULT_INTENSITY,
|
|
16
|
+
quality: 'high',
|
|
16
17
|
});
|
|
17
18
|
});
|
|
18
19
|
|
|
@@ -21,9 +22,15 @@ describe('SSAO parameter authority', () => {
|
|
|
21
22
|
radius: 0.65,
|
|
22
23
|
bias: 0.025,
|
|
23
24
|
intensity: 1.4,
|
|
25
|
+
quality: 'high',
|
|
24
26
|
});
|
|
25
27
|
});
|
|
26
28
|
|
|
29
|
+
it.each([NaN, Infinity, -Infinity])('rejects non-finite parameters (%s)', (value) => {
|
|
30
|
+
for (const field of ['radius', 'bias', 'intensity'])
|
|
31
|
+
expect(resolveSsaoParameters({ [field]: value }).ok).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
27
34
|
it('returns structured failures for invalid radius and bias', () => {
|
|
28
35
|
const invalidRadius = resolveSsaoParameters({ radius: 0 });
|
|
29
36
|
const invalidBias = resolveSsaoParameters({ bias: -0.1 });
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { mat4 } from '@forgeax/engine-math';
|
|
4
|
+
import { expect, it } from 'vitest';
|
|
5
|
+
import {
|
|
6
|
+
GPU_TEXTURE_USAGE_COPY_DST,
|
|
7
|
+
GPU_TEXTURE_USAGE_COPY_SRC,
|
|
8
|
+
GPU_TEXTURE_USAGE_RENDER_ATTACHMENT,
|
|
9
|
+
GPU_TEXTURE_USAGE_TEXTURE_BINDING,
|
|
10
|
+
} from '../gpu-texture-usage';
|
|
11
|
+
import {
|
|
12
|
+
GPU_BUFFER_USAGE_COPY_DST,
|
|
13
|
+
GPU_BUFFER_USAGE_MAP_READ,
|
|
14
|
+
GPU_BUFFER_USAGE_UNIFORM,
|
|
15
|
+
} from '../gpu-usage';
|
|
16
|
+
import { generateSsaoKernel, generateSsaoNoise } from '../ssao-buffers';
|
|
17
|
+
|
|
18
|
+
// Analytic tilted plane: y + z + 4 = 0. Every hemisphere sample lies in
|
|
19
|
+
// empty space. Exercise the production fragment program, not a CPU AO copy.
|
|
20
|
+
it.each([
|
|
21
|
+
['plane', 16],
|
|
22
|
+
['plane', 32],
|
|
23
|
+
['plane', 64],
|
|
24
|
+
['orthographic', 64],
|
|
25
|
+
['contact', 64],
|
|
26
|
+
['distant-edge', 64],
|
|
27
|
+
['background', 64],
|
|
28
|
+
] as const)('production SSAO %s with %i samples', async (scene, sampleCount) => {
|
|
29
|
+
const compiler = await import(
|
|
30
|
+
/* @vite-ignore */ new URL('../../../shader-compiler/dist/index.mjs', import.meta.url).href
|
|
31
|
+
);
|
|
32
|
+
const compiled = await compiler.compileShader(
|
|
33
|
+
readFileSync(resolve('packages/shader/src/hdrp-ssao.wgsl'), 'utf8'),
|
|
34
|
+
{
|
|
35
|
+
id: 'forgeax_hdrp::ssao',
|
|
36
|
+
imports: {
|
|
37
|
+
'forgeax_view::common': readFileSync(resolve('packages/shader/src/common.wgsl'), 'utf8'),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
if (!compiled.ok) throw compiled.error;
|
|
42
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
43
|
+
if (!adapter) throw new Error('Dawn adapter unavailable');
|
|
44
|
+
const device = await adapter.requestDevice();
|
|
45
|
+
const errors: string[] = [];
|
|
46
|
+
device.addEventListener('uncapturederror', (event) => errors.push(event.error.message));
|
|
47
|
+
const size = 128;
|
|
48
|
+
const texture = (format: GPUTextureFormat, width = size, height = size) =>
|
|
49
|
+
device.createTexture({
|
|
50
|
+
size: [width, height],
|
|
51
|
+
format,
|
|
52
|
+
usage:
|
|
53
|
+
GPU_TEXTURE_USAGE_RENDER_ATTACHMENT |
|
|
54
|
+
GPU_TEXTURE_USAGE_TEXTURE_BINDING |
|
|
55
|
+
GPU_TEXTURE_USAGE_COPY_SRC |
|
|
56
|
+
GPU_TEXTURE_USAGE_COPY_DST,
|
|
57
|
+
});
|
|
58
|
+
const depth = texture('depth32float');
|
|
59
|
+
const normal = texture('rgba16float');
|
|
60
|
+
const aoSize = size / 2;
|
|
61
|
+
const raw = texture('r8unorm', aoSize, aoSize);
|
|
62
|
+
const blurred = texture('r8unorm', aoSize, aoSize);
|
|
63
|
+
const noise = texture('rgba32float', 4, 4);
|
|
64
|
+
const uniform = device.createBuffer({
|
|
65
|
+
size: 256,
|
|
66
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
67
|
+
});
|
|
68
|
+
const kernel = device.createBuffer({
|
|
69
|
+
size: 1024,
|
|
70
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
71
|
+
});
|
|
72
|
+
const readback = device.createBuffer({
|
|
73
|
+
size: 256 * aoSize,
|
|
74
|
+
usage: GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_MAP_READ,
|
|
75
|
+
});
|
|
76
|
+
try {
|
|
77
|
+
const projection = mat4.create();
|
|
78
|
+
if (scene === 'orthographic') mat4.orthographic(projection, -3, 3, 3, -3, 0.1, 100);
|
|
79
|
+
else mat4.perspective(projection, Math.PI / 3, 1, 0.1, 100);
|
|
80
|
+
const inverse = mat4.create();
|
|
81
|
+
mat4.invert(inverse, projection);
|
|
82
|
+
const payload = new Float32Array(64);
|
|
83
|
+
payload.set(mat4.identity(mat4.create()));
|
|
84
|
+
payload.set(projection, 16);
|
|
85
|
+
payload.set(inverse, 32);
|
|
86
|
+
payload.set([1, 0.5, 0.025, sampleCount], 48);
|
|
87
|
+
device.queue.writeBuffer(uniform, 0, payload);
|
|
88
|
+
device.queue.writeBuffer(
|
|
89
|
+
kernel,
|
|
90
|
+
0,
|
|
91
|
+
new Float32Array(generateSsaoKernel().flatMap((v) => [...v, 0])),
|
|
92
|
+
);
|
|
93
|
+
const noiseRgb = generateSsaoNoise();
|
|
94
|
+
const noiseRgba = new Float32Array(64);
|
|
95
|
+
for (let i = 0; i < 16; i++) noiseRgba.set(noiseRgb.subarray(i * 3, i * 3 + 3), i * 4);
|
|
96
|
+
device.queue.writeTexture({ texture: noise }, noiseRgba, { bytesPerRow: 64 }, [4, 4]);
|
|
97
|
+
const seedModule = device.createShaderModule({
|
|
98
|
+
code: `
|
|
99
|
+
struct Out { @builtin(position) pos: vec4f, @location(0) uv: vec2f };
|
|
100
|
+
@vertex fn vs(@builtin(vertex_index) i:u32)->Out {
|
|
101
|
+
let p = array<vec2f,3>(vec2f(-1,-1),vec2f(3,-1),vec2f(-1,3));
|
|
102
|
+
return Out(vec4f(p[i],0,1),p[i]*vec2f(0.5,-0.5)+0.5);
|
|
103
|
+
}
|
|
104
|
+
struct Fragment { @builtin(frag_depth) depth:f32, @location(0) normal:vec4f };
|
|
105
|
+
@fragment fn fs(in:Out)->Fragment {
|
|
106
|
+
let rayY = (1.0-2.0*in.uv.y)*0.577350269;
|
|
107
|
+
let distance = ${scene === 'orthographic' ? '4.0+(1.0-2.0*in.uv.y)*3.0' : scene === 'plane' ? '4.0/(1.0-rayY)' : scene === 'contact' ? 'select(4.0,3.75,in.uv.x>0.5)' : scene === 'distant-edge' ? 'select(4.0,1.0,in.uv.x>0.5)' : '100.0'};
|
|
108
|
+
return Fragment(${scene === 'orthographic' ? '(distance-0.1)/99.9' : scene === 'background' ? '1.0' : '100.0/99.9-10.0/(99.9*distance)'},${scene === 'plane' || scene === 'orthographic' ? 'vec4f(0.5,0.853553391,0.853553391,1)' : 'vec4f(0.5,0.5,1,1)'});
|
|
109
|
+
}`,
|
|
110
|
+
});
|
|
111
|
+
const seed = device.createRenderPipeline({
|
|
112
|
+
layout: 'auto',
|
|
113
|
+
vertex: { module: seedModule, entryPoint: 'vs' },
|
|
114
|
+
fragment: { module: seedModule, entryPoint: 'fs', targets: [{ format: 'rgba16float' }] },
|
|
115
|
+
depthStencil: { format: 'depth32float', depthWriteEnabled: true, depthCompare: 'always' },
|
|
116
|
+
});
|
|
117
|
+
const module = device.createShaderModule({ code: compiled.value.wgsl });
|
|
118
|
+
const layout = device.createBindGroupLayout({
|
|
119
|
+
entries: [
|
|
120
|
+
{ binding: 0, visibility: 2, buffer: { type: 'uniform' } },
|
|
121
|
+
{ binding: 1, visibility: 2, buffer: { type: 'uniform' } },
|
|
122
|
+
{ binding: 2, visibility: 2, texture: { sampleType: 'unfilterable-float' } },
|
|
123
|
+
{ binding: 3, visibility: 2, sampler: { type: 'non-filtering' } },
|
|
124
|
+
{ binding: 4, visibility: 2, texture: { sampleType: 'unfilterable-float' } },
|
|
125
|
+
{ binding: 5, visibility: 2, texture: { sampleType: 'depth' } },
|
|
126
|
+
{ binding: 6, visibility: 2, sampler: { type: 'non-filtering' } },
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
const calc = device.createRenderPipeline({
|
|
130
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
|
|
131
|
+
vertex: { module, entryPoint: 'vs_ssao' },
|
|
132
|
+
fragment: { module, entryPoint: 'fs_ssao_calc', targets: [{ format: 'r8unorm' }] },
|
|
133
|
+
});
|
|
134
|
+
const sampler = device.createSampler({ addressModeU: 'repeat', addressModeV: 'repeat' });
|
|
135
|
+
const bindings = device.createBindGroup({
|
|
136
|
+
layout,
|
|
137
|
+
entries: [
|
|
138
|
+
{ binding: 0, resource: { buffer: uniform } },
|
|
139
|
+
{ binding: 1, resource: { buffer: kernel } },
|
|
140
|
+
{ binding: 2, resource: noise.createView() },
|
|
141
|
+
{ binding: 3, resource: sampler },
|
|
142
|
+
{ binding: 4, resource: normal.createView() },
|
|
143
|
+
{ binding: 5, resource: depth.createView() },
|
|
144
|
+
{ binding: 6, resource: device.createSampler() },
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
const encoder = device.createCommandEncoder();
|
|
148
|
+
const seedPass = encoder.beginRenderPass({
|
|
149
|
+
colorAttachments: [{ view: normal.createView(), loadOp: 'clear', storeOp: 'store' }],
|
|
150
|
+
depthStencilAttachment: {
|
|
151
|
+
view: depth.createView(),
|
|
152
|
+
depthLoadOp: 'clear',
|
|
153
|
+
depthStoreOp: 'store',
|
|
154
|
+
depthClearValue: 1,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
seedPass.setPipeline(seed);
|
|
158
|
+
seedPass.draw(3);
|
|
159
|
+
seedPass.end();
|
|
160
|
+
const pass = encoder.beginRenderPass({
|
|
161
|
+
colorAttachments: [{ view: raw.createView(), loadOp: 'clear', storeOp: 'store' }],
|
|
162
|
+
});
|
|
163
|
+
pass.setPipeline(calc);
|
|
164
|
+
pass.setBindGroup(0, bindings);
|
|
165
|
+
pass.draw(3);
|
|
166
|
+
pass.end();
|
|
167
|
+
encoder.copyTextureToBuffer({ texture: raw }, { buffer: readback, bytesPerRow: 256 }, [
|
|
168
|
+
aoSize,
|
|
169
|
+
aoSize,
|
|
170
|
+
]);
|
|
171
|
+
device.queue.submit([encoder.finish()]);
|
|
172
|
+
await readback.mapAsync(GPU_BUFFER_USAGE_MAP_READ);
|
|
173
|
+
const values = new Uint8Array(readback.getMappedRange());
|
|
174
|
+
const mean = (x0: number, x1: number) => {
|
|
175
|
+
let sum = 0;
|
|
176
|
+
for (let y = 8; y < aoSize - 8; y++)
|
|
177
|
+
for (let x = x0; x < x1; x++) sum += 1 - (values[y * 256 + x] ?? NaN) / 255;
|
|
178
|
+
return sum / ((aoSize - 16) * (x1 - x0));
|
|
179
|
+
};
|
|
180
|
+
expect(errors).toEqual([]);
|
|
181
|
+
if (scene === 'contact') {
|
|
182
|
+
expect(
|
|
183
|
+
mean(aoSize / 2 - 4, aoSize / 2),
|
|
184
|
+
'nearby foreground occludes the contact band',
|
|
185
|
+
).toBeGreaterThan(0.02);
|
|
186
|
+
expect(mean(8, 16), 'open surface away from contact stays clear').toBeLessThan(0.01);
|
|
187
|
+
} else {
|
|
188
|
+
if (scene === 'distant-edge') expect(mean(aoSize / 2 - 4, aoSize / 2)).toBeLessThan(0.005);
|
|
189
|
+
expect(
|
|
190
|
+
mean(8, aoSize - 8),
|
|
191
|
+
'unoccluded surface and distant silhouettes stay clear',
|
|
192
|
+
).toBeLessThan(0.01);
|
|
193
|
+
}
|
|
194
|
+
readback.unmap();
|
|
195
|
+
const blurLayout = device.createBindGroupLayout({
|
|
196
|
+
entries: [
|
|
197
|
+
{ binding: 0, visibility: 2, buffer: { type: 'uniform' } },
|
|
198
|
+
{ binding: 4, visibility: 2, texture: { sampleType: 'unfilterable-float' } },
|
|
199
|
+
{ binding: 5, visibility: 2, texture: { sampleType: 'depth' } },
|
|
200
|
+
{ binding: 6, visibility: 2, sampler: { type: 'non-filtering' } },
|
|
201
|
+
{ binding: 7, visibility: 2, texture: { sampleType: 'unfilterable-float' } },
|
|
202
|
+
],
|
|
203
|
+
});
|
|
204
|
+
const blurPipeline = device.createRenderPipeline({
|
|
205
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [blurLayout] }),
|
|
206
|
+
vertex: { module, entryPoint: 'vs_ssao' },
|
|
207
|
+
fragment: { module, entryPoint: 'fs_ssao_blur', targets: [{ format: 'r8unorm' }] },
|
|
208
|
+
});
|
|
209
|
+
const blurGroup = device.createBindGroup({
|
|
210
|
+
layout: blurLayout,
|
|
211
|
+
entries: [
|
|
212
|
+
{ binding: 0, resource: { buffer: uniform } },
|
|
213
|
+
{ binding: 4, resource: normal.createView() },
|
|
214
|
+
{ binding: 5, resource: depth.createView() },
|
|
215
|
+
{ binding: 6, resource: device.createSampler() },
|
|
216
|
+
{ binding: 7, resource: raw.createView() },
|
|
217
|
+
],
|
|
218
|
+
});
|
|
219
|
+
const filterEncoder = device.createCommandEncoder();
|
|
220
|
+
const filter = filterEncoder.beginRenderPass({
|
|
221
|
+
colorAttachments: [{ view: blurred.createView(), loadOp: 'clear', storeOp: 'store' }],
|
|
222
|
+
});
|
|
223
|
+
filter.setPipeline(blurPipeline);
|
|
224
|
+
filter.setBindGroup(0, blurGroup);
|
|
225
|
+
filter.draw(3);
|
|
226
|
+
filter.end();
|
|
227
|
+
filterEncoder.copyTextureToBuffer(
|
|
228
|
+
{ texture: blurred },
|
|
229
|
+
{ buffer: readback, bytesPerRow: 256 },
|
|
230
|
+
[aoSize, aoSize],
|
|
231
|
+
);
|
|
232
|
+
device.queue.submit([filterEncoder.finish()]);
|
|
233
|
+
await readback.mapAsync(GPU_BUFFER_USAGE_MAP_READ);
|
|
234
|
+
const filtered = new Uint8Array(readback.getMappedRange());
|
|
235
|
+
if (scene === 'contact') {
|
|
236
|
+
let foreground = 0;
|
|
237
|
+
for (let y = 8; y < aoSize - 8; y++)
|
|
238
|
+
for (let x = aoSize / 2; x < aoSize / 2 + 3; x++)
|
|
239
|
+
foreground += 1 - (filtered[y * 256 + x] ?? NaN) / 255;
|
|
240
|
+
expect(
|
|
241
|
+
foreground / ((aoSize - 16) * 3),
|
|
242
|
+
'bilateral blur must not bleed contact AO onto the foreground',
|
|
243
|
+
).toBeLessThan(0.005);
|
|
244
|
+
}
|
|
245
|
+
expect(errors).toEqual([]);
|
|
246
|
+
readback.unmap();
|
|
247
|
+
} finally {
|
|
248
|
+
for (const resource of [depth, normal, raw, blurred, noise, uniform, kernel, readback])
|
|
249
|
+
resource.destroy();
|
|
250
|
+
device.destroy();
|
|
251
|
+
}
|
|
252
|
+
});
|