xbintsc 0.5.31 → 0.5.32

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 (181) hide show
  1. package/README.md +16 -10
  2. package/dist/src/binder/references.js +14 -1
  3. package/dist/src/binder/references.js.map +1 -1
  4. package/dist/src/cli/help.d.ts +1 -1
  5. package/dist/src/codegen/generator/module.d.ts +1 -0
  6. package/dist/src/codegen/generator/module.js +54 -0
  7. package/dist/src/codegen/generator/module.js.map +1 -1
  8. package/dist/src/codegen/generator/statements/variables.js +6 -4
  9. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  10. package/dist/src/driver/version.d.ts +1 -1
  11. package/dist/src/driver/version.js +1 -1
  12. package/dist/src/extensions/gui/css.d.ts +26 -0
  13. package/dist/src/extensions/gui/css.js +100 -0
  14. package/dist/src/extensions/gui/css.js.map +1 -0
  15. package/dist/src/extensions/gui/html.js +9 -2
  16. package/dist/src/extensions/gui/html.js.map +1 -1
  17. package/dist/src/extensions/gui/index.js +16 -2
  18. package/dist/src/extensions/gui/index.js.map +1 -1
  19. package/dist/src/extensions/gui/three.d.ts +16 -0
  20. package/dist/src/extensions/gui/three.js +22 -0
  21. package/dist/src/extensions/gui/three.js.map +1 -0
  22. package/dist/src/extensions/media/index.d.ts +6 -5
  23. package/dist/src/extensions/media/index.js +12 -14
  24. package/dist/src/extensions/media/index.js.map +1 -1
  25. package/dist/tests/e2e/compile-builtins.test.js +24 -0
  26. package/dist/tests/e2e/compile-builtins.test.js.map +1 -1
  27. package/dist/tests/e2e/compile-objects-classes.test.js +23 -0
  28. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  29. package/dist/tests/e2e/gui-browser.test.js +1 -1
  30. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  31. package/dist/tests/e2e/gui-effects.test.d.ts +6 -0
  32. package/dist/tests/e2e/gui-effects.test.js +262 -0
  33. package/dist/tests/e2e/gui-effects.test.js.map +1 -0
  34. package/dist/tests/e2e/gui-example-webgl.test.d.ts +10 -0
  35. package/dist/tests/e2e/gui-example-webgl.test.js +75 -0
  36. package/dist/tests/e2e/gui-example-webgl.test.js.map +1 -0
  37. package/dist/tests/e2e/gui-example-webgl2.test.d.ts +8 -0
  38. package/dist/tests/e2e/gui-example-webgl2.test.js +72 -0
  39. package/dist/tests/e2e/gui-example-webgl2.test.js.map +1 -0
  40. package/dist/tests/e2e/gui-layout.test.js +48 -0
  41. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  42. package/dist/tests/e2e/gui-media.test.js +5 -4
  43. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  44. package/dist/tests/e2e/gui-webgl.test.d.ts +11 -0
  45. package/dist/tests/e2e/gui-webgl.test.js +179 -0
  46. package/dist/tests/e2e/gui-webgl.test.js.map +1 -0
  47. package/dist/tests/e2e/gui-webgl2.test.d.ts +10 -0
  48. package/dist/tests/e2e/gui-webgl2.test.js +238 -0
  49. package/dist/tests/e2e/gui-webgl2.test.js.map +1 -0
  50. package/dist/tests/extensions/gui.test.js +41 -0
  51. package/dist/tests/extensions/gui.test.js.map +1 -1
  52. package/dist/tests/extensions/media.test.js +7 -3
  53. package/dist/tests/extensions/media.test.js.map +1 -1
  54. package/doc/ai/extensions.md +29 -9
  55. package/doc/ai/troubleshooting.md +12 -8
  56. package/doc/ai/zh-CN/extensions.md +26 -8
  57. package/doc/ai/zh-CN/troubleshooting.md +10 -6
  58. package/doc/gui/api.md +3 -2
  59. package/doc/gui/build.md +72 -16
  60. package/doc/gui/canvas.md +11 -3
  61. package/doc/gui/dom.md +33 -18
  62. package/doc/gui/known-issues.md +57 -24
  63. package/doc/gui/layout.md +18 -0
  64. package/doc/gui/media.md +17 -13
  65. package/doc/gui/rendering.md +50 -3
  66. package/doc/gui/webgl.md +256 -0
  67. package/doc/gui.md +108 -17
  68. package/doc/npm-package.md +6 -4
  69. package/doc/requirements.md +18 -9
  70. package/doc/zh-CN/gui/api.md +3 -2
  71. package/doc/zh-CN/gui/build.md +64 -15
  72. package/doc/zh-CN/gui/canvas.md +9 -2
  73. package/doc/zh-CN/gui/dom.md +20 -7
  74. package/doc/zh-CN/gui/known-issues.md +45 -19
  75. package/doc/zh-CN/gui/layout.md +14 -0
  76. package/doc/zh-CN/gui/media.md +15 -11
  77. package/doc/zh-CN/gui/rendering.md +41 -4
  78. package/doc/zh-CN/gui/webgl.md +115 -0
  79. package/doc/zh-CN/gui.md +89 -14
  80. package/doc/zh-CN/npm-package.md +9 -6
  81. package/doc/zh-CN/requirements.md +12 -6
  82. package/llms.txt +2 -0
  83. package/package.json +1 -1
  84. package/runtime/ext_gui/browser.cpp +39 -13
  85. package/runtime/ext_gui/canvas.cpp +3 -18
  86. package/runtime/ext_gui/canvas_internal.h +10 -0
  87. package/runtime/ext_gui/canvas_raster.cpp +60 -0
  88. package/runtime/ext_gui/clip_values.cpp +246 -0
  89. package/runtime/ext_gui/css.cpp +94 -0
  90. package/runtime/ext_gui/css.h +22 -1
  91. package/runtime/ext_gui/css_match.cpp +3 -0
  92. package/runtime/ext_gui/css_selector.cpp +9 -0
  93. package/runtime/ext_gui/css_sheet.cpp +89 -45
  94. package/runtime/ext_gui/document.cpp +4 -1
  95. package/runtime/ext_gui/document.h +2 -0
  96. package/runtime/ext_gui/dom.h +16 -0
  97. package/runtime/ext_gui/dom_api.cpp +1 -0
  98. package/runtime/ext_gui/dom_api_canvas.cpp +17 -80
  99. package/runtime/ext_gui/dom_api_canvas_extra.cpp +130 -1
  100. package/runtime/ext_gui/dom_api_canvas_pending.cpp +41 -0
  101. package/runtime/ext_gui/dom_api_internal.h +259 -0
  102. package/runtime/ext_gui/dom_api_webgl.cpp +376 -0
  103. package/runtime/ext_gui/dom_api_webgl2.cpp +434 -0
  104. package/runtime/ext_gui/dom_api_webgl2_draw.cpp +269 -0
  105. package/runtime/ext_gui/dom_api_webgl_buffer.cpp +212 -0
  106. package/runtime/ext_gui/dom_api_webgl_rt.h +30 -0
  107. package/runtime/ext_gui/dom_api_webgl_shader.cpp +443 -0
  108. package/runtime/ext_gui/dom_api_webgl_state.cpp +444 -0
  109. package/runtime/ext_gui/dom_api_webgl_texture.cpp +445 -0
  110. package/runtime/ext_gui/effect_values.cpp +312 -0
  111. package/runtime/ext_gui/gradient_values.cpp +476 -0
  112. package/runtime/ext_gui/grid_values.cpp +230 -0
  113. package/runtime/ext_gui/gui_canvas.cpp +50 -2
  114. package/runtime/ext_gui/gui_engine.h +27 -0
  115. package/runtime/ext_gui/gui_events.cpp +4 -1
  116. package/runtime/ext_gui/gui_media.cpp +1 -1
  117. package/runtime/ext_gui/gui_webgl.cpp +83 -0
  118. package/runtime/ext_gui/layout.cpp +21 -0
  119. package/runtime/ext_gui/layout_flow.cpp +81 -4
  120. package/runtime/ext_gui/layout_flow_internal.h +31 -0
  121. package/runtime/ext_gui/layout_grid.cpp +374 -0
  122. package/runtime/ext_gui/media.h +1 -1
  123. package/runtime/ext_gui/media_api.h +2 -2
  124. package/runtime/ext_gui/media_query.cpp +211 -0
  125. package/runtime/ext_gui/paint.cpp +111 -77
  126. package/runtime/ext_gui/paint.h +33 -3
  127. package/runtime/ext_gui/paint_background.cpp +229 -0
  128. package/runtime/ext_gui/paint_clip.cpp +268 -0
  129. package/runtime/ext_gui/paint_effects.cpp +324 -0
  130. package/runtime/ext_gui/paint_internal.h +85 -0
  131. package/runtime/ext_gui/renderer.cpp +8 -31
  132. package/runtime/ext_gui/renderer_clip.cpp +196 -0
  133. package/runtime/ext_gui/renderer_image.cpp +13 -8
  134. package/runtime/ext_gui/renderer_internal.h +15 -8
  135. package/runtime/ext_gui/renderer_text.cpp +6 -31
  136. package/runtime/ext_gui/style.cpp +343 -20
  137. package/runtime/ext_gui/style.h +144 -3
  138. package/runtime/ext_gui/style_print.cpp +134 -1
  139. package/runtime/ext_gui/style_values.cpp +152 -126
  140. package/runtime/ext_gui/style_values_internal.h +35 -0
  141. package/runtime/ext_gui/webgl.h +466 -0
  142. package/runtime/ext_gui/webgl2_constants.h +274 -0
  143. package/runtime/ext_gui/webgl_buffer.cpp +565 -0
  144. package/runtime/ext_gui/webgl_constants.h +317 -0
  145. package/runtime/ext_gui/webgl_context.cpp +466 -0
  146. package/runtime/ext_gui/webgl_draw.cpp +495 -0
  147. package/runtime/ext_gui/webgl_glsl.h +125 -0
  148. package/runtime/ext_gui/webgl_glsl_compile.cpp +431 -0
  149. package/runtime/ext_gui/webgl_glsl_rewrite.cpp +349 -0
  150. package/runtime/ext_gui/webgl_glsl_scan.cpp +581 -0
  151. package/runtime/ext_gui/webgl_msl.cpp +62 -0
  152. package/runtime/ext_gui/webgl_msl.h +33 -0
  153. package/runtime/ext_gui/webgl_pipeline.cpp +274 -0
  154. package/runtime/ext_gui/webgl_program_internal.h +18 -0
  155. package/runtime/ext_gui/webgl_shader.cpp +582 -0
  156. package/runtime/ext_gui/webgl_texture.cpp +293 -0
  157. package/runtime/ext_gui/webgl_uniform.cpp +167 -0
  158. package/runtime/ext_gui/webgl_webgl2.cpp +224 -0
  159. package/runtime/ext_media/media.cpp +1 -1
  160. package/runtime/ext_media/media_bridge.c +9 -3
  161. package/runtime/rt_internal.h +1 -0
  162. package/runtime/xt_containers/access.inc +8 -0
  163. package/runtime/xt_regex/api.inc +2 -0
  164. package/runtime/xt_regex/matcher.inc +16 -3
  165. package/runtime/xt_regex/parser.inc +8 -2
  166. package/runtime/xt_regex/types.inc +3 -0
  167. package/runtime/xt_stdlib2/regexp-match.inc +60 -21
  168. package/runtime/xt_stdlib2/regexp.inc +1 -0
  169. package/runtime/xt_typed_array.c +1 -1
  170. package/scripts/build-gui.ts +90 -3
  171. package/scripts/build-media.ts +323 -58
  172. package/scripts/setup-vs-toolchain.ps1 +152 -0
  173. package/src/binder/references.ts +13 -1
  174. package/src/codegen/generator/module.ts +50 -0
  175. package/src/codegen/generator/statements/variables.ts +6 -3
  176. package/src/driver/version.ts +1 -1
  177. package/src/extensions/gui/css.ts +113 -0
  178. package/src/extensions/gui/html.ts +9 -2
  179. package/src/extensions/gui/index.ts +16 -2
  180. package/src/extensions/gui/three.ts +25 -0
  181. package/src/extensions/media/index.ts +13 -16
@@ -0,0 +1,581 @@
1
+ /*
2
+ * xbintsc GUI engine — WebGL GLSL scanner.
3
+ *
4
+ * Splits a shader into tokens (keeping byte offsets so the rewriter can splice
5
+ * the original text without disturbing comments or preprocessor lines) and
6
+ * records every top-level declaration WebGL has to translate.
7
+ */
8
+
9
+ #include "webgl_glsl.h"
10
+
11
+ #include <cctype>
12
+ #include <cstdlib>
13
+ #include <set>
14
+ #include <string>
15
+ #include <vector>
16
+
17
+ namespace xtgui {
18
+ namespace {
19
+
20
+ struct Token {
21
+ enum Kind { Ident, Number, Punct, Directive } kind = Punct;
22
+ std::string text;
23
+ size_t begin = 0;
24
+ size_t end = 0;
25
+ };
26
+
27
+ bool is_ident_start(char c) {
28
+ return std::isalpha((unsigned char)c) != 0 || c == '_';
29
+ }
30
+
31
+ bool is_ident_char(char c) {
32
+ return std::isalnum((unsigned char)c) != 0 || c == '_';
33
+ }
34
+
35
+ /** Tokenise GLSL. Comments are skipped (their bytes stay in the source); `#`
36
+ * directives are one Directive token running to the end of the line. */
37
+ std::vector<Token> tokenize(const std::string &source) {
38
+ std::vector<Token> tokens;
39
+ size_t i = 0;
40
+ const size_t size = source.size();
41
+ while (i < size) {
42
+ const char c = source[i];
43
+ if (std::isspace((unsigned char)c) != 0) {
44
+ i++;
45
+ continue;
46
+ }
47
+ if (c == '/' && i + 1 < size && source[i + 1] == '/') {
48
+ while (i < size && source[i] != '\n') i++;
49
+ continue;
50
+ }
51
+ if (c == '/' && i + 1 < size && source[i + 1] == '*') {
52
+ i += 2;
53
+ while (i + 1 < size && !(source[i] == '*' && source[i + 1] == '/')) i++;
54
+ if (i + 1 < size) i += 2;
55
+ continue;
56
+ }
57
+ Token token;
58
+ token.begin = i;
59
+ if (c == '#') {
60
+ size_t line_end = i;
61
+ while (line_end < size && source[line_end] != '\n') line_end++;
62
+ token.kind = Token::Directive;
63
+ token.text = source.substr(i, line_end - i);
64
+ token.end = line_end;
65
+ tokens.push_back(token);
66
+ i = line_end;
67
+ continue;
68
+ }
69
+ if (is_ident_start(c)) {
70
+ size_t end = i + 1;
71
+ while (end < size && is_ident_char(source[end])) end++;
72
+ token.kind = Token::Ident;
73
+ token.text = source.substr(i, end - i);
74
+ token.end = end;
75
+ tokens.push_back(token);
76
+ i = end;
77
+ continue;
78
+ }
79
+ if (std::isdigit((unsigned char)c) != 0 ||
80
+ (c == '.' && i + 1 < size && std::isdigit((unsigned char)source[i + 1]) != 0)) {
81
+ size_t end = i + 1;
82
+ while (end < size &&
83
+ (std::isalnum((unsigned char)source[end]) != 0 || source[end] == '.' ||
84
+ source[end] == '_')) {
85
+ end++;
86
+ }
87
+ token.kind = Token::Number;
88
+ token.text = source.substr(i, end - i);
89
+ token.end = end;
90
+ tokens.push_back(token);
91
+ i = end;
92
+ continue;
93
+ }
94
+ token.kind = Token::Punct;
95
+ token.text = std::string(1, c);
96
+ token.end = i + 1;
97
+ tokens.push_back(token);
98
+ i++;
99
+ }
100
+ return tokens;
101
+ }
102
+
103
+ bool is_qualifier(const std::string &text) {
104
+ return text == "highp" || text == "mediump" || text == "lowp" || text == "flat" ||
105
+ text == "smooth" || text == "centroid" || text == "invariant" || text == "precise" ||
106
+ text == "noperspective" || text == "sample" || text == "const";
107
+ }
108
+
109
+ bool is_sampler_type(const std::string &type) {
110
+ return type.rfind("sampler", 0) == 0 || type == "atomic_uint";
111
+ }
112
+
113
+ /** The scalar/vector/matrix types the std140 block reflection understands. */
114
+
115
+ /** The `location = N` inside a `layout(...)` group starting at token `at`. */
116
+ int parse_layout_location(const std::vector<Token> &tokens, size_t at, size_t *after) {
117
+ int location = -1;
118
+ if (at >= tokens.size() || tokens[at].text != "layout") return -1;
119
+ size_t i = at + 1;
120
+ if (i >= tokens.size() || tokens[i].text != "(") return -1;
121
+ int depth = 1;
122
+ i++;
123
+ while (i < tokens.size() && depth > 0) {
124
+ if (tokens[i].text == "(") depth++;
125
+ if (tokens[i].text == ")") depth--;
126
+ if (depth > 0 && tokens[i].text == "location" && i + 2 < tokens.size() &&
127
+ tokens[i + 1].text == "=" && tokens[i + 2].kind == Token::Number) {
128
+ location = (int)std::strtol(tokens[i + 2].text.c_str(), nullptr, 0);
129
+ }
130
+ i++;
131
+ }
132
+ *after = i;
133
+ return location;
134
+ }
135
+
136
+ /** The byte range of one declarator (between commas at bracket depth 0). */
137
+ struct Declarator {
138
+ size_t first = 0;
139
+ size_t last = 0; // inclusive token index
140
+ };
141
+
142
+ std::vector<Declarator> split_declarators(const std::vector<Token> &tokens, size_t first,
143
+ size_t semicolon) {
144
+ std::vector<Declarator> parts;
145
+ Declarator current;
146
+ current.first = first;
147
+ int brackets = 0;
148
+ int parens = 0;
149
+ for (size_t i = first; i < semicolon; i++) {
150
+ const std::string &text = tokens[i].text;
151
+ if (text == "[") brackets++;
152
+ if (text == "]") brackets--;
153
+ if (text == "(") parens++;
154
+ if (text == ")") parens--;
155
+ if (text == "," && brackets == 0 && parens == 0) {
156
+ current.last = i - 1;
157
+ parts.push_back(current);
158
+ current = Declarator();
159
+ current.first = i + 1;
160
+ }
161
+ }
162
+ current.last = semicolon - 1;
163
+ parts.push_back(current);
164
+ return parts;
165
+ }
166
+
167
+ /** Parse one declarator: `[precision] type name[array] [= init]`. */
168
+ GlslUniformDecl parse_declarator(const std::vector<Token> &tokens, const Declarator &part,
169
+ const std::string &type, bool first_part) {
170
+ GlslUniformDecl decl;
171
+ decl.type = type;
172
+ size_t i = part.first;
173
+ if (first_part) {
174
+ while (i <= part.last && tokens[i].kind == Token::Ident && is_qualifier(tokens[i].text)) {
175
+ decl.precision = tokens[i].text;
176
+ i++;
177
+ }
178
+ /* The type is the first identifier after the qualifiers. */
179
+ if (i > part.last || tokens[i].kind != Token::Ident) return decl;
180
+ decl.type = tokens[i].text;
181
+ i++;
182
+ }
183
+ while (i <= part.last && tokens[i].kind != Token::Ident) i++;
184
+ if (i > part.last) return decl;
185
+ decl.name = tokens[i].text;
186
+ i++;
187
+ if (i <= part.last && tokens[i].text == "[") {
188
+ int depth = 0;
189
+ for (; i <= part.last; i++) {
190
+ decl.array += tokens[i].text;
191
+ if (tokens[i].text == "[") depth++;
192
+ if (tokens[i].text == "]" && --depth == 0) break;
193
+ }
194
+ }
195
+ decl.sampler = is_sampler_type(decl.type);
196
+ return decl;
197
+ }
198
+
199
+ /* -- preprocessor conditions --------------------------------------------- */
200
+
201
+ /** Every macro a `#define` in the source introduces. A name is treated as
202
+ * defined even when its `#define` sits in a conditional branch; that only makes
203
+ * the condition evaluator more conservative (it may keep a declaration that
204
+ * would be inactive, never drop one that is active). */
205
+ std::set<std::string> collect_defined_names(const std::vector<Token> &tokens) {
206
+ std::set<std::string> names;
207
+ for (const Token &token : tokens) {
208
+ if (token.kind != Token::Directive) continue;
209
+ size_t at = token.text.find_first_not_of(" \t");
210
+ if (at == std::string::npos || token.text.compare(at, 7, "#define") != 0) continue;
211
+ at += 7;
212
+ while (at < token.text.size() && (token.text[at] == ' ' || token.text[at] == '\t')) at++;
213
+ size_t end = at;
214
+ while (end < token.text.size() &&
215
+ (std::isalnum((unsigned char)token.text[end]) != 0 || token.text[end] == '_')) {
216
+ end++;
217
+ }
218
+ if (end > at && std::isalpha((unsigned char)token.text[at]) != 0) {
219
+ names.insert(token.text.substr(at, end - at));
220
+ }
221
+ }
222
+ return names;
223
+ }
224
+
225
+ /** Turns `defined(X)` / `defined X` into 1/0 and leaves the logical operators,
226
+ * parentheses and literals. An identifier or a numeric macro makes the
227
+ * expression "unknown" (`?`), which callers treat as active. */
228
+ std::string normalize_condition(const std::string &expression,
229
+ const std::set<std::string> &defined) {
230
+ std::string out;
231
+ for (size_t i = 0; i < expression.size();) {
232
+ const char c = expression[i];
233
+ if (std::isalpha((unsigned char)c) != 0 || c == '_') {
234
+ size_t end = i;
235
+ while (end < expression.size() &&
236
+ (std::isalnum((unsigned char)expression[end]) != 0 || expression[end] == '_')) {
237
+ end++;
238
+ }
239
+ const std::string word = expression.substr(i, end - i);
240
+ if (word == "defined") {
241
+ size_t at = end;
242
+ while (at < expression.size() && std::isspace((unsigned char)expression[at]) != 0) at++;
243
+ const bool paren = at < expression.size() && expression[at] == '(';
244
+ if (paren) {
245
+ at++;
246
+ while (at < expression.size() && std::isspace((unsigned char)expression[at]) != 0) at++;
247
+ }
248
+ const size_t begin = at;
249
+ while (at < expression.size() &&
250
+ (std::isalnum((unsigned char)expression[at]) != 0 || expression[at] == '_')) {
251
+ at++;
252
+ }
253
+ const std::string name = expression.substr(begin, at - begin);
254
+ if (paren) {
255
+ while (at < expression.size() && std::isspace((unsigned char)expression[at]) != 0) at++;
256
+ if (at < expression.size() && expression[at] == ')') at++;
257
+ }
258
+ out += defined.count(name) != 0 ? '1' : '0';
259
+ i = at;
260
+ continue;
261
+ }
262
+ out += '?';
263
+ i = end;
264
+ continue;
265
+ }
266
+ out += c;
267
+ i++;
268
+ }
269
+ return out;
270
+ }
271
+
272
+ struct ConditionValue {
273
+ bool value = true;
274
+ bool known = true;
275
+ };
276
+
277
+ class ConditionParser {
278
+ public:
279
+ explicit ConditionParser(const std::string &text) : text_(text) {}
280
+
281
+ ConditionValue parse() {
282
+ const bool value = parse_or();
283
+ return {value, known_};
284
+ }
285
+
286
+ private:
287
+ void skip() {
288
+ while (at_ < text_.size() && std::isspace((unsigned char)text_[at_]) != 0) at_++;
289
+ }
290
+ bool parse_or() {
291
+ bool value = parse_and();
292
+ for (;;) {
293
+ skip();
294
+ if (at_ + 1 >= text_.size() || text_[at_] != '|' || text_[at_ + 1] != '|') break;
295
+ at_ += 2;
296
+ value = parse_and() || value;
297
+ }
298
+ return value;
299
+ }
300
+ bool parse_and() {
301
+ bool value = parse_unary();
302
+ for (;;) {
303
+ skip();
304
+ if (at_ + 1 >= text_.size() || text_[at_] != '&' || text_[at_ + 1] != '&') break;
305
+ at_ += 2;
306
+ value = parse_unary() && value;
307
+ }
308
+ return value;
309
+ }
310
+ bool parse_unary() {
311
+ skip();
312
+ if (at_ < text_.size() && text_[at_] == '!') {
313
+ at_++;
314
+ return !parse_unary();
315
+ }
316
+ return parse_primary();
317
+ }
318
+ bool parse_primary() {
319
+ skip();
320
+ if (at_ >= text_.size()) {
321
+ known_ = false;
322
+ return false;
323
+ }
324
+ if (text_[at_] == '(') {
325
+ at_++;
326
+ const bool value = parse_or();
327
+ skip();
328
+ if (at_ < text_.size() && text_[at_] == ')') at_++;
329
+ return value;
330
+ }
331
+ if (text_[at_] == '1' || text_[at_] == '0') {
332
+ return text_[at_++] == '1';
333
+ }
334
+ known_ = false;
335
+ at_++;
336
+ return false;
337
+ }
338
+
339
+ const std::string &text_;
340
+ size_t at_ = 0;
341
+ bool known_ = true;
342
+ };
343
+
344
+ ConditionValue evaluate_condition(const std::string &directive,
345
+ const std::set<std::string> &defined) {
346
+ std::string text = directive;
347
+ size_t at = text.find_first_not_of(" \t#");
348
+ if (at == std::string::npos) return {true, false};
349
+ const bool is_ifdef = text.compare(at, 5, "ifdef") == 0;
350
+ const bool is_ifndef = text.compare(at, 6, "ifndef") == 0;
351
+ if (is_ifdef || is_ifndef) {
352
+ at += is_ifdef ? 5 : 6;
353
+ while (at < text.size() && std::isspace((unsigned char)text[at]) != 0) at++;
354
+ size_t end = at;
355
+ while (end < text.size() &&
356
+ (std::isalnum((unsigned char)text[end]) != 0 || text[end] == '_')) {
357
+ end++;
358
+ }
359
+ if (end == at) return {true, false};
360
+ const std::string name = text.substr(at, end - at);
361
+ const bool present = defined.count(name) != 0;
362
+ return {is_ifdef ? present : !present, true};
363
+ }
364
+ if (text.compare(at, 2, "if") != 0) return {true, false};
365
+ const std::string normalized = normalize_condition(text.substr(at + 2), defined);
366
+ ConditionParser parser(normalized);
367
+ return parser.parse();
368
+ }
369
+
370
+ } // namespace
371
+
372
+ void scan_glsl(const std::string &source, bool fragment, GlslShaderInfo *info) {
373
+ *info = GlslShaderInfo();
374
+ const std::vector<Token> tokens = tokenize(source);
375
+ const std::set<std::string> defined_names = collect_defined_names(tokens);
376
+ std::vector<bool> condition_stack;
377
+ int inactive = 0;
378
+ int braces = 0;
379
+ int parens = 0;
380
+ size_t first_uniform_any = std::string::npos;
381
+ size_t pending_layout = std::string::npos;
382
+ int pending_location = -1;
383
+
384
+ for (size_t i = 0; i < tokens.size(); i++) {
385
+ const Token &token = tokens[i];
386
+ if (token.kind == Token::Directive) {
387
+ const std::string &directive = token.text;
388
+ size_t at = directive.find_first_not_of(" \t");
389
+ if (at == std::string::npos) continue;
390
+ if (directive.compare(at, 6, "#endif") == 0) {
391
+ if (!condition_stack.empty()) {
392
+ if (!condition_stack.back()) inactive--;
393
+ condition_stack.pop_back();
394
+ }
395
+ } else if (directive.compare(at, 5, "#elif") == 0) {
396
+ if (!condition_stack.empty()) {
397
+ const ConditionValue cond = evaluate_condition(directive, defined_names);
398
+ const bool active = !cond.known || cond.value;
399
+ if (!condition_stack.back()) inactive--;
400
+ condition_stack.back() = active;
401
+ if (!active) inactive++;
402
+ }
403
+ } else if (directive.compare(at, 5, "#else") == 0) {
404
+ if (!condition_stack.empty()) {
405
+ const bool was_active = condition_stack.back();
406
+ if (was_active) inactive++;
407
+ else inactive--;
408
+ condition_stack.back() = !was_active;
409
+ }
410
+ } else if (directive.compare(at, 3, "#if") == 0) {
411
+ const ConditionValue cond = evaluate_condition(directive, defined_names);
412
+ const bool active = !cond.known || cond.value;
413
+ condition_stack.push_back(active);
414
+ if (!active) inactive++;
415
+ }
416
+ continue;
417
+ }
418
+ if (token.kind == Token::Punct) {
419
+ /* Braces inside a false branch are frequently unbalanced with the
420
+ * active branch (three.js wraps parts of function signatures in
421
+ * #ifdef), so only track the branch the compiler keeps. */
422
+ if (inactive == 0) {
423
+ if (token.text == "{") braces++;
424
+ if (token.text == "}") braces--;
425
+ if (token.text == "(") parens++;
426
+ if (token.text == ")") parens--;
427
+ }
428
+ continue;
429
+ }
430
+ if (token.kind == Token::Ident && token.text == "gl_FragColor") {
431
+ info->legacy_fragment_output = true;
432
+ continue;
433
+ }
434
+ if (token.kind == Token::Ident && token.text == "gl_FragData") {
435
+ info->legacy_fragment_output = true;
436
+ continue;
437
+ }
438
+ /* Declarations inside a definitively false preprocessor branch never reach
439
+ * the compiler; rewriting them would move their types/macros (and array
440
+ * sizes such as `MORPHTARGETS_COUNT`) outside that branch. */
441
+ if (inactive > 0) continue;
442
+ /* Function parameters may use `in`/`out`/`const in` qualifiers; those are
443
+ * not declarations to rewrite (a layout qualifier there is a compile
444
+ * error). */
445
+ if (braces != 0 || parens != 0) continue;
446
+ if (token.kind != Token::Ident) continue;
447
+ /* Active struct definitions are hoisted to the block insertion point so a
448
+ * uniform can name a type declared later in the shader (three.js light
449
+ * structs follow the prefix uniforms). */
450
+ if (token.text == "struct" && i + 1 < tokens.size()) {
451
+ size_t brace = i + 1;
452
+ while (brace < tokens.size() && tokens[brace].text != "{") brace++;
453
+ if (brace < tokens.size()) {
454
+ int depth = 0;
455
+ size_t close = brace;
456
+ for (; close < tokens.size(); close++) {
457
+ if (tokens[close].text == "{") depth++;
458
+ if (tokens[close].text == "}") {
459
+ depth--;
460
+ if (depth == 0) break;
461
+ }
462
+ }
463
+ if (close + 1 < tokens.size() && tokens[close + 1].text == ";") {
464
+ info->structs.push_back({tokens[i].begin, tokens[close + 1].end});
465
+ info->struct_definitions.push_back(
466
+ source.substr(tokens[i].begin, tokens[close + 1].end - tokens[i].begin));
467
+ i = close + 1;
468
+ continue;
469
+ }
470
+ }
471
+ }
472
+ if (token.text == "layout") {
473
+ pending_layout = i;
474
+ size_t after = i;
475
+ pending_location = parse_layout_location(tokens, i, &after);
476
+ i = after - 1;
477
+ continue;
478
+ }
479
+
480
+ GlslDeclSite::Kind kind;
481
+ bool is_decl = true;
482
+ if (token.text == "uniform") {
483
+ kind = GlslDeclSite::Uniform;
484
+ } else if (token.text == "attribute") {
485
+ kind = GlslDeclSite::Attribute;
486
+ } else if (token.text == "varying") {
487
+ kind = GlslDeclSite::Varying;
488
+ } else if (token.text == "in") {
489
+ kind = fragment ? GlslDeclSite::Varying : GlslDeclSite::Attribute;
490
+ } else if (token.text == "out") {
491
+ if (!fragment) {
492
+ kind = GlslDeclSite::Varying;
493
+ } else {
494
+ kind = GlslDeclSite::FragmentOut;
495
+ info->has_fragment_output = true;
496
+ }
497
+ } else {
498
+ is_decl = false;
499
+ }
500
+ if (!is_decl) {
501
+ pending_layout = std::string::npos;
502
+ pending_location = -1;
503
+ continue;
504
+ }
505
+
506
+ /* Find the terminating semicolon at bracket/paren depth 0. */
507
+ size_t semicolon = i + 1;
508
+ int brackets = 0;
509
+ int parens = 0;
510
+ for (; semicolon < tokens.size(); semicolon++) {
511
+ const std::string &text = tokens[semicolon].text;
512
+ if (text == "[") brackets++;
513
+ if (text == "]") brackets--;
514
+ if (text == "(") parens++;
515
+ if (text == ")") parens--;
516
+ if (text == ";" && brackets == 0 && parens == 0) break;
517
+ if (text == "{" && brackets == 0 && parens == 0 && kind == GlslDeclSite::Uniform) {
518
+ info->unsupported = "uniform blocks are not supported";
519
+ }
520
+ }
521
+ if (semicolon >= tokens.size()) {
522
+ fprintf(stderr, "DECL_OVERRUN fragment=%d kind=%d start=%zu token=%s\\n", (int)fragment, (int)kind, i, token.text.c_str());
523
+ break;
524
+ }
525
+
526
+ const std::vector<Declarator> parts = split_declarators(tokens, i + 1, semicolon);
527
+ /* The type is the first non-qualifier identifier of the first declarator. */
528
+ std::string type;
529
+ {
530
+ size_t at = parts[0].first;
531
+ while (at <= parts[0].last && tokens[at].kind == Token::Ident &&
532
+ is_qualifier(tokens[at].text)) {
533
+ at++;
534
+ }
535
+ if (at <= parts[0].last && tokens[at].kind == Token::Ident) type = tokens[at].text;
536
+ }
537
+ for (size_t p = 0; p < parts.size(); p++) {
538
+ GlslUniformDecl decl = parse_declarator(tokens, parts[p], type, p == 0);
539
+ if (decl.name.empty()) continue;
540
+ GlslDeclSite site;
541
+ site.kind = decl.sampler && kind == GlslDeclSite::Uniform ? GlslDeclSite::Sampler : kind;
542
+ site.begin = (p == 0 && pending_layout != std::string::npos) ? tokens[pending_layout].begin
543
+ : tokens[i].begin;
544
+ site.end = tokens[semicolon].end;
545
+ site.decl = decl;
546
+ site.existing_location = p == 0 ? pending_location : -1;
547
+ site.conditional = !condition_stack.empty();
548
+ info->sites.push_back(site);
549
+ switch (site.kind) {
550
+ case GlslDeclSite::Uniform:
551
+ info->uniforms.push_back(decl);
552
+ if (first_uniform_any == std::string::npos) first_uniform_any = site.begin;
553
+ if (!site.conditional && info->first_uniform_offset == std::string::npos) {
554
+ info->first_uniform_offset = site.begin;
555
+ }
556
+ break;
557
+ case GlslDeclSite::Sampler:
558
+ info->samplers.push_back(decl);
559
+ break;
560
+ case GlslDeclSite::Attribute:
561
+ info->attributes.push_back(decl);
562
+ break;
563
+ case GlslDeclSite::Varying:
564
+ info->varyings.push_back(decl);
565
+ break;
566
+ case GlslDeclSite::FragmentOut:
567
+ break;
568
+ }
569
+ }
570
+ i = semicolon;
571
+ pending_layout = std::string::npos;
572
+ pending_location = -1;
573
+ }
574
+ /* Every non-sampler uniform was inside a preprocessor branch; fall back to
575
+ * the first one so the block still has a home. */
576
+ if (info->first_uniform_offset == std::string::npos) {
577
+ info->first_uniform_offset = first_uniform_any;
578
+ }
579
+ }
580
+
581
+ } // namespace xtgui
@@ -0,0 +1,62 @@
1
+ /*
2
+ * xbintsc GUI engine — SPIRV-Cross integration (SPIR-V -> MSL).
3
+ *
4
+ * `cross_compile_msl` is the only place that calls into SPIRV-Cross. It runs
5
+ * after `compile_glsl`, on the module glslang produced, so both GPU backends
6
+ * share one GLSL front end: the Vulkan path hands the SPIR-V to SDL_GPU
7
+ * directly, the Metal path cross-compiles it to MSL text that SDL_GPU's Metal
8
+ * backend compiles at run time.
9
+ */
10
+
11
+ #include "webgl_msl.h"
12
+
13
+ #include <exception>
14
+
15
+ #include <spirv_msl.hpp>
16
+
17
+ namespace xtgui {
18
+
19
+ bool cross_compile_msl(const std::vector<uint32_t> &spirv, bool fragment,
20
+ std::string *source, std::string *entry, std::string *log) {
21
+ if (source == nullptr || entry == nullptr || log == nullptr) return false;
22
+ source->clear();
23
+ entry->clear();
24
+ log->clear();
25
+ if (spirv.empty()) {
26
+ *log = "empty SPIR-V module";
27
+ return false;
28
+ }
29
+ try {
30
+ spirv_cross::CompilerMSL compiler(spirv);
31
+ spirv_cross::CompilerMSL::Options options = compiler.get_msl_options();
32
+ /* Reuse the SPIR-V bindings instead of letting SPIRV-Cross allocate MSL
33
+ * indices on demand. The rewriter gives the uniform block binding 0 and
34
+ * the N-th sampler binding N; SDL_GPU's Metal backend pushes the block at
35
+ * buffer 0 and binds both the texture and sampler arrays over
36
+ * 0..num_samplers-1, so any other allocation would sample the wrong
37
+ * texture (or none at all). */
38
+ options.enable_decoration_binding = true;
39
+ compiler.set_msl_options(options);
40
+ *source = compiler.compile();
41
+ *entry = compiler.get_cleansed_entry_point_name(
42
+ "main", fragment ? spv::ExecutionModelFragment : spv::ExecutionModelVertex);
43
+ if (entry->empty()) {
44
+ *log = "MSL entry point vanished during translation";
45
+ source->clear();
46
+ return false;
47
+ }
48
+ return true;
49
+ } catch (const std::exception &error) {
50
+ *log = error.what();
51
+ source->clear();
52
+ entry->clear();
53
+ return false;
54
+ } catch (...) {
55
+ *log = "SPIRV-Cross failed with an unknown error";
56
+ source->clear();
57
+ entry->clear();
58
+ return false;
59
+ }
60
+ }
61
+
62
+ } // namespace xtgui
@@ -0,0 +1,33 @@
1
+ /*
2
+ * xbintsc GUI engine — Metal Shading Language for WebGL.
3
+ *
4
+ * SDL_GPU's Metal backend has no shader compiler either, but unlike Vulkan's
5
+ * SPIR-V it consumes MSL *text* and compiles it at run time. WebGL's GLSL is
6
+ * already translated to SPIR-V for the Vulkan path, so the Metal path reuses
7
+ * that module instead of growing a second GLSL front end: SPIRV-Cross
8
+ * cross-compiles it to MSL while keeping the binding indices the rewriter
9
+ * assigned — one std140 block at buffer 0 and stage-local samplers at
10
+ * binding N, which is exactly the slot layout SDL_GPU's Metal backend binds.
11
+ */
12
+ #ifndef XT_GUI_WEBGL_MSL_H
13
+ #define XT_GUI_WEBGL_MSL_H
14
+
15
+ #include <cstdint>
16
+ #include <string>
17
+ #include <vector>
18
+
19
+ namespace xtgui {
20
+
21
+ /**
22
+ * Cross-compile one SPIR-V stage to MSL.
23
+ *
24
+ * `source` receives the MSL text, `entry` the entry-point name (Metal reserves
25
+ * `main`, so SPIRV-Cross renames it). On failure `log` explains why and the
26
+ * other outputs are left empty.
27
+ */
28
+ bool cross_compile_msl(const std::vector<uint32_t> &spirv, bool fragment,
29
+ std::string *source, std::string *entry, std::string *log);
30
+
31
+ } // namespace xtgui
32
+
33
+ #endif /* XT_GUI_WEBGL_MSL_H */