@forgeax/engine-shader 0.1.2
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/LICENSE +202 -0
- package/README.md +99 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/ShaderRegistry.d.ts +208 -0
- package/dist/ShaderRegistry.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-background.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-background.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts.map +1 -0
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts +2 -0
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts.map +1 -0
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts +2 -0
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts.map +1 -0
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts +2 -0
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts.map +1 -0
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts +2 -0
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts.map +1 -0
- package/dist/__tests__/fog-oracle.unit.test.d.ts +2 -0
- package/dist/__tests__/fog-oracle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts +2 -0
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts +2 -0
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts.map +1 -0
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ibl-sampling.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl-sampling.unit.test.d.ts.map +1 -0
- package/dist/__tests__/lighting-punctual.unit.test.d.ts +2 -0
- package/dist/__tests__/lighting-punctual.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts +2 -0
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-builtins.unit.test.d.ts +2 -0
- package/dist/__tests__/material-builtins.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/material-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts +2 -0
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts.map +1 -0
- package/dist/__tests__/public-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/public-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-temporal.unit.test.d.ts +2 -0
- package/dist/__tests__/scene-temporal.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts +2 -0
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shader.unit.test.d.ts +2 -0
- package/dist/__tests__/shader.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-lit-shader.test.d.ts +2 -0
- package/dist/__tests__/sprite-lit-shader.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-variants.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-variants.unit.test.d.ts.map +1 -0
- package/dist/__tests__/taa-resolve.unit.test.d.ts +2 -0
- package/dist/__tests__/taa-resolve.unit.test.d.ts.map +1 -0
- package/dist/__tests__/transparent-pbr.unit.test.d.ts +2 -0
- package/dist/__tests__/transparent-pbr.unit.test.d.ts.map +1 -0
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts +2 -0
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts.map +1 -0
- package/dist/errors.d.ts +96 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +560 -0
- package/dist/index.mjs.map +1 -0
- package/dist/material/artifact-registry.d.ts +31 -0
- package/dist/material/artifact-registry.d.ts.map +1 -0
- package/dist/material/artifact-types.d.ts +13 -0
- package/dist/material/artifact-types.d.ts.map +1 -0
- package/dist/material-schemas.d.ts +8 -0
- package/dist/material-schemas.d.ts.map +1 -0
- package/dist/register-default-sprite-lit.d.ts +40 -0
- package/dist/register-default-sprite-lit.d.ts.map +1 -0
- package/dist/register-default-standard-pbr-skin.d.ts +34 -0
- package/dist/register-default-standard-pbr-skin.d.ts.map +1 -0
- package/dist/tonemap.d.ts +13 -0
- package/dist/tonemap.d.ts.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/ShaderRegistry.ts +519 -0
- package/src/__tests__/atmosphere-background.test.ts +33 -0
- package/src/__tests__/atmosphere-cubemap-orientation.test.ts +68 -0
- package/src/__tests__/atmosphere-preetham-numeric.test.ts +122 -0
- package/src/__tests__/atmosphere-preetham-red.test.ts +52 -0
- package/src/__tests__/atmosphere-preetham.test.ts +132 -0
- package/src/__tests__/atmosphere-three-r184-reference.test.ts +122 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +31 -0
- package/src/__tests__/builtin-texture-sampling-contract.test.ts +56 -0
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +19 -0
- package/src/__tests__/deferred-lighting-ssao.test.ts +114 -0
- package/src/__tests__/fog-oracle.unit.test.ts +132 -0
- package/src/__tests__/fog-orthographic-ray.unit.test.ts +62 -0
- package/src/__tests__/fog-producer-matrix.integration.test.ts +77 -0
- package/src/__tests__/hdrp-ssao-compile.test.ts +185 -0
- package/src/__tests__/ibl-irradiance.unit.test.ts +43 -0
- package/src/__tests__/ibl-sampling.unit.test.ts +40 -0
- package/src/__tests__/lighting-punctual.unit.test.ts +66 -0
- package/src/__tests__/manifest.fixture.json +22 -0
- package/src/__tests__/material-artifact-dedup.unit.test.ts +21 -0
- package/src/__tests__/material-builtins.unit.test.ts +111 -0
- package/src/__tests__/material-contract.unit.test.ts +79 -0
- package/src/__tests__/material-derived-builtins.integration.test.ts +30 -0
- package/src/__tests__/public-surface.unit.test.ts +9 -0
- package/src/__tests__/scene-temporal.unit.test.ts +32 -0
- package/src/__tests__/shader-registry-transaction.unit.test.ts +116 -0
- package/src/__tests__/shader.unit.test.ts +1909 -0
- package/src/__tests__/sprite-lit-shader.test.ts +240 -0
- package/src/__tests__/sprite-variants.unit.test.ts +209 -0
- package/src/__tests__/taa-resolve.unit.test.ts +32 -0
- package/src/__tests__/transparent-pbr.unit.test.ts +21 -0
- package/src/__tests__/vertex-color-variant.unit.test.ts +52 -0
- package/src/atmosphere-background.wgsl +80 -0
- package/src/atmosphere-cubemap.wgsl +72 -0
- package/src/atmosphere-ibl.wgsl +127 -0
- package/src/atmosphere-preetham.wgsl +214 -0
- package/src/bloom-blur.wgsl +89 -0
- package/src/bloom-bright.wgsl +50 -0
- package/src/bloom-composite.wgsl +54 -0
- package/src/brdf.wgsl +25 -0
- package/src/common.wgsl +523 -0
- package/src/default-standard-pbr-skin.wgsl +538 -0
- package/src/default-standard-pbr.wgsl +794 -0
- package/src/errors.ts +169 -0
- package/src/fog.wgsl +27 -0
- package/src/fxaa.wgsl +259 -0
- package/src/hdrp-cluster-forward.wgsl +186 -0
- package/src/hdrp-deferred-lighting.wgsl +69 -0
- package/src/hdrp-ssao.wgsl +174 -0
- package/src/ibl-brdf-lut.wgsl +92 -0
- package/src/ibl-equirect-to-cube.wgsl +56 -0
- package/src/ibl-irradiance.wgsl +89 -0
- package/src/ibl-prefilter.wgsl +101 -0
- package/src/ibl-sampling.wgsl +58 -0
- package/src/ibl-shared.wgsl +118 -0
- package/src/index.ts +166 -0
- package/src/lighting-directional.wgsl +471 -0
- package/src/lighting-punctual.wgsl +274 -0
- package/src/material/artifact-registry.ts +78 -0
- package/src/material/artifact-types.ts +26 -0
- package/src/material-schemas.ts +48 -0
- package/src/msdf-text.wgsl +208 -0
- package/src/pbr-temporal.wgsl +73 -0
- package/src/register-default-sprite-lit.ts +87 -0
- package/src/register-default-standard-pbr-skin.ts +74 -0
- package/src/shadow-pcf.wgsl +120 -0
- package/src/shadow_caster.wgsl +54 -0
- package/src/skybox.wgsl +75 -0
- package/src/sprite-lit.wgsl +307 -0
- package/src/sprite.wgsl +365 -0
- package/src/taa-resolve.wgsl +129 -0
- package/src/tbn.wgsl +54 -0
- package/src/tonemap.ts +13 -0
- package/src/tonemap.wgsl +177 -0
- package/src/types.ts +59 -0
- package/src/unlit.wgsl +186 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import { ok, err, ParamSchemaProjectionOwner, findUndeclaredSampledTextures } from '@forgeax/engine-types';
|
|
2
|
+
export { MATERIAL_PARAM_TYPES } from '@forgeax/engine-types';
|
|
3
|
+
import { ok as ok$1, err as err$1 } from '@forgeax/engine-rhi';
|
|
4
|
+
export { err, ok } from '@forgeax/engine-rhi';
|
|
5
|
+
|
|
6
|
+
// src/material-schemas.ts
|
|
7
|
+
var STANDARD_PBR_ALPHA_CUTOFF_DEFAULT = 0;
|
|
8
|
+
var DEFAULT_STANDARD_PBR_PARAM_SCHEMA = [
|
|
9
|
+
{ name: "baseColor", type: "color", default: [1, 1, 1, 1] },
|
|
10
|
+
{ name: "metallic", type: "f32", default: 0 },
|
|
11
|
+
{ name: "roughness", type: "f32", default: 0.5 },
|
|
12
|
+
{ name: "metallicChannel", type: "f32", default: 2 },
|
|
13
|
+
{ name: "roughnessChannel", type: "f32", default: 1 },
|
|
14
|
+
{ name: "aoChannel", type: "f32", default: 0 },
|
|
15
|
+
{ name: "extraChannel", type: "f32", default: 0 },
|
|
16
|
+
{ name: "emissive", type: "vec3", colorSpace: "srgb", default: [0, 0, 0] },
|
|
17
|
+
{ name: "emissiveIntensity", type: "f32", default: 0 },
|
|
18
|
+
{ name: "occlusionStrength", type: "f32", default: 1 },
|
|
19
|
+
{ name: "alphaCutoff", type: "f32", default: STANDARD_PBR_ALPHA_CUTOFF_DEFAULT },
|
|
20
|
+
{ name: "clearcoat", type: "f32", default: 0 },
|
|
21
|
+
{ name: "clearcoatRoughness", type: "f32", default: 0.5 },
|
|
22
|
+
{ name: "specularTint", type: "vec3", colorSpace: "srgb", default: [1, 1, 1] },
|
|
23
|
+
{ name: "normalScale", type: "f32", default: 1 },
|
|
24
|
+
{ name: "baseColorTexture", type: "texture2d" },
|
|
25
|
+
{ name: "metallicRoughnessTexture", type: "texture2d" },
|
|
26
|
+
{ name: "normalTexture", type: "texture2d" },
|
|
27
|
+
{ name: "specularTintTexture", type: "texture2d" },
|
|
28
|
+
{ name: "emissiveTexture", type: "texture2d" },
|
|
29
|
+
{ name: "occlusionTexture", type: "texture2d" }
|
|
30
|
+
];
|
|
31
|
+
var DEFAULT_UNLIT_PARAM_SCHEMA = [
|
|
32
|
+
{ name: "baseColor", type: "color", default: [1, 1, 1, 1] },
|
|
33
|
+
{ name: "alphaCutoff", type: "f32", default: 0 },
|
|
34
|
+
{ name: "baseColorTexture", type: "texture2d" }
|
|
35
|
+
];
|
|
36
|
+
var DEFAULT_SPRITE_PARAM_SCHEMA = [
|
|
37
|
+
{ name: "colorTint", type: "vec4", colorSpace: "srgb", default: [1, 1, 1, 1] },
|
|
38
|
+
{ name: "region", type: "vec4", default: [0, 0, 1, 1] },
|
|
39
|
+
{ name: "pivotAndSize", type: "vec4", default: [0.5, 0.5, 1, 1] },
|
|
40
|
+
{ name: "slicesAndMode", type: "vec4", default: [0, 0, 0, 0] },
|
|
41
|
+
{ name: "baseColorTexture", type: "texture2d" }
|
|
42
|
+
];
|
|
43
|
+
var DEFAULT_MSDF_TEXT_PARAM_SCHEMA = [
|
|
44
|
+
{ name: "tintColor", type: "color", default: [1, 1, 1, 1] },
|
|
45
|
+
{ name: "distanceRange", type: "vec4", default: [4, 512, 512, 0] },
|
|
46
|
+
{ name: "baseColorTexture", type: "texture2d" }
|
|
47
|
+
];
|
|
48
|
+
var ShaderError = class extends Error {
|
|
49
|
+
name = "ShaderError";
|
|
50
|
+
code;
|
|
51
|
+
expected;
|
|
52
|
+
hint;
|
|
53
|
+
lineNum;
|
|
54
|
+
linePos;
|
|
55
|
+
detail;
|
|
56
|
+
constructor(init) {
|
|
57
|
+
super(init.message);
|
|
58
|
+
this.code = init.code;
|
|
59
|
+
this.expected = init.expected;
|
|
60
|
+
this.hint = init.hint;
|
|
61
|
+
this.lineNum = init.lineNum;
|
|
62
|
+
this.linePos = init.linePos;
|
|
63
|
+
this.detail = init.detail;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
function manifestMalformed(args) {
|
|
67
|
+
return new ShaderError({
|
|
68
|
+
code: "manifest-malformed",
|
|
69
|
+
expected: "manifest.json parses + every entry has {hash, wgsl, glsl, bindings}",
|
|
70
|
+
message: args.message,
|
|
71
|
+
hint: args.hint,
|
|
72
|
+
...args.reason !== void 0 ? { detail: { reason: args.reason } } : {}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function shaderNotFound(args) {
|
|
76
|
+
return new ShaderError({
|
|
77
|
+
code: "shader-not-found",
|
|
78
|
+
expected: `manifest.entries contains entry with hash '${args.hash}'`,
|
|
79
|
+
message: `ShaderRegistry: hash '${args.hash}' not present in manifest`,
|
|
80
|
+
hint: args.hint
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function materialShaderNotFound(args) {
|
|
84
|
+
return new ShaderError({
|
|
85
|
+
code: "material-shader-not-found",
|
|
86
|
+
expected: `ShaderRegistry has registered identifiers: [${args.expected.join(", ")}]`,
|
|
87
|
+
message: `ShaderRegistry: material shader identifier '${args.identifier}' not registered`,
|
|
88
|
+
hint: args.hint,
|
|
89
|
+
detail: { reason: `identifier=${args.identifier}`, registeredShaderIds: args.expected }
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function equalBytes(left, right) {
|
|
93
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
94
|
+
}
|
|
95
|
+
var MaterialArtifactRegistry = class {
|
|
96
|
+
#artifacts = /* @__PURE__ */ new Map();
|
|
97
|
+
register(artifact) {
|
|
98
|
+
const previous = this.#artifacts.get(artifact.key);
|
|
99
|
+
if (previous !== void 0) {
|
|
100
|
+
if (equalBytes(previous.bytes, artifact.bytes)) return ok(previous);
|
|
101
|
+
return err({
|
|
102
|
+
code: "material-artifact-conflict",
|
|
103
|
+
expected: "one immutable artifact byte sequence per specialization key",
|
|
104
|
+
hint: "re-cook the conflicting specialization and publish one artifact digest",
|
|
105
|
+
detail: {
|
|
106
|
+
key: artifact.key,
|
|
107
|
+
...previous.digest ? { existingDigest: previous.digest } : {},
|
|
108
|
+
...artifact.digest ? { incomingDigest: artifact.digest } : {}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const immutable = Object.freeze(artifact);
|
|
113
|
+
this.#artifacts.set(artifact.key, immutable);
|
|
114
|
+
return ok(immutable);
|
|
115
|
+
}
|
|
116
|
+
get(key) {
|
|
117
|
+
return this.#artifacts.get(key);
|
|
118
|
+
}
|
|
119
|
+
inspect(key) {
|
|
120
|
+
const artifact = this.#artifacts.get(key);
|
|
121
|
+
if (artifact === void 0) return void 0;
|
|
122
|
+
return {
|
|
123
|
+
key: artifact.key,
|
|
124
|
+
...artifact.digest === void 0 ? {} : { digest: artifact.digest },
|
|
125
|
+
byteLength: artifact.bytes.byteLength,
|
|
126
|
+
...artifact.metadata === void 0 ? {} : { metadata: artifact.metadata }
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
function registerMaterialArtifact(registry, artifact) {
|
|
131
|
+
return registry.register(artifact);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/material/artifact-types.ts
|
|
135
|
+
function isMaterialShaderArtifact(value) {
|
|
136
|
+
if (value === null || typeof value !== "object") return false;
|
|
137
|
+
const artifact = value;
|
|
138
|
+
return typeof artifact.material === "string" && typeof artifact.pass === "string" && typeof artifact.wgsl === "string" && typeof artifact.layoutIdentity === "string" && Array.isArray(artifact.bindings) && Array.isArray(artifact.deps) && Array.isArray(artifact.vertexInputs);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/register-default-sprite-lit.ts
|
|
142
|
+
var RESERVED_ID = "forgeax::sprite-lit";
|
|
143
|
+
function registerDefaultSpriteLit(registry, composedWgsl, caps) {
|
|
144
|
+
registry.installMaterialArtifact(RESERVED_ID, {
|
|
145
|
+
source: composedWgsl,
|
|
146
|
+
paramSchema: DEFAULT_SPRITE_PARAM_SCHEMA
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/register-default-standard-pbr-skin.ts
|
|
151
|
+
var RESERVED_ID2 = "forgeax::pbr-skin";
|
|
152
|
+
function registerDefaultStandardPbrSkin(registry, composedWgsl, caps) {
|
|
153
|
+
registry.installMaterialArtifact(RESERVED_ID2, {
|
|
154
|
+
source: composedWgsl,
|
|
155
|
+
paramSchema: DEFAULT_STANDARD_PBR_PARAM_SCHEMA
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
var FORGEAX_RESERVED_PATH_PREFIX = "forgeax::";
|
|
159
|
+
var ShaderRegistry = class {
|
|
160
|
+
#device;
|
|
161
|
+
#manifestUrl;
|
|
162
|
+
// hash → ManifestEntry index (populated after loadManifest).
|
|
163
|
+
#entries = /* @__PURE__ */ new Map();
|
|
164
|
+
// hash → ShaderModule cache (populated lazily on first get()).
|
|
165
|
+
#moduleCache = /* @__PURE__ */ new Map();
|
|
166
|
+
// identifier → MaterialShaderEntry index (populated by
|
|
167
|
+
// installMaterialArtifact; feat-20260523-shader-template-instance-split M5 / T05).
|
|
168
|
+
#materialShaders = /* @__PURE__ */ new Map();
|
|
169
|
+
#paramSchemaProjectionOwner = new ParamSchemaProjectionOwner();
|
|
170
|
+
#materialShaderManifestEntries = [];
|
|
171
|
+
#manifestLoaded = false;
|
|
172
|
+
constructor(opts) {
|
|
173
|
+
this.#device = opts.device;
|
|
174
|
+
this.#manifestUrl = opts.manifestUrl;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Loads manifest.json and populates the hash → ManifestEntry index.
|
|
178
|
+
*
|
|
179
|
+
* Failure paths (charter proposition 4: explicit failure):
|
|
180
|
+
* - fetch / data: URL parse failed → `Result.err(ShaderError.manifest-malformed)`
|
|
181
|
+
* - JSON.parse failed → ditto
|
|
182
|
+
* - schema missing the `entries` field / an entries element missing
|
|
183
|
+
* hash/wgsl/bindings → ditto
|
|
184
|
+
* - a material shader row is incomplete → ditto
|
|
185
|
+
*
|
|
186
|
+
* Validation is atomic: the registry does not publish any entry or
|
|
187
|
+
* material shader row until the complete document has been validated.
|
|
188
|
+
*
|
|
189
|
+
* Idempotent: subsequent calls reuse the first result (never re-fetch;
|
|
190
|
+
* charter proposition 6: idempotency).
|
|
191
|
+
*/
|
|
192
|
+
async loadManifest() {
|
|
193
|
+
if (this.#manifestLoaded) return ok$1(void 0);
|
|
194
|
+
if (this.#manifestUrl === void 0) {
|
|
195
|
+
this.#manifestLoaded = true;
|
|
196
|
+
return ok$1(void 0);
|
|
197
|
+
}
|
|
198
|
+
let raw;
|
|
199
|
+
try {
|
|
200
|
+
const response = await fetch(this.#manifestUrl);
|
|
201
|
+
raw = await response.text();
|
|
202
|
+
} catch (e) {
|
|
203
|
+
return err$1(
|
|
204
|
+
manifestMalformed({
|
|
205
|
+
message: `ShaderRegistry: failed to fetch manifest at ${this.#manifestUrl}`,
|
|
206
|
+
hint: "verify manifest URL is reachable; check bundler emitFile output path",
|
|
207
|
+
reason: e instanceof Error ? e.message : String(e)
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
let parsed;
|
|
212
|
+
try {
|
|
213
|
+
parsed = JSON.parse(raw);
|
|
214
|
+
} catch (e) {
|
|
215
|
+
return err$1(
|
|
216
|
+
manifestMalformed({
|
|
217
|
+
message: "ShaderRegistry: manifest JSON parse failed",
|
|
218
|
+
hint: "manifest.json must be valid JSON; rebuild via @forgeax/engine-vite-plugin-shader generateBundle",
|
|
219
|
+
reason: e instanceof Error ? e.message : String(e)
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (typeof parsed !== "object" || parsed === null || !("entries" in parsed)) {
|
|
224
|
+
return err$1(
|
|
225
|
+
manifestMalformed({
|
|
226
|
+
message: "ShaderRegistry: manifest missing required `entries` field",
|
|
227
|
+
hint: "manifest schema requires top-level `entries: ManifestEntry[]`"
|
|
228
|
+
})
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
const entries = parsed.entries;
|
|
232
|
+
if (!Array.isArray(entries)) {
|
|
233
|
+
return err$1(
|
|
234
|
+
manifestMalformed({
|
|
235
|
+
message: "ShaderRegistry: manifest.entries is not an array",
|
|
236
|
+
hint: "manifest schema requires `entries: ManifestEntry[]`"
|
|
237
|
+
})
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
const validatedEntries = [];
|
|
241
|
+
for (const entry of entries) {
|
|
242
|
+
if (!isValidManifestEntry(entry)) {
|
|
243
|
+
return err$1(
|
|
244
|
+
manifestMalformed({
|
|
245
|
+
message: "ShaderRegistry: manifest entry missing required fields",
|
|
246
|
+
hint: "every entry needs {hash, wgsl, glsl, bindings} per @forgeax/engine-types.ManifestEntry",
|
|
247
|
+
reason: `bad entry: ${JSON.stringify(entry)}`
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
validatedEntries.push(entry);
|
|
252
|
+
}
|
|
253
|
+
const parsedSlim = parsed;
|
|
254
|
+
const validatedMaterialShaderEntries = [];
|
|
255
|
+
if (Array.isArray(parsedSlim.materialShaders)) {
|
|
256
|
+
for (const ms of parsedSlim.materialShaders) {
|
|
257
|
+
if (!isValidMaterialShaderManifestEntry(ms)) {
|
|
258
|
+
return err$1(
|
|
259
|
+
manifestMalformed({
|
|
260
|
+
message: "ShaderRegistry: manifest material shader entry missing required fields",
|
|
261
|
+
hint: "every material shader entry needs {identifier, sourcePath, composedWgsl, paramSchema, variants}",
|
|
262
|
+
reason: `bad material shader entry: ${JSON.stringify(ms)}`
|
|
263
|
+
})
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
validatedMaterialShaderEntries.push(ms);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
this.#entries.clear();
|
|
270
|
+
for (const entry of validatedEntries) {
|
|
271
|
+
this.#entries.set(entry.hash, entry);
|
|
272
|
+
}
|
|
273
|
+
this.#materialShaderManifestEntries.length = 0;
|
|
274
|
+
this.#materialShaderManifestEntries.push(...validatedMaterialShaderEntries);
|
|
275
|
+
this.#manifestLoaded = true;
|
|
276
|
+
return ok$1(void 0);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Iterate every `ManifestEntry` populated by the most recent successful
|
|
280
|
+
* `loadManifest()` call. Order matches the manifest JSON `entries` array
|
|
281
|
+
* (insertion order; charter P2 structured-over-prose: AI users walk this
|
|
282
|
+
* to discover engine-shipped entries by content marker without
|
|
283
|
+
* re-fetching the manifest).
|
|
284
|
+
*
|
|
285
|
+
* Returns an empty iterator when no manifest has been loaded yet
|
|
286
|
+
* (charter P3 explicit failure: the empty case is not an error — the
|
|
287
|
+
* absent SSOT is the absent contract).
|
|
288
|
+
*/
|
|
289
|
+
entries() {
|
|
290
|
+
return this.#entries.values();
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Looks up a hash and forwards to `device.createShaderModule` (creates lazily
|
|
294
|
+
* on first hit, then caches).
|
|
295
|
+
*
|
|
296
|
+
* Error paths:
|
|
297
|
+
* - hash miss → `Result.err(ShaderError.shader-not-found)`
|
|
298
|
+
* - device.createShaderModule failed → `Result.err(RhiError)` passes through
|
|
299
|
+
* (it is **not** wrapped as a ShaderError).
|
|
300
|
+
*
|
|
301
|
+
* Sync shape: on cache miss, the underlying `device.createShaderModule` is
|
|
302
|
+
* called synchronously; the caller is responsible for handling the async
|
|
303
|
+
* real path at the device-wrapper layer (e.g. the engine pre-compiles
|
|
304
|
+
* during `loadManifest`).
|
|
305
|
+
*/
|
|
306
|
+
get(hash) {
|
|
307
|
+
const cachedModule = this.#moduleCache.get(hash);
|
|
308
|
+
if (cachedModule !== void 0) {
|
|
309
|
+
return ok$1(cachedModule);
|
|
310
|
+
}
|
|
311
|
+
const entry = this.#entries.get(hash);
|
|
312
|
+
if (entry === void 0) {
|
|
313
|
+
return err$1(
|
|
314
|
+
shaderNotFound({
|
|
315
|
+
hash,
|
|
316
|
+
hint: "verify manifest.json contains the expected hash; rerun build to regenerate manifest"
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
const result = this.#device.createShaderModule({ code: entry.wgsl, label: hash });
|
|
321
|
+
if (!result.ok) {
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
this.#moduleCache.set(hash, result.value);
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
// ─── MaterialShader registry surface ───────────────────────────────────────
|
|
328
|
+
// (feat-20260523-shader-template-instance-split M5 / T05 — plan-strategy
|
|
329
|
+
// D-DefaultStandardPbr-Identifier).
|
|
330
|
+
//
|
|
331
|
+
// Two-tier identifier namespace:
|
|
332
|
+
// - `forgeax::*` — engine-shipped reserved prefix; the host wires
|
|
333
|
+
// `forgeax::default-standard-pbr` at engine boot from the
|
|
334
|
+
// vite-plugin-shader manifest's `materialShaders[]` row + the
|
|
335
|
+
// `default-standard-pbr.schema.json` SSOT (M5 / T08 + M6).
|
|
336
|
+
// - any other identifier — user-side custom material shader, registered
|
|
337
|
+
// after asset import.
|
|
338
|
+
//
|
|
339
|
+
// The registry is the runtime SSOT consumed by:
|
|
340
|
+
// - the material decoder's `'::' two-way dispatch` (M4 / T01) — the path
|
|
341
|
+
// branch validates against `paramSchema` and feeds `MaterialSnapshot.
|
|
342
|
+
// materialShaderId` (M4 / T05).
|
|
343
|
+
// - `render-system-record`'s pipeline cache key (M4 / T06) — same
|
|
344
|
+
// identifier feeds the per-pipeline `(materialShaderId, stateHash)` key.
|
|
345
|
+
/**
|
|
346
|
+
* Register a material shader entry under a stable identifier.
|
|
347
|
+
*
|
|
348
|
+
* **Throws** on:
|
|
349
|
+
* - duplicate registration of the same identifier (programmer error per
|
|
350
|
+
* AGENTS.md "explicit registration" + Inspector "fail-fast no overwrite"
|
|
351
|
+
* pattern; charter P3 explicit failure on misuse).
|
|
352
|
+
*
|
|
353
|
+
* Successful path returns void — the registry mutates in-place. Use
|
|
354
|
+
* {@link findMaterialArtifact} to read back.
|
|
355
|
+
*
|
|
356
|
+
* Identifier conventions (charter F1 / plan-strategy §8 §2):
|
|
357
|
+
* - `forgeax::<kebab-case>` — engine-shipped reserved prefix; the host
|
|
358
|
+
* registers `forgeax::default-standard-pbr` at boot.
|
|
359
|
+
* - `<package>::<id>` or `<guid>` — user-side custom shaders.
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* ```ts
|
|
363
|
+
* const registry = new ShaderRegistry({ device, manifestUrl });
|
|
364
|
+
* registry.installMaterialArtifact('forgeax::default-standard-pbr', {
|
|
365
|
+
* source: composedWgsl,
|
|
366
|
+
* paramSchema: defaultStandardPbrSchema,
|
|
367
|
+
* });
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
installMaterialArtifact(identifier, entry) {
|
|
371
|
+
if (this.#materialShaders.has(identifier)) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
`ShaderRegistry: material shader identifier '${identifier}' already registered; same-name re-register is forbidden (AGENTS.md "explicit registration" + Inspector "fail-fast no overwrite" pattern). Use findMaterialArtifact to read back the existing entry.`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
const paramSchemaProjection = this.#paramSchemaProjectionOwner.admit({
|
|
377
|
+
ownerId: identifier,
|
|
378
|
+
revision: 1,
|
|
379
|
+
schema: entry.paramSchema
|
|
380
|
+
});
|
|
381
|
+
if (!identifier.startsWith(FORGEAX_RESERVED_PATH_PREFIX)) {
|
|
382
|
+
const undeclared = findUndeclaredSampledTextures(entry.source, paramSchemaProjection.schema);
|
|
383
|
+
if (undeclared.length > 0) {
|
|
384
|
+
throw new Error(
|
|
385
|
+
`ShaderRegistry: material shader '${identifier}' samples texture(s) [${undeclared.join(", ")}] in its WGSL but its paramSchema does not declare them as texture entries. Add { name: '${undeclared[0]}', type: 'texture2d' } (and any others listed) to the paramSchema, or the engine would silently bind the default white texture (charter P3 explicit failure; see docs/handover/2026-06-19-blending-transparency-regression-bisect.md).`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
this.#materialShaders.set(
|
|
390
|
+
identifier,
|
|
391
|
+
Object.freeze({
|
|
392
|
+
source: entry.source,
|
|
393
|
+
paramSchema: paramSchemaProjection.schema,
|
|
394
|
+
paramSchemaProjection
|
|
395
|
+
})
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Lookup a previously-registered material shader entry by identifier.
|
|
400
|
+
*
|
|
401
|
+
* Returns `Result.ok(entry)` on hit, `Result.err(material-shader-not-found)`
|
|
402
|
+
* on miss. The `Result` shape mirrors `get(hash)` for consistent
|
|
403
|
+
* abstraction across the runtime registry surface (charter P4).
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```ts
|
|
407
|
+
* const r = registry.findMaterialArtifact('forgeax::default-standard-pbr');
|
|
408
|
+
* if (!r.ok) return handleError(r.error);
|
|
409
|
+
* const { source, paramSchema } = r.value;
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
findMaterialArtifact(identifier) {
|
|
413
|
+
const entry = this.#materialShaders.get(identifier);
|
|
414
|
+
if (entry === void 0) {
|
|
415
|
+
return err$1(
|
|
416
|
+
materialShaderNotFound({
|
|
417
|
+
identifier,
|
|
418
|
+
expected: Array.from(this.#materialShaders.keys()),
|
|
419
|
+
hint: `register the shader via ShaderRegistry.installMaterialArtifact('${identifier}', ...) at engine boot, or grep '${FORGEAX_RESERVED_PATH_PREFIX}' to enumerate engine-shipped reserved identifiers`
|
|
420
|
+
})
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
return ok$1(entry);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Iterate every registered material shader identifier in registration
|
|
427
|
+
* order (charter F1 grep gate: `for (const id of registry.materialShaderIdentifiers())`
|
|
428
|
+
* lists every `forgeax::*` + user shader without a private-field reach-in).
|
|
429
|
+
*/
|
|
430
|
+
materialShaderIdentifiers() {
|
|
431
|
+
return this.#materialShaders.keys();
|
|
432
|
+
}
|
|
433
|
+
/** Runtime diagnostic counters for the immutable ParamSchema owner. */
|
|
434
|
+
paramSchemaProjectionStats() {
|
|
435
|
+
return this.#paramSchemaProjectionOwner.stats();
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Iterate every MaterialShaderManifestEntry parsed from manifest.json's
|
|
439
|
+
* `materialShaders[]` field during `loadManifest()`.
|
|
440
|
+
*
|
|
441
|
+
* feat-20260526-pbr-uniform-fallback-no-storage-buffer M3 / w12:
|
|
442
|
+
* createRenderer variant resolution consumes this list to select the
|
|
443
|
+
* correct variant (by `caps.storageBuffer`) and register the resolved
|
|
444
|
+
* WGSL via `installMaterialArtifact`.
|
|
445
|
+
*
|
|
446
|
+
* Returns an empty iterator when no manifest has been loaded yet or
|
|
447
|
+
* the manifest lacks `materialShaders` (backward-compatible).
|
|
448
|
+
*/
|
|
449
|
+
materialShaderManifestEntries() {
|
|
450
|
+
return this.#materialShaderManifestEntries.values();
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
function isValidManifestEntry(value) {
|
|
454
|
+
if (typeof value !== "object" || value === null) return false;
|
|
455
|
+
const v = value;
|
|
456
|
+
if (typeof v.hash !== "string") return false;
|
|
457
|
+
if (typeof v.wgsl !== "string") return false;
|
|
458
|
+
if (v.glsl !== void 0 && v.glsl !== null && typeof v.glsl !== "string") return false;
|
|
459
|
+
if (typeof v.bindings !== "string") return false;
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
function isValidMaterialShaderManifestEntry(value) {
|
|
463
|
+
if (typeof value !== "object" || value === null) return false;
|
|
464
|
+
const v = value;
|
|
465
|
+
if (typeof v.identifier !== "string") return false;
|
|
466
|
+
if (typeof v.sourcePath !== "string") return false;
|
|
467
|
+
if (typeof v.composedWgsl !== "string") return false;
|
|
468
|
+
if (typeof v.paramSchema !== "string") return false;
|
|
469
|
+
if (!Array.isArray(v.variants)) return false;
|
|
470
|
+
if (!v.variants.every(isValidMaterialShaderManifestVariant)) return false;
|
|
471
|
+
return v.uvSetCount === void 0 || typeof v.uvSetCount === "number";
|
|
472
|
+
}
|
|
473
|
+
function isValidMaterialShaderManifestVariant(value) {
|
|
474
|
+
if (typeof value !== "object" || value === null) return false;
|
|
475
|
+
const v = value;
|
|
476
|
+
if (typeof v.definesKey !== "string") return false;
|
|
477
|
+
if (typeof v.composedWgsl !== "string") return false;
|
|
478
|
+
if (typeof v.defines !== "object" || v.defines === null || Array.isArray(v.defines)) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
return Object.values(v.defines).every((define) => typeof define === "boolean");
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// src/types.ts
|
|
485
|
+
function findVariantByKey(entry, definesKey) {
|
|
486
|
+
return entry.variants.find((v) => v.definesKey === definesKey);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/tonemap.ts
|
|
490
|
+
var TONEMAP_SHADER_MODE = {
|
|
491
|
+
none: 0,
|
|
492
|
+
reinhardExtended: 1,
|
|
493
|
+
linear: 2,
|
|
494
|
+
cineon: 3,
|
|
495
|
+
acesFilmic: 4,
|
|
496
|
+
agx: 5,
|
|
497
|
+
neutral: 6,
|
|
498
|
+
reinhard: 7
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
// src/index.ts
|
|
502
|
+
var BUILTIN_MATERIAL_MODULES = {
|
|
503
|
+
standard: "forgeax_material::standard",
|
|
504
|
+
unlit: "forgeax_material::unlit",
|
|
505
|
+
sprite: "forgeax_material::sprite"
|
|
506
|
+
};
|
|
507
|
+
var ENGINE_MATERIAL_MODULES = [
|
|
508
|
+
"forgeax::default-standard-pbr",
|
|
509
|
+
"forgeax::pbr-skin",
|
|
510
|
+
"forgeax::default-standard-pbr-skin",
|
|
511
|
+
"forgeax::default-unlit",
|
|
512
|
+
"forgeax::default-shadow-caster",
|
|
513
|
+
"forgeax::sprite",
|
|
514
|
+
"forgeax::sprite-lit",
|
|
515
|
+
"forgeax::msdf-text",
|
|
516
|
+
BUILTIN_MATERIAL_MODULES.standard,
|
|
517
|
+
BUILTIN_MATERIAL_MODULES.unlit,
|
|
518
|
+
BUILTIN_MATERIAL_MODULES.sprite,
|
|
519
|
+
"forgeax_material::sprite-lit"
|
|
520
|
+
];
|
|
521
|
+
function isEngineMaterialModule(module) {
|
|
522
|
+
return ENGINE_MATERIAL_MODULES.includes(module);
|
|
523
|
+
}
|
|
524
|
+
function isEngineMaterial(material) {
|
|
525
|
+
const passes = material.passes ?? [];
|
|
526
|
+
return passes.length > 0 && passes.every((pass) => isEngineMaterialModule(pass.program.module));
|
|
527
|
+
}
|
|
528
|
+
var BUILTIN_PARAMETERS = {
|
|
529
|
+
standard: [
|
|
530
|
+
{ name: "baseColor", type: "color" },
|
|
531
|
+
{ name: "metallic", type: "f32" },
|
|
532
|
+
{ name: "roughness", type: "f32" },
|
|
533
|
+
{ name: "alphaCutoff", type: "f32", optional: true }
|
|
534
|
+
],
|
|
535
|
+
unlit: [{ name: "baseColor", type: "color" }],
|
|
536
|
+
sprite: [{ name: "colorTint", type: "vec4", colorSpace: "srgb" }]
|
|
537
|
+
};
|
|
538
|
+
var BUILTIN_VALUES = {
|
|
539
|
+
standard: {
|
|
540
|
+
baseColor: [1, 1, 1, 1],
|
|
541
|
+
metallic: 0,
|
|
542
|
+
roughness: 0.5,
|
|
543
|
+
alphaCutoff: STANDARD_PBR_ALPHA_CUTOFF_DEFAULT
|
|
544
|
+
},
|
|
545
|
+
unlit: { baseColor: [1, 1, 1, 1] },
|
|
546
|
+
sprite: { colorTint: [1, 1, 1, 1] }
|
|
547
|
+
};
|
|
548
|
+
function createBuiltinMaterialAsset(kind) {
|
|
549
|
+
return {
|
|
550
|
+
kind: "material",
|
|
551
|
+
passes: [{ name: "forward", program: { module: BUILTIN_MATERIAL_MODULES[kind] } }],
|
|
552
|
+
parameters: BUILTIN_PARAMETERS[kind],
|
|
553
|
+
values: BUILTIN_VALUES[kind]
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
var TONEMAP_LUMINANCE_EPSILON = 1e-5;
|
|
557
|
+
|
|
558
|
+
export { BUILTIN_MATERIAL_MODULES, DEFAULT_MSDF_TEXT_PARAM_SCHEMA, DEFAULT_SPRITE_PARAM_SCHEMA, DEFAULT_STANDARD_PBR_PARAM_SCHEMA, DEFAULT_UNLIT_PARAM_SCHEMA, ENGINE_MATERIAL_MODULES, FORGEAX_RESERVED_PATH_PREFIX, MaterialArtifactRegistry, STANDARD_PBR_ALPHA_CUTOFF_DEFAULT, ShaderError, ShaderRegistry, TONEMAP_LUMINANCE_EPSILON, TONEMAP_SHADER_MODE, createBuiltinMaterialAsset, findVariantByKey, isEngineMaterial, isEngineMaterialModule, isMaterialShaderArtifact, manifestMalformed, materialShaderNotFound, registerDefaultSpriteLit, registerDefaultStandardPbrSkin, registerMaterialArtifact, shaderNotFound };
|
|
559
|
+
//# sourceMappingURL=index.mjs.map
|
|
560
|
+
//# sourceMappingURL=index.mjs.map
|