xbintsc 0.5.37 → 0.5.41

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 (166) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/codegen/generator/module.js +23 -7
  3. package/dist/src/codegen/generator/module.js.map +1 -1
  4. package/dist/src/codegen/generator/state.js +1 -0
  5. package/dist/src/codegen/generator/state.js.map +1 -1
  6. package/dist/src/codegen/generator/tables.js +1 -0
  7. package/dist/src/codegen/generator/tables.js.map +1 -1
  8. package/dist/src/driver/bundler/deferred.d.ts +63 -0
  9. package/dist/src/driver/bundler/deferred.js +279 -0
  10. package/dist/src/driver/bundler/deferred.js.map +1 -0
  11. package/dist/src/driver/bundler/merge.js +214 -2
  12. package/dist/src/driver/bundler/merge.js.map +1 -1
  13. package/dist/src/driver/runtime-objects.d.ts +1 -1
  14. package/dist/src/driver/runtime-objects.js +1 -0
  15. package/dist/src/driver/runtime-objects.js.map +1 -1
  16. package/dist/src/driver/version.d.ts +1 -1
  17. package/dist/src/driver/version.js +1 -1
  18. package/dist/src/extensions/gui/html.js +98 -15
  19. package/dist/src/extensions/gui/html.js.map +1 -1
  20. package/dist/src/extensions/gui/index.js +1 -0
  21. package/dist/src/extensions/gui/index.js.map +1 -1
  22. package/dist/src/extensions/gui/three.d.ts +4 -3
  23. package/dist/src/extensions/gui/three.js +4 -3
  24. package/dist/src/extensions/gui/three.js.map +1 -1
  25. package/dist/tests/codegen/closures.test.js +7 -0
  26. package/dist/tests/codegen/closures.test.js.map +1 -1
  27. package/dist/tests/e2e/compile-objects-classes.test.js +31 -0
  28. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  29. package/dist/tests/e2e/gui-canvas-state.test.d.ts +9 -0
  30. package/dist/tests/e2e/gui-canvas-state.test.js +297 -0
  31. package/dist/tests/e2e/gui-canvas-state.test.js.map +1 -0
  32. package/dist/tests/e2e/gui-draw-perf.test.d.ts +22 -0
  33. package/dist/tests/e2e/gui-draw-perf.test.js +405 -0
  34. package/dist/tests/e2e/gui-draw-perf.test.js.map +1 -0
  35. package/dist/tests/e2e/gui-perf.test.d.ts +23 -0
  36. package/dist/tests/e2e/gui-perf.test.js +283 -0
  37. package/dist/tests/e2e/gui-perf.test.js.map +1 -0
  38. package/dist/tests/e2e/gui-script-import-order.test.d.ts +10 -0
  39. package/dist/tests/e2e/gui-script-import-order.test.js +169 -0
  40. package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -0
  41. package/dist/tests/e2e/gui-webgl.test.js +4 -4
  42. package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
  43. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.d.ts +8 -0
  44. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js +336 -0
  45. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js.map +1 -0
  46. package/dist/tests/e2e/gui-webgl2-shadow.test.d.ts +10 -0
  47. package/dist/tests/e2e/gui-webgl2-shadow.test.js +108 -0
  48. package/dist/tests/e2e/gui-webgl2-shadow.test.js.map +1 -0
  49. package/dist/tests/e2e/url.test.d.ts +6 -0
  50. package/dist/tests/e2e/url.test.js +114 -0
  51. package/dist/tests/e2e/url.test.js.map +1 -0
  52. package/dist/tests/extensions/gui.test.js +45 -10
  53. package/dist/tests/extensions/gui.test.js.map +1 -1
  54. package/doc/gui/api.md +28 -2
  55. package/doc/gui/canvas.md +110 -27
  56. package/doc/gui/known-issues.md +32 -10
  57. package/doc/gui/rendering.md +77 -0
  58. package/doc/gui/runtime.md +18 -3
  59. package/doc/gui/webgl.md +58 -29
  60. package/doc/gui-scripts.md +19 -19
  61. package/doc/gui.md +43 -9
  62. package/doc/implemented/fetch.md +5 -0
  63. package/doc/implemented/modules.md +13 -0
  64. package/doc/implemented/stdlib.md +17 -0
  65. package/doc/unimplemented/deviations.md +1 -0
  66. package/doc/zh-CN/gui/api.md +25 -2
  67. package/doc/zh-CN/gui/canvas.md +95 -26
  68. package/doc/zh-CN/gui/known-issues.md +21 -4
  69. package/doc/zh-CN/gui/rendering.md +59 -0
  70. package/doc/zh-CN/gui/runtime.md +13 -3
  71. package/doc/zh-CN/gui/webgl.md +30 -8
  72. package/doc/zh-CN/gui-scripts.md +14 -13
  73. package/doc/zh-CN/gui.md +32 -8
  74. package/doc/zh-CN/implemented/fetch.md +4 -0
  75. package/doc/zh-CN/implemented/modules.md +10 -0
  76. package/doc/zh-CN/implemented/stdlib.md +15 -0
  77. package/doc/zh-CN/unimplemented/deviations.md +1 -0
  78. package/package.json +7 -7
  79. package/runtime/ext_gui/browser.cpp +3 -1
  80. package/runtime/ext_gui/canvas.cpp +135 -89
  81. package/runtime/ext_gui/canvas.h +116 -0
  82. package/runtime/ext_gui/canvas_clip.cpp +143 -0
  83. package/runtime/ext_gui/canvas_composite.cpp +241 -0
  84. package/runtime/ext_gui/canvas_coverage.cpp +241 -0
  85. package/runtime/ext_gui/canvas_gradient.cpp +41 -4
  86. package/runtime/ext_gui/canvas_image_data.cpp +4 -0
  87. package/runtime/ext_gui/canvas_internal.h +34 -9
  88. package/runtime/ext_gui/canvas_raster.cpp +207 -178
  89. package/runtime/ext_gui/canvas_shadow.cpp +161 -0
  90. package/runtime/ext_gui/canvas_stroke.cpp +37 -9
  91. package/runtime/ext_gui/canvas_style.cpp +59 -0
  92. package/runtime/ext_gui/canvas_text.cpp +140 -0
  93. package/runtime/ext_gui/css.h +7 -0
  94. package/runtime/ext_gui/css_sheet.cpp +22 -0
  95. package/runtime/ext_gui/document.cpp +25 -1
  96. package/runtime/ext_gui/document.h +8 -0
  97. package/runtime/ext_gui/dom_api_canvas.cpp +16 -1
  98. package/runtime/ext_gui/dom_api_canvas_image.cpp +9 -0
  99. package/runtime/ext_gui/dom_api_canvas_state.cpp +217 -0
  100. package/runtime/ext_gui/dom_api_internal.h +36 -37
  101. package/runtime/ext_gui/dom_api_offscreen.cpp +213 -0
  102. package/runtime/ext_gui/dom_api_proto.cpp +18 -0
  103. package/runtime/ext_gui/dom_api_webgl.cpp +29 -9
  104. package/runtime/ext_gui/dom_api_webgl2.cpp +33 -1
  105. package/runtime/ext_gui/dom_api_webgl_internal.h +58 -0
  106. package/runtime/ext_gui/dom_api_webgl_source.cpp +18 -0
  107. package/runtime/ext_gui/gradient_values.cpp +12 -1
  108. package/runtime/ext_gui/gui.cpp +5 -0
  109. package/runtime/ext_gui/gui_canvas.cpp +4 -0
  110. package/runtime/ext_gui/gui_engine.h +32 -0
  111. package/runtime/ext_gui/gui_events.cpp +182 -58
  112. package/runtime/ext_gui/gui_media.cpp +3 -0
  113. package/runtime/ext_gui/image.cpp +69 -0
  114. package/runtime/ext_gui/image_bitmap_browser.cpp +11 -0
  115. package/runtime/ext_gui/layout.cpp +4 -0
  116. package/runtime/ext_gui/paint.cpp +19 -0
  117. package/runtime/ext_gui/paint_background.cpp +18 -1
  118. package/runtime/ext_gui/perf.cpp +171 -0
  119. package/runtime/ext_gui/perf.h +127 -0
  120. package/runtime/ext_gui/renderer.cpp +109 -41
  121. package/runtime/ext_gui/renderer.h +33 -9
  122. package/runtime/ext_gui/renderer_image.cpp +135 -65
  123. package/runtime/ext_gui/renderer_internal.h +15 -3
  124. package/runtime/ext_gui/renderer_text.cpp +24 -12
  125. package/runtime/ext_gui/script.cpp +31 -0
  126. package/runtime/ext_gui/style.cpp +101 -30
  127. package/runtime/ext_gui/style.h +6 -3
  128. package/runtime/ext_gui/style_print.cpp +7 -1
  129. package/runtime/ext_gui/text.cpp +23 -4
  130. package/runtime/ext_gui/text.h +29 -0
  131. package/runtime/ext_gui/text_cache.cpp +118 -0
  132. package/runtime/ext_gui/text_glyph_cache.cpp +100 -0
  133. package/runtime/ext_gui/webgl.h +48 -146
  134. package/runtime/ext_gui/webgl_blit.cpp +12 -1
  135. package/runtime/ext_gui/webgl_blit_depth.cpp +270 -0
  136. package/runtime/ext_gui/webgl_buffer.cpp +59 -1
  137. package/runtime/ext_gui/webgl_buffer_objects.cpp +106 -9
  138. package/runtime/ext_gui/webgl_clear.cpp +3 -0
  139. package/runtime/ext_gui/webgl_context.cpp +29 -3
  140. package/runtime/ext_gui/webgl_draw.cpp +75 -270
  141. package/runtime/ext_gui/webgl_draw_buffers.cpp +1 -0
  142. package/runtime/ext_gui/webgl_draw_setup.cpp +321 -0
  143. package/runtime/ext_gui/webgl_draw_state.h +130 -0
  144. package/runtime/ext_gui/webgl_glsl.h +3 -0
  145. package/runtime/ext_gui/webgl_glsl_scan.cpp +3 -8
  146. package/runtime/ext_gui/webgl_glsl_unroll.cpp +283 -0
  147. package/runtime/ext_gui/webgl_msl.cpp +4 -0
  148. package/runtime/ext_gui/webgl_objects.h +148 -0
  149. package/runtime/ext_gui/webgl_pipeline.cpp +8 -8
  150. package/runtime/ext_gui/webgl_program_internal.h +35 -0
  151. package/runtime/ext_gui/webgl_shader.cpp +34 -36
  152. package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
  153. package/runtime/ext_gui/window.cpp +56 -1
  154. package/runtime/rt.h +3 -0
  155. package/runtime/xt_fetch/async.inc +60 -0
  156. package/runtime/xt_url_object.c +463 -0
  157. package/src/codegen/generator/module.ts +22 -7
  158. package/src/codegen/generator/state.ts +1 -0
  159. package/src/codegen/generator/tables.ts +1 -0
  160. package/src/driver/bundler/deferred.ts +351 -0
  161. package/src/driver/bundler/merge.ts +219 -2
  162. package/src/driver/runtime-objects.ts +1 -0
  163. package/src/driver/version.ts +1 -1
  164. package/src/extensions/gui/html.ts +100 -17
  165. package/src/extensions/gui/index.ts +1 -0
  166. package/src/extensions/gui/three.ts +4 -3
@@ -0,0 +1,283 @@
1
+ /*
2
+ * xbintsc GUI engine — `#pragma unroll_loop_start` expansion.
3
+ *
4
+ * three.js marks its material light loops with
5
+ *
6
+ * #pragma unroll_loop_start
7
+ * for ( int i = 0; i < N; i ++ ) { ... UNROLLED_LOOP_INDEX ... [ i ] ... }
8
+ * #pragma unroll_loop_end
9
+ *
10
+ * and expands them in JavaScript before upload. On this runtime the expansion
11
+ * is best done here: the loop body indexes sampler arrays (`shadowMap[ i ]`),
12
+ * and Vulkan GLSL only accepts a *constant* sampler-array index. Expanding the
13
+ * pragma turns every `i` (and `UNROLLED_LOOP_INDEX`) into a literal, so the
14
+ * shadow-map chunks compile without relying on the regex engine or on three.js
15
+ * having unrolled them first. WebGL ignores the unknown pragmas either way.
16
+ *
17
+ * The transformation is deliberately small: only the `for (int i = A; i < B;
18
+ * i ++)` shape three.js emits is recognised. Anything else is left untouched.
19
+ */
20
+
21
+ #include "webgl_glsl.h"
22
+
23
+ #include <cctype>
24
+ #include <cstdlib>
25
+ #include <string>
26
+ #include <vector>
27
+
28
+ namespace xtgui {
29
+ namespace {
30
+
31
+ const char *const kStartPragma = "#pragma unroll_loop_start";
32
+ const char *const kEndPragma = "#pragma unroll_loop_end";
33
+
34
+ /** Skip whitespace and comments starting at `at`. */
35
+ size_t skip_space(const std::string &text, size_t at) {
36
+ while (at < text.size()) {
37
+ const char c = text[at];
38
+ if (std::isspace((unsigned char)c) != 0) {
39
+ at++;
40
+ continue;
41
+ }
42
+ if (c == '/' && at + 1 < text.size() && text[at + 1] == '/') {
43
+ at += 2;
44
+ while (at < text.size() && text[at] != '\n') at++;
45
+ continue;
46
+ }
47
+ if (c == '/' && at + 1 < text.size() && text[at + 1] == '*') {
48
+ at += 2;
49
+ while (at + 1 < text.size() && !(text[at] == '*' && text[at + 1] == '/')) at++;
50
+ if (at + 1 < text.size()) at += 2;
51
+ continue;
52
+ }
53
+ break;
54
+ }
55
+ return at;
56
+ }
57
+
58
+ /** Index just past the end of a `//` / `#` line starting at `at`. */
59
+ size_t line_end(const std::string &text, size_t at) {
60
+ while (at < text.size() && text[at] != '\n') at++;
61
+ return at;
62
+ }
63
+
64
+ /** Match the `}` for the `{` at `open`, skipping comments. */
65
+ size_t matching_brace(const std::string &text, size_t open, size_t *out) {
66
+ int depth = 0;
67
+ for (size_t i = open; i < text.size(); i++) {
68
+ const char c = text[i];
69
+ if (c == '/' && i + 1 < text.size() && text[i + 1] == '/') {
70
+ i += 2;
71
+ while (i < text.size() && text[i] != '\n') i++;
72
+ if (i < text.size()) continue;
73
+ break;
74
+ }
75
+ if (c == '/' && i + 1 < text.size() && text[i + 1] == '*') {
76
+ i += 2;
77
+ while (i + 1 < text.size() && !(text[i] == '*' && text[i + 1] == '/')) i++;
78
+ if (i + 1 < text.size()) i++;
79
+ continue;
80
+ }
81
+ if (c == '{') depth++;
82
+ if (c == '}') {
83
+ depth--;
84
+ if (depth == 0) {
85
+ *out = i;
86
+ return i;
87
+ }
88
+ }
89
+ }
90
+ return std::string::npos;
91
+ }
92
+
93
+ struct LoopHeader {
94
+ std::string variable;
95
+ long start = 0;
96
+ long end = 0;
97
+ bool inclusive = false;
98
+ };
99
+
100
+ /** Tokenise a for-header into identifiers, numbers and punctuation. */
101
+ std::vector<std::string> tokens(const std::string &text) {
102
+ std::vector<std::string> out;
103
+ size_t i = 0;
104
+ while (i < text.size()) {
105
+ const char c = text[i];
106
+ if (std::isspace((unsigned char)c) != 0) {
107
+ i++;
108
+ continue;
109
+ }
110
+ if (std::isalnum((unsigned char)c) != 0 || c == '_') {
111
+ size_t end = i + 1;
112
+ while (end < text.size() &&
113
+ (std::isalnum((unsigned char)text[end]) != 0 || text[end] == '_')) {
114
+ end++;
115
+ }
116
+ out.push_back(text.substr(i, end - i));
117
+ i = end;
118
+ continue;
119
+ }
120
+ if ((c == '+' && i + 1 < text.size() && text[i + 1] == '+') ||
121
+ (c == '-' && i + 1 < text.size() && text[i + 1] == '-')) {
122
+ out.push_back(text.substr(i, 2));
123
+ i += 2;
124
+ continue;
125
+ }
126
+ if ((c == '+' || c == '-' || c == '<' || c == '=' || c == '!') && i + 1 < text.size() &&
127
+ text[i + 1] == '=') {
128
+ out.push_back(text.substr(i, 2));
129
+ i += 2;
130
+ continue;
131
+ }
132
+ out.push_back(std::string(1, c));
133
+ i++;
134
+ }
135
+ return out;
136
+ }
137
+
138
+ bool parse_header(const std::string &header, LoopHeader *out) {
139
+ std::vector<std::string> parts = tokens(header);
140
+ size_t at = 0;
141
+ /* Optional qualifiers (`int`, `uint`, `highp int`, …). */
142
+ while (at < parts.size() && (parts[at] == "int" || parts[at] == "uint" || parts[at] == "highp" ||
143
+ parts[at] == "mediump" || parts[at] == "lowp")) {
144
+ at++;
145
+ }
146
+ if (at >= parts.size()) return false;
147
+ out->variable = parts[at++];
148
+ if (at >= parts.size() || parts[at] != "=") return false;
149
+ at++;
150
+ if (at >= parts.size()) return false;
151
+ char *end = nullptr;
152
+ out->start = std::strtol(parts[at].c_str(), &end, 10);
153
+ if (end == nullptr || *end != 0) return false;
154
+ at++;
155
+ if (at >= parts.size() || parts[at] != ";") return false;
156
+ at++;
157
+ /* `i < N` / `i <= N`; the repeated variable may be spelled differently. */
158
+ if (at >= parts.size()) return false;
159
+ at++;
160
+ if (at >= parts.size()) return false;
161
+ out->inclusive = parts[at] == "<=";
162
+ if (!out->inclusive && parts[at] != "<") return false;
163
+ at++;
164
+ if (at >= parts.size()) return false;
165
+ out->end = std::strtol(parts[at].c_str(), &end, 10);
166
+ if (end == nullptr || *end != 0) return false;
167
+ at++;
168
+ if (at >= parts.size() || parts[at] != ";") return false;
169
+ at++;
170
+ if (at >= parts.size()) return false;
171
+ at++;
172
+ if (at >= parts.size() || parts[at] != "++") return false;
173
+ return true;
174
+ }
175
+
176
+ /** Replace whole-word `name` with `value`. */
177
+ std::string substitute(const std::string &body, const std::string &name, long value) {
178
+ const std::string text = std::to_string(value);
179
+ std::string out;
180
+ out.reserve(body.size());
181
+ size_t i = 0;
182
+ while (i < body.size()) {
183
+ const char c = body[i];
184
+ if (std::isalpha((unsigned char)c) == 0 && c != '_') {
185
+ out += c;
186
+ i++;
187
+ continue;
188
+ }
189
+ size_t end = i + 1;
190
+ while (end < body.size() &&
191
+ (std::isalnum((unsigned char)body[end]) != 0 || body[end] == '_')) {
192
+ end++;
193
+ }
194
+ const std::string word = body.substr(i, end - i);
195
+ if (word == name || word == "UNROLLED_LOOP_INDEX") {
196
+ out += text;
197
+ } else {
198
+ out += word;
199
+ }
200
+ i = end;
201
+ }
202
+ return out;
203
+ }
204
+
205
+ } // namespace
206
+
207
+ std::string unroll_glsl_pragmas(const std::string &source) {
208
+ if (source.find(kStartPragma) == std::string::npos) return source;
209
+ std::string result;
210
+ size_t cursor = 0;
211
+ while (cursor < source.size()) {
212
+ const size_t pragma = source.find(kStartPragma, cursor);
213
+ if (pragma == std::string::npos) {
214
+ result.append(source, cursor, std::string::npos);
215
+ break;
216
+ }
217
+ result.append(source, cursor, pragma - cursor);
218
+ size_t at = line_end(source, pragma + std::string(kStartPragma).size());
219
+ const size_t after_header = skip_space(source, at);
220
+ if (source.compare(after_header, 3, "for") != 0) {
221
+ /* Not the shape three.js emits: keep the pragma and move on. */
222
+ result.append(source, pragma, at - pragma);
223
+ cursor = at;
224
+ continue;
225
+ }
226
+ size_t open_paren = skip_space(source, after_header + 3);
227
+ if (open_paren >= source.size() || source[open_paren] != '(') {
228
+ result.append(source, pragma, at - pragma);
229
+ cursor = at;
230
+ continue;
231
+ }
232
+ int depth = 0;
233
+ size_t close_paren = open_paren;
234
+ for (; close_paren < source.size(); close_paren++) {
235
+ if (source[close_paren] == '(') depth++;
236
+ if (source[close_paren] == ')') {
237
+ depth--;
238
+ if (depth == 0) break;
239
+ }
240
+ }
241
+ if (close_paren >= source.size()) {
242
+ result.append(source, pragma, at - pragma);
243
+ cursor = at;
244
+ continue;
245
+ }
246
+ LoopHeader loop;
247
+ if (!parse_header(source.substr(open_paren + 1, close_paren - open_paren - 1), &loop) ||
248
+ loop.end < loop.start || loop.end - loop.start > 4096) {
249
+ result.append(source, pragma, at - pragma);
250
+ cursor = at;
251
+ continue;
252
+ }
253
+ size_t open_brace = skip_space(source, close_paren + 1);
254
+ if (open_brace >= source.size() || source[open_brace] != '{') {
255
+ result.append(source, pragma, at - pragma);
256
+ cursor = at;
257
+ continue;
258
+ }
259
+ size_t close_brace = 0;
260
+ if (matching_brace(source, open_brace, &close_brace) == std::string::npos) {
261
+ result.append(source, pragma, at - pragma);
262
+ cursor = at;
263
+ continue;
264
+ }
265
+ size_t end = skip_space(source, close_brace + 1);
266
+ if (source.compare(end, std::string(kEndPragma).size(), kEndPragma) != 0) {
267
+ result.append(source, pragma, at - pragma);
268
+ cursor = at;
269
+ continue;
270
+ }
271
+ end = line_end(source, end + std::string(kEndPragma).size());
272
+ const std::string body = source.substr(open_brace + 1, close_brace - open_brace - 1);
273
+ const long last = loop.inclusive ? loop.end : loop.end - 1;
274
+ for (long i = loop.start; i <= last; i++) {
275
+ result += substitute(body, loop.variable, i);
276
+ result += '\n';
277
+ }
278
+ cursor = end;
279
+ }
280
+ return result;
281
+ }
282
+
283
+ } // namespace xtgui
@@ -36,6 +36,10 @@ bool cross_compile_msl(const std::vector<uint32_t> &spirv, bool fragment,
36
36
  * 0..num_samplers-1, so any other allocation would sample the wrong
37
37
  * texture (or none at all). */
38
38
  options.enable_decoration_binding = true;
39
+ /* Sampler arrays (three.js shadow maps) need MSL 2.0: with the default
40
+ * 1.2 target SPIRV-Cross rejects "MSL 2.0 or greater is required for
41
+ * arrays of textures". Every Metal device SDL_GPU runs on supports 2.0. */
42
+ options.msl_version = spirv_cross::CompilerMSL::Options::make_msl_version(2, 0);
39
43
  compiler.set_msl_options(options);
40
44
  *source = compiler.compile();
41
45
  *entry = compiler.get_cleansed_entry_point_name(
@@ -0,0 +1,148 @@
1
+ /*
2
+ * xbintsc GUI engine — WebGL object records.
3
+ *
4
+ * Handles are integer ids, not pointers: a `deleteBuffer`/`deleteTexture` call
5
+ * must make an old JavaScript handle a no-op rather than a dangling pointer.
6
+ * Every record keeps its CPU-side data (buffer bytes, texture parameters) and
7
+ * the GPU handle it was last uploaded into.
8
+ *
9
+ * Split out of `webgl.h` to keep the context header under the repository's
10
+ * file-length budget; the context owns the tables of these records.
11
+ */
12
+ #ifndef XT_GUI_WEBGL_OBJECTS_H
13
+ #define XT_GUI_WEBGL_OBJECTS_H
14
+
15
+ #include <cstdint>
16
+ #include <vector>
17
+
18
+ #include <SDL3/SDL.h>
19
+
20
+ #include "webgl2_constants.h"
21
+ #include "webgl_constants.h"
22
+
23
+ namespace xtgui {
24
+
25
+ constexpr int kWebGLMaxAttribs = 16;
26
+ constexpr int kWebGLMaxTextureUnits = 16;
27
+ constexpr int kWebGLMaxDrawBuffers = 4;
28
+
29
+ struct WebGLBuffer {
30
+ uint32_t id = 0;
31
+ std::vector<uint8_t> data;
32
+ SDL_GPUBuffer *gpu = nullptr;
33
+ uint32_t capacity = 0;
34
+ bool dirty = true;
35
+ /** Half-open byte range of `data` that changed since the last GPU upload. */
36
+ uint32_t dirty_lo = 0;
37
+ uint32_t dirty_hi = 0;
38
+ /** `bufferData` usage hint, reported by `getBufferParameter`. */
39
+ int usage = GL_STATIC_DRAW;
40
+ };
41
+
42
+ struct WebGLTexture {
43
+ uint32_t id = 0;
44
+ int width = 0;
45
+ int height = 0;
46
+ int levels = 1;
47
+ /** The binding target this texture was created for. */
48
+ int target = GL_TEXTURE_2D;
49
+ /** Layers for a cube/2D-array texture, depth for a 3D texture. */
50
+ int layers = 1;
51
+ SDL_GPUTexture *gpu = nullptr;
52
+ SDL_GPUTextureFormat format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
53
+ /** Depth(-stencil) formats can only be depth attachments. */
54
+ bool depth = false;
55
+ /** Block-compressed textures are sample-only, with one mip level. */
56
+ bool compressed = false;
57
+ /** Usage flags the current `gpu` texture was created with. */
58
+ uint32_t usage = 0;
59
+ bool as_target = false; // bound as a colour attachment
60
+ bool as_depth_target = false; // bound as a depth attachment
61
+ bool has_mipmaps = false;
62
+ int min_filter = 0;
63
+ int mag_filter = 0;
64
+ int wrap_s = 0;
65
+ int wrap_t = 0;
66
+ SDL_GPUSampler *sampler = nullptr;
67
+ /** Comparison sampler for a `sampler2DShadow` binding. */
68
+ SDL_GPUSampler *compare_sampler = nullptr;
69
+ bool compare_mode = false;
70
+ int compare_func = GL_LEQUAL;
71
+ bool dirty = false;
72
+ bool defined = false;
73
+ };
74
+
75
+ struct WebGLRenderbuffer {
76
+ uint32_t id = 0;
77
+ int width = 0;
78
+ int height = 0;
79
+ bool stencil = false;
80
+ SDL_GPUTexture *depth = nullptr;
81
+ };
82
+
83
+ struct WebGLFramebuffer {
84
+ uint32_t id = 0;
85
+ uint32_t color_textures[kWebGLMaxDrawBuffers] = {};
86
+ /** Layer rendered to for an array/cube colour attachment. */
87
+ int color_layers[kWebGLMaxDrawBuffers] = {};
88
+ /** `COLOR_ATTACHMENTi` or `NONE` per draw-buffer slot. */
89
+ int draw_buffers[kWebGLMaxDrawBuffers] = {GL_COLOR_ATTACHMENT0, GL_NONE, GL_NONE, GL_NONE};
90
+ uint32_t depth_texture = 0;
91
+ uint32_t depth_renderbuffer = 0;
92
+ int width = 0;
93
+ int height = 0;
94
+ SDL_GPUTexture *depth = nullptr; // owned when no renderbuffer/texture supplies one
95
+ };
96
+
97
+ /** Per-attribute array state. */
98
+ struct WebGLAttribState {
99
+ bool enabled = false;
100
+ bool integer = false; // `vertexAttribIPointer` rather than `vertexAttribPointer`
101
+ uint32_t buffer = 0;
102
+ int size = 4;
103
+ int type = GL_FLOAT;
104
+ bool normalized = false;
105
+ int stride = 0;
106
+ int offset = 0;
107
+ int divisor = 0; // 0 = per vertex, 1 = per instance
108
+ float current[4] = {0.0f, 0.0f, 0.0f, 1.0f};
109
+ /** GPU buffer holding `current[]` while this attribute is disabled. It is
110
+ * created lazily and re-uploaded only when `attrib_value` changes, so a draw
111
+ * never creates a GPU buffer just to supply a constant attribute. */
112
+ SDL_GPUBuffer *constant_gpu = nullptr;
113
+ uint32_t constant_bytes = 0;
114
+ bool constant_dirty = true;
115
+ };
116
+
117
+ /** A WebGL 2 vertex-array object; binding one restores this state. */
118
+ struct WebGLVertexArray {
119
+ uint32_t id = 0;
120
+ WebGLAttribState attribs[kWebGLMaxAttribs];
121
+ uint32_t element_buffer = 0;
122
+ };
123
+
124
+ /** A WebGL 2 sampler object, bound to a texture unit with `bindSampler`. */
125
+ struct WebGLSampler {
126
+ uint32_t id = 0;
127
+ int min_filter = 0;
128
+ int mag_filter = 0;
129
+ int wrap_s = 0;
130
+ int wrap_t = 0;
131
+ /** `TEXTURE_COMPARE_MODE`/`TEXTURE_COMPARE_FUNC` (WebGL 2 sampler objects
132
+ * carry them too). */
133
+ bool compare_mode = false;
134
+ int compare_func = GL_LEQUAL;
135
+ SDL_GPUSampler *gpu = nullptr;
136
+ };
137
+
138
+ struct WebGLQuery {
139
+ uint32_t id = 0;
140
+ };
141
+
142
+ struct WebGLSync {
143
+ uint32_t id = 0;
144
+ };
145
+
146
+ } // namespace xtgui
147
+
148
+ #endif /* XT_GUI_WEBGL_OBJECTS_H */
@@ -16,6 +16,7 @@
16
16
  #include <vector>
17
17
 
18
18
  #include "gui_internal.h"
19
+ #include "perf.h"
19
20
 
20
21
  namespace xtgui {
21
22
  namespace {
@@ -251,10 +252,8 @@ SDL_GPUBuffer *WebGLContext::upload_temp_buffer(const void *data, uint32_t size,
251
252
  }
252
253
 
253
254
  SDL_GPUGraphicsPipeline *WebGLContext::create_pipeline(
254
- WebGLProgram *program, int mode, int index_size, int attribute_count, int buffer_count,
255
- const SDL_GPUVertexAttribute *attrs, const SDL_GPUVertexBufferDescription *buffers,
256
- const std::string &key) {
257
- (void)index_size;
255
+ WebGLProgram *program, int mode, int attribute_count, int buffer_count,
256
+ const SDL_GPUVertexAttribute *attrs, const SDL_GPUVertexBufferDescription *buffers) {
258
257
  SDL_GPUColorTargetDescription color_targets[kWebGLMaxDrawBuffers];
259
258
  const int color_target_count =
260
259
  state_.color_target_count > 0 ? state_.color_target_count : 1;
@@ -318,9 +317,10 @@ SDL_GPUGraphicsPipeline *WebGLContext::create_pipeline(
318
317
  set_error(GL_INVALID_OPERATION);
319
318
  return nullptr;
320
319
  }
321
- program->pipeline_cache[key] = pipeline;
322
- program->pipelines.push_back(pipeline);
323
- return pipeline;
324
- }
320
+ /* Ownership stays with the caller: a program draw pushes the pipeline into
321
+ * its own list, an internal helper draw keeps it in the context. Releasing
322
+ * both would free it twice. */
323
+ g_perf_frame.webglPipelineMisses += 1;
324
+ return pipeline;}
325
325
 
326
326
  } // namespace xtgui
@@ -10,6 +10,41 @@
10
10
  namespace xtgui {
11
11
  namespace webgl_detail {
12
12
 
13
+ /** The GL enum reported for a GLSL uniform type (`getActiveUniform`). */
14
+ inline int gl_type_of(const std::string &type) {
15
+ if (type == "float") return GL_FLOAT;
16
+ if (type == "vec2") return GL_FLOAT_VEC2;
17
+ if (type == "vec3") return GL_FLOAT_VEC3;
18
+ if (type == "vec4") return GL_FLOAT_VEC4;
19
+ if (type == "int" || type == "uint") return GL_INT;
20
+ if (type == "ivec2" || type == "uvec2") return GL_INT_VEC2;
21
+ if (type == "ivec3" || type == "uvec3") return GL_INT_VEC3;
22
+ if (type == "ivec4" || type == "uvec4") return GL_INT_VEC4;
23
+ if (type == "bool") return GL_BOOL;
24
+ if (type == "bvec2") return GL_BOOL_VEC2;
25
+ if (type == "bvec3") return GL_BOOL_VEC3;
26
+ if (type == "bvec4") return GL_BOOL_VEC4;
27
+ if (type == "mat2") return GL_FLOAT_MAT2;
28
+ if (type == "mat3") return GL_FLOAT_MAT3;
29
+ if (type == "mat4") return GL_FLOAT_MAT4;
30
+ if (type == "sampler2D") return GL_SAMPLER_2D;
31
+ if (type == "samplerCube") return GL_SAMPLER_CUBE;
32
+ if (type == "sampler3D") return GL_SAMPLER_3D;
33
+ if (type == "sampler2DArray") return GL_SAMPLER_2D_ARRAY;
34
+ if (type == "sampler2DShadow") return GL_SAMPLER_2D_SHADOW;
35
+ if (type == "samplerCubeShadow") return GL_SAMPLER_CUBE_SHADOW;
36
+ if (type == "sampler2DArrayShadow") return GL_SAMPLER_2D_ARRAY_SHADOW;
37
+ return 0;
38
+ }
39
+
40
+ /** How many consecutive attribute/varying locations a type consumes. */
41
+ inline int location_slots(const std::string &type) {
42
+ if (type == "mat2") return 2;
43
+ if (type == "mat3") return 3;
44
+ if (type == "mat4") return 4;
45
+ return 1;
46
+ }
47
+
13
48
  /** Bytes a std140 block member occupies (the whole array for an array). */
14
49
  int member_bytes(const GlslBlockMember &member);
15
50
 
@@ -22,36 +22,8 @@
22
22
  namespace xtgui {
23
23
  namespace {
24
24
 
25
- int gl_type_of(const std::string &type) {
26
- if (type == "float") return GL_FLOAT;
27
- if (type == "vec2") return GL_FLOAT_VEC2;
28
- if (type == "vec3") return GL_FLOAT_VEC3;
29
- if (type == "vec4") return GL_FLOAT_VEC4;
30
- if (type == "int" || type == "uint") return GL_INT;
31
- if (type == "ivec2" || type == "uvec2") return GL_INT_VEC2;
32
- if (type == "ivec3" || type == "uvec3") return GL_INT_VEC3;
33
- if (type == "ivec4" || type == "uvec4") return GL_INT_VEC4;
34
- if (type == "bool") return GL_BOOL;
35
- if (type == "bvec2") return GL_BOOL_VEC2;
36
- if (type == "bvec3") return GL_BOOL_VEC3;
37
- if (type == "bvec4") return GL_BOOL_VEC4;
38
- if (type == "mat2") return GL_FLOAT_MAT2;
39
- if (type == "mat3") return GL_FLOAT_MAT3;
40
- if (type == "mat4") return GL_FLOAT_MAT4;
41
- if (type == "sampler2D") return GL_SAMPLER_2D;
42
- if (type == "samplerCube") return GL_SAMPLER_CUBE;
43
- if (type == "sampler3D") return GL_SAMPLER_3D;
44
- if (type == "sampler2DArray") return GL_SAMPLER_2D_ARRAY;
45
- return 0;
46
- }
47
-
48
- /** How many consecutive attribute/varying locations a type consumes. */
49
- int location_slots(const std::string &type) {
50
- if (type == "mat2") return 2;
51
- if (type == "mat3") return 3;
52
- if (type == "mat4") return 4;
53
- return 1;
54
- }
25
+ using webgl_detail::gl_type_of;
26
+ using webgl_detail::location_slots;
55
27
 
56
28
  /** Merge uniforms declared by both stages, in declaration order. */
57
29
  bool merge_uniforms(const GlslShaderInfo &vs, const GlslShaderInfo &fs,
@@ -174,8 +146,12 @@ bool WebGLContext::compile_shader(uint32_t id) {
174
146
  WebGLShader *record = shader(id);
175
147
  if (record == nullptr) return false;
176
148
  const bool fragment = record->type == GL_FRAGMENT_SHADER;
149
+ /* Expand three.js's pragma-marked light loops first: a sampler-array index
150
+ * must be constant in Vulkan GLSL, and the expansion is the only place that
151
+ * knows the loop bounds. Scanning and rewriting then share one text. */
152
+ const std::string prepared = unroll_glsl_pragmas(record->source);
177
153
  GlslShaderInfo info;
178
- scan_glsl(record->source, fragment, &info);
154
+ scan_glsl(prepared, fragment, &info);
179
155
  if (!info.unsupported.empty()) {
180
156
  record->log = info.unsupported;
181
157
  record->compiled = false;
@@ -186,7 +162,7 @@ bool WebGLContext::compile_shader(uint32_t id) {
186
162
  assign_locations(info.varyings, {}, &layout.varyings);
187
163
  assign_locations(info.samplers, {}, &layout.samplers);
188
164
  layout.block_members = info.uniforms;
189
- const std::string source = rewrite_glsl(record->source, fragment, layout, info);
165
+ const std::string source = rewrite_glsl(prepared, fragment, layout, info);
190
166
  std::vector<uint32_t> spirv;
191
167
  record->compiled = compile_glsl(source, fragment, &spirv, &record->log);
192
168
  return record->compiled;
@@ -228,6 +204,9 @@ void WebGLContext::release_program_pipelines(WebGLProgram *program) {
228
204
  if (program->fs_shader != nullptr) SDL_ReleaseGPUShader(guidev::g_device, program->fs_shader);
229
205
  program->vs_shader = nullptr;
230
206
  program->fs_shader = nullptr;
207
+ /* Prepared draws hold pipeline pointers too; a relink reuses the same program
208
+ * id, so they must not be served from the cache after this. */
209
+ clear_draw_cache();
231
210
  }
232
211
 
233
212
  void WebGLContext::delete_program(uint32_t id) {
@@ -289,10 +268,12 @@ bool WebGLContext::link_program(uint32_t id) {
289
268
  return false;
290
269
  }
291
270
 
271
+ const std::string prepared_vertex = unroll_glsl_pragmas(vertex->source);
272
+ const std::string prepared_fragment = unroll_glsl_pragmas(fragment->source);
292
273
  GlslShaderInfo vs_info;
293
274
  GlslShaderInfo fs_info;
294
- scan_glsl(vertex->source, false, &vs_info);
295
- scan_glsl(fragment->source, true, &fs_info);
275
+ scan_glsl(prepared_vertex, false, &vs_info);
276
+ scan_glsl(prepared_fragment, true, &fs_info);
296
277
  if (!vs_info.unsupported.empty() || !fs_info.unsupported.empty()) {
297
278
  record->log = vs_info.unsupported.empty() ? fs_info.unsupported : vs_info.unsupported;
298
279
  record->linked = false;
@@ -361,8 +342,8 @@ bool WebGLContext::link_program(uint32_t id) {
361
342
  }
362
343
  fs_layout.varyings = vs_layout.varyings;
363
344
 
364
- const std::string vs_source = rewrite_glsl(vertex->source, false, vs_layout, vs_info);
365
- const std::string fs_source = rewrite_glsl(fragment->source, true, fs_layout, fs_info);
345
+ const std::string vs_source = rewrite_glsl(prepared_vertex, false, vs_layout, vs_info);
346
+ const std::string fs_source = rewrite_glsl(prepared_fragment, true, fs_layout, fs_info);
366
347
  std::vector<uint32_t> vs_spirv;
367
348
  std::vector<uint32_t> fs_spirv;
368
349
  std::string log;
@@ -419,6 +400,19 @@ bool WebGLContext::link_program(uint32_t id) {
419
400
  record->vs_sampler_uniforms.clear();
420
401
  record->fs_sampler_uniforms.clear();
421
402
 
403
+ /* Resolve the active attributes once: a draw then iterates this plan instead
404
+ * of re-scanning `attribs` and hashing every attribute name again. */
405
+ record->draw_attribs.clear();
406
+ record->draw_attribs.reserve(record->attribs.size());
407
+ for (const GlslUniformDecl &decl : record->attribs) {
408
+ auto found = record->attrib_locations.find(decl.name);
409
+ if (found == record->attrib_locations.end()) continue;
410
+ WebGLDrawAttribPlan plan;
411
+ plan.location = found->second;
412
+ plan.type = decl.type;
413
+ record->draw_attribs.push_back(plan);
414
+ }
415
+
422
416
  for (const GlslBlockMember &member : members) {
423
417
  WebGLUniform uniform;
424
418
  uniform.name = member.name;
@@ -436,14 +430,18 @@ bool WebGLContext::link_program(uint32_t id) {
436
430
 
437
431
  auto add_sampler = [](WebGLProgram *program, std::vector<int> *stage,
438
432
  const GlslUniformDecl &decl) {
433
+ const bool compare = decl.type == "sampler2DShadow" || decl.type == "samplerCubeShadow" ||
434
+ decl.type == "sampler2DArrayShadow";
439
435
  int index = -1;
440
436
  auto found = program->uniform_index.find(decl.name);
441
437
  if (found != program->uniform_index.end()) {
442
438
  index = found->second;
439
+ program->uniforms[(size_t)index].compare = compare;
443
440
  } else {
444
441
  WebGLUniform uniform;
445
442
  uniform.name = decl.name;
446
443
  uniform.sampler = true;
444
+ uniform.compare = compare;
447
445
  uniform.unit = 0;
448
446
  uniform.layout.gl_type = gl_type_of(decl.type);
449
447
  index = (int)program->uniforms.size();