@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.
Files changed (91) hide show
  1. package/README.md +40 -4
  2. package/bundler/cli.js +199 -104
  3. package/bundler/precompile.cjs.js +117 -47
  4. package/bundler/precompile.js +117 -47
  5. package/bundler/rollup.cjs.js +115 -45
  6. package/bundler/rollup.js +115 -45
  7. package/dist/browser.js +13421 -6555
  8. package/dist/browser.js.map +1 -1
  9. package/dist/browser.min.js +1 -1
  10. package/dist/browser.min.js.map +1 -1
  11. package/dist/main.js +1758 -8450
  12. package/dist/main.js.map +1 -1
  13. package/dist/module.js +1730 -8427
  14. package/dist/module.js.map +1 -1
  15. package/dist/precompile.main.js +2280 -0
  16. package/dist/precompile.main.js.map +1 -0
  17. package/dist/precompile.module.js +2276 -0
  18. package/dist/precompile.module.js.map +1 -0
  19. package/package.json +24 -13
  20. package/types/GLESBackend.d.ts +22 -0
  21. package/types/ShaderBackend.d.ts +15 -0
  22. package/types/ShaderCompilation.d.ts +40 -0
  23. package/types/ShaderCompiler.d.ts +42 -14
  24. package/types/ShaderInstructionEncoder.d.ts +46 -14
  25. package/types/ShaderPrecompiler.d.ts +31 -0
  26. package/types/bundler/precompile.d.ts +26 -3
  27. package/types/bundler/rollup.d.ts +23 -2
  28. package/types/codeGen/CodeGenVisitor.d.ts +11 -6
  29. package/types/codeGen/GLES100.d.ts +3 -5
  30. package/types/codeGen/GLES300.d.ts +14 -7
  31. package/types/codeGen/GLESVisitor.d.ts +55 -9
  32. package/types/codeGen/VisitorContext.d.ts +88 -29
  33. package/types/codeGen/types.d.ts +1 -1
  34. package/types/index.d.ts +4 -1
  35. package/types/precompile.d.ts +1 -0
  36. package/dist/browser.verbose.js +0 -9601
  37. package/dist/browser.verbose.js.map +0 -1
  38. package/dist/browser.verbose.min.js +0 -2
  39. package/dist/browser.verbose.min.js.map +0 -1
  40. package/dist/main.verbose.js +0 -9598
  41. package/dist/main.verbose.js.map +0 -1
  42. package/dist/module.verbose.js +0 -9591
  43. package/dist/module.verbose.js.map +0 -1
  44. package/types/GSError.d.ts +0 -16
  45. package/types/ParserUtils.d.ts +0 -43
  46. package/types/Preprocessor.d.ts +0 -30
  47. package/types/ShaderCompilerUtils.d.ts +0 -10
  48. package/types/common/BaseLexer.d.ts +0 -51
  49. package/types/common/BaseToken.d.ts +0 -35
  50. package/types/common/IBaseSymbol.d.ts +0 -5
  51. package/types/common/ShaderPosition.d.ts +0 -8
  52. package/types/common/ShaderRange.d.ts +0 -8
  53. package/types/common/SymbolTable.d.ts +0 -13
  54. package/types/common/SymbolTableStack.d.ts +0 -17
  55. package/types/common/enums/Keyword.d.ts +0 -124
  56. package/types/common/enums/ShaderStage.d.ts +0 -5
  57. package/types/common/index.d.ts +0 -5
  58. package/types/common/types.d.ts +0 -101
  59. package/types/lalr/CFG.d.ts +0 -5
  60. package/types/lalr/LALR1.d.ts +0 -27
  61. package/types/lalr/Production.d.ts +0 -9
  62. package/types/lalr/State.d.ts +0 -20
  63. package/types/lalr/StateItem.d.ts +0 -20
  64. package/types/lalr/Utils.d.ts +0 -22
  65. package/types/lalr/index.d.ts +0 -2
  66. package/types/lalr/types.d.ts +0 -14
  67. package/types/lexer/Lexer.d.ts +0 -143
  68. package/types/lexer/index.d.ts +0 -1
  69. package/types/parser/AST.d.ts +0 -502
  70. package/types/parser/Grammar.d.ts +0 -11
  71. package/types/parser/GrammarSymbol.d.ts +0 -128
  72. package/types/parser/SemanticAnalyzer.d.ts +0 -42
  73. package/types/parser/ShaderInfo.d.ts +0 -10
  74. package/types/parser/ShaderTargetParser.d.ts +0 -26
  75. package/types/parser/builtin/functions.d.ts +0 -34
  76. package/types/parser/builtin/index.d.ts +0 -2
  77. package/types/parser/builtin/variables.d.ts +0 -11
  78. package/types/parser/index.d.ts +0 -2
  79. package/types/parser/symbolTable/FnSymbol.d.ts +0 -6
  80. package/types/parser/symbolTable/StructSymbol.d.ts +0 -6
  81. package/types/parser/symbolTable/SymbolDataType.d.ts +0 -8
  82. package/types/parser/symbolTable/SymbolInfo.d.ts +0 -22
  83. package/types/parser/symbolTable/VarSymbol.d.ts +0 -8
  84. package/types/parser/symbolTable/index.d.ts +0 -6
  85. package/types/parser/types.d.ts +0 -24
  86. package/types/sourceParser/ShaderSourceFactory.d.ts +0 -8
  87. package/types/sourceParser/ShaderSourceParser.d.ts +0 -30
  88. package/types/sourceParser/ShaderSourceSymbol.d.ts +0 -10
  89. package/types/sourceParser/SourceLexer.d.ts +0 -19
  90. package/types/sourceParser/index.d.ts +0 -1
  91. package/verbose/package.json +0 -10
package/README.md CHANGED
@@ -7,6 +7,7 @@ npm install @galacean/engine-shader-compiler
7
7
  ## Usage
8
8
 
9
9
  ```typescript
10
+ import { Shader, ShaderFactory, WebGLEngine } from "@galacean/engine";
10
11
  import { ShaderCompiler } from "@galacean/engine-shader-compiler";
11
12
 
12
13
  // Create shader compiler
@@ -20,20 +21,55 @@ const engine = await WebGLEngine.create({ canvas: "canvas", shaderCompiler });
20
21
  // Create shader by galacean shader code directly
21
22
  const shader = Shader.create(galaceanShaderCode);
22
23
 
24
+ // Register project chunks before creating shaders that include them
25
+ ShaderFactory.registerInclude("ShaderLibrary/UserCommon.glsl", userCommonSource);
26
+ const shaderWithIncludes = Shader.create(galaceanShaderCodeWithIncludes);
27
+
23
28
  .......
24
29
 
25
30
  // Run engine
26
31
  engine.run()
27
32
  ```
28
33
 
29
- There are two versions of the shader compiler: `Release` and `Verbose`. The `Verbose` version offers more user-friendly diagnostic information for debugging shader compilation errors, while the Release version provides superior performance.
34
+ Authoring diagnostics are provided separately by `@galacean/engine-shader-analyzer`; the runtime compiler does not include analyzer diagnostics.
35
+
36
+ `Shader.create()` never needs a filesystem path. Include paths are resolved against logical keys registered through `ShaderFactory.registerInclude()`:
37
+
38
+ | Registered key | Current logical shader | `#include` | Result |
39
+ | --- | --- | --- | --- |
40
+ | `User/Math.glsl` | no loader metadata | `"User/Math.glsl"` or `"/User/Math.glsl"` | `User/Math.glsl` |
41
+ | `User/Math.glsl` | no loader metadata | `"./User/Math.glsl"` | `User/Math.glsl` |
42
+ | `Assets/Shaders/Math.glsl` | `Assets/Shaders/PBR.shader` | `"./Math.glsl"` | `Assets/Shaders/Math.glsl` |
43
+ | `Assets/Shared/Math.glsl` | `Assets/Shaders/PBR.shader` | `"../Shared/Math.glsl"` | `Assets/Shared/Math.glsl` |
44
+
45
+ The asset loader supplies the current logical shader location internally. Moving a shader folder remains valid when the shader asset and its registered include keys move together. Disk paths do not define the logical root.
30
46
 
31
- you can use `Verbose` version by import:
47
+ ## Precompile
32
48
 
33
- ```ts
34
- import { ShaderCompiler } from "@galacean/engine-shader-compiler/verbose";
49
+ Import `ShaderPrecompiler` from `@galacean/engine-shader-compiler/precompile` to generate `.shaderc` data in memory. The CLI writes this data to files.
50
+
51
+ Compile a directory tree to `.shaderc` artifacts:
52
+
53
+ ```sh
54
+ shader-compiler-precompile Assets/Shaders build/shaders --clean --emit-index
35
55
  ```
36
56
 
57
+ The input directory is the logical registry root. A shader at `PBR/Root.shader` resolves `./Common.glsl` to `PBR/Common.glsl`; `/Shared/Math.glsl` and `Shared/Math.glsl` both resolve to `Shared/Math.glsl` below the input root. Compilation failures exit non-zero and prevent Rollup builds using the plugin from succeeding.
58
+
59
+ Inherited helper calls use forward declarations while overriding function bodies keep their original macro context. GLES can move ordinary struct signature dependencies ahead of those declarations. If this requires moving a type with conditional fields, macro-dependent array bounds, or across macro/precision changes, code generation reports `UnsupportedForwardDeclaration` with source context and rejects the pass. This is a GLES backend limitation; authoring analysis can still accept the source, and future backends can provide their own declaration ordering.
60
+
61
+ GLES also moves replacement uniforms, struct declarations, and their enclosing conditions ahead of inherited references when their macro and precision context remains unchanged. Mutually exclusive functions that need different signatures at a shared forward-declaration site, and declarations that cross a macro or precision change, report `UnsupportedForwardDeclaration`.
62
+
63
+ When static inheritance coverage remains unknown, the instruction stream selects declarations using the actual macro state at their original source positions. The active declaration in the narrowest ShaderLab scope wins; macro directives still execute in source order. This applies to functions, uniforms, ordinary structs, and generated IO declarations. Forward declarations and moved types retain the ownership of their original declarations.
64
+
65
+ `ShaderCompiler.generate()` returns these variant-selectable instruction streams alongside unselected `vertex` and `fragment` text for inspection. The text views alone do not contain deferred ownership and must not be re-encoded as a substitute for the returned instructions. Engine `ShaderPass` consumes the instructions to produce final GLSL. `.shaderc` preserves them as JSON; use matching compiler and engine versions when shipping artifacts. Existing instruction opcodes retain their numeric values.
66
+
67
+ ## Declaration dependencies
68
+
69
+ The neutral parser records exact struct dependencies at each type occurrence, including possible macro replacement types. Macro-dependent types retain conservative semantic typing; their dependency identities do not select a runtime macro variant. GLES consumes these facts while generating reachable declarations, including helper signatures and nested struct fields, without a second declaration-tree scan.
70
+
71
+ Global emission is deduplicated by declaration identity. Same-named structs, functions, and uniforms in different macro branches remain separate candidates, retain their original guards and positions, and can discover further dependencies during generation. Backend-owned return lowering preserves control flow by emitting a block when a single return becomes an output assignment followed by an exit.
72
+
37
73
  ## CFG Grammar conflict detection
38
74
 
39
75
  The Galacean shader syntax is defined using Context-Free Grammar (CFG) and is documented within the `\*.y` file. When modifications to the shader syntax are required, it is recommended to make changes to the existing CFG syntax file, and employ [Bison](https://www.gnu.org/software/bison/manual/bison.html) to detect any potential grammar conflicts.
package/bundler/cli.js CHANGED
@@ -1,20 +1,48 @@
1
1
  #!/usr/bin/env node
2
- import { parseArgs } from 'node:util';
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import { createRequire } from 'node:module';
6
- import { pathToFileURL } from 'node:url';
2
+ 'use strict';
7
3
 
8
- /** Normalize to forward slashes (Windows uses `\`; downstream regex/keys assume `/`). */ function normalizePath(p) {
9
- return p.split(path.sep).join("/");
4
+ var node_util = require('node:util');
5
+ var fs = require('node:fs');
6
+ var path = require('node:path');
7
+ var node_module = require('node:module');
8
+
9
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
10
+
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n["default"] = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
30
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
31
+
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) {
37
+ return p.split(path__default["default"].sep).join("/");
10
38
  }
11
39
  /** Recursively find all files with a given extension. */ function findFiles(dir, ext) {
12
40
  const results = [];
13
- if (!fs.existsSync(dir)) return results;
14
- for (const entry of fs.readdirSync(dir, {
41
+ if (!fs__default["default"].existsSync(dir)) return results;
42
+ for (const entry of fs__default["default"].readdirSync(dir, {
15
43
  withFileTypes: true
16
44
  })){
17
- const fullPath = path.join(dir, entry.name);
45
+ const fullPath = path__default["default"].join(dir, entry.name);
18
46
  if (entry.isDirectory()) {
19
47
  results.push(...findFiles(fullPath, ext));
20
48
  } else if (entry.name.endsWith(ext)) {
@@ -37,13 +65,17 @@ import { pathToFileURL } from 'node:url';
37
65
  }
38
66
  /** Remove empty parent directories upward from `startDir` until `stopDir` is hit. */ function pruneEmptyDirs(startDir, stopDir) {
39
67
  let dir = startDir;
40
- while(dir !== stopDir && fs.existsSync(dir) && fs.readdirSync(dir).length === 0){
41
- fs.rmdirSync(dir);
42
- dir = path.dirname(dir);
68
+ while(dir !== stopDir && fs__default["default"].existsSync(dir) && fs__default["default"].readdirSync(dir).length === 0){
69
+ fs__default["default"].rmdirSync(dir);
70
+ dir = path__default["default"].dirname(dir);
43
71
  }
44
72
  }
45
- // One-shot mode exits non-zero on failure so CI breaks; watch mode logs and keeps running.
46
- async function precompile(options) {
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) {
47
79
  const { failed } = await runFull(options);
48
80
  if (options.watch) {
49
81
  await startWatcher(options);
@@ -51,17 +83,22 @@ async function precompile(options) {
51
83
  }
52
84
  console.log("[shader-compiler-bundler] Done.");
53
85
  if (failed > 0) {
54
- process.exit(1);
86
+ throw new Error(`${failed} shader(s) failed to precompile.`);
55
87
  }
56
88
  }
57
- async function runFull(options) {
58
- const inputDir = path.resolve(options.input);
59
- const outputDir = path.resolve(options.output);
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) {
95
+ const inputDir = path__default["default"].resolve(options.input);
96
+ const outputDir = path__default["default"].resolve(options.output);
60
97
  const platformTarget = options.platformTarget ?? 0;
61
- if (!fs.existsSync(inputDir)) {
98
+ if (!fs__default["default"].existsSync(inputDir)) {
62
99
  throw new Error(`Input directory does not exist: ${inputDir}`);
63
100
  }
64
- fs.mkdirSync(outputDir, {
101
+ fs__default["default"].mkdirSync(outputDir, {
65
102
  recursive: true
66
103
  });
67
104
  // Emit source indexes before compile so collectIncludeMap can resolve the freshly-built sources.
@@ -69,22 +106,13 @@ async function runFull(options) {
69
106
  emitSources(inputDir);
70
107
  }
71
108
  const shaderCompiler = await loadShaderCompiler();
72
- shaderCompiler._setIncludeMap(await collectIncludeMap(inputDir));
109
+ shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
73
110
  let failed = 0;
74
111
  if (options.only) {
75
- const target = path.resolve(options.only);
112
+ const target = path__default["default"].resolve(options.only);
76
113
  if (!compileSingle(shaderCompiler, target, inputDir, outputDir, platformTarget)) failed++;
77
114
  } else {
78
- const shaderFiles = findFiles(inputDir, ".shader");
79
- console.log(`[shader-compiler-bundler] Precompiling ${shaderFiles.length} shader(s)...`);
80
- for (const file of shaderFiles){
81
- if (!compileSingle(shaderCompiler, file, inputDir, outputDir, platformTarget)) failed++;
82
- }
83
- if (options.clean) {
84
- const removed = cleanOrphanedBundles(shaderFiles, inputDir, outputDir);
85
- if (removed > 0) console.log(`[shader-compiler-bundler] Cleaned ${removed} orphaned .shaderc file(s).`);
86
- }
87
- if (failed > 0) console.warn(`[shader-compiler-bundler] ${failed} shader(s) failed to precompile.`);
115
+ failed = compileAll(shaderCompiler, inputDir, outputDir, platformTarget, options.clean);
88
116
  }
89
117
  if (options.emitIndex) {
90
118
  emitIndex(outputDir);
@@ -93,26 +121,40 @@ async function runFull(options) {
93
121
  failed
94
122
  };
95
123
  }
96
- async function startWatcher(options) {
97
- const inputDir = path.resolve(options.input);
98
- const outputDir = path.resolve(options.output);
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) {
130
+ const inputDir = path__default["default"].resolve(options.input);
131
+ const outputDir = path__default["default"].resolve(options.output);
99
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
+ });
100
139
  const shaderCompiler = await loadShaderCompiler();
101
- shaderCompiler._setIncludeMap(await collectIncludeMap(inputDir));
140
+ shaderCompiler.setIncludeMap(await collectIncludeMap(inputDir));
102
141
  // .glsl change → full recompile (can't cheaply track include graphs).
103
142
  // .shader change → single-file recompile.
104
143
  const handleInputChange = async (filename)=>{
105
144
  if (!filename) return;
106
145
  const norm = normalizePath(filename);
107
- const fullPath = path.join(inputDir, filename);
146
+ const fullPath = path__default["default"].join(inputDir, filename);
108
147
  if (norm.endsWith(".glsl")) {
109
148
  console.log(`[shader-compiler-bundler] Include changed (${norm}), full recompile...`);
110
- shaderCompiler._setIncludeMap(await collectIncludeMap(inputDir));
111
- runFull(options).catch((e)=>console.error(e));
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.`);
112
154
  return;
113
155
  }
114
156
  if (!norm.endsWith(".shader")) return;
115
- if (!fs.existsSync(fullPath)) {
157
+ if (!fs__default["default"].existsSync(fullPath)) {
116
158
  removeBundleFor(fullPath, inputDir, outputDir);
117
159
  } else {
118
160
  compileSingle(shaderCompiler, fullPath, inputDir, outputDir, platformTarget);
@@ -121,69 +163,93 @@ async function startWatcher(options) {
121
163
  if (options.emitIndex) emitIndex(outputDir);
122
164
  };
123
165
  console.log(`[shader-compiler-bundler] Watching ${inputDir} ...`);
124
- fs.watch(inputDir, {
166
+ let changeQueue = Promise.resolve();
167
+ fs__default["default"].watch(inputDir, {
125
168
  recursive: true
126
169
  }, (_event, filename)=>{
127
- handleInputChange(filename).catch((e)=>console.error(e));
170
+ changeQueue = changeQueue.then(()=>handleInputChange(filename)).catch((e)=>console.error(e));
128
171
  });
129
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
+ }
130
187
  async function loadShaderCompiler() {
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');
135
- const instance = new mod.ShaderCompiler();
136
- if (typeof instance._precompile !== "function") {
137
- 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.");
138
192
  }
139
193
  return instance;
140
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
+ }
141
203
  // Local .glsl come from inputDir; standard library comes from
142
204
  // `@galacean/engine-shader/sources` with a sibling-dir fallback for engine self-build cold-start.
143
205
  async function collectIncludeMap(inputDir) {
144
- const map = {};
206
+ const map = Object.create(null);
145
207
  for (const file of findFiles(inputDir, ".glsl")){
146
- const relPath = normalizePath(path.relative(inputDir, file));
147
- map[relPath] = fs.readFileSync(file, "utf-8");
208
+ const relPath = normalizePath(path__default["default"].relative(inputDir, file));
209
+ addIncludeSource(map, relPath, fs__default["default"].readFileSync(file, "utf-8"));
148
210
  }
149
- let usedRelease = false;
150
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;
151
214
  try {
152
- const requireFromInput = createRequire(path.join(inputDir, "package.json"));
153
- const sourcesPath = requireFromInput.resolve("@galacean/engine-shader/sources");
154
- const { shaderLibrary } = await import(pathToFileURL(sourcesPath).href);
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);
155
221
  for (const { source, path: chunkPath } of shaderLibrary){
156
- map[chunkPath] = source;
222
+ addIncludeSource(map, chunkPath, source);
157
223
  }
158
- usedRelease = true;
159
- } catch {
160
- // engine self-build cold-start: dist/sources.* not yet built; use sibling source.
161
- }
162
- if (!usedRelease) {
163
- const siblingLibrary = path.join(path.dirname(inputDir), "ShaderLibrary");
164
- if (fs.existsSync(siblingLibrary)) {
224
+ } else {
225
+ // Engine self-build cold-start has no generated sources entry yet.
226
+ const siblingLibrary = path__default["default"].join(path__default["default"].dirname(inputDir), "ShaderLibrary");
227
+ if (fs__default["default"].existsSync(siblingLibrary)) {
165
228
  for (const file of findFiles(siblingLibrary, ".glsl")){
166
- const rel = normalizePath(path.relative(siblingLibrary, file));
167
- map[`ShaderLibrary/${rel}`] = fs.readFileSync(file, "utf-8");
229
+ const rel = normalizePath(path__default["default"].relative(siblingLibrary, file));
230
+ addIncludeSource(map, `ShaderLibrary/${rel}`, fs__default["default"].readFileSync(file, "utf-8"));
168
231
  }
169
232
  }
170
233
  }
171
234
  return map;
172
235
  }
173
- // Mirrors `ShaderPass._shaderRootPath` in engine; inlined to keep bundler engine-free.
174
- const SHADER_ROOT_PATH = "shaders://root/";
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
+ }
175
242
  function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platformTarget) {
176
- const source = fs.readFileSync(shaderPath, "utf-8");
177
- const relativePath = normalizePath(path.relative(inputDir, shaderPath));
243
+ const relativePath = shaderSourceRelativePath(shaderPath, inputDir);
244
+ const source = fs__default["default"].readFileSync(shaderPath, "utf-8");
178
245
  const bundleRelative = relativePath.replace(/\.shader$/, ".shaderc");
179
- const bundlePath = path.join(outputDir, bundleRelative);
180
- const basePathForIncludeKey = new URL(relativePath, SHADER_ROOT_PATH).href;
181
- fs.mkdirSync(path.dirname(bundlePath), {
246
+ const bundlePath = path__default["default"].join(outputDir, bundleRelative);
247
+ fs__default["default"].mkdirSync(path__default["default"].dirname(bundlePath), {
182
248
  recursive: true
183
249
  });
184
250
  try {
185
- const precompiled = shaderCompiler._precompile(source, platformTarget, basePathForIncludeKey);
186
- fs.writeFileSync(bundlePath, JSON.stringify(precompiled));
251
+ const precompiled = shaderCompiler.precompile(source, platformTarget, relativePath);
252
+ fs__default["default"].writeFileSync(bundlePath, JSON.stringify(precompiled));
187
253
  console.log(` ${relativePath} -> ${bundleRelative}`);
188
254
  return true;
189
255
  } catch (e) {
@@ -192,17 +258,24 @@ function compileSingle(shaderCompiler, shaderPath, inputDir, outputDir, platform
192
258
  return false;
193
259
  }
194
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
+ }
195
268
  function cleanOrphanedBundles(shaderFiles, inputDir, outputDir) {
196
- const aliveSet = new Set(shaderFiles.map((f)=>normalizePath(path.relative(inputDir, f)).replace(/\.shader$/, "")));
269
+ const aliveSet = new Set(shaderFiles.map((f)=>normalizePath(path__default["default"].relative(inputDir, f)).replace(/\.shader$/, "")));
197
270
  const bundleFiles = findFiles(outputDir, ".shaderc");
198
271
  let removed = 0;
199
272
  for (const bundleFile of bundleFiles){
200
- const rel = normalizePath(path.relative(outputDir, bundleFile)).replace(/\.shaderc$/, "");
273
+ const rel = normalizePath(path__default["default"].relative(outputDir, bundleFile)).replace(/\.shaderc$/, "");
201
274
  if (!aliveSet.has(rel)) {
202
- fs.unlinkSync(bundleFile);
275
+ fs__default["default"].unlinkSync(bundleFile);
203
276
  console.log(` Removed orphaned: ${rel}.shaderc`);
204
277
  removed++;
205
- pruneEmptyDirs(path.dirname(bundleFile), outputDir);
278
+ pruneEmptyDirs(path__default["default"].dirname(bundleFile), outputDir);
206
279
  }
207
280
  }
208
281
  return removed;
@@ -210,19 +283,23 @@ function cleanOrphanedBundles(shaderFiles, inputDir, outputDir) {
210
283
  function emitIndex(outputDir) {
211
284
  const bundleFiles = findFiles(outputDir, ".shaderc");
212
285
  const entries = bundleFiles.map((file)=>{
213
- const rel = normalizePath(path.relative(outputDir, file));
286
+ const rel = normalizePath(path__default["default"].relative(outputDir, file));
214
287
  return {
215
288
  varName: shadercPathToVarName(rel),
216
289
  importPath: `./${rel}`
217
290
  };
218
291
  }).sort((a, b)=>a.varName.localeCompare(b.varName));
292
+ assertUniqueIdentifiers(entries.map(({ varName, importPath })=>({
293
+ identifier: varName,
294
+ path: importPath
295
+ })));
219
296
  const imports = entries.map((e)=>`import ${e.varName}Data from "${e.importPath}";`).join("\n");
220
297
  const exports = entries.map((e)=>`export const ${e.varName}: IPrecompiledShader = ${e.varName}Data;`).join("\n");
221
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`;
222
- const indexPath = path.join(outputDir, "index.ts");
223
- const existing = fs.existsSync(indexPath) ? fs.readFileSync(indexPath, "utf-8") : "";
299
+ const indexPath = path__default["default"].join(outputDir, "index.ts");
300
+ const existing = fs__default["default"].existsSync(indexPath) ? fs__default["default"].readFileSync(indexPath, "utf-8") : "";
224
301
  if (content !== existing) {
225
- fs.writeFileSync(indexPath, content);
302
+ fs__default["default"].writeFileSync(indexPath, content);
226
303
  console.log(" Updated index.ts");
227
304
  }
228
305
  }
@@ -231,17 +308,21 @@ function emitIndex(outputDir) {
231
308
  // <sibling>/ShaderLibrary/index.ts — `.glsl` array (only if the directory exists)
232
309
  function emitSources(inputDir) {
233
310
  emitShaderSourceIndex(inputDir);
234
- const libraryDir = path.join(path.dirname(inputDir), "ShaderLibrary");
235
- if (fs.existsSync(libraryDir)) {
311
+ const libraryDir = path__default["default"].join(path__default["default"].dirname(inputDir), "ShaderLibrary");
312
+ if (fs__default["default"].existsSync(libraryDir)) {
236
313
  emitLibrarySourceIndex(libraryDir);
237
314
  }
238
315
  }
239
316
  function emitShaderSourceIndex(rootDir) {
240
- const srcDir = path.dirname(rootDir);
317
+ const srcDir = path__default["default"].dirname(rootDir);
241
318
  const files = findFiles(rootDir, ".shader").sort();
242
- const importPaths = files.map((f)=>normalizePath(path.relative(rootDir, f)));
243
- const paths = files.map((f)=>normalizePath(path.relative(srcDir, f)));
319
+ const importPaths = files.map((f)=>normalizePath(path__default["default"].relative(rootDir, f)));
320
+ const paths = files.map((f)=>normalizePath(path__default["default"].relative(srcDir, f)));
244
321
  const ids = importPaths.map((p)=>pathToIdentifier(p, ".shader"));
322
+ assertUniqueIdentifiers(ids.map((identifier, index)=>({
323
+ identifier,
324
+ path: importPaths[index]
325
+ })));
245
326
  const lines = [];
246
327
  lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
247
328
  lines.push("");
@@ -257,16 +338,20 @@ function emitShaderSourceIndex(rootDir) {
257
338
  paths.forEach((p, i)=>lines.push(` { source: ${ids[i]}, path: ${JSON.stringify(p)} },`));
258
339
  lines.push("];");
259
340
  lines.push("");
260
- const out = path.join(rootDir, "index.ts");
261
- fs.writeFileSync(out, lines.join("\n"), "utf-8");
262
- console.log(` Emitted shaders index ${path.relative(process.cwd(), out)} (${files.length})`);
341
+ const out = path__default["default"].join(rootDir, "index.ts");
342
+ fs__default["default"].writeFileSync(out, lines.join("\n"), "utf-8");
343
+ console.log(` Emitted shaders index ${path__default["default"].relative(process.cwd(), out)} (${files.length})`);
263
344
  }
264
345
  function emitLibrarySourceIndex(rootDir) {
265
- const srcDir = path.dirname(rootDir);
346
+ const srcDir = path__default["default"].dirname(rootDir);
266
347
  const files = findFiles(rootDir, ".glsl").sort();
267
- const importPaths = files.map((f)=>normalizePath(path.relative(rootDir, f)));
268
- const paths = files.map((f)=>normalizePath(path.relative(srcDir, f)));
348
+ const importPaths = files.map((f)=>normalizePath(path__default["default"].relative(rootDir, f)));
349
+ const paths = files.map((f)=>normalizePath(path__default["default"].relative(srcDir, f)));
269
350
  const ids = importPaths.map((p)=>pathToIdentifier(p, ".glsl"));
351
+ assertUniqueIdentifiers(ids.map((identifier, index)=>({
352
+ identifier,
353
+ path: importPaths[index]
354
+ })));
270
355
  const lines = [];
271
356
  lines.push("// Auto-generated by shader-compiler-precompile --emit-sources — do not edit.");
272
357
  lines.push("");
@@ -278,17 +363,27 @@ function emitLibrarySourceIndex(rootDir) {
278
363
  paths.forEach((p, i)=>lines.push(` { source: ${ids[i]}, path: ${JSON.stringify(p)} },`));
279
364
  lines.push("];");
280
365
  lines.push("");
281
- const out = path.join(rootDir, "index.ts");
282
- fs.writeFileSync(out, lines.join("\n"), "utf-8");
283
- console.log(` Emitted library index ${path.relative(process.cwd(), out)} (${files.length})`);
366
+ const out = path__default["default"].join(rootDir, "index.ts");
367
+ fs__default["default"].writeFileSync(out, lines.join("\n"), "utf-8");
368
+ console.log(` Emitted library index ${path__default["default"].relative(process.cwd(), out)} (${files.length})`);
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
+ }
284
379
  }
285
380
  function removeBundleFor(shaderPath, inputDir, outputDir) {
286
- const rel = normalizePath(path.relative(inputDir, shaderPath)).replace(/\.shader$/, ".shaderc");
287
- const bundlePath = path.join(outputDir, rel);
288
- if (fs.existsSync(bundlePath)) {
289
- fs.unlinkSync(bundlePath);
381
+ const rel = normalizePath(path__default["default"].relative(inputDir, shaderPath)).replace(/\.shader$/, ".shaderc");
382
+ const bundlePath = path__default["default"].join(outputDir, rel);
383
+ if (fs__default["default"].existsSync(bundlePath)) {
384
+ fs__default["default"].unlinkSync(bundlePath);
290
385
  console.log(` Removed: ${rel}`);
291
- pruneEmptyDirs(path.dirname(bundlePath), outputDir);
386
+ pruneEmptyDirs(path__default["default"].dirname(bundlePath), outputDir);
292
387
  }
293
388
  }
294
389
 
@@ -299,7 +394,7 @@ function removeBundleFor(shaderPath, inputDir, outputDir) {
299
394
  * shader-compiler-precompile <input-dir> <output-dir>
300
395
  * [--clean] [--watch] [--only <file>] [--emit-index] [--emit-sources]
301
396
  */ async function main() {
302
- const { positionals, values } = parseArgs({
397
+ const { positionals, values } = node_util.parseArgs({
303
398
  allowPositionals: true,
304
399
  options: {
305
400
  clean: {