@forgeax/engine-shader 0.1.27 → 0.1.29
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/NOTICE +35 -0
- package/README.md +39 -0
- package/dist/ShaderRegistry.d.ts +8 -0
- package/dist/ShaderRegistry.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +312 -71
- package/dist/index.mjs.map +1 -1
- package/dist/material/artifact-types.d.ts +12 -2
- package/dist/material/artifact-types.d.ts.map +1 -1
- package/dist/material-schemas.d.ts +5 -0
- package/dist/material-schemas.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/ShaderRegistry.ts +10 -0
- package/src/__tests__/auto-exposure-graph.unit.test.ts +59 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +30 -15
- package/src/__tests__/builtin-texture-sampling-contract.test.ts +1 -1
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +2 -1
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +167 -6
- package/src/__tests__/direct-light-layout.unit.test.ts +1 -1
- package/src/__tests__/lighting-punctual.unit.test.ts +29 -1
- package/src/__tests__/ltc-provenance.unit.test.ts +13 -2
- package/src/__tests__/material-builtins.unit.test.ts +1 -0
- package/src/__tests__/material-contract.unit.test.ts +42 -9
- package/src/__tests__/material-derived-builtins.integration.test.ts +18 -2
- package/src/__tests__/probe-lighting-composition.unit.test.ts +1 -1
- package/src/__tests__/reflection-probe-sampling.unit.test.ts +23 -2
- package/src/__tests__/scene-temporal.unit.test.ts +10 -0
- package/src/__tests__/shader.unit.test.ts +1 -0
- package/src/__tests__/sprite-variants.unit.test.ts +1 -0
- package/src/__tests__/ssr-artifact.unit.test.ts +141 -0
- package/src/__tests__/ssr-bgl.integration.test.ts +185 -0
- package/src/__tests__/standard-output-domain.unit.test.ts +36 -0
- package/src/__tests__/standard-pbr-artifact-receipt.unit.test.ts +93 -0
- package/src/__tests__/standard-surface-pass-evaluation.integration.test.ts +16 -4
- package/src/__tests__/surface-v1.unit.test.ts +6 -0
- package/src/__tests__/taa-resolve.unit.test.ts +20 -9
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +6 -1
- package/src/__tests__/transparent-pbr.unit.test.ts +1 -1
- package/src/__tests__/vertex-color-variant.unit.test.ts +4 -1
- package/src/atmosphere-background.wgsl +3 -3
- package/src/auto-exposure-meter.wgsl +179 -0
- package/src/color-lut.wgsl +17 -0
- package/src/common.wgsl +7 -5
- package/src/default-standard-pbr-skin.wgsl +142 -40
- package/src/default-standard-pbr.wgsl +198 -82
- package/src/default_standard_surface.wgsl +56 -19
- package/src/fxaa.wgsl +9 -5
- package/src/ibl-sampling.wgsl +30 -6
- package/src/index.ts +189 -0
- package/src/lighting-punctual.wgsl +5 -6
- package/src/material/artifact-types.ts +109 -63
- package/src/material-schemas.ts +84 -8
- package/src/output-encoding.wgsl +10 -0
- package/src/pbr-temporal.wgsl +9 -1
- package/src/scene-temporal.wgsl +2 -0
- package/src/shadow-pcf.wgsl +6 -8
- package/src/shadow-surface.wgsl +16 -0
- package/src/shadow_caster.wgsl +99 -61
- package/src/sprite-lit.wgsl +4 -4
- package/src/sprite.wgsl +3 -3
- package/src/ssr-compose.wgsl +129 -0
- package/src/ssr-hiz-reduce.wgsl +53 -0
- package/src/ssr-hiz.wgsl +66 -0
- package/src/ssr-temporal.wgsl +305 -0
- package/src/ssr-trace.wgsl +784 -0
- package/src/standard-cluster.wgsl +6 -4
- package/src/standard-surface.wgsl +696 -0
- package/src/surface_v1.wgsl +6 -0
- package/src/taa-resolve.wgsl +222 -28
- package/src/tbn.wgsl +1 -1
- package/src/tonemap.wgsl +37 -18
- package/src/unlit.wgsl +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type BindGroupLayoutDescriptor, type MaterialParticleInput, type ParamSchemaEntry } from '@forgeax/engine-types';
|
|
2
2
|
export interface MaterialShaderResourceSlot {
|
|
3
3
|
readonly name: string;
|
|
4
4
|
readonly parameter: string;
|
|
@@ -51,15 +51,25 @@ export interface MaterialShaderArtifact {
|
|
|
51
51
|
readonly bindings: readonly BindGroupLayoutDescriptor[];
|
|
52
52
|
readonly deps: readonly string[];
|
|
53
53
|
readonly vertexInputs: readonly Readonly<Record<string, unknown>>[];
|
|
54
|
+
/** Reflection-owned dynamic values supplied by a VFX renderer. */
|
|
55
|
+
readonly particleInputs?: readonly MaterialParticleInput[];
|
|
54
56
|
readonly specializationKey?: string;
|
|
55
57
|
/** Producer-owned ABI facts shared by direct and scene-index consumers. */
|
|
56
58
|
readonly receipt?: MaterialShaderArtifactReceipt;
|
|
57
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Canonical Standard material schema consumed by the scene-index row.
|
|
62
|
+
* Physical texture handles stay in the fixed injected range (26..45); their
|
|
63
|
+
* numeric factors remain in this row, after the seven stable user texture
|
|
64
|
+
* coordinate pairs. Keeping this projection beside the receipt lets the
|
|
65
|
+
* shader producer and the render-side table consume one schema identity.
|
|
66
|
+
*/
|
|
67
|
+
export declare const STANDARD_PIPELINE_PARAM_SCHEMA: readonly ParamSchemaEntry[];
|
|
58
68
|
/**
|
|
59
69
|
* Builds the one Standard PBR ABI receipt consumed by both material lanes.
|
|
60
70
|
* The skinned module uses the same material producer and only adds its palette
|
|
61
71
|
* address; it must retain the same receipt identity for material compatibility.
|
|
62
72
|
*/
|
|
63
|
-
export declare function createStandardPbrArtifactReceipt(skinned?: boolean): MaterialShaderArtifactReceipt;
|
|
73
|
+
export declare function createStandardPbrArtifactReceipt(skinned?: boolean, vertexColorAvailable?: boolean): MaterialShaderArtifactReceipt;
|
|
64
74
|
export declare function isMaterialShaderArtifact(value: unknown): value is MaterialShaderArtifact;
|
|
65
75
|
//# sourceMappingURL=artifact-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifact-types.d.ts","sourceRoot":"","sources":["../../src/material/artifact-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"artifact-types.d.ts","sourceRoot":"","sources":["../../src/material/artifact-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,10 @@ import { type MaterialShaderArtifactReceipt } from './material/artifact-types.js
|
|
|
3
3
|
export declare const STANDARD_PBR_ALPHA_CUTOFF_DEFAULT = 0;
|
|
4
4
|
/** Shared material contract for the standard PBR and skinned PBR shaders. */
|
|
5
5
|
export declare const DEFAULT_STANDARD_PBR_PARAM_SCHEMA: readonly ParamSchemaEntry[];
|
|
6
|
+
/** Numeric WGSL override ID, stable across Naga symbol mangling. */
|
|
7
|
+
export declare const STANDARD_TEXTURE_MASK_OVERRIDE = "64000";
|
|
8
|
+
/** Presence follows the authored contract, never asynchronous GPU residency. */
|
|
9
|
+
export declare function standardTextureMask(schema: readonly ParamSchemaEntry[]): number;
|
|
6
10
|
export type { StandardPhysicalTextureField } from '@forgeax/engine-types';
|
|
7
11
|
/**
|
|
8
12
|
* Backward-compatible export name for callers that need the physical
|
|
@@ -34,6 +38,7 @@ export declare const STANDARD_BASE_PARAM_SCHEMA: readonly ParamSchemaEntry[];
|
|
|
34
38
|
* root contract that declares them.
|
|
35
39
|
*/
|
|
36
40
|
export declare const STANDARD_PIPELINE_PARAM_SCHEMA: readonly ParamSchemaEntry[];
|
|
41
|
+
export declare const PARTICLE_MESH_SURFACE_PARAM_SCHEMA: readonly ParamSchemaEntry[];
|
|
37
42
|
/** The single producer receipt shared by direct and scene-index Standard PBR. */
|
|
38
43
|
export declare const STANDARD_PBR_ARTIFACT_RECEIPT: MaterialShaderArtifactReceipt;
|
|
39
44
|
/** Skinned Standard PBR keeps the material receipt identity and adds palette ABI. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"material-schemas.d.ts","sourceRoot":"","sources":["../src/material-schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAK9D,OAAO,
|
|
1
|
+
{"version":3,"file":"material-schemas.d.ts","sourceRoot":"","sources":["../src/material-schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAK9D,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,8BAA8B,CAAC;AAEtC,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAEnD,6EAA6E;AAC7E,eAAO,MAAM,iCAAiC,6BAAiC,CAAC;AAEhF,oEAAoE;AACpE,eAAO,MAAM,8BAA8B,UAAU,CAAC;AAQtD,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,CAM/E;AAED,YAAY,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAC1E;;;;GAIG;AACH;;;;;GAKG;AACH,OAAO,EACL,gCAAgC,EAChC,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,oCAAoC,EAAE,SAAS,gBAAgB,EAGzE,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,EAAE,SAAS,gBAAgB,EAY/D,CAAC;AAEJ;;;;;;;;;GASG;AAIH,eAAO,MAAM,8BAA8B,6BAA2C,CAAC;AA0DvF,eAAO,MAAM,kCAAkC,EAAE,SAAS,gBAAgB,EAGxE,CAAC;AAEH,iFAAiF;AACjF,eAAO,MAAM,6BAA6B,EAAE,6BACR,CAAC;AAErC,qFAAqF;AACrF,eAAO,MAAM,kCAAkC,EAAE,6BACT,CAAC;AAEzC,eAAO,MAAM,0BAA0B,EAAE,SAAS,gBAAgB,EAIjE,CAAC;AAEF,eAAO,MAAM,2BAA2B,EAAE,SAAS,gBAAgB,EAMlE,CAAC;AAEF,eAAO,MAAM,8BAA8B,EAAE,SAAS,gBAAgB,EAIrE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-shader",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -19,15 +19,16 @@
|
|
|
19
19
|
"dist",
|
|
20
20
|
"src",
|
|
21
21
|
"README.md",
|
|
22
|
+
"NOTICE",
|
|
22
23
|
"LICENSE"
|
|
23
24
|
],
|
|
24
25
|
"dependencies": {
|
|
25
|
-
"@forgeax/engine-rhi": "0.1.
|
|
26
|
-
"@forgeax/engine-types": "0.1.
|
|
26
|
+
"@forgeax/engine-rhi": "0.1.29",
|
|
27
|
+
"@forgeax/engine-types": "0.1.29",
|
|
27
28
|
"@webgpu/types": "^0.1.71"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
|
-
"@forgeax/engine-vite-plugin-shader": "0.1.
|
|
31
|
+
"@forgeax/engine-vite-plugin-shader": "0.1.29"
|
|
31
32
|
},
|
|
32
33
|
"forgeax": {
|
|
33
34
|
"metrics": {
|
package/src/ShaderRegistry.ts
CHANGED
|
@@ -73,6 +73,16 @@ export interface ShaderRegistryDevice {
|
|
|
73
73
|
*/
|
|
74
74
|
export const FORGEAX_RESERVED_PATH_PREFIX = 'forgeax::' as const;
|
|
75
75
|
|
|
76
|
+
/** Stable build-time module ids for the M1 spatial SSR artifact set. */
|
|
77
|
+
export const SSR_SHADER_MODULES = Object.freeze({
|
|
78
|
+
hiz: 'forgeax_ssr::hiz',
|
|
79
|
+
hizReduce: 'forgeax_ssr::hiz_reduce',
|
|
80
|
+
trace: 'forgeax_ssr::trace',
|
|
81
|
+
temporal: 'forgeax_ssr::temporal',
|
|
82
|
+
} as const);
|
|
83
|
+
|
|
84
|
+
export type SsrShaderModule = (typeof SSR_SHADER_MODULES)[keyof typeof SSR_SHADER_MODULES];
|
|
85
|
+
|
|
76
86
|
/**
|
|
77
87
|
* Material shader admission input accepted by
|
|
78
88
|
* `installMaterialArtifact(identifier, entry)`. `findMaterialArtifact`
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { AUTO_EXPOSURE_METER_WGSL } from '../index';
|
|
5
|
+
|
|
6
|
+
const standaloneMeterSource = readFileSync(
|
|
7
|
+
fileURLToPath(new URL('../auto-exposure-meter.wgsl', import.meta.url)),
|
|
8
|
+
'utf8',
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
describe('auto exposure shader graph', () => {
|
|
12
|
+
it('exposes one fixed-cohort WGSL module without compiler or Naga imports', () => {
|
|
13
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('@compute');
|
|
14
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('@workgroup_size(256, 1, 1)');
|
|
15
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain(
|
|
16
|
+
'var<workgroup> localHistogram: array<atomic<u32>, 256>',
|
|
17
|
+
);
|
|
18
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('atomicStore(&localHistogram[localIndex], 0u)');
|
|
19
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('fn auto_exposure_clear');
|
|
20
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('fn auto_exposure_histogram');
|
|
21
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('fn auto_exposure_adapt');
|
|
22
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('@builtin(num_workgroups) numWorkgroups');
|
|
23
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('let blockStride = numWorkgroups.xy * tileSize');
|
|
24
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('for (var blockY = blockStart.y;');
|
|
25
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('for (var blockX = blockStart.x;');
|
|
26
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('fn centerWeight');
|
|
27
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('value == value');
|
|
28
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain(
|
|
29
|
+
'atomicAdd(&localHistogram[bin], centerWeight(block, center, extent))',
|
|
30
|
+
);
|
|
31
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('atomicAdd(&histogram[localIndex], localCount)');
|
|
32
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('parameters.compensationEv');
|
|
33
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('parameters.rangeMinEv');
|
|
34
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('parameters.upRate');
|
|
35
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('parameters.deltaTime');
|
|
36
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('validParameters');
|
|
37
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('previous.x');
|
|
38
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('state[0] = candidate[0]');
|
|
39
|
+
expect(AUTO_EXPOSURE_METER_WGSL).not.toContain('fn auto_exposure_meter');
|
|
40
|
+
expect(AUTO_EXPOSURE_METER_WGSL).not.toContain('sampleBins');
|
|
41
|
+
expect(AUTO_EXPOSURE_METER_WGSL).not.toContain('shader-compiler');
|
|
42
|
+
expect(AUTO_EXPOSURE_METER_WGSL).not.toContain('naga');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('keeps the standalone source and runtime entrypoints single and aligned', () => {
|
|
46
|
+
expect(standaloneMeterSource.match(/struct AutoExposureParameters/g)).toHaveLength(1);
|
|
47
|
+
expect(standaloneMeterSource.match(/fn finite\(/g)).toHaveLength(1);
|
|
48
|
+
expect(standaloneMeterSource.match(/@group\(0\) @binding\(0\)/g)).toHaveLength(1);
|
|
49
|
+
expect(standaloneMeterSource).toContain('@compute @workgroup_size(256, 1, 1)');
|
|
50
|
+
expect(standaloneMeterSource).toContain('fn auto_exposure_clear');
|
|
51
|
+
expect(standaloneMeterSource).toContain('fn auto_exposure_histogram');
|
|
52
|
+
expect(standaloneMeterSource).toContain('fn auto_exposure_adapt');
|
|
53
|
+
expect(standaloneMeterSource).toContain('@builtin(num_workgroups) numWorkgroups');
|
|
54
|
+
expect(standaloneMeterSource).toContain('let blockStride = numWorkgroups.xy * tileSize');
|
|
55
|
+
expect(standaloneMeterSource).not.toContain('fn auto_exposure_meter');
|
|
56
|
+
expect(standaloneMeterSource).not.toContain('@workgroup_size(16');
|
|
57
|
+
expect(AUTO_EXPOSURE_METER_WGSL).toContain('fn auto_exposure_histogram');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -4,6 +4,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
4
4
|
|
|
5
5
|
const shaderRoot = resolve(import.meta.dirname, '..');
|
|
6
6
|
const tonemap = readFileSync(resolve(shaderRoot, 'tonemap.wgsl'), 'utf8');
|
|
7
|
+
const outputEncoding = readFileSync(resolve(shaderRoot, 'output-encoding.wgsl'), 'utf8');
|
|
7
8
|
const common = readFileSync(resolve(shaderRoot, 'common.wgsl'), 'utf8');
|
|
8
9
|
const bloom = readFileSync(resolve(shaderRoot, 'bloom-blur.wgsl'), 'utf8');
|
|
9
10
|
const fxaa = readFileSync(resolve(shaderRoot, 'fxaa.wgsl'), 'utf8');
|
|
@@ -34,32 +35,46 @@ describe('post shader color-domain contract', () => {
|
|
|
34
35
|
it('declares Output Transform as the only display conversion owner', () => {
|
|
35
36
|
const code = codeOnly(tonemap);
|
|
36
37
|
expect(tonemap).toContain('Output Transform');
|
|
37
|
-
expect(tonemap).toContain('
|
|
38
|
-
expect(
|
|
38
|
+
expect(tonemap).toContain('fs_encode_only');
|
|
39
|
+
expect(outputEncoding.match(/linearToSrgbOetf\s*\(/g)).toHaveLength(1);
|
|
40
|
+
expect(tonemap).toContain('#import forgeax_view::output_encoding');
|
|
39
41
|
expect(code).toContain('let exposed : vec3<f32> = sample * params.exposure;');
|
|
40
|
-
|
|
41
|
-
expect(
|
|
42
|
-
|
|
43
|
-
);
|
|
42
|
+
const mapping = functionBody(code, 'mapTonemap');
|
|
43
|
+
expect(mapping.indexOf('let exposed')).toBeGreaterThanOrEqual(0);
|
|
44
|
+
expect(mapping.indexOf('let exposed')).toBeLessThan(mapping.indexOf('switch (params.mode)'));
|
|
45
|
+
expect(code).toContain('encodeFinal(mapTonemap');
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
it('maps every public mode before one shared OETF', () => {
|
|
47
49
|
const code = codeOnly(tonemap);
|
|
48
|
-
const body = functionBody(code, '
|
|
50
|
+
const body = functionBody(code, 'mapTonemap');
|
|
49
51
|
for (const mode of [1, 2, 3, 4, 5, 6, 7]) {
|
|
50
|
-
expect(body).toMatch(
|
|
52
|
+
expect(body).toMatch(
|
|
53
|
+
new RegExp(`case ${mode}u:\\s*\\{\\s*mapped\\s*=\\s*(?:toneMap|tonemap)`),
|
|
54
|
+
);
|
|
51
55
|
}
|
|
52
|
-
expect(body).toMatch(/default:\s*\{\s*mapped\s*=\s*sample;\s*\}/);
|
|
53
|
-
expect(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
expect(
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
expect(body).toMatch(/default:\s*\{\s*mapped\s*=\s*toneMapLinearLdr\(sample\);\s*\}/);
|
|
57
|
+
expect(functionBody(code, 'fs_main')).toContain(
|
|
58
|
+
'return encodeFinal(mapTonemap(source.rgb), source.a, in.position);',
|
|
59
|
+
);
|
|
60
|
+
expect(functionBody(code, 'fs_main')).not.toContain('linearToSrgbOetf(');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('exposes tone-only and encoding-only entry points for the positive LUT path', () => {
|
|
64
|
+
const code = codeOnly(tonemap);
|
|
65
|
+
const toneOnly = functionBody(code, 'fs_tone_only');
|
|
66
|
+
const encodeOnly = functionBody(code, 'fs_encode_only');
|
|
67
|
+
expect(toneOnly).toContain('mapTonemap(source.rgb)');
|
|
68
|
+
expect(toneOnly).not.toContain('encodeOutput(');
|
|
69
|
+
expect(encodeOnly).toContain('return encodeFinal(source.rgb, source.a, in.position);');
|
|
70
|
+
expect(functionBody(code, 'encodeFinal')).toContain('encodeOutput(linearColor, alpha)');
|
|
71
|
+
expect(functionBody(code, 'encodeFinal')).toContain('ditherUnorm8(encoded.rgb, position.xy)');
|
|
72
|
+
expect(code.indexOf('fn fs_tone_only')).toBeLessThan(code.indexOf('fn fs_encode_only'));
|
|
59
73
|
});
|
|
60
74
|
|
|
61
75
|
it('keeps the sole OETF free of software precision emulation', () => {
|
|
62
76
|
expect(common).not.toContain('quantizeToF16');
|
|
77
|
+
expect(outputEncoding).not.toContain('quantizeToF16');
|
|
63
78
|
expect(tonemap).not.toContain('quantizeToF16');
|
|
64
79
|
expect(tonemap).not.toContain('Float16');
|
|
65
80
|
});
|
|
@@ -14,8 +14,9 @@ const surface = readFileSync(
|
|
|
14
14
|
describe('standard PBR MASK alpha contract', () => {
|
|
15
15
|
it('multiplies factor alpha by sampled texture alpha before discard', () => {
|
|
16
16
|
expect(surface).toContain(
|
|
17
|
-
'clamp(
|
|
17
|
+
'clamp(materialValue.baseColor.a * baseSample.a * vertexColor.a, 0.0, 1.0)',
|
|
18
18
|
);
|
|
19
|
+
expect(surface).toContain('return evaluate_standard_surface(input, material)');
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
it('matches Three r184 alphaTest by discarding values equal to or below cutoff', () => {
|
|
@@ -38,12 +38,72 @@ describe('default Standard PBR transmission manifest contract', () => {
|
|
|
38
38
|
|
|
39
39
|
expect(standard).toBeDefined();
|
|
40
40
|
if (standard === undefined) return;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
const transmissionVariants = standard.variants.filter(
|
|
42
|
+
(variant) => 'TRANSMISSION_AVAILABLE' in variant.defines,
|
|
43
|
+
);
|
|
44
|
+
// The producer emits the complete supported Cartesian product. Storage is
|
|
45
|
+
// required for cluster/probe/extended-lighting variants, and extended
|
|
46
|
+
// lighting additionally requires the projector texture lane. GPU-driven
|
|
47
|
+
// scene-index and reflection fallback are storage-backed lanes as well.
|
|
48
|
+
// Keep the expected keys derived here so adding a new axis cannot silently
|
|
49
|
+
// shrink the published set.
|
|
50
|
+
const expectedKeys = new Set<string>();
|
|
51
|
+
for (const clusterForwardAvailable of [false, true]) {
|
|
52
|
+
for (const storageBufferAvailable of [false, true]) {
|
|
53
|
+
for (const vertexColorAvailable of [false, true]) {
|
|
54
|
+
for (const probeBlendAvailable of [false, true]) {
|
|
55
|
+
for (const extendedLightingAvailable of [false, true]) {
|
|
56
|
+
for (const directionalPcssAvailable of [false, true]) {
|
|
57
|
+
for (const transmissionAvailable of [false, true]) {
|
|
58
|
+
for (const projectorAvailable of [false, true]) {
|
|
59
|
+
for (const gpuDrivenSceneIndexAvailable of [false, true]) {
|
|
60
|
+
for (const reflectionFallbackAvailable of [false, true]) {
|
|
61
|
+
if (
|
|
62
|
+
(clusterForwardAvailable && !storageBufferAvailable) ||
|
|
63
|
+
(probeBlendAvailable && !storageBufferAvailable) ||
|
|
64
|
+
(extendedLightingAvailable &&
|
|
65
|
+
(!storageBufferAvailable || !projectorAvailable)) ||
|
|
66
|
+
(gpuDrivenSceneIndexAvailable && !storageBufferAvailable)
|
|
67
|
+
) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const expectedKeyEntries = [
|
|
71
|
+
`CLUSTER_FORWARD_AVAILABLE=${clusterForwardAvailable}`,
|
|
72
|
+
`DIRECTIONAL_PCSS_AVAILABLE=${directionalPcssAvailable}`,
|
|
73
|
+
`EXTENDED_LIGHTING_AVAILABLE=${extendedLightingAvailable}`,
|
|
74
|
+
`GPU_DRIVEN_SCENE_INDEX_AVAILABLE=${gpuDrivenSceneIndexAvailable}`,
|
|
75
|
+
`PROBE_BLEND_AVAILABLE=${probeBlendAvailable}`,
|
|
76
|
+
`PROJECTOR_AVAILABLE=${projectorAvailable}`,
|
|
77
|
+
`REFLECTION_FALLBACK_AVAILABLE=${reflectionFallbackAvailable}`,
|
|
78
|
+
`STORAGE_BUFFER_AVAILABLE=${storageBufferAvailable}`,
|
|
79
|
+
`TRANSMISSION_AVAILABLE=${transmissionAvailable}`,
|
|
80
|
+
`VERTEX_COLOR_AVAILABLE=${vertexColorAvailable}`,
|
|
81
|
+
];
|
|
82
|
+
expectedKeys.add(
|
|
83
|
+
expectedKeyEntries.every((entry) => entry.endsWith('=true'))
|
|
84
|
+
? ''
|
|
85
|
+
: expectedKeyEntries.join('+'),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
expect(transmissionVariants).toHaveLength(expectedKeys.size);
|
|
98
|
+
expect(new Set(transmissionVariants.map((variant) => variant.definesKey)).size).toBe(
|
|
99
|
+
expectedKeys.size,
|
|
100
|
+
);
|
|
101
|
+
expect(new Set(transmissionVariants.map((variant) => variant.definesKey))).toEqual(
|
|
102
|
+
expectedKeys,
|
|
103
|
+
);
|
|
104
|
+
expect(new Set(standard.variants.map((variant) => variant.composedWgsl)).size).toBe(
|
|
105
|
+
expectedKeys.size,
|
|
106
|
+
);
|
|
47
107
|
expect(
|
|
48
108
|
standard.variants.every(
|
|
49
109
|
(variant) =>
|
|
@@ -62,6 +122,69 @@ describe('default Standard PBR transmission manifest contract', () => {
|
|
|
62
122
|
),
|
|
63
123
|
),
|
|
64
124
|
).toBe(true);
|
|
125
|
+
for (const clusterForwardAvailable of [false, true]) {
|
|
126
|
+
for (const storageBufferAvailable of [false, true]) {
|
|
127
|
+
if (clusterForwardAvailable && !storageBufferAvailable) continue;
|
|
128
|
+
for (const vertexColorAvailable of [false, true]) {
|
|
129
|
+
for (const probeBlendAvailable of [false, true]) {
|
|
130
|
+
for (const extendedLightingAvailable of [false, true]) {
|
|
131
|
+
for (const directionalPcssAvailable of [false, true]) {
|
|
132
|
+
for (const transmissionAvailable of [false, true]) {
|
|
133
|
+
for (const projectorAvailable of [false, true]) {
|
|
134
|
+
for (const gpuDrivenSceneIndexAvailable of [false, true]) {
|
|
135
|
+
for (const reflectionFallbackAvailable of [false, true]) {
|
|
136
|
+
if (
|
|
137
|
+
(probeBlendAvailable && !storageBufferAvailable) ||
|
|
138
|
+
(extendedLightingAvailable &&
|
|
139
|
+
(!storageBufferAvailable || !projectorAvailable)) ||
|
|
140
|
+
(gpuDrivenSceneIndexAvailable && !storageBufferAvailable)
|
|
141
|
+
) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const expectedKeyEntries = [
|
|
145
|
+
`CLUSTER_FORWARD_AVAILABLE=${clusterForwardAvailable}`,
|
|
146
|
+
`DIRECTIONAL_PCSS_AVAILABLE=${directionalPcssAvailable}`,
|
|
147
|
+
`EXTENDED_LIGHTING_AVAILABLE=${extendedLightingAvailable}`,
|
|
148
|
+
`GPU_DRIVEN_SCENE_INDEX_AVAILABLE=${gpuDrivenSceneIndexAvailable}`,
|
|
149
|
+
`PROBE_BLEND_AVAILABLE=${probeBlendAvailable}`,
|
|
150
|
+
`PROJECTOR_AVAILABLE=${projectorAvailable}`,
|
|
151
|
+
`REFLECTION_FALLBACK_AVAILABLE=${reflectionFallbackAvailable}`,
|
|
152
|
+
`STORAGE_BUFFER_AVAILABLE=${storageBufferAvailable}`,
|
|
153
|
+
`TRANSMISSION_AVAILABLE=${transmissionAvailable}`,
|
|
154
|
+
`VERTEX_COLOR_AVAILABLE=${vertexColorAvailable}`,
|
|
155
|
+
];
|
|
156
|
+
const expectedKey = expectedKeyEntries.every((entry) =>
|
|
157
|
+
entry.endsWith('=true'),
|
|
158
|
+
)
|
|
159
|
+
? ''
|
|
160
|
+
: expectedKeyEntries.join('+');
|
|
161
|
+
expect(transmissionVariants).toContainEqual(
|
|
162
|
+
expect.objectContaining({
|
|
163
|
+
definesKey: expectedKey,
|
|
164
|
+
defines: expect.objectContaining({
|
|
165
|
+
CLUSTER_FORWARD_AVAILABLE: clusterForwardAvailable,
|
|
166
|
+
DIRECTIONAL_PCSS_AVAILABLE: directionalPcssAvailable,
|
|
167
|
+
EXTENDED_LIGHTING_AVAILABLE: extendedLightingAvailable,
|
|
168
|
+
GPU_DRIVEN_SCENE_INDEX_AVAILABLE: gpuDrivenSceneIndexAvailable,
|
|
169
|
+
PROBE_BLEND_AVAILABLE: probeBlendAvailable,
|
|
170
|
+
PROJECTOR_AVAILABLE: projectorAvailable,
|
|
171
|
+
REFLECTION_FALLBACK_AVAILABLE: reflectionFallbackAvailable,
|
|
172
|
+
STORAGE_BUFFER_AVAILABLE: storageBufferAvailable,
|
|
173
|
+
TRANSMISSION_AVAILABLE: transmissionAvailable,
|
|
174
|
+
VERTEX_COLOR_AVAILABLE: vertexColorAvailable,
|
|
175
|
+
}),
|
|
176
|
+
}),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
65
188
|
expect(standard.variants.every((variant) => 'TRANSMISSION_AVAILABLE' in variant.defines)).toBe(
|
|
66
189
|
true,
|
|
67
190
|
);
|
|
@@ -93,6 +216,44 @@ describe('default Standard PBR transmission manifest contract', () => {
|
|
|
93
216
|
);
|
|
94
217
|
});
|
|
95
218
|
|
|
219
|
+
it('keeps material-index varying GPU-only while preserving the scene-index variant', {
|
|
220
|
+
timeout: 60_000,
|
|
221
|
+
}, async () => {
|
|
222
|
+
const manifest = await engineManifest();
|
|
223
|
+
const standard = manifest.materialShaders.find(
|
|
224
|
+
(entry) => entry.identifier === 'forgeax::default-standard-pbr',
|
|
225
|
+
);
|
|
226
|
+
expect(standard).toBeDefined();
|
|
227
|
+
if (standard === undefined) return;
|
|
228
|
+
|
|
229
|
+
const sceneIndex = standard.variants.find(
|
|
230
|
+
(variant) =>
|
|
231
|
+
variant.defines.GPU_DRIVEN_SCENE_INDEX_AVAILABLE === true &&
|
|
232
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === true &&
|
|
233
|
+
variant.defines.TRANSMISSION_AVAILABLE === false &&
|
|
234
|
+
variant.defines.CLUSTER_FORWARD_AVAILABLE === false &&
|
|
235
|
+
variant.defines.VERTEX_COLOR_AVAILABLE === false,
|
|
236
|
+
);
|
|
237
|
+
expect(sceneIndex).toBeDefined();
|
|
238
|
+
expect(sceneIndex?.composedWgsl).toMatch(/out(?:_\d+)?\.materialIndex\s*=\s*materialIndex/);
|
|
239
|
+
|
|
240
|
+
const directFallback = standard.variants.find(
|
|
241
|
+
(variant) =>
|
|
242
|
+
variant.defines.GPU_DRIVEN_SCENE_INDEX_AVAILABLE === false &&
|
|
243
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === false &&
|
|
244
|
+
variant.defines.TRANSMISSION_AVAILABLE === false &&
|
|
245
|
+
variant.defines.CLUSTER_FORWARD_AVAILABLE === false &&
|
|
246
|
+
variant.defines.VERTEX_COLOR_AVAILABLE === false,
|
|
247
|
+
);
|
|
248
|
+
expect(directFallback).toBeDefined();
|
|
249
|
+
expect(directFallback?.composedWgsl).not.toMatch(
|
|
250
|
+
/@location\(15\)\s+@interpolate\(flat\)\s+materialIndex/,
|
|
251
|
+
);
|
|
252
|
+
expect(directFallback?.composedWgsl).not.toMatch(
|
|
253
|
+
/out(?:_\d+)?\.materialIndex\s*=\s*materialIndex/,
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
96
257
|
it('publishes the same explicit transmission axis for the skin Standard template', {
|
|
97
258
|
timeout: 60_000,
|
|
98
259
|
}, async () => {
|
|
@@ -26,7 +26,7 @@ describe('DirectLightSlot WGSL ABI', () => {
|
|
|
26
26
|
|
|
27
27
|
it('keeps the slot type owned by the unified Cluster shader consumer', async () => {
|
|
28
28
|
const common = await readShader('common.wgsl');
|
|
29
|
-
const standard = await readShader('
|
|
29
|
+
const standard = await readShader('standard-surface.wgsl');
|
|
30
30
|
const clustered = await readShader('standard-cluster.wgsl');
|
|
31
31
|
|
|
32
32
|
expect(common).toContain('struct DirectLightSlot');
|
|
@@ -40,6 +40,14 @@ describe('direct punctual lighting shader contract', () => {
|
|
|
40
40
|
expect(source).not.toMatch(/intensityMultiplier|magicMultiplier|profile/);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
it('keeps point-shadow cube direction and bias arguments in the shared convention', async () => {
|
|
44
|
+
const source = await readFile(shaderPath, 'utf8');
|
|
45
|
+
|
|
46
|
+
expect(source).toContain('let lightLocal = fromLight;');
|
|
47
|
+
expect(source).not.toContain('vec3<f32>(fromLight.x, fromLight.y, -fromLight.z)');
|
|
48
|
+
expect(source).toContain('depthRef, normalBias, depthBias, nDotL,');
|
|
49
|
+
});
|
|
50
|
+
|
|
43
51
|
it('leaves spot direction normalization to extract instead of HDRP', async () => {
|
|
44
52
|
const source = await readFile(shaderPath, 'utf8');
|
|
45
53
|
|
|
@@ -47,6 +55,26 @@ describe('direct punctual lighting shader contract', () => {
|
|
|
47
55
|
expect(source).not.toContain('normalize(lightDir)');
|
|
48
56
|
});
|
|
49
57
|
|
|
58
|
+
it('keeps the point receiver on the canonical normalized depth bias', async () => {
|
|
59
|
+
const source = await readFile(join(dirname(shaderPath), 'shadow-pcf.wgsl'), 'utf8');
|
|
60
|
+
const point = source.slice(source.indexOf('fn sample_shadow_cube_hw2x2('));
|
|
61
|
+
expect(point).toContain('shadow_biased_receiver_depth(depthRef, normalBias, depthBias, nDotL)');
|
|
62
|
+
expect(point).not.toContain('depthBias / 1000');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('shares default Surface evaluation between CPU, GPU-driven and skinned Standard', async () => {
|
|
66
|
+
for (const name of ['default-standard-pbr.wgsl', 'default-standard-pbr-skin.wgsl']) {
|
|
67
|
+
const source = await readFile(join(dirname(shaderPath), name), 'utf8');
|
|
68
|
+
expect(source).toContain('slot::surface::{evaluate_surface, evaluate_standard_surface}');
|
|
69
|
+
expect(source).not.toContain('fn evaluateSelectedStandardSurface');
|
|
70
|
+
}
|
|
71
|
+
const source = await readFile(
|
|
72
|
+
join(dirname(shaderPath), 'default_standard_surface.wgsl'),
|
|
73
|
+
'utf8',
|
|
74
|
+
);
|
|
75
|
+
expect(source).toContain('return evaluate_standard_surface(input, material);');
|
|
76
|
+
});
|
|
77
|
+
|
|
50
78
|
it('shares Three r184 direct-PBR Fresnel and multiscatter ownership', async () => {
|
|
51
79
|
const [source, brdf, directional] = await Promise.all([
|
|
52
80
|
readFile(shaderPath, 'utf8'),
|
|
@@ -105,7 +133,7 @@ describe('direct punctual lighting shader contract', () => {
|
|
|
105
133
|
const source = await readFile(pbrShaderPath, 'utf8');
|
|
106
134
|
|
|
107
135
|
expect(source).toMatch(
|
|
108
|
-
/evaluateStandardClusterLights\(\s*in\.ndc\.xyz,\s*standardViewZ\(in\),\s*in\.worldPos,\s*physicalNormal,\s*v,\s*diffuseAlbedo,\s*metallic,\s*a,\s*f0,\s*false
|
|
136
|
+
/evaluateStandardClusterLights\(\s*in\.ndc\.xyz,\s*(?:standardViewZ\(in\)|in\.viewZ),\s*in\.worldPos,\s*physicalNormal,\s*v,\s*diffuseAlbedo,\s*metallic,\s*a,\s*f0,\s*false,\s*(?:true|factors\.receiveShadows)\s*,?\s*\)/u,
|
|
109
137
|
);
|
|
110
138
|
expect(source).toContain('clearcoatAlpha,');
|
|
111
139
|
expect(source).toContain('vec3<f32>(0.04),');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
1
2
|
import { describe, expect, it } from 'vitest';
|
|
2
3
|
import {
|
|
3
4
|
generateLtcTables,
|
|
@@ -9,6 +10,14 @@ import {
|
|
|
9
10
|
LTC_TABLES,
|
|
10
11
|
} from '../ltc/tables';
|
|
11
12
|
|
|
13
|
+
function tableBytes(table: Uint16Array): Buffer {
|
|
14
|
+
return Buffer.from(table.buffer, table.byteOffset, table.byteLength);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function expectSameTableBytes(left: Uint16Array, right: Uint16Array): void {
|
|
18
|
+
expect(Buffer.compare(tableBytes(left), tableBytes(right))).toBe(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
describe('LTC provenance and tracked data contract', () => {
|
|
13
22
|
it('pins the fixed Three.js reference and two 64 by 64 rgba16float tables', () => {
|
|
14
23
|
expect(LTC_SOURCE_PROVENANCE.source).toBe('Three.js');
|
|
@@ -23,8 +32,10 @@ describe('LTC provenance and tracked data contract', () => {
|
|
|
23
32
|
const first = generateLtcTables();
|
|
24
33
|
const second = generateLtcTables();
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
expectSameTableBytes(first.lambert, second.lambert);
|
|
36
|
+
expectSameTableBytes(first.ggx, second.ggx);
|
|
37
|
+
expectSameTableBytes(first.lambert, LTC_TABLES.lambert);
|
|
38
|
+
expectSameTableBytes(first.ggx, LTC_TABLES.ggx);
|
|
28
39
|
expect(hashLtcTable(first.lambert)).toBe(LTC_TABLE_HASHES.lambert);
|
|
29
40
|
expect(hashLtcTable(first.ggx)).toBe(LTC_TABLE_HASHES.ggx);
|
|
30
41
|
expect(LTC_TABLE_HASHES.lambert).toMatch(/^[a-f0-9]{64}$/);
|
|
@@ -45,6 +45,7 @@ describe('built-in MaterialAsset sources', () => {
|
|
|
45
45
|
|
|
46
46
|
expect(material.kind).toBe('material');
|
|
47
47
|
expect(pass?.program.module).toBe(BUILTIN_MATERIAL_MODULES[kind]);
|
|
48
|
+
expect(pass?.renderState?.tags).toEqual({ LightMode: 'Forward' });
|
|
48
49
|
expect(pass).not.toHaveProperty('shader');
|
|
49
50
|
expect(material.parameters?.length).toBeGreaterThan(0);
|
|
50
51
|
expect(material.values).toBeDefined();
|