xbintsc 0.5.38 → 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 (117) 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/driver/version.d.ts +1 -1
  5. package/dist/src/driver/version.js +1 -1
  6. package/dist/src/extensions/gui/html.js +9 -2
  7. package/dist/src/extensions/gui/html.js.map +1 -1
  8. package/dist/tests/codegen/closures.test.js +7 -0
  9. package/dist/tests/codegen/closures.test.js.map +1 -1
  10. package/dist/tests/e2e/compile-objects-classes.test.js +31 -0
  11. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  12. package/dist/tests/e2e/gui-canvas-state.test.d.ts +9 -0
  13. package/dist/tests/e2e/gui-canvas-state.test.js +297 -0
  14. package/dist/tests/e2e/gui-canvas-state.test.js.map +1 -0
  15. package/dist/tests/e2e/gui-draw-perf.test.d.ts +22 -0
  16. package/dist/tests/e2e/gui-draw-perf.test.js +405 -0
  17. package/dist/tests/e2e/gui-draw-perf.test.js.map +1 -0
  18. package/dist/tests/e2e/gui-perf.test.d.ts +23 -0
  19. package/dist/tests/e2e/gui-perf.test.js +283 -0
  20. package/dist/tests/e2e/gui-perf.test.js.map +1 -0
  21. package/dist/tests/e2e/gui-script-import-order.test.js +9 -1
  22. package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -1
  23. package/dist/tests/e2e/gui-webgl.test.js +4 -4
  24. package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
  25. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.d.ts +8 -0
  26. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js +336 -0
  27. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js.map +1 -0
  28. package/dist/tests/extensions/gui.test.js +23 -0
  29. package/dist/tests/extensions/gui.test.js.map +1 -1
  30. package/doc/gui/api.md +28 -2
  31. package/doc/gui/canvas.md +110 -27
  32. package/doc/gui/known-issues.md +32 -10
  33. package/doc/gui/rendering.md +77 -0
  34. package/doc/gui/runtime.md +18 -3
  35. package/doc/gui/webgl.md +38 -19
  36. package/doc/gui.md +33 -1
  37. package/doc/zh-CN/gui/api.md +25 -2
  38. package/doc/zh-CN/gui/canvas.md +95 -26
  39. package/doc/zh-CN/gui/known-issues.md +21 -4
  40. package/doc/zh-CN/gui/rendering.md +59 -0
  41. package/doc/zh-CN/gui/runtime.md +13 -3
  42. package/doc/zh-CN/gui/webgl.md +27 -5
  43. package/doc/zh-CN/gui.md +25 -2
  44. package/package.json +7 -7
  45. package/runtime/ext_gui/browser.cpp +3 -1
  46. package/runtime/ext_gui/canvas.cpp +135 -89
  47. package/runtime/ext_gui/canvas.h +116 -0
  48. package/runtime/ext_gui/canvas_clip.cpp +143 -0
  49. package/runtime/ext_gui/canvas_composite.cpp +241 -0
  50. package/runtime/ext_gui/canvas_coverage.cpp +241 -0
  51. package/runtime/ext_gui/canvas_gradient.cpp +41 -4
  52. package/runtime/ext_gui/canvas_image_data.cpp +4 -0
  53. package/runtime/ext_gui/canvas_internal.h +34 -9
  54. package/runtime/ext_gui/canvas_raster.cpp +207 -178
  55. package/runtime/ext_gui/canvas_shadow.cpp +161 -0
  56. package/runtime/ext_gui/canvas_stroke.cpp +37 -9
  57. package/runtime/ext_gui/canvas_style.cpp +59 -0
  58. package/runtime/ext_gui/canvas_text.cpp +140 -0
  59. package/runtime/ext_gui/css.h +7 -0
  60. package/runtime/ext_gui/css_sheet.cpp +22 -0
  61. package/runtime/ext_gui/document.cpp +25 -1
  62. package/runtime/ext_gui/document.h +8 -0
  63. package/runtime/ext_gui/dom_api_canvas.cpp +16 -1
  64. package/runtime/ext_gui/dom_api_canvas_image.cpp +9 -0
  65. package/runtime/ext_gui/dom_api_canvas_state.cpp +217 -0
  66. package/runtime/ext_gui/dom_api_internal.h +36 -37
  67. package/runtime/ext_gui/dom_api_offscreen.cpp +213 -0
  68. package/runtime/ext_gui/dom_api_proto.cpp +18 -0
  69. package/runtime/ext_gui/dom_api_webgl.cpp +19 -9
  70. package/runtime/ext_gui/dom_api_webgl_internal.h +58 -0
  71. package/runtime/ext_gui/dom_api_webgl_source.cpp +18 -0
  72. package/runtime/ext_gui/gradient_values.cpp +12 -1
  73. package/runtime/ext_gui/gui.cpp +5 -0
  74. package/runtime/ext_gui/gui_canvas.cpp +4 -0
  75. package/runtime/ext_gui/gui_engine.h +30 -0
  76. package/runtime/ext_gui/gui_events.cpp +182 -58
  77. package/runtime/ext_gui/gui_media.cpp +3 -0
  78. package/runtime/ext_gui/image.cpp +69 -0
  79. package/runtime/ext_gui/image_bitmap_browser.cpp +11 -0
  80. package/runtime/ext_gui/layout.cpp +4 -0
  81. package/runtime/ext_gui/paint.cpp +19 -0
  82. package/runtime/ext_gui/paint_background.cpp +18 -1
  83. package/runtime/ext_gui/perf.cpp +171 -0
  84. package/runtime/ext_gui/perf.h +127 -0
  85. package/runtime/ext_gui/renderer.cpp +109 -41
  86. package/runtime/ext_gui/renderer.h +33 -9
  87. package/runtime/ext_gui/renderer_image.cpp +135 -65
  88. package/runtime/ext_gui/renderer_internal.h +15 -3
  89. package/runtime/ext_gui/renderer_text.cpp +24 -12
  90. package/runtime/ext_gui/style.cpp +101 -30
  91. package/runtime/ext_gui/style.h +6 -3
  92. package/runtime/ext_gui/style_print.cpp +7 -1
  93. package/runtime/ext_gui/text.cpp +23 -4
  94. package/runtime/ext_gui/text.h +29 -0
  95. package/runtime/ext_gui/text_cache.cpp +118 -0
  96. package/runtime/ext_gui/text_glyph_cache.cpp +100 -0
  97. package/runtime/ext_gui/webgl.h +48 -146
  98. package/runtime/ext_gui/webgl_blit.cpp +12 -1
  99. package/runtime/ext_gui/webgl_blit_depth.cpp +270 -0
  100. package/runtime/ext_gui/webgl_buffer.cpp +59 -1
  101. package/runtime/ext_gui/webgl_buffer_objects.cpp +106 -9
  102. package/runtime/ext_gui/webgl_clear.cpp +3 -0
  103. package/runtime/ext_gui/webgl_context.cpp +29 -3
  104. package/runtime/ext_gui/webgl_draw.cpp +75 -270
  105. package/runtime/ext_gui/webgl_draw_buffers.cpp +1 -0
  106. package/runtime/ext_gui/webgl_draw_setup.cpp +321 -0
  107. package/runtime/ext_gui/webgl_draw_state.h +130 -0
  108. package/runtime/ext_gui/webgl_glsl_scan.cpp +3 -8
  109. package/runtime/ext_gui/webgl_objects.h +148 -0
  110. package/runtime/ext_gui/webgl_pipeline.cpp +8 -8
  111. package/runtime/ext_gui/webgl_program_internal.h +35 -0
  112. package/runtime/ext_gui/webgl_shader.cpp +22 -30
  113. package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
  114. package/runtime/ext_gui/window.cpp +56 -1
  115. package/src/codegen/generator/module.ts +22 -7
  116. package/src/driver/version.ts +1 -1
  117. package/src/extensions/gui/html.ts +9 -2
@@ -0,0 +1,321 @@
1
+ /*
2
+ * xbintsc GUI engine — prepared WebGL draws.
3
+ *
4
+ * Every draw needs the same three things: the pipeline for the current
5
+ * fixed-function state, the vertex layout of the program's active attributes,
6
+ * and the GPU buffer handles behind them. Building that per draw was the
7
+ * dominant CPU cost of a multi-element scene, so it is built once per
8
+ * (program, layout version, state) and cached in `draw_setups_`.
9
+ *
10
+ * A setup is invalidated by bumping one of the context's state versions:
11
+ * `attribs_version_` for anything that changes the vertex layout (attribute
12
+ * pointers, enables, VAO loads), `gpu_version_` for buffer handle changes, and
13
+ * `framebuffer_version_` for attachment-format changes.
14
+ */
15
+
16
+ #include "webgl.h"
17
+
18
+ #include <cstring>
19
+ #include <utility>
20
+ #include <vector>
21
+
22
+ #include "gui_internal.h"
23
+
24
+ namespace xtgui {
25
+ namespace {
26
+
27
+ int components_of_type(const std::string &type) {
28
+ if (!type.empty() && type.back() >= '2' && type.back() <= '4') return type.back() - '0';
29
+ return 1;
30
+ }
31
+
32
+ int matrix_slots(const std::string &type) {
33
+ if (type == "mat2") return 2;
34
+ if (type == "mat3") return 3;
35
+ if (type == "mat4") return 4;
36
+ return 1;
37
+ }
38
+
39
+ SDL_GPUVertexElementFormat float_format(int size) {
40
+ switch (size) {
41
+ case 1:
42
+ return SDL_GPU_VERTEXELEMENTFORMAT_FLOAT;
43
+ case 2:
44
+ return SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
45
+ case 3:
46
+ return SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
47
+ default:
48
+ return SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
49
+ }
50
+ }
51
+
52
+ SDL_GPUVertexElementFormat format_of(int size, int type, bool normalized) {
53
+ if (size < 1 || size > 4) return SDL_GPU_VERTEXELEMENTFORMAT_INVALID;
54
+ if (type == GL_FLOAT) return float_format(size);
55
+ if (type == GL_UNSIGNED_BYTE || type == GL_BYTE) {
56
+ const bool is_byte = type == GL_BYTE;
57
+ if (size == 2) {
58
+ return normalized ? (is_byte ? SDL_GPU_VERTEXELEMENTFORMAT_BYTE2_NORM
59
+ : SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2_NORM)
60
+ : (is_byte ? SDL_GPU_VERTEXELEMENTFORMAT_BYTE2
61
+ : SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2);
62
+ }
63
+ if (size == 4) {
64
+ return normalized ? (is_byte ? SDL_GPU_VERTEXELEMENTFORMAT_BYTE4_NORM
65
+ : SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM)
66
+ : (is_byte ? SDL_GPU_VERTEXELEMENTFORMAT_BYTE4
67
+ : SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4);
68
+ }
69
+ return SDL_GPU_VERTEXELEMENTFORMAT_INVALID;
70
+ }
71
+ if (type == GL_UNSIGNED_SHORT || type == GL_SHORT) {
72
+ const bool is_short = type == GL_SHORT;
73
+ if (size == 2) {
74
+ return normalized ? (is_short ? SDL_GPU_VERTEXELEMENTFORMAT_SHORT2_NORM
75
+ : SDL_GPU_VERTEXELEMENTFORMAT_USHORT2_NORM)
76
+ : (is_short ? SDL_GPU_VERTEXELEMENTFORMAT_SHORT2
77
+ : SDL_GPU_VERTEXELEMENTFORMAT_USHORT2);
78
+ }
79
+ if (size == 4) {
80
+ return normalized ? (is_short ? SDL_GPU_VERTEXELEMENTFORMAT_SHORT4_NORM
81
+ : SDL_GPU_VERTEXELEMENTFORMAT_USHORT4_NORM)
82
+ : (is_short ? SDL_GPU_VERTEXELEMENTFORMAT_SHORT4
83
+ : SDL_GPU_VERTEXELEMENTFORMAT_USHORT4);
84
+ }
85
+ return SDL_GPU_VERTEXELEMENTFORMAT_INVALID;
86
+ }
87
+ return SDL_GPU_VERTEXELEMENTFORMAT_INVALID;
88
+ }
89
+
90
+ SDL_GPUVertexElementFormat int_format_of(int size, int type) {
91
+ if (size < 1 || size > 4) return SDL_GPU_VERTEXELEMENTFORMAT_INVALID;
92
+ if (type == GL_INT) {
93
+ switch (size) {
94
+ case 1:
95
+ return SDL_GPU_VERTEXELEMENTFORMAT_INT;
96
+ case 2:
97
+ return SDL_GPU_VERTEXELEMENTFORMAT_INT2;
98
+ case 3:
99
+ return SDL_GPU_VERTEXELEMENTFORMAT_INT3;
100
+ default:
101
+ return SDL_GPU_VERTEXELEMENTFORMAT_INT4;
102
+ }
103
+ }
104
+ if (type == GL_UNSIGNED_INT) {
105
+ switch (size) {
106
+ case 1:
107
+ return SDL_GPU_VERTEXELEMENTFORMAT_UINT;
108
+ case 2:
109
+ return SDL_GPU_VERTEXELEMENTFORMAT_UINT2;
110
+ case 3:
111
+ return SDL_GPU_VERTEXELEMENTFORMAT_UINT3;
112
+ default:
113
+ return SDL_GPU_VERTEXELEMENTFORMAT_UINT4;
114
+ }
115
+ }
116
+ if (type == GL_SHORT && (size == 2 || size == 4)) {
117
+ return size == 2 ? SDL_GPU_VERTEXELEMENTFORMAT_SHORT2 : SDL_GPU_VERTEXELEMENTFORMAT_SHORT4;
118
+ }
119
+ if (type == GL_UNSIGNED_SHORT && (size == 2 || size == 4)) {
120
+ return size == 2 ? SDL_GPU_VERTEXELEMENTFORMAT_USHORT2
121
+ : SDL_GPU_VERTEXELEMENTFORMAT_USHORT4;
122
+ }
123
+ if (type == GL_BYTE && (size == 2 || size == 4)) {
124
+ return size == 2 ? SDL_GPU_VERTEXELEMENTFORMAT_BYTE2 : SDL_GPU_VERTEXELEMENTFORMAT_BYTE4;
125
+ }
126
+ if (type == GL_UNSIGNED_BYTE && (size == 2 || size == 4)) {
127
+ return size == 2 ? SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2 : SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4;
128
+ }
129
+ return SDL_GPU_VERTEXELEMENTFORMAT_INVALID;
130
+ }
131
+
132
+ int type_size(int type) {
133
+ switch (type) {
134
+ case GL_BYTE:
135
+ case GL_UNSIGNED_BYTE:
136
+ return 1;
137
+ case GL_SHORT:
138
+ case GL_UNSIGNED_SHORT:
139
+ return 2;
140
+ default:
141
+ return 4;
142
+ }
143
+ }
144
+
145
+ } // namespace
146
+
147
+ void WebGLContext::draw_state_hash(uint64_t *first, uint64_t *second) const {
148
+ uint64_t h1 = 1469598103934665603ull;
149
+ uint64_t h2 = 0x9e3779b97f4a7c15ull;
150
+ auto mix = [&h1, &h2](uint64_t value) {
151
+ h1 ^= value;
152
+ h1 *= 1099511628211ull;
153
+ h1 ^= h1 >> 32;
154
+ h2 += value + 0x9e3779b97f4a7c15ull;
155
+ h2 = (h2 << 27) | (h2 >> 37);
156
+ h2 *= 0xbf58476d1ce4e5b9ull;
157
+ };
158
+ auto mix_face = [&mix](const WebGLStencilFace &face) {
159
+ /* `ref` is a per-pass dynamic value, not part of the pipeline. */
160
+ mix((uint64_t)(uint32_t)face.func);
161
+ mix(face.value_mask);
162
+ mix(face.write_mask);
163
+ mix((uint64_t)(uint32_t)face.fail);
164
+ mix((uint64_t)(uint32_t)face.zfail);
165
+ mix((uint64_t)(uint32_t)face.zpass);
166
+ };
167
+
168
+ mix(state_.blend ? 1u : 0u);
169
+ mix((uint64_t)(uint32_t)state_.blend_src_rgb);
170
+ mix((uint64_t)(uint32_t)state_.blend_dst_rgb);
171
+ mix((uint64_t)(uint32_t)state_.blend_src_alpha);
172
+ mix((uint64_t)(uint32_t)state_.blend_dst_alpha);
173
+ mix((uint64_t)(uint32_t)state_.blend_eq_rgb);
174
+ mix((uint64_t)(uint32_t)state_.blend_eq_alpha);
175
+ mix(state_.depth_test ? 1u : 0u);
176
+ mix(state_.depth_write ? 1u : 0u);
177
+ mix((uint64_t)(uint32_t)state_.depth_func);
178
+ mix(state_.stencil ? 1u : 0u);
179
+ mix_face(state_.stencil_front);
180
+ mix_face(state_.stencil_back);
181
+ mix(state_.cull ? 1u : 0u);
182
+ mix((uint64_t)(uint32_t)state_.cull_face);
183
+ mix((uint64_t)(uint32_t)state_.front_face);
184
+ for (bool channel : state_.color_mask) mix(channel ? 1u : 0u);
185
+ mix((uint64_t)(uint32_t)state_.color_target_count);
186
+ for (int i = 0; i < state_.color_target_count; i++) {
187
+ mix((uint64_t)(uint32_t)state_.color_formats[i]);
188
+ }
189
+ mix((uint64_t)(uint32_t)state_.depth_format);
190
+
191
+ *first = h1;
192
+ *second = h2;
193
+ }
194
+
195
+ void WebGLContext::clear_draw_cache() {
196
+ draw_setups_.clear();
197
+ last_setup_ = nullptr;
198
+ last_pipeline_ = nullptr;
199
+ last_setup_pass_ = 0;
200
+ }
201
+
202
+ const WebGLDrawSetup *WebGLContext::prepare_draw(const WebGLDrawKey &key) {
203
+ auto found = draw_setups_.find(key);
204
+ if (found != draw_setups_.end()) return &found->second;
205
+ WebGLProgram *program_ = program(key.program);
206
+ if (program_ == nullptr) {
207
+ set_error(GL_INVALID_OPERATION);
208
+ return nullptr;
209
+ }
210
+ /* A page that cycles through many states would otherwise grow the table
211
+ * without bound; clearing only costs one rebuild per live state. */
212
+ if (draw_setups_.size() >= 256) clear_draw_cache();
213
+
214
+ WebGLDrawSetup setup;
215
+ setup.attributes.reserve(program_->draw_attribs.size());
216
+ setup.buffers.reserve(program_->draw_attribs.size());
217
+ setup.bindings.reserve(program_->draw_attribs.size());
218
+
219
+ for (const WebGLDrawAttribPlan &plan : program_->draw_attribs) {
220
+ if (plan.location < 0 || plan.location >= kWebGLMaxAttribs) continue;
221
+ WebGLAttribState &attrib = attribs_[plan.location];
222
+ const int slots = matrix_slots(plan.type);
223
+ const int type_components = components_of_type(plan.type);
224
+ const int components = slots > 1 ? slots : (type_components > 4 ? 4 : type_components);
225
+ const uint32_t slot = (uint32_t)setup.buffers.size();
226
+
227
+ SDL_GPUVertexBufferDescription description;
228
+ memset(&description, 0, sizeof(description));
229
+ description.slot = slot;
230
+ description.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
231
+ SDL_GPUBufferBinding binding;
232
+ memset(&binding, 0, sizeof(binding));
233
+
234
+ if (!attrib.enabled) {
235
+ /* A disabled attribute is a constant read from a persistent 16-byte
236
+ * buffer, not a fresh GPU allocation per draw. */
237
+ SDL_GPUBuffer *constant = constant_attrib_buffer(plan.location);
238
+ if (constant == nullptr) return nullptr;
239
+ binding.buffer = constant;
240
+ description.pitch = 0;
241
+ for (int column = 0; column < slots; column++) {
242
+ SDL_GPUVertexAttribute attribute;
243
+ memset(&attribute, 0, sizeof(attribute));
244
+ attribute.location = (Uint32)(plan.location + column);
245
+ attribute.buffer_slot = slot;
246
+ attribute.format = float_format(components);
247
+ attribute.offset = 0;
248
+ setup.attributes.push_back(attribute);
249
+ }
250
+ setup.buffers.push_back(description);
251
+ setup.bindings.push_back(binding);
252
+ continue;
253
+ }
254
+
255
+ WebGLBuffer *buffer_ = buffer(attrib.buffer);
256
+ if (buffer_ == nullptr || buffer_->data.empty()) {
257
+ set_error(GL_INVALID_OPERATION);
258
+ return nullptr;
259
+ }
260
+ SDL_GPUVertexElementFormat format = attrib.integer
261
+ ? int_format_of(attrib.size, attrib.type)
262
+ : format_of(attrib.size, attrib.type, attrib.normalized);
263
+ if (format == SDL_GPU_VERTEXELEMENTFORMAT_INVALID) {
264
+ set_error(GL_INVALID_OPERATION);
265
+ return nullptr;
266
+ }
267
+ description.input_rate = attrib.divisor > 0 ? SDL_GPU_VERTEXINPUTRATE_INSTANCE
268
+ : SDL_GPU_VERTEXINPUTRATE_VERTEX;
269
+ const int stride =
270
+ attrib.stride > 0 ? attrib.stride : components * type_size(attrib.type) * slots;
271
+ description.pitch = (Uint32)stride;
272
+ SDL_GPUBuffer *gpu = ensure_buffer_gpu(buffer_);
273
+ if (gpu == nullptr) return nullptr;
274
+ binding.buffer = gpu;
275
+ for (int column = 0; column < slots; column++) {
276
+ SDL_GPUVertexAttribute attribute;
277
+ memset(&attribute, 0, sizeof(attribute));
278
+ attribute.location = (Uint32)(plan.location + column);
279
+ attribute.buffer_slot = slot;
280
+ attribute.format = format;
281
+ attribute.offset = (Uint32)(attrib.offset + column * components * type_size(attrib.type));
282
+ setup.attributes.push_back(attribute);
283
+ }
284
+ setup.buffers.push_back(description);
285
+ setup.bindings.push_back(binding);
286
+ }
287
+
288
+ if (key.index_size > 0 && key.element_buffer != 0) {
289
+ WebGLBuffer *element = buffer(key.element_buffer);
290
+ if (element == nullptr || element->data.empty()) {
291
+ set_error(GL_INVALID_OPERATION);
292
+ return nullptr;
293
+ }
294
+ setup.index_buffer = ensure_buffer_gpu(element);
295
+ if (setup.index_buffer == nullptr) return nullptr;
296
+ }
297
+
298
+ /* The pipeline depends on the vertex layout and fixed-function state, not on
299
+ * which GPU buffer happens to hold the data (a buffer can be recreated after
300
+ * a grow) nor on the element-buffer binding. Normalise those fields out so a
301
+ * dynamic buffer does not create a fresh pipeline. */
302
+ WebGLDrawKey pipeline_key = key;
303
+ pipeline_key.gpu_version = 0;
304
+ pipeline_key.element_buffer = 0;
305
+ auto cached = program_->pipeline_cache.find(pipeline_key);
306
+ if (cached != program_->pipeline_cache.end()) {
307
+ setup.pipeline = cached->second;
308
+ } else {
309
+ setup.pipeline = create_pipeline(program_, key.mode, (int)setup.attributes.size(),
310
+ (int)setup.buffers.size(), setup.attributes.data(),
311
+ setup.buffers.data());
312
+ if (setup.pipeline == nullptr) return nullptr;
313
+ program_->pipeline_cache[pipeline_key] = setup.pipeline;
314
+ program_->pipelines.push_back(setup.pipeline);
315
+ }
316
+
317
+ auto inserted = draw_setups_.emplace(key, std::move(setup));
318
+ return &inserted.first->second;
319
+ }
320
+
321
+ } // namespace xtgui
@@ -0,0 +1,130 @@
1
+ /*
2
+ * xbintsc GUI engine — the fixed-function WebGL state a pipeline depends on.
3
+ *
4
+ * Split out of `webgl.h` to keep that header under the repository's
5
+ * file-length budget.
6
+ */
7
+ #ifndef XT_GUI_WEBGL_DRAW_STATE_H
8
+ #define XT_GUI_WEBGL_DRAW_STATE_H
9
+
10
+ #include <cstddef>
11
+ #include <cstdint>
12
+ #include <string>
13
+ #include <vector>
14
+
15
+ #include <SDL3/SDL.h>
16
+
17
+ #include "webgl_constants.h"
18
+
19
+ namespace xtgui {
20
+
21
+ /** One face's stencil test and update state. */
22
+ struct WebGLStencilFace {
23
+ int func = GL_ALWAYS;
24
+ int ref = 0;
25
+ uint32_t value_mask = 0xFFFFFFFFu; // compare mask
26
+ uint32_t write_mask = 0xFFFFFFFFu;
27
+ int fail = GL_KEEP;
28
+ int zfail = GL_KEEP;
29
+ int zpass = GL_KEEP;
30
+ };
31
+
32
+ /** The fixed-function state a pipeline depends on. */
33
+ struct WebGLDrawState {
34
+ bool blend = false;
35
+ int blend_src_rgb = GL_ONE;
36
+ int blend_dst_rgb = GL_ZERO;
37
+ int blend_src_alpha = GL_ONE;
38
+ int blend_dst_alpha = GL_ZERO;
39
+ int blend_eq_rgb = GL_FUNC_ADD;
40
+ int blend_eq_alpha = GL_FUNC_ADD;
41
+ bool depth_test = false;
42
+ bool depth_write = true;
43
+ int depth_func = GL_LESS;
44
+ bool stencil = false;
45
+ WebGLStencilFace stencil_front;
46
+ WebGLStencilFace stencil_back;
47
+ bool cull = false;
48
+ int cull_face = GL_BACK;
49
+ int front_face = GL_CCW;
50
+ bool color_mask[4] = {true, true, true, true};
51
+ /** Attachment formats of the bound target; pipelines are format-specific. */
52
+ int color_formats[4] = {
53
+ SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
54
+ SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM};
55
+ int color_target_count = 1;
56
+ int depth_format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT;
57
+ };
58
+
59
+ /**
60
+ * Identity of one prepared draw. Everything a draw's pipeline, vertex layout
61
+ * and GPU buffer handles depend on is either an exact integer here or folds
62
+ * into `state_hash`/`state_hash2` (a 128-bit hash of `WebGLDrawState`). Two
63
+ * independent hashes keep a collision in the same place a 128-bit hash would
64
+ * have it — the state struct has no padding-sensitive fields to compare.
65
+ */
66
+ struct WebGLDrawKey {
67
+ uint32_t program = 0;
68
+ int mode = 0; // GL primitive mode
69
+ int index_size = 0; // 0 = drawArrays, otherwise index element bytes
70
+ uint32_t element_buffer = 0; // bound GL_ELEMENT_ARRAY_BUFFER, 0 = none
71
+ uint64_t attribs_version = 0;
72
+ uint64_t gpu_version = 0;
73
+ uint64_t framebuffer_version = 0;
74
+ uint64_t state_hash = 0;
75
+ uint64_t state_hash2 = 0;
76
+
77
+ bool operator==(const WebGLDrawKey &other) const {
78
+ return program == other.program && mode == other.mode && index_size == other.index_size &&
79
+ element_buffer == other.element_buffer && attribs_version == other.attribs_version &&
80
+ gpu_version == other.gpu_version &&
81
+ framebuffer_version == other.framebuffer_version && state_hash == other.state_hash &&
82
+ state_hash2 == other.state_hash2;
83
+ }
84
+ };
85
+
86
+ struct WebGLDrawKeyHash {
87
+ size_t operator()(const WebGLDrawKey &key) const {
88
+ uint64_t hash = 1469598103934665603ull;
89
+ auto mix = [&hash](uint64_t value) {
90
+ hash ^= value;
91
+ hash *= 1099511628211ull;
92
+ hash ^= hash >> 29;
93
+ };
94
+ mix(key.program);
95
+ mix((uint64_t)(uint32_t)key.mode);
96
+ mix((uint64_t)(uint32_t)key.index_size);
97
+ mix(key.element_buffer);
98
+ mix(key.attribs_version);
99
+ mix(key.gpu_version);
100
+ mix(key.framebuffer_version);
101
+ mix(key.state_hash);
102
+ mix(key.state_hash2);
103
+ return (size_t)hash;
104
+ }
105
+ };
106
+
107
+ /**
108
+ * One active vertex attribute, resolved once at link time: `location` is the
109
+ * base attribute location and `type` the GLSL type name the draw needs to size
110
+ * the vertex layout. Keeping this out of the draw path removes the per-draw
111
+ * `attribs` scan and the `name -> location` string hash lookup.
112
+ */
113
+ struct WebGLDrawAttribPlan {
114
+ int location = -1;
115
+ std::string type;
116
+ };
117
+
118
+ /** A pipeline and the vertex-layout/binding set the draw prepared for it. A
119
+ * cache hit is a bind (when it changed) and a draw. */
120
+ struct WebGLDrawSetup {
121
+ SDL_GPUGraphicsPipeline *pipeline = nullptr;
122
+ std::vector<SDL_GPUVertexAttribute> attributes;
123
+ std::vector<SDL_GPUVertexBufferDescription> buffers;
124
+ std::vector<SDL_GPUBufferBinding> bindings;
125
+ SDL_GPUBuffer *index_buffer = nullptr;
126
+ };
127
+
128
+ } // namespace xtgui
129
+
130
+ #endif /* XT_GUI_WEBGL_DRAW_STATE_H */
@@ -425,14 +425,9 @@ void scan_glsl(const std::string &source, bool fragment, GlslShaderInfo *info) {
425
425
  info->legacy_fragment_output = true;
426
426
  continue;
427
427
  }
428
- /* SDL_GPU render targets carry no writable depth attachment, so there is
429
- * no place for a shader-written depth value; reject it with a clear log
430
- * instead of failing later inside glslang. */
431
- if (inactive == 0 && fragment && token.kind == Token::Ident &&
432
- (token.text == "gl_FragDepth" || token.text == "gl_FragDepthEXT")) {
433
- info->unsupported = "gl_FragDepth is not supported";
434
- continue;
435
- }
428
+ /* `gl_FragDepth`/`gl_FragDepthEXT`: the Vulkan SPIR-V path emits
429
+ * `DepthReplacing`, and SPIRV-Cross turns that into `[[depth(any)]]` for
430
+ * Metal, so a fragment shader may write the depth attachment directly. */
436
431
  /* Declarations inside a definitively false preprocessor branch never reach
437
432
  * the compiler; rewriting them would move their types/macros (and array
438
433
  * sizes such as `MORPHTARGETS_COUNT`) outside that branch. */
@@ -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