xbintsc 0.3.46 → 0.3.49

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/AGENTS.md +95 -0
  2. package/README.md +25 -0
  3. package/README.zh-CN.md +23 -0
  4. package/dist/src/cli/hints.d.ts +54 -0
  5. package/dist/src/cli/hints.js +165 -0
  6. package/dist/src/cli/hints.js.map +1 -0
  7. package/dist/src/cli/main.js +73 -9
  8. package/dist/src/cli/main.js.map +1 -1
  9. package/dist/src/codegen/generator/tables.d.ts +26 -0
  10. package/dist/src/codegen/generator/tables.js +64 -12
  11. package/dist/src/codegen/generator/tables.js.map +1 -1
  12. package/dist/src/diagnostics/source-text.d.ts +22 -0
  13. package/dist/src/diagnostics/source-text.js +76 -0
  14. package/dist/src/diagnostics/source-text.js.map +1 -0
  15. package/dist/src/driver/bundler/graph.js +2 -1
  16. package/dist/src/driver/bundler/graph.js.map +1 -1
  17. package/dist/src/driver/compiler.js +3 -2
  18. package/dist/src/driver/compiler.js.map +1 -1
  19. package/dist/src/lexer/scanner/strings.js +16 -3
  20. package/dist/src/lexer/scanner/strings.js.map +1 -1
  21. package/dist/tests/cli/hints.test.d.ts +9 -0
  22. package/dist/tests/cli/hints.test.js +143 -0
  23. package/dist/tests/cli/hints.test.js.map +1 -0
  24. package/dist/tests/cli/main.test.js +6 -4
  25. package/dist/tests/cli/main.test.js.map +1 -1
  26. package/dist/tests/codegen/llvm.test.js +17 -2
  27. package/dist/tests/codegen/llvm.test.js.map +1 -1
  28. package/dist/tests/helpers.js +3 -2
  29. package/dist/tests/helpers.js.map +1 -1
  30. package/dist/tests/lexer/strings.test.js +14 -2
  31. package/dist/tests/lexer/strings.test.js.map +1 -1
  32. package/doc/DESIGN.md +117 -0
  33. package/doc/ai/README.md +63 -0
  34. package/doc/ai/build-recipe.md +137 -0
  35. package/doc/ai/cli.md +142 -0
  36. package/doc/ai/contributing.md +196 -0
  37. package/doc/ai/extensions.md +148 -0
  38. package/doc/ai/language-support.md +152 -0
  39. package/doc/ai/troubleshooting.md +163 -0
  40. package/doc/ai/zh-CN/README.md +56 -0
  41. package/doc/ai/zh-CN/build-recipe.md +132 -0
  42. package/doc/ai/zh-CN/cli.md +127 -0
  43. package/doc/ai/zh-CN/contributing.md +173 -0
  44. package/doc/ai/zh-CN/extensions.md +139 -0
  45. package/doc/ai/zh-CN/language-support.md +147 -0
  46. package/doc/ai/zh-CN/troubleshooting.md +150 -0
  47. package/doc/gui-scripts.md +350 -0
  48. package/doc/gui.md +646 -0
  49. package/doc/icon.md +265 -0
  50. package/doc/implemented.md +373 -0
  51. package/doc/node-implemented.md +588 -0
  52. package/doc/node-unimplemented.md +167 -0
  53. package/doc/post/announce.md +43 -0
  54. package/doc/requirements.md +145 -0
  55. package/doc/unimplemented.md +286 -0
  56. package/doc/xbintsc.config.schema.json +67 -0
  57. package/doc/zh-CN/DESIGN.md +104 -0
  58. package/doc/zh-CN/gui-scripts.md +329 -0
  59. package/doc/zh-CN/gui.md +588 -0
  60. package/doc/zh-CN/icon.md +241 -0
  61. package/doc/zh-CN/implemented.md +365 -0
  62. package/doc/zh-CN/node-implemented.md +533 -0
  63. package/doc/zh-CN/node-unimplemented.md +141 -0
  64. package/doc/zh-CN/plan-require-node-modules.md +284 -0
  65. package/doc/zh-CN/post/announce.md +47 -0
  66. package/doc/zh-CN/requirements.md +134 -0
  67. package/doc/zh-CN/unimplemented.md +247 -0
  68. package/llms.txt +45 -0
  69. package/package.json +4 -1
  70. package/runtime/ext_gui/gui.cpp +3 -1
  71. package/runtime/ext_gui/renderer.cpp +13 -11
  72. package/runtime/ext_gui/renderer_image.cpp +12 -8
  73. package/runtime/ext_gui/renderer_shaders.h +131 -4
  74. package/runtime/ext_gui/renderer_shaders_data.h +1809 -0
  75. package/runtime/ext_gui/renderer_text.cpp +12 -8
  76. package/runtime/ext_gui/shaders.hlsl +98 -0
  77. package/runtime/ext_gui/spirv/fill.frag +19 -0
  78. package/runtime/ext_gui/spirv/fill.vert +42 -0
  79. package/runtime/ext_gui/spirv/image.frag +16 -0
  80. package/runtime/ext_gui/spirv/quad.vert +30 -0
  81. package/runtime/ext_gui/spirv/text.frag +16 -0
  82. package/scripts/build-gui-shaders.mjs +204 -0
  83. package/scripts/build-gui.ts +35 -0
  84. package/scripts/check-file-length.ts +5 -1
  85. package/src/cli/hints.ts +194 -0
  86. package/src/cli/main.ts +82 -9
  87. package/src/codegen/generator/tables.ts +60 -14
  88. package/src/diagnostics/source-text.ts +78 -0
  89. package/src/driver/bundler/graph.ts +2 -1
  90. package/src/driver/compiler.ts +3 -2
  91. package/src/lexer/scanner/strings.ts +16 -3
@@ -187,22 +187,26 @@ void flushAtlas(SDL_GPUDevice *device) {
187
187
  }
188
188
 
189
189
  SDL_GPUGraphicsPipeline *createTextPipeline(SDL_GPUDevice *device, SDL_Window *reference) {
190
+ const ShaderSource text_vs = selectShader(device, ShaderProgram::Text, false);
191
+ const ShaderSource text_fs = selectShader(device, ShaderProgram::Text, true);
192
+ if (!text_vs.valid() || !text_fs.valid()) return nullptr;
193
+
190
194
  SDL_GPUShaderCreateInfo vs_info;
191
195
  memset(&vs_info, 0, sizeof(vs_info));
192
- vs_info.code_size = strlen(kTextMSL);
193
- vs_info.code = (const Uint8 *)kTextMSL;
194
- vs_info.entrypoint = "vs_text";
195
- vs_info.format = SDL_GPU_SHADERFORMAT_MSL;
196
+ vs_info.code_size = text_vs.size;
197
+ vs_info.code = text_vs.code;
198
+ vs_info.entrypoint = text_vs.entry;
199
+ vs_info.format = text_vs.format;
196
200
  vs_info.stage = SDL_GPU_SHADERSTAGE_VERTEX;
197
201
  vs_info.num_uniform_buffers = 1;
198
202
  SDL_GPUShader *vs = SDL_CreateGPUShader(device, &vs_info);
199
203
 
200
204
  SDL_GPUShaderCreateInfo fs_info;
201
205
  memset(&fs_info, 0, sizeof(fs_info));
202
- fs_info.code_size = strlen(kTextMSL);
203
- fs_info.code = (const Uint8 *)kTextMSL;
204
- fs_info.entrypoint = "fs_text";
205
- fs_info.format = SDL_GPU_SHADERFORMAT_MSL;
206
+ fs_info.code_size = text_fs.size;
207
+ fs_info.code = text_fs.code;
208
+ fs_info.entrypoint = text_fs.entry;
209
+ fs_info.format = text_fs.format;
206
210
  fs_info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
207
211
  fs_info.num_samplers = 1;
208
212
  SDL_GPUShader *fs = SDL_CreateGPUShader(device, &fs_info);
@@ -0,0 +1,98 @@
1
+ /*
2
+ * xbintsc GUI engine — shader sources compiled ahead of time into the embedded
3
+ * blobs in `renderer_blobs.h`.
4
+ *
5
+ * Three programs, two entry points each:
6
+ * - `vs_main` / `fs_main` rounded-rectangle fills
7
+ * - `vs_text` / `fs_text` glyph quads (single-channel atlas)
8
+ * - `vs_text` / `fs_image` image quads (RGBA texture)
9
+ *
10
+ * `vs_main` and `vs_text` are identical in effect; they are kept separate so the
11
+ * Metal (MSL) path keeps its distinct entry-point names, and this file is the
12
+ * HLSL half of the pair. Vertex attribute semantics map 1:1 onto
13
+ * `SDL_GPUVertexAttribute.location`, and the push constant becomes `b0`.
14
+ *
15
+ * Compile with (Windows SDK `dxc`):
16
+ * dxc -T vs_6_0 -E vs_main -Fo fill_vs.dxil shaders.hlsl
17
+ * dxc -T ps_6_0 -E fs_main -Fo fill_fs.dxil shaders.hlsl
18
+ * dxc -T vs_6_0 -E vs_text -Fo text_vs.dxil shaders.hlsl
19
+ * dxc -T ps_6_0 -E fs_text -Fo text_fs.dxil shaders.hlsl
20
+ * dxc -T ps_6_0 -E fs_image -Fo image_fs.dxil shaders.hlsl
21
+ */
22
+
23
+ cbuffer Viewport : register(b0) { float2 viewport; };
24
+
25
+ /* -- fills ---------------------------------------------------------------- */
26
+
27
+ struct FillIn {
28
+ float2 position : TEXCOORD0;
29
+ float2 local : TEXCOORD1;
30
+ float2 halfsize : TEXCOORD2;
31
+ float radius : TEXCOORD3;
32
+ float4 color : TEXCOORD4;
33
+ };
34
+
35
+ struct FillOut {
36
+ float4 position : SV_Position;
37
+ float2 local : TEXCOORD1;
38
+ float2 halfsize : TEXCOORD2;
39
+ float radius : TEXCOORD3;
40
+ float4 color : TEXCOORD4;
41
+ };
42
+
43
+ FillOut vs_main(FillIn input) {
44
+ FillOut output;
45
+ float2 ndc = float2(input.position.x / viewport.x * 2.0 - 1.0,
46
+ 1.0 - input.position.y / viewport.y * 2.0);
47
+ output.position = float4(ndc, 0.0, 1.0);
48
+ output.local = input.local;
49
+ output.halfsize = input.halfsize;
50
+ output.radius = input.radius;
51
+ output.color = input.color;
52
+ return output;
53
+ }
54
+
55
+ float4 fs_main(FillOut input) : SV_Target {
56
+ float2 d = abs(input.local) - input.halfsize + input.radius;
57
+ float dist = min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - input.radius;
58
+ float aa = max(fwidth(dist), 0.0001);
59
+ float alpha = clamp(0.5 - dist / aa, 0.0, 1.0);
60
+ return float4(input.color.rgb, input.color.a * alpha);
61
+ }
62
+
63
+ /* -- text and images ------------------------------------------------------ */
64
+
65
+ struct TextIn {
66
+ float2 position : TEXCOORD0;
67
+ float2 uv : TEXCOORD1;
68
+ float4 color : TEXCOORD2;
69
+ };
70
+
71
+ struct TextOut {
72
+ float4 position : SV_Position;
73
+ float2 uv : TEXCOORD1;
74
+ float4 color : TEXCOORD2;
75
+ };
76
+
77
+ Texture2D atlas : register(t0);
78
+ SamplerState samp : register(s0);
79
+
80
+ TextOut vs_text(TextIn input) {
81
+ TextOut output;
82
+ float2 ndc = float2(input.position.x / viewport.x * 2.0 - 1.0,
83
+ 1.0 - input.position.y / viewport.y * 2.0);
84
+ output.position = float4(ndc, 0.0, 1.0);
85
+ output.uv = input.uv;
86
+ output.color = input.color;
87
+ return output;
88
+ }
89
+
90
+ float4 fs_text(TextOut input) : SV_Target {
91
+ float coverage = atlas.Sample(samp, input.uv).r;
92
+ return float4(input.color.rgb, input.color.a * coverage);
93
+ }
94
+
95
+ float4 fs_image(TextOut input) : SV_Target {
96
+ float4 c = atlas.Sample(samp, input.uv);
97
+ return float4(c.rgb * input.color.rgb, c.a * input.color.a);
98
+ }
@@ -0,0 +1,19 @@
1
+ #version 450
2
+
3
+ /* xbintsc GUI engine — rounded-rectangle fill fragment shader (Vulkan/SPIR-V).
4
+ * The distance field mirrors the MSL/DXIL fill shaders. */
5
+
6
+ layout(location = 0) in vec2 v_local;
7
+ layout(location = 1) in vec2 v_halfsize;
8
+ layout(location = 2) in float v_radius;
9
+ layout(location = 3) in vec4 v_color;
10
+
11
+ layout(location = 0) out vec4 out_color;
12
+
13
+ void main() {
14
+ vec2 d = abs(v_local) - v_halfsize + v_radius;
15
+ float dist = min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - v_radius;
16
+ float aa = max(fwidth(dist), 0.0001);
17
+ float alpha = clamp(0.5 - dist / aa, 0.0, 1.0);
18
+ out_color = vec4(v_color.rgb, v_color.a * alpha);
19
+ }
@@ -0,0 +1,42 @@
1
+ #version 450
2
+
3
+ /* xbintsc GUI engine — rounded-rectangle fill, Vulkan/SPIR-V path.
4
+ *
5
+ * The descriptor layout matches what SDL_GPU's Vulkan backend builds
6
+ * (`SDL_gpu_vulkan.c`, `CreateDescriptorSetLayout`): uniforms at
7
+ * `set=1, binding=0`, and (in the text/image shaders) samplers at
8
+ * `set=2, binding=0` with textures at `set=2, binding=1`.
9
+ *
10
+ * Vertex attributes (matching SDL_GPUVertexAttribute.location):
11
+ * 0 -> position (pixels, viewport-relative)
12
+ * 1 -> local (pixels, relative to the rectangle centre)
13
+ * 2 -> halfsize (rectangle half extents, pixels)
14
+ * 3 -> radius (pixels)
15
+ * 4 -> colour
16
+ * The push constant is the viewport size in logical pixels.
17
+ */
18
+
19
+ layout(location = 0) in vec2 in_position;
20
+ layout(location = 1) in vec2 in_local;
21
+ layout(location = 2) in vec2 in_halfsize;
22
+ layout(location = 3) in float in_radius;
23
+ layout(location = 4) in vec4 in_color;
24
+
25
+ layout(location = 0) out vec2 v_local;
26
+ layout(location = 1) out vec2 v_halfsize;
27
+ layout(location = 2) out float v_radius;
28
+ layout(location = 3) out vec4 v_color;
29
+
30
+ layout(set = 1, binding = 0) uniform Viewport {
31
+ vec2 size;
32
+ } viewport;
33
+
34
+ void main() {
35
+ vec2 ndc = vec2(in_position.x / viewport.size.x * 2.0 - 1.0,
36
+ 1.0 - in_position.y / viewport.size.y * 2.0);
37
+ gl_Position = vec4(ndc, 0.0, 1.0);
38
+ v_local = in_local;
39
+ v_halfsize = in_halfsize;
40
+ v_radius = in_radius;
41
+ v_color = in_color;
42
+ }
@@ -0,0 +1,16 @@
1
+ #version 450
2
+
3
+ /* xbintsc GUI engine — image quads (Vulkan/SPIR-V): sample an RGBA texture and
4
+ * modulate it by the vertex colour. */
5
+
6
+ layout(location = 0) in vec2 v_uv;
7
+ layout(location = 1) in vec4 v_color;
8
+
9
+ layout(location = 0) out vec4 out_color;
10
+
11
+ layout(set = 2, binding = 0) uniform sampler2D image;
12
+
13
+ void main() {
14
+ vec4 c = texture(image, v_uv);
15
+ out_color = vec4(c.rgb * v_color.rgb, c.a * v_color.a);
16
+ }
@@ -0,0 +1,30 @@
1
+ #version 450
2
+
3
+ /* xbintsc GUI engine — glyph and image quads, Vulkan/SPIR-V path.
4
+ *
5
+ * The vertex stage is shared by both: the fragment stage differs only in how it
6
+ * uses the sampled texture (`fs_text` reads the atlas' red channel, `fs_image`
7
+ * modulates an RGBA texture). Attributes:
8
+ * 0 -> position (pixels, viewport-relative)
9
+ * 1 -> uv (atlas coordinates in [0,1])
10
+ * 2 -> colour
11
+ */
12
+
13
+ layout(location = 0) in vec2 in_position;
14
+ layout(location = 1) in vec2 in_uv;
15
+ layout(location = 2) in vec4 in_color;
16
+
17
+ layout(location = 0) out vec2 v_uv;
18
+ layout(location = 1) out vec4 v_color;
19
+
20
+ layout(set = 1, binding = 0) uniform Viewport {
21
+ vec2 size;
22
+ } viewport;
23
+
24
+ void main() {
25
+ vec2 ndc = vec2(in_position.x / viewport.size.x * 2.0 - 1.0,
26
+ 1.0 - in_position.y / viewport.size.y * 2.0);
27
+ gl_Position = vec4(ndc, 0.0, 1.0);
28
+ v_uv = in_uv;
29
+ v_color = in_color;
30
+ }
@@ -0,0 +1,16 @@
1
+ #version 450
2
+
3
+ /* xbintsc GUI engine — glyph quads (Vulkan/SPIR-V): sample the single-channel
4
+ * atlas and modulate alpha by its coverage. */
5
+
6
+ layout(location = 0) in vec2 v_uv;
7
+ layout(location = 1) in vec4 v_color;
8
+
9
+ layout(location = 0) out vec4 out_color;
10
+
11
+ layout(set = 2, binding = 0) uniform sampler2D atlas;
12
+
13
+ void main() {
14
+ float coverage = texture(atlas, v_uv).r;
15
+ out_color = vec4(v_color.rgb, v_color.a * coverage);
16
+ }
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Compile the GUI engine's shaders ahead of time and embed them in
4
+ * `runtime/ext_gui/renderer_shaders_data.h`.
5
+ *
6
+ * SDL_GPU cannot compile GLSL or HLSL at runtime: each backend consumes its own
7
+ * binary format. The engine therefore ships:
8
+ *
9
+ * - SPIR-V, for Vulkan, compiled from `runtime/ext_gui/spirv/*.{vert,frag}`
10
+ * with `glslc` (from the Vulkan SDK); and
11
+ * - DXIL, for Direct3D 12, compiled from `runtime/ext_gui/shaders.hlsl` with
12
+ * `dxc` (inside the Windows SDK).
13
+ *
14
+ * Metal needs no blob: SDL compiles the MSL literals in `renderer_shaders.h` at
15
+ * runtime.
16
+ *
17
+ * The generated header is committed so a machine without either compiler (Linux,
18
+ * macOS CI) still builds the engine; only the corresponding backend is
19
+ * unavailable there.
20
+ *
21
+ * Environment:
22
+ * xbintsc_GLSLC explicit `glslc` path (default: newest `C:\VulkanSDK\*`)
23
+ * xbintsc_DXC explicit `dxc` path (default: newest Windows SDK)
24
+ *
25
+ * Usage:
26
+ * node scripts/build-gui-shaders.mjs # regenerate the header
27
+ * node scripts/build-gui-shaders.mjs --check # fail if it is out of date
28
+ */
29
+
30
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
31
+ import { spawnSync } from "node:child_process";
32
+ import { dirname, join, resolve } from "node:path";
33
+ import { fileURLToPath } from "node:url";
34
+
35
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
36
+ const engineDir = join(root, "runtime", "ext_gui");
37
+ const glslDir = join(engineDir, "spirv");
38
+ const hlsl = join(engineDir, "shaders.hlsl");
39
+ const header = join(engineDir, "renderer_shaders_data.h");
40
+ const scratch = join(root, "build", "gui-shaders");
41
+
42
+ /** SPIR-V programs: GLSL source file and the symbol to emit. */
43
+ const SPIRV_PROGRAMS = [
44
+ { source: "fill.vert", symbol: "kFillSpirvVs" },
45
+ { source: "fill.frag", symbol: "kFillSpirvFs" },
46
+ { source: "quad.vert", symbol: "kTextSpirvVs" },
47
+ { source: "text.frag", symbol: "kTextSpirvFs" },
48
+ { source: "image.frag", symbol: "kImageSpirvFs" },
49
+ ];
50
+
51
+ /** DXIL programs: HLSL entry point, stage and the symbol to emit. */
52
+ const DXIL_PROGRAMS = [
53
+ { target: "vs_6_0", entry: "vs_main", symbol: "kFillDxilVs" },
54
+ { target: "ps_6_0", entry: "fs_main", symbol: "kFillDxilFs" },
55
+ { target: "vs_6_0", entry: "vs_text", symbol: "kTextDxilVs" },
56
+ { target: "ps_6_0", entry: "fs_text", symbol: "kTextDxilFs" },
57
+ { target: "ps_6_0", entry: "fs_image", symbol: "kImageDxilFs" },
58
+ ];
59
+
60
+ function fail(message) {
61
+ console.error(`xbintsc: ${message}`);
62
+ process.exit(1);
63
+ }
64
+
65
+ /** Newest version-suffixed directory under `base`, or undefined. */
66
+ function newestVersionDir(base, isVersion) {
67
+ if (!existsSync(base)) return undefined;
68
+ const versions = readdirSync(base)
69
+ .filter(isVersion)
70
+ .sort((a, b) => {
71
+ const pa = a.split(".").map(Number);
72
+ const pb = b.split(".").map(Number);
73
+ for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
74
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
75
+ if (diff !== 0) return diff;
76
+ }
77
+ return 0;
78
+ })
79
+ .reverse();
80
+ return { base, versions };
81
+ }
82
+
83
+ /** Locate `glslc`: the explicit override, then the newest installed Vulkan SDK. */
84
+ function findGlslc() {
85
+ if (process.env.xbintsc_GLSLC) return process.env.xbintsc_GLSLC;
86
+ const found = newestVersionDir("C:\\VulkanSDK", (name) => /^\d+\.\d+\.\d+\.\d+$/.test(name));
87
+ if (!found) return undefined;
88
+ for (const version of found.versions) {
89
+ const bin = join(found.base, version, "Bin");
90
+ for (const name of ["glslc.exe", "glslc"]) {
91
+ if (existsSync(join(bin, name))) return join(bin, name);
92
+ }
93
+ }
94
+ return undefined;
95
+ }
96
+
97
+ /** Locate `dxc`: the explicit override, then the newest Windows SDK. */
98
+ function findDxc() {
99
+ if (process.env.xbintsc_DXC) return process.env.xbintsc_DXC;
100
+ const found = newestVersionDir("C:\\Program Files (x86)\\Windows Kits\\10\\bin", (name) =>
101
+ /^\d+\.\d+\.\d+\.\d+$/.test(name),
102
+ );
103
+ if (!found) return undefined;
104
+ for (const version of found.versions) {
105
+ for (const arch of ["x64", "arm64"]) {
106
+ const candidate = join(found.base, version, arch, "dxc.exe");
107
+ if (existsSync(candidate)) return candidate;
108
+ }
109
+ }
110
+ return undefined;
111
+ }
112
+
113
+ /** Compile `args` with `tool`, failing with its output on a non-zero exit. */
114
+ function compile(tool, args, label) {
115
+ const result = spawnSync(tool, args, { encoding: "utf8" });
116
+ if (result.status !== 0) {
117
+ fail(`${label} failed:\n${result.stderr ?? ""}${result.stdout ?? ""}`);
118
+ }
119
+ }
120
+
121
+ /** Render one blob as a C++ array initializer, 16 bytes per line. */
122
+ function renderArray(symbol, bytes) {
123
+ const lines = [];
124
+ for (let i = 0; i < bytes.length; i += 16) {
125
+ lines.push(" " + Array.from(bytes.subarray(i, i + 16)).join(", ") + ",");
126
+ }
127
+ return [
128
+ `inline constexpr unsigned char ${symbol}[] = {`,
129
+ ...lines,
130
+ "};",
131
+ `inline constexpr size_t ${symbol}Size = sizeof(${symbol});`,
132
+ ].join("\n");
133
+ }
134
+
135
+ mkdirSync(scratch, { recursive: true });
136
+
137
+ const blobs = [];
138
+
139
+ const glslc = findGlslc();
140
+ if (glslc === undefined) {
141
+ fail(
142
+ "no glslc found; install the Vulkan SDK (https://vulkan.lunarg.com) or set " +
143
+ `xbintsc_GLSLC.\nThe committed ${header} was not regenerated.`,
144
+ );
145
+ }
146
+ for (const program of SPIRV_PROGRAMS) {
147
+ const output = join(scratch, `${program.symbol}.spv`);
148
+ compile(
149
+ glslc,
150
+ ["-c", join(glslDir, program.source), "-o", output],
151
+ `glslc ${program.source}`,
152
+ );
153
+ blobs.push({ symbol: program.symbol, bytes: readFileSync(output) });
154
+ }
155
+
156
+ const dxc = findDxc();
157
+ if (dxc === undefined) {
158
+ fail(
159
+ "no dxc found; install the Windows SDK or set xbintsc_DXC.\n" +
160
+ `The committed ${header} was not regenerated.`,
161
+ );
162
+ }
163
+ for (const program of DXIL_PROGRAMS) {
164
+ const output = join(scratch, `${program.symbol}.dxil`);
165
+ compile(
166
+ dxc,
167
+ ["-T", program.target, "-E", program.entry, "-Fo", output, hlsl],
168
+ `dxc ${program.target} ${program.entry}`,
169
+ );
170
+ blobs.push({ symbol: program.symbol, bytes: readFileSync(output) });
171
+ }
172
+
173
+ rmSync(scratch, { recursive: true, force: true });
174
+
175
+ const generated = [
176
+ "/*",
177
+ " * Generated by `scripts/build-gui-shaders.mjs` — do not edit.",
178
+ " *",
179
+ " * SPIR-V blobs (Vulkan) compiled from `runtime/ext_gui/spirv/` with glslc, and",
180
+ " * DXIL blobs (Direct3D 12) compiled from `runtime/ext_gui/shaders.hlsl` with dxc.",
181
+ " * The Metal path uses the MSL literals in `renderer_shaders.h` directly.",
182
+ " */",
183
+ "#ifndef XT_GUI_RENDERER_SHADERS_DATA_H",
184
+ "#define XT_GUI_RENDERER_SHADERS_DATA_H",
185
+ "",
186
+ "#include <stddef.h>",
187
+ "",
188
+ ...blobs.map((blob) => renderArray(blob.symbol, blob.bytes)),
189
+ "",
190
+ "#endif /* XT_GUI_RENDERER_SHADERS_DATA_H */",
191
+ "",
192
+ ].join("\n");
193
+
194
+ if (process.argv.includes("--check")) {
195
+ const current = existsSync(header) ? readFileSync(header, "utf8") : "";
196
+ if (current !== generated) {
197
+ fail(`${header} is out of date; run \`node scripts/build-gui-shaders.mjs\``);
198
+ }
199
+ console.log(`xbintsc: ${header} is up to date`);
200
+ } else {
201
+ writeFileSync(header, generated);
202
+ const total = blobs.reduce((sum, blob) => sum + blob.bytes.length, 0);
203
+ console.log(`xbintsc: wrote ${header} (${blobs.length} blobs, ${total} bytes)`);
204
+ }
@@ -53,6 +53,32 @@ const isWindows = process.platform === "win32";
53
53
  const objectSuffix = isWindows ? ".obj" : ".o";
54
54
  const output = join(outDir, isWindows ? "gui.lib" : "gui.a");
55
55
 
56
+ /* CMake generator and ABI settings used for every vendored library. Ninja is
57
+ * selected on Windows because the Visual Studio generator imports the
58
+ * machine-wide MSBuild/vcpkg integration, whose `LIBPATH` shadows the Windows
59
+ * SDK and makes the compiler probe fail with
60
+ * `LNK1104: cannot open file 'ucrtd.lib'`.
61
+ *
62
+ * `CMAKE_MSVC_RUNTIME_LIBRARY` is pinned to the *static* CRT (`MultiThreaded`),
63
+ * which is clang's Windows default (`-defaultlib:libcmt`) and what xbintsc uses
64
+ * for its own runtime objects. Building the vendored libraries against the DLL
65
+ * CRT instead fails the final link with
66
+ * `LNK2038: mismatch detected for 'RuntimeLibrary'`. The variable only takes
67
+ * effect when policy CMP0091 is NEW, but FreeType and HarfBuzz declare
68
+ * `cmake_minimum_required` below 3.15, which defaults the policy to OLD and
69
+ * silently ignores it — hence `CMAKE_POLICY_DEFAULT_CMP0091`. The C/C++ compilers
70
+ * are pinned to the same clang the engine is compiled with, so every object in
71
+ * `gui.a` shares one ABI and optimization behaviour. */
72
+ const cmakeGeneratorArgs: readonly string[] = isWindows
73
+ ? [
74
+ "-G", "Ninja",
75
+ "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded",
76
+ "-DCMAKE_POLICY_DEFAULT_CMP0091=NEW",
77
+ "-DCMAKE_C_COMPILER=clang",
78
+ "-DCMAKE_CXX_COMPILER=clang++",
79
+ ]
80
+ : [];
81
+
56
82
  const SDL_TAG = process.env.SDL3_TAG || "release-3.2.10";
57
83
  const FREETYPE_VERSION = process.env.FREETYPE_VERSION || "2.13.3";
58
84
  const HARFBUZZ_VERSION = process.env.HARFBUZZ_VERSION || "10.1.0";
@@ -178,6 +204,7 @@ if (!existingSdlArchive) {
178
204
  const configureArgs = [
179
205
  "-S", sdlSrc,
180
206
  "-B", sdlBuild,
207
+ ...cmakeGeneratorArgs,
181
208
  "-DCMAKE_BUILD_TYPE=Release",
182
209
  "-DSDL_SHARED=OFF",
183
210
  "-DSDL_STATIC=ON",
@@ -220,6 +247,7 @@ if (!existsSync(freetypeArchive() ?? "")) {
220
247
  run(cmake, [
221
248
  "-S", freetypeSrc,
222
249
  "-B", freetypeBuild,
250
+ ...cmakeGeneratorArgs,
223
251
  "-DCMAKE_BUILD_TYPE=Release",
224
252
  "-DBUILD_SHARED_LIBS=OFF",
225
253
  /* We only need TrueType/OpenType outlines; skip the optional codecs. */
@@ -240,6 +268,7 @@ if (!existsSync(harfbuzzArchive() ?? "")) {
240
268
  run(cmake, [
241
269
  "-S", harfbuzzSrc,
242
270
  "-B", harfbuzzBuild,
271
+ ...cmakeGeneratorArgs,
243
272
  "-DCMAKE_BUILD_TYPE=Release",
244
273
  "-DBUILD_SHARED_LIBS=OFF",
245
274
  "-DHB_BUILD_UTILS=OFF",
@@ -272,6 +301,12 @@ const sources = readdirSync(engineDir)
272
301
 
273
302
  const objects: string[] = [];
274
303
  const cxxFlags = ["-std=c++17", "-O2", "-Wall", "-Wextra", ...includeFlags];
304
+ /* The engine is C++ and the MSVC C++ runtime must match the static C CRT that
305
+ * clang links by default, so let every engine object record `libcpmt` as a
306
+ * default library. The extension itself only contributes the DLL import library
307
+ * (`-lmsvcprt`, `src/extensions/gui/index.ts`), which is inconsistent with that
308
+ * CRT and makes the final link fail with `LNK2038`/unresolved `__imp_*`. */
309
+ if (isWindows) cxxFlags.push("-Xclang", "--dependent-lib=libcpmt");
275
310
  /* The MSVC ABI rejects `-fPIC`; position independence is the default elsewhere. */
276
311
  if (!isWindows) cxxFlags.splice(3, 0, "-fPIC");
277
312
  for (const source of sources) {
@@ -38,6 +38,10 @@ const IGNORED_DIRECTORIES = new Set([
38
38
  /** Vendored third-party sources that ship verbatim and are not ours to split. */
39
39
  const VENDORED_FILES = new Set([join("runtime", "ext_gui", "stb_image.h")]);
40
40
 
41
+ /** Machine-generated files: they are reproduced from their sources by a script,
42
+ * so their size is not a sign of a module that needs splitting. */
43
+ const GENERATED_FILES = new Set([join("runtime", "ext_gui", "renderer_shaders_data.h")]);
44
+
41
45
  interface Offense {
42
46
  file: string;
43
47
  lines: number;
@@ -65,7 +69,7 @@ function collect(dir: string, offenses: Offense[]): void {
65
69
  const dot = entry.name.lastIndexOf(".");
66
70
  if (dot < 0 || !CODE_EXTENSIONS.has(entry.name.slice(dot))) continue;
67
71
  const file = relative(root, full);
68
- if (VENDORED_FILES.has(file)) continue;
72
+ if (VENDORED_FILES.has(file) || GENERATED_FILES.has(file)) continue;
69
73
  const lines = countLines(full);
70
74
  if (lines > MAX_LINES) offenses.push({ file, lines });
71
75
  }