xbintsc 0.3.68 → 0.3.79

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 (100) hide show
  1. package/README.md +59 -4
  2. package/README.zh-CN.md +52 -2
  3. package/dist/tests/e2e/gui-advanced.test.d.ts +6 -0
  4. package/dist/tests/e2e/gui-advanced.test.js +261 -0
  5. package/dist/tests/e2e/gui-advanced.test.js.map +1 -0
  6. package/dist/tests/e2e/gui-canvas.test.d.ts +9 -0
  7. package/dist/tests/e2e/gui-canvas.test.js +233 -0
  8. package/dist/tests/e2e/gui-canvas.test.js.map +1 -0
  9. package/dist/tests/e2e/gui-example.test.d.ts +1 -1
  10. package/dist/tests/e2e/gui-example.test.js +2 -2
  11. package/dist/tests/e2e/gui-helpers.d.ts +3 -0
  12. package/dist/tests/e2e/gui-helpers.js +5 -0
  13. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  14. package/dist/tests/e2e/gui-layout.test.js +73 -0
  15. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  16. package/dist/tests/e2e/gui-media.test.d.ts +8 -0
  17. package/dist/tests/e2e/gui-media.test.js +119 -0
  18. package/dist/tests/e2e/gui-media.test.js.map +1 -0
  19. package/dist/tests/e2e/gui-paint.test.js +40 -0
  20. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  21. package/doc/ai/extensions.md +16 -3
  22. package/doc/ai/zh-CN/extensions.md +10 -3
  23. package/doc/gui/animation.md +46 -0
  24. package/doc/gui/api.md +92 -0
  25. package/doc/gui/build.md +91 -0
  26. package/doc/gui/canvas.md +98 -0
  27. package/doc/gui/dom.md +123 -0
  28. package/doc/gui/input.md +22 -0
  29. package/doc/gui/known-issues.md +66 -0
  30. package/doc/gui/layout.md +72 -0
  31. package/doc/gui/media.md +87 -0
  32. package/doc/gui/rendering.md +98 -0
  33. package/doc/gui/runtime.md +88 -0
  34. package/doc/gui-scripts.md +7 -3
  35. package/doc/gui.md +80 -612
  36. package/doc/implemented.md +1 -0
  37. package/doc/zh-CN/gui/animation.md +46 -0
  38. package/doc/zh-CN/gui/api.md +88 -0
  39. package/doc/zh-CN/gui/build.md +84 -0
  40. package/doc/zh-CN/gui/canvas.md +80 -0
  41. package/doc/zh-CN/gui/dom.md +114 -0
  42. package/doc/zh-CN/gui/input.md +21 -0
  43. package/doc/zh-CN/gui/known-issues.md +50 -0
  44. package/doc/zh-CN/gui/layout.md +60 -0
  45. package/doc/zh-CN/gui/media.md +71 -0
  46. package/doc/zh-CN/gui/rendering.md +81 -0
  47. package/doc/zh-CN/gui/runtime.md +76 -0
  48. package/doc/zh-CN/gui-scripts.md +6 -2
  49. package/doc/zh-CN/gui.md +64 -532
  50. package/doc/zh-CN/implemented.md +1 -0
  51. package/doc/zh-CN/post/xbintsc-build-pelican.md +2 -2
  52. package/llms.txt +15 -2
  53. package/package.json +1 -1
  54. package/runtime/ext_gui/canvas.cpp +580 -0
  55. package/runtime/ext_gui/canvas.h +170 -0
  56. package/runtime/ext_gui/canvas_internal.h +59 -0
  57. package/runtime/ext_gui/canvas_raster.cpp +254 -0
  58. package/runtime/ext_gui/document.cpp +24 -1
  59. package/runtime/ext_gui/document.h +11 -0
  60. package/runtime/ext_gui/dom_api.cpp +3 -0
  61. package/runtime/ext_gui/dom_api_canvas.cpp +548 -0
  62. package/runtime/ext_gui/dom_api_internal.h +73 -0
  63. package/runtime/ext_gui/dom_api_media.cpp +274 -0
  64. package/runtime/ext_gui/dom_api_proto.cpp +80 -0
  65. package/runtime/ext_gui/gui.cpp +6 -0
  66. package/runtime/ext_gui/gui_canvas.cpp +95 -0
  67. package/runtime/ext_gui/gui_engine.h +49 -0
  68. package/runtime/ext_gui/gui_events.cpp +21 -5
  69. package/runtime/ext_gui/gui_media.cpp +193 -0
  70. package/runtime/ext_gui/image.cpp +31 -12
  71. package/runtime/ext_gui/image.h +20 -7
  72. package/runtime/ext_gui/layout.cpp +52 -5
  73. package/runtime/ext_gui/layout.h +33 -1
  74. package/runtime/ext_gui/layout_flex.cpp +263 -99
  75. package/runtime/ext_gui/layout_flow.cpp +85 -9
  76. package/runtime/ext_gui/layout_flow_internal.h +84 -5
  77. package/runtime/ext_gui/layout_inline.cpp +54 -37
  78. package/runtime/ext_gui/media.cpp +514 -0
  79. package/runtime/ext_gui/media.h +104 -0
  80. package/runtime/ext_gui/nanosvg.h +3278 -0
  81. package/runtime/ext_gui/nanosvgrast.h +1472 -0
  82. package/runtime/ext_gui/paint.cpp +123 -38
  83. package/runtime/ext_gui/paint.h +36 -6
  84. package/runtime/ext_gui/renderer.cpp +113 -10
  85. package/runtime/ext_gui/renderer.h +49 -9
  86. package/runtime/ext_gui/renderer_image.cpp +203 -77
  87. package/runtime/ext_gui/renderer_internal.h +6 -8
  88. package/runtime/ext_gui/renderer_text.cpp +11 -26
  89. package/runtime/ext_gui/style.h +11 -0
  90. package/runtime/ext_gui/style_print.cpp +11 -0
  91. package/runtime/ext_gui/style_ua.cpp +7 -0
  92. package/runtime/ext_gui/style_values.cpp +40 -1
  93. package/runtime/ext_gui/svg.cpp +182 -0
  94. package/runtime/ext_gui/svg.h +46 -0
  95. package/runtime/ext_gui/text.cpp +156 -25
  96. package/runtime/ext_gui/text.h +11 -2
  97. package/runtime/ext_gui/window.cpp +4 -1
  98. package/scripts/build-gui.ts +148 -6
  99. package/scripts/check-file-length.ts +5 -1
  100. package/scripts/install-gui-deps.sh +28 -0
@@ -4,6 +4,7 @@
4
4
 
5
5
  #include "paint.h"
6
6
 
7
+ #include <algorithm>
7
8
  #include <cstdio>
8
9
  #include <utility>
9
10
 
@@ -31,7 +32,7 @@ FontSpec specOf(const XtStyle &style) {
31
32
  }
32
33
 
33
34
  void addRect(DisplayList &out, float x, float y, float width, float height, float radius,
34
- const Color &color) {
35
+ const Color &color, const PaintClip &clip) {
35
36
  if (width <= 0.0f || height <= 0.0f || color.a <= 0.0f) return;
36
37
  PaintRect rect;
37
38
  rect.x = x;
@@ -41,9 +42,11 @@ void addRect(DisplayList &out, float x, float y, float width, float height, floa
41
42
  rect.radius = radius;
42
43
  rect.color = color;
43
44
  out.rects.push_back(rect);
45
+ out.ops.push_back(PaintOp{PaintKind::Rect, (uint32_t)(out.rects.size() - 1), clip});
44
46
  }
45
47
 
46
- void addTextRuns(const LayoutBox *box, const XtStyle &style, DisplayList &out) {
48
+ void addTextRuns(const LayoutBox *box, const XtStyle &style, DisplayList &out,
49
+ const PaintClip &clip) {
47
50
  if (box->fragments.empty() || style.color.a <= 0.0f) return;
48
51
  FontSpec spec = specOf(style);
49
52
  float ascent = 0;
@@ -60,28 +63,71 @@ void addTextRuns(const LayoutBox *box, const XtStyle &style, DisplayList &out) {
60
63
  run.text = fragment.text;
61
64
  run.font = spec;
62
65
  out.texts.push_back(std::move(run));
66
+ out.ops.push_back(PaintOp{PaintKind::Text, (uint32_t)(out.texts.size() - 1), clip});
63
67
  }
64
68
  }
65
69
 
66
- void paintBox(const LayoutBox *box, DisplayList &out) {
70
+ void addImage(const PaintImage &image, DisplayList &out, const PaintClip &clip) {
71
+ out.images.push_back(image);
72
+ out.ops.push_back(PaintOp{PaintKind::Image, (uint32_t)(out.images.size() - 1), clip});
73
+ }
74
+
75
+ void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip);
76
+
77
+ /** The clip a box imposes on its content: its padding box, intersected with the
78
+ * clip already in effect. Only `overflow: hidden/scroll/auto` clips. */
79
+ PaintClip clipForChildren(const LayoutBox *box, const PaintClip &parent) {
80
+ if (box->style == nullptr || box->style->overflow == Overflow::Visible) return parent;
81
+ float x = box->x + box->border_left;
82
+ float y = box->y + box->border_top;
83
+ float width = box->width - box->border_left - box->border_right;
84
+ float height = box->height - box->border_top - box->border_bottom;
85
+ if (width < 0) width = 0;
86
+ if (height < 0) height = 0;
87
+ if (!parent.active) return PaintClip{true, x, y, width, height};
88
+ float x0 = std::max(x, parent.x);
89
+ float y0 = std::max(y, parent.y);
90
+ float x1 = std::min(x + width, parent.x + parent.width);
91
+ float y1 = std::min(y + height, parent.y + parent.height);
92
+ return PaintClip{true, x0, y0, std::max(0.0f, x1 - x0), std::max(0.0f, y1 - y0)};
93
+ }
94
+
95
+ int zIndexOf(const LayoutBox *box) {
96
+ if (box->style == nullptr || !box->style->has_z_index) return 0;
97
+ return box->style->z_index;
98
+ }
99
+
100
+ /** Paint `box`'s children following z-index: negative z first (ascending), then
101
+ * auto/zero in document order, then positive z (ascending), so higher stacks
102
+ * paint last. */
103
+ void paintChildren(const LayoutBox *box, DisplayList &out, const PaintClip &clip) {
104
+ std::vector<const LayoutBox *> children;
105
+ for (const std::unique_ptr<LayoutBox> &child : box->children) {
106
+ if (child->display == Display::None || child->style == nullptr) continue;
107
+ children.push_back(child.get());
108
+ }
109
+ std::stable_sort(children.begin(), children.end(), [](const LayoutBox *a, const LayoutBox *b) {
110
+ return zIndexOf(a) < zIndexOf(b);
111
+ });
112
+ for (const LayoutBox *child : children) paintBox(child, out, clip);
113
+ }
114
+
115
+ void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip) {
67
116
  if (box == nullptr) return;
68
117
  const XtStyle *style = box->style;
69
118
 
70
- /* A fully transparent box paints nothing — not itself, and not its subtree.
71
- * `opacity` is otherwise unsupported: a shape carries its colour's alpha only,
72
- * so a partly transparent box (an `opacity` transition mid-flight, say) is
73
- * still painted opaque. */
119
+ /* A fully transparent box paints nothing — not itself, and not its subtree. */
74
120
  if (style != nullptr && style->opacity <= 0.0f) return;
75
121
 
76
122
  if (style != nullptr && box->display != Display::None) {
77
123
  if (box->is_text) {
78
- addTextRuns(box, *style, out);
124
+ addTextRuns(box, *style, out, clip);
79
125
  } else {
80
- /* Background first, then the (sharp-cornered) border ring on top, then the
81
- * children, so their content paints over the padding. */
126
+ /* Background first, then the border ring, then replaced content. Children
127
+ * are clipped by this box's padding box when it establishes one. */
82
128
  if (style->has_background) {
83
129
  addRect(out, box->x, box->y, box->width, box->height, style->border_radius,
84
- style->background);
130
+ style->background, clip);
85
131
  }
86
132
  if (style->border_style != BorderStyle::None && style->has_border_color) {
87
133
  const Color bc = style->border_color;
@@ -90,52 +136,91 @@ void paintBox(const LayoutBox *box, DisplayList &out) {
90
136
  float bottom = box->border_bottom;
91
137
  float left = box->border_left;
92
138
  float inner_height = box->height - top - bottom;
93
- /* Top/bottom span the whole width and overlap the side edges, which is
94
- * harmless (same colour) and avoids seams from float rounding. */
95
- addRect(out, box->x, box->y, box->width, top, 0.0f, bc);
96
- addRect(out, box->x, box->y + box->height - bottom, box->width, bottom, 0.0f, bc);
97
- addRect(out, box->x, box->y + top, left, inner_height, 0.0f, bc);
98
- addRect(out, box->x + box->width - right, box->y + top, right, inner_height, 0.0f, bc);
139
+ addRect(out, box->x, box->y, box->width, top, 0.0f, bc, clip);
140
+ addRect(out, box->x, box->y + box->height - bottom, box->width, bottom, 0.0f, bc, clip);
141
+ addRect(out, box->x, box->y + top, left, inner_height, 0.0f, bc, clip);
142
+ addRect(out, box->x + box->width - right, box->y + top, right, inner_height, 0.0f, bc,
143
+ clip);
99
144
  }
100
- if (box->is_image && !box->image_src.empty() && box->content_width > 0.0f &&
145
+ if (box->is_canvas && box->canvas_id != 0 && box->content_width > 0.0f &&
101
146
  box->content_height > 0.0f) {
102
147
  PaintImage image;
103
148
  image.x = box->content_x;
104
149
  image.y = box->content_y;
105
150
  image.width = box->content_width;
106
151
  image.height = box->content_height;
152
+ image.canvas_id = box->canvas_id;
153
+ addImage(image, out, clip);
154
+ } else if (box->is_media && box->media_id != 0 && box->content_width > 0.0f &&
155
+ box->content_height > 0.0f) {
156
+ PaintImage image;
157
+ image.x = box->content_x;
158
+ image.y = box->content_y;
159
+ image.width = box->content_width;
160
+ image.height = box->content_height;
161
+ image.media_id = box->media_id;
162
+ addImage(image, out, clip);
163
+ } else if (box->is_image && !box->image_src.empty() && box->content_width > 0.0f &&
164
+ box->content_height > 0.0f) {
165
+ PaintImage image;
166
+ image.x = box->content_x;
167
+ image.y = box->content_y;
168
+ image.width = box->content_width;
169
+ image.height = box->content_height;
107
170
  image.src = box->image_src;
108
- out.images.push_back(std::move(image));
171
+ addImage(image, out, clip);
109
172
  }
110
173
  }
111
174
  }
112
175
 
113
- for (const std::unique_ptr<LayoutBox> &child : box->children) paintBox(child.get(), out);
176
+ paintChildren(box, out, clipForChildren(box, clip));
114
177
  }
115
178
 
116
179
  } // namespace
117
180
 
118
- void xt_paint_build(const LayoutBox *root, DisplayList &out) { paintBox(root, out); }
181
+ void xt_paint_build(const LayoutBox *root, DisplayList &out) {
182
+ PaintClip none;
183
+ paintBox(root, out, none);
184
+ }
119
185
 
120
186
  std::string DisplayList::dump() const {
121
187
  std::string text;
122
- char buffer[192];
123
- for (const PaintRect &rect : rects) {
124
- std::snprintf(buffer, sizeof(buffer), "rect x=%.1f y=%.1f w=%.1f h=%.1f r=%.1f color=%s\n",
125
- rect.x, rect.y, rect.width, rect.height, rect.radius,
126
- formatColor(rect.color).c_str());
127
- text += buffer;
128
- }
129
- for (const PaintText &run : texts) {
130
- std::snprintf(buffer, sizeof(buffer),
131
- "text x=%.1f baseline=%.1f size=%.1f color=%s \"%s\"\n", run.x, run.baseline,
132
- run.font.pixel_size, formatColor(run.color).c_str(), run.text.c_str());
133
- text += buffer;
134
- }
135
- for (const PaintImage &image : images) {
136
- std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f src=%s\n", image.x,
137
- image.y, image.width, image.height, image.src.c_str());
138
- text += buffer;
188
+ char buffer[224];
189
+ for (const PaintOp &op : ops) {
190
+ std::string prefix;
191
+ if (op.clip.active) {
192
+ std::snprintf(buffer, sizeof(buffer), "clip=%.1f,%.1f,%.1f,%.1f ", op.clip.x, op.clip.y,
193
+ op.clip.width, op.clip.height);
194
+ prefix = buffer;
195
+ }
196
+ if (op.kind == PaintKind::Rect) {
197
+ const PaintRect &rect = rects[op.index];
198
+ std::snprintf(buffer, sizeof(buffer),
199
+ "rect x=%.1f y=%.1f w=%.1f h=%.1f r=%.1f color=%s\n", rect.x, rect.y,
200
+ rect.width, rect.height, rect.radius, formatColor(rect.color).c_str());
201
+ text += prefix + buffer;
202
+ } else if (op.kind == PaintKind::Text) {
203
+ const PaintText &run = texts[op.index];
204
+ std::snprintf(buffer, sizeof(buffer),
205
+ "text x=%.1f baseline=%.1f size=%.1f color=%s \"%s\"\n", run.x, run.baseline,
206
+ run.font.pixel_size, formatColor(run.color).c_str(), run.text.c_str());
207
+ text += prefix + buffer;
208
+ } else {
209
+ const PaintImage &image = images[op.index];
210
+ if (image.media_id != 0) {
211
+ std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f media=%llu\n",
212
+ image.x, image.y, image.width, image.height,
213
+ (unsigned long long)image.media_id);
214
+ } else if (image.canvas_id != 0) {
215
+ std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f canvas=%llu\n",
216
+ image.x, image.y, image.width, image.height,
217
+ (unsigned long long)image.canvas_id);
218
+ } else {
219
+ std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f src=%s\n",
220
+ image.x, image.y, image.width, image.height, image.src.c_str());
221
+ }
222
+ text += prefix + buffer;
223
+ }
139
224
  }
140
225
  return text;
141
226
  }
@@ -6,13 +6,14 @@
6
6
  * the same list into triangles (rectangles) and glyph quads (text), and
7
7
  * diagnostics/tests can inspect it without a GPU.
8
8
  *
9
- * Rectangles and text are kept in separate lists: backgrounds/borders are drawn
10
- * first, then all text on top. That matches almost every static UI and lets the
11
- * renderer use one draw call per list instead of switching pipelines per shape.
12
- */
9
+ * Rectangles, text and images share one painter-order `ops` list, so a
10
+ * `z-index`ed child can paint over a sibling's background and an `overflow`
11
+ * clip applies to every shape in the subtree. Coordinates are viewport-
12
+ * relative. */
13
13
  #ifndef XT_GUI_PAINT_H
14
14
  #define XT_GUI_PAINT_H
15
15
 
16
+ #include <cstdint>
16
17
  #include <string>
17
18
  #include <vector>
18
19
 
@@ -43,27 +44,56 @@ struct PaintText {
43
44
  FontSpec font;
44
45
  };
45
46
 
46
- /** An image to draw, scaled into the given rectangle (viewport-relative). */
47
+ /** An image to draw, scaled into the given rectangle (viewport-relative).
48
+ * A non-zero `media_id` names a live `<video>` frame owned by the media
49
+ * subsystem; a non-zero `canvas_id` names a live `<canvas>` bitmap owned by the
50
+ * DOM; `src` is then empty and the renderer binds that subsystem's texture. */
47
51
  struct PaintImage {
48
52
  float x = 0;
49
53
  float y = 0;
50
54
  float width = 0;
51
55
  float height = 0;
52
56
  std::string src;
57
+ uint64_t media_id = 0;
58
+ uint64_t canvas_id = 0;
59
+ };
60
+
61
+ /** The clip rectangle in effect for a painted shape (viewport-relative logical
62
+ * pixels). `width <= 0` means "no clip". */
63
+ struct PaintClip {
64
+ bool active = false;
65
+ float x = 0;
66
+ float y = 0;
67
+ float width = 0;
68
+ float height = 0;
69
+ };
70
+
71
+ enum class PaintKind { Rect, Text, Image };
72
+
73
+ /** One entry in painter order: which kind of shape, its index in the matching
74
+ * payload vector, and the clip in effect. */
75
+ struct PaintOp {
76
+ PaintKind kind = PaintKind::Rect;
77
+ uint32_t index = 0;
78
+ PaintClip clip;
53
79
  };
54
80
 
55
81
  struct DisplayList {
56
82
  std::vector<PaintRect> rects;
57
83
  std::vector<PaintText> texts;
58
84
  std::vector<PaintImage> images;
85
+ /** Shapes in painter order (backgrounds/borders first, then children, then
86
+ * stacked siblings), each with the clip rectangle that bounds it. */
87
+ std::vector<PaintOp> ops;
59
88
 
60
89
  void clear() {
61
90
  rects.clear();
62
91
  texts.clear();
63
92
  images.clear();
93
+ ops.clear();
64
94
  }
65
95
  bool empty() const { return rects.empty() && texts.empty() && images.empty(); }
66
- /** Human-readable dump, one shape per line (for diagnostics/tests). */
96
+ /** Human-readable dump, one shape per line, in painter order (diagnostics/tests). */
67
97
  std::string dump() const;
68
98
  };
69
99
 
@@ -28,7 +28,6 @@ SDL_GPUGraphicsPipeline *g_image_pipeline = nullptr;
28
28
  SDL_GPUSampler *g_sampler = nullptr;
29
29
  int g_renderer_failed = 0;
30
30
  bool g_text_ok = false;
31
- std::vector<ImageBatch> g_image_batches;
32
31
  std::unordered_map<std::string, SDL_GPUTexture *> g_image_textures;
33
32
  SDL_GPUTexture *g_atlas_texture = nullptr;
34
33
  std::vector<unsigned char> g_atlas_mirror;
@@ -246,7 +245,6 @@ void xt_gui_renderer_destroy(SDL_GPUDevice *device) {
246
245
  if (entry.second != nullptr) SDL_ReleaseGPUTexture(device, entry.second);
247
246
  }
248
247
  g_image_textures.clear();
249
- g_image_batches.clear();
250
248
  if (g_atlas_texture != nullptr) {
251
249
  SDL_ReleaseGPUTexture(device, g_atlas_texture);
252
250
  g_atlas_texture = nullptr;
@@ -277,21 +275,126 @@ void xt_gui_geometry_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
277
275
  using namespace xtgui::renderer_detail;
278
276
  std::vector<Vertex> verts;
279
277
  verts.reserve(list.rects.size() * 6);
278
+ geometry->op_counts.assign(list.rects.size(), 6);
280
279
  for (const xtgui::PaintRect &rect : list.rects) pushRect(verts, rect);
281
280
  uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(Vertex));
282
281
  }
283
282
 
283
+ void xt_gui_build_runs(XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
284
+ const xtgui::DisplayList &list) {
285
+ shapes->runs.clear();
286
+ uint32_t cursor[3] = {0, 0, 0};
287
+ for (const xtgui::PaintOp &op : list.ops) {
288
+ int pipeline = (int)op.kind;
289
+ XtGuiGeometry *geometry = pipeline == 0 ? shapes : (pipeline == 1 ? texts : images);
290
+ if (op.index >= geometry->op_counts.size()) continue;
291
+ uint32_t count = geometry->op_counts[op.index];
292
+ uint32_t first = cursor[pipeline];
293
+ cursor[pipeline] += count;
294
+ if (count == 0) continue;
295
+ SDL_GPUTexture *texture = nullptr;
296
+ if (pipeline == 2 && op.index < images->op_textures.size()) texture = images->op_textures[op.index];
297
+ float cx = op.clip.active ? op.clip.x : 0.0f;
298
+ float cy = op.clip.active ? op.clip.y : 0.0f;
299
+ float cw = op.clip.active ? op.clip.width : 0.0f;
300
+ float ch = op.clip.active ? op.clip.height : 0.0f;
301
+ if (!shapes->runs.empty()) {
302
+ XtGuiDrawRun &last = shapes->runs.back();
303
+ if (last.pipeline == pipeline && last.first + last.count == first && last.clip_x == cx &&
304
+ last.clip_y == cy && last.clip_width == cw && last.clip_height == ch &&
305
+ last.texture == texture) {
306
+ last.count += count;
307
+ continue;
308
+ }
309
+ }
310
+ XtGuiDrawRun run;
311
+ run.pipeline = pipeline;
312
+ run.first = first;
313
+ run.count = count;
314
+ run.clip_x = cx;
315
+ run.clip_y = cy;
316
+ run.clip_width = cw;
317
+ run.clip_height = ch;
318
+ run.texture = texture;
319
+ shapes->runs.push_back(run);
320
+ }
321
+ }
322
+
284
323
  void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
285
- XtGuiGeometry *geometry, float viewport_width, float viewport_height) {
324
+ XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
325
+ float viewport_width, float viewport_height, float pixel_density) {
286
326
  using namespace xtgui::renderer_detail;
287
- if (g_pipeline == nullptr || geometry->buffer == nullptr || geometry->vertex_count == 0) return;
288
327
  if (viewport_width <= 0.0f || viewport_height <= 0.0f) return;
289
- SDL_BindGPUGraphicsPipeline(pass, g_pipeline);
290
- SDL_GPUBufferBinding binding;
291
- binding.buffer = geometry->buffer;
292
- binding.offset = 0;
293
- SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
328
+ if (pixel_density <= 0.0f) pixel_density = 1.0f;
294
329
  float viewport[2] = {viewport_width, viewport_height};
295
330
  SDL_PushGPUVertexUniformData(cmd, 0, viewport, sizeof(viewport));
296
- SDL_DrawGPUPrimitives(pass, geometry->vertex_count, 1, 0, 0);
331
+
332
+ int target_width = (int)(viewport_width * pixel_density + 0.5f);
333
+ int target_height = (int)(viewport_height * pixel_density + 0.5f);
334
+ for (const XtGuiDrawRun &run : shapes->runs) {
335
+ SDL_Rect scissor;
336
+ scissor.x = 0;
337
+ scissor.y = 0;
338
+ scissor.w = target_width;
339
+ scissor.h = target_height;
340
+ if (run.clip_width > 0.0f && run.clip_height > 0.0f) {
341
+ int x0 = (int)(run.clip_x * pixel_density);
342
+ int y0 = (int)(run.clip_y * pixel_density);
343
+ int x1 = (int)((run.clip_x + run.clip_width) * pixel_density + 0.5f);
344
+ int y1 = (int)((run.clip_y + run.clip_height) * pixel_density + 0.5f);
345
+ if (x0 < 0) x0 = 0;
346
+ if (y0 < 0) y0 = 0;
347
+ if (x1 > target_width) x1 = target_width;
348
+ if (y1 > target_height) y1 = target_height;
349
+ scissor.x = x0;
350
+ scissor.y = y0;
351
+ scissor.w = x1 > x0 ? x1 - x0 : 0;
352
+ scissor.h = y1 > y0 ? y1 - y0 : 0;
353
+ }
354
+ SDL_SetGPUScissor(pass, &scissor);
355
+
356
+ if (run.pipeline == XtGuiDrawRun::Shape) {
357
+ if (g_pipeline == nullptr || shapes->buffer == nullptr) continue;
358
+ SDL_BindGPUGraphicsPipeline(pass, g_pipeline);
359
+ SDL_GPUBufferBinding binding;
360
+ binding.buffer = shapes->buffer;
361
+ binding.offset = 0;
362
+ SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
363
+ SDL_DrawGPUPrimitives(pass, run.count, 1, run.first, 0);
364
+ } else if (run.pipeline == XtGuiDrawRun::Text) {
365
+ if (!g_text_ok || g_atlas_texture == nullptr || texts->buffer == nullptr) continue;
366
+ SDL_BindGPUGraphicsPipeline(pass, g_text_pipeline);
367
+ SDL_GPUBufferBinding binding;
368
+ binding.buffer = texts->buffer;
369
+ binding.offset = 0;
370
+ SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
371
+ SDL_GPUTextureSamplerBinding atlas_binding;
372
+ atlas_binding.texture = g_atlas_texture;
373
+ atlas_binding.sampler = g_sampler;
374
+ SDL_BindGPUFragmentSamplers(pass, 0, &atlas_binding, 1);
375
+ SDL_DrawGPUPrimitives(pass, run.count, 1, run.first, 0);
376
+ } else {
377
+ if (g_image_pipeline == nullptr || g_sampler == nullptr || images->buffer == nullptr ||
378
+ run.texture == nullptr)
379
+ continue;
380
+ SDL_BindGPUGraphicsPipeline(pass, g_image_pipeline);
381
+ SDL_GPUBufferBinding binding;
382
+ binding.buffer = images->buffer;
383
+ binding.offset = 0;
384
+ SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
385
+ SDL_GPUTextureSamplerBinding texture_binding;
386
+ texture_binding.texture = run.texture;
387
+ texture_binding.sampler = g_sampler;
388
+ SDL_BindGPUFragmentSamplers(pass, 0, &texture_binding, 1);
389
+ SDL_DrawGPUPrimitives(pass, run.count, 1, run.first, 0);
390
+ }
391
+ }
392
+
393
+ /* Restore the full-target scissor so the next frame starts clean. */
394
+ SDL_Rect full;
395
+ full.x = 0;
396
+ full.y = 0;
397
+ full.w = target_width;
398
+ full.h = target_height;
399
+ SDL_SetGPUScissor(pass, &full);
297
400
  }
@@ -22,17 +22,43 @@
22
22
 
23
23
  #include <SDL3/SDL.h>
24
24
  #include <cstdint>
25
+ #include <vector>
25
26
 
26
27
  namespace xtgui {
27
28
  struct DisplayList;
28
29
  }
29
30
 
31
+ /** One draw call in painter order: a contiguous range of vertices in one of the
32
+ * three pipelines, with the clip rectangle (logical pixels) that bounds it. A
33
+ * `clip_width <= 0` means the whole viewport. For image runs, `texture` is the
34
+ * bound texture. */
35
+ struct XtGuiDrawRun {
36
+ enum Pipeline { Shape = 0, Text = 1, Image = 2 };
37
+ int pipeline = Shape;
38
+ uint32_t first = 0;
39
+ uint32_t count = 0;
40
+ float clip_x = 0;
41
+ float clip_y = 0;
42
+ float clip_width = 0;
43
+ float clip_height = 0;
44
+ SDL_GPUTexture *texture = nullptr;
45
+ };
46
+
30
47
  /** Per-window GPU geometry (vertex buffer + draw range). */
31
48
  struct XtGuiGeometry {
32
49
  SDL_GPUBuffer *buffer = nullptr;
33
50
  uint32_t capacity_bytes = 0;
34
51
  uint32_t vertex_count = 0;
35
52
  bool dirty = true;
53
+ /** Vertex count contributed by each shape of this pipeline's kind, aligned
54
+ * with the display list's payload vector (used to build the run list). */
55
+ std::vector<uint32_t> op_counts;
56
+ /** Texture for each image op, aligned with `DisplayList::images` (image
57
+ * geometry only). */
58
+ std::vector<SDL_GPUTexture *> op_textures;
59
+ /** Painter-order draw runs, built from the display list by
60
+ * `xt_gui_build_runs` and consumed by `xt_gui_renderer_draw`. */
61
+ std::vector<XtGuiDrawRun> runs;
36
62
  };
37
63
 
38
64
  /** Create the shared pipelines if needed. Returns false when the backend has no
@@ -47,22 +73,36 @@ void xt_gui_geometry_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
47
73
  const xtgui::DisplayList &list);
48
74
  /** Draw `geometry` inside an active render pass. */
49
75
  void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
50
- XtGuiGeometry *geometry, float viewport_width, float viewport_height);
76
+ XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
77
+ float viewport_width, float viewport_height, float pixel_density);
51
78
  /** Shape `list.texts`, ensure their glyphs are in the atlas, and upload the
52
79
  * text geometry. `scale` is the device pixel density (1.0 = logical pixels). */
53
80
  void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
54
81
  const xtgui::DisplayList &list, float scale);
55
- /** Draw the text geometry (glyph atlas bound) inside an active render pass. */
56
- void xt_gui_text_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass, XtGuiGeometry *geometry,
57
- float viewport_width, float viewport_height);
58
82
  /** Decode/upload `list.images` and build the image quad geometry (one batch per
59
- * texture). Textures are cached by source path. */
83
+ * texture). Textures are cached by source path and pixel size. `scale` is the
84
+ * device pixel density (1.0 = logical pixels) used to pick the rasterisation
85
+ * size of a vector source (SVG); raster sources decode at their intrinsic size. */
60
86
  void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
61
- const xtgui::DisplayList &list);
62
- /** Draw the image geometry (binds each batch's texture) inside a render pass. */
63
- void xt_gui_image_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass, XtGuiGeometry *geometry,
64
- float viewport_width, float viewport_height);
87
+ const xtgui::DisplayList &list, float scale);
65
88
  /** Release the image texture cache (after SDL_WaitForGPUIdle). */
66
89
  void xt_gui_image_textures_destroy(SDL_GPUDevice *device);
90
+ /** Build the painter-order run list from the display list, using the per-op
91
+ * vertex counts and textures recorded by the three upload functions. Stores the
92
+ * result in `shapes->runs`. */
93
+ void xt_gui_build_runs(XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
94
+ const xtgui::DisplayList &list);
95
+ /** Re-upload the current decoded frame of every live `<video>` texture. Called
96
+ * 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);
99
+ /** Release one media texture (when a `<video>` is removed or navigated away). */
100
+ 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
102
+ * 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);
105
+ /** Release one canvas texture (when the canvas is dropped). */
106
+ void xt_gui_canvas_texture_release(SDL_GPUDevice *device, uint64_t canvas_id);
67
107
 
68
108
  #endif /* XT_GUI_RENDERER_H */