@galacean/engine-shader-compiler 2.0.0-alpha.51 → 2.0.0-alpha.53
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 +40 -4
- package/bundler/cli.js +199 -104
- package/bundler/precompile.cjs.js +117 -47
- package/bundler/precompile.js +117 -47
- package/bundler/rollup.cjs.js +115 -45
- package/bundler/rollup.js +115 -45
- package/dist/browser.js +13421 -6555
- package/dist/browser.js.map +1 -1
- package/dist/browser.min.js +1 -1
- package/dist/browser.min.js.map +1 -1
- package/dist/main.js +1758 -8450
- package/dist/main.js.map +1 -1
- package/dist/module.js +1730 -8427
- package/dist/module.js.map +1 -1
- package/dist/precompile.main.js +2280 -0
- package/dist/precompile.main.js.map +1 -0
- package/dist/precompile.module.js +2276 -0
- package/dist/precompile.module.js.map +1 -0
- package/package.json +24 -13
- package/types/GLESBackend.d.ts +22 -0
- package/types/ShaderBackend.d.ts +15 -0
- package/types/ShaderCompilation.d.ts +40 -0
- package/types/ShaderCompiler.d.ts +42 -14
- package/types/ShaderInstructionEncoder.d.ts +46 -14
- package/types/ShaderPrecompiler.d.ts +31 -0
- package/types/bundler/precompile.d.ts +26 -3
- package/types/bundler/rollup.d.ts +23 -2
- package/types/codeGen/CodeGenVisitor.d.ts +11 -6
- package/types/codeGen/GLES100.d.ts +3 -5
- package/types/codeGen/GLES300.d.ts +14 -7
- package/types/codeGen/GLESVisitor.d.ts +55 -9
- package/types/codeGen/VisitorContext.d.ts +88 -29
- package/types/codeGen/types.d.ts +1 -1
- package/types/index.d.ts +4 -1
- package/types/precompile.d.ts +1 -0
- package/dist/browser.verbose.js +0 -9601
- package/dist/browser.verbose.js.map +0 -1
- package/dist/browser.verbose.min.js +0 -2
- package/dist/browser.verbose.min.js.map +0 -1
- package/dist/main.verbose.js +0 -9598
- package/dist/main.verbose.js.map +0 -1
- package/dist/module.verbose.js +0 -9591
- package/dist/module.verbose.js.map +0 -1
- package/types/GSError.d.ts +0 -16
- package/types/ParserUtils.d.ts +0 -43
- package/types/Preprocessor.d.ts +0 -30
- package/types/ShaderCompilerUtils.d.ts +0 -10
- package/types/common/BaseLexer.d.ts +0 -51
- package/types/common/BaseToken.d.ts +0 -35
- package/types/common/IBaseSymbol.d.ts +0 -5
- package/types/common/ShaderPosition.d.ts +0 -8
- package/types/common/ShaderRange.d.ts +0 -8
- package/types/common/SymbolTable.d.ts +0 -13
- package/types/common/SymbolTableStack.d.ts +0 -17
- package/types/common/enums/Keyword.d.ts +0 -124
- package/types/common/enums/ShaderStage.d.ts +0 -5
- package/types/common/index.d.ts +0 -5
- package/types/common/types.d.ts +0 -101
- package/types/lalr/CFG.d.ts +0 -5
- package/types/lalr/LALR1.d.ts +0 -27
- package/types/lalr/Production.d.ts +0 -9
- package/types/lalr/State.d.ts +0 -20
- package/types/lalr/StateItem.d.ts +0 -20
- package/types/lalr/Utils.d.ts +0 -22
- package/types/lalr/index.d.ts +0 -2
- package/types/lalr/types.d.ts +0 -14
- package/types/lexer/Lexer.d.ts +0 -143
- package/types/lexer/index.d.ts +0 -1
- package/types/parser/AST.d.ts +0 -502
- package/types/parser/Grammar.d.ts +0 -11
- package/types/parser/GrammarSymbol.d.ts +0 -128
- package/types/parser/SemanticAnalyzer.d.ts +0 -42
- package/types/parser/ShaderInfo.d.ts +0 -10
- package/types/parser/ShaderTargetParser.d.ts +0 -26
- package/types/parser/builtin/functions.d.ts +0 -34
- package/types/parser/builtin/index.d.ts +0 -2
- package/types/parser/builtin/variables.d.ts +0 -11
- package/types/parser/index.d.ts +0 -2
- package/types/parser/symbolTable/FnSymbol.d.ts +0 -6
- package/types/parser/symbolTable/StructSymbol.d.ts +0 -6
- package/types/parser/symbolTable/SymbolDataType.d.ts +0 -8
- package/types/parser/symbolTable/SymbolInfo.d.ts +0 -22
- package/types/parser/symbolTable/VarSymbol.d.ts +0 -8
- package/types/parser/symbolTable/index.d.ts +0 -6
- package/types/parser/types.d.ts +0 -24
- package/types/sourceParser/ShaderSourceFactory.d.ts +0 -8
- package/types/sourceParser/ShaderSourceParser.d.ts +0 -30
- package/types/sourceParser/ShaderSourceSymbol.d.ts +0 -10
- package/types/sourceParser/SourceLexer.d.ts +0 -19
- package/types/sourceParser/index.d.ts +0 -1
- package/verbose/package.json +0 -10
|
@@ -5,7 +5,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var fs = require('node:fs');
|
|
6
6
|
var path = require('node:path');
|
|
7
7
|
var node_module = require('node:module');
|
|
8
|
-
var node_url = require('node:url');
|
|
9
8
|
|
|
10
9
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
11
10
|
|
|
@@ -30,7 +29,11 @@ function _interopNamespace(e) {
|
|
|
30
29
|
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
31
30
|
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
32
31
|
|
|
33
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Converts a filesystem path to the slash-separated form used by shader asset keys.
|
|
34
|
+
* @param p - Filesystem path.
|
|
35
|
+
* @returns Slash-separated path.
|
|
36
|
+
*/ function normalizePath(p) {
|
|
34
37
|
return p.split(path__default["default"].sep).join("/");
|
|
35
38
|
}
|
|
36
39
|
/** Recursively find all files with a given extension. */ function findFiles(dir, ext) {
|
|
@@ -67,8 +70,12 @@ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
|
67
70
|
dir = path__default["default"].dirname(dir);
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Precompiles shaders once or starts incremental watch mode.
|
|
75
|
+
* @param options - Source, output, cleanup, watch, and artifact-generation options.
|
|
76
|
+
* @returns After a one-shot compile finishes or a watch has been installed.
|
|
77
|
+
* @throws Error when a one-shot compile has failures or the source tree is invalid.
|
|
78
|
+
*/ async function precompile(options) {
|
|
72
79
|
const { failed } = await runFull(options);
|
|
73
80
|
if (options.watch) {
|
|
74
81
|
await startWatcher(options);
|
|
@@ -76,10 +83,15 @@ async function precompile(options) {
|
|
|
76
83
|
}
|
|
77
84
|
console.log("[shader-compiler-bundler] Done.");
|
|
78
85
|
if (failed > 0) {
|
|
79
|
-
|
|
86
|
+
throw new Error(`${failed} shader(s) failed to precompile.`);
|
|
80
87
|
}
|
|
81
88
|
}
|
|
82
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Precompiles the current shader source tree once.
|
|
91
|
+
* @param options - Source, output, cleanup, and artifact-generation options.
|
|
92
|
+
* @returns An object containing the number of shader sources that failed to compile.
|
|
93
|
+
* @throws Error when the input tree or an explicitly selected source is invalid.
|
|
94
|
+
*/ async function runFull(options) {
|
|
83
95
|
const inputDir = path__default["default"].resolve(options.input);
|
|
84
96
|
const outputDir = path__default["default"].resolve(options.output);
|
|
85
97
|
const platformTarget = options.platformTarget ?? 0;
|
|
@@ -94,22 +106,13 @@ async function runFull(options) {
|
|
|
94
106
|
emitSources(inputDir);
|
|
95
107
|
}
|
|
96
108
|
const shaderCompiler = await loadShaderCompiler();
|
|
97
|
-
shaderCompiler.
|
|
109
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
98
110
|
let failed = 0;
|
|
99
111
|
if (options.only) {
|
|
100
112
|
const target = path__default["default"].resolve(options.only);
|
|
101
113
|
if (!compileSingle(shaderCompiler, target, inputDir, outputDir, platformTarget)) failed++;
|
|
102
114
|
} else {
|
|
103
|
-
|
|
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.`);
|
|
115
|
+
failed = compileAll(shaderCompiler, inputDir, outputDir, platformTarget, options.clean);
|
|
113
116
|
}
|
|
114
117
|
if (options.emitIndex) {
|
|
115
118
|
emitIndex(outputDir);
|
|
@@ -118,12 +121,23 @@ async function runFull(options) {
|
|
|
118
121
|
failed
|
|
119
122
|
};
|
|
120
123
|
}
|
|
121
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Watches one shader source tree and serializes incremental rebuilds.
|
|
126
|
+
* @param options - Source, output, cleanup, and artifact-generation options.
|
|
127
|
+
* @returns Once the filesystem watcher has been installed.
|
|
128
|
+
* @throws Error when the initial include registry cannot be loaded.
|
|
129
|
+
*/ async function startWatcher(options) {
|
|
122
130
|
const inputDir = path__default["default"].resolve(options.input);
|
|
123
131
|
const outputDir = path__default["default"].resolve(options.output);
|
|
124
132
|
const platformTarget = options.platformTarget ?? 0;
|
|
133
|
+
if (!fs__default["default"].existsSync(inputDir)) {
|
|
134
|
+
throw new Error(`Input directory does not exist: ${inputDir}`);
|
|
135
|
+
}
|
|
136
|
+
fs__default["default"].mkdirSync(outputDir, {
|
|
137
|
+
recursive: true
|
|
138
|
+
});
|
|
125
139
|
const shaderCompiler = await loadShaderCompiler();
|
|
126
|
-
shaderCompiler.
|
|
140
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
127
141
|
// .glsl change → full recompile (can't cheaply track include graphs).
|
|
128
142
|
// .shader change → single-file recompile.
|
|
129
143
|
const handleInputChange = async (filename)=>{
|
|
@@ -132,8 +146,11 @@ async function startWatcher(options) {
|
|
|
132
146
|
const fullPath = path__default["default"].join(inputDir, filename);
|
|
133
147
|
if (norm.endsWith(".glsl")) {
|
|
134
148
|
console.log(`[shader-compiler-bundler] Include changed (${norm}), full recompile...`);
|
|
135
|
-
|
|
136
|
-
|
|
149
|
+
if (options.emitSources) emitSources(inputDir);
|
|
150
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
151
|
+
const failed = compileAll(shaderCompiler, inputDir, outputDir, platformTarget, options.clean);
|
|
152
|
+
if (options.emitIndex) emitIndex(outputDir);
|
|
153
|
+
if (failed > 0) console.error(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
137
154
|
return;
|
|
138
155
|
}
|
|
139
156
|
if (!norm.endsWith(".shader")) return;
|
|
@@ -146,68 +163,92 @@ async function startWatcher(options) {
|
|
|
146
163
|
if (options.emitIndex) emitIndex(outputDir);
|
|
147
164
|
};
|
|
148
165
|
console.log(`[shader-compiler-bundler] Watching ${inputDir} ...`);
|
|
166
|
+
let changeQueue = Promise.resolve();
|
|
149
167
|
fs__default["default"].watch(inputDir, {
|
|
150
168
|
recursive: true
|
|
151
169
|
}, (_event, filename)=>{
|
|
152
|
-
handleInputChange(filename).catch((e)=>console.error(e));
|
|
170
|
+
changeQueue = changeQueue.then(()=>handleInputChange(filename)).catch((e)=>console.error(e));
|
|
153
171
|
});
|
|
154
172
|
}
|
|
173
|
+
function compileAll(shaderCompiler, inputDir, outputDir, platformTarget, clean) {
|
|
174
|
+
const shaderFiles = findFiles(inputDir, ".shader");
|
|
175
|
+
console.log(`[shader-compiler-bundler] Precompiling ${shaderFiles.length} shader(s)...`);
|
|
176
|
+
let failed = 0;
|
|
177
|
+
for (const file of shaderFiles){
|
|
178
|
+
if (!compileSingle(shaderCompiler, file, inputDir, outputDir, platformTarget)) failed++;
|
|
179
|
+
}
|
|
180
|
+
if (clean) {
|
|
181
|
+
const removed = cleanOrphanedBundles(shaderFiles, inputDir, outputDir);
|
|
182
|
+
if (removed > 0) console.log(`[shader-compiler-bundler] Cleaned ${removed} orphaned .shaderc file(s).`);
|
|
183
|
+
}
|
|
184
|
+
if (failed > 0) console.warn(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
185
|
+
return failed;
|
|
186
|
+
}
|
|
155
187
|
async function loadShaderCompiler() {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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.");
|
|
188
|
+
const mod = await (process.env.GALACEAN_SHADER_COMPILER_BOOTSTRAP === "true" ? importBootstrapPrecompiler() : importReleasePrecompiler());
|
|
189
|
+
const instance = new mod.ShaderPrecompiler();
|
|
190
|
+
if (typeof instance.precompile !== "function") {
|
|
191
|
+
throw new Error("ShaderPrecompiler is unavailable; rebuild @galacean/engine-shader-compiler first.");
|
|
163
192
|
}
|
|
164
193
|
return instance;
|
|
165
194
|
}
|
|
195
|
+
async function importBootstrapPrecompiler() {
|
|
196
|
+
// @ts-expect-error generated repo-local runtime has no declaration file
|
|
197
|
+
return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('../.bootstrap/main.js')); });
|
|
198
|
+
}
|
|
199
|
+
async function importReleasePrecompiler() {
|
|
200
|
+
// @ts-expect-error generated Node-only runtime has no declaration file
|
|
201
|
+
return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('../dist/precompile.main.js')); });
|
|
202
|
+
}
|
|
166
203
|
// Local .glsl come from inputDir; standard library comes from
|
|
167
204
|
// `@galacean/engine-shader/sources` with a sibling-dir fallback for engine self-build cold-start.
|
|
168
205
|
async function collectIncludeMap(inputDir) {
|
|
169
|
-
const map =
|
|
206
|
+
const map = Object.create(null);
|
|
170
207
|
for (const file of findFiles(inputDir, ".glsl")){
|
|
171
208
|
const relPath = normalizePath(path__default["default"].relative(inputDir, file));
|
|
172
|
-
map
|
|
209
|
+
addIncludeSource(map, relPath, fs__default["default"].readFileSync(file, "utf-8"));
|
|
173
210
|
}
|
|
174
|
-
let usedRelease = false;
|
|
175
211
|
// Resolve from inputDir's node_modules so consumers without transitive linkage to shader-compiler can still find it.
|
|
212
|
+
const requireFromInput = node_module.createRequire(path__default["default"].join(inputDir, "package.json"));
|
|
213
|
+
let sourcesPath;
|
|
176
214
|
try {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
215
|
+
sourcesPath = requireFromInput.resolve("@galacean/engine-shader/sources");
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (error.code !== "MODULE_NOT_FOUND") throw error;
|
|
218
|
+
}
|
|
219
|
+
if (sourcesPath) {
|
|
220
|
+
const { shaderLibrary } = requireFromInput(sourcesPath);
|
|
180
221
|
for (const { source, path: chunkPath } of shaderLibrary){
|
|
181
|
-
map
|
|
222
|
+
addIncludeSource(map, chunkPath, source);
|
|
182
223
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// engine self-build cold-start: dist/sources.* not yet built; use sibling source.
|
|
186
|
-
}
|
|
187
|
-
if (!usedRelease) {
|
|
224
|
+
} else {
|
|
225
|
+
// Engine self-build cold-start has no generated sources entry yet.
|
|
188
226
|
const siblingLibrary = path__default["default"].join(path__default["default"].dirname(inputDir), "ShaderLibrary");
|
|
189
227
|
if (fs__default["default"].existsSync(siblingLibrary)) {
|
|
190
228
|
for (const file of findFiles(siblingLibrary, ".glsl")){
|
|
191
229
|
const rel = normalizePath(path__default["default"].relative(siblingLibrary, file));
|
|
192
|
-
map
|
|
230
|
+
addIncludeSource(map, `ShaderLibrary/${rel}`, fs__default["default"].readFileSync(file, "utf-8"));
|
|
193
231
|
}
|
|
194
232
|
}
|
|
195
233
|
}
|
|
196
234
|
return map;
|
|
197
235
|
}
|
|
198
|
-
|
|
199
|
-
|
|
236
|
+
function addIncludeSource(map, key, source) {
|
|
237
|
+
if (Object.prototype.hasOwnProperty.call(map, key)) {
|
|
238
|
+
throw new Error(`Shader include "${key}" is registered more than once.`);
|
|
239
|
+
}
|
|
240
|
+
map[key] = source;
|
|
241
|
+
}
|
|
200
242
|
function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platformTarget) {
|
|
243
|
+
const relativePath = shaderSourceRelativePath(shaderPath, inputDir);
|
|
201
244
|
const source = fs__default["default"].readFileSync(shaderPath, "utf-8");
|
|
202
|
-
const relativePath = normalizePath(path__default["default"].relative(inputDir, shaderPath));
|
|
203
245
|
const bundleRelative = relativePath.replace(/\.shader$/, ".shaderc");
|
|
204
246
|
const bundlePath = path__default["default"].join(outputDir, bundleRelative);
|
|
205
|
-
const basePathForIncludeKey = new URL(relativePath, SHADER_ROOT_PATH).href;
|
|
206
247
|
fs__default["default"].mkdirSync(path__default["default"].dirname(bundlePath), {
|
|
207
248
|
recursive: true
|
|
208
249
|
});
|
|
209
250
|
try {
|
|
210
|
-
const precompiled = shaderCompiler.
|
|
251
|
+
const precompiled = shaderCompiler.precompile(source, platformTarget, relativePath);
|
|
211
252
|
fs__default["default"].writeFileSync(bundlePath, JSON.stringify(precompiled));
|
|
212
253
|
console.log(` ${relativePath} -> ${bundleRelative}`);
|
|
213
254
|
return true;
|
|
@@ -217,6 +258,13 @@ function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platform
|
|
|
217
258
|
return false;
|
|
218
259
|
}
|
|
219
260
|
}
|
|
261
|
+
function shaderSourceRelativePath(shaderPath, inputDir) {
|
|
262
|
+
const relativePath = path__default["default"].relative(inputDir, shaderPath);
|
|
263
|
+
if (relativePath === ".." || relativePath.startsWith(`..${path__default["default"].sep}`) || path__default["default"].isAbsolute(relativePath) || path__default["default"].extname(relativePath) !== ".shader") {
|
|
264
|
+
throw new Error(`Shader source must be a .shader file inside the input directory: ${shaderPath}`);
|
|
265
|
+
}
|
|
266
|
+
return normalizePath(relativePath);
|
|
267
|
+
}
|
|
220
268
|
function cleanOrphanedBundles(shaderFiles, inputDir, outputDir) {
|
|
221
269
|
const aliveSet = new Set(shaderFiles.map((f)=>normalizePath(path__default["default"].relative(inputDir, f)).replace(/\.shader$/, "")));
|
|
222
270
|
const bundleFiles = findFiles(outputDir, ".shaderc");
|
|
@@ -241,6 +289,10 @@ function emitIndex(outputDir) {
|
|
|
241
289
|
importPath: `./${rel}`
|
|
242
290
|
};
|
|
243
291
|
}).sort((a, b)=>a.varName.localeCompare(b.varName));
|
|
292
|
+
assertUniqueIdentifiers(entries.map(({ varName, importPath })=>({
|
|
293
|
+
identifier: varName,
|
|
294
|
+
path: importPath
|
|
295
|
+
})));
|
|
244
296
|
const imports = entries.map((e)=>`import ${e.varName}Data from "${e.importPath}";`).join("\n");
|
|
245
297
|
const exports = entries.map((e)=>`export const ${e.varName}: IPrecompiledShader = ${e.varName}Data;`).join("\n");
|
|
246
298
|
const content = `// Auto-generated by shader-compiler-precompile --emit-index — do not edit.\nimport type { IPrecompiledShader } from "@galacean/engine-design";\n${imports}\n\n${exports}\n`;
|
|
@@ -267,6 +319,10 @@ function emitShaderSourceIndex(rootDir) {
|
|
|
267
319
|
const importPaths = files.map((f)=>normalizePath(path__default["default"].relative(rootDir, f)));
|
|
268
320
|
const paths = files.map((f)=>normalizePath(path__default["default"].relative(srcDir, f)));
|
|
269
321
|
const ids = importPaths.map((p)=>pathToIdentifier(p, ".shader"));
|
|
322
|
+
assertUniqueIdentifiers(ids.map((identifier, index)=>({
|
|
323
|
+
identifier,
|
|
324
|
+
path: importPaths[index]
|
|
325
|
+
})));
|
|
270
326
|
const lines = [];
|
|
271
327
|
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
272
328
|
lines.push("");
|
|
@@ -292,6 +348,10 @@ function emitLibrarySourceIndex(rootDir) {
|
|
|
292
348
|
const importPaths = files.map((f)=>normalizePath(path__default["default"].relative(rootDir, f)));
|
|
293
349
|
const paths = files.map((f)=>normalizePath(path__default["default"].relative(srcDir, f)));
|
|
294
350
|
const ids = importPaths.map((p)=>pathToIdentifier(p, ".glsl"));
|
|
351
|
+
assertUniqueIdentifiers(ids.map((identifier, index)=>({
|
|
352
|
+
identifier,
|
|
353
|
+
path: importPaths[index]
|
|
354
|
+
})));
|
|
295
355
|
const lines = [];
|
|
296
356
|
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
297
357
|
lines.push("");
|
|
@@ -307,6 +367,16 @@ function emitLibrarySourceIndex(rootDir) {
|
|
|
307
367
|
fs__default["default"].writeFileSync(out, lines.join("\n"), "utf-8");
|
|
308
368
|
console.log(` Emitted library index ${path__default["default"].relative(process.cwd(), out)} (${files.length})`);
|
|
309
369
|
}
|
|
370
|
+
function assertUniqueIdentifiers(entries) {
|
|
371
|
+
const pathsByIdentifier = new Map();
|
|
372
|
+
for (const { identifier, path: sourcePath } of entries){
|
|
373
|
+
const existingPath = pathsByIdentifier.get(identifier);
|
|
374
|
+
if (existingPath !== undefined) {
|
|
375
|
+
throw new Error(`Shader asset paths "${existingPath}" and "${sourcePath}" both emit identifier "${identifier}".`);
|
|
376
|
+
}
|
|
377
|
+
pathsByIdentifier.set(identifier, sourcePath);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
310
380
|
function removeBundleFor(shaderPath, inputDir, outputDir) {
|
|
311
381
|
const rel = normalizePath(path__default["default"].relative(inputDir, shaderPath)).replace(/\.shader$/, ".shaderc");
|
|
312
382
|
const bundlePath = path__default["default"].join(outputDir, rel);
|
package/bundler/precompile.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
4
|
|
|
6
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Converts a filesystem path to the slash-separated form used by shader asset keys.
|
|
7
|
+
* @param p - Filesystem path.
|
|
8
|
+
* @returns Slash-separated path.
|
|
9
|
+
*/ function normalizePath(p) {
|
|
7
10
|
return p.split(path.sep).join("/");
|
|
8
11
|
}
|
|
9
12
|
/** Recursively find all files with a given extension. */ function findFiles(dir, ext) {
|
|
@@ -40,8 +43,12 @@ import { pathToFileURL } from 'node:url';
|
|
|
40
43
|
dir = path.dirname(dir);
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Precompiles shaders once or starts incremental watch mode.
|
|
48
|
+
* @param options - Source, output, cleanup, watch, and artifact-generation options.
|
|
49
|
+
* @returns After a one-shot compile finishes or a watch has been installed.
|
|
50
|
+
* @throws Error when a one-shot compile has failures or the source tree is invalid.
|
|
51
|
+
*/ async function precompile(options) {
|
|
45
52
|
const { failed } = await runFull(options);
|
|
46
53
|
if (options.watch) {
|
|
47
54
|
await startWatcher(options);
|
|
@@ -49,10 +56,15 @@ async function precompile(options) {
|
|
|
49
56
|
}
|
|
50
57
|
console.log("[shader-compiler-bundler] Done.");
|
|
51
58
|
if (failed > 0) {
|
|
52
|
-
|
|
59
|
+
throw new Error(`${failed} shader(s) failed to precompile.`);
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Precompiles the current shader source tree once.
|
|
64
|
+
* @param options - Source, output, cleanup, and artifact-generation options.
|
|
65
|
+
* @returns An object containing the number of shader sources that failed to compile.
|
|
66
|
+
* @throws Error when the input tree or an explicitly selected source is invalid.
|
|
67
|
+
*/ async function runFull(options) {
|
|
56
68
|
const inputDir = path.resolve(options.input);
|
|
57
69
|
const outputDir = path.resolve(options.output);
|
|
58
70
|
const platformTarget = options.platformTarget ?? 0;
|
|
@@ -67,22 +79,13 @@ async function runFull(options) {
|
|
|
67
79
|
emitSources(inputDir);
|
|
68
80
|
}
|
|
69
81
|
const shaderCompiler = await loadShaderCompiler();
|
|
70
|
-
shaderCompiler.
|
|
82
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
71
83
|
let failed = 0;
|
|
72
84
|
if (options.only) {
|
|
73
85
|
const target = path.resolve(options.only);
|
|
74
86
|
if (!compileSingle(shaderCompiler, target, inputDir, outputDir, platformTarget)) failed++;
|
|
75
87
|
} else {
|
|
76
|
-
|
|
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.`);
|
|
88
|
+
failed = compileAll(shaderCompiler, inputDir, outputDir, platformTarget, options.clean);
|
|
86
89
|
}
|
|
87
90
|
if (options.emitIndex) {
|
|
88
91
|
emitIndex(outputDir);
|
|
@@ -91,12 +94,23 @@ async function runFull(options) {
|
|
|
91
94
|
failed
|
|
92
95
|
};
|
|
93
96
|
}
|
|
94
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Watches one shader source tree and serializes incremental rebuilds.
|
|
99
|
+
* @param options - Source, output, cleanup, and artifact-generation options.
|
|
100
|
+
* @returns Once the filesystem watcher has been installed.
|
|
101
|
+
* @throws Error when the initial include registry cannot be loaded.
|
|
102
|
+
*/ async function startWatcher(options) {
|
|
95
103
|
const inputDir = path.resolve(options.input);
|
|
96
104
|
const outputDir = path.resolve(options.output);
|
|
97
105
|
const platformTarget = options.platformTarget ?? 0;
|
|
106
|
+
if (!fs.existsSync(inputDir)) {
|
|
107
|
+
throw new Error(`Input directory does not exist: ${inputDir}`);
|
|
108
|
+
}
|
|
109
|
+
fs.mkdirSync(outputDir, {
|
|
110
|
+
recursive: true
|
|
111
|
+
});
|
|
98
112
|
const shaderCompiler = await loadShaderCompiler();
|
|
99
|
-
shaderCompiler.
|
|
113
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
100
114
|
// .glsl change → full recompile (can't cheaply track include graphs).
|
|
101
115
|
// .shader change → single-file recompile.
|
|
102
116
|
const handleInputChange = async (filename)=>{
|
|
@@ -105,8 +119,11 @@ async function startWatcher(options) {
|
|
|
105
119
|
const fullPath = path.join(inputDir, filename);
|
|
106
120
|
if (norm.endsWith(".glsl")) {
|
|
107
121
|
console.log(`[shader-compiler-bundler] Include changed (${norm}), full recompile...`);
|
|
108
|
-
|
|
109
|
-
|
|
122
|
+
if (options.emitSources) emitSources(inputDir);
|
|
123
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
124
|
+
const failed = compileAll(shaderCompiler, inputDir, outputDir, platformTarget, options.clean);
|
|
125
|
+
if (options.emitIndex) emitIndex(outputDir);
|
|
126
|
+
if (failed > 0) console.error(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
110
127
|
return;
|
|
111
128
|
}
|
|
112
129
|
if (!norm.endsWith(".shader")) return;
|
|
@@ -119,68 +136,92 @@ async function startWatcher(options) {
|
|
|
119
136
|
if (options.emitIndex) emitIndex(outputDir);
|
|
120
137
|
};
|
|
121
138
|
console.log(`[shader-compiler-bundler] Watching ${inputDir} ...`);
|
|
139
|
+
let changeQueue = Promise.resolve();
|
|
122
140
|
fs.watch(inputDir, {
|
|
123
141
|
recursive: true
|
|
124
142
|
}, (_event, filename)=>{
|
|
125
|
-
handleInputChange(filename).catch((e)=>console.error(e));
|
|
143
|
+
changeQueue = changeQueue.then(()=>handleInputChange(filename)).catch((e)=>console.error(e));
|
|
126
144
|
});
|
|
127
145
|
}
|
|
146
|
+
function compileAll(shaderCompiler, inputDir, outputDir, platformTarget, clean) {
|
|
147
|
+
const shaderFiles = findFiles(inputDir, ".shader");
|
|
148
|
+
console.log(`[shader-compiler-bundler] Precompiling ${shaderFiles.length} shader(s)...`);
|
|
149
|
+
let failed = 0;
|
|
150
|
+
for (const file of shaderFiles){
|
|
151
|
+
if (!compileSingle(shaderCompiler, file, inputDir, outputDir, platformTarget)) failed++;
|
|
152
|
+
}
|
|
153
|
+
if (clean) {
|
|
154
|
+
const removed = cleanOrphanedBundles(shaderFiles, inputDir, outputDir);
|
|
155
|
+
if (removed > 0) console.log(`[shader-compiler-bundler] Cleaned ${removed} orphaned .shaderc file(s).`);
|
|
156
|
+
}
|
|
157
|
+
if (failed > 0) console.warn(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
158
|
+
return failed;
|
|
159
|
+
}
|
|
128
160
|
async function loadShaderCompiler() {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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.");
|
|
161
|
+
const mod = await (process.env.GALACEAN_SHADER_COMPILER_BOOTSTRAP === "true" ? importBootstrapPrecompiler() : importReleasePrecompiler());
|
|
162
|
+
const instance = new mod.ShaderPrecompiler();
|
|
163
|
+
if (typeof instance.precompile !== "function") {
|
|
164
|
+
throw new Error("ShaderPrecompiler is unavailable; rebuild @galacean/engine-shader-compiler first.");
|
|
136
165
|
}
|
|
137
166
|
return instance;
|
|
138
167
|
}
|
|
168
|
+
async function importBootstrapPrecompiler() {
|
|
169
|
+
// @ts-expect-error generated repo-local runtime has no declaration file
|
|
170
|
+
return import('../.bootstrap/main.js');
|
|
171
|
+
}
|
|
172
|
+
async function importReleasePrecompiler() {
|
|
173
|
+
// @ts-expect-error generated Node-only runtime has no declaration file
|
|
174
|
+
return import('../dist/precompile.main.js');
|
|
175
|
+
}
|
|
139
176
|
// Local .glsl come from inputDir; standard library comes from
|
|
140
177
|
// `@galacean/engine-shader/sources` with a sibling-dir fallback for engine self-build cold-start.
|
|
141
178
|
async function collectIncludeMap(inputDir) {
|
|
142
|
-
const map =
|
|
179
|
+
const map = Object.create(null);
|
|
143
180
|
for (const file of findFiles(inputDir, ".glsl")){
|
|
144
181
|
const relPath = normalizePath(path.relative(inputDir, file));
|
|
145
|
-
map
|
|
182
|
+
addIncludeSource(map, relPath, fs.readFileSync(file, "utf-8"));
|
|
146
183
|
}
|
|
147
|
-
let usedRelease = false;
|
|
148
184
|
// Resolve from inputDir's node_modules so consumers without transitive linkage to shader-compiler can still find it.
|
|
185
|
+
const requireFromInput = createRequire(path.join(inputDir, "package.json"));
|
|
186
|
+
let sourcesPath;
|
|
149
187
|
try {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
188
|
+
sourcesPath = requireFromInput.resolve("@galacean/engine-shader/sources");
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (error.code !== "MODULE_NOT_FOUND") throw error;
|
|
191
|
+
}
|
|
192
|
+
if (sourcesPath) {
|
|
193
|
+
const { shaderLibrary } = requireFromInput(sourcesPath);
|
|
153
194
|
for (const { source, path: chunkPath } of shaderLibrary){
|
|
154
|
-
map
|
|
195
|
+
addIncludeSource(map, chunkPath, source);
|
|
155
196
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
// engine self-build cold-start: dist/sources.* not yet built; use sibling source.
|
|
159
|
-
}
|
|
160
|
-
if (!usedRelease) {
|
|
197
|
+
} else {
|
|
198
|
+
// Engine self-build cold-start has no generated sources entry yet.
|
|
161
199
|
const siblingLibrary = path.join(path.dirname(inputDir), "ShaderLibrary");
|
|
162
200
|
if (fs.existsSync(siblingLibrary)) {
|
|
163
201
|
for (const file of findFiles(siblingLibrary, ".glsl")){
|
|
164
202
|
const rel = normalizePath(path.relative(siblingLibrary, file));
|
|
165
|
-
map
|
|
203
|
+
addIncludeSource(map, `ShaderLibrary/${rel}`, fs.readFileSync(file, "utf-8"));
|
|
166
204
|
}
|
|
167
205
|
}
|
|
168
206
|
}
|
|
169
207
|
return map;
|
|
170
208
|
}
|
|
171
|
-
|
|
172
|
-
|
|
209
|
+
function addIncludeSource(map, key, source) {
|
|
210
|
+
if (Object.prototype.hasOwnProperty.call(map, key)) {
|
|
211
|
+
throw new Error(`Shader include "${key}" is registered more than once.`);
|
|
212
|
+
}
|
|
213
|
+
map[key] = source;
|
|
214
|
+
}
|
|
173
215
|
function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platformTarget) {
|
|
216
|
+
const relativePath = shaderSourceRelativePath(shaderPath, inputDir);
|
|
174
217
|
const source = fs.readFileSync(shaderPath, "utf-8");
|
|
175
|
-
const relativePath = normalizePath(path.relative(inputDir, shaderPath));
|
|
176
218
|
const bundleRelative = relativePath.replace(/\.shader$/, ".shaderc");
|
|
177
219
|
const bundlePath = path.join(outputDir, bundleRelative);
|
|
178
|
-
const basePathForIncludeKey = new URL(relativePath, SHADER_ROOT_PATH).href;
|
|
179
220
|
fs.mkdirSync(path.dirname(bundlePath), {
|
|
180
221
|
recursive: true
|
|
181
222
|
});
|
|
182
223
|
try {
|
|
183
|
-
const precompiled = shaderCompiler.
|
|
224
|
+
const precompiled = shaderCompiler.precompile(source, platformTarget, relativePath);
|
|
184
225
|
fs.writeFileSync(bundlePath, JSON.stringify(precompiled));
|
|
185
226
|
console.log(` ${relativePath} -> ${bundleRelative}`);
|
|
186
227
|
return true;
|
|
@@ -190,6 +231,13 @@ function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platform
|
|
|
190
231
|
return false;
|
|
191
232
|
}
|
|
192
233
|
}
|
|
234
|
+
function shaderSourceRelativePath(shaderPath, inputDir) {
|
|
235
|
+
const relativePath = path.relative(inputDir, shaderPath);
|
|
236
|
+
if (relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath) || path.extname(relativePath) !== ".shader") {
|
|
237
|
+
throw new Error(`Shader source must be a .shader file inside the input directory: ${shaderPath}`);
|
|
238
|
+
}
|
|
239
|
+
return normalizePath(relativePath);
|
|
240
|
+
}
|
|
193
241
|
function cleanOrphanedBundles(shaderFiles, inputDir, outputDir) {
|
|
194
242
|
const aliveSet = new Set(shaderFiles.map((f)=>normalizePath(path.relative(inputDir, f)).replace(/\.shader$/, "")));
|
|
195
243
|
const bundleFiles = findFiles(outputDir, ".shaderc");
|
|
@@ -214,6 +262,10 @@ function emitIndex(outputDir) {
|
|
|
214
262
|
importPath: `./${rel}`
|
|
215
263
|
};
|
|
216
264
|
}).sort((a, b)=>a.varName.localeCompare(b.varName));
|
|
265
|
+
assertUniqueIdentifiers(entries.map(({ varName, importPath })=>({
|
|
266
|
+
identifier: varName,
|
|
267
|
+
path: importPath
|
|
268
|
+
})));
|
|
217
269
|
const imports = entries.map((e)=>`import ${e.varName}Data from "${e.importPath}";`).join("\n");
|
|
218
270
|
const exports = entries.map((e)=>`export const ${e.varName}: IPrecompiledShader = ${e.varName}Data;`).join("\n");
|
|
219
271
|
const content = `// Auto-generated by shader-compiler-precompile --emit-index — do not edit.\nimport type { IPrecompiledShader } from "@galacean/engine-design";\n${imports}\n\n${exports}\n`;
|
|
@@ -240,6 +292,10 @@ function emitShaderSourceIndex(rootDir) {
|
|
|
240
292
|
const importPaths = files.map((f)=>normalizePath(path.relative(rootDir, f)));
|
|
241
293
|
const paths = files.map((f)=>normalizePath(path.relative(srcDir, f)));
|
|
242
294
|
const ids = importPaths.map((p)=>pathToIdentifier(p, ".shader"));
|
|
295
|
+
assertUniqueIdentifiers(ids.map((identifier, index)=>({
|
|
296
|
+
identifier,
|
|
297
|
+
path: importPaths[index]
|
|
298
|
+
})));
|
|
243
299
|
const lines = [];
|
|
244
300
|
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
245
301
|
lines.push("");
|
|
@@ -265,6 +321,10 @@ function emitLibrarySourceIndex(rootDir) {
|
|
|
265
321
|
const importPaths = files.map((f)=>normalizePath(path.relative(rootDir, f)));
|
|
266
322
|
const paths = files.map((f)=>normalizePath(path.relative(srcDir, f)));
|
|
267
323
|
const ids = importPaths.map((p)=>pathToIdentifier(p, ".glsl"));
|
|
324
|
+
assertUniqueIdentifiers(ids.map((identifier, index)=>({
|
|
325
|
+
identifier,
|
|
326
|
+
path: importPaths[index]
|
|
327
|
+
})));
|
|
268
328
|
const lines = [];
|
|
269
329
|
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
270
330
|
lines.push("");
|
|
@@ -280,6 +340,16 @@ function emitLibrarySourceIndex(rootDir) {
|
|
|
280
340
|
fs.writeFileSync(out, lines.join("\n"), "utf-8");
|
|
281
341
|
console.log(` Emitted library index ${path.relative(process.cwd(), out)} (${files.length})`);
|
|
282
342
|
}
|
|
343
|
+
function assertUniqueIdentifiers(entries) {
|
|
344
|
+
const pathsByIdentifier = new Map();
|
|
345
|
+
for (const { identifier, path: sourcePath } of entries){
|
|
346
|
+
const existingPath = pathsByIdentifier.get(identifier);
|
|
347
|
+
if (existingPath !== undefined) {
|
|
348
|
+
throw new Error(`Shader asset paths "${existingPath}" and "${sourcePath}" both emit identifier "${identifier}".`);
|
|
349
|
+
}
|
|
350
|
+
pathsByIdentifier.set(identifier, sourcePath);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
283
353
|
function removeBundleFor(shaderPath, inputDir, outputDir) {
|
|
284
354
|
const rel = normalizePath(path.relative(inputDir, shaderPath)).replace(/\.shader$/, ".shaderc");
|
|
285
355
|
const bundlePath = path.join(outputDir, rel);
|