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