@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
|
@@ -10,6 +10,7 @@ const material = {
|
|
|
10
10
|
baseColor: new Float32Array([1, 1, 1]),
|
|
11
11
|
metallic: 0,
|
|
12
12
|
roughness: 1,
|
|
13
|
+
materialShaderId: 'forgeax::default-standard-pbr',
|
|
13
14
|
} as MaterialSnapshot;
|
|
14
15
|
|
|
15
16
|
function snapshot(entityKey: number, x: number, resourceClass = 'plain'): RenderableSnapshot {
|
|
@@ -104,6 +104,21 @@ function drawTimingFrame(fixture: BrowserTimingFixture): {
|
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
export async function observeBrowserGpuPassTimingDisabled() {
|
|
108
|
+
const fixture = await createBrowserTimingFixture(false);
|
|
109
|
+
try {
|
|
110
|
+
const { receipt } = drawTimingFrame(fixture);
|
|
111
|
+
const requested = await fixture.renderer.observe(receipt, { include: ['timings'] });
|
|
112
|
+
const omitted = await fixture.renderer.observe(receipt, { include: ['draws'] });
|
|
113
|
+
if (!requested.ok) throw requested.error;
|
|
114
|
+
if (!omitted.ok) throw omitted.error;
|
|
115
|
+
return { requested: requested.value, omitted: omitted.value };
|
|
116
|
+
} finally {
|
|
117
|
+
await fixture.renderer.dispose();
|
|
118
|
+
fixture.canvas.remove();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
107
122
|
function adapterString(value: unknown): string | undefined {
|
|
108
123
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
109
124
|
}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
observeBrowserGpuPassTimingDisabled,
|
|
4
|
+
runBrowserGpuPassTiming,
|
|
5
|
+
} from './gpu-pass-timing-browser-runner.js';
|
|
3
6
|
|
|
4
7
|
const browserReady = typeof navigator !== 'undefined' && navigator.gpu !== undefined;
|
|
5
8
|
|
|
6
9
|
describe.skipIf(!browserReady)('GPU pass timing Browser dev-server runner', () => {
|
|
10
|
+
it('returns the standard unavailable observation when GPU timing is disabled', async () => {
|
|
11
|
+
const { requested, omitted } = await observeBrowserGpuPassTimingDisabled();
|
|
12
|
+
expect(requested.timings).toMatchObject({
|
|
13
|
+
status: 'unavailable',
|
|
14
|
+
reason: { code: 'gpu-timing-not-enabled' },
|
|
15
|
+
});
|
|
16
|
+
expect(requested.timings).not.toHaveProperty('frame');
|
|
17
|
+
expect(omitted.timings).toBeUndefined();
|
|
18
|
+
});
|
|
19
|
+
|
|
7
20
|
// The first live lavapipe renderer pair can exceed Vitest's 15s default while
|
|
8
21
|
// the shader manifest and timestamp-query path are cold; keep this bound
|
|
9
22
|
// local to the real WebGPU owner instead of weakening the global timeout.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { err, ok, type RhiDevice, RhiError } from '@forgeax/engine-rhi';
|
|
2
|
+
import { rhi } from '@forgeax/engine-rhi-null';
|
|
3
|
+
import { expect, it, vi } from 'vitest';
|
|
4
|
+
import { DeviceScope } from '../device/device-scope';
|
|
5
|
+
import { GpuBuffer, GpuTexture } from '../gpu-resource';
|
|
6
|
+
|
|
7
|
+
it.each([
|
|
8
|
+
'buffer',
|
|
9
|
+
'texture',
|
|
10
|
+
] as const)('keeps %s ownership on failed destruction and releases it after successful retry', async (kind) => {
|
|
11
|
+
const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
|
|
12
|
+
const scope = DeviceScope.create(1, 'gpu-resource');
|
|
13
|
+
const method = kind === 'buffer' ? 'destroyBuffer' : 'destroyTexture';
|
|
14
|
+
const original = device[method].bind(device);
|
|
15
|
+
const spy = vi
|
|
16
|
+
.spyOn(device, method)
|
|
17
|
+
.mockReturnValueOnce(
|
|
18
|
+
err(
|
|
19
|
+
new RhiError({ code: 'webgpu-runtime-error', expected: 'injected failure', hint: 'retry' }),
|
|
20
|
+
),
|
|
21
|
+
);
|
|
22
|
+
const resource =
|
|
23
|
+
kind === 'buffer'
|
|
24
|
+
? new GpuBuffer(device, device.createBuffer({ size: 64, usage: 8 }).unwrap(), scope)
|
|
25
|
+
: new GpuTexture(
|
|
26
|
+
device,
|
|
27
|
+
device
|
|
28
|
+
.createTexture({
|
|
29
|
+
size: [1, 1, 1],
|
|
30
|
+
format: 'rgba8unorm',
|
|
31
|
+
usage: 4,
|
|
32
|
+
textureBindingViewDimension: '2d',
|
|
33
|
+
})
|
|
34
|
+
.unwrap(),
|
|
35
|
+
scope,
|
|
36
|
+
);
|
|
37
|
+
expect(scope.resourceDelta()).toBe(1);
|
|
38
|
+
expect(resource.destroy().ok).toBe(false);
|
|
39
|
+
expect(resource.isDestroyed).toBe(false);
|
|
40
|
+
expect(scope.resourceDelta()).toBe(1);
|
|
41
|
+
spy.mockImplementation(original as RhiDevice[typeof method]);
|
|
42
|
+
expect(resource.destroy().ok).toBe(true);
|
|
43
|
+
expect(resource.isDestroyed).toBe(true);
|
|
44
|
+
expect(scope.resourceDelta()).toBe(0);
|
|
45
|
+
scope.retire();
|
|
46
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
47
|
+
expect(resource.destroy().ok).toBe(false);
|
|
48
|
+
expect(scope.resourceDelta()).toBe(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('cleans remaining resources in reverse order after an earlier resource was released', async () => {
|
|
52
|
+
const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
|
|
53
|
+
const scope = DeviceScope.create(1, 'gpu-resource-order');
|
|
54
|
+
const resources = Array.from(
|
|
55
|
+
{ length: 3 },
|
|
56
|
+
() => new GpuBuffer(device, device.createBuffer({ size: 64, usage: 8 }).unwrap(), scope),
|
|
57
|
+
);
|
|
58
|
+
const handles: unknown[] = [];
|
|
59
|
+
vi.spyOn(device, 'destroyBuffer').mockImplementation((handle) => {
|
|
60
|
+
handles.push(handle);
|
|
61
|
+
return ok(undefined);
|
|
62
|
+
});
|
|
63
|
+
const [first, second, third] = resources;
|
|
64
|
+
if (first === undefined || second === undefined || third === undefined)
|
|
65
|
+
throw new Error('missing fixture resource');
|
|
66
|
+
second.destroy().unwrap();
|
|
67
|
+
scope.retire();
|
|
68
|
+
scope.dispose();
|
|
69
|
+
expect(handles).toEqual([second.handle, third.handle, first.handle]);
|
|
70
|
+
expect(scope.resourceDelta()).toBe(0);
|
|
71
|
+
});
|
|
@@ -7,7 +7,7 @@ import { err } from '@forgeax/engine-types';
|
|
|
7
7
|
import { describe, expect, it } from 'vitest';
|
|
8
8
|
import { GpuScene } from '../gpu-scene';
|
|
9
9
|
import { GPU_SCENE_LAYOUTS, gpuSceneFieldOffset } from '../gpu-scene-schema';
|
|
10
|
-
import { packStandardPbrMaterialRow } from '../material-row';
|
|
10
|
+
import { packMaterialProgramRow, packStandardPbrMaterialRow } from '../material-row';
|
|
11
11
|
import { makeZeroCameraFallbackSnapshot } from '../record/frame-snapshot';
|
|
12
12
|
import type {
|
|
13
13
|
ExtractedFrame,
|
|
@@ -176,6 +176,60 @@ describe('GpuScene', () => {
|
|
|
176
176
|
).toBeCloseTo(0.4);
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
+
it('prefers extracted Standard PBR values over schema defaults', () => {
|
|
180
|
+
const row = packStandardPbrMaterialRow(createStandardPbrArtifactReceipt(), material);
|
|
181
|
+
const view = new DataView(row.buffer);
|
|
182
|
+
expect(
|
|
183
|
+
view.getFloat32(gpuSceneFieldOffset(GPU_SCENE_LAYOUTS.material, 'baseColor'), true),
|
|
184
|
+
).toBeCloseTo(0.25);
|
|
185
|
+
expect(
|
|
186
|
+
view.getFloat32(gpuSceneFieldOffset(GPU_SCENE_LAYOUTS.material, 'metallic'), true),
|
|
187
|
+
).toBeCloseTo(0.2);
|
|
188
|
+
expect(
|
|
189
|
+
view.getFloat32(gpuSceneFieldOffset(GPU_SCENE_LAYOUTS.material, 'roughness'), true),
|
|
190
|
+
).toBeCloseTo(0.8);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('packs a custom schema into the same scene row page', () => {
|
|
194
|
+
const row = packMaterialProgramRow(
|
|
195
|
+
[
|
|
196
|
+
{ name: 'tint', type: 'color' },
|
|
197
|
+
{ name: 'roughness', type: 'f32' },
|
|
198
|
+
],
|
|
199
|
+
{
|
|
200
|
+
...material,
|
|
201
|
+
materialShaderId: 'game::custom-surface',
|
|
202
|
+
materialParamSchema: [
|
|
203
|
+
{ name: 'tint', type: 'color' },
|
|
204
|
+
{ name: 'roughness', type: 'f32' },
|
|
205
|
+
],
|
|
206
|
+
paramSnapshot: { tint: [0.1, 0.2, 0.3, 1], roughness: 0.7 },
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
expect(row).toBeDefined();
|
|
210
|
+
if (row === undefined) return;
|
|
211
|
+
expect(row.byteLength).toBe(GPU_SCENE_LAYOUTS.material.stride);
|
|
212
|
+
expect(new DataView(row.buffer).getFloat32(0, true)).toBeCloseTo(0.1);
|
|
213
|
+
expect(new DataView(row.buffer).getFloat32(16, true)).toBeCloseTo(0.7);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('writes the last vector in a 512-byte page and rejects overflow', () => {
|
|
217
|
+
const schema = Array.from({ length: 32 }, (_, index) => ({
|
|
218
|
+
name: `value${index}`,
|
|
219
|
+
type: 'vec4' as const,
|
|
220
|
+
}));
|
|
221
|
+
const row = packMaterialProgramRow(schema, {
|
|
222
|
+
...material,
|
|
223
|
+
paramSnapshot: { value31: [7, 8, 9, 10] },
|
|
224
|
+
});
|
|
225
|
+
if (row === undefined) throw new Error('512-byte row missing');
|
|
226
|
+
expect(row.byteLength).toBe(512);
|
|
227
|
+
expect(Array.from(new Float32Array(row.buffer, 496, 4))).toEqual([7, 8, 9, 10]);
|
|
228
|
+
expect(
|
|
229
|
+
packMaterialProgramRow([...schema, { name: 'overflow', type: 'f32' }], material),
|
|
230
|
+
).toBeUndefined();
|
|
231
|
+
});
|
|
232
|
+
|
|
179
233
|
it('exposes GPU scene facts as exact temporal coverage', () => {
|
|
180
234
|
const coverage = classifySceneDataCoverage({
|
|
181
235
|
contributors: [{ id: 'gpu-scene', kind: 'exact' }],
|
|
@@ -149,6 +149,10 @@ describe('Materials.standard clearcoat public contract', () => {
|
|
|
149
149
|
{ slot: 2, sampler, view },
|
|
150
150
|
],
|
|
151
151
|
);
|
|
152
|
-
expect(
|
|
152
|
+
expect(
|
|
153
|
+
merged
|
|
154
|
+
.filter((entry) => entry.binding >= 26 && entry.binding < 46)
|
|
155
|
+
.map((entry) => entry.binding),
|
|
156
|
+
).toEqual([26, 27, 28, 29, 30, 31]);
|
|
153
157
|
});
|
|
154
158
|
});
|
|
@@ -2,6 +2,7 @@ import { World } from '@forgeax/engine-ecs';
|
|
|
2
2
|
import { createBoxGeometry } from '@forgeax/engine-geometry';
|
|
3
3
|
import { rhi } from '@forgeax/engine-rhi-null';
|
|
4
4
|
import { expect, it } from 'vitest';
|
|
5
|
+
import { DeviceScope } from '../device/device-scope';
|
|
5
6
|
import { GpuResidencyCache } from '../device/gpu-residency';
|
|
6
7
|
|
|
7
8
|
it('fences ordinary mesh submissions independently of candidates, including reversed completion', async () => {
|
|
@@ -15,6 +16,8 @@ it('fences ordinary mesh submissions independently of candidates, including reve
|
|
|
15
16
|
},
|
|
16
17
|
device.caps,
|
|
17
18
|
);
|
|
19
|
+
const scope = DeviceScope.create(1, 'mesh-submission');
|
|
20
|
+
store.bindDeviceScope(scope);
|
|
18
21
|
const world = new World();
|
|
19
22
|
const mesh = createBoxGeometry(1, 1, 1).unwrap();
|
|
20
23
|
const handle = world.allocSharedRef('MeshAsset', mesh);
|
|
@@ -44,10 +47,15 @@ it('fences ordinary mesh submissions independently of candidates, including reve
|
|
|
44
47
|
finishSecond();
|
|
45
48
|
await second;
|
|
46
49
|
expect(original.vertexBuffer.isDestroyed).toBe(false);
|
|
50
|
+
expect(scope.resourceDelta()).toBe(4);
|
|
47
51
|
finishFirst();
|
|
48
52
|
await first;
|
|
49
53
|
expect(original.vertexBuffer.isDestroyed).toBe(true);
|
|
54
|
+
expect(scope.resourceDelta()).toBe(2);
|
|
50
55
|
expect(store.getMeshGpuHandles(handle, world)).toBe(replacement);
|
|
51
56
|
expect(replacement.vertexBuffer.isDestroyed).toBe(false);
|
|
52
57
|
store.destroyAll();
|
|
58
|
+
scope.retire();
|
|
59
|
+
expect(replacement.vertexBuffer.isDestroyed).toBe(true);
|
|
60
|
+
expect(scope.resourceDelta()).toBe(0);
|
|
53
61
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MeshAsset } from '@forgeax/engine-types';
|
|
2
|
+
import { expect, it } from 'vitest';
|
|
3
|
+
import { morphSnapshotFor } from '../render-system-extract-tail';
|
|
4
|
+
|
|
5
|
+
it('idle morph authoring preserves the source stream; activating and clearing it changes admission', () => {
|
|
6
|
+
const mesh = {
|
|
7
|
+
vertices: new Float32Array(12),
|
|
8
|
+
morphTargets: [{ position: new Float32Array([1, 2, 3]) }],
|
|
9
|
+
} satisfies Pick<MeshAsset, 'vertices' | 'morphTargets'>;
|
|
10
|
+
const weights = new Float32Array([0]);
|
|
11
|
+
expect(morphSnapshotFor(mesh, weights)).toBeUndefined();
|
|
12
|
+
weights[0] = 0.25;
|
|
13
|
+
expect(morphSnapshotFor(mesh, weights)).toMatchObject({
|
|
14
|
+
weights: new Float32Array([0.25]),
|
|
15
|
+
targetCount: 1,
|
|
16
|
+
});
|
|
17
|
+
weights[0] = 0;
|
|
18
|
+
expect(morphSnapshotFor(mesh, weights)).toBeUndefined();
|
|
19
|
+
});
|
|
@@ -78,7 +78,7 @@ describe('Standard clearcoat root contract', () => {
|
|
|
78
78
|
// their canonical fixed slots (26, 27, 30, 31), followed by the
|
|
79
79
|
// scene-index row at binding 46. Compacting the authored root here shifts WGSL IBL bindings and
|
|
80
80
|
// causes Dawn to reject the pipeline before any pixels are submitted.
|
|
81
|
-
expect(descriptor.entries).toHaveLength(
|
|
81
|
+
expect(descriptor.entries).toHaveLength(30);
|
|
82
82
|
expect(descriptor.entries.slice(0, 15).map((entry) => entry.binding)).toEqual(
|
|
83
83
|
Array.from({ length: 15 }, (_, binding) => binding),
|
|
84
84
|
);
|
|
@@ -89,6 +89,10 @@ describe('Standard clearcoat root contract', () => {
|
|
|
89
89
|
expect(descriptor.entries.slice(24, 28).map((entry) => entry.binding)).toEqual([
|
|
90
90
|
26, 27, 30, 31,
|
|
91
91
|
]);
|
|
92
|
+
expect(descriptor.entries[29]).toMatchObject({
|
|
93
|
+
binding: 47,
|
|
94
|
+
texture: { sampleType: 'float', viewDimension: 'cube' },
|
|
95
|
+
});
|
|
92
96
|
expect(descriptor.entries[28]).toMatchObject({
|
|
93
97
|
binding: 46,
|
|
94
98
|
visibility: GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT,
|
|
@@ -118,6 +122,7 @@ describe('Standard clearcoat root contract', () => {
|
|
|
118
122
|
expect(descriptor.entries.map((entry) => entry.binding)).toEqual([
|
|
119
123
|
...Array.from({ length: 10 }, (_, binding) => binding),
|
|
120
124
|
46,
|
|
125
|
+
47,
|
|
121
126
|
]);
|
|
122
127
|
});
|
|
123
128
|
});
|
|
@@ -13,6 +13,7 @@ const expectedCodes = [
|
|
|
13
13
|
'fullscreen-input-not-found',
|
|
14
14
|
'ssao-radius-non-positive',
|
|
15
15
|
'ssao-bias-negative',
|
|
16
|
+
'ssao-parameter-invalid',
|
|
16
17
|
'params-size-mismatch',
|
|
17
18
|
'params-update-size-mismatch',
|
|
18
19
|
] as const satisfies readonly PostProcessErrorCode[];
|
|
@@ -38,9 +39,9 @@ const evidence = [
|
|
|
38
39
|
detail: { id: 'pkg::missing' },
|
|
39
40
|
}),
|
|
40
41
|
expected: 'typed fullscreen plan references a registered post-process id',
|
|
41
|
-
hint: "no post-process is registered for id 'pkg::missing'.
|
|
42
|
+
hint: "no post-process is registered for id 'pkg::missing'. For Engine-owned effects, rebuild the shader manifest with that Engine entry enabled. For custom effects, register 'pkg::missing' with the feature host ({source, reads?}), then reference it from a typed fullscreen RenderFeaturePlan.",
|
|
42
43
|
message:
|
|
43
|
-
"post-process: post-process-not-found (no post-process is registered for id 'pkg::missing'.
|
|
44
|
+
"post-process: post-process-not-found (no post-process is registered for id 'pkg::missing'. For Engine-owned effects, rebuild the shader manifest with that Engine entry enabled. For custom effects, register 'pkg::missing' with the feature host ({source, reads?}), then reference it from a typed fullscreen RenderFeaturePlan.)",
|
|
44
45
|
},
|
|
45
46
|
{
|
|
46
47
|
code: 'fullscreen-input-not-found',
|
|
@@ -100,9 +101,9 @@ const evidence = [
|
|
|
100
101
|
];
|
|
101
102
|
|
|
102
103
|
describe('PostProcessError policy ownership', () => {
|
|
103
|
-
it('preserves the
|
|
104
|
-
expect(expectedCodes).toHaveLength(
|
|
105
|
-
expect(new Set(expectedCodes).size).toBe(
|
|
104
|
+
it('preserves the closed parameter-validation vocabulary owned by this policy', () => {
|
|
105
|
+
expect(expectedCodes).toHaveLength(8);
|
|
106
|
+
expect(new Set(expectedCodes).size).toBe(8);
|
|
106
107
|
expectTypeOf<PostProcessErrorCode>().toEqualTypeOf<ExpectedCodeUnion>();
|
|
107
108
|
expectTypeOf<ExpectedCodeUnion>().toEqualTypeOf<PostProcessErrorCode>();
|
|
108
109
|
const acceptsCode = (code: PostProcessErrorCode): PostProcessErrorCode => code;
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import { createBoxGeometry } from '@forgeax/engine-geometry';
|
|
1
2
|
import { vec3 } from '@forgeax/engine-math';
|
|
2
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createMaterialProgramArtifactReceipt,
|
|
5
|
+
createStandardPbrArtifactReceipt,
|
|
6
|
+
type ParamSchemaEntry,
|
|
7
|
+
} from '@forgeax/engine-shader';
|
|
8
|
+
import type { MeshAsset } from '@forgeax/engine-types';
|
|
3
9
|
import { describe, expect, it } from 'vitest';
|
|
4
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
buildGpuDrivenDraws,
|
|
12
|
+
gpuDrivenMaterialArtifactKey,
|
|
13
|
+
resolvePreparedGpuDrivenDraw,
|
|
14
|
+
} from '../extract/gpu-driven';
|
|
5
15
|
import type { MaterialSnapshot, RenderableSnapshot } from '../render-system-extract';
|
|
6
16
|
|
|
7
17
|
const textureNames = [
|
|
@@ -52,7 +62,9 @@ function snapshot(
|
|
|
52
62
|
};
|
|
53
63
|
}
|
|
54
64
|
|
|
55
|
-
function geometry(source:
|
|
65
|
+
function geometry(source: {
|
|
66
|
+
readonly receipt: ReturnType<typeof createStandardPbrArtifactReceipt>;
|
|
67
|
+
}) {
|
|
56
68
|
return {
|
|
57
69
|
identity: 'canonical-pbr-geometry',
|
|
58
70
|
vertexInputs: source.receipt.vertexInputs,
|
|
@@ -61,8 +73,8 @@ function geometry(source: ReturnType<typeof artifact>) {
|
|
|
61
73
|
};
|
|
62
74
|
}
|
|
63
75
|
|
|
64
|
-
function artifact() {
|
|
65
|
-
const receipt = createStandardPbrArtifactReceipt();
|
|
76
|
+
function artifact(vertexColorAvailable = false) {
|
|
77
|
+
const receipt = createStandardPbrArtifactReceipt(false, vertexColorAvailable);
|
|
66
78
|
return {
|
|
67
79
|
material: 'forgeax::default-standard-pbr',
|
|
68
80
|
pass: 'forward',
|
|
@@ -75,7 +87,174 @@ function artifact() {
|
|
|
75
87
|
};
|
|
76
88
|
}
|
|
77
89
|
|
|
90
|
+
function customArtifact() {
|
|
91
|
+
const schema = [
|
|
92
|
+
{ name: 'tint', type: 'color' },
|
|
93
|
+
{ name: 'roughnessBias', type: 'f32' },
|
|
94
|
+
] as const satisfies readonly ParamSchemaEntry[];
|
|
95
|
+
const receipt = createMaterialProgramArtifactReceipt({
|
|
96
|
+
schema,
|
|
97
|
+
directEntry: 'vs_main',
|
|
98
|
+
sceneIndexEntry: 'vs_scene_index',
|
|
99
|
+
vertexInputs: [
|
|
100
|
+
{ semantic: 'position', location: 0, format: 'float32x3' },
|
|
101
|
+
{ semantic: 'normal', location: 1, format: 'float32x3' },
|
|
102
|
+
{ semantic: 'uv', location: 2, format: 'float32x2' },
|
|
103
|
+
{ semantic: 'tangent', location: 3, format: 'float32x4' },
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
material: 'game::custom-surface',
|
|
108
|
+
pass: 'forward',
|
|
109
|
+
wgsl: 'producer-composed-custom-surface',
|
|
110
|
+
layoutIdentity: receipt.reflection.layoutIdentity,
|
|
111
|
+
bindings: [],
|
|
112
|
+
deps: ['game::custom-surface'],
|
|
113
|
+
vertexInputs: receipt.vertexInputs.map((input) => ({ ...input })),
|
|
114
|
+
receipt,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
78
118
|
describe('prepared Standard PBR producer-to-record integration', () => {
|
|
119
|
+
it.each([
|
|
120
|
+
'forgeax::default-standard-pbr',
|
|
121
|
+
'physical-material::standard-clearcoat-factor-r',
|
|
122
|
+
])('distinguishes canonical roots from legacy direct-only aliases: %s', (materialShaderId) => {
|
|
123
|
+
const source = { ...material(), materialShaderId };
|
|
124
|
+
const draws = buildGpuDrivenDraws({
|
|
125
|
+
mesh: createBoxGeometry(1, 1, 1).unwrap(),
|
|
126
|
+
materials: [source],
|
|
127
|
+
fallbackMaterial: source,
|
|
128
|
+
baseSnapshot: snapshot(source),
|
|
129
|
+
getMaterialShaderArtifact: () => undefined,
|
|
130
|
+
});
|
|
131
|
+
expect(draws).toHaveLength(1);
|
|
132
|
+
expect(draws[0]?.preparationError?.code).toBe(
|
|
133
|
+
materialShaderId.startsWith('forgeax::') ? 'missing-material-receipt' : undefined,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('requires the published scene-index artifact for a custom surface', () => {
|
|
138
|
+
const source: MaterialSnapshot = {
|
|
139
|
+
...material(),
|
|
140
|
+
materialShaderId: 'game::custom-surface-direct',
|
|
141
|
+
materialProgramKeys: { forward: 'game::custom-surface-direct' },
|
|
142
|
+
materialSceneIndexProgramKeys: {
|
|
143
|
+
forward: { specializationKey: 'game::custom-surface-scene', pass: 'forward' },
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
const requested: string[] = [];
|
|
147
|
+
const draws = buildGpuDrivenDraws({
|
|
148
|
+
mesh: createBoxGeometry(1, 1, 1).unwrap(),
|
|
149
|
+
materials: [source],
|
|
150
|
+
fallbackMaterial: source,
|
|
151
|
+
baseSnapshot: snapshot(source),
|
|
152
|
+
getMaterialShaderArtifact: (key) => {
|
|
153
|
+
requested.push(key);
|
|
154
|
+
return undefined;
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
expect(requested).toEqual(['game::custom-surface-scene']);
|
|
158
|
+
expect(draws).toHaveLength(1);
|
|
159
|
+
expect(draws[0]?.preparationError?.code).toBe('missing-material-receipt');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('prepares a COLOR_0 geometry with the matching published receipt', () => {
|
|
163
|
+
const source = artifact(true);
|
|
164
|
+
const result = resolvePreparedGpuDrivenDraw({
|
|
165
|
+
snapshot: snapshot(),
|
|
166
|
+
artifact: source,
|
|
167
|
+
geometry: geometry(source),
|
|
168
|
+
generation: source.receipt.generation,
|
|
169
|
+
draw: snapshot().gpuDrivenDraws?.[0] as NonNullable<
|
|
170
|
+
RenderableSnapshot['gpuDrivenDraws']
|
|
171
|
+
>[number],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(result.ok).toBe(true);
|
|
175
|
+
if (!result.ok) return;
|
|
176
|
+
expect(result.value.receiptIdentity).toBe(source.receipt.receiptIdentity);
|
|
177
|
+
expect(result.value.vertexInputs).toContainEqual({
|
|
178
|
+
semantic: 'color',
|
|
179
|
+
location: 13,
|
|
180
|
+
format: 'float32x4',
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('selects the colored receipt while extracting a real colored mesh draw', () => {
|
|
185
|
+
const coloredSource = artifact(true);
|
|
186
|
+
const coloredMesh: MeshAsset = {
|
|
187
|
+
kind: 'mesh',
|
|
188
|
+
vertices: new Float32Array(36),
|
|
189
|
+
indices: new Uint16Array([0, 1, 2]),
|
|
190
|
+
attributes: {
|
|
191
|
+
position: new Float32Array(9),
|
|
192
|
+
normal: new Float32Array(9),
|
|
193
|
+
uv: new Float32Array(6),
|
|
194
|
+
tangent: new Float32Array(12),
|
|
195
|
+
color: new Float32Array(12),
|
|
196
|
+
},
|
|
197
|
+
submeshes: [
|
|
198
|
+
{
|
|
199
|
+
indexOffset: 0,
|
|
200
|
+
indexCount: 3,
|
|
201
|
+
vertexCount: 3,
|
|
202
|
+
materialSlot: 0,
|
|
203
|
+
topology: 'triangle-list',
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
materialSlots: [{ slotName: 'default' }],
|
|
207
|
+
};
|
|
208
|
+
const selectedColorFacts: boolean[] = [];
|
|
209
|
+
const draws = buildGpuDrivenDraws({
|
|
210
|
+
mesh: coloredMesh,
|
|
211
|
+
materials: [material()],
|
|
212
|
+
fallbackMaterial: material(),
|
|
213
|
+
baseSnapshot: snapshot(),
|
|
214
|
+
getMaterialShaderArtifact: (_materialShaderId, request) => {
|
|
215
|
+
selectedColorFacts.push(request?.vertexColorAvailable === true);
|
|
216
|
+
return coloredSource;
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(selectedColorFacts).toEqual([true]);
|
|
221
|
+
expect(draws).toHaveLength(1);
|
|
222
|
+
expect(draws[0]?.prepared?.vertexInputs).toContainEqual({
|
|
223
|
+
semantic: 'color',
|
|
224
|
+
location: 13,
|
|
225
|
+
format: 'float32x4',
|
|
226
|
+
});
|
|
227
|
+
expect(draws[0]?.preparationError).toBeUndefined();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('keeps colored and colorless receipts on distinct production identities', () => {
|
|
231
|
+
const plain = artifact(false);
|
|
232
|
+
const colored = artifact(true);
|
|
233
|
+
const plainKey = gpuDrivenMaterialArtifactKey({
|
|
234
|
+
material: plain.material,
|
|
235
|
+
deformation: 'rigid',
|
|
236
|
+
receiptIdentity: plain.receipt.receiptIdentity,
|
|
237
|
+
receiptGeneration: plain.receipt.generation,
|
|
238
|
+
});
|
|
239
|
+
const coloredKey = gpuDrivenMaterialArtifactKey({
|
|
240
|
+
material: colored.material,
|
|
241
|
+
deformation: 'rigid',
|
|
242
|
+
receiptIdentity: colored.receipt.receiptIdentity,
|
|
243
|
+
receiptGeneration: colored.receipt.generation,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(plain.receipt.vertexInputs).not.toContainEqual(
|
|
247
|
+
expect.objectContaining({ semantic: 'color', location: 13 }),
|
|
248
|
+
);
|
|
249
|
+
expect(colored.receipt.vertexInputs).toContainEqual({
|
|
250
|
+
semantic: 'color',
|
|
251
|
+
location: 13,
|
|
252
|
+
format: 'float32x4',
|
|
253
|
+
});
|
|
254
|
+
expect(plain.receipt.receiptIdentity).not.toBe(colored.receipt.receiptIdentity);
|
|
255
|
+
expect(plainKey).not.toBe(coloredKey);
|
|
256
|
+
});
|
|
257
|
+
|
|
79
258
|
it('carries one producer receipt to direct and scene-index preparation', () => {
|
|
80
259
|
const source = artifact();
|
|
81
260
|
const result = resolvePreparedGpuDrivenDraw({
|
|
@@ -224,4 +403,36 @@ describe('prepared Standard PBR producer-to-record integration', () => {
|
|
|
224
403
|
});
|
|
225
404
|
expect(result.ok).toBe(true);
|
|
226
405
|
});
|
|
406
|
+
|
|
407
|
+
it('admits a producer-backed custom material without a Standard shader name', () => {
|
|
408
|
+
const source = customArtifact();
|
|
409
|
+
const customMaterial: MaterialSnapshot = {
|
|
410
|
+
...material(),
|
|
411
|
+
materialShaderId: source.material,
|
|
412
|
+
materialParamSchema: [
|
|
413
|
+
{ name: 'tint', type: 'color' },
|
|
414
|
+
{ name: 'roughnessBias', type: 'f32' },
|
|
415
|
+
],
|
|
416
|
+
paramSnapshot: { tint: [0.8, 0.25, 0.1, 1], roughnessBias: 0.12 },
|
|
417
|
+
textureHandles: undefined,
|
|
418
|
+
samplerHandles: undefined,
|
|
419
|
+
};
|
|
420
|
+
const baseSnapshot = snapshot(customMaterial);
|
|
421
|
+
const result = resolvePreparedGpuDrivenDraw({
|
|
422
|
+
snapshot: baseSnapshot,
|
|
423
|
+
artifact: source,
|
|
424
|
+
geometry: geometry(source),
|
|
425
|
+
generation: source.receipt.generation,
|
|
426
|
+
draw: baseSnapshot.gpuDrivenDraws?.[0] as NonNullable<
|
|
427
|
+
RenderableSnapshot['gpuDrivenDraws']
|
|
428
|
+
>[number],
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
expect(result.ok).toBe(true);
|
|
432
|
+
if (!result.ok) return;
|
|
433
|
+
expect(result.value.identity.material).toBe('game::custom-surface');
|
|
434
|
+
expect(result.value.materialRow.byteLength).toBe(512);
|
|
435
|
+
expect(result.value.materialRow.fields).toEqual(['tint', 'roughnessBias']);
|
|
436
|
+
expect(result.value.sceneIndexEntry).toBe('vs_scene_index');
|
|
437
|
+
});
|
|
227
438
|
});
|
|
@@ -3,10 +3,12 @@ import type { RenderSystemInternals } from '../record/render-context';
|
|
|
3
3
|
import { ReflectionProbeRecordOwner } from '../reflection/record-owner';
|
|
4
4
|
import type { SkylightSnapshot } from '../render-system-extract';
|
|
5
5
|
|
|
6
|
-
function fixture() {
|
|
6
|
+
function fixture(captureReadback = true) {
|
|
7
7
|
const internals = {
|
|
8
8
|
device: { caps: { rgba16floatRenderable: true } },
|
|
9
9
|
deviceScope: { owner: 'test', generation: 0 },
|
|
10
|
+
assets: { catalogEpoch: 0 },
|
|
11
|
+
captureReflectionFallbackReadback: captureReadback,
|
|
10
12
|
} as unknown as RenderSystemInternals;
|
|
11
13
|
const owner = new ReflectionProbeRecordOwner(internals);
|
|
12
14
|
const stage = (frame: number, source = 1) =>
|
|
@@ -101,4 +103,69 @@ describe('fallback asynchronous publication', () => {
|
|
|
101
103
|
projectionGeneration: 1,
|
|
102
104
|
});
|
|
103
105
|
});
|
|
106
|
+
|
|
107
|
+
it('publishes changing sources after GPU completion without requesting pixel readback', async () => {
|
|
108
|
+
const { owner, stage } = fixture(false);
|
|
109
|
+
for (let frame = 1; frame <= 10; frame++) {
|
|
110
|
+
let finish!: () => void;
|
|
111
|
+
const completed = new Promise<void>((resolve) => {
|
|
112
|
+
finish = resolve;
|
|
113
|
+
});
|
|
114
|
+
// biome-ignore lint/complexity/useLiteralKeys: Exercise the real completed-output boundary.
|
|
115
|
+
const publication = owner['completeSubmission'](true, stage(frame, frame), completed, {
|
|
116
|
+
frameId: frame,
|
|
117
|
+
format: 'rgba16float',
|
|
118
|
+
size: { width: 1, height: 1 },
|
|
119
|
+
graphGeneration: 1,
|
|
120
|
+
textureIdentity: frame,
|
|
121
|
+
});
|
|
122
|
+
expect(owner.inspect().reflectionFallback.frameId).not.toBe(frame);
|
|
123
|
+
finish();
|
|
124
|
+
await publication;
|
|
125
|
+
expect(owner.inspect().reflectionFallback).toMatchObject({ state: 'active', frameId: frame });
|
|
126
|
+
expect(owner.inspect().reflectionFallbackReadback).toBeUndefined();
|
|
127
|
+
expect(owner.inspect().reflectionFallback).not.toHaveProperty('linearHdr');
|
|
128
|
+
expect(owner.inspect().reflectionFallbackInspection.failureCode).toBeUndefined();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('does not publish non-neutral source metadata without an executed output', async () => {
|
|
133
|
+
const { owner, stage } = fixture(false);
|
|
134
|
+
// biome-ignore lint/complexity/useLiteralKeys: Missing graph output must remain fail-closed.
|
|
135
|
+
await owner['completeSubmission'](true, stage(1), Promise.resolve());
|
|
136
|
+
expect(owner.inspect().reflectionFallback.state).not.toBe('active');
|
|
137
|
+
expect(owner.inspect().reflectionFallbackInspection.failureCode).toBe(
|
|
138
|
+
'reflection-fallback-output-missing',
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('requires new mapped proof after an asset catalog replacement', async () => {
|
|
143
|
+
const { owner, stage, complete, internals } = fixture();
|
|
144
|
+
await complete(stage(1), 1);
|
|
145
|
+
Object.assign(internals.assets, { catalogEpoch: 1 });
|
|
146
|
+
const replacement = stage(2);
|
|
147
|
+
// biome-ignore lint/complexity/useLiteralKeys: A changed catalog must not reuse the old source proof.
|
|
148
|
+
await owner['completeSubmission'](true, replacement, Promise.resolve());
|
|
149
|
+
expect(owner.inspect().reflectionFallback.state).not.toBe('active');
|
|
150
|
+
expect(owner.inspect().reflectionFallbackInspection.failureCode).toBe(
|
|
151
|
+
'reflection-fallback-output-missing',
|
|
152
|
+
);
|
|
153
|
+
await complete(replacement, 2);
|
|
154
|
+
expect(owner.inspect().reflectionFallback).toMatchObject({ state: 'active', frameId: 2 });
|
|
155
|
+
expect(owner.inspect().reflectionFallbackReadback?.frameId).toBe(2);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('requires fresh readback to promote a failed source-identical submission from LKG', async () => {
|
|
159
|
+
const { owner, stage, complete } = fixture();
|
|
160
|
+
await complete(stage(1), 1);
|
|
161
|
+
// biome-ignore lint/complexity/useLiteralKeys: Fail at the real submission owner boundary.
|
|
162
|
+
await owner['completeSubmission'](false, stage(2), Promise.resolve());
|
|
163
|
+
expect(owner.inspect().reflectionFallback.state).toBe('lkg');
|
|
164
|
+
const retry = stage(3);
|
|
165
|
+
// biome-ignore lint/complexity/useLiteralKeys: An old proof must not turn a failed publication active again.
|
|
166
|
+
await owner['completeSubmission'](true, retry, Promise.resolve());
|
|
167
|
+
expect(owner.inspect().reflectionFallback).toMatchObject({ state: 'lkg', frameId: 1 });
|
|
168
|
+
await complete(retry, 3);
|
|
169
|
+
expect(owner.inspect().reflectionFallback).toMatchObject({ state: 'active', frameId: 3 });
|
|
170
|
+
});
|
|
104
171
|
});
|