@galacean/engine-shader-compiler 2.0.0-alpha.32 → 2.0.0-alpha.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{dist/bundler → bundler}/cli.js +4 -2
- package/bundler/precompile.cjs.js +324 -0
- package/bundler/precompile.js +294 -0
- package/{dist/bundler → bundler}/rollup.cjs.js +4 -2
- package/{dist/bundler → bundler}/rollup.js +4 -2
- package/dist/browser.js +425 -207
- package/dist/browser.js.map +1 -1
- package/dist/browser.min.js +1 -1
- package/dist/browser.min.js.map +1 -1
- package/dist/browser.verbose.js +416 -200
- package/dist/browser.verbose.js.map +1 -1
- package/dist/browser.verbose.min.js +1 -1
- package/dist/browser.verbose.min.js.map +1 -1
- package/dist/main.js +425 -207
- package/dist/main.js.map +1 -1
- package/dist/main.verbose.js +416 -200
- package/dist/main.verbose.js.map +1 -1
- package/dist/module.js +425 -207
- package/dist/module.js.map +1 -1
- package/dist/module.verbose.js +416 -200
- package/dist/module.verbose.js.map +1 -1
- package/package.json +14 -8
- package/types/ParserUtils.d.ts +19 -6
- package/types/Preprocessor.d.ts +11 -14
- package/types/ShaderCompilerUtils.d.ts +1 -1
- package/types/lalr/LALR1.d.ts +1 -0
- package/types/lexer/Lexer.d.ts +51 -19
- package/types/parser/AST.d.ts +24 -4
|
@@ -128,8 +128,10 @@ async function startWatcher(options) {
|
|
|
128
128
|
});
|
|
129
129
|
}
|
|
130
130
|
async function loadShaderCompiler() {
|
|
131
|
-
// @ts-ignore — `../main.js` is the compiled runtime entry; no .ts source.
|
|
132
|
-
|
|
131
|
+
// @ts-ignore — `../dist/main.js` is the compiled runtime entry; no .ts source.
|
|
132
|
+
// Bundler ships at `<pkg>/bundler/` and the runtime at `<pkg>/dist/`, so this
|
|
133
|
+
// resolves to the sibling `dist/` from `bundler/cli.js` at runtime.
|
|
134
|
+
const mod = await import('../dist/main.js');
|
|
133
135
|
const instance = new mod.ShaderCompiler();
|
|
134
136
|
if (typeof instance._precompile !== "function") {
|
|
135
137
|
throw new Error("ShaderCompiler._precompile is not available; rebuild @galacean/engine-shader-compiler first.");
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var fs = require('node:fs');
|
|
6
|
+
var path = require('node:path');
|
|
7
|
+
var node_module = require('node:module');
|
|
8
|
+
var node_url = require('node:url');
|
|
9
|
+
|
|
10
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
11
|
+
|
|
12
|
+
function _interopNamespace(e) {
|
|
13
|
+
if (e && e.__esModule) return e;
|
|
14
|
+
var n = Object.create(null);
|
|
15
|
+
if (e) {
|
|
16
|
+
Object.keys(e).forEach(function (k) {
|
|
17
|
+
if (k !== 'default') {
|
|
18
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
19
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return e[k]; }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
n["default"] = e;
|
|
27
|
+
return Object.freeze(n);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
31
|
+
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
32
|
+
|
|
33
|
+
/** Normalize to forward slashes (Windows uses `\`; downstream regex/keys assume `/`). */ function normalizePath(p) {
|
|
34
|
+
return p.split(path__default["default"].sep).join("/");
|
|
35
|
+
}
|
|
36
|
+
/** Recursively find all files with a given extension. */ function findFiles(dir, ext) {
|
|
37
|
+
const results = [];
|
|
38
|
+
if (!fs__default["default"].existsSync(dir)) return results;
|
|
39
|
+
for (const entry of fs__default["default"].readdirSync(dir, {
|
|
40
|
+
withFileTypes: true
|
|
41
|
+
})){
|
|
42
|
+
const fullPath = path__default["default"].join(dir, entry.name);
|
|
43
|
+
if (entry.isDirectory()) {
|
|
44
|
+
results.push(...findFiles(fullPath, ext));
|
|
45
|
+
} else if (entry.name.endsWith(ext)) {
|
|
46
|
+
results.push(normalizePath(fullPath));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
/** `PBR.shaderc` → `PBRSource`, `2D/Sprite.shaderc` → `SpriteSource`. */ function shadercPathToVarName(relPath) {
|
|
52
|
+
const normalized = normalizePath(relPath).replace(/\.shaderc$/, "");
|
|
53
|
+
const base = normalized.split("/").pop() ?? normalized;
|
|
54
|
+
const cleaned = base.replace(/[^A-Za-z0-9]/g, "");
|
|
55
|
+
if (cleaned.length === 0) return "Source";
|
|
56
|
+
return `${cleaned[0].toUpperCase()}${cleaned.slice(1)}Source`;
|
|
57
|
+
}
|
|
58
|
+
/** `Common/Common.glsl` → `Common_Common` — TS identifier for source-index imports. */ function pathToIdentifier(rel, ext) {
|
|
59
|
+
let id = rel.replace(new RegExp(`\\${ext}$`), "").replace(/[^A-Za-z0-9_]/g, "_");
|
|
60
|
+
if (/^[0-9]/.test(id)) id = "_" + id;
|
|
61
|
+
return id;
|
|
62
|
+
}
|
|
63
|
+
/** Remove empty parent directories upward from `startDir` until `stopDir` is hit. */ function pruneEmptyDirs(startDir, stopDir) {
|
|
64
|
+
let dir = startDir;
|
|
65
|
+
while(dir !== stopDir && fs__default["default"].existsSync(dir) && fs__default["default"].readdirSync(dir).length === 0){
|
|
66
|
+
fs__default["default"].rmdirSync(dir);
|
|
67
|
+
dir = path__default["default"].dirname(dir);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// One-shot mode exits non-zero on failure so CI breaks; watch mode logs and keeps running.
|
|
71
|
+
async function precompile(options) {
|
|
72
|
+
const { failed } = await runFull(options);
|
|
73
|
+
if (options.watch) {
|
|
74
|
+
await startWatcher(options);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
console.log("[shader-compiler-bundler] Done.");
|
|
78
|
+
if (failed > 0) {
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function runFull(options) {
|
|
83
|
+
const inputDir = path__default["default"].resolve(options.input);
|
|
84
|
+
const outputDir = path__default["default"].resolve(options.output);
|
|
85
|
+
const platformTarget = options.platformTarget ?? 0;
|
|
86
|
+
if (!fs__default["default"].existsSync(inputDir)) {
|
|
87
|
+
throw new Error(`Input directory does not exist: ${inputDir}`);
|
|
88
|
+
}
|
|
89
|
+
fs__default["default"].mkdirSync(outputDir, {
|
|
90
|
+
recursive: true
|
|
91
|
+
});
|
|
92
|
+
// Emit source indexes before compile so collectIncludeMap can resolve the freshly-built sources.
|
|
93
|
+
if (options.emitSources) {
|
|
94
|
+
emitSources(inputDir);
|
|
95
|
+
}
|
|
96
|
+
const shaderCompiler = await loadShaderCompiler();
|
|
97
|
+
shaderCompiler._setIncludeMap(await collectIncludeMap(inputDir));
|
|
98
|
+
let failed = 0;
|
|
99
|
+
if (options.only) {
|
|
100
|
+
const target = path__default["default"].resolve(options.only);
|
|
101
|
+
if (!compileSingle(shaderCompiler, target, inputDir, outputDir, platformTarget)) failed++;
|
|
102
|
+
} else {
|
|
103
|
+
const shaderFiles = findFiles(inputDir, ".shader");
|
|
104
|
+
console.log(`[shader-compiler-bundler] Precompiling ${shaderFiles.length} shader(s)...`);
|
|
105
|
+
for (const file of shaderFiles){
|
|
106
|
+
if (!compileSingle(shaderCompiler, file, inputDir, outputDir, platformTarget)) failed++;
|
|
107
|
+
}
|
|
108
|
+
if (options.clean) {
|
|
109
|
+
const removed = cleanOrphanedBundles(shaderFiles, inputDir, outputDir);
|
|
110
|
+
if (removed > 0) console.log(`[shader-compiler-bundler] Cleaned ${removed} orphaned .shaderc file(s).`);
|
|
111
|
+
}
|
|
112
|
+
if (failed > 0) console.warn(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
113
|
+
}
|
|
114
|
+
if (options.emitIndex) {
|
|
115
|
+
emitIndex(outputDir);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
failed
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function startWatcher(options) {
|
|
122
|
+
const inputDir = path__default["default"].resolve(options.input);
|
|
123
|
+
const outputDir = path__default["default"].resolve(options.output);
|
|
124
|
+
const platformTarget = options.platformTarget ?? 0;
|
|
125
|
+
const shaderCompiler = await loadShaderCompiler();
|
|
126
|
+
shaderCompiler._setIncludeMap(await collectIncludeMap(inputDir));
|
|
127
|
+
// .glsl change → full recompile (can't cheaply track include graphs).
|
|
128
|
+
// .shader change → single-file recompile.
|
|
129
|
+
const handleInputChange = async (filename)=>{
|
|
130
|
+
if (!filename) return;
|
|
131
|
+
const norm = normalizePath(filename);
|
|
132
|
+
const fullPath = path__default["default"].join(inputDir, filename);
|
|
133
|
+
if (norm.endsWith(".glsl")) {
|
|
134
|
+
console.log(`[shader-compiler-bundler] Include changed (${norm}), full recompile...`);
|
|
135
|
+
shaderCompiler._setIncludeMap(await collectIncludeMap(inputDir));
|
|
136
|
+
runFull(options).catch((e)=>console.error(e));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!norm.endsWith(".shader")) return;
|
|
140
|
+
if (!fs__default["default"].existsSync(fullPath)) {
|
|
141
|
+
removeBundleFor(fullPath, inputDir, outputDir);
|
|
142
|
+
} else {
|
|
143
|
+
compileSingle(shaderCompiler, fullPath, inputDir, outputDir, platformTarget);
|
|
144
|
+
}
|
|
145
|
+
if (options.emitSources) emitSources(inputDir);
|
|
146
|
+
if (options.emitIndex) emitIndex(outputDir);
|
|
147
|
+
};
|
|
148
|
+
console.log(`[shader-compiler-bundler] Watching ${inputDir} ...`);
|
|
149
|
+
fs__default["default"].watch(inputDir, {
|
|
150
|
+
recursive: true
|
|
151
|
+
}, (_event, filename)=>{
|
|
152
|
+
handleInputChange(filename).catch((e)=>console.error(e));
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async function loadShaderCompiler() {
|
|
156
|
+
// @ts-ignore — `../dist/main.js` is the compiled runtime entry; no .ts source.
|
|
157
|
+
// Bundler ships at `<pkg>/bundler/` and the runtime at `<pkg>/dist/`, so this
|
|
158
|
+
// resolves to the sibling `dist/` from `bundler/cli.js` at runtime.
|
|
159
|
+
const mod = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('../dist/main.js')); });
|
|
160
|
+
const instance = new mod.ShaderCompiler();
|
|
161
|
+
if (typeof instance._precompile !== "function") {
|
|
162
|
+
throw new Error("ShaderCompiler._precompile is not available; rebuild @galacean/engine-shader-compiler first.");
|
|
163
|
+
}
|
|
164
|
+
return instance;
|
|
165
|
+
}
|
|
166
|
+
// Local .glsl come from inputDir; standard library comes from
|
|
167
|
+
// `@galacean/engine-shader/sources` with a sibling-dir fallback for engine self-build cold-start.
|
|
168
|
+
async function collectIncludeMap(inputDir) {
|
|
169
|
+
const map = {};
|
|
170
|
+
for (const file of findFiles(inputDir, ".glsl")){
|
|
171
|
+
const relPath = normalizePath(path__default["default"].relative(inputDir, file));
|
|
172
|
+
map[relPath] = fs__default["default"].readFileSync(file, "utf-8");
|
|
173
|
+
}
|
|
174
|
+
let usedRelease = false;
|
|
175
|
+
// Resolve from inputDir's node_modules so consumers without transitive linkage to shader-compiler can still find it.
|
|
176
|
+
try {
|
|
177
|
+
const requireFromInput = node_module.createRequire(path__default["default"].join(inputDir, "package.json"));
|
|
178
|
+
const sourcesPath = requireFromInput.resolve("@galacean/engine-shader/sources");
|
|
179
|
+
const { shaderLibrary } = await (function (t) { return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require(t)); }); })(node_url.pathToFileURL(sourcesPath).href);
|
|
180
|
+
for (const { source, path: chunkPath } of shaderLibrary){
|
|
181
|
+
map[chunkPath] = source;
|
|
182
|
+
}
|
|
183
|
+
usedRelease = true;
|
|
184
|
+
} catch {
|
|
185
|
+
// engine self-build cold-start: dist/sources.* not yet built; use sibling source.
|
|
186
|
+
}
|
|
187
|
+
if (!usedRelease) {
|
|
188
|
+
const siblingLibrary = path__default["default"].join(path__default["default"].dirname(inputDir), "ShaderLibrary");
|
|
189
|
+
if (fs__default["default"].existsSync(siblingLibrary)) {
|
|
190
|
+
for (const file of findFiles(siblingLibrary, ".glsl")){
|
|
191
|
+
const rel = normalizePath(path__default["default"].relative(siblingLibrary, file));
|
|
192
|
+
map[`ShaderLibrary/${rel}`] = fs__default["default"].readFileSync(file, "utf-8");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return map;
|
|
197
|
+
}
|
|
198
|
+
// Mirrors `ShaderPass._shaderRootPath` in engine; inlined to keep bundler engine-free.
|
|
199
|
+
const SHADER_ROOT_PATH = "shaders://root/";
|
|
200
|
+
function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platformTarget) {
|
|
201
|
+
const source = fs__default["default"].readFileSync(shaderPath, "utf-8");
|
|
202
|
+
const relativePath = normalizePath(path__default["default"].relative(inputDir, shaderPath));
|
|
203
|
+
const bundleRelative = relativePath.replace(/\.shader$/, ".shaderc");
|
|
204
|
+
const bundlePath = path__default["default"].join(outputDir, bundleRelative);
|
|
205
|
+
const basePathForIncludeKey = new URL(relativePath, SHADER_ROOT_PATH).href;
|
|
206
|
+
fs__default["default"].mkdirSync(path__default["default"].dirname(bundlePath), {
|
|
207
|
+
recursive: true
|
|
208
|
+
});
|
|
209
|
+
try {
|
|
210
|
+
const precompiled = shaderCompiler._precompile(source, platformTarget, basePathForIncludeKey);
|
|
211
|
+
fs__default["default"].writeFileSync(bundlePath, JSON.stringify(precompiled));
|
|
212
|
+
console.log(` ${relativePath} -> ${bundleRelative}`);
|
|
213
|
+
return true;
|
|
214
|
+
} catch (e) {
|
|
215
|
+
console.error(` FAILED: ${relativePath}`);
|
|
216
|
+
console.error(e);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function cleanOrphanedBundles(shaderFiles, inputDir, outputDir) {
|
|
221
|
+
const aliveSet = new Set(shaderFiles.map((f)=>normalizePath(path__default["default"].relative(inputDir, f)).replace(/\.shader$/, "")));
|
|
222
|
+
const bundleFiles = findFiles(outputDir, ".shaderc");
|
|
223
|
+
let removed = 0;
|
|
224
|
+
for (const bundleFile of bundleFiles){
|
|
225
|
+
const rel = normalizePath(path__default["default"].relative(outputDir, bundleFile)).replace(/\.shaderc$/, "");
|
|
226
|
+
if (!aliveSet.has(rel)) {
|
|
227
|
+
fs__default["default"].unlinkSync(bundleFile);
|
|
228
|
+
console.log(` Removed orphaned: ${rel}.shaderc`);
|
|
229
|
+
removed++;
|
|
230
|
+
pruneEmptyDirs(path__default["default"].dirname(bundleFile), outputDir);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return removed;
|
|
234
|
+
}
|
|
235
|
+
function emitIndex(outputDir) {
|
|
236
|
+
const bundleFiles = findFiles(outputDir, ".shaderc");
|
|
237
|
+
const entries = bundleFiles.map((file)=>{
|
|
238
|
+
const rel = normalizePath(path__default["default"].relative(outputDir, file));
|
|
239
|
+
return {
|
|
240
|
+
varName: shadercPathToVarName(rel),
|
|
241
|
+
importPath: `./${rel}`
|
|
242
|
+
};
|
|
243
|
+
}).sort((a, b)=>a.varName.localeCompare(b.varName));
|
|
244
|
+
const imports = entries.map((e)=>`import ${e.varName} from "${e.importPath}";`).join("\n");
|
|
245
|
+
const exportBlock = `export {\n${entries.map((e)=>` ${e.varName}`).join(",\n")}\n};`;
|
|
246
|
+
// prettier-ignore the export block: consumer's prettier config is unknown.
|
|
247
|
+
const content = `// Auto-generated by shader-compiler-precompile --emit-index — do not edit.\n${imports}\n\n// prettier-ignore\n${exportBlock}\n`;
|
|
248
|
+
const indexPath = path__default["default"].join(outputDir, "index.ts");
|
|
249
|
+
const existing = fs__default["default"].existsSync(indexPath) ? fs__default["default"].readFileSync(indexPath, "utf-8") : "";
|
|
250
|
+
if (content !== existing) {
|
|
251
|
+
fs__default["default"].writeFileSync(indexPath, content);
|
|
252
|
+
console.log(" Updated index.ts");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Generate raw-source indexes:
|
|
256
|
+
// <inputDir>/index.ts — `.shader` array (defines IShaderSource interface)
|
|
257
|
+
// <sibling>/ShaderLibrary/index.ts — `.glsl` array (only if the directory exists)
|
|
258
|
+
function emitSources(inputDir) {
|
|
259
|
+
emitShaderSourceIndex(inputDir);
|
|
260
|
+
const libraryDir = path__default["default"].join(path__default["default"].dirname(inputDir), "ShaderLibrary");
|
|
261
|
+
if (fs__default["default"].existsSync(libraryDir)) {
|
|
262
|
+
emitLibrarySourceIndex(libraryDir);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function emitShaderSourceIndex(rootDir) {
|
|
266
|
+
const srcDir = path__default["default"].dirname(rootDir);
|
|
267
|
+
const files = findFiles(rootDir, ".shader").sort();
|
|
268
|
+
const importPaths = files.map((f)=>normalizePath(path__default["default"].relative(rootDir, f)));
|
|
269
|
+
const paths = files.map((f)=>normalizePath(path__default["default"].relative(srcDir, f)));
|
|
270
|
+
const ids = importPaths.map((p)=>pathToIdentifier(p, ".shader"));
|
|
271
|
+
const lines = [];
|
|
272
|
+
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
273
|
+
lines.push("");
|
|
274
|
+
importPaths.forEach((rel, i)=>lines.push(`import ${ids[i]} from "./${rel}";`));
|
|
275
|
+
lines.push("");
|
|
276
|
+
lines.push("export interface IShaderSource {");
|
|
277
|
+
lines.push(" path: string;");
|
|
278
|
+
lines.push(" source: string;");
|
|
279
|
+
lines.push("}");
|
|
280
|
+
lines.push("");
|
|
281
|
+
lines.push("// prettier-ignore");
|
|
282
|
+
lines.push("export const shaders: IShaderSource[] = [");
|
|
283
|
+
paths.forEach((p, i)=>lines.push(` { source: ${ids[i]}, path: ${JSON.stringify(p)} },`));
|
|
284
|
+
lines.push("];");
|
|
285
|
+
lines.push("");
|
|
286
|
+
const out = path__default["default"].join(rootDir, "index.ts");
|
|
287
|
+
fs__default["default"].writeFileSync(out, lines.join("\n"), "utf-8");
|
|
288
|
+
console.log(` Emitted shaders index ${path__default["default"].relative(process.cwd(), out)} (${files.length})`);
|
|
289
|
+
}
|
|
290
|
+
function emitLibrarySourceIndex(rootDir) {
|
|
291
|
+
const srcDir = path__default["default"].dirname(rootDir);
|
|
292
|
+
const files = findFiles(rootDir, ".glsl").sort();
|
|
293
|
+
const importPaths = files.map((f)=>normalizePath(path__default["default"].relative(rootDir, f)));
|
|
294
|
+
const paths = files.map((f)=>normalizePath(path__default["default"].relative(srcDir, f)));
|
|
295
|
+
const ids = importPaths.map((p)=>pathToIdentifier(p, ".glsl"));
|
|
296
|
+
const lines = [];
|
|
297
|
+
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
298
|
+
lines.push("");
|
|
299
|
+
lines.push(`import type { IShaderSource } from "../Shaders";`);
|
|
300
|
+
importPaths.forEach((rel, i)=>lines.push(`import ${ids[i]} from "./${rel}";`));
|
|
301
|
+
lines.push("");
|
|
302
|
+
lines.push("// prettier-ignore");
|
|
303
|
+
lines.push("export const shaderLibrary: IShaderSource[] = [");
|
|
304
|
+
paths.forEach((p, i)=>lines.push(` { source: ${ids[i]}, path: ${JSON.stringify(p)} },`));
|
|
305
|
+
lines.push("];");
|
|
306
|
+
lines.push("");
|
|
307
|
+
const out = path__default["default"].join(rootDir, "index.ts");
|
|
308
|
+
fs__default["default"].writeFileSync(out, lines.join("\n"), "utf-8");
|
|
309
|
+
console.log(` Emitted library index ${path__default["default"].relative(process.cwd(), out)} (${files.length})`);
|
|
310
|
+
}
|
|
311
|
+
function removeBundleFor(shaderPath, inputDir, outputDir) {
|
|
312
|
+
const rel = normalizePath(path__default["default"].relative(inputDir, shaderPath)).replace(/\.shader$/, ".shaderc");
|
|
313
|
+
const bundlePath = path__default["default"].join(outputDir, rel);
|
|
314
|
+
if (fs__default["default"].existsSync(bundlePath)) {
|
|
315
|
+
fs__default["default"].unlinkSync(bundlePath);
|
|
316
|
+
console.log(` Removed: ${rel}`);
|
|
317
|
+
pruneEmptyDirs(path__default["default"].dirname(bundlePath), outputDir);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
exports.normalizePath = normalizePath;
|
|
322
|
+
exports.precompile = precompile;
|
|
323
|
+
exports.runFull = runFull;
|
|
324
|
+
exports.startWatcher = startWatcher;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
|
|
6
|
+
/** Normalize to forward slashes (Windows uses `\`; downstream regex/keys assume `/`). */ function normalizePath(p) {
|
|
7
|
+
return p.split(path.sep).join("/");
|
|
8
|
+
}
|
|
9
|
+
/** Recursively find all files with a given extension. */ function findFiles(dir, ext) {
|
|
10
|
+
const results = [];
|
|
11
|
+
if (!fs.existsSync(dir)) return results;
|
|
12
|
+
for (const entry of fs.readdirSync(dir, {
|
|
13
|
+
withFileTypes: true
|
|
14
|
+
})){
|
|
15
|
+
const fullPath = path.join(dir, entry.name);
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
results.push(...findFiles(fullPath, ext));
|
|
18
|
+
} else if (entry.name.endsWith(ext)) {
|
|
19
|
+
results.push(normalizePath(fullPath));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return results;
|
|
23
|
+
}
|
|
24
|
+
/** `PBR.shaderc` → `PBRSource`, `2D/Sprite.shaderc` → `SpriteSource`. */ function shadercPathToVarName(relPath) {
|
|
25
|
+
const normalized = normalizePath(relPath).replace(/\.shaderc$/, "");
|
|
26
|
+
const base = normalized.split("/").pop() ?? normalized;
|
|
27
|
+
const cleaned = base.replace(/[^A-Za-z0-9]/g, "");
|
|
28
|
+
if (cleaned.length === 0) return "Source";
|
|
29
|
+
return `${cleaned[0].toUpperCase()}${cleaned.slice(1)}Source`;
|
|
30
|
+
}
|
|
31
|
+
/** `Common/Common.glsl` → `Common_Common` — TS identifier for source-index imports. */ function pathToIdentifier(rel, ext) {
|
|
32
|
+
let id = rel.replace(new RegExp(`\\${ext}$`), "").replace(/[^A-Za-z0-9_]/g, "_");
|
|
33
|
+
if (/^[0-9]/.test(id)) id = "_" + id;
|
|
34
|
+
return id;
|
|
35
|
+
}
|
|
36
|
+
/** Remove empty parent directories upward from `startDir` until `stopDir` is hit. */ function pruneEmptyDirs(startDir, stopDir) {
|
|
37
|
+
let dir = startDir;
|
|
38
|
+
while(dir !== stopDir && fs.existsSync(dir) && fs.readdirSync(dir).length === 0){
|
|
39
|
+
fs.rmdirSync(dir);
|
|
40
|
+
dir = path.dirname(dir);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// One-shot mode exits non-zero on failure so CI breaks; watch mode logs and keeps running.
|
|
44
|
+
async function precompile(options) {
|
|
45
|
+
const { failed } = await runFull(options);
|
|
46
|
+
if (options.watch) {
|
|
47
|
+
await startWatcher(options);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
console.log("[shader-compiler-bundler] Done.");
|
|
51
|
+
if (failed > 0) {
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function runFull(options) {
|
|
56
|
+
const inputDir = path.resolve(options.input);
|
|
57
|
+
const outputDir = path.resolve(options.output);
|
|
58
|
+
const platformTarget = options.platformTarget ?? 0;
|
|
59
|
+
if (!fs.existsSync(inputDir)) {
|
|
60
|
+
throw new Error(`Input directory does not exist: ${inputDir}`);
|
|
61
|
+
}
|
|
62
|
+
fs.mkdirSync(outputDir, {
|
|
63
|
+
recursive: true
|
|
64
|
+
});
|
|
65
|
+
// Emit source indexes before compile so collectIncludeMap can resolve the freshly-built sources.
|
|
66
|
+
if (options.emitSources) {
|
|
67
|
+
emitSources(inputDir);
|
|
68
|
+
}
|
|
69
|
+
const shaderCompiler = await loadShaderCompiler();
|
|
70
|
+
shaderCompiler._setIncludeMap(await collectIncludeMap(inputDir));
|
|
71
|
+
let failed = 0;
|
|
72
|
+
if (options.only) {
|
|
73
|
+
const target = path.resolve(options.only);
|
|
74
|
+
if (!compileSingle(shaderCompiler, target, inputDir, outputDir, platformTarget)) failed++;
|
|
75
|
+
} else {
|
|
76
|
+
const shaderFiles = findFiles(inputDir, ".shader");
|
|
77
|
+
console.log(`[shader-compiler-bundler] Precompiling ${shaderFiles.length} shader(s)...`);
|
|
78
|
+
for (const file of shaderFiles){
|
|
79
|
+
if (!compileSingle(shaderCompiler, file, inputDir, outputDir, platformTarget)) failed++;
|
|
80
|
+
}
|
|
81
|
+
if (options.clean) {
|
|
82
|
+
const removed = cleanOrphanedBundles(shaderFiles, inputDir, outputDir);
|
|
83
|
+
if (removed > 0) console.log(`[shader-compiler-bundler] Cleaned ${removed} orphaned .shaderc file(s).`);
|
|
84
|
+
}
|
|
85
|
+
if (failed > 0) console.warn(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
86
|
+
}
|
|
87
|
+
if (options.emitIndex) {
|
|
88
|
+
emitIndex(outputDir);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
failed
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function startWatcher(options) {
|
|
95
|
+
const inputDir = path.resolve(options.input);
|
|
96
|
+
const outputDir = path.resolve(options.output);
|
|
97
|
+
const platformTarget = options.platformTarget ?? 0;
|
|
98
|
+
const shaderCompiler = await loadShaderCompiler();
|
|
99
|
+
shaderCompiler._setIncludeMap(await collectIncludeMap(inputDir));
|
|
100
|
+
// .glsl change → full recompile (can't cheaply track include graphs).
|
|
101
|
+
// .shader change → single-file recompile.
|
|
102
|
+
const handleInputChange = async (filename)=>{
|
|
103
|
+
if (!filename) return;
|
|
104
|
+
const norm = normalizePath(filename);
|
|
105
|
+
const fullPath = path.join(inputDir, filename);
|
|
106
|
+
if (norm.endsWith(".glsl")) {
|
|
107
|
+
console.log(`[shader-compiler-bundler] Include changed (${norm}), full recompile...`);
|
|
108
|
+
shaderCompiler._setIncludeMap(await collectIncludeMap(inputDir));
|
|
109
|
+
runFull(options).catch((e)=>console.error(e));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!norm.endsWith(".shader")) return;
|
|
113
|
+
if (!fs.existsSync(fullPath)) {
|
|
114
|
+
removeBundleFor(fullPath, inputDir, outputDir);
|
|
115
|
+
} else {
|
|
116
|
+
compileSingle(shaderCompiler, fullPath, inputDir, outputDir, platformTarget);
|
|
117
|
+
}
|
|
118
|
+
if (options.emitSources) emitSources(inputDir);
|
|
119
|
+
if (options.emitIndex) emitIndex(outputDir);
|
|
120
|
+
};
|
|
121
|
+
console.log(`[shader-compiler-bundler] Watching ${inputDir} ...`);
|
|
122
|
+
fs.watch(inputDir, {
|
|
123
|
+
recursive: true
|
|
124
|
+
}, (_event, filename)=>{
|
|
125
|
+
handleInputChange(filename).catch((e)=>console.error(e));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function loadShaderCompiler() {
|
|
129
|
+
// @ts-ignore — `../dist/main.js` is the compiled runtime entry; no .ts source.
|
|
130
|
+
// Bundler ships at `<pkg>/bundler/` and the runtime at `<pkg>/dist/`, so this
|
|
131
|
+
// resolves to the sibling `dist/` from `bundler/cli.js` at runtime.
|
|
132
|
+
const mod = await import('../dist/main.js');
|
|
133
|
+
const instance = new mod.ShaderCompiler();
|
|
134
|
+
if (typeof instance._precompile !== "function") {
|
|
135
|
+
throw new Error("ShaderCompiler._precompile is not available; rebuild @galacean/engine-shader-compiler first.");
|
|
136
|
+
}
|
|
137
|
+
return instance;
|
|
138
|
+
}
|
|
139
|
+
// Local .glsl come from inputDir; standard library comes from
|
|
140
|
+
// `@galacean/engine-shader/sources` with a sibling-dir fallback for engine self-build cold-start.
|
|
141
|
+
async function collectIncludeMap(inputDir) {
|
|
142
|
+
const map = {};
|
|
143
|
+
for (const file of findFiles(inputDir, ".glsl")){
|
|
144
|
+
const relPath = normalizePath(path.relative(inputDir, file));
|
|
145
|
+
map[relPath] = fs.readFileSync(file, "utf-8");
|
|
146
|
+
}
|
|
147
|
+
let usedRelease = false;
|
|
148
|
+
// Resolve from inputDir's node_modules so consumers without transitive linkage to shader-compiler can still find it.
|
|
149
|
+
try {
|
|
150
|
+
const requireFromInput = createRequire(path.join(inputDir, "package.json"));
|
|
151
|
+
const sourcesPath = requireFromInput.resolve("@galacean/engine-shader/sources");
|
|
152
|
+
const { shaderLibrary } = await import(pathToFileURL(sourcesPath).href);
|
|
153
|
+
for (const { source, path: chunkPath } of shaderLibrary){
|
|
154
|
+
map[chunkPath] = source;
|
|
155
|
+
}
|
|
156
|
+
usedRelease = true;
|
|
157
|
+
} catch {
|
|
158
|
+
// engine self-build cold-start: dist/sources.* not yet built; use sibling source.
|
|
159
|
+
}
|
|
160
|
+
if (!usedRelease) {
|
|
161
|
+
const siblingLibrary = path.join(path.dirname(inputDir), "ShaderLibrary");
|
|
162
|
+
if (fs.existsSync(siblingLibrary)) {
|
|
163
|
+
for (const file of findFiles(siblingLibrary, ".glsl")){
|
|
164
|
+
const rel = normalizePath(path.relative(siblingLibrary, file));
|
|
165
|
+
map[`ShaderLibrary/${rel}`] = fs.readFileSync(file, "utf-8");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return map;
|
|
170
|
+
}
|
|
171
|
+
// Mirrors `ShaderPass._shaderRootPath` in engine; inlined to keep bundler engine-free.
|
|
172
|
+
const SHADER_ROOT_PATH = "shaders://root/";
|
|
173
|
+
function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platformTarget) {
|
|
174
|
+
const source = fs.readFileSync(shaderPath, "utf-8");
|
|
175
|
+
const relativePath = normalizePath(path.relative(inputDir, shaderPath));
|
|
176
|
+
const bundleRelative = relativePath.replace(/\.shader$/, ".shaderc");
|
|
177
|
+
const bundlePath = path.join(outputDir, bundleRelative);
|
|
178
|
+
const basePathForIncludeKey = new URL(relativePath, SHADER_ROOT_PATH).href;
|
|
179
|
+
fs.mkdirSync(path.dirname(bundlePath), {
|
|
180
|
+
recursive: true
|
|
181
|
+
});
|
|
182
|
+
try {
|
|
183
|
+
const precompiled = shaderCompiler._precompile(source, platformTarget, basePathForIncludeKey);
|
|
184
|
+
fs.writeFileSync(bundlePath, JSON.stringify(precompiled));
|
|
185
|
+
console.log(` ${relativePath} -> ${bundleRelative}`);
|
|
186
|
+
return true;
|
|
187
|
+
} catch (e) {
|
|
188
|
+
console.error(` FAILED: ${relativePath}`);
|
|
189
|
+
console.error(e);
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function cleanOrphanedBundles(shaderFiles, inputDir, outputDir) {
|
|
194
|
+
const aliveSet = new Set(shaderFiles.map((f)=>normalizePath(path.relative(inputDir, f)).replace(/\.shader$/, "")));
|
|
195
|
+
const bundleFiles = findFiles(outputDir, ".shaderc");
|
|
196
|
+
let removed = 0;
|
|
197
|
+
for (const bundleFile of bundleFiles){
|
|
198
|
+
const rel = normalizePath(path.relative(outputDir, bundleFile)).replace(/\.shaderc$/, "");
|
|
199
|
+
if (!aliveSet.has(rel)) {
|
|
200
|
+
fs.unlinkSync(bundleFile);
|
|
201
|
+
console.log(` Removed orphaned: ${rel}.shaderc`);
|
|
202
|
+
removed++;
|
|
203
|
+
pruneEmptyDirs(path.dirname(bundleFile), outputDir);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return removed;
|
|
207
|
+
}
|
|
208
|
+
function emitIndex(outputDir) {
|
|
209
|
+
const bundleFiles = findFiles(outputDir, ".shaderc");
|
|
210
|
+
const entries = bundleFiles.map((file)=>{
|
|
211
|
+
const rel = normalizePath(path.relative(outputDir, file));
|
|
212
|
+
return {
|
|
213
|
+
varName: shadercPathToVarName(rel),
|
|
214
|
+
importPath: `./${rel}`
|
|
215
|
+
};
|
|
216
|
+
}).sort((a, b)=>a.varName.localeCompare(b.varName));
|
|
217
|
+
const imports = entries.map((e)=>`import ${e.varName} from "${e.importPath}";`).join("\n");
|
|
218
|
+
const exportBlock = `export {\n${entries.map((e)=>` ${e.varName}`).join(",\n")}\n};`;
|
|
219
|
+
// prettier-ignore the export block: consumer's prettier config is unknown.
|
|
220
|
+
const content = `// Auto-generated by shader-compiler-precompile --emit-index — do not edit.\n${imports}\n\n// prettier-ignore\n${exportBlock}\n`;
|
|
221
|
+
const indexPath = path.join(outputDir, "index.ts");
|
|
222
|
+
const existing = fs.existsSync(indexPath) ? fs.readFileSync(indexPath, "utf-8") : "";
|
|
223
|
+
if (content !== existing) {
|
|
224
|
+
fs.writeFileSync(indexPath, content);
|
|
225
|
+
console.log(" Updated index.ts");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Generate raw-source indexes:
|
|
229
|
+
// <inputDir>/index.ts — `.shader` array (defines IShaderSource interface)
|
|
230
|
+
// <sibling>/ShaderLibrary/index.ts — `.glsl` array (only if the directory exists)
|
|
231
|
+
function emitSources(inputDir) {
|
|
232
|
+
emitShaderSourceIndex(inputDir);
|
|
233
|
+
const libraryDir = path.join(path.dirname(inputDir), "ShaderLibrary");
|
|
234
|
+
if (fs.existsSync(libraryDir)) {
|
|
235
|
+
emitLibrarySourceIndex(libraryDir);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function emitShaderSourceIndex(rootDir) {
|
|
239
|
+
const srcDir = path.dirname(rootDir);
|
|
240
|
+
const files = findFiles(rootDir, ".shader").sort();
|
|
241
|
+
const importPaths = files.map((f)=>normalizePath(path.relative(rootDir, f)));
|
|
242
|
+
const paths = files.map((f)=>normalizePath(path.relative(srcDir, f)));
|
|
243
|
+
const ids = importPaths.map((p)=>pathToIdentifier(p, ".shader"));
|
|
244
|
+
const lines = [];
|
|
245
|
+
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
246
|
+
lines.push("");
|
|
247
|
+
importPaths.forEach((rel, i)=>lines.push(`import ${ids[i]} from "./${rel}";`));
|
|
248
|
+
lines.push("");
|
|
249
|
+
lines.push("export interface IShaderSource {");
|
|
250
|
+
lines.push(" path: string;");
|
|
251
|
+
lines.push(" source: string;");
|
|
252
|
+
lines.push("}");
|
|
253
|
+
lines.push("");
|
|
254
|
+
lines.push("// prettier-ignore");
|
|
255
|
+
lines.push("export const shaders: IShaderSource[] = [");
|
|
256
|
+
paths.forEach((p, i)=>lines.push(` { source: ${ids[i]}, path: ${JSON.stringify(p)} },`));
|
|
257
|
+
lines.push("];");
|
|
258
|
+
lines.push("");
|
|
259
|
+
const out = path.join(rootDir, "index.ts");
|
|
260
|
+
fs.writeFileSync(out, lines.join("\n"), "utf-8");
|
|
261
|
+
console.log(` Emitted shaders index ${path.relative(process.cwd(), out)} (${files.length})`);
|
|
262
|
+
}
|
|
263
|
+
function emitLibrarySourceIndex(rootDir) {
|
|
264
|
+
const srcDir = path.dirname(rootDir);
|
|
265
|
+
const files = findFiles(rootDir, ".glsl").sort();
|
|
266
|
+
const importPaths = files.map((f)=>normalizePath(path.relative(rootDir, f)));
|
|
267
|
+
const paths = files.map((f)=>normalizePath(path.relative(srcDir, f)));
|
|
268
|
+
const ids = importPaths.map((p)=>pathToIdentifier(p, ".glsl"));
|
|
269
|
+
const lines = [];
|
|
270
|
+
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
271
|
+
lines.push("");
|
|
272
|
+
lines.push(`import type { IShaderSource } from "../Shaders";`);
|
|
273
|
+
importPaths.forEach((rel, i)=>lines.push(`import ${ids[i]} from "./${rel}";`));
|
|
274
|
+
lines.push("");
|
|
275
|
+
lines.push("// prettier-ignore");
|
|
276
|
+
lines.push("export const shaderLibrary: IShaderSource[] = [");
|
|
277
|
+
paths.forEach((p, i)=>lines.push(` { source: ${ids[i]}, path: ${JSON.stringify(p)} },`));
|
|
278
|
+
lines.push("];");
|
|
279
|
+
lines.push("");
|
|
280
|
+
const out = path.join(rootDir, "index.ts");
|
|
281
|
+
fs.writeFileSync(out, lines.join("\n"), "utf-8");
|
|
282
|
+
console.log(` Emitted library index ${path.relative(process.cwd(), out)} (${files.length})`);
|
|
283
|
+
}
|
|
284
|
+
function removeBundleFor(shaderPath, inputDir, outputDir) {
|
|
285
|
+
const rel = normalizePath(path.relative(inputDir, shaderPath)).replace(/\.shader$/, ".shaderc");
|
|
286
|
+
const bundlePath = path.join(outputDir, rel);
|
|
287
|
+
if (fs.existsSync(bundlePath)) {
|
|
288
|
+
fs.unlinkSync(bundlePath);
|
|
289
|
+
console.log(` Removed: ${rel}`);
|
|
290
|
+
pruneEmptyDirs(path.dirname(bundlePath), outputDir);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export { normalizePath, precompile, runFull, startWatcher };
|
|
@@ -141,8 +141,10 @@ async function startWatcher(options) {
|
|
|
141
141
|
});
|
|
142
142
|
}
|
|
143
143
|
async function loadShaderCompiler() {
|
|
144
|
-
// @ts-ignore — `../main.js` is the compiled runtime entry; no .ts source.
|
|
145
|
-
|
|
144
|
+
// @ts-ignore — `../dist/main.js` is the compiled runtime entry; no .ts source.
|
|
145
|
+
// Bundler ships at `<pkg>/bundler/` and the runtime at `<pkg>/dist/`, so this
|
|
146
|
+
// resolves to the sibling `dist/` from `bundler/cli.js` at runtime.
|
|
147
|
+
const mod = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('../dist/main.js')); });
|
|
146
148
|
const instance = new mod.ShaderCompiler();
|
|
147
149
|
if (typeof instance._precompile !== "function") {
|
|
148
150
|
throw new Error("ShaderCompiler._precompile is not available; rebuild @galacean/engine-shader-compiler first.");
|
|
@@ -114,8 +114,10 @@ async function startWatcher(options) {
|
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
116
|
async function loadShaderCompiler() {
|
|
117
|
-
// @ts-ignore — `../main.js` is the compiled runtime entry; no .ts source.
|
|
118
|
-
|
|
117
|
+
// @ts-ignore — `../dist/main.js` is the compiled runtime entry; no .ts source.
|
|
118
|
+
// Bundler ships at `<pkg>/bundler/` and the runtime at `<pkg>/dist/`, so this
|
|
119
|
+
// resolves to the sibling `dist/` from `bundler/cli.js` at runtime.
|
|
120
|
+
const mod = await import('../dist/main.js');
|
|
119
121
|
const instance = new mod.ShaderCompiler();
|
|
120
122
|
if (typeof instance._precompile !== "function") {
|
|
121
123
|
throw new Error("ShaderCompiler._precompile is not available; rebuild @galacean/engine-shader-compiler first.");
|