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
@@ -64,9 +64,71 @@ std::string normalizePath(const std::string &input) {
64
64
  return out;
65
65
  }
66
66
 
67
+ /** Decode a `data:` URL's base64 payload (the only encoding the engine emits
68
+ * and the common one a page assigns). */
69
+ bool decodeDataUrl(const std::string &url, std::vector<unsigned char> *out) {
70
+ const size_t comma = url.find(',');
71
+ if (comma == std::string::npos) return false;
72
+ const std::string header = url.substr(0, comma);
73
+ if (header.find(";base64") == std::string::npos) return false;
74
+ static const signed char kDecode[128] = {
75
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
76
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
77
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
78
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
79
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
80
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
81
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
82
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1};
83
+ out->clear();
84
+ out->reserve((url.size() - comma) * 3 / 4 + 4);
85
+ uint32_t accumulator = 0;
86
+ int bits = 0;
87
+ for (size_t i = comma + 1; i < url.size(); i++) {
88
+ const unsigned char c = (unsigned char)url[i];
89
+ if (c == '=' || c == '\n' || c == '\r' || c == ' ') continue;
90
+ if (c >= 128) return false;
91
+ const int value = kDecode[c];
92
+ if (value < 0) return false;
93
+ accumulator = (accumulator << 6) | (uint32_t)value;
94
+ bits += 6;
95
+ if (bits >= 8) {
96
+ bits -= 8;
97
+ out->push_back((unsigned char)((accumulator >> bits) & 0xFF));
98
+ }
99
+ }
100
+ return !out->empty();
101
+ }
102
+
67
103
  } // namespace
68
104
 
69
105
  const Image *xt_image_load(const std::string &raw_path, int target_width, int target_height) {
106
+ /* A `data:` URL carries its bytes inline (a canvas used as a CSS
107
+ * `background-image` reaches this path through `toDataURL()`). */
108
+ if (raw_path.rfind("data:", 0) == 0) {
109
+ auto &cache = imageCache();
110
+ auto found = cache.find(raw_path);
111
+ if (found != cache.end()) {
112
+ return found->second.width > 0 ? &found->second : nullptr;
113
+ }
114
+ Image image;
115
+ std::vector<unsigned char> bytes;
116
+ if (decodeDataUrl(raw_path, &bytes)) {
117
+ int width = 0;
118
+ int height = 0;
119
+ int channels = 0;
120
+ unsigned char *data = stbi_load_from_memory(bytes.data(), (int)bytes.size(), &width, &height,
121
+ &channels, 4);
122
+ if (data != nullptr) {
123
+ image.width = width;
124
+ image.height = height;
125
+ image.pixels.assign(data, data + (size_t)width * (size_t)height * 4);
126
+ stbi_image_free(data);
127
+ }
128
+ }
129
+ auto inserted = cache.emplace(raw_path, std::move(image));
130
+ return inserted.first->second.width > 0 ? &inserted.first->second : nullptr;
131
+ }
70
132
  std::string path = normalizePath(raw_path);
71
133
  bool svg = xt_svg_is_svg(path);
72
134
  /* Raster formats always decode at their intrinsic resolution, so their cache
@@ -102,6 +164,13 @@ const Image *xt_image_load(const std::string &raw_path, int target_width, int ta
102
164
  }
103
165
 
104
166
  bool xt_image_size(const std::string &raw_path, int *width, int *height) {
167
+ if (raw_path.rfind("data:", 0) == 0) {
168
+ const Image *image = xt_image_load(raw_path);
169
+ if (image == nullptr) return false;
170
+ if (width != nullptr) *width = image->width;
171
+ if (height != nullptr) *height = image->height;
172
+ return true;
173
+ }
105
174
  std::string path = normalizePath(raw_path);
106
175
  if (xt_svg_is_svg(path)) return xt_svg_size(path, width, height);
107
176
  auto &cache = sizeCache();
@@ -15,6 +15,7 @@
15
15
  #include <vector>
16
16
 
17
17
  #include "image_bitmap.h"
18
+ #include "canvas.h"
18
19
  #include "webgl.h"
19
20
 
20
21
  namespace {
@@ -47,6 +48,16 @@ bool source_pixels(xt_value source, xtgui::Image *out) {
47
48
  return true;
48
49
  }
49
50
 
51
+ if (XT_IS_OBJECT(source)) {
52
+ xt_value canvas_id = xt_object_get_cstr(source, "__xt_gui_canvas");
53
+ if (XT_IS_NUMBER(canvas_id)) {
54
+ xtgui::Canvas2D *canvas = xtgui::xt_canvas_by_id((uint64_t)xt_to_number(canvas_id));
55
+ if (canvas == nullptr || canvas->image().width <= 0) return false;
56
+ *out = canvas->image();
57
+ return true;
58
+ }
59
+ }
60
+
50
61
  XtGuiWindow *win = nullptr;
51
62
  xtgui::Node *node = domapi::resolve_node(source, &win);
52
63
  if (node == nullptr || !node->isElement()) return false;
@@ -20,6 +20,7 @@
20
20
 
21
21
  #include "image.h"
22
22
  #include "inline_svg.h"
23
+ #include "perf.h"
23
24
  #include "style_internal.h"
24
25
 
25
26
  namespace xtgui {
@@ -172,6 +173,8 @@ std::string dumpBox(const LayoutBox *box, int depth) {
172
173
  void LayoutTree::compute(const Node *root, const std::unordered_map<const Node *, XtStyle> &styles,
173
174
  float viewportWidth, float viewportHeight, const MediaSizeMap *media,
174
175
  const CanvasInfoMap *canvases) {
176
+ PerfScope perf_scope(&g_perf_frame_ms.layout);
177
+ g_perf_frame.layouts += 1;
175
178
  root_.reset();
176
179
  index_.clear();
177
180
  root_style_ = xt_style_initial();
@@ -214,6 +217,7 @@ void LayoutTree::compute(const Node *root, const std::unordered_map<const Node *
214
217
  const LayoutBox *box = stack.back();
215
218
  stack.pop_back();
216
219
  if (box->node != nullptr) index_[box->node] = box;
220
+ g_perf_frame.layoutBoxes += 1;
217
221
  for (const std::unique_ptr<LayoutBox> &child : box->children) stack.push_back(child.get());
218
222
  }
219
223
  }
@@ -101,7 +101,26 @@ PaintClip clipForChildren(const LayoutBox *box, const PaintClip &parent) {
101
101
  void paintChildren(const LayoutBox *box, DisplayList &out, const PaintClip &clip,
102
102
  const paint_detail::FilterChain &chain, float viewportWidth,
103
103
  float viewportHeight) {
104
+ /* Fast path: with no stacking child the painter order *is* document order, so
105
+ * skip the temporary vector and the sort (they used to be allocated and run
106
+ * for every parent, every repaint). */
107
+ bool stacked = false;
108
+ for (const std::unique_ptr<LayoutBox> &child : box->children) {
109
+ if (child->display == Display::None || child->style == nullptr) continue;
110
+ if (zIndexOf(child.get()) != 0) {
111
+ stacked = true;
112
+ break;
113
+ }
114
+ }
115
+ if (!stacked) {
116
+ for (const std::unique_ptr<LayoutBox> &child : box->children) {
117
+ if (child->display == Display::None || child->style == nullptr) continue;
118
+ paintBox(child.get(), out, clip, chain, viewportWidth, viewportHeight);
119
+ }
120
+ return;
121
+ }
104
122
  std::vector<const LayoutBox *> children;
123
+ children.reserve(box->children.size());
105
124
  for (const std::unique_ptr<LayoutBox> &child : box->children) {
106
125
  if (child->display == Display::None || child->style == nullptr) continue;
107
126
  children.push_back(child.get());
@@ -200,6 +200,21 @@ void paintRadial(const LayoutBox &box, const XtStyle &style, const BackgroundLay
200
200
  addEllipse(out, cx, cy, 0.6f, 0.6f, center, clip, box.world);
201
201
  }
202
202
 
203
+ void paintImage(const LayoutBox &box, const BackgroundLayer &layer, const FilterChain &chain,
204
+ const PaintClip &clip, DisplayList &out) {
205
+ if (layer.image_src.empty() || box.width <= 0.0f || box.height <= 0.0f) return;
206
+ /* A `background-image: url(...)` layer fills the box. (CSS `background-size`
207
+ * is not modelled, so the intrinsic-size/tiling default becomes a stretch.) */
208
+ PaintImage image;
209
+ image.x = box.x;
210
+ image.y = box.y;
211
+ image.width = box.width;
212
+ image.height = box.height;
213
+ image.src = layer.image_src;
214
+ image.transform = box.world;
215
+ addImage(out, image, clip, chain);
216
+ }
217
+
203
218
  } // namespace
204
219
 
205
220
  void paintBackgrounds(DisplayList &out, const LayoutBox &box, const XtStyle &style,
@@ -219,8 +234,10 @@ void paintBackgrounds(DisplayList &out, const LayoutBox &box, const XtStyle &sty
219
234
  const BackgroundLayer &layer = style.background_layers[i];
220
235
  if (layer.kind == BackgroundKind::Linear) {
221
236
  paintLinear(box, layer, chain, layerClip, out);
222
- } else {
237
+ } else if (layer.kind == BackgroundKind::Radial) {
223
238
  paintRadial(box, style, layer, chain, layerClip, viewportWidth, viewportHeight, out);
239
+ } else {
240
+ paintImage(box, layer, chain, layerClip, out);
224
241
  }
225
242
  }
226
243
  }
@@ -0,0 +1,171 @@
1
+ /*
2
+ * xbintsc GUI engine — frame timing implementation (see `perf.h`).
3
+ */
4
+
5
+ #include "perf.h"
6
+
7
+ #include <SDL3/SDL.h>
8
+
9
+ #include <cstdio>
10
+ #include <cstdlib>
11
+ #include <cstring>
12
+
13
+ namespace xtgui {
14
+
15
+ PerfCounters g_perf_frame;
16
+ PerfCounters g_perf_total;
17
+ PerfTimings g_perf_frame_ms;
18
+ PerfTimings g_perf_last;
19
+
20
+ namespace {
21
+
22
+ int g_perf_mode = -1; // -1 = not read yet, 0 = disabled, N = every N frames
23
+ uint64_t g_frame_start = 0;
24
+ /** Accumulator for `XT_GUI_PERF=<N>` (N > 1). */
25
+ PerfTimings g_agg_ms;
26
+ PerfCounters g_agg_counters;
27
+ uint64_t g_agg_frames = 0;
28
+
29
+ void readMode() {
30
+ if (g_perf_mode >= 0) return;
31
+ const char *value = SDL_getenv("XT_GUI_PERF");
32
+ if (value == nullptr || value[0] == '\0' || std::strcmp(value, "0") == 0) {
33
+ g_perf_mode = 0;
34
+ return;
35
+ }
36
+ int every = std::atoi(value);
37
+ g_perf_mode = every > 1 ? every : 1;
38
+ }
39
+
40
+ void addCounters(PerfCounters *target, const PerfCounters &value) {
41
+ target->frames += value.frames;
42
+ target->presents += value.presents;
43
+ target->exposes += value.exposes;
44
+ target->deferred += value.deferred;
45
+ target->starvedFrames += value.starvedFrames;
46
+ target->skippedFrames += value.skippedFrames;
47
+ target->restyles += value.restyles;
48
+ target->restyleNodes += value.restyleNodes;
49
+ target->rulesEvaluated += value.rulesEvaluated;
50
+ target->mediaEvaluated += value.mediaEvaluated;
51
+ target->layouts += value.layouts;
52
+ target->layoutBoxes += value.layoutBoxes;
53
+ target->textShapeCalls += value.textShapeCalls;
54
+ target->textShapeHits += value.textShapeHits;
55
+ target->paints += value.paints;
56
+ target->paintOps += value.paintOps;
57
+ target->drawRuns += value.drawRuns;
58
+ target->drawCalls += value.drawCalls;
59
+ target->uploadSubmits += value.uploadSubmits;
60
+ target->vertices += value.vertices;
61
+ target->syncNodes += value.syncNodes;
62
+ target->transitionFrames += value.transitionFrames;
63
+ target->canvasDraws += value.canvasDraws;
64
+ target->canvasUploads += value.canvasUploads;
65
+ target->canvasUploadBytes += value.canvasUploadBytes;
66
+ target->webglDraws += value.webglDraws;
67
+ target->webglPasses += value.webglPasses;
68
+ target->webglPipelineMisses += value.webglPipelineMisses;
69
+ target->webglBufferUploadBytes += value.webglBufferUploadBytes;
70
+ }
71
+
72
+ void addTimings(PerfTimings *target, const PerfTimings &value) {
73
+ target->frame += value.frame;
74
+ target->restyle += value.restyle;
75
+ target->layout += value.layout;
76
+ target->paint += value.paint;
77
+ target->upload += value.upload;
78
+ target->draw += value.draw;
79
+ }
80
+
81
+ void printLine(uint64_t frames, const PerfTimings &ms, const PerfCounters &counters) {
82
+ double divisor = frames > 0 ? (double)frames : 1.0;
83
+ std::fprintf(stderr,
84
+ "xt_gui perf frames=%llu ms=%.3f restyle=%.3f layout=%.3f paint=%.3f "
85
+ "upload=%.3f draw=%.3f restyles=%llu nodes=%llu rules=%llu media=%llu "
86
+ "layouts=%llu boxes=%llu text=%llu textHit=%llu ops=%llu runs=%llu "
87
+ "draws=%llu uploads=%llu verts=%llu sync=%llu presents=%llu exposes=%llu "
88
+ "deferred=%llu starved=%llu skip=%llu "
89
+ "cdraw=%llu cups=%llu cbytes=%llu gldraw=%llu passes=%llu gmiss=%llu "
90
+ "gbytes=%llu\n",
91
+ (unsigned long long)frames, ms.frame / divisor, ms.restyle / divisor,
92
+ ms.layout / divisor, ms.paint / divisor, ms.upload / divisor, ms.draw / divisor,
93
+ (unsigned long long)counters.restyles, (unsigned long long)counters.restyleNodes,
94
+ (unsigned long long)counters.rulesEvaluated,
95
+ (unsigned long long)counters.mediaEvaluated, (unsigned long long)counters.layouts,
96
+ (unsigned long long)counters.layoutBoxes,
97
+ (unsigned long long)counters.textShapeCalls,
98
+ (unsigned long long)counters.textShapeHits, (unsigned long long)counters.paintOps,
99
+ (unsigned long long)counters.drawRuns, (unsigned long long)counters.drawCalls,
100
+ (unsigned long long)counters.uploadSubmits, (unsigned long long)counters.vertices,
101
+ (unsigned long long)counters.syncNodes, (unsigned long long)counters.presents,
102
+ (unsigned long long)counters.exposes,
103
+ (unsigned long long)counters.deferred,
104
+ (unsigned long long)counters.starvedFrames,
105
+ (unsigned long long)counters.skippedFrames,
106
+ (unsigned long long)counters.canvasDraws,
107
+ (unsigned long long)counters.canvasUploads,
108
+ (unsigned long long)counters.canvasUploadBytes,
109
+ (unsigned long long)counters.webglDraws,
110
+ (unsigned long long)counters.webglPasses,
111
+ (unsigned long long)counters.webglPipelineMisses,
112
+ (unsigned long long)counters.webglBufferUploadBytes);
113
+ }
114
+
115
+ } // namespace
116
+
117
+ bool xt_perf_active() {
118
+ readMode();
119
+ return g_perf_mode != 0;
120
+ }
121
+
122
+ uint64_t xt_perf_counter() { return SDL_GetPerformanceCounter(); }
123
+
124
+ double xt_perf_ms_since(uint64_t start) {
125
+ uint64_t frequency = SDL_GetPerformanceFrequency();
126
+ if (frequency == 0 || start == 0) return 0.0;
127
+ return (double)(SDL_GetPerformanceCounter() - start) * 1000.0 / (double)frequency;
128
+ }
129
+
130
+ PerfScope::PerfScope(double *slot) {
131
+ /* Always time the phases: five counter pairs per frame are free, and
132
+ * `win.stats()` is then useful without the environment variable (which only
133
+ * controls printing). */
134
+ slot_ = slot;
135
+ if (slot_ != nullptr) start_ = SDL_GetPerformanceCounter();
136
+ }
137
+
138
+ PerfScope::~PerfScope() {
139
+ if (slot_ == nullptr) return;
140
+ *slot_ += xt_perf_ms_since(start_);
141
+ }
142
+
143
+ void xt_perf_frame_begin() {
144
+ g_perf_frame = PerfCounters();
145
+ g_perf_frame_ms = PerfTimings();
146
+ g_frame_start = SDL_GetPerformanceCounter();
147
+ }
148
+
149
+ void xt_perf_frame_end() {
150
+ g_perf_frame_ms.frame = xt_perf_ms_since(g_frame_start);
151
+ g_perf_last = g_perf_frame_ms;
152
+ g_perf_frame.frames = 1;
153
+ addCounters(&g_perf_total, g_perf_frame);
154
+
155
+ readMode();
156
+ if (g_perf_mode == 0) return;
157
+ if (g_perf_mode == 1) {
158
+ printLine(1, g_perf_frame_ms, g_perf_frame);
159
+ return;
160
+ }
161
+ addTimings(&g_agg_ms, g_perf_frame_ms);
162
+ addCounters(&g_agg_counters, g_perf_frame);
163
+ g_agg_frames += 1;
164
+ if (g_agg_frames < (uint64_t)g_perf_mode) return;
165
+ printLine(g_agg_frames, g_agg_ms, g_agg_counters);
166
+ g_agg_ms = PerfTimings();
167
+ g_agg_counters = PerfCounters();
168
+ g_agg_frames = 0;
169
+ }
170
+
171
+ } // namespace xtgui
@@ -0,0 +1,127 @@
1
+ /*
2
+ * xbintsc GUI engine — frame counters and phase timings.
3
+ *
4
+ * The engine is a single-threaded UI loop, so one process-wide record is
5
+ * enough. `xt_gui_render_window` brackets every frame with
6
+ * `xt_perf_frame_begin()` / `xt_perf_frame_end()`; the rest of the engine only
7
+ * bumps counters or wraps a phase in `PerfScope`. `win.stats()` exposes the
8
+ * last frame plus the cumulative totals, which is what
9
+ * `tests/e2e/gui-perf.test.ts` asserts on (wall-clock times are informational
10
+ * only, so the suite also runs on a software GPU in CI).
11
+ *
12
+ * Set `XT_GUI_PERF=1` to print one line per frame, or `XT_GUI_PERF=<N>` to
13
+ * print an aggregated line every N frames (average of the timings, sums of the
14
+ * counters). The counters and phase timings are always recorded (a few counter
15
+ * reads per frame), so `win.stats()` is useful without the variable; the
16
+ * environment variable only controls the stderr printing.
17
+ */
18
+ #ifndef XT_GUI_PERF_H
19
+ #define XT_GUI_PERF_H
20
+
21
+ #include <cstdint>
22
+
23
+ namespace xtgui {
24
+
25
+ /** Counters for one frame; the same layout is reused for cumulative totals. */
26
+ struct PerfCounters {
27
+ uint64_t frames = 0;
28
+ uint64_t presents = 0;
29
+ /** Presents the *window system* asked for (a map/expose/restore event), not
30
+ * the page. They are real work — the compositor lost the contents — but a
31
+ * quiet page must not be blamed for them: a window server sends its first
32
+ * `Expose` whenever it gets around to mapping the window, which on a busy CI
33
+ * runner can land long after the first frames. At most one is charged per
34
+ * frame (the events coalesce into a single present), so
35
+ * `presents - exposes - deferred` counts the presents only the page asked
36
+ * for. */
37
+ uint64_t exposes = 0;
38
+ /** Presents a starved frame had to defer: no swapchain image was available
39
+ * then (the GPU still held it, or the window was minimized), so the frame's
40
+ * paint was presented on a later frame. Those presents replay work built in
41
+ * an earlier frame, so they are not this frame's page work either. */
42
+ uint64_t deferred = 0;
43
+ /** Frames that asked to present and got no drawable target at all: SDL's
44
+ * non-blocking swapchain acquire had no image (the GPU still held it) or the
45
+ * device gave no command buffer. The invalidation stays pending (see
46
+ * `deferred`), so the content is presented on a later frame rather than lost;
47
+ * a climbing value means the compositor keeps refusing images and the window
48
+ * is not being updated. */
49
+ uint64_t starvedFrames = 0;
50
+ uint64_t skippedFrames = 0;
51
+ uint64_t restyles = 0;
52
+ uint64_t restyleNodes = 0;
53
+ uint64_t rulesEvaluated = 0;
54
+ uint64_t mediaEvaluated = 0;
55
+ uint64_t layouts = 0;
56
+ uint64_t layoutBoxes = 0;
57
+ uint64_t textShapeCalls = 0;
58
+ uint64_t textShapeHits = 0;
59
+ uint64_t paints = 0;
60
+ uint64_t paintOps = 0;
61
+ uint64_t drawRuns = 0;
62
+ uint64_t drawCalls = 0;
63
+ uint64_t uploadSubmits = 0;
64
+ uint64_t vertices = 0;
65
+ uint64_t syncNodes = 0;
66
+ uint64_t transitionFrames = 0;
67
+ /* Canvas 2D: CPU draws, texture uploads and the bytes they move. A canvas
68
+ * that is only redrawn in a small region should show a small `bytes` count. */
69
+ uint64_t canvasDraws = 0;
70
+ uint64_t canvasUploads = 0;
71
+ uint64_t canvasUploadBytes = 0;
72
+ /* WebGL: draw calls, render passes actually opened (a draw that has nothing
73
+ * to upload must not open a new pass), pipeline-cache misses and the bytes
74
+ * buffer updates copy to the GPU. */
75
+ uint64_t webglDraws = 0;
76
+ uint64_t webglPasses = 0;
77
+ uint64_t webglPipelineMisses = 0;
78
+ uint64_t webglBufferUploadBytes = 0;
79
+ };
80
+
81
+ /** Wall-clock phase timings in milliseconds. */
82
+ struct PerfTimings {
83
+ double frame = 0;
84
+ double restyle = 0;
85
+ double layout = 0;
86
+ double paint = 0;
87
+ double upload = 0;
88
+ double draw = 0;
89
+ };
90
+
91
+ /** The frame being recorded; reset by `xt_perf_frame_begin`. */
92
+ extern PerfCounters g_perf_frame;
93
+ /** Every frame since startup. */
94
+ extern PerfCounters g_perf_total;
95
+ /** Phase timings of the frame being recorded. */
96
+ extern PerfTimings g_perf_frame_ms;
97
+ /** Phase timings of the last completed frame. */
98
+ extern PerfTimings g_perf_last;
99
+
100
+ /** True when `XT_GUI_PERF` asks for timings/printing. Cached after first use. */
101
+ bool xt_perf_active();
102
+
103
+ /** Raw performance counter, for `PerfScope` / ad-hoc measurements. */
104
+ uint64_t xt_perf_counter();
105
+ /** Milliseconds elapsed since `start` (from `xt_perf_counter`). */
106
+ double xt_perf_ms_since(uint64_t start);
107
+
108
+ void xt_perf_frame_begin();
109
+ void xt_perf_frame_end();
110
+
111
+ /** Accumulate elapsed milliseconds into `*slot` while in scope. A no-op (beyond
112
+ * one branch) when `XT_GUI_PERF` is unset. */
113
+ class PerfScope {
114
+ public:
115
+ explicit PerfScope(double *slot);
116
+ ~PerfScope();
117
+ PerfScope(const PerfScope &) = delete;
118
+ PerfScope &operator=(const PerfScope &) = delete;
119
+
120
+ private:
121
+ double *slot_ = nullptr;
122
+ uint64_t start_ = 0;
123
+ };
124
+
125
+ } // namespace xtgui
126
+
127
+ #endif // XT_GUI_PERF_H