@forgeax/engine-shader 0.1.20 → 0.1.23
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 +10 -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 +33070 -8
- package/dist/index.mjs.map +1 -1
- package/dist/ltc/provenance.d.ts +17 -0
- package/dist/ltc/provenance.d.ts.map +1 -0
- package/dist/ltc/tables.d.ts +22 -0
- package/dist/ltc/tables.d.ts.map +1 -0
- package/dist/material/artifact-types.d.ts +52 -0
- 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/dist/register-default-sprite-lit.d.ts +3 -6
- package/dist/register-default-sprite-lit.d.ts.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ShaderRegistry.ts +65 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +30 -4
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +7 -0
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +138 -30
- package/src/__tests__/direct-light-layout.unit.test.ts +40 -0
- package/src/__tests__/ibl-irradiance.unit.test.ts +7 -4
- package/src/__tests__/ibl-sampling.unit.test.ts +3 -4
- package/src/__tests__/lighting-point-spot-volume.unit.test.ts +16 -2
- package/src/__tests__/lighting-punctual.unit.test.ts +61 -8
- package/src/__tests__/lighting-spot-volume-attenuation.unit.test.ts +1 -1
- package/src/__tests__/ltc-provenance.unit.test.ts +33 -0
- package/src/__tests__/manifest.fixture.json +33 -0
- package/src/__tests__/material-contract.unit.test.ts +61 -1
- package/src/__tests__/material-derived-builtins.integration.test.ts +27 -0
- package/src/__tests__/probe-lighting-composition.unit.test.ts +38 -0
- package/src/__tests__/rect-area-brdf.unit.test.ts +36 -0
- package/src/__tests__/reflection-probe-sampling.unit.test.ts +45 -0
- package/src/__tests__/scene-temporal.unit.test.ts +20 -0
- package/src/__tests__/shader.unit.test.ts +40 -15
- package/src/__tests__/shadow-pcss.unit.test.ts +131 -0
- package/src/__tests__/spot-cookie-composition.unit.test.ts +57 -0
- package/src/__tests__/spot-modifier-composition.unit.test.ts +19 -0
- package/src/__tests__/sprite-lit-shader.test.ts +24 -11
- package/src/__tests__/sprite-variants.unit.test.ts +5 -1
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +7 -1
- package/src/__tests__/transparent-pbr.unit.test.ts +7 -0
- package/src/brdf.wgsl +115 -1
- package/src/common.wgsl +95 -91
- package/src/default-standard-pbr-skin.wgsl +108 -92
- package/src/default-standard-pbr.wgsl +189 -191
- package/src/fxaa.wgsl +27 -6
- package/src/ibl-irradiance.wgsl +4 -1
- package/src/ibl-sampling.wgsl +56 -2
- package/src/index.ts +23 -0
- package/src/lighting-directional.wgsl +161 -132
- package/src/lighting-probe.wgsl +51 -0
- package/src/lighting-punctual.wgsl +69 -29
- package/src/lighting-rect-area.wgsl +162 -0
- package/src/lighting-spot-modifiers.wgsl +93 -0
- package/src/ltc/provenance.ts +19 -0
- package/src/ltc/tables.ts +2831 -0
- package/src/material/artifact-types.ts +137 -0
- package/src/material-schemas.ts +12 -0
- package/src/register-default-sprite-lit.ts +3 -6
- package/src/scene-temporal.wgsl +14 -8
- package/src/shadow-pcf.wgsl +44 -1
- package/src/sprite-lit.wgsl +37 -53
- package/src/sprite.wgsl +1 -1
- package/src/standard-cluster.wgsl +274 -0
- package/src/tonemap.wgsl +8 -3
- package/src/types.ts +2 -1
- package/src/volume/volume-inject.wgsl +48 -38
- package/src/volume/volume-integrate.wgsl +47 -42
- package/dist/.tsbuildinfo +0 -1
- package/src/hdrp-cluster-forward.wgsl +0 -224
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The table source is the Three.js r184 RectAreaLight implementation. The
|
|
3
|
+
* source arrays are converted to IEEE-754 binary16 offline and checked into
|
|
4
|
+
* `tables.ts`; the runtime never fetches or parses a source file.
|
|
5
|
+
*/
|
|
6
|
+
export declare const LTC_SOURCE_PROVENANCE: {
|
|
7
|
+
readonly source: "Three.js";
|
|
8
|
+
readonly revision: "r184";
|
|
9
|
+
readonly sourceUrl: "https://github.com/mrdoob/three.js/blob/r184/examples/jsm/lights/RectAreaLightTexturesLib.js";
|
|
10
|
+
readonly upstreamData: "selfshadow/ltc_code fit/results/ltc.js";
|
|
11
|
+
readonly generator: "deterministic Three.js source-array to rgba16float conversion";
|
|
12
|
+
readonly inputSha256: {
|
|
13
|
+
readonly lambert: "cf5cf21e5c112d2095c7e2418cb0a1ac54636e275d73e42f3453646c67f26814";
|
|
14
|
+
readonly ggx: "3b1b09080b26104498db277c14fc1733786465c6958e7a8403d688b1e24c2ff5";
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=provenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../../src/ltc/provenance.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;CAWxB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { LTC_SOURCE_PROVENANCE } from './provenance';
|
|
2
|
+
export { LTC_SOURCE_PROVENANCE };
|
|
3
|
+
export declare const LTC_TABLE_WIDTH = 64;
|
|
4
|
+
export declare const LTC_TABLE_HEIGHT = 64;
|
|
5
|
+
export declare const LTC_TABLES: {
|
|
6
|
+
readonly lambert: Uint16Array<ArrayBuffer>;
|
|
7
|
+
readonly ggx: Uint16Array<ArrayBuffer>;
|
|
8
|
+
};
|
|
9
|
+
export declare const LTC_TABLE_HASHES: {
|
|
10
|
+
readonly lambert: "a391de32f868fd4aa8774917b793174b7be804c08e2fb8924c30f31d7aa8dcd7";
|
|
11
|
+
readonly ggx: "fa1ecbc6deb3c85ddf603cdf1e98e30279444f905eb1cebb849d761b570dd696";
|
|
12
|
+
};
|
|
13
|
+
export declare const LTC_TABLE_INPUT_HASHES: {
|
|
14
|
+
readonly lambert: "cf5cf21e5c112d2095c7e2418cb0a1ac54636e275d73e42f3453646c67f26814";
|
|
15
|
+
readonly ggx: "3b1b09080b26104498db277c14fc1733786465c6958e7a8403d688b1e24c2ff5";
|
|
16
|
+
};
|
|
17
|
+
export declare function generateLtcTables(): {
|
|
18
|
+
lambert: Uint16Array;
|
|
19
|
+
ggx: Uint16Array;
|
|
20
|
+
};
|
|
21
|
+
export declare function hashLtcTable(table: Uint16Array): string;
|
|
22
|
+
//# sourceMappingURL=tables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/ltc/tables.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAAE,qBAAqB,EAAE,CAAC;AACjC,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAorFnC,eAAO,MAAM,UAAU;;;CAAkD,CAAC;AAC1E,eAAO,MAAM,gBAAgB;;;CAGnB,CAAC;AACX,eAAO,MAAM,sBAAsB;;;CAAoC,CAAC;AAExE,wBAAgB,iBAAiB,IAAI;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,WAAW,CAAA;CAAE,CAE9E;AAwED,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAEvD"}
|
|
@@ -1,13 +1,65 @@
|
|
|
1
1
|
import type { BindGroupLayoutDescriptor } from '@forgeax/engine-types';
|
|
2
|
+
export interface MaterialShaderResourceSlot {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly parameter: string;
|
|
5
|
+
readonly kind: 'sampler' | 'texture';
|
|
6
|
+
readonly group: number;
|
|
7
|
+
readonly binding: number;
|
|
8
|
+
}
|
|
9
|
+
export interface MaterialShaderVertexInput {
|
|
10
|
+
readonly semantic: string;
|
|
11
|
+
readonly location: number;
|
|
12
|
+
readonly format: string;
|
|
13
|
+
}
|
|
14
|
+
export interface MaterialShaderArtifactReceipt {
|
|
15
|
+
readonly directEntry: string;
|
|
16
|
+
readonly sceneIndexEntry: string;
|
|
17
|
+
readonly materialRow: {
|
|
18
|
+
readonly byteLength: number;
|
|
19
|
+
readonly fields: readonly string[];
|
|
20
|
+
};
|
|
21
|
+
readonly resourceSlots: readonly MaterialShaderResourceSlot[];
|
|
22
|
+
readonly uvSets: readonly {
|
|
23
|
+
readonly parameter: string;
|
|
24
|
+
readonly set: number;
|
|
25
|
+
}[];
|
|
26
|
+
readonly vertexInputs: readonly MaterialShaderVertexInput[];
|
|
27
|
+
readonly alphaMask: {
|
|
28
|
+
readonly cutoff: string;
|
|
29
|
+
readonly source: string;
|
|
30
|
+
};
|
|
31
|
+
readonly skinPaletteAddress?: {
|
|
32
|
+
readonly group: number;
|
|
33
|
+
readonly binding: number;
|
|
34
|
+
readonly stride: number;
|
|
35
|
+
};
|
|
36
|
+
readonly reflection: {
|
|
37
|
+
readonly layoutIdentity: string;
|
|
38
|
+
readonly resourceSlots: readonly MaterialShaderResourceSlot[];
|
|
39
|
+
readonly vertexInputs: readonly MaterialShaderVertexInput[];
|
|
40
|
+
};
|
|
41
|
+
readonly receiptIdentity: string;
|
|
42
|
+
readonly generation: number;
|
|
43
|
+
}
|
|
2
44
|
export interface MaterialShaderArtifact {
|
|
3
45
|
readonly material: string;
|
|
4
46
|
readonly pass: string;
|
|
5
47
|
readonly wgsl: string;
|
|
6
48
|
readonly layoutIdentity: string;
|
|
49
|
+
/** Naga-reflected UV set count used to derive clamp-to-last vertex aliases. */
|
|
50
|
+
readonly uvSetCount?: number;
|
|
7
51
|
readonly bindings: readonly BindGroupLayoutDescriptor[];
|
|
8
52
|
readonly deps: readonly string[];
|
|
9
53
|
readonly vertexInputs: readonly Readonly<Record<string, unknown>>[];
|
|
10
54
|
readonly specializationKey?: string;
|
|
55
|
+
/** Producer-owned ABI facts shared by direct and scene-index consumers. */
|
|
56
|
+
readonly receipt?: MaterialShaderArtifactReceipt;
|
|
11
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Builds the one Standard PBR ABI receipt consumed by both material lanes.
|
|
60
|
+
* The skinned module uses the same material producer and only adds its palette
|
|
61
|
+
* address; it must retain the same receipt identity for material compatibility.
|
|
62
|
+
*/
|
|
63
|
+
export declare function createStandardPbrArtifactReceipt(skinned?: boolean): MaterialShaderArtifactReceipt;
|
|
12
64
|
export declare function isMaterialShaderArtifact(value: unknown): value is MaterialShaderArtifact;
|
|
13
65
|
//# 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,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAEvE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,yBAAyB,EAAE,CAAC;IACxD,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;IACpE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"artifact-types.d.ts","sourceRoot":"","sources":["../../src/material/artifact-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAEvE,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;KACpC,CAAC;IACF,QAAQ,CAAC,aAAa,EAAE,SAAS,0BAA0B,EAAE,CAAC;IAC9D,QAAQ,CAAC,MAAM,EAAE,SAAS;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjF,QAAQ,CAAC,YAAY,EAAE,SAAS,yBAAyB,EAAE,CAAC;IAC5D,QAAQ,CAAC,SAAS,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzE,QAAQ,CAAC,kBAAkB,CAAC,EAAE;QAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,QAAQ,CAAC,UAAU,EAAE;QACnB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,aAAa,EAAE,SAAS,0BAA0B,EAAE,CAAC;QAC9D,QAAQ,CAAC,YAAY,EAAE,SAAS,yBAAyB,EAAE,CAAC;KAC7D,CAAC;IACF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,yBAAyB,EAAE,CAAC;IACxD,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;IACpE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,CAAC,EAAE,6BAA6B,CAAC;CAClD;AA8DD;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,UAAQ,GAAG,6BAA6B,CA2B/F;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,CAYxF"}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { ParamSchemaEntry } from '@forgeax/engine-types';
|
|
2
|
+
import { type MaterialShaderArtifactReceipt } from './material/artifact-types.js';
|
|
2
3
|
export declare const STANDARD_PBR_ALPHA_CUTOFF_DEFAULT = 0;
|
|
3
4
|
/** Shared material contract for the standard PBR and skinned PBR shaders. */
|
|
4
5
|
export declare const DEFAULT_STANDARD_PBR_PARAM_SCHEMA: readonly ParamSchemaEntry[];
|
|
6
|
+
/** The single producer receipt shared by direct and scene-index Standard PBR. */
|
|
7
|
+
export declare const STANDARD_PBR_ARTIFACT_RECEIPT: MaterialShaderArtifactReceipt;
|
|
8
|
+
/** Skinned Standard PBR keeps the material receipt identity and adds palette ABI. */
|
|
9
|
+
export declare const STANDARD_PBR_SKIN_ARTIFACT_RECEIPT: MaterialShaderArtifactReceipt;
|
|
5
10
|
export declare const DEFAULT_UNLIT_PARAM_SCHEMA: readonly ParamSchemaEntry[];
|
|
6
11
|
export declare const DEFAULT_SPRITE_PARAM_SCHEMA: readonly ParamSchemaEntry[];
|
|
7
12
|
export declare const DEFAULT_MSDF_TEXT_PARAM_SCHEMA: readonly ParamSchemaEntry[];
|
|
@@ -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;
|
|
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;AAC9D,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,8BAA8B,CAAC;AAEtC,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAEnD,6EAA6E;AAC7E,eAAO,MAAM,iCAAiC,EAAE,SAAS,gBAAgB,EA6BxE,CAAC;AAEF,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"}
|
|
@@ -22,12 +22,9 @@ export interface SpriteLitCaps {
|
|
|
22
22
|
* compiled by @forgeax/engine-vite-plugin-shader at build time and surfaced
|
|
23
23
|
* in manifest.json. The caller reads it from the manifest entries.
|
|
24
24
|
*
|
|
25
|
-
* `caps.storageBuffer`
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* The BGL itself is built downstream in runtime/pbr-pipeline.ts (sprite-lit
|
|
29
|
-
* reuses pbr-view / pbr-mesh-array / pbr-instances unchanged; AC-07
|
|
30
|
-
* BGL byte-identical).
|
|
25
|
+
* `caps.storageBuffer` is retained for the shared mesh/instance layout
|
|
26
|
+
* selection. Sprite-lit local lighting reads the same Standard Cluster group
|
|
27
|
+
* as PBR; the view group has no direct point/spot arrays.
|
|
31
28
|
*
|
|
32
29
|
* @param registry ShaderRegistry instance (engine boot path).
|
|
33
30
|
* @param composedWgsl Post-naga_oil composed WGSL source string.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-default-sprite-lit.d.ts","sourceRoot":"","sources":["../src/register-default-sprite-lit.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAiBjD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED
|
|
1
|
+
{"version":3,"file":"register-default-sprite-lit.d.ts","sourceRoot":"","sources":["../src/register-default-sprite-lit.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAiBjD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,cAAc,EACxB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,aAAa,GAClB,IAAI,CAUN"}
|
package/dist/types.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* no longer carry a bindingLayout sidecar (§Change stance: no v1/v2
|
|
12
12
|
* dual-path, no incremental drop window).
|
|
13
13
|
*/
|
|
14
|
+
import type { MaterialShaderArtifactReceipt } from './material/artifact-types.js';
|
|
14
15
|
export interface MaterialShaderManifestVariant {
|
|
15
16
|
readonly definesKey: string;
|
|
16
17
|
readonly defines: Record<string, boolean>;
|
|
@@ -26,7 +27,7 @@ export interface MaterialShaderManifestVariant {
|
|
|
26
27
|
*
|
|
27
28
|
* For Engine.create() internal use only; AI users never see this type.
|
|
28
29
|
*/
|
|
29
|
-
export interface MaterialShaderManifestEntry {
|
|
30
|
+
export interface MaterialShaderManifestEntry extends Partial<MaterialShaderArtifactReceipt> {
|
|
30
31
|
readonly identifier: string;
|
|
31
32
|
readonly sourcePath: string;
|
|
32
33
|
readonly composedWgsl: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAClF,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,2BAA4B,SAAQ,OAAO,CAAC,6BAA6B,CAAC;IACzF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,6BAA6B,EAAE,CAAC;IAC5D;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,2BAA2B,EAClC,UAAU,EAAE,MAAM,GACjB,6BAA6B,GAAG,SAAS,CAE3C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-shader",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@forgeax/engine-rhi": "0.1.
|
|
26
|
-
"@forgeax/engine-types": "0.1.
|
|
25
|
+
"@forgeax/engine-rhi": "0.1.23",
|
|
26
|
+
"@forgeax/engine-types": "0.1.23",
|
|
27
27
|
"@webgpu/types": "^0.1.71"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@forgeax/engine-vite-plugin-shader": "0.1.
|
|
30
|
+
"@forgeax/engine-vite-plugin-shader": "0.1.23"
|
|
31
31
|
},
|
|
32
32
|
"forgeax": {
|
|
33
33
|
"metrics": {
|
package/src/ShaderRegistry.ts
CHANGED
|
@@ -122,6 +122,36 @@ export interface ShaderRegistryOptions {
|
|
|
122
122
|
readonly manifestUrl: string | undefined;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
type ShaderManifestDiagnostic = {
|
|
126
|
+
readonly name: string;
|
|
127
|
+
readonly startedAt: number;
|
|
128
|
+
readonly previousElapsedMs: number;
|
|
129
|
+
readonly url?: string;
|
|
130
|
+
readonly status?: number;
|
|
131
|
+
readonly responseUrl?: string;
|
|
132
|
+
readonly entryCount?: number;
|
|
133
|
+
readonly materialShaderCount?: number;
|
|
134
|
+
readonly error?: string;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
function markShaderManifestStage(
|
|
138
|
+
name: string,
|
|
139
|
+
detail: Omit<ShaderManifestDiagnostic, 'name' | 'startedAt' | 'previousElapsedMs'> = {},
|
|
140
|
+
): void {
|
|
141
|
+
if (typeof globalThis === 'undefined') return;
|
|
142
|
+
const host = globalThis as {
|
|
143
|
+
__forgeaxShaderManifest?: ShaderManifestDiagnostic;
|
|
144
|
+
};
|
|
145
|
+
const previous = host.__forgeaxShaderManifest;
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
host.__forgeaxShaderManifest = {
|
|
148
|
+
name,
|
|
149
|
+
startedAt: now,
|
|
150
|
+
previousElapsedMs: previous === undefined ? 0 : now - previous.startedAt,
|
|
151
|
+
...detail,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
125
155
|
// ─── ShaderRegistry main class (instance-per-engine) ============================
|
|
126
156
|
|
|
127
157
|
/**
|
|
@@ -194,10 +224,35 @@ export class ShaderRegistry {
|
|
|
194
224
|
}
|
|
195
225
|
|
|
196
226
|
let raw: string;
|
|
227
|
+
markShaderManifestStage('shader-manifest-fetch-start', { url: this.#manifestUrl });
|
|
197
228
|
try {
|
|
198
229
|
const response = await fetch(this.#manifestUrl);
|
|
230
|
+
markShaderManifestStage('shader-manifest-response', {
|
|
231
|
+
url: this.#manifestUrl,
|
|
232
|
+
status: response.status,
|
|
233
|
+
responseUrl: response.url,
|
|
234
|
+
});
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
markShaderManifestStage('shader-manifest-http-failed', {
|
|
237
|
+
url: this.#manifestUrl,
|
|
238
|
+
status: response.status,
|
|
239
|
+
responseUrl: response.url,
|
|
240
|
+
error: `HTTP ${response.status} ${response.statusText}`,
|
|
241
|
+
});
|
|
242
|
+
return err(
|
|
243
|
+
manifestMalformed({
|
|
244
|
+
message: `ShaderRegistry: manifest request returned HTTP ${response.status}`,
|
|
245
|
+
hint: 'verify the dev manifest middleware or production manifest asset is reachable',
|
|
246
|
+
reason: `${response.statusText || 'HTTP error'} at ${this.#manifestUrl}`,
|
|
247
|
+
}),
|
|
248
|
+
);
|
|
249
|
+
}
|
|
199
250
|
raw = await response.text();
|
|
200
251
|
} catch (e) {
|
|
252
|
+
markShaderManifestStage('shader-manifest-fetch-failed', {
|
|
253
|
+
url: this.#manifestUrl,
|
|
254
|
+
error: e instanceof Error ? e.message : String(e),
|
|
255
|
+
});
|
|
201
256
|
return err(
|
|
202
257
|
manifestMalformed({
|
|
203
258
|
message: `ShaderRegistry: failed to fetch manifest at ${this.#manifestUrl}`,
|
|
@@ -211,6 +266,10 @@ export class ShaderRegistry {
|
|
|
211
266
|
try {
|
|
212
267
|
parsed = JSON.parse(raw);
|
|
213
268
|
} catch (e) {
|
|
269
|
+
markShaderManifestStage('shader-manifest-json-failed', {
|
|
270
|
+
url: this.#manifestUrl,
|
|
271
|
+
error: e instanceof Error ? e.message : String(e),
|
|
272
|
+
});
|
|
214
273
|
return err(
|
|
215
274
|
manifestMalformed({
|
|
216
275
|
message: 'ShaderRegistry: manifest JSON parse failed',
|
|
@@ -325,6 +384,12 @@ export class ShaderRegistry {
|
|
|
325
384
|
this.#materialShaderManifestEntries.length = 0;
|
|
326
385
|
this.#materialShaderManifestEntries.push(...validatedMaterialShaderEntries);
|
|
327
386
|
this.#manifestLoaded = true;
|
|
387
|
+
markShaderManifestStage('shader-manifest-validated', {
|
|
388
|
+
url: this.#manifestUrl,
|
|
389
|
+
status: 200,
|
|
390
|
+
entryCount: validatedEntries.length,
|
|
391
|
+
materialShaderCount: validatedMaterialShaderEntries.length,
|
|
392
|
+
});
|
|
328
393
|
return ok(undefined);
|
|
329
394
|
}
|
|
330
395
|
|
|
@@ -51,7 +51,10 @@ describe('post shader color-domain contract', () => {
|
|
|
51
51
|
}
|
|
52
52
|
expect(body).toMatch(/default:\s*\{\s*mapped\s*=\s*sample;\s*\}/);
|
|
53
53
|
expect(body).toContain('let encoded = linearToSrgbOetf(mapped);');
|
|
54
|
-
expect(body).toContain('
|
|
54
|
+
expect(body).toContain('let output = select(');
|
|
55
|
+
expect(body).toContain('ditherUnorm8(encoded, in.position.xy)');
|
|
56
|
+
expect(body).toContain('params.ditherEnabled > 0.5');
|
|
57
|
+
expect(body).toContain('return vec4<f32>(output, source.a);');
|
|
55
58
|
expect(body).not.toContain('linearToSrgbOetf(exposed)');
|
|
56
59
|
});
|
|
57
60
|
|
|
@@ -84,13 +87,36 @@ describe('post shader color-domain contract', () => {
|
|
|
84
87
|
expect(`${tonemap}\n${bloom}\n${fxaa}`).not.toContain('encodedDestinationBlend');
|
|
85
88
|
});
|
|
86
89
|
|
|
87
|
-
it('returns display-encoded RGB
|
|
90
|
+
it('returns display-encoded RGB from both FXAA exits through the dither gate', () => {
|
|
88
91
|
const body = functionBody(codeOnly(fxaa), 'fs_main');
|
|
89
|
-
expect(
|
|
90
|
-
expect(
|
|
92
|
+
expect(fxaa).toContain('struct FxaaParams');
|
|
93
|
+
expect(fxaa).toMatch(/@group\(0\)\s+@binding\(2\)\s+var<uniform> params/);
|
|
94
|
+
expect(body).toContain('params.ditherEnabled > 0.5');
|
|
95
|
+
expect(body).toContain('select(centerColor, ditherUnorm8(centerColor, in.position.xy)');
|
|
96
|
+
expect(body).toContain('select(finalColor, ditherUnorm8(finalColor, in.position.xy)');
|
|
91
97
|
expect(body).not.toMatch(/linearToSrgbOetf\s*\(/);
|
|
92
98
|
});
|
|
93
99
|
|
|
100
|
+
it('dithers the final FXAA output before the 8-bit surface quantization', () => {
|
|
101
|
+
const code = codeOnly(fxaa);
|
|
102
|
+
const body = functionBody(code, 'fs_main');
|
|
103
|
+
expect(common).toContain('fn ditherUnorm8');
|
|
104
|
+
expect(common).toContain('fn ditherNoise');
|
|
105
|
+
expect(code).not.toContain('fn ditherUnorm8');
|
|
106
|
+
expect(code).not.toContain('fn ditherNoise');
|
|
107
|
+
expect(code).toContain('in.position.xy');
|
|
108
|
+
expect(body).toContain('ditherUnorm8(centerColor, in.position.xy)');
|
|
109
|
+
expect(body).toContain('ditherUnorm8(finalColor, in.position.xy)');
|
|
110
|
+
|
|
111
|
+
const quantize = (value: number, noise: number): number =>
|
|
112
|
+
Math.round(Math.min(1, Math.max(0, value + (0.5 - noise) * (1 / 255))) * 255);
|
|
113
|
+
expect(quantize(0.5, 0)).toBe(128);
|
|
114
|
+
expect(quantize(0.5, 1)).toBe(127);
|
|
115
|
+
expect(quantize(0, 0)).toBe(1);
|
|
116
|
+
expect(quantize(0, 1)).toBe(0);
|
|
117
|
+
expect(quantize(1, 1)).toBe(255);
|
|
118
|
+
});
|
|
119
|
+
|
|
94
120
|
it('retains FXAA luma, edge search, and subpixel contract markers', () => {
|
|
95
121
|
const code = codeOnly(fxaa);
|
|
96
122
|
expect(code).toContain('fn rgb2luma');
|
|
@@ -16,4 +16,11 @@ describe('standard PBR MASK alpha contract', () => {
|
|
|
16
16
|
expect(shader).toContain('alpha <= material.alphaCutoff');
|
|
17
17
|
expect(shader).not.toContain('alpha < material.alphaCutoff');
|
|
18
18
|
});
|
|
19
|
+
|
|
20
|
+
it('keeps alpha PBR on the shared Directional factor path', () => {
|
|
21
|
+
expect(shader).toContain('lighting_directional');
|
|
22
|
+
expect(shader.match(/evalDirectionalShadowFactor\(/g)).toHaveLength(1);
|
|
23
|
+
expect(shader).toContain('directionalBase');
|
|
24
|
+
expect(shader).toContain('directionalClearcoat');
|
|
25
|
+
});
|
|
19
26
|
});
|
|
@@ -19,13 +19,17 @@ function device(): ShaderRegistryDevice {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async function engineManifest(): Promise<EngineShaderManifest> {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
engineManifestPromise ??= import('@forgeax/engine-vite-plugin-shader').then(
|
|
23
|
+
({ buildEngineShaderManifest }) => buildEngineShaderManifest(),
|
|
24
|
+
);
|
|
25
|
+
return engineManifestPromise;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
let engineManifestPromise: Promise<EngineShaderManifest> | undefined;
|
|
29
|
+
|
|
26
30
|
describe('default Standard PBR transmission manifest contract', () => {
|
|
27
31
|
it('publishes both TRANSMISSION_AVAILABLE variants from the cooked producer', {
|
|
28
|
-
timeout:
|
|
32
|
+
timeout: 60_000,
|
|
29
33
|
}, async () => {
|
|
30
34
|
const manifest = await engineManifest();
|
|
31
35
|
const standard = manifest.materialShaders.find(
|
|
@@ -37,38 +41,138 @@ describe('default Standard PBR transmission manifest contract', () => {
|
|
|
37
41
|
const transmissionVariants = standard.variants.filter(
|
|
38
42
|
(variant) => 'TRANSMISSION_AVAILABLE' in variant.defines,
|
|
39
43
|
);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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. Keep the
|
|
47
|
+
// expected keys derived here so adding a new axis cannot silently shrink
|
|
48
|
+
// the published set.
|
|
49
|
+
const expectedKeys = new Set<string>();
|
|
50
|
+
for (const clusterForwardAvailable of [false, true]) {
|
|
51
|
+
for (const storageBufferAvailable of [false, true]) {
|
|
52
|
+
for (const vertexColorAvailable of [false, true]) {
|
|
53
|
+
for (const probeBlendAvailable of [false, true]) {
|
|
54
|
+
for (const extendedLightingAvailable of [false, true]) {
|
|
55
|
+
for (const directionalPcssAvailable of [false, true]) {
|
|
56
|
+
for (const transmissionAvailable of [false, true]) {
|
|
57
|
+
for (const projectorAvailable of [false, true]) {
|
|
58
|
+
for (const reflectionFallbackAvailable of [false, true]) {
|
|
59
|
+
if (
|
|
60
|
+
(clusterForwardAvailable && !storageBufferAvailable) ||
|
|
61
|
+
(probeBlendAvailable && !storageBufferAvailable) ||
|
|
62
|
+
(extendedLightingAvailable &&
|
|
63
|
+
(!storageBufferAvailable || !projectorAvailable))
|
|
64
|
+
) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const expectedKeyEntries = [
|
|
68
|
+
`CLUSTER_FORWARD_AVAILABLE=${clusterForwardAvailable}`,
|
|
69
|
+
`DIRECTIONAL_PCSS_AVAILABLE=${directionalPcssAvailable}`,
|
|
70
|
+
`EXTENDED_LIGHTING_AVAILABLE=${extendedLightingAvailable}`,
|
|
71
|
+
`PROBE_BLEND_AVAILABLE=${probeBlendAvailable}`,
|
|
72
|
+
`PROJECTOR_AVAILABLE=${projectorAvailable}`,
|
|
73
|
+
`REFLECTION_FALLBACK_AVAILABLE=${reflectionFallbackAvailable}`,
|
|
74
|
+
`STORAGE_BUFFER_AVAILABLE=${storageBufferAvailable}`,
|
|
75
|
+
`TRANSMISSION_AVAILABLE=${transmissionAvailable}`,
|
|
76
|
+
`VERTEX_COLOR_AVAILABLE=${vertexColorAvailable}`,
|
|
77
|
+
];
|
|
78
|
+
expectedKeys.add(
|
|
79
|
+
expectedKeyEntries.every((entry) => entry.endsWith('=true'))
|
|
80
|
+
? ''
|
|
81
|
+
: expectedKeyEntries.join('+'),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
expect(transmissionVariants).toHaveLength(expectedKeys.size);
|
|
93
|
+
expect(new Set(transmissionVariants.map((variant) => variant.definesKey))).toEqual(
|
|
94
|
+
expectedKeys,
|
|
95
|
+
);
|
|
96
|
+
expect(
|
|
97
|
+
transmissionVariants.every(
|
|
98
|
+
(variant) =>
|
|
99
|
+
!(
|
|
100
|
+
variant.defines.CLUSTER_FORWARD_AVAILABLE === true &&
|
|
101
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === false
|
|
102
|
+
) &&
|
|
103
|
+
!(
|
|
104
|
+
variant.defines.PROBE_BLEND_AVAILABLE === true &&
|
|
105
|
+
variant.defines.STORAGE_BUFFER_AVAILABLE === false
|
|
106
|
+
) &&
|
|
107
|
+
!(
|
|
108
|
+
variant.defines.EXTENDED_LIGHTING_AVAILABLE === true &&
|
|
109
|
+
(variant.defines.STORAGE_BUFFER_AVAILABLE === false ||
|
|
110
|
+
variant.defines.PROJECTOR_AVAILABLE === false)
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
).toBe(true);
|
|
114
|
+
for (const clusterForwardAvailable of [false, true]) {
|
|
115
|
+
for (const storageBufferAvailable of [false, true]) {
|
|
116
|
+
if (clusterForwardAvailable && !storageBufferAvailable) continue;
|
|
117
|
+
for (const vertexColorAvailable of [false, true]) {
|
|
118
|
+
for (const probeBlendAvailable of [false, true]) {
|
|
119
|
+
for (const extendedLightingAvailable of [false, true]) {
|
|
120
|
+
for (const directionalPcssAvailable of [false, true]) {
|
|
121
|
+
for (const transmissionAvailable of [false, true]) {
|
|
122
|
+
for (const projectorAvailable of [false, true]) {
|
|
123
|
+
for (const reflectionFallbackAvailable of [false, true]) {
|
|
124
|
+
if (
|
|
125
|
+
(probeBlendAvailable && !storageBufferAvailable) ||
|
|
126
|
+
(extendedLightingAvailable &&
|
|
127
|
+
(!storageBufferAvailable || !projectorAvailable))
|
|
128
|
+
) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const expectedKeyEntries = [
|
|
132
|
+
`CLUSTER_FORWARD_AVAILABLE=${clusterForwardAvailable}`,
|
|
133
|
+
`DIRECTIONAL_PCSS_AVAILABLE=${directionalPcssAvailable}`,
|
|
134
|
+
`EXTENDED_LIGHTING_AVAILABLE=${extendedLightingAvailable}`,
|
|
135
|
+
`PROBE_BLEND_AVAILABLE=${probeBlendAvailable}`,
|
|
136
|
+
`PROJECTOR_AVAILABLE=${projectorAvailable}`,
|
|
137
|
+
`REFLECTION_FALLBACK_AVAILABLE=${reflectionFallbackAvailable}`,
|
|
138
|
+
`STORAGE_BUFFER_AVAILABLE=${storageBufferAvailable}`,
|
|
139
|
+
`TRANSMISSION_AVAILABLE=${transmissionAvailable}`,
|
|
140
|
+
`VERTEX_COLOR_AVAILABLE=${vertexColorAvailable}`,
|
|
141
|
+
];
|
|
142
|
+
const expectedKey = expectedKeyEntries.every((entry) =>
|
|
143
|
+
entry.endsWith('=true'),
|
|
144
|
+
)
|
|
145
|
+
? ''
|
|
146
|
+
: expectedKeyEntries.join('+');
|
|
147
|
+
expect(transmissionVariants).toContainEqual(
|
|
148
|
+
expect.objectContaining({
|
|
149
|
+
definesKey: expectedKey,
|
|
150
|
+
defines: expect.objectContaining({
|
|
151
|
+
CLUSTER_FORWARD_AVAILABLE: clusterForwardAvailable,
|
|
152
|
+
DIRECTIONAL_PCSS_AVAILABLE: directionalPcssAvailable,
|
|
153
|
+
EXTENDED_LIGHTING_AVAILABLE: extendedLightingAvailable,
|
|
154
|
+
PROBE_BLEND_AVAILABLE: probeBlendAvailable,
|
|
155
|
+
PROJECTOR_AVAILABLE: projectorAvailable,
|
|
156
|
+
REFLECTION_FALLBACK_AVAILABLE: reflectionFallbackAvailable,
|
|
157
|
+
STORAGE_BUFFER_AVAILABLE: storageBufferAvailable,
|
|
158
|
+
TRANSMISSION_AVAILABLE: transmissionAvailable,
|
|
159
|
+
VERTEX_COLOR_AVAILABLE: vertexColorAvailable,
|
|
160
|
+
}),
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
65
169
|
}
|
|
66
170
|
}
|
|
67
171
|
}
|
|
68
172
|
});
|
|
69
173
|
|
|
70
174
|
it('omits transmission-only texture bindings from the fallback variant', {
|
|
71
|
-
timeout:
|
|
175
|
+
timeout: 60_000,
|
|
72
176
|
}, async () => {
|
|
73
177
|
const manifest = await engineManifest();
|
|
74
178
|
const standard = manifest.materialShaders.find(
|
|
@@ -81,6 +185,8 @@ describe('default Standard PBR transmission manifest contract', () => {
|
|
|
81
185
|
(variant) =>
|
|
82
186
|
variant.defines.CLUSTER_FORWARD_AVAILABLE === false &&
|
|
83
187
|
variant.defines.STORAGE_BUFFER_AVAILABLE === true &&
|
|
188
|
+
variant.defines.EXTENDED_LIGHTING_AVAILABLE === false &&
|
|
189
|
+
variant.defines.PROBE_BLEND_AVAILABLE === false &&
|
|
84
190
|
variant.defines.TRANSMISSION_AVAILABLE === false &&
|
|
85
191
|
variant.defines.VERTEX_COLOR_AVAILABLE === false,
|
|
86
192
|
);
|
|
@@ -88,6 +194,8 @@ describe('default Standard PBR transmission manifest contract', () => {
|
|
|
88
194
|
(variant) =>
|
|
89
195
|
variant.defines.CLUSTER_FORWARD_AVAILABLE === false &&
|
|
90
196
|
variant.defines.STORAGE_BUFFER_AVAILABLE === true &&
|
|
197
|
+
variant.defines.EXTENDED_LIGHTING_AVAILABLE === false &&
|
|
198
|
+
variant.defines.PROBE_BLEND_AVAILABLE === false &&
|
|
91
199
|
variant.defines.TRANSMISSION_AVAILABLE === true &&
|
|
92
200
|
variant.defines.VERTEX_COLOR_AVAILABLE === false,
|
|
93
201
|
);
|
|
@@ -98,7 +206,7 @@ describe('default Standard PBR transmission manifest contract', () => {
|
|
|
98
206
|
});
|
|
99
207
|
|
|
100
208
|
it('registers one Standard row and resolves a declared variant by exact key', {
|
|
101
|
-
timeout:
|
|
209
|
+
timeout: 60_000,
|
|
102
210
|
}, async () => {
|
|
103
211
|
const manifest = await engineManifest();
|
|
104
212
|
const standard = manifest.materialShaders.find(
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
async function readShader(name: string): Promise<string> {
|
|
5
|
+
return readFile(new URL(`../${name}`, import.meta.url), 'utf8');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
describe('DirectLightSlot WGSL ABI', () => {
|
|
9
|
+
it('declares the canonical five-row 80-byte slot', async () => {
|
|
10
|
+
const common = await readShader('common.wgsl');
|
|
11
|
+
const match = common.match(/struct DirectLightSlot\s*\{([\s\S]*?)\};/);
|
|
12
|
+
|
|
13
|
+
expect(match).not.toBeNull();
|
|
14
|
+
const struct = match?.[1] ?? '';
|
|
15
|
+
expect(struct.match(/vec4<f32>/g)).toHaveLength(4);
|
|
16
|
+
expect(struct.match(/vec4<u32>/g)).toHaveLength(1);
|
|
17
|
+
expect(struct).toContain('row0');
|
|
18
|
+
expect(struct).toContain('row1');
|
|
19
|
+
expect(struct).toContain('row2');
|
|
20
|
+
expect(struct).toContain('row3');
|
|
21
|
+
expect(struct).toContain('metadata : vec4<u32>');
|
|
22
|
+
expect(common).not.toContain('struct DirectLightArray');
|
|
23
|
+
expect(common).not.toContain('array<DirectLightSlot, 4>');
|
|
24
|
+
expect(common).toContain('@group(0) @binding(0)');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('keeps the slot type owned by the unified Cluster shader consumer', async () => {
|
|
28
|
+
const common = await readShader('common.wgsl');
|
|
29
|
+
const standard = await readShader('default-standard-pbr.wgsl');
|
|
30
|
+
const clustered = await readShader('standard-cluster.wgsl');
|
|
31
|
+
|
|
32
|
+
expect(common).toContain('struct DirectLightSlot');
|
|
33
|
+
expect(standard).not.toContain('DirectLightSlot');
|
|
34
|
+
expect(standard).not.toContain('pointLightsBuffer');
|
|
35
|
+
expect(standard).not.toContain('spotLightsBuffer');
|
|
36
|
+
expect(clustered).toContain('DirectLightSlot');
|
|
37
|
+
expect(clustered).toContain('array<DirectLightSlot, 256>');
|
|
38
|
+
expect(clustered).not.toContain('struct LightSlot');
|
|
39
|
+
});
|
|
40
|
+
});
|