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
@@ -15,6 +15,7 @@
15
15
  #include <cstring>
16
16
 
17
17
  #include "paint.h"
18
+ #include "perf.h"
18
19
  #include "text.h"
19
20
 
20
21
  #include "renderer_shaders.h"
@@ -39,10 +40,32 @@ bool g_atlas_dirty = false;
39
40
  int g_atlas_dirty_x0 = 0, g_atlas_dirty_y0 = 0, g_atlas_dirty_x1 = 0, g_atlas_dirty_y1 = 0;
40
41
  bool g_atlas_full_logged = false;
41
42
 
43
+ std::vector<Vertex> g_shape_staging;
44
+ std::vector<TextVertex> g_text_staging;
45
+ std::vector<TextVertex> g_image_staging;
46
+
47
+ /** The command buffer an upload records into. With a batch, the frame's single
48
+ * command buffer is acquired lazily and submitted by `xt_gui_upload_end`;
49
+ * without one, each upload submits on its own. */
50
+ SDL_GPUCommandBuffer *uploadCommand(SDL_GPUDevice *device, XtGuiUploadBatch *batch) {
51
+ if (batch == nullptr) return SDL_AcquireGPUCommandBuffer(device);
52
+ if (batch->cmd == nullptr) batch->cmd = SDL_AcquireGPUCommandBuffer(batch->device);
53
+ return batch->cmd;
54
+ }
55
+
56
+ void uploadDone(SDL_GPUCommandBuffer *cmd, XtGuiUploadBatch *batch) {
57
+ if (batch != nullptr) {
58
+ batch->recorded += 1;
59
+ return;
60
+ }
61
+ SDL_SubmitGPUCommandBuffer(cmd);
62
+ g_perf_frame.uploadSubmits += 1;
63
+ }
64
+
42
65
  /** Upload `count` vertices (of `stride` bytes) into `geometry`, reusing or
43
66
  * growing its GPU buffer. Does nothing when `count` is zero. */
44
67
  void uploadVertices(SDL_GPUDevice *device, XtGuiGeometry *geometry, const void *data,
45
- uint32_t count, uint32_t stride) {
68
+ uint32_t count, uint32_t stride, XtGuiUploadBatch *batch) {
46
69
  geometry->vertex_count = count;
47
70
  if (count == 0) return;
48
71
  uint32_t bytes = count * stride;
@@ -61,29 +84,37 @@ void uploadVertices(SDL_GPUDevice *device, XtGuiGeometry *geometry, const void *
61
84
  return;
62
85
  }
63
86
 
64
- SDL_GPUTransferBufferCreateInfo transfer_info;
65
- memset(&transfer_info, 0, sizeof(transfer_info));
66
- transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
67
- transfer_info.size = bytes;
68
- SDL_GPUTransferBuffer *transfer = SDL_CreateGPUTransferBuffer(device, &transfer_info);
69
- if (transfer == nullptr) {
87
+ /* Reuse the geometry's staging buffer: a repaint used to create and release a
88
+ * transfer buffer for every vertex array, every frame. */
89
+ if (geometry->staging == nullptr || geometry->staging_capacity < bytes) {
90
+ if (geometry->staging != nullptr) SDL_ReleaseGPUTransferBuffer(device, geometry->staging);
91
+ SDL_GPUTransferBufferCreateInfo transfer_info;
92
+ memset(&transfer_info, 0, sizeof(transfer_info));
93
+ transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
94
+ transfer_info.size = bytes;
95
+ geometry->staging = SDL_CreateGPUTransferBuffer(device, &transfer_info);
96
+ geometry->staging_capacity = geometry->staging != nullptr ? bytes : 0;
97
+ }
98
+ if (geometry->staging == nullptr) {
70
99
  fprintf(stderr, "xt_gui: transfer buffer creation failed: %s\n", SDL_GetError());
71
100
  geometry->vertex_count = 0;
72
101
  return;
73
102
  }
74
- void *mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
103
+ /* `cycle` rotates SDL's internal allocation while the previous frame's copy
104
+ * may still be in flight: the staging buffer is reused every frame, so the
105
+ * non-cycling map could overwrite data the GPU has not read yet. */
106
+ void *mapped = SDL_MapGPUTransferBuffer(device, geometry->staging, true);
75
107
  std::memcpy(mapped, data, bytes);
76
- SDL_UnmapGPUTransferBuffer(device, transfer);
108
+ SDL_UnmapGPUTransferBuffer(device, geometry->staging);
77
109
 
78
- SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(device);
110
+ SDL_GPUCommandBuffer *cmd = uploadCommand(device, batch);
79
111
  if (cmd == nullptr) {
80
- SDL_ReleaseGPUTransferBuffer(device, transfer);
81
112
  geometry->vertex_count = 0;
82
113
  return;
83
114
  }
84
115
  SDL_GPUCopyPass *copy = SDL_BeginGPUCopyPass(cmd);
85
116
  SDL_GPUTransferBufferLocation source;
86
- source.transfer_buffer = transfer;
117
+ source.transfer_buffer = geometry->staging;
87
118
  source.offset = 0;
88
119
  SDL_GPUBufferRegion destination;
89
120
  destination.buffer = geometry->buffer;
@@ -91,13 +122,26 @@ void uploadVertices(SDL_GPUDevice *device, XtGuiGeometry *geometry, const void *
91
122
  destination.size = bytes;
92
123
  SDL_UploadToGPUBuffer(copy, &source, &destination, false);
93
124
  SDL_EndGPUCopyPass(copy);
94
- SDL_SubmitGPUCommandBuffer(cmd);
95
- SDL_ReleaseGPUTransferBuffer(device, transfer);
125
+ uploadDone(cmd, batch);
126
+ g_perf_frame.vertices += count;
96
127
  }
97
128
 
98
129
  } // namespace renderer_detail
99
130
  } // namespace xtgui
100
131
 
132
+ void xt_gui_upload_begin(XtGuiUploadBatch *batch, SDL_GPUDevice *device) {
133
+ batch->device = device;
134
+ batch->cmd = nullptr;
135
+ batch->recorded = 0;
136
+ }
137
+
138
+ void xt_gui_upload_end(XtGuiUploadBatch *batch) {
139
+ if (batch == nullptr || batch->cmd == nullptr) return;
140
+ SDL_SubmitGPUCommandBuffer(batch->cmd);
141
+ batch->cmd = nullptr;
142
+ xtgui::g_perf_frame.uploadSubmits += 1;
143
+ }
144
+
101
145
  bool xt_gui_renderer_ensure(SDL_GPUDevice *device, SDL_Window *reference) {
102
146
  using namespace xtgui::renderer_detail;
103
147
  if (g_pipeline != nullptr) return true;
@@ -238,15 +282,21 @@ void xt_gui_geometry_destroy(SDL_GPUDevice *device, XtGuiGeometry *geometry) {
238
282
  SDL_ReleaseGPUBuffer(device, geometry->buffer);
239
283
  geometry->buffer = nullptr;
240
284
  }
285
+ if (geometry->staging != nullptr) {
286
+ SDL_ReleaseGPUTransferBuffer(device, geometry->staging);
287
+ geometry->staging = nullptr;
288
+ }
289
+ geometry->staging_capacity = 0;
241
290
  geometry->capacity_bytes = 0;
242
291
  geometry->vertex_count = 0;
243
292
  geometry->dirty = true;
244
293
  }
245
294
 
246
295
  void xt_gui_geometry_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
247
- const xtgui::DisplayList &list) {
296
+ const xtgui::DisplayList &list, XtGuiUploadBatch *batch) {
248
297
  using namespace xtgui::renderer_detail;
249
- std::vector<Vertex> verts;
298
+ std::vector<Vertex> &verts = g_shape_staging;
299
+ verts.clear();
250
300
  verts.reserve(list.rects.size() * 6);
251
301
  geometry->op_counts.assign(list.rects.size(), 0);
252
302
  /* Walk painter order so each rectangle carries its op's clip path. */
@@ -256,7 +306,7 @@ void xt_gui_geometry_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
256
306
  pushShape(verts, list.rects[op.index], op.clip, list);
257
307
  geometry->op_counts[op.index] = (uint32_t)verts.size() - before;
258
308
  }
259
- uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(Vertex));
309
+ uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(Vertex), batch);
260
310
  }
261
311
 
262
312
  void xt_gui_build_runs(XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
@@ -310,6 +360,35 @@ void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
310
360
 
311
361
  int target_width = (int)(viewport_width * pixel_density + 0.5f);
312
362
  int target_height = (int)(viewport_height * pixel_density + 0.5f);
363
+ /* A run list alternates pipelines (background, text, background, …), so the
364
+ * same scissor rectangle and the same pipeline were re-bound for every run.
365
+ * Track what is bound and only issue the state change when it actually
366
+ * changes: a 1,000-row list has hundreds of runs. */
367
+ SDL_Rect bound_scissor;
368
+ bound_scissor.x = -1;
369
+ bound_scissor.y = -1;
370
+ bound_scissor.w = -1;
371
+ bound_scissor.h = -1;
372
+ int bound_pipeline = -1;
373
+ SDL_GPUTexture *bound_texture = nullptr;
374
+ auto bindPipeline = [&](int pipeline, SDL_GPUBuffer *buffer, SDL_GPUGraphicsPipeline *gpu) {
375
+ if (bound_pipeline == pipeline) return;
376
+ SDL_BindGPUGraphicsPipeline(pass, gpu);
377
+ SDL_GPUBufferBinding binding;
378
+ binding.buffer = buffer;
379
+ binding.offset = 0;
380
+ SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
381
+ bound_pipeline = pipeline;
382
+ bound_texture = nullptr; // a new pipeline invalidates the bound sampler
383
+ };
384
+ auto bindTexture = [&](SDL_GPUTexture *texture) {
385
+ if (bound_texture == texture) return;
386
+ SDL_GPUTextureSamplerBinding texture_binding;
387
+ texture_binding.texture = texture;
388
+ texture_binding.sampler = g_sampler;
389
+ SDL_BindGPUFragmentSamplers(pass, 0, &texture_binding, 1);
390
+ bound_texture = texture;
391
+ };
313
392
  for (const XtGuiDrawRun &run : shapes->runs) {
314
393
  SDL_Rect scissor;
315
394
  scissor.x = 0;
@@ -330,42 +409,31 @@ void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
330
409
  scissor.w = x1 > x0 ? x1 - x0 : 0;
331
410
  scissor.h = y1 > y0 ? y1 - y0 : 0;
332
411
  }
333
- SDL_SetGPUScissor(pass, &scissor);
412
+ if (scissor.x != bound_scissor.x || scissor.y != bound_scissor.y ||
413
+ scissor.w != bound_scissor.w || scissor.h != bound_scissor.h) {
414
+ SDL_SetGPUScissor(pass, &scissor);
415
+ bound_scissor = scissor;
416
+ }
334
417
 
335
418
  if (run.pipeline == XtGuiDrawRun::Shape) {
336
419
  if (g_pipeline == nullptr || shapes->buffer == nullptr) continue;
337
- SDL_BindGPUGraphicsPipeline(pass, g_pipeline);
338
- SDL_GPUBufferBinding binding;
339
- binding.buffer = shapes->buffer;
340
- binding.offset = 0;
341
- SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
420
+ bindPipeline(XtGuiDrawRun::Shape, shapes->buffer, g_pipeline);
342
421
  SDL_DrawGPUPrimitives(pass, run.count, 1, run.first, 0);
422
+ xtgui::g_perf_frame.drawCalls += 1;
343
423
  } else if (run.pipeline == XtGuiDrawRun::Text) {
344
424
  if (!g_text_ok || g_atlas_texture == nullptr || texts->buffer == nullptr) continue;
345
- SDL_BindGPUGraphicsPipeline(pass, g_text_pipeline);
346
- SDL_GPUBufferBinding binding;
347
- binding.buffer = texts->buffer;
348
- binding.offset = 0;
349
- SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
350
- SDL_GPUTextureSamplerBinding atlas_binding;
351
- atlas_binding.texture = g_atlas_texture;
352
- atlas_binding.sampler = g_sampler;
353
- SDL_BindGPUFragmentSamplers(pass, 0, &atlas_binding, 1);
425
+ bindPipeline(XtGuiDrawRun::Text, texts->buffer, g_text_pipeline);
426
+ bindTexture(g_atlas_texture);
354
427
  SDL_DrawGPUPrimitives(pass, run.count, 1, run.first, 0);
428
+ xtgui::g_perf_frame.drawCalls += 1;
355
429
  } else {
356
430
  if (g_image_pipeline == nullptr || g_sampler == nullptr || images->buffer == nullptr ||
357
431
  run.texture == nullptr)
358
432
  continue;
359
- SDL_BindGPUGraphicsPipeline(pass, g_image_pipeline);
360
- SDL_GPUBufferBinding binding;
361
- binding.buffer = images->buffer;
362
- binding.offset = 0;
363
- SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
364
- SDL_GPUTextureSamplerBinding texture_binding;
365
- texture_binding.texture = run.texture;
366
- texture_binding.sampler = g_sampler;
367
- SDL_BindGPUFragmentSamplers(pass, 0, &texture_binding, 1);
433
+ bindPipeline(XtGuiDrawRun::Image, images->buffer, g_image_pipeline);
434
+ bindTexture(run.texture);
368
435
  SDL_DrawGPUPrimitives(pass, run.count, 1, run.first, 0);
436
+ xtgui::g_perf_frame.drawCalls += 1;
369
437
  }
370
438
  }
371
439
 
@@ -50,6 +50,10 @@ struct XtGuiGeometry {
50
50
  uint32_t capacity_bytes = 0;
51
51
  uint32_t vertex_count = 0;
52
52
  bool dirty = true;
53
+ /** Reusable upload staging buffer (grown on demand), so a repaint does not
54
+ * create and destroy a transfer buffer for every vertex array. */
55
+ SDL_GPUTransferBuffer *staging = nullptr;
56
+ uint32_t staging_capacity = 0;
53
57
  /** Vertex count contributed by each shape of this pipeline's kind, aligned
54
58
  * with the display list's payload vector (used to build the run list). */
55
59
  std::vector<uint32_t> op_counts;
@@ -61,6 +65,20 @@ struct XtGuiGeometry {
61
65
  std::vector<XtGuiDrawRun> runs;
62
66
  };
63
67
 
68
+ /** One GPU command buffer shared by every upload a frame performs (shape, text,
69
+ * glyph atlas and image geometry), so a dirty frame submits its copy passes
70
+ * once instead of once per buffer. Uploads recorded into a batch must be
71
+ * finished by `xt_gui_upload_end` before the render pass is recorded. */
72
+ struct XtGuiUploadBatch {
73
+ SDL_GPUDevice *device = nullptr;
74
+ SDL_GPUCommandBuffer *cmd = nullptr;
75
+ int recorded = 0;
76
+ };
77
+
78
+ void xt_gui_upload_begin(XtGuiUploadBatch *batch, SDL_GPUDevice *device);
79
+ /** Submit the batch if anything was recorded (no-op otherwise). */
80
+ void xt_gui_upload_end(XtGuiUploadBatch *batch);
81
+
64
82
  /** Create the shared pipelines if needed. Returns false when the backend has no
65
83
  * shader format we support (the caller then skips geometry). */
66
84
  bool xt_gui_renderer_ensure(SDL_GPUDevice *device, SDL_Window *reference);
@@ -68,9 +86,11 @@ bool xt_gui_renderer_ensure(SDL_GPUDevice *device, SDL_Window *reference);
68
86
  void xt_gui_renderer_destroy(SDL_GPUDevice *device);
69
87
  /** Release a window's vertex buffer (after SDL_WaitForGPUIdle). */
70
88
  void xt_gui_geometry_destroy(SDL_GPUDevice *device, XtGuiGeometry *geometry);
71
- /** Upload the rectangle geometry from `list` (own command buffer). */
89
+ /** Upload the rectangle geometry from `list`. With a `batch` the copy pass is
90
+ * recorded into the frame's shared command buffer; otherwise it is submitted
91
+ * immediately. */
72
92
  void xt_gui_geometry_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
73
- const xtgui::DisplayList &list);
93
+ const xtgui::DisplayList &list, XtGuiUploadBatch *batch = nullptr);
74
94
  /** Draw `geometry` inside an active render pass. */
75
95
  void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
76
96
  XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
@@ -78,13 +98,15 @@ void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
78
98
  /** Shape `list.texts`, ensure their glyphs are in the atlas, and upload the
79
99
  * text geometry. `scale` is the device pixel density (1.0 = logical pixels). */
80
100
  void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
81
- const xtgui::DisplayList &list, float scale);
101
+ const xtgui::DisplayList &list, float scale,
102
+ XtGuiUploadBatch *batch = nullptr);
82
103
  /** Decode/upload `list.images` and build the image quad geometry (one batch per
83
104
  * texture). Textures are cached by source path and pixel size. `scale` is the
84
105
  * device pixel density (1.0 = logical pixels) used to pick the rasterisation
85
106
  * size of a vector source (SVG); raster sources decode at their intrinsic size. */
86
107
  void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
87
- const xtgui::DisplayList &list, float scale);
108
+ const xtgui::DisplayList &list, float scale,
109
+ XtGuiUploadBatch *batch = nullptr);
88
110
  /** Release the image texture cache (after SDL_WaitForGPUIdle). */
89
111
  void xt_gui_image_textures_destroy(SDL_GPUDevice *device);
90
112
  /** Build the painter-order run list from the display list, using the per-op
@@ -94,14 +116,16 @@ void xt_gui_build_runs(XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometr
94
116
  const xtgui::DisplayList &list);
95
117
  /** Re-upload the current decoded frame of every live `<video>` texture. Called
96
118
  * once per frame, before the render command buffer, so the texture sampled by
97
- * the draw already holds that frame. */
98
- void xt_gui_media_upload_frames(SDL_GPUDevice *device);
119
+ * the draw already holds that frame. Returns true when a texture was uploaded
120
+ * (the frame loop then presents even without a DOM change). */
121
+ bool xt_gui_media_upload_frames(SDL_GPUDevice *device, XtGuiUploadBatch *batch = nullptr);
99
122
  /** Release one media texture (when a `<video>` is removed or navigated away). */
100
123
  void xt_gui_media_texture_release(SDL_GPUDevice *device, uint64_t media_id);
101
- /** Re-upload the current bitmap of every live `<canvas>` texture. Called once
124
+ /** Re-upload the dirty region of every live `<canvas>` texture. Called once
102
125
  * per frame, before the render command buffer, so a canvas animated from
103
- * `requestAnimationFrame` reaches the screen without a relayout. */
104
- void xt_gui_canvas_upload_frames(SDL_GPUDevice *device);
126
+ * `requestAnimationFrame` reaches the screen without a relayout. Returns true
127
+ * when a texture was uploaded. */
128
+ bool xt_gui_canvas_upload_frames(SDL_GPUDevice *device, XtGuiUploadBatch *batch = nullptr);
105
129
  /** Release one canvas texture (when the canvas is dropped). */
106
130
  void xt_gui_canvas_texture_release(SDL_GPUDevice *device, uint64_t canvas_id);
107
131