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
@@ -12,6 +12,7 @@
12
12
  #include "canvas.h"
13
13
  #include "image.h"
14
14
  #include "media.h"
15
+ #include "perf.h"
15
16
  #include "svg.h"
16
17
  #include "webgl.h"
17
18
 
@@ -27,61 +28,98 @@ struct MediaTexture {
27
28
  int width = 0;
28
29
  int height = 0;
29
30
  uint64_t generation = ~0ull;
31
+ /** Staging transfer buffer reused by every upload into this texture. */
32
+ SDL_GPUTransferBuffer *staging = nullptr;
33
+ uint32_t staging_capacity = 0;
30
34
  };
31
35
 
32
36
  static std::unordered_map<uint64_t, MediaTexture> g_media_textures;
33
37
 
34
38
  /** One live `<canvas>` texture, keyed by the canvas id from `canvas.h`. It is
35
- * re-uploaded in place whenever the canvas bumps its `generation`. */
39
+ * re-uploaded in place whenever the canvas bumps its `generation`; only the
40
+ * canvas's dirty rectangle is copied. */
36
41
  static std::unordered_map<uint64_t, MediaTexture> g_canvas_textures;
37
42
 
38
- /** Upload RGBA8 `pixels` into `texture` through a staging buffer, submitting
39
- * its own command buffer. Because command buffers run in submission order and
40
- * this one is submitted before the frame that samples the texture, re-uploading
41
- * a texture that the previous frame drew is safe. */
42
- static bool uploadPixels(SDL_GPUDevice *device, SDL_GPUTexture *texture, int width, int height,
43
- const void *pixels, uint32_t bytes) {
43
+ static void releaseStaging(SDL_GPUDevice *device, MediaTexture &entry) {
44
+ if (entry.staging != nullptr) SDL_ReleaseGPUTransferBuffer(device, entry.staging);
45
+ entry.staging = nullptr;
46
+ entry.staging_capacity = 0;
47
+ }
48
+
49
+ /** Upload the device-space rectangle `(x, y, w, h)` of an RGBA8 image into
50
+ * `texture`, through `entry`'s reused staging buffer. Because command buffers
51
+ * run in submission order and this copy is submitted before the frame that
52
+ * samples the texture, re-uploading a texture the previous frame drew is
53
+ * safe. With a batch the copy joins the frame's upload command buffer. */
54
+ static bool uploadPixels(SDL_GPUDevice *device, MediaTexture &entry, const void *pixels,
55
+ int image_width, int x, int y, int w, int h,
56
+ XtGuiUploadBatch *batch) {
57
+ if (entry.texture == nullptr || w <= 0 || h <= 0) return false;
58
+ const uint32_t bytes = (uint32_t)w * (uint32_t)h * 4u;
44
59
  if (bytes == 0) return false;
45
- SDL_GPUTransferBufferCreateInfo transfer_info;
46
- memset(&transfer_info, 0, sizeof(transfer_info));
47
- transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
48
- transfer_info.size = bytes;
49
- SDL_GPUTransferBuffer *transfer = SDL_CreateGPUTransferBuffer(device, &transfer_info);
50
- if (transfer == nullptr) return false;
51
- void *mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
52
- std::memcpy(mapped, pixels, bytes);
53
- SDL_UnmapGPUTransferBuffer(device, transfer);
54
-
55
- SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(device);
56
- if (cmd == nullptr) {
57
- SDL_ReleaseGPUTransferBuffer(device, transfer);
58
- return false;
60
+ if (entry.staging == nullptr || entry.staging_capacity < bytes) {
61
+ releaseStaging(device, entry);
62
+ SDL_GPUTransferBufferCreateInfo transfer_info;
63
+ memset(&transfer_info, 0, sizeof(transfer_info));
64
+ transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
65
+ transfer_info.size = bytes;
66
+ entry.staging = SDL_CreateGPUTransferBuffer(device, &transfer_info);
67
+ entry.staging_capacity = entry.staging != nullptr ? bytes : 0;
68
+ if (entry.staging == nullptr) return false;
69
+ }
70
+ /* `cycle` rotates SDL's internal allocation while the previous frame's copy
71
+ * may still be in flight. */
72
+ void *mapped = SDL_MapGPUTransferBuffer(device, entry.staging, true);
73
+ if (mapped == nullptr) return false;
74
+ const unsigned char *source = (const unsigned char *)pixels;
75
+ unsigned char *destination = (unsigned char *)mapped;
76
+ for (int row = 0; row < h; row++) {
77
+ memcpy(destination + (size_t)row * (size_t)w * 4,
78
+ source + ((size_t)(y + row) * (size_t)image_width + (size_t)x) * 4,
79
+ (size_t)w * 4);
59
80
  }
60
- SDL_GPUTextureTransferInfo source;
61
- memset(&source, 0, sizeof(source));
62
- source.transfer_buffer = transfer;
63
- source.offset = 0;
64
- source.pixels_per_row = (Uint32)width;
65
- source.rows_per_layer = (Uint32)height;
66
- SDL_GPUTextureRegion destination;
67
- memset(&destination, 0, sizeof(destination));
68
- destination.texture = texture;
69
- destination.mip_level = 0;
70
- destination.layer = 0;
71
- destination.w = (Uint32)width;
72
- destination.h = (Uint32)height;
73
- destination.d = 1;
81
+ SDL_UnmapGPUTransferBuffer(device, entry.staging);
82
+
83
+ SDL_GPUCommandBuffer *cmd = xtgui::renderer_detail::uploadCommand(device, batch);
84
+ if (cmd == nullptr) return false;
85
+ SDL_GPUTextureTransferInfo source_info;
86
+ memset(&source_info, 0, sizeof(source_info));
87
+ source_info.transfer_buffer = entry.staging;
88
+ source_info.offset = 0;
89
+ source_info.pixels_per_row = (Uint32)w;
90
+ source_info.rows_per_layer = (Uint32)h;
91
+ SDL_GPUTextureRegion destination_region;
92
+ memset(&destination_region, 0, sizeof(destination_region));
93
+ destination_region.texture = entry.texture;
94
+ destination_region.x = (Uint32)x;
95
+ destination_region.y = (Uint32)y;
96
+ destination_region.mip_level = 0;
97
+ destination_region.layer = 0;
98
+ destination_region.w = (Uint32)w;
99
+ destination_region.h = (Uint32)h;
100
+ destination_region.d = 1;
74
101
  SDL_GPUCopyPass *copy = SDL_BeginGPUCopyPass(cmd);
75
- SDL_UploadToGPUTexture(copy, &source, &destination, false);
102
+ SDL_UploadToGPUTexture(copy, &source_info, &destination_region, false);
76
103
  SDL_EndGPUCopyPass(copy);
77
- SDL_SubmitGPUCommandBuffer(cmd);
78
- SDL_ReleaseGPUTransferBuffer(device, transfer);
104
+ xtgui::renderer_detail::uploadDone(cmd, batch);
79
105
  return true;
80
106
  }
81
107
 
108
+ /** One-shot full-texture upload (image/SVG decode), used when there is no
109
+ * long-lived texture entry to keep a staging buffer on. */
110
+ static bool uploadWholeTexture(SDL_GPUDevice *device, SDL_GPUTexture *texture, int width,
111
+ int height, const void *pixels, XtGuiUploadBatch *batch) {
112
+ MediaTexture temporary;
113
+ temporary.texture = texture;
114
+ const bool ok = uploadPixels(device, temporary, pixels, width, 0, 0, width, height, batch);
115
+ releaseStaging(device, temporary);
116
+ return ok;
117
+ }
118
+
82
119
  /** Ensure the texture for `media_id` exists and holds the decoder's newest
83
120
  * frame. Returns `nullptr` before the first frame is decoded. */
84
- static SDL_GPUTexture *ensureMediaTexture(SDL_GPUDevice *device, uint64_t media_id) {
121
+ static SDL_GPUTexture *ensureMediaTexture(SDL_GPUDevice *device, uint64_t media_id,
122
+ XtGuiUploadBatch *batch) {
85
123
  xtgui::MediaPlayer *player = xtgui::xt_media_by_id(media_id);
86
124
  if (player == nullptr) return nullptr;
87
125
  const xtgui::Image *image = player->frame();
@@ -106,8 +144,8 @@ static SDL_GPUTexture *ensureMediaTexture(SDL_GPUDevice *device, uint64_t media_
106
144
  if (entry.texture == nullptr) return nullptr;
107
145
  }
108
146
  if (entry.generation != player->frame_generation()) {
109
- if (uploadPixels(device, entry.texture, entry.width, entry.height, image->pixels.data(),
110
- (uint32_t)image->pixels.size())) {
147
+ if (uploadPixels(device, entry, image->pixels.data(), image->width, 0, 0, entry.width,
148
+ entry.height, batch)) {
111
149
  entry.generation = player->frame_generation();
112
150
  }
113
151
  }
@@ -117,7 +155,8 @@ static SDL_GPUTexture *ensureMediaTexture(SDL_GPUDevice *device, uint64_t media_
117
155
  /** Ensure the texture for `canvas_id` exists and holds the canvas's newest
118
156
  * pixels. A WebGL canvas already owns its colour target, so it is returned
119
157
  * directly and nothing is copied through the CPU. */
120
- static SDL_GPUTexture *ensureCanvasTexture(SDL_GPUDevice *device, uint64_t canvas_id) {
158
+ static SDL_GPUTexture *ensureCanvasTexture(SDL_GPUDevice *device, uint64_t canvas_id,
159
+ XtGuiUploadBatch *batch) {
121
160
  if (xtgui::WebGLContext *webgl = xtgui::xt_webgl_by_id(canvas_id)) {
122
161
  return webgl->color_texture();
123
162
  }
@@ -144,11 +183,24 @@ static SDL_GPUTexture *ensureCanvasTexture(SDL_GPUDevice *device, uint64_t canva
144
183
  entry.generation = ~0ull;
145
184
  if (entry.texture == nullptr) return nullptr;
146
185
  }
147
- if (entry.generation != canvas->generation()) {
148
- if (uploadPixels(device, entry.texture, entry.width, entry.height, image.pixels.data(),
149
- (uint32_t)image.pixels.size())) {
150
- entry.generation = canvas->generation();
151
- }
186
+ if (entry.generation == canvas->generation()) return entry.texture;
187
+ const xtgui::Canvas2D::CanvasDirtyRect &dirty = canvas->pending_dirty();
188
+ /* A draw that changed no pixel (off canvas, zero coverage) must not pay for
189
+ * an upload: remember the generation and skip it. */
190
+ if (dirty.empty()) {
191
+ entry.generation = canvas->generation();
192
+ return entry.texture;
193
+ }
194
+ /* Capture the rectangle before `clear_dirty()` resets it. */
195
+ const int dirty_w = dirty.x1 - dirty.x0;
196
+ const int dirty_h = dirty.y1 - dirty.y0;
197
+ if (uploadPixels(device, entry, image.pixels.data(), image.width, dirty.x0, dirty.y0, dirty_w,
198
+ dirty_h, batch)) {
199
+ entry.generation = canvas->generation();
200
+ canvas->clear_dirty();
201
+ xtgui::g_perf_frame.canvasUploads += 1;
202
+ xtgui::g_perf_frame.canvasUploadBytes +=
203
+ (uint64_t)dirty_w * (uint64_t)dirty_h * 4u;
152
204
  }
153
205
  return entry.texture;
154
206
  }
@@ -235,7 +287,8 @@ SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *
235
287
 
236
288
  /** Create an RGBA texture holding `image` and upload its pixels. Returns null
237
289
  * (after reporting the SDL error) when either step fails. */
238
- SDL_GPUTexture *createImageTexture(SDL_GPUDevice *device, const xtgui::Image &image) {
290
+ SDL_GPUTexture *createImageTexture(SDL_GPUDevice *device, const xtgui::Image &image,
291
+ XtGuiUploadBatch *batch) {
239
292
  SDL_GPUTextureCreateInfo info;
240
293
  memset(&info, 0, sizeof(info));
241
294
  info.type = SDL_GPU_TEXTURETYPE_2D;
@@ -251,8 +304,7 @@ SDL_GPUTexture *createImageTexture(SDL_GPUDevice *device, const xtgui::Image &im
251
304
  fprintf(stderr, "xt_gui: image texture creation failed: %s\n", SDL_GetError());
252
305
  return nullptr;
253
306
  }
254
- uint32_t bytes = (uint32_t)(image.pixels.size());
255
- if (!uploadPixels(device, texture, image.width, image.height, image.pixels.data(), bytes)) {
307
+ if (!uploadWholeTexture(device, texture, image.width, image.height, image.pixels.data(), batch)) {
256
308
  SDL_ReleaseGPUTexture(device, texture);
257
309
  return nullptr;
258
310
  }
@@ -263,7 +315,7 @@ SDL_GPUTexture *createImageTexture(SDL_GPUDevice *device, const xtgui::Image &im
263
315
  * rasterised at the drawn pixel size and cached by that size; bitmaps decode at
264
316
  * their intrinsic size and are cached by path. */
265
317
  SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src, int pixel_width,
266
- int pixel_height) {
318
+ int pixel_height, XtGuiUploadBatch *batch) {
267
319
  bool vector_source = xtgui::xt_image_is_vector(src);
268
320
  std::string key =
269
321
  vector_source ? src + "@" + std::to_string(pixel_width) + "x" + std::to_string(pixel_height)
@@ -278,7 +330,7 @@ SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src
278
330
  return nullptr;
279
331
  }
280
332
 
281
- SDL_GPUTexture *texture = createImageTexture(device, *image);
333
+ SDL_GPUTexture *texture = createImageTexture(device, *image, batch);
282
334
  g_image_textures.emplace(key, texture);
283
335
  return texture;
284
336
  }
@@ -287,7 +339,8 @@ SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src
287
339
  * size and upload it. The markup is the cache key: a static document therefore
288
340
  * rasterises once per drawn size, and an edited one gets a fresh texture. */
289
341
  SDL_GPUTexture *ensureInlineSvgTexture(SDL_GPUDevice *device, const std::string &markup,
290
- int pixel_width, int pixel_height) {
342
+ int pixel_width, int pixel_height,
343
+ XtGuiUploadBatch *batch) {
291
344
  std::string key = "inline-svg@" + std::to_string(pixel_width) + "x" +
292
345
  std::to_string(pixel_height) + ":" + markup;
293
346
  auto found = g_image_textures.find(key);
@@ -300,7 +353,7 @@ SDL_GPUTexture *ensureInlineSvgTexture(SDL_GPUDevice *device, const std::string
300
353
  return nullptr;
301
354
  }
302
355
 
303
- SDL_GPUTexture *texture = createImageTexture(device, image);
356
+ SDL_GPUTexture *texture = createImageTexture(device, image, batch);
304
357
  g_image_textures.emplace(key, texture);
305
358
  return texture;
306
359
  }
@@ -309,7 +362,7 @@ SDL_GPUTexture *ensureInlineSvgTexture(SDL_GPUDevice *device, const std::string
309
362
  } // namespace xtgui
310
363
 
311
364
  void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
312
- const xtgui::DisplayList &list, float scale) {
365
+ const xtgui::DisplayList &list, float scale, XtGuiUploadBatch *batch) {
313
366
  using namespace xtgui::renderer_detail;
314
367
  geometry->op_counts.assign(list.images.size(), 0);
315
368
  geometry->op_textures.assign(list.images.size(), nullptr);
@@ -318,7 +371,8 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
318
371
  return;
319
372
  }
320
373
  if (scale <= 0.0f) scale = 1.0f;
321
- std::vector<TextVertex> verts;
374
+ std::vector<TextVertex> &verts = g_image_staging;
375
+ verts.clear();
322
376
  verts.reserve(list.images.size() * 6);
323
377
  GlyphEntry full;
324
378
  full.u0 = 0.0f;
@@ -337,13 +391,13 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
337
391
  if (pixel_height < 1) pixel_height = 1;
338
392
  SDL_GPUTexture *texture = nullptr;
339
393
  if (image.canvas_id != 0) {
340
- texture = ensureCanvasTexture(device, image.canvas_id);
394
+ texture = ensureCanvasTexture(device, image.canvas_id, batch);
341
395
  } else if (image.media_id != 0) {
342
- texture = ensureMediaTexture(device, image.media_id);
396
+ texture = ensureMediaTexture(device, image.media_id, batch);
343
397
  } else if (!image.svg.empty()) {
344
- texture = ensureInlineSvgTexture(device, image.svg, pixel_width, pixel_height);
398
+ texture = ensureInlineSvgTexture(device, image.svg, pixel_width, pixel_height, batch);
345
399
  } else {
346
- texture = ensureImageTexture(device, image.src, pixel_width, pixel_height);
400
+ texture = ensureImageTexture(device, image.src, pixel_width, pixel_height, batch);
347
401
  }
348
402
  if (texture == nullptr) continue;
349
403
  uint32_t count = pushGlyph(verts, image.x, image.y, image.width, image.height, full,
@@ -351,7 +405,7 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
351
405
  geometry->op_counts[op.index] = count;
352
406
  geometry->op_textures[op.index] = texture;
353
407
  }
354
- uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex));
408
+ uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex), batch);
355
409
  }
356
410
 
357
411
  void xt_gui_image_textures_destroy(SDL_GPUDevice *device) {
@@ -362,17 +416,25 @@ void xt_gui_image_textures_destroy(SDL_GPUDevice *device) {
362
416
  g_image_textures.clear();
363
417
  for (auto &entry : g_media_textures) {
364
418
  if (entry.second.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.second.texture);
419
+ releaseStaging(device, entry.second);
365
420
  }
366
421
  g_media_textures.clear();
367
422
  for (auto &entry : g_canvas_textures) {
368
423
  if (entry.second.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.second.texture);
424
+ releaseStaging(device, entry.second);
369
425
  }
370
426
  g_canvas_textures.clear();
371
427
  }
372
428
 
373
- void xt_gui_media_upload_frames(SDL_GPUDevice *device) {
429
+ bool xt_gui_media_upload_frames(SDL_GPUDevice *device, XtGuiUploadBatch *batch) {
374
430
  using namespace xtgui::renderer_detail;
375
- for (auto &entry : g_media_textures) ensureMediaTexture(device, entry.first);
431
+ bool uploaded = false;
432
+ for (auto &entry : g_media_textures) {
433
+ const uint64_t before = entry.second.generation;
434
+ ensureMediaTexture(device, entry.first, batch);
435
+ uploaded = uploaded || entry.second.generation != before;
436
+ }
437
+ return uploaded;
376
438
  }
377
439
 
378
440
  void xt_gui_media_texture_release(SDL_GPUDevice *device, uint64_t media_id) {
@@ -380,12 +442,19 @@ void xt_gui_media_texture_release(SDL_GPUDevice *device, uint64_t media_id) {
380
442
  auto it = g_media_textures.find(media_id);
381
443
  if (it == g_media_textures.end()) return;
382
444
  if (it->second.texture != nullptr) SDL_ReleaseGPUTexture(device, it->second.texture);
445
+ releaseStaging(device, it->second);
383
446
  g_media_textures.erase(it);
384
447
  }
385
448
 
386
- void xt_gui_canvas_upload_frames(SDL_GPUDevice *device) {
449
+ bool xt_gui_canvas_upload_frames(SDL_GPUDevice *device, XtGuiUploadBatch *batch) {
387
450
  using namespace xtgui::renderer_detail;
388
- for (auto &entry : g_canvas_textures) ensureCanvasTexture(device, entry.first);
451
+ bool uploaded = false;
452
+ for (auto &entry : g_canvas_textures) {
453
+ const uint64_t before = xtgui::g_perf_frame.canvasUploads;
454
+ ensureCanvasTexture(device, entry.first, batch);
455
+ uploaded = uploaded || xtgui::g_perf_frame.canvasUploads != before;
456
+ }
457
+ return uploaded;
389
458
  }
390
459
 
391
460
  void xt_gui_canvas_texture_release(SDL_GPUDevice *device, uint64_t canvas_id) {
@@ -393,5 +462,6 @@ void xt_gui_canvas_texture_release(SDL_GPUDevice *device, uint64_t canvas_id) {
393
462
  auto it = g_canvas_textures.find(canvas_id);
394
463
  if (it == g_canvas_textures.end()) return;
395
464
  if (it->second.texture != nullptr) SDL_ReleaseGPUTexture(device, it->second.texture);
465
+ releaseStaging(device, it->second);
396
466
  g_canvas_textures.erase(it);
397
467
  }
@@ -79,6 +79,18 @@ extern bool g_atlas_full_logged;
79
79
 
80
80
  /* -- geometry helpers ----------------------------------------------------- */
81
81
 
82
+ /** Reused vertex staging arrays (one frame at a time, cleared on each upload).
83
+ * Keeping their capacity avoids reallocating a few megabytes per repaint. */
84
+ extern std::vector<Vertex> g_shape_staging;
85
+ extern std::vector<TextVertex> g_text_staging;
86
+ extern std::vector<TextVertex> g_image_staging;
87
+
88
+ /** The command buffer an upload records into: the frame's batch when given,
89
+ * otherwise a freshly acquired one that `uploadDone` submits. */
90
+ SDL_GPUCommandBuffer *uploadCommand(SDL_GPUDevice *device, XtGuiUploadBatch *batch);
91
+ /** Submit a command buffer acquired for a one-off upload (`batch == nullptr`). */
92
+ void uploadDone(SDL_GPUCommandBuffer *cmd, XtGuiUploadBatch *batch);
93
+
82
94
  /** Push one rounded rectangle, clipped by the op's `clip-path` polygons (if
83
95
  * any). Returns the number of vertices appended. */
84
96
  uint32_t pushShape(std::vector<Vertex> &verts, const xtgui::PaintRect &rect,
@@ -95,14 +107,14 @@ uint32_t pushQuad(std::vector<TextVertex> &verts, const float corners_x[4],
95
107
  const xtgui::Color &color, const xtgui::Matrix2D &transform,
96
108
  const xtgui::PaintClip &clip, const xtgui::DisplayList &list);
97
109
  void uploadVertices(SDL_GPUDevice *device, XtGuiGeometry *geometry, const void *data,
98
- uint32_t count, uint32_t stride);
110
+ uint32_t count, uint32_t stride, XtGuiUploadBatch *batch);
99
111
 
100
112
  /* -- text ----------------------------------------------------------------- */
101
113
 
102
114
  bool ensureAtlasTexture(SDL_GPUDevice *device);
103
115
  void markAtlasDirty(int x, int y, int width, int height);
104
116
  bool ensureGlyph(SDL_GPUDevice *device, xtgui::Font *font, uint32_t glyph, GlyphEntry &out);
105
- void flushAtlas(SDL_GPUDevice *device);
117
+ void flushAtlas(SDL_GPUDevice *device, XtGuiUploadBatch *batch);
106
118
  SDL_GPUGraphicsPipeline *createTextPipeline(SDL_GPUDevice *device, SDL_Window *reference);
107
119
  SDL_GPUSampler *createSampler(SDL_GPUDevice *device);
108
120
 
@@ -114,7 +126,7 @@ SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *
114
126
  * rasterised at that size, raster sources ignore it. The texture cache is keyed
115
127
  * by the resulting pixel size, so an SVG drawn at two sizes gets two textures. */
116
128
  SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src, int pixel_width,
117
- int pixel_height);
129
+ int pixel_height, XtGuiUploadBatch *batch);
118
130
 
119
131
  } // namespace renderer_detail
120
132
  } // namespace xtgui
@@ -12,6 +12,8 @@
12
12
 
13
13
  #include "text.h"
14
14
 
15
+ #include "perf.h"
16
+
15
17
  #include "renderer_shaders.h"
16
18
 
17
19
  namespace xtgui {
@@ -114,7 +116,7 @@ bool ensureGlyph(SDL_GPUDevice *device, xtgui::Font *font, uint32_t glyph, Glyph
114
116
  return true;
115
117
  }
116
118
 
117
- void flushAtlas(SDL_GPUDevice *device) {
119
+ void flushAtlas(SDL_GPUDevice *device, XtGuiUploadBatch *batch) {
118
120
  if (!g_atlas_dirty || g_atlas_texture == nullptr) return;
119
121
  g_atlas_dirty = false;
120
122
  int x0 = g_atlas_dirty_x0;
@@ -141,7 +143,7 @@ void flushAtlas(SDL_GPUDevice *device) {
141
143
  }
142
144
  SDL_UnmapGPUTransferBuffer(device, transfer);
143
145
 
144
- SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(device);
146
+ SDL_GPUCommandBuffer *cmd = uploadCommand(device, batch);
145
147
  if (cmd == nullptr) {
146
148
  SDL_ReleaseGPUTransferBuffer(device, transfer);
147
149
  return;
@@ -166,7 +168,7 @@ void flushAtlas(SDL_GPUDevice *device) {
166
168
  SDL_GPUCopyPass *copy = SDL_BeginGPUCopyPass(cmd);
167
169
  SDL_UploadToGPUTexture(copy, &source, &destination, false);
168
170
  SDL_EndGPUCopyPass(copy);
169
- SDL_SubmitGPUCommandBuffer(cmd);
171
+ uploadDone(cmd, batch);
170
172
  SDL_ReleaseGPUTransferBuffer(device, transfer);
171
173
  }
172
174
 
@@ -270,7 +272,7 @@ SDL_GPUSampler *createSampler(SDL_GPUDevice *device) {
270
272
  } // namespace xtgui
271
273
 
272
274
  void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
273
- const xtgui::DisplayList &list, float scale) {
275
+ const xtgui::DisplayList &list, float scale, XtGuiUploadBatch *batch) {
274
276
  using namespace xtgui::renderer_detail;
275
277
  if (!g_text_ok) {
276
278
  geometry->vertex_count = 0;
@@ -278,7 +280,8 @@ void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
278
280
  }
279
281
  if (scale <= 0.0f) scale = 1.0f;
280
282
 
281
- std::vector<TextVertex> verts;
283
+ std::vector<TextVertex> &verts = g_text_staging;
284
+ verts.clear();
282
285
  geometry->op_counts.assign(list.texts.size(), 0);
283
286
  for (const xtgui::PaintOp &op : list.ops) {
284
287
  if (op.kind != xtgui::PaintKind::Text || op.index >= list.texts.size()) continue;
@@ -296,16 +299,25 @@ void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
296
299
  if (glyph_scale < kMinGlyphScale) glyph_scale = kMinGlyphScale;
297
300
  if (glyph_scale > kMaxGlyphScale) glyph_scale = kMaxGlyphScale;
298
301
 
299
- std::vector<xtgui::ShapedGlyph> glyphs;
300
- xtgui::xt_text_shape_run(run.text, logical, glyphs);
302
+ const xtgui::ShapedRun *shaped = xtgui::xt_text_shape_cached(run.text, logical);
303
+ if (shaped == nullptr) continue;
301
304
  float pen_x = run.x;
302
305
  uint32_t before = (uint32_t)verts.size();
303
- for (const xtgui::ShapedGlyph &glyph : glyphs) {
306
+ /* The scaled face is a per-run decision (the size and matrix are constant
307
+ * for the run); resolving it per glyph used to build a 512-byte key and
308
+ * hash it for every character on a HiDPI display. */
309
+ xtgui::Font *scaled_input = nullptr;
310
+ xtgui::Font *scaled_output = nullptr;
311
+ for (const xtgui::ShapedGlyph &glyph : shaped->glyphs) {
304
312
  if (glyph.glyph != 0) {
305
313
  xtgui::Font *face = glyph.font != nullptr ? glyph.font : logical;
306
314
  if (glyph_scale != 1.0f) {
307
- xtgui::Font *scaled = xtgui::xt_text_scale(face, run.font.pixel_size * glyph_scale);
308
- if (scaled != nullptr) face = scaled;
315
+ if (face != scaled_input) {
316
+ xtgui::Font *scaled = xtgui::xt_text_scale(face, run.font.pixel_size * glyph_scale);
317
+ scaled_input = face;
318
+ scaled_output = scaled != nullptr ? scaled : face;
319
+ }
320
+ face = scaled_output;
309
321
  }
310
322
  GlyphEntry entry;
311
323
  if (ensureGlyph(device, face, glyph.glyph, entry)) {
@@ -323,6 +335,6 @@ void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
323
335
  }
324
336
  geometry->op_counts[op.index] = (uint32_t)verts.size() - before;
325
337
  }
326
- uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex));
327
- flushAtlas(device);
338
+ uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex), batch);
339
+ flushAtlas(device, batch);
328
340
  }
@@ -25,6 +25,14 @@ static std::unordered_map<std::string, xt_value> &script_registry() {
25
25
  return registry;
26
26
  }
27
27
 
28
+ /** Deferred module id -> its exports object. A `<script src>`'s imports are
29
+ * evaluated on first use (after `loadHTML`) and then shared by every script
30
+ * that imports the same module. */
31
+ static std::unordered_map<std::string, xt_value> &deferred_registry() {
32
+ static std::unordered_map<std::string, xt_value> registry;
33
+ return registry;
34
+ }
35
+
28
36
  extern "C" xt_value xt_register_script(int32_t argc, xt_value *argv) {
29
37
  if (argc >= 2) {
30
38
  xt_value id_value = xt_to_string(xt_arg(argc, argv, 0));
@@ -42,6 +50,29 @@ extern "C" xt_value xt_register_script(int32_t argc, xt_value *argv) {
42
50
 
43
51
  void xt_gui_script_gc_scan(void) {
44
52
  for (const auto &entry : script_registry()) xt_gc_mark_value(entry.second);
53
+ for (const auto &entry : deferred_registry()) xt_gc_mark_value(entry.second);
54
+ }
55
+
56
+ /** `__xt_defer(id, init)`: evaluate `init` once and return its exports. The
57
+ * placeholder is published before the call so a cyclic import sees the same
58
+ * object instead of recursing. */
59
+ extern "C" xt_value xt_gui_defer_module(int32_t argc, xt_value *argv) {
60
+ if (argc < 2) return XT_UNDEFINED;
61
+ xt_value id_value = xt_to_string(xt_arg(argc, argv, 0));
62
+ const char *id = xt_string_data(id_value);
63
+ if (id == nullptr) return XT_UNDEFINED;
64
+ std::unordered_map<std::string, xt_value> &registry = deferred_registry();
65
+ auto found = registry.find(std::string(id));
66
+ if (found != registry.end()) return found->second;
67
+ xt_gc_register_root_provider(xt_gui_script_gc_scan);
68
+ xt_value exports = xt_object_new();
69
+ registry[std::string(id)] = exports;
70
+ xt_value init = xt_arg(argc, argv, 1);
71
+ if (XT_IS_FUNCTION(init)) {
72
+ exports = xt_call_with_this(init, XT_UNDEFINED, 0, nullptr);
73
+ registry[std::string(id)] = exports;
74
+ }
75
+ return exports;
45
76
  }
46
77
 
47
78
  /** Collect `data-xt-id` markers from `<script>` elements, in document order. */