@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
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createStandardPbrArtifactReceipt } from '@forgeax/engine-shader';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
assembleStandardPbrArtifact,
|
|
5
|
+
resolveMaterialShaderArtifact,
|
|
6
|
+
resolveStandardPbrArtifact,
|
|
7
|
+
} from '../assembly/material/assembly';
|
|
4
8
|
|
|
5
9
|
const source = '@vertex fn vs_main() -> @builtin(position) vec4<f32> { return vec4(0.0); }';
|
|
6
10
|
|
|
@@ -23,6 +27,24 @@ describe('Standard PBR artifact assembly', () => {
|
|
|
23
27
|
);
|
|
24
28
|
});
|
|
25
29
|
|
|
30
|
+
it('retains shader UV aliases beyond the minimum geometry receipt after recovery', () => {
|
|
31
|
+
const receipt = createStandardPbrArtifactReceipt();
|
|
32
|
+
const wgsl = `struct VsIn { @location(2) uv: vec2<f32>, @location(12) uv7: vec2<f32>, }
|
|
33
|
+
@vertex fn vs_scene_index(input: VsIn) -> @builtin(position) vec4<f32> { return vec4(input.uv, 0., 1.); }`;
|
|
34
|
+
const artifact = resolveMaterialShaderArtifact(
|
|
35
|
+
'forgeax::default-standard-pbr',
|
|
36
|
+
{
|
|
37
|
+
findMaterialArtifact: () => ({
|
|
38
|
+
ok: true,
|
|
39
|
+
value: { source: wgsl, paramSchema: [], receipt },
|
|
40
|
+
}),
|
|
41
|
+
} as unknown as Parameters<typeof resolveMaterialShaderArtifact>[1],
|
|
42
|
+
{ address: 'scene-index' },
|
|
43
|
+
);
|
|
44
|
+
expect(artifact?.uvSetCount).toBe(8);
|
|
45
|
+
expect(artifact?.receipt).toBe(receipt);
|
|
46
|
+
});
|
|
47
|
+
|
|
26
48
|
it('adds the skin palette receipt facts for the skinned registry entry', () => {
|
|
27
49
|
const artifact = assembleStandardPbrArtifact('forgeax::default-standard-pbr-skin', source);
|
|
28
50
|
const receipt = createStandardPbrArtifactReceipt(true);
|
|
@@ -35,4 +57,219 @@ describe('Standard PBR artifact assembly', () => {
|
|
|
35
57
|
it('rejects unsupported shader identifiers without manufacturing an artifact', () => {
|
|
36
58
|
expect(assembleStandardPbrArtifact('forgeax::default-unlit', source)).toBeUndefined();
|
|
37
59
|
});
|
|
60
|
+
|
|
61
|
+
it('fails closed when a geometry-specific color fact has no matching producer receipt', () => {
|
|
62
|
+
expect(
|
|
63
|
+
assembleStandardPbrArtifact(
|
|
64
|
+
'forgeax::default-standard-pbr',
|
|
65
|
+
{ source, paramSchema: [] },
|
|
66
|
+
{ vertexColorAvailable: true },
|
|
67
|
+
),
|
|
68
|
+
).toBeUndefined();
|
|
69
|
+
expect(
|
|
70
|
+
assembleStandardPbrArtifact(
|
|
71
|
+
'forgeax::default-standard-pbr',
|
|
72
|
+
{ source, paramSchema: [], receipt: createStandardPbrArtifactReceipt() },
|
|
73
|
+
{ vertexColorAvailable: true },
|
|
74
|
+
),
|
|
75
|
+
).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('selects the published COLOR_0 ABI and keeps colored/plain identities apart', () => {
|
|
79
|
+
const plain = createStandardPbrArtifactReceipt();
|
|
80
|
+
const colored = createStandardPbrArtifactReceipt(false, true);
|
|
81
|
+
const shader = {
|
|
82
|
+
findMaterialArtifact: () => ({
|
|
83
|
+
ok: true as const,
|
|
84
|
+
value: { source: 'fallback', paramSchema: [], receipt: plain },
|
|
85
|
+
}),
|
|
86
|
+
materialShaderManifestEntries: () => [
|
|
87
|
+
{
|
|
88
|
+
identifier: 'forgeax::default-standard-pbr',
|
|
89
|
+
sourcePath: 'default-standard-pbr.wgsl',
|
|
90
|
+
composedWgsl: 'fallback',
|
|
91
|
+
paramSchema: '[]',
|
|
92
|
+
variants: [
|
|
93
|
+
{
|
|
94
|
+
definesKey: 'VERTEX_COLOR_AVAILABLE=false',
|
|
95
|
+
defines: { VERTEX_COLOR_AVAILABLE: false },
|
|
96
|
+
composedWgsl: 'plain-variant',
|
|
97
|
+
receipt: plain,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
definesKey: 'VERTEX_COLOR_AVAILABLE=true',
|
|
101
|
+
defines: { VERTEX_COLOR_AVAILABLE: true },
|
|
102
|
+
composedWgsl: 'colored-variant',
|
|
103
|
+
receipt: colored,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
} as unknown as Parameters<typeof resolveStandardPbrArtifact>[1];
|
|
109
|
+
|
|
110
|
+
const coloredArtifact = resolveStandardPbrArtifact('forgeax::default-standard-pbr', shader, {
|
|
111
|
+
vertexColorAvailable: true,
|
|
112
|
+
});
|
|
113
|
+
const plainArtifact = resolveStandardPbrArtifact('forgeax::default-standard-pbr', shader, {
|
|
114
|
+
vertexColorAvailable: false,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(coloredArtifact?.wgsl).toBe('colored-variant');
|
|
118
|
+
expect(coloredArtifact?.receipt).toEqual(colored);
|
|
119
|
+
expect(coloredArtifact?.receipt?.vertexInputs).toContainEqual({
|
|
120
|
+
semantic: 'color',
|
|
121
|
+
location: 13,
|
|
122
|
+
format: 'float32x4',
|
|
123
|
+
});
|
|
124
|
+
expect(plainArtifact?.wgsl).toBe('plain-variant');
|
|
125
|
+
expect(plainArtifact?.receipt).toEqual(plain);
|
|
126
|
+
expect(plainArtifact?.receipt?.vertexInputs).not.toContainEqual(
|
|
127
|
+
expect.objectContaining({ semantic: 'color' }),
|
|
128
|
+
);
|
|
129
|
+
expect(coloredArtifact?.receipt?.receiptIdentity).not.toBe(
|
|
130
|
+
plainArtifact?.receipt?.receiptIdentity,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const custom = resolveMaterialShaderArtifact(
|
|
134
|
+
'game::custom-surface',
|
|
135
|
+
{
|
|
136
|
+
findMaterialArtifact: () => ({
|
|
137
|
+
ok: true as const,
|
|
138
|
+
value: { source: 'custom-fallback', paramSchema: [], receipt: plain },
|
|
139
|
+
}),
|
|
140
|
+
materialShaderManifestEntries: () => [
|
|
141
|
+
{
|
|
142
|
+
identifier: 'game::custom-surface',
|
|
143
|
+
sourcePath: 'custom.wgsl',
|
|
144
|
+
composedWgsl: 'custom-fallback',
|
|
145
|
+
paramSchema: '[]',
|
|
146
|
+
variants: [
|
|
147
|
+
{
|
|
148
|
+
definesKey: 'VERTEX_COLOR_AVAILABLE=true',
|
|
149
|
+
defines: { VERTEX_COLOR_AVAILABLE: true },
|
|
150
|
+
composedWgsl: 'custom-colored',
|
|
151
|
+
receipt: colored,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
} as unknown as Parameters<typeof resolveMaterialShaderArtifact>[1],
|
|
157
|
+
{ vertexColorAvailable: true },
|
|
158
|
+
);
|
|
159
|
+
expect(custom?.wgsl).toBe('custom-colored');
|
|
160
|
+
expect(custom?.receipt).toEqual(colored);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('routes built-in Standard shadow requests through the shadow-caster artifact', () => {
|
|
164
|
+
const rigidPlain = createStandardPbrArtifactReceipt();
|
|
165
|
+
const rigidColor = createStandardPbrArtifactReceipt(false, true);
|
|
166
|
+
const skinPlain = createStandardPbrArtifactReceipt(true);
|
|
167
|
+
const shadowEntry = {
|
|
168
|
+
identifier: 'forgeax::default-shadow-caster',
|
|
169
|
+
sourcePath: 'default-shadow-caster.wgsl',
|
|
170
|
+
composedWgsl: 'shadow-primary',
|
|
171
|
+
paramSchema: '[]',
|
|
172
|
+
variants: [
|
|
173
|
+
{
|
|
174
|
+
definesKey: 'SKINNING_DISABLED=true+VERTEX_COLOR_AVAILABLE=false',
|
|
175
|
+
defines: { SKINNING_DISABLED: true, VERTEX_COLOR_AVAILABLE: false },
|
|
176
|
+
composedWgsl: 'shadow-rigid-plain-with-fs_shadow',
|
|
177
|
+
receipt: rigidPlain,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
definesKey: 'SKINNING_DISABLED=true+VERTEX_COLOR_AVAILABLE=true',
|
|
181
|
+
defines: { SKINNING_DISABLED: true, VERTEX_COLOR_AVAILABLE: true },
|
|
182
|
+
composedWgsl: 'shadow-rigid-color-with-fs_shadow',
|
|
183
|
+
receipt: rigidColor,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
definesKey: 'SKINNING_DISABLED=false+VERTEX_COLOR_AVAILABLE=false',
|
|
187
|
+
defines: { SKINNING_DISABLED: false, VERTEX_COLOR_AVAILABLE: false },
|
|
188
|
+
composedWgsl: 'shadow-skin-plain-with-fs_shadow',
|
|
189
|
+
receipt: skinPlain,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
const standardEntry = {
|
|
194
|
+
identifier: 'forgeax::default-standard-pbr',
|
|
195
|
+
sourcePath: 'default-standard-pbr.wgsl',
|
|
196
|
+
composedWgsl: 'standard-forward-without-fs_shadow',
|
|
197
|
+
paramSchema: '[]',
|
|
198
|
+
variants: [],
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
for (const entries of [
|
|
202
|
+
[standardEntry, shadowEntry],
|
|
203
|
+
[shadowEntry, standardEntry],
|
|
204
|
+
]) {
|
|
205
|
+
const shader = {
|
|
206
|
+
findMaterialArtifact: (identifier: string) => ({
|
|
207
|
+
ok: true as const,
|
|
208
|
+
value: {
|
|
209
|
+
source: identifier === 'forgeax::default-shadow-caster' ? 'shadow-primary' : 'standard',
|
|
210
|
+
paramSchema: [],
|
|
211
|
+
receipt: rigidPlain,
|
|
212
|
+
},
|
|
213
|
+
}),
|
|
214
|
+
materialShaderManifestEntries: () => entries,
|
|
215
|
+
} as unknown as Parameters<typeof resolveMaterialShaderArtifact>[1];
|
|
216
|
+
|
|
217
|
+
const rigidColorArtifact = resolveMaterialShaderArtifact(
|
|
218
|
+
'forgeax::default-standard-pbr',
|
|
219
|
+
shader,
|
|
220
|
+
{
|
|
221
|
+
address: 'scene-index',
|
|
222
|
+
deformation: 'rigid',
|
|
223
|
+
pass: 'shadow',
|
|
224
|
+
vertexColorAvailable: true,
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
const skinPlainArtifact = resolveMaterialShaderArtifact(
|
|
228
|
+
'forgeax::default-standard-pbr-skin',
|
|
229
|
+
shader,
|
|
230
|
+
{ address: 'scene-index', deformation: 'skin', pass: 'shadow' },
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
expect(rigidColorArtifact?.material).toBe('forgeax::default-shadow-caster');
|
|
234
|
+
expect(rigidColorArtifact?.fragmentEntry).toBe('fs_shadow');
|
|
235
|
+
expect(rigidColorArtifact?.wgsl).toBe('shadow-rigid-color-with-fs_shadow');
|
|
236
|
+
expect(rigidColorArtifact?.receipt).toEqual(rigidColor);
|
|
237
|
+
expect(skinPlainArtifact?.material).toBe('forgeax::default-shadow-caster');
|
|
238
|
+
expect(skinPlainArtifact?.wgsl).toBe('shadow-skin-plain-with-fs_shadow');
|
|
239
|
+
expect(skinPlainArtifact?.receipt).toEqual(skinPlain);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
it('reuses immutable publications and invalidates changed registry or manifest identities', () => {
|
|
243
|
+
const receipt = createStandardPbrArtifactReceipt();
|
|
244
|
+
let entry = { source, paramSchema: [], receipt };
|
|
245
|
+
let manifest = {
|
|
246
|
+
identifier: 'game::surface',
|
|
247
|
+
sourcePath: 'surface.wgsl',
|
|
248
|
+
composedWgsl: source,
|
|
249
|
+
paramSchema: '[]',
|
|
250
|
+
variants: [{ definesKey: '', defines: {}, composedWgsl: 'first', receipt }],
|
|
251
|
+
};
|
|
252
|
+
const registry = {
|
|
253
|
+
findMaterialArtifact: () => ({ ok: true, value: entry }),
|
|
254
|
+
materialShaderManifestEntries: () => [manifest].values(),
|
|
255
|
+
} as unknown as Parameters<typeof resolveMaterialShaderArtifact>[1];
|
|
256
|
+
const resolve = () =>
|
|
257
|
+
resolveMaterialShaderArtifact('game::surface', registry, { address: 'scene-index' });
|
|
258
|
+
const first = resolve();
|
|
259
|
+
expect(first?.wgsl).toBe('first');
|
|
260
|
+
expect(resolve()).toBe(first);
|
|
261
|
+
expect(
|
|
262
|
+
resolveMaterialShaderArtifact('game::surface', registry, { address: 'direct' })?.vertexEntry,
|
|
263
|
+
).toBe(receipt.directEntry);
|
|
264
|
+
manifest = {
|
|
265
|
+
...manifest,
|
|
266
|
+
variants: [{ definesKey: '', defines: {}, receipt, composedWgsl: 'second' }],
|
|
267
|
+
};
|
|
268
|
+
const second = resolve();
|
|
269
|
+
expect(second).not.toBe(first);
|
|
270
|
+
expect(second?.wgsl).toBe('second');
|
|
271
|
+
expect(resolve()).toBe(second);
|
|
272
|
+
entry = { ...entry };
|
|
273
|
+
expect(resolve()).not.toBe(second);
|
|
274
|
+
});
|
|
38
275
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { freezeRenderProfile, validateRenderProfile } from '../assembly/renderer-facade';
|
|
2
3
|
import { AUTO_EXPOSURE_PRESET_V1 } from '../pipeline/standard-output/auto-exposure/preset';
|
|
3
4
|
import {
|
|
4
5
|
DEFAULT_STANDARD_PROFILE,
|
|
@@ -77,3 +78,29 @@ describe('Standard profile oracle', () => {
|
|
|
77
78
|
expect(AUTO_EXPOSURE_PRESET_V1.histogramBins).toBe(256);
|
|
78
79
|
});
|
|
79
80
|
});
|
|
81
|
+
|
|
82
|
+
describe('SSAO public profile', () => {
|
|
83
|
+
it('preserves immutable AO parameters through the public profile', () => {
|
|
84
|
+
const ssao = { radius: 0.8, bias: 0.01, intensity: 1.5, quality: 'low' as const };
|
|
85
|
+
const profile = { ...DEFAULT_STANDARD_PROFILE, renderPath: 'deferred' as const, ssao };
|
|
86
|
+
expect(validateRenderProfile(profile)).toBeUndefined();
|
|
87
|
+
const frozen = freezeRenderProfile(profile);
|
|
88
|
+
ssao.radius = 2;
|
|
89
|
+
expect(frozen.ssao).toEqual({ radius: 0.8, bias: 0.01, intensity: 1.5, quality: 'low' });
|
|
90
|
+
expect(Object.isFrozen(frozen.ssao)).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it('rejects unavailable normals and malformed parameter objects', () => {
|
|
93
|
+
expect(validateRenderProfile({ ...DEFAULT_STANDARD_PROFILE, ssao: true })).toContain(
|
|
94
|
+
'deferred',
|
|
95
|
+
);
|
|
96
|
+
for (const ssao of [[], null, { quality: 'ultra' }, { radius: Number.NaN }, { other: 1 }]) {
|
|
97
|
+
expect(
|
|
98
|
+
validateRenderProfile({
|
|
99
|
+
...DEFAULT_STANDARD_PROFILE,
|
|
100
|
+
renderPath: 'deferred',
|
|
101
|
+
ssao,
|
|
102
|
+
} as unknown as StandardProfile),
|
|
103
|
+
).toBeTypeOf('string');
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -92,6 +92,11 @@ async function resolveCoverage(source: string, edge: boolean) {
|
|
|
92
92
|
sampler: { type: 'filtering' as const },
|
|
93
93
|
})),
|
|
94
94
|
{ binding: 8, visibility: GPU_SHADER_STAGE_COMPUTE, buffer: { type: 'uniform' } },
|
|
95
|
+
{
|
|
96
|
+
binding: 11,
|
|
97
|
+
visibility: GPU_SHADER_STAGE_COMPUTE,
|
|
98
|
+
texture: { sampleType: 'unfilterable-float' as const, viewDimension: '2d' as const },
|
|
99
|
+
},
|
|
95
100
|
],
|
|
96
101
|
})
|
|
97
102
|
.unwrap();
|
|
@@ -124,6 +129,7 @@ async function resolveCoverage(source: string, edge: boolean) {
|
|
|
124
129
|
{ binding: 8, resource: { kind: 'buffer', value: { buffer: params } } },
|
|
125
130
|
{ binding: 9, resource: { kind: 'textureView', value: age.view } },
|
|
126
131
|
{ binding: 10, resource: { kind: 'textureView', value: age.view } },
|
|
132
|
+
{ binding: 11, resource: { kind: 'textureView', value: currentTemporal.view } },
|
|
127
133
|
],
|
|
128
134
|
})
|
|
129
135
|
.unwrap();
|
|
@@ -522,7 +522,7 @@ async function runPair(
|
|
|
522
522
|
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
523
523
|
texture: { sampleType: 'float' as const, viewDimension: '2d' as const },
|
|
524
524
|
})),
|
|
525
|
-
...[4, 6, 10].map((binding) => ({
|
|
525
|
+
...[4, 6, 10, 11].map((binding) => ({
|
|
526
526
|
binding,
|
|
527
527
|
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
528
528
|
texture: { sampleType: 'unfilterable-float' as const, viewDimension: '2d' as const },
|
|
@@ -586,6 +586,10 @@ async function runPair(
|
|
|
586
586
|
{ binding: 8, resource: { kind: 'buffer', value: { buffer: params } } },
|
|
587
587
|
{ binding: 9, resource: { kind: 'textureView', value: historyStability.view } },
|
|
588
588
|
{ binding: 10, resource: { kind: 'textureView', value: secondaryReactivity.view } },
|
|
589
|
+
// Ordinary TAA does not allocate an output-domain coverage target.
|
|
590
|
+
// The production resolve still declares the stable binding and uses
|
|
591
|
+
// this current-temporal fallback while hasCurrentCoverage is zero.
|
|
592
|
+
{ binding: 11, resource: { kind: 'textureView', value: currentTemporal.view } },
|
|
589
593
|
],
|
|
590
594
|
})
|
|
591
595
|
.unwrap();
|
|
@@ -162,6 +162,7 @@ export async function verifyTaaStabilityFeedback(
|
|
|
162
162
|
{ binding: 8, resource: { kind: 'buffer', value: { buffer: params } } },
|
|
163
163
|
{ binding: 9, resource: { kind: 'textureView', value: state.stability[index].view } },
|
|
164
164
|
{ binding: 10, resource: { kind: 'textureView', value: secondary.view } },
|
|
165
|
+
{ binding: 11, resource: { kind: 'textureView', value: temporal.view } },
|
|
165
166
|
],
|
|
166
167
|
})
|
|
167
168
|
.unwrap();
|
|
@@ -97,8 +97,8 @@ describe('TemporalView reset matrix', () => {
|
|
|
97
97
|
expect(next.jitter).toEqual(HALTON_23_8[1]);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
it('records the
|
|
101
|
-
expect(TEMPORAL_TAAU_DEPENDENCY_STATUS).toBe('
|
|
100
|
+
it('records the renderer TAAU owner as available while volume stays deferred', () => {
|
|
101
|
+
expect(TEMPORAL_TAAU_DEPENDENCY_STATUS).toBe('available');
|
|
102
102
|
expect(VOLUME_TAAU_DEPENDENCY.status).toBe('dependency-deferred');
|
|
103
103
|
expect(VOLUME_TAAU_DEPENDENCY.siblingFeature).toBe('feat-20260831-taau-dynamic-resolution');
|
|
104
104
|
expect(VOLUME_TAAU_DEPENDENCY.candidateShas).toHaveLength(2);
|
|
@@ -39,6 +39,23 @@ describe('TemporalView v1', () => {
|
|
|
39
39
|
expect(view.jitterCancellationUv).toEqual([jitter[0] / 1280, jitter[1] / 720]);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
it('uses the fixed internal extent for TAAU jitter while preserving output dimensions', () => {
|
|
43
|
+
const view = createTemporalView({
|
|
44
|
+
antialias: 'taa',
|
|
45
|
+
width: 200,
|
|
46
|
+
height: 150,
|
|
47
|
+
internalWidth: 128,
|
|
48
|
+
internalHeight: 96,
|
|
49
|
+
frameIndex: 1,
|
|
50
|
+
});
|
|
51
|
+
const jitter = (HALTON_23_8[1] ?? [0, 0]) as readonly [number, number];
|
|
52
|
+
expect(view.input.width).toBe(200);
|
|
53
|
+
expect(view.input.height).toBe(150);
|
|
54
|
+
expect(view.input.internalWidth).toBe(128);
|
|
55
|
+
expect(view.input.internalHeight).toBe(96);
|
|
56
|
+
expect(view.jitterCancellationUv).toEqual([jitter[0] / 128, jitter[1] / 96]);
|
|
57
|
+
});
|
|
58
|
+
|
|
42
59
|
it.each([
|
|
43
60
|
['first-frame', undefined, 'first-frame'],
|
|
44
61
|
['resize', { width: 640, height: 360 }, 'resize'],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import {
|
|
3
3
|
type MaterialShaderManifestEntry,
|
|
4
|
+
selectGpuDrivenSceneIndexVariant,
|
|
4
5
|
selectHdrpPbrPrewarmVariants,
|
|
5
6
|
selectProbePrewarmVariants,
|
|
6
7
|
selectStandardPbrTransmissionPrewarmVariants,
|
|
@@ -57,6 +58,16 @@ function transmissionVariant(definesKey: string, transmission: boolean) {
|
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
function coverageTransmissionVariant(definesKey: string, transmission: boolean) {
|
|
62
|
+
const base = transmissionVariant(definesKey, transmission);
|
|
63
|
+
return {
|
|
64
|
+
...base,
|
|
65
|
+
definesKey: `COVERAGE_ONLY=true+${definesKey}`,
|
|
66
|
+
defines: { ...base.defines, COVERAGE_ONLY: true },
|
|
67
|
+
composedWgsl: `coverage-${transmission}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
function probeTransmissionVariant(definesKey: string, transmission: boolean) {
|
|
61
72
|
const base = transmissionVariant(definesKey, transmission);
|
|
62
73
|
return {
|
|
@@ -82,6 +93,18 @@ describe('Standard transmission exact-key prewarm selection', () => {
|
|
|
82
93
|
'TRANSMISSION_AVAILABLE=true',
|
|
83
94
|
);
|
|
84
95
|
});
|
|
96
|
+
|
|
97
|
+
it('keeps coverage-only variants out of the ordinary prewarm set', () => {
|
|
98
|
+
const entry = standardEntry([
|
|
99
|
+
coverageTransmissionVariant(falseKey, false),
|
|
100
|
+
transmissionVariant(falseKey, false),
|
|
101
|
+
coverageTransmissionVariant(trueKey, true),
|
|
102
|
+
transmissionVariant(trueKey, true),
|
|
103
|
+
]);
|
|
104
|
+
expect(
|
|
105
|
+
selectStandardPbrTransmissionPrewarmVariants(entry, true).map((item) => item.definesKey),
|
|
106
|
+
).toEqual([falseKey, trueKey]);
|
|
107
|
+
});
|
|
85
108
|
});
|
|
86
109
|
|
|
87
110
|
describe('HDRP PBR capability prewarm selection', () => {
|
|
@@ -170,3 +193,37 @@ describe('probe material module prewarm capability selection', () => {
|
|
|
170
193
|
expect(selected.map((candidate) => candidate.definesKey)).toEqual([goodKey]);
|
|
171
194
|
});
|
|
172
195
|
});
|
|
196
|
+
|
|
197
|
+
describe('GPU-driven scene-index prewarm capability selection', () => {
|
|
198
|
+
it('uses the non-reflection-fallback variant for the dedicated scene-index ABI', () => {
|
|
199
|
+
const makeVariant = (reflectionFallback: boolean) => {
|
|
200
|
+
const defines = {
|
|
201
|
+
CLUSTER_FORWARD_AVAILABLE: false,
|
|
202
|
+
DIRECTIONAL_PCSS_AVAILABLE: true,
|
|
203
|
+
EXTENDED_LIGHTING_AVAILABLE: true,
|
|
204
|
+
GPU_DRIVEN_SCENE_INDEX_AVAILABLE: true,
|
|
205
|
+
PROBE_BLEND_AVAILABLE: false,
|
|
206
|
+
PROJECTOR_AVAILABLE: true,
|
|
207
|
+
REFLECTION_FALLBACK_AVAILABLE: reflectionFallback,
|
|
208
|
+
STORAGE_BUFFER_AVAILABLE: true,
|
|
209
|
+
TRANSMISSION_AVAILABLE: false,
|
|
210
|
+
VERTEX_COLOR_AVAILABLE: false,
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
defines,
|
|
214
|
+
definesKey: Object.entries(defines)
|
|
215
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
216
|
+
.map(([name, value]) => `${name}=${value}`)
|
|
217
|
+
.join('+'),
|
|
218
|
+
composedWgsl: JSON.stringify(defines),
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
const selected = selectGpuDrivenSceneIndexVariant(
|
|
222
|
+
standardEntry([makeVariant(true), makeVariant(false)]),
|
|
223
|
+
true,
|
|
224
|
+
true,
|
|
225
|
+
true,
|
|
226
|
+
);
|
|
227
|
+
expect(selected?.defines.REFLECTION_FALLBACK_AVAILABLE).toBe(false);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -73,7 +73,13 @@ async function requestDevice(): Promise<RhiDevice> {
|
|
|
73
73
|
return device.value;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
async function runInjectReadback(
|
|
76
|
+
async function runInjectReadback(
|
|
77
|
+
sceneDepthClear = 1,
|
|
78
|
+
densitySource: number | 'height-gradient' | 'depth-gradient' = 255,
|
|
79
|
+
heightOffset = 0,
|
|
80
|
+
frameIndex = 0,
|
|
81
|
+
slopedShadow = false,
|
|
82
|
+
): Promise<VolumeStageReadbacks> {
|
|
77
83
|
const device = await requestDevice();
|
|
78
84
|
const moduleResult = await createShaderModule(device, {
|
|
79
85
|
code: manifestShader('fn volume_inject'),
|
|
@@ -227,22 +233,26 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
|
|
|
227
233
|
const viewData = new Float32Array(240);
|
|
228
234
|
for (const offset of [0, 28, 44, 60, 76, 92, 196, 212]) identityMatrix(viewData, offset);
|
|
229
235
|
viewData[24] = 0;
|
|
230
|
-
viewData[25] =
|
|
236
|
+
viewData[25] = heightOffset;
|
|
237
|
+
viewData[44 + 13] = heightOffset;
|
|
231
238
|
viewData[26] = -1;
|
|
232
|
-
viewData[124] = 0;
|
|
239
|
+
viewData[124] = slopedShadow ? 1 : 0;
|
|
240
|
+
viewData[108] = 10;
|
|
241
|
+
if (slopedShadow) viewData[28 + 2] = 0.5;
|
|
233
242
|
viewData[228] = 0.1;
|
|
234
243
|
viewData[229] = 10;
|
|
235
244
|
viewData[230] = 0;
|
|
236
245
|
viewData[235] = 1;
|
|
237
246
|
const paramsData = new Float32Array(32);
|
|
238
|
-
paramsData.set([-1, -1, 0.1, 0], 0);
|
|
239
|
-
paramsData.set([1, 1, 0.9, 0], 4);
|
|
247
|
+
paramsData.set([-1, heightOffset - 1, 0.1, 0], 0);
|
|
248
|
+
paramsData.set([1, heightOffset + 1, 0.9, 0], 4);
|
|
240
249
|
paramsData.set([1, 1, 1, 0], 8);
|
|
241
250
|
paramsData.set([1, 1, 1, 0], 12);
|
|
242
251
|
paramsData.set([0, 0, 0, 0], 16);
|
|
243
252
|
paramsData.set([0, -1, 0, 0], 20);
|
|
244
253
|
paramsData.set([1, 1, 1, 0], 24);
|
|
245
254
|
paramsData.set([2, 0, 0, 0], 28);
|
|
255
|
+
paramsData[23] = frameIndex;
|
|
246
256
|
const lightData = new Uint8Array(CLUSTER_LIGHT_DATA_BYTES);
|
|
247
257
|
const clusterUniformData = new Float32Array(8);
|
|
248
258
|
const clusterGrid = new Uint32Array(clusterUniformData.buffer);
|
|
@@ -261,7 +271,21 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
|
|
|
261
271
|
if (!viewWrite.ok || !paramsWrite.ok || !lightDataWrite.ok || !clusterUniformWrite.ok)
|
|
262
272
|
throw new Error('Dawn volume stage readback unavailable: uniform upload failed');
|
|
263
273
|
const densityData = new Uint8Array((BYTES_PER_PIXEL / 2) * SIZE * SIZE * SIZE);
|
|
264
|
-
|
|
274
|
+
for (let z = 0; z < SIZE; z++)
|
|
275
|
+
for (let y = 0; y < SIZE; y++)
|
|
276
|
+
for (let x = 0; x < SIZE; x++) {
|
|
277
|
+
const value =
|
|
278
|
+
densitySource === 'height-gradient'
|
|
279
|
+
? Math.round((y / (SIZE - 1)) * 255)
|
|
280
|
+
: densitySource === 'depth-gradient'
|
|
281
|
+
? Math.round((z / (SIZE - 1)) * 255)
|
|
282
|
+
: densitySource;
|
|
283
|
+
densityData.fill(
|
|
284
|
+
value,
|
|
285
|
+
((z * SIZE + y) * SIZE + x) * 4,
|
|
286
|
+
((z * SIZE + y) * SIZE + x + 1) * 4,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
265
289
|
const densityWrite = device.queue.writeTexture(
|
|
266
290
|
{ texture: densityTexture.value },
|
|
267
291
|
densityData,
|
|
@@ -438,7 +462,7 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
|
|
|
438
462
|
colorAttachments: [],
|
|
439
463
|
depthStencilAttachment: {
|
|
440
464
|
view,
|
|
441
|
-
depthClearValue: index === 0 ? sceneDepthClear : 1,
|
|
465
|
+
depthClearValue: index === 0 ? sceneDepthClear : slopedShadow ? 0.5 : 1,
|
|
442
466
|
depthLoadOp: 'clear',
|
|
443
467
|
depthStoreOp: 'store',
|
|
444
468
|
},
|
|
@@ -520,6 +544,22 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
|
|
|
520
544
|
}
|
|
521
545
|
|
|
522
546
|
describe('volumetric fog production stage readback on Dawn', () => {
|
|
547
|
+
it('keeps static shadow visibility stable across renderer frame phases', async () => {
|
|
548
|
+
const first = await runInjectReadback(1, 255, 0, 0, true);
|
|
549
|
+
const next = await runInjectReadback(1, 255, 0, 1, true);
|
|
550
|
+
expect(first.inject.red.some((value) => value === 0)).toBe(true);
|
|
551
|
+
expect(first.inject.red.some((value) => value > 0.9)).toBe(true);
|
|
552
|
+
expect(next.inject).toEqual(first.inject);
|
|
553
|
+
});
|
|
554
|
+
it('keeps a static depth-density field stable across renderer frame phases', async () => {
|
|
555
|
+
const first = await runInjectReadback(1, 'depth-gradient', 0, 0);
|
|
556
|
+
for (const frameIndex of [1, 7]) {
|
|
557
|
+
const next = await runInjectReadback(1, 'depth-gradient', 0, frameIndex);
|
|
558
|
+
expect(next.integrate.alpha).toEqual(first.integrate.alpha);
|
|
559
|
+
expect(next.integrate.red).toEqual(first.integrate.red);
|
|
560
|
+
}
|
|
561
|
+
expect(first.integrate.alpha[0]).toBeCloseTo(Math.exp(-0.4), 2);
|
|
562
|
+
});
|
|
523
563
|
it('injects raw density then resolves a 2d integrated volume', async () => {
|
|
524
564
|
const values = await runInjectReadback();
|
|
525
565
|
// biome-ignore lint/suspicious/noConsole: stage readback is machine evidence
|
|
@@ -542,12 +582,21 @@ describe('volumetric fog production stage readback on Dawn', () => {
|
|
|
542
582
|
it('clips the final ray step to the scene-depth segment', async () => {
|
|
543
583
|
const values = await runInjectReadback(0.57);
|
|
544
584
|
const transmittance = values.integrate.alpha[0];
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
// Beer-Lambert consumes it; only the final 0.47-unit segment is allowed
|
|
548
|
-
// to contribute after the scene-depth clip.
|
|
549
|
-
const sourceDensity = 2 * 1.5 ** 3 - 1;
|
|
550
|
-
expect(transmittance).toBeCloseTo(Math.exp(-0.47 * sourceDensity), 2);
|
|
585
|
+
// White encodes density one; integrate only the visible 0.47-unit segment.
|
|
586
|
+
expect(transmittance).toBeCloseTo(Math.exp(-0.47), 2);
|
|
551
587
|
expect(values.integrate.red[0]).toBeGreaterThan(0);
|
|
552
588
|
});
|
|
589
|
+
it.each([0, 64, 128])('preserves authored uniform density %s/255', async (densityByte) => {
|
|
590
|
+
const values = await runInjectReadback(0.57, densityByte);
|
|
591
|
+
expect(values.integrate.alpha[0]).toBeCloseTo(Math.exp((-0.47 * densityByte) / 255), 2);
|
|
592
|
+
if (densityByte === 0) expect(values.integrate.red.every((value) => value === 0)).toBe(true);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it.each([
|
|
596
|
+
0, 20,
|
|
597
|
+
])('maps height density through world bounds translated by %s', async (heightOffset) => {
|
|
598
|
+
const values = await runInjectReadback(0.57, 'height-gradient', heightOffset);
|
|
599
|
+
expect(values.integrate.alpha[0]).toBeCloseTo(Math.exp(-0.47), 2);
|
|
600
|
+
expect(values.integrate.alpha[(SIZE - 1) * SIZE]).toBeCloseTo(1, 3);
|
|
601
|
+
});
|
|
553
602
|
});
|
|
@@ -34,9 +34,9 @@ describe('volumetric fog RenderGraph topology', () => {
|
|
|
34
34
|
expect(composite).toContain('abs(neighbor.a - center.a) * 6.0');
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
it('
|
|
37
|
+
it('derives deterministic integration density from the authored froxel depth', () => {
|
|
38
38
|
const integrate = shaderSource('volume-integrate.wgsl');
|
|
39
|
-
expect(integrate).toContain('let ray_step_count =
|
|
39
|
+
expect(integrate).toContain('let ray_step_count = visibility_depth;');
|
|
40
40
|
expect(integrate).toContain('for (var step = 0u; step < ray_step_count; step = step + 1u)');
|
|
41
41
|
expect(integrate).toContain('let visibility_depth = max(textureNumLayers(froxel) * 4u, 1u);');
|
|
42
42
|
});
|
|
@@ -44,7 +44,7 @@ describe('volumetric fog RenderGraph topology', () => {
|
|
|
44
44
|
it('keeps the Beer-Lambert extinction input physically non-negative', () => {
|
|
45
45
|
const integrate = shaderSource('volume-integrate.wgsl');
|
|
46
46
|
expect(integrate).toContain(
|
|
47
|
-
'let density_value = max(volume_scattering_density(world_position
|
|
47
|
+
'let density_value = max(volume_scattering_density(world_position), 0.0);',
|
|
48
48
|
);
|
|
49
49
|
});
|
|
50
50
|
|
|
@@ -130,8 +130,8 @@ describe('volumetric fog RenderGraph topology', () => {
|
|
|
130
130
|
expect(shaderSource('volume-inject.wgsl')).toContain('fn ray_box_interval');
|
|
131
131
|
expect(shaderSource('volume-inject.wgsl')).toContain('view.inverseViewProj');
|
|
132
132
|
expect(shaderSource('volume-inject.wgsl')).not.toContain('textureSampleLevel(density');
|
|
133
|
-
expect(shaderSource('volume-inject.wgsl')).toContain('stratified32');
|
|
134
|
-
expect(shaderSource('volume-inject.wgsl')).toContain('dither_unorm8');
|
|
133
|
+
expect(shaderSource('volume-inject.wgsl')).not.toContain('stratified32');
|
|
134
|
+
expect(shaderSource('volume-inject.wgsl')).not.toContain('dither_unorm8');
|
|
135
135
|
expect(shaderSource('volume-integrate.wgsl')).toContain('exp(-sigma_scale');
|
|
136
136
|
expect(shaderSource('volume-integrate.wgsl')).toContain('textureSampleLevel(froxel');
|
|
137
137
|
expect(shaderSource('volume-integrate.wgsl')).toContain('textureStore(resolved');
|
|
@@ -159,8 +159,8 @@ describe('volumetric fog RenderGraph topology', () => {
|
|
|
159
159
|
const integrate = shaderSource('volume-integrate.wgsl');
|
|
160
160
|
expect(inject).toContain('(f32(logical_slice) + 0.5) / f32(logical_depth)');
|
|
161
161
|
expect(inject).not.toContain('depth_phase');
|
|
162
|
-
expect(integrate).toContain('fn stratified32');
|
|
163
|
-
expect(integrate).toContain('segment_start + segment_length *
|
|
162
|
+
expect(integrate).not.toContain('fn stratified32');
|
|
163
|
+
expect(integrate).toContain('segment_start + segment_length * 0.5');
|
|
164
164
|
});
|
|
165
165
|
|
|
166
166
|
it('keeps temporal history graph-owned and slot-selectable', () => {
|
|
@@ -2,7 +2,7 @@ import { RhiError, type ShaderModule } from '@forgeax/engine-rhi';
|
|
|
2
2
|
import type { ManifestEntry } from '@forgeax/engine-types';
|
|
3
3
|
import type { AtmosphereShaderSources } from '../render-contract';
|
|
4
4
|
|
|
5
|
-
/** Admit and compile the complete analytic-atmosphere shader
|
|
5
|
+
/** Admit and compile the complete analytic-atmosphere shader programs atomically. */
|
|
6
6
|
export async function prewarmAtmosphereShaders(
|
|
7
7
|
entries: readonly ManifestEntry[],
|
|
8
8
|
compile: (label: string, source: string) => Promise<ShaderModule>,
|
|
@@ -11,6 +11,7 @@ export async function prewarmAtmosphereShaders(
|
|
|
11
11
|
const stages = [
|
|
12
12
|
['cube', 'atmosphere_cube', 'atmosphere_cubemap_vs'],
|
|
13
13
|
['background', 'atmosphere_background', 'atmosphere_background_vs'],
|
|
14
|
+
['ibl', 'atmosphere_ibl', 'atmosphere_ibl_vs'],
|
|
14
15
|
] as const;
|
|
15
16
|
const resolved = stages.map(([stage, label, entryPoint]) => ({
|
|
16
17
|
stage,
|
|
@@ -22,7 +23,7 @@ export async function prewarmAtmosphereShaders(
|
|
|
22
23
|
if (missing !== undefined) {
|
|
23
24
|
throw new RhiError({
|
|
24
25
|
code: 'shader-compile-failed',
|
|
25
|
-
expected: 'shader manifest contains
|
|
26
|
+
expected: 'shader manifest contains all analytic-atmosphere utility entries',
|
|
26
27
|
hint: `add the package-owned WGSL entry for '${missing.label}' to the engine shader manifest`,
|
|
27
28
|
});
|
|
28
29
|
}
|