@forgeax/engine-render 0.1.32 → 0.1.33
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 +379 -33
- package/dist/assembly/analytic-fog-registration.d.ts +3 -0
- package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
- package/dist/assembly/fxaa-registration.d.ts +4 -0
- package/dist/assembly/fxaa-registration.d.ts.map +1 -0
- package/dist/assembly/lod-observation.d.ts +4 -0
- package/dist/assembly/lod-observation.d.ts.map +1 -0
- package/dist/assembly/motion-blur-registration.d.ts +3 -0
- package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
- package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
- package/dist/assembly/webgpu-ready-contract.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/bloom-admission.d.ts +13 -0
- package/dist/bloom-admission.d.ts.map +1 -0
- package/dist/{chunk-DRR7D3KX.mjs → chunk-3RTCJTH4.mjs} +12 -9
- package/dist/chunk-3RTCJTH4.mjs.map +1 -0
- package/dist/{chunk-S665NJNF.mjs → chunk-LEAXOMYJ.mjs} +3 -3
- package/dist/{chunk-S665NJNF.mjs.map → chunk-LEAXOMYJ.mjs.map} +1 -1
- package/dist/{chunk-6TFBXLRJ.mjs → chunk-NB2S7ZRX.mjs} +155 -11
- package/dist/chunk-NB2S7ZRX.mjs.map +1 -0
- package/dist/{chunk-LULCA3OF.mjs → chunk-NNQL7CB3.mjs} +4 -4
- package/dist/{chunk-LULCA3OF.mjs.map → chunk-NNQL7CB3.mjs.map} +1 -1
- package/dist/{chunk-4NNZQCL3.mjs → chunk-OO3DRYK7.mjs} +66 -200
- package/dist/chunk-OO3DRYK7.mjs.map +1 -0
- package/dist/{chunk-GWCEMDVR.mjs → chunk-PLAXOOX5.mjs} +232 -7
- package/dist/chunk-PLAXOOX5.mjs.map +1 -0
- package/dist/{chunk-JRIPCMYC.mjs → chunk-VOZWDM3L.mjs} +283 -193
- package/dist/chunk-VOZWDM3L.mjs.map +1 -0
- package/dist/{chunk-JCIRYQ4V.mjs → chunk-ZDQWGQW7.mjs} +10 -4
- package/dist/chunk-ZDQWGQW7.mjs.map +1 -0
- package/dist/components/atmosphere.d.ts +2 -0
- package/dist/components/atmosphere.d.ts.map +1 -1
- package/dist/components/camera.d.ts +20 -4
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/mesh-filter.d.ts +6 -5
- package/dist/components/mesh-filter.d.ts.map +1 -1
- package/dist/components/motion-blur.d.ts +1 -0
- package/dist/components/motion-blur.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +2865 -526
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +6 -0
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/analytic-fog.d.ts +6 -0
- package/dist/environment/analytic-fog.d.ts.map +1 -0
- package/dist/errors/render.d.ts +6 -2
- 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/environment.d.ts +2 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/features/error-reporting.d.ts +13 -0
- package/dist/features/error-reporting.d.ts.map +1 -0
- package/dist/features/host.d.ts +5 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
- package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
- package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
- package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
- package/dist/features/plan.d.ts +7 -4
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +9 -5
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/render-graph-compute.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +7 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +3 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +9 -0
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +6 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/inspection-types.d.ts +43 -2
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances.d.ts +8 -1
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +6 -6
- package/dist/pipeline/standard-post.d.ts +28 -19
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +2 -2
- package/dist/record/frame-snapshot.d.ts +3 -0
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +5 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +6 -5
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +12 -28
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +5 -3
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +1 -1
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/render-contract.d.ts +24 -4
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +3 -1
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +8 -1
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +7 -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/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +7 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- package/dist/temporal/view.d.ts +6 -1
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +10 -7
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/component.d.ts +0 -1
- package/dist/volume/component.d.ts.map +1 -1
- package/dist/volume/passes.d.ts.map +1 -1
- package/dist/volume/temporal.d.ts +3 -1
- package/dist/volume/temporal.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
- package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
- package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
- package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
- package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
- package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
- package/src/__tests__/environment-selection.unit.test.ts +12 -0
- package/src/__tests__/factory-contract.integration.test.ts +331 -68
- package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
- package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +1 -1
- package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +11 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
- package/src/__tests__/gpu-scene.unit.test.ts +91 -0
- package/src/__tests__/instances-store.unit.test.ts +75 -0
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
- package/src/__tests__/motion-blur-compute.dawn.test.ts +583 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +21 -0
- package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
- package/src/__tests__/public-surface.test-d.ts +8 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +2 -0
- package/src/__tests__/render-feature-error-reporting.unit.test.ts +24 -0
- package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +31 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +7 -1
- package/src/__tests__/shader-manifest-fixture.ts +4 -4
- package/src/__tests__/skinned-shadow-caster.test.ts +2 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
- package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
- package/src/__tests__/standard-pipeline.integration.test.ts +18 -8
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
- package/src/__tests__/temporal-view.unit.test.ts +2 -1
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
- package/src/__tests__/volume-optics-oracle.unit.test.ts +3 -1
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +177 -13
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +447 -61
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +19 -3
- package/src/assembly/analytic-fog-registration.ts +14 -0
- package/src/assembly/fxaa-registration.ts +17 -0
- package/src/assembly/lod-observation.ts +12 -0
- package/src/assembly/motion-blur-registration.ts +17 -0
- package/src/assembly/renderer-facade.ts +2 -0
- package/src/assembly/temporal-post-process-prewarm.ts +40 -0
- package/src/assembly/webgpu-ready-contract.ts +8 -3
- package/src/assembly/webgpu-ready.ts +181 -245
- package/src/assembly/webgpu-renderer.ts +16 -31
- package/src/bloom-admission.ts +21 -0
- package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
- package/src/components/atmosphere.ts +4 -0
- package/src/components/camera.ts +66 -10
- package/src/components/mesh-filter.ts +11 -9
- package/src/components/motion-blur.ts +1 -0
- package/src/device/gpu-residency.ts +56 -12
- package/src/environment/analytic-fog.ts +44 -0
- package/src/environment/background.ts +2 -2
- package/src/errors/render.ts +14 -1
- package/src/extract/camera.ts +20 -7
- package/src/extract/contracts.ts +7 -0
- package/src/extract/environment.ts +4 -0
- package/src/features/error-reporting.ts +78 -0
- package/src/features/host.ts +25 -12
- package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
- package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
- package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
- package/src/features/motion-blur/motion-blur-params.ts +70 -2
- package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
- package/src/features/plan.ts +25 -8
- package/src/features/prepared-gpu-work.ts +86 -35
- package/src/features/render-graph-compute.ts +13 -5
- package/src/features/render-graph-contribution.ts +2 -0
- package/src/gpu-driven/material-artifacts.ts +2 -7
- package/src/gpu-driven/prepared-draw.ts +15 -10
- package/src/gpu-driven/production-raster.ts +83 -7
- package/src/gpu-driven/view-gpu.ts +17 -0
- package/src/gpu-scene.ts +188 -21
- package/src/index.ts +32 -0
- package/src/inspection-types.ts +60 -2
- package/src/instances.ts +80 -1
- package/src/pipeline/standard-post.ts +307 -78
- package/src/pipeline-spec.ts +5 -5
- package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
- package/src/record/__tests__/temporal-payload.unit.test.ts +2 -1
- package/src/record/frame-snapshot.ts +5 -1
- package/src/record/frame-targets.ts +10 -6
- package/src/record/frame.ts +163 -22
- package/src/record/main-pass-geometry.ts +30 -1
- package/src/record/main-pass-sprite-draws.ts +6 -1
- package/src/record/mesh-ssbo.ts +43 -6
- package/src/record/render-context.ts +12 -28
- package/src/record/shadow-pass.ts +6 -1
- package/src/record/skybox-post-pass.ts +122 -196
- package/src/record/typed-frame-graph.ts +153 -9
- package/src/recovery/render-system-candidate.ts +22 -10
- package/src/render-contract.ts +23 -4
- package/src/render-pipeline.ts +6 -1
- package/src/render-system-extract-tail.ts +16 -0
- package/src/render-system-extract.ts +45 -13
- package/src/render-system.ts +255 -120
- package/src/scene/render-scene.ts +85 -4
- package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
- package/src/temporal/frame-coordinator.ts +8 -1
- package/src/temporal/frame.ts +20 -0
- package/src/temporal/view.ts +13 -2
- package/src/typed-render-graph-primitives.ts +121 -48
- package/src/volume/component.ts +0 -1
- package/src/volume/passes.ts +8 -4
- package/src/volume/resources.ts +1 -1
- package/src/volume/temporal.ts +11 -0
- package/dist/chunk-4NNZQCL3.mjs.map +0 -1
- package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
- package/dist/chunk-DRR7D3KX.mjs.map +0 -1
- package/dist/chunk-GWCEMDVR.mjs.map +0 -1
- package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
- package/dist/chunk-JRIPCMYC.mjs.map +0 -1
|
@@ -5,11 +5,12 @@ import type { RhiDevice } from '@forgeax/engine-rhi';
|
|
|
5
5
|
import { rhi } from '@forgeax/engine-rhi-null';
|
|
6
6
|
import { ok } from '@forgeax/engine-types';
|
|
7
7
|
import { beforeAll, describe, expect, it } from 'vitest';
|
|
8
|
+
import { standardBloomAdmitted } from '../bloom-admission';
|
|
8
9
|
import { prepareStandardLighting } from '../pipeline/standard-lighting/prepare';
|
|
9
10
|
import { deriveStandardTopologyInput } from '../pipeline/standard-lighting/topology';
|
|
10
11
|
import { selectStandardClusterTransport } from '../pipeline/standard-lighting/transport';
|
|
11
12
|
import { standardPipeline } from '../pipeline/standard-pipeline';
|
|
12
|
-
import { inspectStandardBloomGraph
|
|
13
|
+
import { inspectStandardBloomGraph } from '../pipeline/standard-post';
|
|
13
14
|
import { DEFAULT_STANDARD_PROFILE, STANDARD_PIPELINE_ID } from '../pipeline/standard-profile';
|
|
14
15
|
import type { RenderPipelineFrame, RenderPipelineTopology } from '../render-pipeline';
|
|
15
16
|
|
|
@@ -23,7 +24,7 @@ beforeAll(async () => {
|
|
|
23
24
|
device = result.value;
|
|
24
25
|
});
|
|
25
26
|
|
|
26
|
-
function topology(bloom: 'off' | 'on'): RenderPipelineTopology {
|
|
27
|
+
function topology(bloom: 'off' | 'on', bloomIntensity = 1): RenderPipelineTopology {
|
|
27
28
|
return {
|
|
28
29
|
pipelineId: STANDARD_PIPELINE_ID,
|
|
29
30
|
standardProfile: DEFAULT_STANDARD_PROFILE,
|
|
@@ -34,7 +35,12 @@ function topology(bloom: 'off' | 'on'): RenderPipelineTopology {
|
|
|
34
35
|
storageFormat: 'bgra8unorm',
|
|
35
36
|
viewFormat: 'bgra8unorm-srgb',
|
|
36
37
|
},
|
|
37
|
-
camera: {
|
|
38
|
+
camera: {
|
|
39
|
+
tonemap: 'aces-filmic',
|
|
40
|
+
antialias: 'fxaa',
|
|
41
|
+
bloom,
|
|
42
|
+
bloomIntensity,
|
|
43
|
+
},
|
|
38
44
|
shadow: {
|
|
39
45
|
directional: { mapSize: 64, cascadeCount: 1 },
|
|
40
46
|
spotMapSize: 64,
|
|
@@ -80,7 +86,7 @@ function clusteredStandardLighting() {
|
|
|
80
86
|
return lighting.value;
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
function inspect(bloom: 'off' | 'on') {
|
|
89
|
+
function inspect(bloom: 'off' | 'on', bloomIntensity = 1) {
|
|
84
90
|
const graph = new RenderGraphBuilder<RenderPipelineFrame>();
|
|
85
91
|
const built = standardPipeline.build(
|
|
86
92
|
{
|
|
@@ -89,7 +95,7 @@ function inspect(bloom: 'off' | 'on') {
|
|
|
89
95
|
projectGpuDriven: () => ok(undefined),
|
|
90
96
|
contributeFeatures: () => ok(undefined),
|
|
91
97
|
},
|
|
92
|
-
topology(bloom),
|
|
98
|
+
topology(bloom, bloomIntensity),
|
|
93
99
|
);
|
|
94
100
|
if (!built.ok) throw built.error;
|
|
95
101
|
const compiled = graph.compile({ device, surfaceSize: { width: 640, height: 360 } });
|
|
@@ -98,6 +104,15 @@ function inspect(bloom: 'off' | 'on') {
|
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
describe('Standard Bloom exact-zero topology', () => {
|
|
107
|
+
it.each([
|
|
108
|
+
['off', 'aces-filmic', 1, false],
|
|
109
|
+
['on', 'aces-filmic', 0, false],
|
|
110
|
+
['on', 'none', 1, false],
|
|
111
|
+
['on', 'aces-filmic', 1, true],
|
|
112
|
+
] as const)('uses one effective admission for %s/%s/%s', (bloom, tonemap, intensity, expected) => {
|
|
113
|
+
expect(standardBloomAdmitted({ bloom, tonemap, bloomIntensity: intensity })).toBe(expected);
|
|
114
|
+
});
|
|
115
|
+
|
|
101
116
|
it('Bloom off declares no Bloom targets or passes', () => {
|
|
102
117
|
const info = inspect('off');
|
|
103
118
|
const bloomPasses = info.passes.filter((pass) => pass.name.startsWith('bloom-'));
|
|
@@ -109,6 +124,10 @@ describe('Standard Bloom exact-zero topology', () => {
|
|
|
109
124
|
targetCount: 0,
|
|
110
125
|
targetBytes: 0,
|
|
111
126
|
passCount: 0,
|
|
127
|
+
levelCount: 0,
|
|
128
|
+
levelDimensions: [],
|
|
129
|
+
downsamplePassCount: 0,
|
|
130
|
+
upsamplePassCount: 0,
|
|
112
131
|
});
|
|
113
132
|
expect({
|
|
114
133
|
targetCount: bloomResources.length,
|
|
@@ -127,6 +146,15 @@ describe('Standard Bloom exact-zero topology', () => {
|
|
|
127
146
|
});
|
|
128
147
|
});
|
|
129
148
|
|
|
149
|
+
it('treats intensity=0 as exact-zero even when the Camera enum is on', () => {
|
|
150
|
+
const info = inspect('on', 0);
|
|
151
|
+
expect(info.passes.filter((pass) => pass.name.startsWith('bloom-'))).toHaveLength(0);
|
|
152
|
+
expect(info.resources.filter((resource) => resource.label.startsWith('bloom-'))).toHaveLength(
|
|
153
|
+
0,
|
|
154
|
+
);
|
|
155
|
+
expect(inspectStandardBloomGraph(info).status).toBe('empty');
|
|
156
|
+
});
|
|
157
|
+
|
|
130
158
|
it('keeps Bloom declaration behind the camera gate in both lane owners', () => {
|
|
131
159
|
const forwardSource = readFileSync(
|
|
132
160
|
new URL('../pipeline/standard-forward-lane.ts', import.meta.url),
|
|
@@ -142,8 +170,8 @@ describe('Standard Bloom exact-zero topology', () => {
|
|
|
142
170
|
);
|
|
143
171
|
expect(forwardSource).toContain('addStandardPost');
|
|
144
172
|
expect(clusteredSource).toContain('addStandardPost');
|
|
145
|
-
const gate = postSource.indexOf(
|
|
146
|
-
const declaration = postSource.indexOf('
|
|
173
|
+
const gate = postSource.indexOf('bloom: !clearOnly && standardBloomAdmitted(topology.camera)');
|
|
174
|
+
const declaration = postSource.indexOf('const bloomSpecs = standardBloomTargetSpecs(');
|
|
147
175
|
expect(gate).toBeGreaterThanOrEqual(0);
|
|
148
176
|
expect(declaration).toBeGreaterThan(gate);
|
|
149
177
|
});
|
|
@@ -153,15 +181,27 @@ describe('Standard Bloom exact-zero topology', () => {
|
|
|
153
181
|
const info = inspect('on');
|
|
154
182
|
expect(inspectStandardBloomGraph(info)).toEqual({
|
|
155
183
|
status: 'valid',
|
|
156
|
-
targetCount:
|
|
157
|
-
targetBytes:
|
|
158
|
-
passCount:
|
|
184
|
+
targetCount: 10,
|
|
185
|
+
targetBytes: 3_069_440,
|
|
186
|
+
passCount: 10,
|
|
187
|
+
levelCount: 5,
|
|
188
|
+
levelDimensions: [
|
|
189
|
+
{ width: 320, height: 180 },
|
|
190
|
+
{ width: 160, height: 90 },
|
|
191
|
+
{ width: 80, height: 45 },
|
|
192
|
+
{ width: 40, height: 23 },
|
|
193
|
+
{ width: 20, height: 12 },
|
|
194
|
+
],
|
|
195
|
+
downsamplePassCount: 5,
|
|
196
|
+
upsamplePassCount: 4,
|
|
159
197
|
});
|
|
160
|
-
expect(
|
|
198
|
+
expect(
|
|
199
|
+
info.resources.find((resource) => resource.label === 'bloom-downsample-0'),
|
|
200
|
+
).toMatchObject({
|
|
161
201
|
descriptor: {
|
|
162
202
|
kind: 'texture',
|
|
163
203
|
format: 'rgba16float',
|
|
164
|
-
size:
|
|
204
|
+
size: { width: 320, height: 180 },
|
|
165
205
|
width: 320,
|
|
166
206
|
height: 180,
|
|
167
207
|
depthOrArrayLayers: 1,
|
|
@@ -171,7 +211,18 @@ describe('Standard Bloom exact-zero topology', () => {
|
|
|
171
211
|
});
|
|
172
212
|
expect(
|
|
173
213
|
info.passes.map((pass) => pass.name).filter((name) => name.startsWith('bloom-')),
|
|
174
|
-
).toEqual([
|
|
214
|
+
).toEqual([
|
|
215
|
+
'bloom-downsample-0',
|
|
216
|
+
'bloom-downsample-1',
|
|
217
|
+
'bloom-downsample-2',
|
|
218
|
+
'bloom-downsample-3',
|
|
219
|
+
'bloom-downsample-4',
|
|
220
|
+
'bloom-upsample-3',
|
|
221
|
+
'bloom-upsample-2',
|
|
222
|
+
'bloom-upsample-1',
|
|
223
|
+
'bloom-upsample-0',
|
|
224
|
+
'bloom-composite',
|
|
225
|
+
]);
|
|
175
226
|
}
|
|
176
227
|
});
|
|
177
228
|
|
|
@@ -180,7 +231,7 @@ describe('Standard Bloom exact-zero topology', () => {
|
|
|
180
231
|
const drifted = {
|
|
181
232
|
...info,
|
|
182
233
|
resources: info.resources.map((resource) =>
|
|
183
|
-
resource.label ===
|
|
234
|
+
resource.label === 'bloom-downsample-0'
|
|
184
235
|
? {
|
|
185
236
|
...resource,
|
|
186
237
|
descriptor:
|
|
@@ -196,6 +247,10 @@ describe('Standard Bloom exact-zero topology', () => {
|
|
|
196
247
|
targetCount: 0,
|
|
197
248
|
targetBytes: 0,
|
|
198
249
|
passCount: 0,
|
|
250
|
+
levelCount: 0,
|
|
251
|
+
levelDimensions: [],
|
|
252
|
+
downsamplePassCount: 0,
|
|
253
|
+
upsamplePassCount: 0,
|
|
199
254
|
});
|
|
200
255
|
});
|
|
201
256
|
|
|
@@ -203,15 +258,17 @@ describe('Standard Bloom exact-zero topology', () => {
|
|
|
203
258
|
const info = inspect('on');
|
|
204
259
|
const drifted = {
|
|
205
260
|
...info,
|
|
206
|
-
resources: info.resources.filter(
|
|
207
|
-
(resource) => resource.label !== STANDARD_BLOOM_TARGET_SPEC.bright.label,
|
|
208
|
-
),
|
|
261
|
+
resources: info.resources.filter((resource) => resource.label !== 'bloom-downsample-0'),
|
|
209
262
|
};
|
|
210
263
|
expect(inspectStandardBloomGraph(drifted)).toEqual({
|
|
211
264
|
status: 'invalid',
|
|
212
265
|
targetCount: 0,
|
|
213
266
|
targetBytes: 0,
|
|
214
267
|
passCount: 0,
|
|
268
|
+
levelCount: 0,
|
|
269
|
+
levelDimensions: [],
|
|
270
|
+
downsamplePassCount: 0,
|
|
271
|
+
upsamplePassCount: 0,
|
|
215
272
|
});
|
|
216
273
|
});
|
|
217
274
|
|
|
@@ -219,13 +276,50 @@ describe('Standard Bloom exact-zero topology', () => {
|
|
|
219
276
|
const info = inspect('on');
|
|
220
277
|
const drifted = {
|
|
221
278
|
...info,
|
|
222
|
-
passes: info.passes.filter((pass) => pass.name !== 'bloom-
|
|
279
|
+
passes: info.passes.filter((pass) => pass.name !== 'bloom-downsample-1'),
|
|
223
280
|
};
|
|
224
281
|
expect(inspectStandardBloomGraph(drifted)).toEqual({
|
|
225
282
|
status: 'invalid',
|
|
226
283
|
targetCount: 0,
|
|
227
284
|
targetBytes: 0,
|
|
228
285
|
passCount: 0,
|
|
286
|
+
levelCount: 0,
|
|
287
|
+
levelDimensions: [],
|
|
288
|
+
downsamplePassCount: 0,
|
|
289
|
+
upsamplePassCount: 0,
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('rejects forged odd-scale extents instead of reporting inferred sizes or cost', () => {
|
|
294
|
+
const info = inspect('on');
|
|
295
|
+
const forgedLabels = new Set(['bloom-downsample-0', 'bloom-downsample-1', 'bloom-upsample-0']);
|
|
296
|
+
const forged = {
|
|
297
|
+
...info,
|
|
298
|
+
resources: info.resources.map((resource) =>
|
|
299
|
+
forgedLabels.has(resource.label) && resource.descriptor.kind === 'texture'
|
|
300
|
+
? {
|
|
301
|
+
...resource,
|
|
302
|
+
descriptor: {
|
|
303
|
+
...resource.descriptor,
|
|
304
|
+
size: { width: 4, height: 4 },
|
|
305
|
+
width: 4,
|
|
306
|
+
height: 4,
|
|
307
|
+
},
|
|
308
|
+
extent: { width: 4, height: 4, depthOrArrayLayers: 1 },
|
|
309
|
+
byteSize: 128,
|
|
310
|
+
}
|
|
311
|
+
: resource,
|
|
312
|
+
),
|
|
313
|
+
};
|
|
314
|
+
expect(inspectStandardBloomGraph(forged)).toEqual({
|
|
315
|
+
status: 'invalid',
|
|
316
|
+
targetCount: 0,
|
|
317
|
+
targetBytes: 0,
|
|
318
|
+
passCount: 0,
|
|
319
|
+
levelCount: 0,
|
|
320
|
+
levelDimensions: [],
|
|
321
|
+
downsamplePassCount: 0,
|
|
322
|
+
upsamplePassCount: 0,
|
|
229
323
|
});
|
|
230
324
|
});
|
|
231
325
|
});
|
|
@@ -33,7 +33,12 @@ function build(bloom: 'off' | 'on') {
|
|
|
33
33
|
storageFormat: 'bgra8unorm',
|
|
34
34
|
viewFormat: 'bgra8unorm-srgb',
|
|
35
35
|
},
|
|
36
|
-
camera: {
|
|
36
|
+
camera: {
|
|
37
|
+
tonemap: 'aces-filmic',
|
|
38
|
+
antialias: 'fxaa',
|
|
39
|
+
bloom,
|
|
40
|
+
bloomIntensity: 1,
|
|
41
|
+
},
|
|
37
42
|
shadow: {
|
|
38
43
|
directional: { mapSize: 64, cascadeCount: 1 },
|
|
39
44
|
spotMapSize: 64,
|
|
@@ -103,7 +108,7 @@ describe('Standard Bloom lifecycle contract', () => {
|
|
|
103
108
|
const on = bloomSummary(build('on'));
|
|
104
109
|
const off = bloomSummary(build('off'));
|
|
105
110
|
const recovered = bloomSummary(build('on'));
|
|
106
|
-
expect(on.passes).toHaveLength(
|
|
111
|
+
expect(on.passes).toHaveLength(10);
|
|
107
112
|
expect(on.resources.length).toBeGreaterThan(0);
|
|
108
113
|
expect(off).toEqual({ passes: [], resources: [] });
|
|
109
114
|
expect(recovered).toEqual(on);
|
|
@@ -113,7 +118,15 @@ describe('Standard Bloom lifecycle contract', () => {
|
|
|
113
118
|
const names = build('on').passes.map((pass) => pass.name);
|
|
114
119
|
const order = [
|
|
115
120
|
'forward',
|
|
116
|
-
'bloom-
|
|
121
|
+
'bloom-downsample-0',
|
|
122
|
+
'bloom-downsample-1',
|
|
123
|
+
'bloom-downsample-2',
|
|
124
|
+
'bloom-downsample-3',
|
|
125
|
+
'bloom-downsample-4',
|
|
126
|
+
'bloom-upsample-3',
|
|
127
|
+
'bloom-upsample-2',
|
|
128
|
+
'bloom-upsample-1',
|
|
129
|
+
'bloom-upsample-0',
|
|
117
130
|
'bloom-composite',
|
|
118
131
|
'output-transform',
|
|
119
132
|
'fxaa',
|
|
@@ -132,7 +145,7 @@ describe('Standard Bloom lifecycle contract', () => {
|
|
|
132
145
|
const transaction = new LifecycleTransaction(bloom);
|
|
133
146
|
transaction.add({
|
|
134
147
|
kind: 'texture',
|
|
135
|
-
create: () => ({ label: 'bloom-
|
|
148
|
+
create: () => ({ label: 'bloom-downsample-0' }),
|
|
136
149
|
cleanup: () => undefined,
|
|
137
150
|
});
|
|
138
151
|
const committed = await transaction.commit();
|
|
@@ -69,13 +69,33 @@ describe('DynamicResolution render component registration', () => {
|
|
|
69
69
|
shutterAngle: 120,
|
|
70
70
|
maxRadiusPixels: 12,
|
|
71
71
|
sampleCount: 6,
|
|
72
|
+
targetFps: 60,
|
|
72
73
|
});
|
|
73
74
|
expect(extractFrames([world], 0).cameras[0]?.motionBlur).toEqual({
|
|
74
75
|
shutterAngle: 120,
|
|
75
76
|
maxRadiusPixels: 12,
|
|
76
77
|
sampleCount: 6,
|
|
78
|
+
targetFps: 60,
|
|
77
79
|
});
|
|
78
80
|
|
|
79
81
|
await context.fiber.dispose();
|
|
80
82
|
});
|
|
83
|
+
|
|
84
|
+
it('routes invalid MotionBlur data through the structured extraction error', async () => {
|
|
85
|
+
const world = new World();
|
|
86
|
+
const context = await createWorldContext(world, [renderComponentsPlugin(), scenePlugin()]);
|
|
87
|
+
world
|
|
88
|
+
.spawn(
|
|
89
|
+
{ component: Transform, data: {} },
|
|
90
|
+
{ component: Camera, data: { antialias: ANTIALIAS_TAA } },
|
|
91
|
+
{ component: MotionBlur, data: { targetFps: 59.5 } },
|
|
92
|
+
)
|
|
93
|
+
.unwrap();
|
|
94
|
+
world.update(1 / 60).unwrap();
|
|
95
|
+
|
|
96
|
+
expect(() => extractCameraSnapshots(world)).toThrowError(
|
|
97
|
+
expect.objectContaining({ code: 'motion-blur-invalid-params' }),
|
|
98
|
+
);
|
|
99
|
+
await context.fiber.dispose();
|
|
100
|
+
});
|
|
81
101
|
});
|
|
@@ -20,6 +20,8 @@ const frame = selectEnvironment({
|
|
|
20
20
|
mieCoefficient: 0.005,
|
|
21
21
|
mieDirectionalG: 0.8,
|
|
22
22
|
sunAngularRadius: 0.004675,
|
|
23
|
+
circumsolarStrength: 1,
|
|
24
|
+
circumsolarWidth: 1,
|
|
23
25
|
},
|
|
24
26
|
},
|
|
25
27
|
],
|
|
@@ -48,6 +50,8 @@ describe('Environment and Fog lane graph contract', () => {
|
|
|
48
50
|
mieCoefficient: 0.006,
|
|
49
51
|
mieDirectionalG: 0.7,
|
|
50
52
|
sunAngularRadius: 0.01,
|
|
53
|
+
circumsolarStrength: 1.4,
|
|
54
|
+
circumsolarWidth: 1.6,
|
|
51
55
|
},
|
|
52
56
|
})
|
|
53
57
|
.unwrap();
|
|
@@ -70,6 +74,8 @@ describe('Environment and Fog lane graph contract', () => {
|
|
|
70
74
|
expect(extracted.environment.source.atmosphere?.mieCoefficient).toBeCloseTo(0.006);
|
|
71
75
|
expect(extracted.environment.source.atmosphere?.mieDirectionalG).toBeCloseTo(0.7);
|
|
72
76
|
expect(extracted.environment.source.atmosphere?.sunAngularRadius).toBeCloseTo(0.01);
|
|
77
|
+
expect(extracted.environment.source.atmosphere?.circumsolarStrength).toBeCloseTo(1.4);
|
|
78
|
+
expect(extracted.environment.source.atmosphere?.circumsolarWidth).toBeCloseTo(1.6);
|
|
73
79
|
const signature = JSON.parse(extracted.environment.signature) as {
|
|
74
80
|
environments: [{ atmosphere: Record<string, number> }];
|
|
75
81
|
suns: [{ direction: [number, number, number] }];
|
|
@@ -79,6 +85,8 @@ describe('Environment and Fog lane graph contract', () => {
|
|
|
79
85
|
expect(signature.environments[0].atmosphere.mieCoefficient).toBeCloseTo(0.006);
|
|
80
86
|
expect(signature.environments[0].atmosphere.mieDirectionalG).toBeCloseTo(0.7);
|
|
81
87
|
expect(signature.environments[0].atmosphere.sunAngularRadius).toBeCloseTo(0.01);
|
|
88
|
+
expect(signature.environments[0].atmosphere.circumsolarStrength).toBeCloseTo(1.4);
|
|
89
|
+
expect(signature.environments[0].atmosphere.circumsolarWidth).toBeCloseTo(1.6);
|
|
82
90
|
expect(signature.suns[0].direction).toEqual([0, 1, 0]);
|
|
83
91
|
});
|
|
84
92
|
|
|
@@ -162,6 +170,8 @@ describe('Environment and Fog lane graph contract', () => {
|
|
|
162
170
|
mieCoefficient: 0.005,
|
|
163
171
|
mieDirectionalG: 0.8,
|
|
164
172
|
sunAngularRadius: 0.004675,
|
|
173
|
+
circumsolarStrength: 1,
|
|
174
|
+
circumsolarWidth: 1,
|
|
165
175
|
},
|
|
166
176
|
})
|
|
167
177
|
.unwrap();
|
|
@@ -190,6 +200,8 @@ describe('Environment and Fog lane graph contract', () => {
|
|
|
190
200
|
mieCoefficient: 0.005,
|
|
191
201
|
mieDirectionalG: 0.8,
|
|
192
202
|
sunAngularRadius: 0.004675,
|
|
203
|
+
circumsolarStrength: 1,
|
|
204
|
+
circumsolarWidth: 1,
|
|
193
205
|
})
|
|
194
206
|
.unwrap();
|
|
195
207
|
const invalid = extractFrames([world], 0);
|
|
@@ -203,6 +215,8 @@ describe('Environment and Fog lane graph contract', () => {
|
|
|
203
215
|
mieCoefficient: 0.006,
|
|
204
216
|
mieDirectionalG: 0.7,
|
|
205
217
|
sunAngularRadius: 0.01,
|
|
218
|
+
circumsolarStrength: 1,
|
|
219
|
+
circumsolarWidth: 1,
|
|
206
220
|
})
|
|
207
221
|
.unwrap();
|
|
208
222
|
const corrected = extractFrames([world], 0);
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
EnvironmentCandidate,
|
|
8
8
|
FogCandidate,
|
|
9
9
|
} from '../extract/environment';
|
|
10
|
+
import { SunCardinalityError } from '../index';
|
|
10
11
|
|
|
11
12
|
const atmosphereParameters: AtmosphereParameters = {
|
|
12
13
|
turbidity: 2,
|
|
@@ -14,6 +15,8 @@ const atmosphereParameters: AtmosphereParameters = {
|
|
|
14
15
|
mieCoefficient: 0.005,
|
|
15
16
|
mieDirectionalG: 0.8,
|
|
16
17
|
sunAngularRadius: 0.004675,
|
|
18
|
+
circumsolarStrength: 1,
|
|
19
|
+
circumsolarWidth: 1,
|
|
17
20
|
};
|
|
18
21
|
|
|
19
22
|
const image = (entityKey: number, sourceKey: string): EnvironmentCandidate => ({
|
|
@@ -97,6 +100,7 @@ describe('Environment and Fog selection (M2)', () => {
|
|
|
97
100
|
expect(result.error.expected).toContain('sun');
|
|
98
101
|
expect(result.error.hint).toContain('sun');
|
|
99
102
|
expect(result.error.detail).toMatchObject({ field, value });
|
|
103
|
+
expect(result.error).toBeInstanceOf(SunCardinalityError);
|
|
100
104
|
});
|
|
101
105
|
|
|
102
106
|
it('rejects invalid Fog parameters at the field owner', () => {
|
|
@@ -114,6 +118,8 @@ describe('Environment and Fog selection (M2)', () => {
|
|
|
114
118
|
['mieCoefficient', { mieCoefficient: -0.001 }],
|
|
115
119
|
['mieDirectionalG', { mieDirectionalG: 1.1 }],
|
|
116
120
|
['sunAngularRadius', { sunAngularRadius: Number.POSITIVE_INFINITY }],
|
|
121
|
+
['circumsolarStrength', { circumsolarStrength: -0.001 }],
|
|
122
|
+
['circumsolarWidth', { circumsolarWidth: 0.1 }],
|
|
117
123
|
] as const)('rejects invalid Atmosphere.%s with field-owned detail', (field, override) => {
|
|
118
124
|
const result = selection(
|
|
119
125
|
[
|
|
@@ -141,6 +147,8 @@ describe('Environment and Fog selection (M2)', () => {
|
|
|
141
147
|
['mieCoefficient', 0, 100],
|
|
142
148
|
['mieDirectionalG', 0, 0.999],
|
|
143
149
|
['sunAngularRadius', 0, 1],
|
|
150
|
+
['circumsolarStrength', 0, 4],
|
|
151
|
+
['circumsolarWidth', 0.25, 4],
|
|
144
152
|
] as const)('accepts the documented %s boundaries', (field, minimum, maximum) => {
|
|
145
153
|
for (const value of [minimum, maximum]) {
|
|
146
154
|
const result = selection(
|
|
@@ -165,6 +173,8 @@ describe('Environment and Fog selection (M2)', () => {
|
|
|
165
173
|
['mieCoefficient', [-Number.MIN_VALUE, Number.POSITIVE_INFINITY]],
|
|
166
174
|
['mieDirectionalG', [-Number.MIN_VALUE, 1]],
|
|
167
175
|
['sunAngularRadius', [-Number.MIN_VALUE, Number.POSITIVE_INFINITY]],
|
|
176
|
+
['circumsolarStrength', [-Number.MIN_VALUE, 4.001]],
|
|
177
|
+
['circumsolarWidth', [0.249, 4.001]],
|
|
168
178
|
] as const)('rejects %s values outside the shared shader domain', (field, values) => {
|
|
169
179
|
for (const value of values) {
|
|
170
180
|
const result = selection(
|
|
@@ -207,6 +217,8 @@ describe('Environment and Fog selection (M2)', () => {
|
|
|
207
217
|
expect(baseline.value.source).toMatchObject({ atmosphere: atmosphereParameters });
|
|
208
218
|
expect(baseline.value.signature).not.toBe(changed.value.signature);
|
|
209
219
|
expect(baseline.value.signature).toContain('sunAngularRadius');
|
|
220
|
+
expect(baseline.value.signature).toContain('circumsolarStrength');
|
|
221
|
+
expect(baseline.value.signature).toContain('circumsolarWidth');
|
|
210
222
|
});
|
|
211
223
|
|
|
212
224
|
it('keeps Fog orthogonal and produces identical direct/clustered frame facts', () => {
|