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.
- package/README.md +59 -4
- package/README.zh-CN.md +52 -2
- package/dist/tests/e2e/gui-advanced.test.d.ts +6 -0
- package/dist/tests/e2e/gui-advanced.test.js +261 -0
- package/dist/tests/e2e/gui-advanced.test.js.map +1 -0
- package/dist/tests/e2e/gui-canvas.test.d.ts +9 -0
- package/dist/tests/e2e/gui-canvas.test.js +233 -0
- package/dist/tests/e2e/gui-canvas.test.js.map +1 -0
- package/dist/tests/e2e/gui-example.test.d.ts +1 -1
- package/dist/tests/e2e/gui-example.test.js +2 -2
- package/dist/tests/e2e/gui-helpers.d.ts +3 -0
- package/dist/tests/e2e/gui-helpers.js +5 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-layout.test.js +73 -0
- package/dist/tests/e2e/gui-layout.test.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.d.ts +8 -0
- package/dist/tests/e2e/gui-media.test.js +119 -0
- package/dist/tests/e2e/gui-media.test.js.map +1 -0
- package/dist/tests/e2e/gui-paint.test.js +40 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/doc/ai/extensions.md +16 -3
- package/doc/ai/zh-CN/extensions.md +10 -3
- package/doc/gui/animation.md +46 -0
- package/doc/gui/api.md +92 -0
- package/doc/gui/build.md +91 -0
- package/doc/gui/canvas.md +98 -0
- package/doc/gui/dom.md +123 -0
- package/doc/gui/input.md +22 -0
- package/doc/gui/known-issues.md +66 -0
- package/doc/gui/layout.md +72 -0
- package/doc/gui/media.md +87 -0
- package/doc/gui/rendering.md +98 -0
- package/doc/gui/runtime.md +88 -0
- package/doc/gui-scripts.md +7 -3
- package/doc/gui.md +80 -612
- package/doc/implemented.md +1 -0
- package/doc/zh-CN/gui/animation.md +46 -0
- package/doc/zh-CN/gui/api.md +88 -0
- package/doc/zh-CN/gui/build.md +84 -0
- package/doc/zh-CN/gui/canvas.md +80 -0
- package/doc/zh-CN/gui/dom.md +114 -0
- package/doc/zh-CN/gui/input.md +21 -0
- package/doc/zh-CN/gui/known-issues.md +50 -0
- package/doc/zh-CN/gui/layout.md +60 -0
- package/doc/zh-CN/gui/media.md +71 -0
- package/doc/zh-CN/gui/rendering.md +81 -0
- package/doc/zh-CN/gui/runtime.md +76 -0
- package/doc/zh-CN/gui-scripts.md +6 -2
- package/doc/zh-CN/gui.md +64 -532
- package/doc/zh-CN/implemented.md +1 -0
- package/doc/zh-CN/post/xbintsc-build-pelican.md +2 -2
- package/llms.txt +15 -2
- package/package.json +1 -1
- package/runtime/ext_gui/canvas.cpp +580 -0
- package/runtime/ext_gui/canvas.h +170 -0
- package/runtime/ext_gui/canvas_internal.h +59 -0
- package/runtime/ext_gui/canvas_raster.cpp +254 -0
- package/runtime/ext_gui/document.cpp +24 -1
- package/runtime/ext_gui/document.h +11 -0
- package/runtime/ext_gui/dom_api.cpp +3 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +548 -0
- package/runtime/ext_gui/dom_api_internal.h +73 -0
- package/runtime/ext_gui/dom_api_media.cpp +274 -0
- package/runtime/ext_gui/dom_api_proto.cpp +80 -0
- package/runtime/ext_gui/gui.cpp +6 -0
- package/runtime/ext_gui/gui_canvas.cpp +95 -0
- package/runtime/ext_gui/gui_engine.h +49 -0
- package/runtime/ext_gui/gui_events.cpp +21 -5
- package/runtime/ext_gui/gui_media.cpp +193 -0
- package/runtime/ext_gui/image.cpp +31 -12
- package/runtime/ext_gui/image.h +20 -7
- package/runtime/ext_gui/layout.cpp +52 -5
- package/runtime/ext_gui/layout.h +33 -1
- package/runtime/ext_gui/layout_flex.cpp +263 -99
- package/runtime/ext_gui/layout_flow.cpp +85 -9
- package/runtime/ext_gui/layout_flow_internal.h +84 -5
- package/runtime/ext_gui/layout_inline.cpp +54 -37
- package/runtime/ext_gui/media.cpp +514 -0
- package/runtime/ext_gui/media.h +104 -0
- package/runtime/ext_gui/nanosvg.h +3278 -0
- package/runtime/ext_gui/nanosvgrast.h +1472 -0
- package/runtime/ext_gui/paint.cpp +123 -38
- package/runtime/ext_gui/paint.h +36 -6
- package/runtime/ext_gui/renderer.cpp +113 -10
- package/runtime/ext_gui/renderer.h +49 -9
- package/runtime/ext_gui/renderer_image.cpp +203 -77
- package/runtime/ext_gui/renderer_internal.h +6 -8
- package/runtime/ext_gui/renderer_text.cpp +11 -26
- package/runtime/ext_gui/style.h +11 -0
- package/runtime/ext_gui/style_print.cpp +11 -0
- package/runtime/ext_gui/style_ua.cpp +7 -0
- package/runtime/ext_gui/style_values.cpp +40 -1
- package/runtime/ext_gui/svg.cpp +182 -0
- package/runtime/ext_gui/svg.h +46 -0
- package/runtime/ext_gui/text.cpp +156 -25
- package/runtime/ext_gui/text.h +11 -2
- package/runtime/ext_gui/window.cpp +4 -1
- package/scripts/build-gui.ts +148 -6
- package/scripts/check-file-length.ts +5 -1
- 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
|
|
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
|
|
81
|
-
*
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
addRect(out, box->x, box->y,
|
|
96
|
-
addRect(out, box->x
|
|
97
|
-
|
|
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->
|
|
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
|
-
|
|
171
|
+
addImage(image, out, clip);
|
|
109
172
|
}
|
|
110
173
|
}
|
|
111
174
|
}
|
|
112
175
|
|
|
113
|
-
|
|
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) {
|
|
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[
|
|
123
|
-
for (const
|
|
124
|
-
std::
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
}
|
package/runtime/ext_gui/paint.h
CHANGED
|
@@ -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
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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 (
|
|
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 *
|
|
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
|
-
|
|
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
|
-
|
|
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 *
|
|
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 */
|