@forgeax/engine-shader 0.1.7 → 0.1.19
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 +34 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/ShaderRegistry.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +53 -2
- package/dist/index.mjs.map +1 -1
- package/dist/material-schemas.d.ts.map +1 -1
- package/package.json +6 -3
- package/src/ShaderRegistry.ts +50 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +86 -12
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +181 -0
- package/src/__tests__/lighting-punctual.unit.test.ts +1 -1
- package/src/__tests__/material-builtins.unit.test.ts +38 -1
- package/src/__tests__/material-contract.unit.test.ts +123 -0
- package/src/__tests__/motion-blur.unit.test.ts +44 -0
- package/src/__tests__/scene-temporal.unit.test.ts +23 -5
- package/src/__tests__/shader-manifest-transmission.unit.test.ts +75 -0
- package/src/__tests__/shader.unit.test.ts +43 -3
- package/src/__tests__/sprite-variants.unit.test.ts +19 -0
- package/src/__tests__/taa-resolve.unit.test.ts +75 -3
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +19 -0
- package/src/common.wgsl +30 -30
- package/src/default-standard-pbr-skin.wgsl +27 -9
- package/src/default-standard-pbr.wgsl +209 -28
- package/src/fxaa.wgsl +5 -6
- package/src/index.ts +3 -0
- package/src/material-schemas.ts +7 -0
- package/src/motion-blur.wgsl +77 -0
- package/src/pbr-temporal.wgsl +15 -2
- package/src/scene-temporal.wgsl +54 -0
- package/src/shadow_caster.wgsl +27 -2
- package/src/sprite-lit.wgsl +5 -4
- package/src/sprite.wgsl +5 -4
- package/src/taa-resolve.wgsl +76 -81
- package/src/tonemap.wgsl +7 -9
- package/src/unlit.wgsl +6 -5
- package/dist/__tests__/atmosphere-background.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-background.test.d.ts.map +0 -1
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts.map +0 -1
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts.map +0 -1
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts.map +0 -1
- package/dist/__tests__/atmosphere-preetham.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-preetham.test.d.ts.map +0 -1
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts.map +0 -1
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts +0 -2
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts.map +0 -1
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts +0 -2
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts.map +0 -1
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts +0 -2
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts.map +0 -1
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts +0 -2
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts.map +0 -1
- package/dist/__tests__/fog-oracle.unit.test.d.ts +0 -2
- package/dist/__tests__/fog-oracle.unit.test.d.ts.map +0 -1
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts +0 -2
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts.map +0 -1
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts +0 -2
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts.map +0 -1
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts +0 -2
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts.map +0 -1
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts +0 -2
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts.map +0 -1
- package/dist/__tests__/ibl-sampling.unit.test.d.ts +0 -2
- package/dist/__tests__/ibl-sampling.unit.test.d.ts.map +0 -1
- package/dist/__tests__/lighting-punctual.unit.test.d.ts +0 -2
- package/dist/__tests__/lighting-punctual.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts +0 -2
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-builtins.unit.test.d.ts +0 -2
- package/dist/__tests__/material-builtins.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-contract.unit.test.d.ts +0 -2
- package/dist/__tests__/material-contract.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts +0 -2
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts.map +0 -1
- package/dist/__tests__/points-lines-shader.unit.test.d.ts +0 -2
- package/dist/__tests__/points-lines-shader.unit.test.d.ts.map +0 -1
- package/dist/__tests__/public-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/public-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/scene-temporal.unit.test.d.ts +0 -2
- package/dist/__tests__/scene-temporal.unit.test.d.ts.map +0 -1
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts +0 -2
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts.map +0 -1
- package/dist/__tests__/shader.unit.test.d.ts +0 -2
- package/dist/__tests__/shader.unit.test.d.ts.map +0 -1
- package/dist/__tests__/sprite-lit-shader.test.d.ts +0 -2
- package/dist/__tests__/sprite-lit-shader.test.d.ts.map +0 -1
- package/dist/__tests__/sprite-variants.unit.test.d.ts +0 -2
- package/dist/__tests__/sprite-variants.unit.test.d.ts.map +0 -1
- package/dist/__tests__/taa-resolve.unit.test.d.ts +0 -2
- package/dist/__tests__/taa-resolve.unit.test.d.ts.map +0 -1
- package/dist/__tests__/transparent-pbr.unit.test.d.ts +0 -2
- package/dist/__tests__/transparent-pbr.unit.test.d.ts.map +0 -1
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts +0 -2
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts.map +0 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { ok, type ShaderModule } from '@forgeax/engine-rhi';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { findVariantByKey, ShaderRegistry, type ShaderRegistryDevice } from '../index.js';
|
|
4
|
+
|
|
5
|
+
type EngineShaderManifest = Awaited<
|
|
6
|
+
ReturnType<typeof import('@forgeax/engine-vite-plugin-shader').buildEngineShaderManifest>
|
|
7
|
+
>;
|
|
8
|
+
|
|
9
|
+
function dataUrl(payload: unknown): string {
|
|
10
|
+
return `data:application/json,${encodeURIComponent(JSON.stringify(payload))}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function device(): ShaderRegistryDevice {
|
|
14
|
+
return {
|
|
15
|
+
createShaderModule(): ReturnType<ShaderRegistryDevice['createShaderModule']> {
|
|
16
|
+
return ok({} as ShaderModule);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function engineManifest(): Promise<EngineShaderManifest> {
|
|
22
|
+
const { buildEngineShaderManifest } = await import('@forgeax/engine-vite-plugin-shader');
|
|
23
|
+
return buildEngineShaderManifest();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('default Standard PBR transmission manifest contract', () => {
|
|
27
|
+
it('publishes both TRANSMISSION_AVAILABLE variants from the cooked producer', {
|
|
28
|
+
timeout: 20_000,
|
|
29
|
+
}, async () => {
|
|
30
|
+
const manifest = await engineManifest();
|
|
31
|
+
const standard = manifest.materialShaders.find(
|
|
32
|
+
(entry) => entry.identifier === 'forgeax::default-standard-pbr',
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(standard).toBeDefined();
|
|
36
|
+
if (standard === undefined) return;
|
|
37
|
+
const transmissionVariants = standard.variants.filter(
|
|
38
|
+
(variant) => 'TRANSMISSION_AVAILABLE' in variant.defines,
|
|
39
|
+
);
|
|
40
|
+
expect(transmissionVariants).toHaveLength(16);
|
|
41
|
+
expect(new Set(transmissionVariants.map((variant) => variant.definesKey)).size).toBe(16);
|
|
42
|
+
expect(new Set(transmissionVariants.map((variant) => variant.composedWgsl)).size).toBe(16);
|
|
43
|
+
for (const storageBufferAvailable of [false, true]) {
|
|
44
|
+
for (const transmissionAvailable of [false, true]) {
|
|
45
|
+
const expectedKey = [
|
|
46
|
+
'CLUSTER_FORWARD_AVAILABLE=false',
|
|
47
|
+
`STORAGE_BUFFER_AVAILABLE=${storageBufferAvailable}`,
|
|
48
|
+
`TRANSMISSION_AVAILABLE=${transmissionAvailable}`,
|
|
49
|
+
'VERTEX_COLOR_AVAILABLE=false',
|
|
50
|
+
].join('+');
|
|
51
|
+
expect(transmissionVariants).toContainEqual(
|
|
52
|
+
expect.objectContaining({
|
|
53
|
+
definesKey: expectedKey,
|
|
54
|
+
defines: expect.objectContaining({
|
|
55
|
+
CLUSTER_FORWARD_AVAILABLE: false,
|
|
56
|
+
STORAGE_BUFFER_AVAILABLE: storageBufferAvailable,
|
|
57
|
+
TRANSMISSION_AVAILABLE: transmissionAvailable,
|
|
58
|
+
VERTEX_COLOR_AVAILABLE: false,
|
|
59
|
+
}),
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('omits transmission-only texture bindings from the fallback variant', {
|
|
67
|
+
timeout: 20_000,
|
|
68
|
+
}, async () => {
|
|
69
|
+
const manifest = await engineManifest();
|
|
70
|
+
const standard = manifest.materialShaders.find(
|
|
71
|
+
(entry) => entry.identifier === 'forgeax::default-standard-pbr',
|
|
72
|
+
);
|
|
73
|
+
expect(standard).toBeDefined();
|
|
74
|
+
if (standard === undefined) return;
|
|
75
|
+
|
|
76
|
+
const fallback = standard.variants.find(
|
|
77
|
+
(variant) =>
|
|
78
|
+
variant.defines.CLUSTER_FORWARD_AVAILABLE === false &&
|
|
79
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === true &&
|
|
80
|
+
variant.defines.TRANSMISSION_AVAILABLE === false &&
|
|
81
|
+
variant.defines.VERTEX_COLOR_AVAILABLE === false,
|
|
82
|
+
);
|
|
83
|
+
const transmission = standard.variants.find(
|
|
84
|
+
(variant) =>
|
|
85
|
+
variant.defines.CLUSTER_FORWARD_AVAILABLE === false &&
|
|
86
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === true &&
|
|
87
|
+
variant.defines.TRANSMISSION_AVAILABLE === true &&
|
|
88
|
+
variant.defines.VERTEX_COLOR_AVAILABLE === false,
|
|
89
|
+
);
|
|
90
|
+
expect(fallback?.composedWgsl).not.toContain('@binding(13)');
|
|
91
|
+
expect(fallback?.composedWgsl).not.toContain('@binding(24)');
|
|
92
|
+
expect(transmission?.composedWgsl).toContain('@binding(13)');
|
|
93
|
+
expect(transmission?.composedWgsl).toContain('@binding(24)');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('registers one Standard row and resolves a declared variant by exact key', {
|
|
97
|
+
timeout: 20_000,
|
|
98
|
+
}, async () => {
|
|
99
|
+
const manifest = await engineManifest();
|
|
100
|
+
const standard = manifest.materialShaders.find(
|
|
101
|
+
(entry) => entry.identifier === 'forgeax::default-standard-pbr',
|
|
102
|
+
);
|
|
103
|
+
expect(standard).toBeDefined();
|
|
104
|
+
if (standard === undefined) return;
|
|
105
|
+
|
|
106
|
+
const registry = new ShaderRegistry({
|
|
107
|
+
device: device(),
|
|
108
|
+
manifestUrl: dataUrl({ entries: [], materialShaders: [standard] }),
|
|
109
|
+
});
|
|
110
|
+
const loaded = await registry.loadManifest();
|
|
111
|
+
expect(loaded.ok).toBe(true);
|
|
112
|
+
const rows = Array.from(registry.materialShaderManifestEntries()).filter(
|
|
113
|
+
(entry) => entry.identifier === 'forgeax::default-standard-pbr',
|
|
114
|
+
);
|
|
115
|
+
expect(rows).toHaveLength(1);
|
|
116
|
+
const registered = rows[0];
|
|
117
|
+
expect(registered).toBeDefined();
|
|
118
|
+
if (registered === undefined) return;
|
|
119
|
+
|
|
120
|
+
const transmission = registered.variants.find(
|
|
121
|
+
(variant) => variant.defines.TRANSMISSION_AVAILABLE === true,
|
|
122
|
+
);
|
|
123
|
+
expect(transmission).toBeDefined();
|
|
124
|
+
if (transmission === undefined) return;
|
|
125
|
+
expect(findVariantByKey(registered, transmission.definesKey)).toBe(transmission);
|
|
126
|
+
expect(
|
|
127
|
+
findVariantByKey(registered, `${transmission.definesKey}+UNDECLARED=true`),
|
|
128
|
+
).toBeUndefined();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('rejects a manifest with a missing transmission variant before prewarm', async () => {
|
|
132
|
+
const missingVariant = {
|
|
133
|
+
identifier: 'forgeax::default-standard-pbr',
|
|
134
|
+
sourcePath: 'default-standard-pbr.wgsl',
|
|
135
|
+
composedWgsl: 'standard-default',
|
|
136
|
+
paramSchema: '[]',
|
|
137
|
+
variants: [
|
|
138
|
+
{
|
|
139
|
+
definesKey: 'TRANSMISSION_AVAILABLE=false',
|
|
140
|
+
defines: { TRANSMISSION_AVAILABLE: false },
|
|
141
|
+
composedWgsl: 'standard-no-transmission',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
const registry = new ShaderRegistry({
|
|
146
|
+
device: device(),
|
|
147
|
+
manifestUrl: dataUrl({ entries: [], materialShaders: [missingVariant] }),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const loaded = await registry.loadManifest();
|
|
151
|
+
expect(loaded.ok).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('rejects duplicate variant keys instead of allowing ambiguous prewarm', async () => {
|
|
155
|
+
const duplicateVariant = {
|
|
156
|
+
identifier: 'forgeax::default-standard-pbr',
|
|
157
|
+
sourcePath: 'default-standard-pbr.wgsl',
|
|
158
|
+
composedWgsl: 'standard-default',
|
|
159
|
+
paramSchema: '[]',
|
|
160
|
+
variants: [
|
|
161
|
+
{
|
|
162
|
+
definesKey: 'TRANSMISSION_AVAILABLE=true',
|
|
163
|
+
defines: { TRANSMISSION_AVAILABLE: true },
|
|
164
|
+
composedWgsl: 'standard-transmission-a',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
definesKey: 'TRANSMISSION_AVAILABLE=true',
|
|
168
|
+
defines: { TRANSMISSION_AVAILABLE: true },
|
|
169
|
+
composedWgsl: 'standard-transmission-b',
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
const registry = new ShaderRegistry({
|
|
174
|
+
device: device(),
|
|
175
|
+
manifestUrl: dataUrl({ entries: [], materialShaders: [duplicateVariant] }),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const loaded = await registry.loadManifest();
|
|
179
|
+
expect(loaded.ok).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -59,7 +59,7 @@ describe('direct punctual lighting shader contract', () => {
|
|
|
59
59
|
const source = await readFile(pbrShaderPath, 'utf8');
|
|
60
60
|
|
|
61
61
|
expect(source).toContain(
|
|
62
|
-
'evaluate_cluster_lights(in.ndc, in.viewZ, in.worldPos, n, v,
|
|
62
|
+
'evaluate_cluster_lights(in.ndc, in.viewZ, in.worldPos, n, v, diffuseAlbedo, metallic, a, f0)',
|
|
63
63
|
);
|
|
64
64
|
expect(source).toContain('coatAlpha, vec3<f32>(0.04)');
|
|
65
65
|
});
|
|
@@ -70,6 +70,41 @@ describe('built-in MaterialAsset sources', () => {
|
|
|
70
70
|
{ ...material, values: { ...material.values, baseColor: [0.2, 0.3, 0.4, 1] } }.values,
|
|
71
71
|
).toMatchObject({ baseColor: [0.2, 0.3, 0.4, 1] });
|
|
72
72
|
});
|
|
73
|
+
|
|
74
|
+
it.each([
|
|
75
|
+
'default-standard-pbr.wgsl',
|
|
76
|
+
'default-standard-pbr-skin.wgsl',
|
|
77
|
+
] as const)('keeps the Standard user-region ABI before Skylight in %s', (file) => {
|
|
78
|
+
const text = source(file);
|
|
79
|
+
const bindings = [
|
|
80
|
+
'baseColorSampler',
|
|
81
|
+
'baseColorTexture',
|
|
82
|
+
'metallicRoughnessSampler',
|
|
83
|
+
'metallicRoughnessTexture',
|
|
84
|
+
'normalSampler',
|
|
85
|
+
'normalTexture',
|
|
86
|
+
'specularTintSampler',
|
|
87
|
+
'specularTintTexture',
|
|
88
|
+
'emissiveSampler',
|
|
89
|
+
'emissiveTexture',
|
|
90
|
+
'occlusionSampler',
|
|
91
|
+
'occlusionTexture',
|
|
92
|
+
'transmissionSampler',
|
|
93
|
+
'transmissionTexture',
|
|
94
|
+
'thicknessSampler',
|
|
95
|
+
'thicknessTexture',
|
|
96
|
+
'irradianceMap',
|
|
97
|
+
];
|
|
98
|
+
let previous = -1;
|
|
99
|
+
for (const name of bindings) {
|
|
100
|
+
const index = text.indexOf(`var ${name}`);
|
|
101
|
+
expect(index, `${file} must declare ${name}`).toBeGreaterThan(previous);
|
|
102
|
+
previous = index;
|
|
103
|
+
}
|
|
104
|
+
expect(text).toContain('@binding(13) var transmissionSampler');
|
|
105
|
+
expect(text).toContain('@binding(16) var thicknessTexture');
|
|
106
|
+
expect(text).toContain('@binding(17) var irradianceMap');
|
|
107
|
+
});
|
|
73
108
|
});
|
|
74
109
|
|
|
75
110
|
describe('built-in and custom material derived layout matrix', () => {
|
|
@@ -77,9 +112,11 @@ describe('built-in and custom material derived layout matrix', () => {
|
|
|
77
112
|
const names = DEFAULT_STANDARD_PBR_PARAM_SCHEMA.map((entry) => entry.name);
|
|
78
113
|
expect(names).toContain('emissiveTexture');
|
|
79
114
|
expect(names).toContain('occlusionTexture');
|
|
115
|
+
expect(names).toContain('transmissionTexture');
|
|
116
|
+
expect(names).toContain('thicknessTexture');
|
|
80
117
|
expect(
|
|
81
118
|
DEFAULT_STANDARD_PBR_PARAM_SCHEMA.filter((entry) => entry.type === 'texture2d'),
|
|
82
|
-
).toHaveLength(
|
|
119
|
+
).toHaveLength(8);
|
|
83
120
|
});
|
|
84
121
|
|
|
85
122
|
it('derives a coordinate record for every texture parameter in an interleaved custom schema', () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { derive } from '@forgeax/engine-types';
|
|
3
4
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
4
5
|
import { describe, expect, it } from 'vitest';
|
|
5
6
|
import { DEFAULT_STANDARD_PBR_PARAM_SCHEMA } from '../material-schemas.js';
|
|
@@ -56,3 +57,125 @@ describe('standard PBR material alpha contract', () => {
|
|
|
56
57
|
expect(validateAlpha({ baseColor: [1, 1, 1, 0.5], alphaMode: 'MASK' })).toBe(true);
|
|
57
58
|
});
|
|
58
59
|
});
|
|
60
|
+
|
|
61
|
+
describe('standard PBR transmission material contract', () => {
|
|
62
|
+
it('declares the seven transmission and volume parameters once, separate from CPU and GPU evidence', () => {
|
|
63
|
+
const entries = DEFAULT_STANDARD_PBR_PARAM_SCHEMA.filter((entry) =>
|
|
64
|
+
[
|
|
65
|
+
'transmission',
|
|
66
|
+
'ior',
|
|
67
|
+
'thickness',
|
|
68
|
+
'attenuationColor',
|
|
69
|
+
'attenuationDistance',
|
|
70
|
+
'transmissionTexture',
|
|
71
|
+
'thicknessTexture',
|
|
72
|
+
].includes(entry.name),
|
|
73
|
+
);
|
|
74
|
+
expect(entries).toEqual([
|
|
75
|
+
{ name: 'transmission', type: 'f32', default: 0 },
|
|
76
|
+
{ name: 'ior', type: 'f32', default: 1.5 },
|
|
77
|
+
{ name: 'thickness', type: 'f32', default: 0 },
|
|
78
|
+
{ name: 'attenuationColor', type: 'vec3', colorSpace: 'linear', default: [1, 1, 1] },
|
|
79
|
+
{ name: 'attenuationDistance', type: 'f32' },
|
|
80
|
+
{ name: 'transmissionTexture', type: 'texture2d' },
|
|
81
|
+
{ name: 'thicknessTexture', type: 'texture2d' },
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('derives numeric layout and paired texture bindings from the same schema', () => {
|
|
86
|
+
const derived = derive(DEFAULT_STANDARD_PBR_PARAM_SCHEMA);
|
|
87
|
+
expect(derived.numericMembers.map((member) => member.name)).toEqual(
|
|
88
|
+
expect.arrayContaining([
|
|
89
|
+
'transmission',
|
|
90
|
+
'ior',
|
|
91
|
+
'thickness',
|
|
92
|
+
'attenuationColor',
|
|
93
|
+
'attenuationDistance',
|
|
94
|
+
]),
|
|
95
|
+
);
|
|
96
|
+
expect(derived.textureFieldNames.has('transmissionTexture')).toBe(true);
|
|
97
|
+
expect(derived.textureFieldNames.has('thicknessTexture')).toBe(true);
|
|
98
|
+
expect(derived.samplerForTexture.get('transmissionTexture')).toBe(
|
|
99
|
+
'transmissionTexture_sampler',
|
|
100
|
+
);
|
|
101
|
+
expect(derived.samplerForTexture.get('thicknessTexture')).toBe('thicknessTexture_sampler');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('keeps the seven transmission inputs in the reflected UBO and coordinate contract', {
|
|
105
|
+
timeout: 20_000,
|
|
106
|
+
}, async () => {
|
|
107
|
+
const derived = derive(DEFAULT_STANDARD_PBR_PARAM_SCHEMA);
|
|
108
|
+
const transmissionNames = [
|
|
109
|
+
'transmission',
|
|
110
|
+
'ior',
|
|
111
|
+
'thickness',
|
|
112
|
+
'attenuationColor',
|
|
113
|
+
'attenuationDistance',
|
|
114
|
+
'transmissionTexture',
|
|
115
|
+
'thicknessTexture',
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
expect(derived.numericMembers.map((member) => member.name)).toEqual(
|
|
119
|
+
expect.arrayContaining(transmissionNames.slice(0, 5)),
|
|
120
|
+
);
|
|
121
|
+
expect(derived.coordinateRecords.map((record) => record.parameter)).toEqual(
|
|
122
|
+
expect.arrayContaining(transmissionNames.slice(5)),
|
|
123
|
+
);
|
|
124
|
+
expect(derived.resourceBindings.filter((binding) => binding.binding >= 13)).toEqual([
|
|
125
|
+
{
|
|
126
|
+
name: 'transmissionTexture_sampler',
|
|
127
|
+
parameter: 'transmissionTexture',
|
|
128
|
+
kind: 'sampler',
|
|
129
|
+
binding: 13,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'transmissionTexture',
|
|
133
|
+
parameter: 'transmissionTexture',
|
|
134
|
+
kind: 'texture',
|
|
135
|
+
binding: 14,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'thicknessTexture_sampler',
|
|
139
|
+
parameter: 'thicknessTexture',
|
|
140
|
+
kind: 'sampler',
|
|
141
|
+
binding: 15,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'thicknessTexture',
|
|
145
|
+
parameter: 'thicknessTexture',
|
|
146
|
+
kind: 'texture',
|
|
147
|
+
binding: 16,
|
|
148
|
+
},
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
const { buildEngineShaderManifest } = await import('@forgeax/engine-vite-plugin-shader');
|
|
152
|
+
const manifest = await buildEngineShaderManifest();
|
|
153
|
+
const material = manifest.materialShaders.find(
|
|
154
|
+
(entry) => entry.identifier === 'forgeax::default-standard-pbr',
|
|
155
|
+
);
|
|
156
|
+
expect(material).toBeDefined();
|
|
157
|
+
if (material === undefined) return;
|
|
158
|
+
const transmissionVariant = material.variants.find(
|
|
159
|
+
(variant) =>
|
|
160
|
+
variant.defines.CLUSTER_FORWARD_AVAILABLE === false &&
|
|
161
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === true &&
|
|
162
|
+
variant.defines.TRANSMISSION_AVAILABLE === true &&
|
|
163
|
+
variant.defines.VERTEX_COLOR_AVAILABLE === false,
|
|
164
|
+
);
|
|
165
|
+
expect(transmissionVariant).toBeDefined();
|
|
166
|
+
if (transmissionVariant === undefined) return;
|
|
167
|
+
expect(derived.totalBytes).toBe(384);
|
|
168
|
+
expect(transmissionVariant.composedWgsl).toMatch(
|
|
169
|
+
/@group\(1\)\s*@binding\(13\)\s*var transmissionSampler/,
|
|
170
|
+
);
|
|
171
|
+
expect(transmissionVariant.composedWgsl).toMatch(
|
|
172
|
+
/@group\(1\)\s*@binding\(14\)\s*var transmissionTexture/,
|
|
173
|
+
);
|
|
174
|
+
expect(transmissionVariant.composedWgsl).toMatch(
|
|
175
|
+
/@group\(1\)\s*@binding\(15\)\s*var thicknessSampler/,
|
|
176
|
+
);
|
|
177
|
+
expect(transmissionVariant.composedWgsl).toMatch(
|
|
178
|
+
/@group\(1\)\s*@binding\(16\)\s*var thicknessTexture/,
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
const source = readFileSync(resolve(import.meta.dirname, '../motion-blur.wgsl'), 'utf8');
|
|
6
|
+
|
|
7
|
+
describe('motion-blur.wgsl', () => {
|
|
8
|
+
it('declares the portable raster gather contract', () => {
|
|
9
|
+
expect(source).toContain('@fragment');
|
|
10
|
+
expect(source).toContain('symmetric');
|
|
11
|
+
expect(source).toContain('maxRadiusPixels');
|
|
12
|
+
expect(source).toContain('shutterAngle');
|
|
13
|
+
expect(source).toContain('sampleCount');
|
|
14
|
+
expect(source).toContain('textureSample');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('samples scene temporal depth through the portable color path', () => {
|
|
18
|
+
expect(source).toContain('@group(1) @binding(3) var sceneTemporal : texture_2d<f32>');
|
|
19
|
+
expect(source).toContain('textureLoad(sceneTemporal, samplePixel, 0)');
|
|
20
|
+
expect(source).not.toContain('texture_depth_2d');
|
|
21
|
+
expect(source).not.toContain('sceneDepth');
|
|
22
|
+
expect(source).not.toContain('depthSampler');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('keeps depth, reactive, reset, and alpha guards in the shader owner', () => {
|
|
26
|
+
expect(source).toContain('reactive');
|
|
27
|
+
expect(source).toContain('invalidDepth');
|
|
28
|
+
expect(source).toContain('depthReject');
|
|
29
|
+
expect(source).toContain('reset');
|
|
30
|
+
expect(source).toContain('output.a');
|
|
31
|
+
expect(source).toContain('#import forgeax_scene_temporal::{unpackSceneTemporalV1}');
|
|
32
|
+
expect(source).toContain('let blurWeight = (1.0 - reactive)');
|
|
33
|
+
expect(source).toContain('for (var index = 0u; index < 16u; index += 1u)');
|
|
34
|
+
expect(source).not.toContain('positiveUv');
|
|
35
|
+
expect(source).not.toContain('negativeUv');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('does not introduce compute, storage, or temporal history writes', () => {
|
|
39
|
+
expect(source).not.toContain('@compute');
|
|
40
|
+
expect(source).not.toContain('var<storage');
|
|
41
|
+
expect(source).not.toContain('history');
|
|
42
|
+
expect(source).not.toContain('atomic');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -10,10 +10,25 @@ const pbrTemporalConsumers = [
|
|
|
10
10
|
] as const;
|
|
11
11
|
|
|
12
12
|
describe('scene temporal varyings', () => {
|
|
13
|
-
it
|
|
13
|
+
it('defines the temporal-v1 accessor ABI in one owner module', () => {
|
|
14
|
+
const source = readFileSync(resolve(import.meta.dirname, '..', 'scene-temporal.wgsl'), 'utf8');
|
|
15
|
+
expect(source).toContain('struct SceneTemporalV1');
|
|
16
|
+
expect(source).toContain('fn unpackSceneTemporalV1');
|
|
17
|
+
expect(source).toContain('motionUv = packed.xy');
|
|
18
|
+
expect(source).toContain('exp2(packed.z) - 1.0');
|
|
19
|
+
expect(source).toContain('reactive = clamp(packed.w');
|
|
20
|
+
expect(source).toContain('packed.z < 0.0');
|
|
21
|
+
expect(source).toContain('SCENE_DATA_TEMPORAL_V1_CLEAR');
|
|
22
|
+
expect(source).toContain('rgba16float');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it.each(localTemporalShaders)('%s perspective-interpolates both clip authorities', (file) => {
|
|
14
26
|
const source = readFileSync(resolve(import.meta.dirname, '..', file), 'utf8');
|
|
15
|
-
expect(source).toMatch(/@interpolate\(
|
|
16
|
-
expect(source).toMatch(/@interpolate\(
|
|
27
|
+
expect(source).toMatch(/@interpolate\(perspective\) currentClip : vec4<f32>/);
|
|
28
|
+
expect(source).toMatch(/@interpolate\(perspective\) previousClip : vec4<f32>/);
|
|
29
|
+
expect(source).not.toMatch(
|
|
30
|
+
/@interpolate\((?:linear|noperspective)\) (?:currentClip|previousClip)/,
|
|
31
|
+
);
|
|
17
32
|
});
|
|
18
33
|
|
|
19
34
|
it('keeps PBR UV selection and fragment projection in one module', () => {
|
|
@@ -25,8 +40,11 @@ describe('scene temporal varyings', () => {
|
|
|
25
40
|
it.each(pbrTemporalConsumers)('%s imports the shared PBR temporal authority', (file) => {
|
|
26
41
|
const source = readFileSync(resolve(import.meta.dirname, '..', file), 'utf8');
|
|
27
42
|
expect(source).toContain('#import forgeax_pbr::temporal::{projectPbrSceneTemporal}');
|
|
28
|
-
expect(source).toMatch(/@interpolate\(
|
|
29
|
-
expect(source).toMatch(/@interpolate\(
|
|
43
|
+
expect(source).toMatch(/@interpolate\(perspective\) currentClip : vec4<f32>/);
|
|
44
|
+
expect(source).toMatch(/@interpolate\(perspective\) previousClip : vec4<f32>/);
|
|
45
|
+
expect(source).not.toMatch(
|
|
46
|
+
/@interpolate\((?:linear|noperspective)\) (?:currentClip|previousClip)/,
|
|
47
|
+
);
|
|
30
48
|
expect(source).not.toContain('fn transformedTemporalUv');
|
|
31
49
|
});
|
|
32
50
|
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ok, type ShaderModule } from '@forgeax/engine-rhi';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { ShaderRegistry, type ShaderRegistryDevice } from '../index.js';
|
|
4
|
+
|
|
5
|
+
function dataUrl(payload: unknown): string {
|
|
6
|
+
return `data:application/json,${encodeURIComponent(JSON.stringify(payload))}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function device(): ShaderRegistryDevice {
|
|
10
|
+
return {
|
|
11
|
+
createShaderModule(): ReturnType<ShaderRegistryDevice['createShaderModule']> {
|
|
12
|
+
return ok({} as ShaderModule);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function variant(definesKey: string, transmission: boolean) {
|
|
18
|
+
return {
|
|
19
|
+
definesKey,
|
|
20
|
+
defines: {
|
|
21
|
+
CLUSTER_FORWARD_AVAILABLE: false,
|
|
22
|
+
STORAGE_BUFFER_AVAILABLE: true,
|
|
23
|
+
TRANSMISSION_AVAILABLE: transmission,
|
|
24
|
+
VERTEX_COLOR_AVAILABLE: false,
|
|
25
|
+
},
|
|
26
|
+
composedWgsl: `standard-${transmission}`,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function standardRow(variants: readonly unknown[]) {
|
|
31
|
+
return {
|
|
32
|
+
identifier: 'forgeax::default-standard-pbr',
|
|
33
|
+
sourcePath: 'default-standard-pbr.wgsl',
|
|
34
|
+
composedWgsl: 'standard-default',
|
|
35
|
+
paramSchema: '[]',
|
|
36
|
+
variants,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function load(materialShaders: readonly unknown[]) {
|
|
41
|
+
const registry = new ShaderRegistry({
|
|
42
|
+
device: device(),
|
|
43
|
+
manifestUrl: dataUrl({ entries: [], materialShaders }),
|
|
44
|
+
});
|
|
45
|
+
return registry.loadManifest();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('Standard transmission manifest validation', () => {
|
|
49
|
+
const falseKey =
|
|
50
|
+
'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=false+VERTEX_COLOR_AVAILABLE=false';
|
|
51
|
+
const trueKey =
|
|
52
|
+
'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=true+VERTEX_COLOR_AVAILABLE=false';
|
|
53
|
+
|
|
54
|
+
it('accepts one Standard row with both exact transmission states', async () => {
|
|
55
|
+
const result = await load([standardRow([variant(falseKey, false), variant(trueKey, true)])]);
|
|
56
|
+
expect(result.ok).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('rejects a Standard row when either transmission state is missing', async () => {
|
|
60
|
+
const result = await load([standardRow([variant(falseKey, false)])]);
|
|
61
|
+
expect(result.ok).toBe(false);
|
|
62
|
+
if (!result.ok) expect(result.error.code).toBe('manifest-malformed');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('rejects duplicate material rows and duplicate variant keys', async () => {
|
|
66
|
+
const row = standardRow([variant(falseKey, false), variant(trueKey, true)]);
|
|
67
|
+
const duplicateRow = await load([row, row]);
|
|
68
|
+
expect(duplicateRow.ok).toBe(false);
|
|
69
|
+
|
|
70
|
+
const duplicateVariant = await load([
|
|
71
|
+
standardRow([variant(trueKey, true), variant(trueKey, true)]),
|
|
72
|
+
]);
|
|
73
|
+
expect(duplicateVariant.ok).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// Paradigm: each block-scoped describe('<source-filename>.test.ts', ...) preserves
|
|
17
17
|
// source as ancestorTitles[0]. Top-level imports merged + deduped.
|
|
18
18
|
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
19
20
|
import type { Result, RhiError, ShaderModule } from '@forgeax/engine-rhi';
|
|
20
21
|
import { beforeAll, describe, expect, it } from 'vitest';
|
|
21
22
|
import type { ShaderError } from '../errors.js';
|
|
@@ -31,6 +32,19 @@ import {
|
|
|
31
32
|
ShaderRegistry,
|
|
32
33
|
} from '../ShaderRegistry.js';
|
|
33
34
|
|
|
35
|
+
{
|
|
36
|
+
const sceneTemporalSource = readFileSync(
|
|
37
|
+
new URL('../scene-temporal.wgsl', import.meta.url),
|
|
38
|
+
'utf8',
|
|
39
|
+
);
|
|
40
|
+
describe('scene-temporal accessor ABI', () => {
|
|
41
|
+
it('keeps the public shader module free of compiler and backend policy imports', () => {
|
|
42
|
+
expect(sceneTemporalSource).toContain('forgeax_scene_temporal');
|
|
43
|
+
expect(sceneTemporalSource).not.toMatch(/naga|shader-compiler|engine-rhi/i);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
{
|
|
35
49
|
// --- from fullscreen-triangle.test.ts ---
|
|
36
50
|
// @forgeax/engine-shader/__tests__/fullscreen-triangle.test.ts -
|
|
@@ -179,7 +193,7 @@ import {
|
|
|
179
193
|
});
|
|
180
194
|
});
|
|
181
195
|
|
|
182
|
-
describe('fxaa.wgsl SSOT gate (AC-05)', () => {
|
|
196
|
+
describe('fxaa.wgsl display-encoded SSOT gate (AC-05)', () => {
|
|
183
197
|
it('does NOT define fullscreen_triangle (SSOT is in common.wgsl)', () => {
|
|
184
198
|
expect(fxaaSource).not.toMatch(/fn fullscreen_triangle/);
|
|
185
199
|
});
|
|
@@ -191,6 +205,17 @@ import {
|
|
|
191
205
|
it('imports forgeax_view::common::FullscreenOutput', () => {
|
|
192
206
|
expect(fxaaSource).toContain('#import forgeax_view::common::FullscreenOutput');
|
|
193
207
|
});
|
|
208
|
+
|
|
209
|
+
it('declares display-encoded input/output without an OETF owner', () => {
|
|
210
|
+
expect(fxaaSource).toContain('display-encoded');
|
|
211
|
+
expect(fxaaSource).not.toContain('linearLdrColorDomain');
|
|
212
|
+
expect(fxaaSource).not.toContain('linearToSrgbOetf');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('returns encoded colors directly from early and final paths', () => {
|
|
216
|
+
expect(fxaaSource).toMatch(/return vec4<f32>\(centerColor, 1\.0\);/);
|
|
217
|
+
expect(fxaaSource).toMatch(/return vec4<f32>\(finalColor, 1\.0\);/);
|
|
218
|
+
});
|
|
194
219
|
});
|
|
195
220
|
|
|
196
221
|
describe('fxaa.wgsl bindings', () => {
|
|
@@ -779,6 +804,7 @@ import {
|
|
|
779
804
|
'#pragma variant_axis STORAGE_BUFFER_AVAILABLE',
|
|
780
805
|
'#pragma variant_axis CLUSTER_FORWARD_AVAILABLE',
|
|
781
806
|
'#pragma variant_axis VERTEX_COLOR_AVAILABLE',
|
|
807
|
+
'#pragma variant_axis TRANSMISSION_AVAILABLE',
|
|
782
808
|
]);
|
|
783
809
|
});
|
|
784
810
|
|
|
@@ -923,12 +949,19 @@ import {
|
|
|
923
949
|
'clearcoatRoughness',
|
|
924
950
|
'specularTint',
|
|
925
951
|
'normalScale',
|
|
952
|
+
'transmission',
|
|
953
|
+
'ior',
|
|
954
|
+
'thickness',
|
|
955
|
+
'attenuationColor',
|
|
956
|
+
'attenuationDistance',
|
|
926
957
|
'baseColorTexture',
|
|
927
958
|
'metallicRoughnessTexture',
|
|
928
959
|
'normalTexture',
|
|
929
960
|
'specularTintTexture',
|
|
930
961
|
'emissiveTexture',
|
|
931
962
|
'occlusionTexture',
|
|
963
|
+
'transmissionTexture',
|
|
964
|
+
'thicknessTexture',
|
|
932
965
|
] as const;
|
|
933
966
|
|
|
934
967
|
const STUB_WGSL = '// stub composed pbr-skin wgsl\n';
|
|
@@ -987,7 +1020,7 @@ import {
|
|
|
987
1020
|
const registry = makeMockRegistry();
|
|
988
1021
|
registerDefaultStandardPbrSkin(registry, STUB_WGSL, STORAGE_CAPS);
|
|
989
1022
|
const entry = lookup(registry);
|
|
990
|
-
expect(entry.paramSchema).toHaveLength(
|
|
1023
|
+
expect(entry.paramSchema).toHaveLength(28);
|
|
991
1024
|
});
|
|
992
1025
|
|
|
993
1026
|
it('paramSchema names match default-standard-pbr schema field-for-field', () => {
|
|
@@ -1020,7 +1053,12 @@ import {
|
|
|
1020
1053
|
'f32',
|
|
1021
1054
|
'f32',
|
|
1022
1055
|
'f32',
|
|
1023
|
-
// specular tint and
|
|
1056
|
+
// specular tint, transmission, and attenuation controls.
|
|
1057
|
+
'vec3',
|
|
1058
|
+
'f32',
|
|
1059
|
+
'f32',
|
|
1060
|
+
'f32',
|
|
1061
|
+
'f32',
|
|
1024
1062
|
'vec3',
|
|
1025
1063
|
'f32',
|
|
1026
1064
|
'texture2d',
|
|
@@ -1029,6 +1067,8 @@ import {
|
|
|
1029
1067
|
'texture2d',
|
|
1030
1068
|
'texture2d',
|
|
1031
1069
|
'texture2d',
|
|
1070
|
+
'texture2d',
|
|
1071
|
+
'texture2d',
|
|
1032
1072
|
]);
|
|
1033
1073
|
});
|
|
1034
1074
|
});
|
|
@@ -117,6 +117,7 @@ describe('w6 (b) -- pbr / unlit / sprite-adjacent shaders DO NOT pick up PER_INS
|
|
|
117
117
|
'#pragma variant_axis STORAGE_BUFFER_AVAILABLE',
|
|
118
118
|
'#pragma variant_axis CLUSTER_FORWARD_AVAILABLE',
|
|
119
119
|
'#pragma variant_axis VERTEX_COLOR_AVAILABLE',
|
|
120
|
+
'#pragma variant_axis TRANSMISSION_AVAILABLE',
|
|
120
121
|
]);
|
|
121
122
|
});
|
|
122
123
|
|
|
@@ -207,3 +208,21 @@ describe('w6 (d) -- MAX_UNIFORM_INSTANCES=128 stays unchanged under the new axis
|
|
|
207
208
|
expect(src).toMatch(/MAX_UNIFORM_INSTANCES\s*=\s*128/);
|
|
208
209
|
});
|
|
209
210
|
});
|
|
211
|
+
|
|
212
|
+
describe('HDR/display fragment contract', () => {
|
|
213
|
+
for (const file of ['sprite.wgsl', 'sprite-lit.wgsl'] as const) {
|
|
214
|
+
it(`${file} keeps LDR OETF in fs_main and HDR output linear`, () => {
|
|
215
|
+
const src = stripComments(readWgsl(file));
|
|
216
|
+
const ldrStart = src.indexOf('fn fs_main(');
|
|
217
|
+
const hdrStart = src.indexOf('fn fs_main_hdr(');
|
|
218
|
+
expect(ldrStart).toBeGreaterThanOrEqual(0);
|
|
219
|
+
expect(hdrStart).toBeGreaterThan(ldrStart);
|
|
220
|
+
|
|
221
|
+
const ldrBody = src.slice(ldrStart, hdrStart);
|
|
222
|
+
const hdrBody = src.slice(hdrStart);
|
|
223
|
+
expect(ldrBody).toContain('linear_to_srgb(');
|
|
224
|
+
expect(hdrBody).not.toContain('linear_to_srgb(');
|
|
225
|
+
expect(hdrBody).toContain('return vec4<f32>');
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
});
|