@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
package/bundler/rollup.cjs.js
CHANGED
|
@@ -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,7 +70,12 @@ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
|
67
70
|
dir = path__default["default"].dirname(dir);
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Precompiles the current shader source tree once.
|
|
75
|
+
* @param options - Source, output, cleanup, and artifact-generation options.
|
|
76
|
+
* @returns An object containing the number of shader sources that failed to compile.
|
|
77
|
+
* @throws Error when the input tree or an explicitly selected source is invalid.
|
|
78
|
+
*/ async function runFull(options) {
|
|
71
79
|
const inputDir = path__default["default"].resolve(options.input);
|
|
72
80
|
const outputDir = path__default["default"].resolve(options.output);
|
|
73
81
|
const platformTarget = options.platformTarget ?? 0;
|
|
@@ -82,22 +90,13 @@ async function runFull(options) {
|
|
|
82
90
|
emitSources(inputDir);
|
|
83
91
|
}
|
|
84
92
|
const shaderCompiler = await loadShaderCompiler();
|
|
85
|
-
shaderCompiler.
|
|
93
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
86
94
|
let failed = 0;
|
|
87
95
|
if (options.only) {
|
|
88
96
|
const target = path__default["default"].resolve(options.only);
|
|
89
97
|
if (!compileSingle(shaderCompiler, target, inputDir, outputDir, platformTarget)) failed++;
|
|
90
98
|
} else {
|
|
91
|
-
|
|
92
|
-
console.log(`[shader-compiler-bundler] Precompiling ${shaderFiles.length} shader(s)...`);
|
|
93
|
-
for (const file of shaderFiles){
|
|
94
|
-
if (!compileSingle(shaderCompiler, file, inputDir, outputDir, platformTarget)) failed++;
|
|
95
|
-
}
|
|
96
|
-
if (options.clean) {
|
|
97
|
-
const removed = cleanOrphanedBundles(shaderFiles, inputDir, outputDir);
|
|
98
|
-
if (removed > 0) console.log(`[shader-compiler-bundler] Cleaned ${removed} orphaned .shaderc file(s).`);
|
|
99
|
-
}
|
|
100
|
-
if (failed > 0) console.warn(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
99
|
+
failed = compileAll(shaderCompiler, inputDir, outputDir, platformTarget, options.clean);
|
|
101
100
|
}
|
|
102
101
|
if (options.emitIndex) {
|
|
103
102
|
emitIndex(outputDir);
|
|
@@ -106,12 +105,23 @@ async function runFull(options) {
|
|
|
106
105
|
failed
|
|
107
106
|
};
|
|
108
107
|
}
|
|
109
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Watches one shader source tree and serializes incremental rebuilds.
|
|
110
|
+
* @param options - Source, output, cleanup, and artifact-generation options.
|
|
111
|
+
* @returns Once the filesystem watcher has been installed.
|
|
112
|
+
* @throws Error when the initial include registry cannot be loaded.
|
|
113
|
+
*/ async function startWatcher(options) {
|
|
110
114
|
const inputDir = path__default["default"].resolve(options.input);
|
|
111
115
|
const outputDir = path__default["default"].resolve(options.output);
|
|
112
116
|
const platformTarget = options.platformTarget ?? 0;
|
|
117
|
+
if (!fs__default["default"].existsSync(inputDir)) {
|
|
118
|
+
throw new Error(`Input directory does not exist: ${inputDir}`);
|
|
119
|
+
}
|
|
120
|
+
fs__default["default"].mkdirSync(outputDir, {
|
|
121
|
+
recursive: true
|
|
122
|
+
});
|
|
113
123
|
const shaderCompiler = await loadShaderCompiler();
|
|
114
|
-
shaderCompiler.
|
|
124
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
115
125
|
// .glsl change → full recompile (can't cheaply track include graphs).
|
|
116
126
|
// .shader change → single-file recompile.
|
|
117
127
|
const handleInputChange = async (filename)=>{
|
|
@@ -120,8 +130,11 @@ async function startWatcher(options) {
|
|
|
120
130
|
const fullPath = path__default["default"].join(inputDir, filename);
|
|
121
131
|
if (norm.endsWith(".glsl")) {
|
|
122
132
|
console.log(`[shader-compiler-bundler] Include changed (${norm}), full recompile...`);
|
|
123
|
-
|
|
124
|
-
|
|
133
|
+
if (options.emitSources) emitSources(inputDir);
|
|
134
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
135
|
+
const failed = compileAll(shaderCompiler, inputDir, outputDir, platformTarget, options.clean);
|
|
136
|
+
if (options.emitIndex) emitIndex(outputDir);
|
|
137
|
+
if (failed > 0) console.error(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
125
138
|
return;
|
|
126
139
|
}
|
|
127
140
|
if (!norm.endsWith(".shader")) return;
|
|
@@ -134,68 +147,92 @@ async function startWatcher(options) {
|
|
|
134
147
|
if (options.emitIndex) emitIndex(outputDir);
|
|
135
148
|
};
|
|
136
149
|
console.log(`[shader-compiler-bundler] Watching ${inputDir} ...`);
|
|
150
|
+
let changeQueue = Promise.resolve();
|
|
137
151
|
fs__default["default"].watch(inputDir, {
|
|
138
152
|
recursive: true
|
|
139
153
|
}, (_event, filename)=>{
|
|
140
|
-
handleInputChange(filename).catch((e)=>console.error(e));
|
|
154
|
+
changeQueue = changeQueue.then(()=>handleInputChange(filename)).catch((e)=>console.error(e));
|
|
141
155
|
});
|
|
142
156
|
}
|
|
157
|
+
function compileAll(shaderCompiler, inputDir, outputDir, platformTarget, clean) {
|
|
158
|
+
const shaderFiles = findFiles(inputDir, ".shader");
|
|
159
|
+
console.log(`[shader-compiler-bundler] Precompiling ${shaderFiles.length} shader(s)...`);
|
|
160
|
+
let failed = 0;
|
|
161
|
+
for (const file of shaderFiles){
|
|
162
|
+
if (!compileSingle(shaderCompiler, file, inputDir, outputDir, platformTarget)) failed++;
|
|
163
|
+
}
|
|
164
|
+
if (clean) {
|
|
165
|
+
const removed = cleanOrphanedBundles(shaderFiles, inputDir, outputDir);
|
|
166
|
+
if (removed > 0) console.log(`[shader-compiler-bundler] Cleaned ${removed} orphaned .shaderc file(s).`);
|
|
167
|
+
}
|
|
168
|
+
if (failed > 0) console.warn(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
169
|
+
return failed;
|
|
170
|
+
}
|
|
143
171
|
async function loadShaderCompiler() {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const instance = new mod.ShaderCompiler();
|
|
149
|
-
if (typeof instance._precompile !== "function") {
|
|
150
|
-
throw new Error("ShaderCompiler._precompile is not available; rebuild @galacean/engine-shader-compiler first.");
|
|
172
|
+
const mod = await (process.env.GALACEAN_SHADER_COMPILER_BOOTSTRAP === "true" ? importBootstrapPrecompiler() : importReleasePrecompiler());
|
|
173
|
+
const instance = new mod.ShaderPrecompiler();
|
|
174
|
+
if (typeof instance.precompile !== "function") {
|
|
175
|
+
throw new Error("ShaderPrecompiler is unavailable; rebuild @galacean/engine-shader-compiler first.");
|
|
151
176
|
}
|
|
152
177
|
return instance;
|
|
153
178
|
}
|
|
179
|
+
async function importBootstrapPrecompiler() {
|
|
180
|
+
// @ts-expect-error generated repo-local runtime has no declaration file
|
|
181
|
+
return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('../.bootstrap/main.js')); });
|
|
182
|
+
}
|
|
183
|
+
async function importReleasePrecompiler() {
|
|
184
|
+
// @ts-expect-error generated Node-only runtime has no declaration file
|
|
185
|
+
return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('../dist/precompile.main.js')); });
|
|
186
|
+
}
|
|
154
187
|
// Local .glsl come from inputDir; standard library comes from
|
|
155
188
|
// `@galacean/engine-shader/sources` with a sibling-dir fallback for engine self-build cold-start.
|
|
156
189
|
async function collectIncludeMap(inputDir) {
|
|
157
|
-
const map =
|
|
190
|
+
const map = Object.create(null);
|
|
158
191
|
for (const file of findFiles(inputDir, ".glsl")){
|
|
159
192
|
const relPath = normalizePath(path__default["default"].relative(inputDir, file));
|
|
160
|
-
map
|
|
193
|
+
addIncludeSource(map, relPath, fs__default["default"].readFileSync(file, "utf-8"));
|
|
161
194
|
}
|
|
162
|
-
let usedRelease = false;
|
|
163
195
|
// Resolve from inputDir's node_modules so consumers without transitive linkage to shader-compiler can still find it.
|
|
196
|
+
const requireFromInput = node_module.createRequire(path__default["default"].join(inputDir, "package.json"));
|
|
197
|
+
let sourcesPath;
|
|
164
198
|
try {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
199
|
+
sourcesPath = requireFromInput.resolve("@galacean/engine-shader/sources");
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (error.code !== "MODULE_NOT_FOUND") throw error;
|
|
202
|
+
}
|
|
203
|
+
if (sourcesPath) {
|
|
204
|
+
const { shaderLibrary } = requireFromInput(sourcesPath);
|
|
168
205
|
for (const { source, path: chunkPath } of shaderLibrary){
|
|
169
|
-
map
|
|
206
|
+
addIncludeSource(map, chunkPath, source);
|
|
170
207
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
// engine self-build cold-start: dist/sources.* not yet built; use sibling source.
|
|
174
|
-
}
|
|
175
|
-
if (!usedRelease) {
|
|
208
|
+
} else {
|
|
209
|
+
// Engine self-build cold-start has no generated sources entry yet.
|
|
176
210
|
const siblingLibrary = path__default["default"].join(path__default["default"].dirname(inputDir), "ShaderLibrary");
|
|
177
211
|
if (fs__default["default"].existsSync(siblingLibrary)) {
|
|
178
212
|
for (const file of findFiles(siblingLibrary, ".glsl")){
|
|
179
213
|
const rel = normalizePath(path__default["default"].relative(siblingLibrary, file));
|
|
180
|
-
map
|
|
214
|
+
addIncludeSource(map, `ShaderLibrary/${rel}`, fs__default["default"].readFileSync(file, "utf-8"));
|
|
181
215
|
}
|
|
182
216
|
}
|
|
183
217
|
}
|
|
184
218
|
return map;
|
|
185
219
|
}
|
|
186
|
-
|
|
187
|
-
|
|
220
|
+
function addIncludeSource(map, key, source) {
|
|
221
|
+
if (Object.prototype.hasOwnProperty.call(map, key)) {
|
|
222
|
+
throw new Error(`Shader include "${key}" is registered more than once.`);
|
|
223
|
+
}
|
|
224
|
+
map[key] = source;
|
|
225
|
+
}
|
|
188
226
|
function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platformTarget) {
|
|
227
|
+
const relativePath = shaderSourceRelativePath(shaderPath, inputDir);
|
|
189
228
|
const source = fs__default["default"].readFileSync(shaderPath, "utf-8");
|
|
190
|
-
const relativePath = normalizePath(path__default["default"].relative(inputDir, shaderPath));
|
|
191
229
|
const bundleRelative = relativePath.replace(/\.shader$/, ".shaderc");
|
|
192
230
|
const bundlePath = path__default["default"].join(outputDir, bundleRelative);
|
|
193
|
-
const basePathForIncludeKey = new URL(relativePath, SHADER_ROOT_PATH).href;
|
|
194
231
|
fs__default["default"].mkdirSync(path__default["default"].dirname(bundlePath), {
|
|
195
232
|
recursive: true
|
|
196
233
|
});
|
|
197
234
|
try {
|
|
198
|
-
const precompiled = shaderCompiler.
|
|
235
|
+
const precompiled = shaderCompiler.precompile(source, platformTarget, relativePath);
|
|
199
236
|
fs__default["default"].writeFileSync(bundlePath, JSON.stringify(precompiled));
|
|
200
237
|
console.log(` ${relativePath} -> ${bundleRelative}`);
|
|
201
238
|
return true;
|
|
@@ -205,6 +242,13 @@ function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platform
|
|
|
205
242
|
return false;
|
|
206
243
|
}
|
|
207
244
|
}
|
|
245
|
+
function shaderSourceRelativePath(shaderPath, inputDir) {
|
|
246
|
+
const relativePath = path__default["default"].relative(inputDir, shaderPath);
|
|
247
|
+
if (relativePath === ".." || relativePath.startsWith(`..${path__default["default"].sep}`) || path__default["default"].isAbsolute(relativePath) || path__default["default"].extname(relativePath) !== ".shader") {
|
|
248
|
+
throw new Error(`Shader source must be a .shader file inside the input directory: ${shaderPath}`);
|
|
249
|
+
}
|
|
250
|
+
return normalizePath(relativePath);
|
|
251
|
+
}
|
|
208
252
|
function cleanOrphanedBundles(shaderFiles, inputDir, outputDir) {
|
|
209
253
|
const aliveSet = new Set(shaderFiles.map((f)=>normalizePath(path__default["default"].relative(inputDir, f)).replace(/\.shader$/, "")));
|
|
210
254
|
const bundleFiles = findFiles(outputDir, ".shaderc");
|
|
@@ -229,6 +273,10 @@ function emitIndex(outputDir) {
|
|
|
229
273
|
importPath: `./${rel}`
|
|
230
274
|
};
|
|
231
275
|
}).sort((a, b)=>a.varName.localeCompare(b.varName));
|
|
276
|
+
assertUniqueIdentifiers(entries.map(({ varName, importPath })=>({
|
|
277
|
+
identifier: varName,
|
|
278
|
+
path: importPath
|
|
279
|
+
})));
|
|
232
280
|
const imports = entries.map((e)=>`import ${e.varName}Data from "${e.importPath}";`).join("\n");
|
|
233
281
|
const exports = entries.map((e)=>`export const ${e.varName}: IPrecompiledShader = ${e.varName}Data;`).join("\n");
|
|
234
282
|
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`;
|
|
@@ -255,6 +303,10 @@ function emitShaderSourceIndex(rootDir) {
|
|
|
255
303
|
const importPaths = files.map((f)=>normalizePath(path__default["default"].relative(rootDir, f)));
|
|
256
304
|
const paths = files.map((f)=>normalizePath(path__default["default"].relative(srcDir, f)));
|
|
257
305
|
const ids = importPaths.map((p)=>pathToIdentifier(p, ".shader"));
|
|
306
|
+
assertUniqueIdentifiers(ids.map((identifier, index)=>({
|
|
307
|
+
identifier,
|
|
308
|
+
path: importPaths[index]
|
|
309
|
+
})));
|
|
258
310
|
const lines = [];
|
|
259
311
|
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
260
312
|
lines.push("");
|
|
@@ -280,6 +332,10 @@ function emitLibrarySourceIndex(rootDir) {
|
|
|
280
332
|
const importPaths = files.map((f)=>normalizePath(path__default["default"].relative(rootDir, f)));
|
|
281
333
|
const paths = files.map((f)=>normalizePath(path__default["default"].relative(srcDir, f)));
|
|
282
334
|
const ids = importPaths.map((p)=>pathToIdentifier(p, ".glsl"));
|
|
335
|
+
assertUniqueIdentifiers(ids.map((identifier, index)=>({
|
|
336
|
+
identifier,
|
|
337
|
+
path: importPaths[index]
|
|
338
|
+
})));
|
|
283
339
|
const lines = [];
|
|
284
340
|
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
285
341
|
lines.push("");
|
|
@@ -295,6 +351,16 @@ function emitLibrarySourceIndex(rootDir) {
|
|
|
295
351
|
fs__default["default"].writeFileSync(out, lines.join("\n"), "utf-8");
|
|
296
352
|
console.log(` Emitted library index ${path__default["default"].relative(process.cwd(), out)} (${files.length})`);
|
|
297
353
|
}
|
|
354
|
+
function assertUniqueIdentifiers(entries) {
|
|
355
|
+
const pathsByIdentifier = new Map();
|
|
356
|
+
for (const { identifier, path: sourcePath } of entries){
|
|
357
|
+
const existingPath = pathsByIdentifier.get(identifier);
|
|
358
|
+
if (existingPath !== undefined) {
|
|
359
|
+
throw new Error(`Shader asset paths "${existingPath}" and "${sourcePath}" both emit identifier "${identifier}".`);
|
|
360
|
+
}
|
|
361
|
+
pathsByIdentifier.set(identifier, sourcePath);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
298
364
|
function removeBundleFor(shaderPath, inputDir, outputDir) {
|
|
299
365
|
const rel = normalizePath(path__default["default"].relative(inputDir, shaderPath)).replace(/\.shader$/, ".shaderc");
|
|
300
366
|
const bundlePath = path__default["default"].join(outputDir, rel);
|
|
@@ -333,6 +399,9 @@ function removeBundleFor(shaderPath, inputDir, outputDir) {
|
|
|
333
399
|
* - When `precompile` option is set: runs a full precompile in buildStart and,
|
|
334
400
|
* in watch mode, starts a background watcher that mirrors `.shader` source
|
|
335
401
|
* changes into `.shaderc` outputs and refreshes the aggregated index.
|
|
402
|
+
* @param options - Transform filter and optional offline precompile configuration.
|
|
403
|
+
* @returns Rollup plugin for shader source and artifact modules.
|
|
404
|
+
* @throws Error during `buildStart` when offline precompilation reports failures.
|
|
336
405
|
*/ function shaderCompiler(options = {}) {
|
|
337
406
|
const { filter, precompile } = options;
|
|
338
407
|
const precompileOptions = precompile && {
|
|
@@ -347,7 +416,8 @@ function removeBundleFor(shaderPath, inputDir, outputDir) {
|
|
|
347
416
|
async buildStart () {
|
|
348
417
|
if (!precompileOptions || initialDone) return;
|
|
349
418
|
initialDone = true;
|
|
350
|
-
await runFull(precompileOptions);
|
|
419
|
+
const { failed } = await runFull(precompileOptions);
|
|
420
|
+
if (failed > 0) this.error(`${failed} shader(s) failed to precompile.`);
|
|
351
421
|
if (this.meta.watchMode && !watcherStarted) {
|
|
352
422
|
watcherStarted = true;
|
|
353
423
|
startWatcher(precompileOptions).catch((e)=>this.warn(`[shader-compiler] watcher failed: ${e.message}`));
|
package/bundler/rollup.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,7 +43,12 @@ import { pathToFileURL } from 'node:url';
|
|
|
40
43
|
dir = path.dirname(dir);
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Precompiles the current shader source tree once.
|
|
48
|
+
* @param options - Source, output, cleanup, and artifact-generation options.
|
|
49
|
+
* @returns An object containing the number of shader sources that failed to compile.
|
|
50
|
+
* @throws Error when the input tree or an explicitly selected source is invalid.
|
|
51
|
+
*/ async function runFull(options) {
|
|
44
52
|
const inputDir = path.resolve(options.input);
|
|
45
53
|
const outputDir = path.resolve(options.output);
|
|
46
54
|
const platformTarget = options.platformTarget ?? 0;
|
|
@@ -55,22 +63,13 @@ async function runFull(options) {
|
|
|
55
63
|
emitSources(inputDir);
|
|
56
64
|
}
|
|
57
65
|
const shaderCompiler = await loadShaderCompiler();
|
|
58
|
-
shaderCompiler.
|
|
66
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
59
67
|
let failed = 0;
|
|
60
68
|
if (options.only) {
|
|
61
69
|
const target = path.resolve(options.only);
|
|
62
70
|
if (!compileSingle(shaderCompiler, target, inputDir, outputDir, platformTarget)) failed++;
|
|
63
71
|
} else {
|
|
64
|
-
|
|
65
|
-
console.log(`[shader-compiler-bundler] Precompiling ${shaderFiles.length} shader(s)...`);
|
|
66
|
-
for (const file of shaderFiles){
|
|
67
|
-
if (!compileSingle(shaderCompiler, file, inputDir, outputDir, platformTarget)) failed++;
|
|
68
|
-
}
|
|
69
|
-
if (options.clean) {
|
|
70
|
-
const removed = cleanOrphanedBundles(shaderFiles, inputDir, outputDir);
|
|
71
|
-
if (removed > 0) console.log(`[shader-compiler-bundler] Cleaned ${removed} orphaned .shaderc file(s).`);
|
|
72
|
-
}
|
|
73
|
-
if (failed > 0) console.warn(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
72
|
+
failed = compileAll(shaderCompiler, inputDir, outputDir, platformTarget, options.clean);
|
|
74
73
|
}
|
|
75
74
|
if (options.emitIndex) {
|
|
76
75
|
emitIndex(outputDir);
|
|
@@ -79,12 +78,23 @@ async function runFull(options) {
|
|
|
79
78
|
failed
|
|
80
79
|
};
|
|
81
80
|
}
|
|
82
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Watches one shader source tree and serializes incremental rebuilds.
|
|
83
|
+
* @param options - Source, output, cleanup, and artifact-generation options.
|
|
84
|
+
* @returns Once the filesystem watcher has been installed.
|
|
85
|
+
* @throws Error when the initial include registry cannot be loaded.
|
|
86
|
+
*/ async function startWatcher(options) {
|
|
83
87
|
const inputDir = path.resolve(options.input);
|
|
84
88
|
const outputDir = path.resolve(options.output);
|
|
85
89
|
const platformTarget = options.platformTarget ?? 0;
|
|
90
|
+
if (!fs.existsSync(inputDir)) {
|
|
91
|
+
throw new Error(`Input directory does not exist: ${inputDir}`);
|
|
92
|
+
}
|
|
93
|
+
fs.mkdirSync(outputDir, {
|
|
94
|
+
recursive: true
|
|
95
|
+
});
|
|
86
96
|
const shaderCompiler = await loadShaderCompiler();
|
|
87
|
-
shaderCompiler.
|
|
97
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
88
98
|
// .glsl change → full recompile (can't cheaply track include graphs).
|
|
89
99
|
// .shader change → single-file recompile.
|
|
90
100
|
const handleInputChange = async (filename)=>{
|
|
@@ -93,8 +103,11 @@ async function startWatcher(options) {
|
|
|
93
103
|
const fullPath = path.join(inputDir, filename);
|
|
94
104
|
if (norm.endsWith(".glsl")) {
|
|
95
105
|
console.log(`[shader-compiler-bundler] Include changed (${norm}), full recompile...`);
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
if (options.emitSources) emitSources(inputDir);
|
|
107
|
+
shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
|
|
108
|
+
const failed = compileAll(shaderCompiler, inputDir, outputDir, platformTarget, options.clean);
|
|
109
|
+
if (options.emitIndex) emitIndex(outputDir);
|
|
110
|
+
if (failed > 0) console.error(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
98
111
|
return;
|
|
99
112
|
}
|
|
100
113
|
if (!norm.endsWith(".shader")) return;
|
|
@@ -107,68 +120,92 @@ async function startWatcher(options) {
|
|
|
107
120
|
if (options.emitIndex) emitIndex(outputDir);
|
|
108
121
|
};
|
|
109
122
|
console.log(`[shader-compiler-bundler] Watching ${inputDir} ...`);
|
|
123
|
+
let changeQueue = Promise.resolve();
|
|
110
124
|
fs.watch(inputDir, {
|
|
111
125
|
recursive: true
|
|
112
126
|
}, (_event, filename)=>{
|
|
113
|
-
handleInputChange(filename).catch((e)=>console.error(e));
|
|
127
|
+
changeQueue = changeQueue.then(()=>handleInputChange(filename)).catch((e)=>console.error(e));
|
|
114
128
|
});
|
|
115
129
|
}
|
|
130
|
+
function compileAll(shaderCompiler, inputDir, outputDir, platformTarget, clean) {
|
|
131
|
+
const shaderFiles = findFiles(inputDir, ".shader");
|
|
132
|
+
console.log(`[shader-compiler-bundler] Precompiling ${shaderFiles.length} shader(s)...`);
|
|
133
|
+
let failed = 0;
|
|
134
|
+
for (const file of shaderFiles){
|
|
135
|
+
if (!compileSingle(shaderCompiler, file, inputDir, outputDir, platformTarget)) failed++;
|
|
136
|
+
}
|
|
137
|
+
if (clean) {
|
|
138
|
+
const removed = cleanOrphanedBundles(shaderFiles, inputDir, outputDir);
|
|
139
|
+
if (removed > 0) console.log(`[shader-compiler-bundler] Cleaned ${removed} orphaned .shaderc file(s).`);
|
|
140
|
+
}
|
|
141
|
+
if (failed > 0) console.warn(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
|
|
142
|
+
return failed;
|
|
143
|
+
}
|
|
116
144
|
async function loadShaderCompiler() {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const instance = new mod.ShaderCompiler();
|
|
122
|
-
if (typeof instance._precompile !== "function") {
|
|
123
|
-
throw new Error("ShaderCompiler._precompile is not available; rebuild @galacean/engine-shader-compiler first.");
|
|
145
|
+
const mod = await (process.env.GALACEAN_SHADER_COMPILER_BOOTSTRAP === "true" ? importBootstrapPrecompiler() : importReleasePrecompiler());
|
|
146
|
+
const instance = new mod.ShaderPrecompiler();
|
|
147
|
+
if (typeof instance.precompile !== "function") {
|
|
148
|
+
throw new Error("ShaderPrecompiler is unavailable; rebuild @galacean/engine-shader-compiler first.");
|
|
124
149
|
}
|
|
125
150
|
return instance;
|
|
126
151
|
}
|
|
152
|
+
async function importBootstrapPrecompiler() {
|
|
153
|
+
// @ts-expect-error generated repo-local runtime has no declaration file
|
|
154
|
+
return import('../.bootstrap/main.js');
|
|
155
|
+
}
|
|
156
|
+
async function importReleasePrecompiler() {
|
|
157
|
+
// @ts-expect-error generated Node-only runtime has no declaration file
|
|
158
|
+
return import('../dist/precompile.main.js');
|
|
159
|
+
}
|
|
127
160
|
// Local .glsl come from inputDir; standard library comes from
|
|
128
161
|
// `@galacean/engine-shader/sources` with a sibling-dir fallback for engine self-build cold-start.
|
|
129
162
|
async function collectIncludeMap(inputDir) {
|
|
130
|
-
const map =
|
|
163
|
+
const map = Object.create(null);
|
|
131
164
|
for (const file of findFiles(inputDir, ".glsl")){
|
|
132
165
|
const relPath = normalizePath(path.relative(inputDir, file));
|
|
133
|
-
map
|
|
166
|
+
addIncludeSource(map, relPath, fs.readFileSync(file, "utf-8"));
|
|
134
167
|
}
|
|
135
|
-
let usedRelease = false;
|
|
136
168
|
// Resolve from inputDir's node_modules so consumers without transitive linkage to shader-compiler can still find it.
|
|
169
|
+
const requireFromInput = createRequire(path.join(inputDir, "package.json"));
|
|
170
|
+
let sourcesPath;
|
|
137
171
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
172
|
+
sourcesPath = requireFromInput.resolve("@galacean/engine-shader/sources");
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (error.code !== "MODULE_NOT_FOUND") throw error;
|
|
175
|
+
}
|
|
176
|
+
if (sourcesPath) {
|
|
177
|
+
const { shaderLibrary } = requireFromInput(sourcesPath);
|
|
141
178
|
for (const { source, path: chunkPath } of shaderLibrary){
|
|
142
|
-
map
|
|
179
|
+
addIncludeSource(map, chunkPath, source);
|
|
143
180
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// engine self-build cold-start: dist/sources.* not yet built; use sibling source.
|
|
147
|
-
}
|
|
148
|
-
if (!usedRelease) {
|
|
181
|
+
} else {
|
|
182
|
+
// Engine self-build cold-start has no generated sources entry yet.
|
|
149
183
|
const siblingLibrary = path.join(path.dirname(inputDir), "ShaderLibrary");
|
|
150
184
|
if (fs.existsSync(siblingLibrary)) {
|
|
151
185
|
for (const file of findFiles(siblingLibrary, ".glsl")){
|
|
152
186
|
const rel = normalizePath(path.relative(siblingLibrary, file));
|
|
153
|
-
map
|
|
187
|
+
addIncludeSource(map, `ShaderLibrary/${rel}`, fs.readFileSync(file, "utf-8"));
|
|
154
188
|
}
|
|
155
189
|
}
|
|
156
190
|
}
|
|
157
191
|
return map;
|
|
158
192
|
}
|
|
159
|
-
|
|
160
|
-
|
|
193
|
+
function addIncludeSource(map, key, source) {
|
|
194
|
+
if (Object.prototype.hasOwnProperty.call(map, key)) {
|
|
195
|
+
throw new Error(`Shader include "${key}" is registered more than once.`);
|
|
196
|
+
}
|
|
197
|
+
map[key] = source;
|
|
198
|
+
}
|
|
161
199
|
function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platformTarget) {
|
|
200
|
+
const relativePath = shaderSourceRelativePath(shaderPath, inputDir);
|
|
162
201
|
const source = fs.readFileSync(shaderPath, "utf-8");
|
|
163
|
-
const relativePath = normalizePath(path.relative(inputDir, shaderPath));
|
|
164
202
|
const bundleRelative = relativePath.replace(/\.shader$/, ".shaderc");
|
|
165
203
|
const bundlePath = path.join(outputDir, bundleRelative);
|
|
166
|
-
const basePathForIncludeKey = new URL(relativePath, SHADER_ROOT_PATH).href;
|
|
167
204
|
fs.mkdirSync(path.dirname(bundlePath), {
|
|
168
205
|
recursive: true
|
|
169
206
|
});
|
|
170
207
|
try {
|
|
171
|
-
const precompiled = shaderCompiler.
|
|
208
|
+
const precompiled = shaderCompiler.precompile(source, platformTarget, relativePath);
|
|
172
209
|
fs.writeFileSync(bundlePath, JSON.stringify(precompiled));
|
|
173
210
|
console.log(` ${relativePath} -> ${bundleRelative}`);
|
|
174
211
|
return true;
|
|
@@ -178,6 +215,13 @@ function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platform
|
|
|
178
215
|
return false;
|
|
179
216
|
}
|
|
180
217
|
}
|
|
218
|
+
function shaderSourceRelativePath(shaderPath, inputDir) {
|
|
219
|
+
const relativePath = path.relative(inputDir, shaderPath);
|
|
220
|
+
if (relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath) || path.extname(relativePath) !== ".shader") {
|
|
221
|
+
throw new Error(`Shader source must be a .shader file inside the input directory: ${shaderPath}`);
|
|
222
|
+
}
|
|
223
|
+
return normalizePath(relativePath);
|
|
224
|
+
}
|
|
181
225
|
function cleanOrphanedBundles(shaderFiles, inputDir, outputDir) {
|
|
182
226
|
const aliveSet = new Set(shaderFiles.map((f)=>normalizePath(path.relative(inputDir, f)).replace(/\.shader$/, "")));
|
|
183
227
|
const bundleFiles = findFiles(outputDir, ".shaderc");
|
|
@@ -202,6 +246,10 @@ function emitIndex(outputDir) {
|
|
|
202
246
|
importPath: `./${rel}`
|
|
203
247
|
};
|
|
204
248
|
}).sort((a, b)=>a.varName.localeCompare(b.varName));
|
|
249
|
+
assertUniqueIdentifiers(entries.map(({ varName, importPath })=>({
|
|
250
|
+
identifier: varName,
|
|
251
|
+
path: importPath
|
|
252
|
+
})));
|
|
205
253
|
const imports = entries.map((e)=>`import ${e.varName}Data from "${e.importPath}";`).join("\n");
|
|
206
254
|
const exports = entries.map((e)=>`export const ${e.varName}: IPrecompiledShader = ${e.varName}Data;`).join("\n");
|
|
207
255
|
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`;
|
|
@@ -228,6 +276,10 @@ function emitShaderSourceIndex(rootDir) {
|
|
|
228
276
|
const importPaths = files.map((f)=>normalizePath(path.relative(rootDir, f)));
|
|
229
277
|
const paths = files.map((f)=>normalizePath(path.relative(srcDir, f)));
|
|
230
278
|
const ids = importPaths.map((p)=>pathToIdentifier(p, ".shader"));
|
|
279
|
+
assertUniqueIdentifiers(ids.map((identifier, index)=>({
|
|
280
|
+
identifier,
|
|
281
|
+
path: importPaths[index]
|
|
282
|
+
})));
|
|
231
283
|
const lines = [];
|
|
232
284
|
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
233
285
|
lines.push("");
|
|
@@ -253,6 +305,10 @@ function emitLibrarySourceIndex(rootDir) {
|
|
|
253
305
|
const importPaths = files.map((f)=>normalizePath(path.relative(rootDir, f)));
|
|
254
306
|
const paths = files.map((f)=>normalizePath(path.relative(srcDir, f)));
|
|
255
307
|
const ids = importPaths.map((p)=>pathToIdentifier(p, ".glsl"));
|
|
308
|
+
assertUniqueIdentifiers(ids.map((identifier, index)=>({
|
|
309
|
+
identifier,
|
|
310
|
+
path: importPaths[index]
|
|
311
|
+
})));
|
|
256
312
|
const lines = [];
|
|
257
313
|
lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
|
|
258
314
|
lines.push("");
|
|
@@ -268,6 +324,16 @@ function emitLibrarySourceIndex(rootDir) {
|
|
|
268
324
|
fs.writeFileSync(out, lines.join("\n"), "utf-8");
|
|
269
325
|
console.log(` Emitted library index ${path.relative(process.cwd(), out)} (${files.length})`);
|
|
270
326
|
}
|
|
327
|
+
function assertUniqueIdentifiers(entries) {
|
|
328
|
+
const pathsByIdentifier = new Map();
|
|
329
|
+
for (const { identifier, path: sourcePath } of entries){
|
|
330
|
+
const existingPath = pathsByIdentifier.get(identifier);
|
|
331
|
+
if (existingPath !== undefined) {
|
|
332
|
+
throw new Error(`Shader asset paths "${existingPath}" and "${sourcePath}" both emit identifier "${identifier}".`);
|
|
333
|
+
}
|
|
334
|
+
pathsByIdentifier.set(identifier, sourcePath);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
271
337
|
function removeBundleFor(shaderPath, inputDir, outputDir) {
|
|
272
338
|
const rel = normalizePath(path.relative(inputDir, shaderPath)).replace(/\.shader$/, ".shaderc");
|
|
273
339
|
const bundlePath = path.join(outputDir, rel);
|
|
@@ -306,6 +372,9 @@ function removeBundleFor(shaderPath, inputDir, outputDir) {
|
|
|
306
372
|
* - When `precompile` option is set: runs a full precompile in buildStart and,
|
|
307
373
|
* in watch mode, starts a background watcher that mirrors `.shader` source
|
|
308
374
|
* changes into `.shaderc` outputs and refreshes the aggregated index.
|
|
375
|
+
* @param options - Transform filter and optional offline precompile configuration.
|
|
376
|
+
* @returns Rollup plugin for shader source and artifact modules.
|
|
377
|
+
* @throws Error during `buildStart` when offline precompilation reports failures.
|
|
309
378
|
*/ function shaderCompiler(options = {}) {
|
|
310
379
|
const { filter, precompile } = options;
|
|
311
380
|
const precompileOptions = precompile && {
|
|
@@ -320,7 +389,8 @@ function removeBundleFor(shaderPath, inputDir, outputDir) {
|
|
|
320
389
|
async buildStart () {
|
|
321
390
|
if (!precompileOptions || initialDone) return;
|
|
322
391
|
initialDone = true;
|
|
323
|
-
await runFull(precompileOptions);
|
|
392
|
+
const { failed } = await runFull(precompileOptions);
|
|
393
|
+
if (failed > 0) this.error(`${failed} shader(s) failed to precompile.`);
|
|
324
394
|
if (this.meta.watchMode && !watcherStarted) {
|
|
325
395
|
watcherStarted = true;
|
|
326
396
|
startWatcher(precompileOptions).catch((e)=>this.warn(`[shader-compiler] watcher failed: ${e.message}`));
|