xbintsc 0.5.38 → 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/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/html.js +9 -2
- package/dist/src/extensions/gui/html.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.js +9 -1
- package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -1
- 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/extensions/gui.test.js +23 -0
- 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 +38 -19
- package/doc/gui.md +33 -1
- 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 +27 -5
- package/doc/zh-CN/gui.md +25 -2
- 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 +19 -9
- 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 +30 -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/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_scan.cpp +3 -8
- 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 +22 -30
- package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
- package/runtime/ext_gui/window.cpp +56 -1
- package/src/codegen/generator/module.ts +22 -7
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/html.ts +9 -2
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
#include <vector>
|
|
14
14
|
|
|
15
15
|
#include "dom_api_webgl_rt.h"
|
|
16
|
+
#include "canvas.h"
|
|
16
17
|
#include "image.h"
|
|
17
18
|
#include "image_bitmap.h"
|
|
18
19
|
#include "webgl.h"
|
|
@@ -100,6 +101,23 @@ bool webgl_read_source(xt_value source, int *width, int *height, std::vector<uin
|
|
|
100
101
|
return true;
|
|
101
102
|
}
|
|
102
103
|
}
|
|
104
|
+
if (XT_IS_OBJECT(source)) {
|
|
105
|
+
xt_value canvas_id = xt_object_get_cstr(source, "__xt_gui_canvas");
|
|
106
|
+
if (XT_IS_NUMBER(canvas_id)) {
|
|
107
|
+
xtgui::Canvas2D *canvas = xtgui::xt_canvas_by_id((uint64_t)xt_to_number(canvas_id));
|
|
108
|
+
if (canvas == nullptr || canvas->image().width <= 0) return false;
|
|
109
|
+
const xtgui::Image &image = canvas->image();
|
|
110
|
+
*width = image.width;
|
|
111
|
+
*height = image.height;
|
|
112
|
+
if (premultiply) {
|
|
113
|
+
webgl_convert_pixels(image.pixels.data(), image.width * image.height, GL_RGBA, true,
|
|
114
|
+
rgba);
|
|
115
|
+
} else {
|
|
116
|
+
*rgba = image.pixels;
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
103
121
|
XtGuiWindow *win = nullptr;
|
|
104
122
|
xtgui::Node *node = resolve_node(source, &win);
|
|
105
123
|
const xtgui::Image *image = nullptr;
|
|
@@ -392,7 +392,7 @@ bool parseRadialGradient(const std::string &body, BackgroundLayer *layer) {
|
|
|
392
392
|
return true;
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
/** Parse one layer, appending it when it is a supported gradient. */
|
|
395
|
+
/** Parse one layer, appending it when it is a supported gradient or image. */
|
|
396
396
|
bool parseLayer(const std::string &part, BackgroundLayer *layer) {
|
|
397
397
|
std::string body;
|
|
398
398
|
if (findCall(part, "linear-gradient", &body) ||
|
|
@@ -403,6 +403,17 @@ bool parseLayer(const std::string &part, BackgroundLayer *layer) {
|
|
|
403
403
|
findCall(part, "repeating-radial-gradient", &body)) {
|
|
404
404
|
return parseRadialGradient(body, layer);
|
|
405
405
|
}
|
|
406
|
+
if (findCall(part, "url", &body)) {
|
|
407
|
+
std::string source = xt_css_trim(body);
|
|
408
|
+
if (source.size() >= 2 && (source.front() == '"' || source.front() == '\'') &&
|
|
409
|
+
source.back() == source.front()) {
|
|
410
|
+
source = source.substr(1, source.size() - 2);
|
|
411
|
+
}
|
|
412
|
+
if (source.empty()) return false;
|
|
413
|
+
layer->kind = BackgroundKind::Image;
|
|
414
|
+
layer->image_src = source;
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
406
417
|
return false;
|
|
407
418
|
}
|
|
408
419
|
|
package/runtime/ext_gui/gui.cpp
CHANGED
|
@@ -194,6 +194,7 @@ void xt_gui_quit_window(XtGuiWindow *win) {
|
|
|
194
194
|
win->document_object = XT_UNDEFINED;
|
|
195
195
|
win->document.reset();
|
|
196
196
|
win->html.clear();
|
|
197
|
+
win->display_list.clear();
|
|
197
198
|
if (g_device != NULL) {
|
|
198
199
|
xt_gui_geometry_destroy(g_device, &win->geometry);
|
|
199
200
|
xt_gui_geometry_destroy(g_device, &win->text_geometry);
|
|
@@ -253,6 +254,10 @@ static XtGuiWindow *create_window_record(const char *title, int width, int heigh
|
|
|
253
254
|
record->window = window;
|
|
254
255
|
record->open = 1;
|
|
255
256
|
record->ready = 0;
|
|
257
|
+
record->present_dirty = 1;
|
|
258
|
+
record->expose_pending = 0;
|
|
259
|
+
record->present_deferred = 0;
|
|
260
|
+
record->sync_dirty = 1;
|
|
256
261
|
record->width = width;
|
|
257
262
|
record->height = height;
|
|
258
263
|
record->html.clear();
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
#include "document.h"
|
|
16
16
|
#include "gui_engine.h"
|
|
17
17
|
#include "gui_internal.h"
|
|
18
|
+
#include "perf.h"
|
|
18
19
|
|
|
19
20
|
namespace {
|
|
20
21
|
|
|
@@ -25,6 +26,7 @@ bool isCanvasElement(const xtgui::Node *node) {
|
|
|
25
26
|
/** Canvas children are fallback content, so a canvas element ends the walk. */
|
|
26
27
|
void collectCanvases(const xtgui::Node *node, std::vector<const xtgui::Node *> *out) {
|
|
27
28
|
if (node == nullptr) return;
|
|
29
|
+
xtgui::g_perf_frame.syncNodes += 1;
|
|
28
30
|
if (isCanvasElement(node)) {
|
|
29
31
|
out->push_back(node);
|
|
30
32
|
return;
|
|
@@ -137,6 +139,8 @@ void xt_gui_canvas_shutdown(XtGuiWindow *win) {
|
|
|
137
139
|
xt_gui_webgl_shutdown(win);
|
|
138
140
|
for (auto &entry : win->canvases) releaseCanvasTexture(entry.second.get());
|
|
139
141
|
win->canvases.clear();
|
|
142
|
+
for (auto &entry : win->offscreen_canvases) releaseCanvasTexture(entry.second.get());
|
|
143
|
+
win->offscreen_canvases.clear();
|
|
140
144
|
/* `pending_canvases` survives `loadHTML`: a module that asked for a context
|
|
141
145
|
* before the page existed still needs its bitmap when the parsed element
|
|
142
146
|
* binds. They are released with the window record. */
|
|
@@ -92,6 +92,7 @@ xt_value xt_gui_audio_create(int32_t argc, xt_value *argv);
|
|
|
92
92
|
#include "canvas.h"
|
|
93
93
|
#include "document.h"
|
|
94
94
|
#include "media.h"
|
|
95
|
+
#include "paint.h"
|
|
95
96
|
#include "renderer.h"
|
|
96
97
|
#include "webgl.h"
|
|
97
98
|
|
|
@@ -159,6 +160,10 @@ struct XtGuiWindow {
|
|
|
159
160
|
XtGuiGeometry geometry;
|
|
160
161
|
XtGuiGeometry text_geometry;
|
|
161
162
|
XtGuiGeometry image_geometry;
|
|
163
|
+
/** The frame's display list. Kept on the window so its payload vectors keep
|
|
164
|
+
* their capacity across frames instead of being reallocated from zero on
|
|
165
|
+
* every repaint. */
|
|
166
|
+
xtgui::DisplayList display_list;
|
|
162
167
|
float background[4];
|
|
163
168
|
int width;
|
|
164
169
|
int height;
|
|
@@ -188,6 +193,28 @@ struct XtGuiWindow {
|
|
|
188
193
|
std::vector<XtGuiWindowListener> window_listeners;
|
|
189
194
|
/** Set by a DOM mutation; consumed by the frame loop before repainting. */
|
|
190
195
|
int struct_dirty = 0;
|
|
196
|
+
/** Set when something may have changed on screen (a mutation, a scroll, an
|
|
197
|
+
* animation frame, a new video/canvas frame, an expose event, …). The frame
|
|
198
|
+
* loop skips acquiring a swapchain and recording a render pass while it is
|
|
199
|
+
* clear, so a static page does no GPU work at all; `XT_GUI_ALWAYS_PRESENT=1`
|
|
200
|
+
* disables the gate. */
|
|
201
|
+
int present_dirty = 1;
|
|
202
|
+
/** Set when the window system asked for a redraw (map/expose/restore) since
|
|
203
|
+
* the last frame. Several such events coalesce into one present, so the frame
|
|
204
|
+
* loop charges at most one `exposes` counter per frame — that way
|
|
205
|
+
* `presents - exposes - deferred` is the work the page caused, and a present
|
|
206
|
+
* can be attributed to the compositor rather than to the document (a window
|
|
207
|
+
* server delivers its first `Expose` whenever it maps the window, which no
|
|
208
|
+
* test can schedule). */
|
|
209
|
+
int expose_pending = 0;
|
|
210
|
+
/** Set when a frame could not get a swapchain image and left its present
|
|
211
|
+
* pending (see `xt_gui_render_window`). The present that finally happens
|
|
212
|
+
* carries that earlier frame's work, so it is charged to `deferred`. */
|
|
213
|
+
int present_deferred = 0;
|
|
214
|
+
/** Set when the media/canvas tables must be reconciled with the document
|
|
215
|
+
* without a restyle (a replaced document, for example): the frame loop then
|
|
216
|
+
* runs the DOM walk once even though no style changed. */
|
|
217
|
+
int sync_dirty = 0;
|
|
191
218
|
/** Callbacks queued for the next frame, plus the id counter. */
|
|
192
219
|
std::vector<XtGuiAnimationFrame> animation_frames;
|
|
193
220
|
/** One decoder per `<video>`/`<audio>` element, keyed by the element node. The
|
|
@@ -206,6 +233,9 @@ struct XtGuiWindow {
|
|
|
206
233
|
* initialises). Keyed by canvas id; moved into `canvases` when the pending
|
|
207
234
|
* handle binds to the parsed element. */
|
|
208
235
|
std::unordered_map<uint64_t, std::unique_ptr<xtgui::Canvas2D>> pending_canvases;
|
|
236
|
+
/** `new OffscreenCanvas(w, h)` bitmaps, keyed by their canvas id. They are
|
|
237
|
+
* not part of the DOM; they exist until the window closes. */
|
|
238
|
+
std::unordered_map<uint64_t, std::unique_ptr<xtgui::Canvas2D>> offscreen_canvases;
|
|
209
239
|
/** The batch currently running: its callbacks have been swapped out of
|
|
210
240
|
* `animation_frames`, and a collection triggered by one of them must still
|
|
211
241
|
* see the rest. Kept on the record (not a local) so the GC root provider can
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#include "listener_options.h"
|
|
8
8
|
|
|
9
9
|
#include "paint.h"
|
|
10
|
+
#include "perf.h"
|
|
10
11
|
|
|
11
12
|
#include <algorithm>
|
|
12
13
|
#include <stdio.h>
|
|
@@ -69,6 +70,7 @@ void xt_gui_flush_dom(XtGuiWindow *win) {
|
|
|
69
70
|
xt_gui_window_viewport(win, &width, &height);
|
|
70
71
|
win->document->restyle(width, height);
|
|
71
72
|
win->geometry.dirty = 1;
|
|
73
|
+
win->present_dirty = 1;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
/* -- page scroll ---------------------------------------------------------- */
|
|
@@ -115,6 +117,7 @@ void xt_gui_scroll_to(XtGuiWindow *win, float x, float y) {
|
|
|
115
117
|
win->scroll_x = x;
|
|
116
118
|
win->scroll_y = y;
|
|
117
119
|
win->geometry.dirty = 1;
|
|
120
|
+
win->present_dirty = 1;
|
|
118
121
|
xt_gui_emit(win, "scroll");
|
|
119
122
|
}
|
|
120
123
|
|
|
@@ -145,14 +148,21 @@ std::string xt_gui_hit_test(XtGuiWindow *win, float x, float y) {
|
|
|
145
148
|
return node == nullptr ? std::string() : xtgui::xt_dom_describe(node);
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
/** Update the hovered element from
|
|
149
|
-
|
|
151
|
+
/** Update the hovered element from an already resolved target node; marks paint
|
|
152
|
+
* dirty when the hover state (and therefore the styles) changed. */
|
|
153
|
+
static void xt_gui_update_hover(XtGuiWindow *win, const xtgui::Node *node) {
|
|
150
154
|
if (win->document == nullptr) return;
|
|
151
|
-
if (win->document->setHover(
|
|
155
|
+
if (win->document->setHover(node)) {
|
|
156
|
+
win->geometry.dirty = 1;
|
|
157
|
+
win->present_dirty = 1;
|
|
158
|
+
}
|
|
152
159
|
}
|
|
153
160
|
|
|
161
|
+
/** Pointer payload. `node` is the hit-test result the caller already resolved,
|
|
162
|
+
* so one event does not walk the layout tree three times. */
|
|
154
163
|
static xt_value xt_gui_pointer_payload(const char *type, XtGuiWindow *win, float x, float y,
|
|
155
|
-
int button, int clicks) {
|
|
164
|
+
int button, int clicks, const xtgui::Node *node) {
|
|
165
|
+
(void)win;
|
|
156
166
|
xt_value payload = xt_object_new();
|
|
157
167
|
xt_object_set(payload, xt_string_from_cstr("type"), xt_string_from_cstr(type));
|
|
158
168
|
xt_object_set(payload, xt_string_from_cstr("x"), xt_number(x));
|
|
@@ -161,9 +171,12 @@ static xt_value xt_gui_pointer_payload(const char *type, XtGuiWindow *win, float
|
|
|
161
171
|
xt_object_set(payload, xt_string_from_cstr("clientY"), xt_number(y));
|
|
162
172
|
xt_object_set(payload, xt_string_from_cstr("button"), xt_number(button));
|
|
163
173
|
xt_object_set(payload, xt_string_from_cstr("clicks"), xt_number(clicks));
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
if (node != nullptr) {
|
|
175
|
+
std::string target = xtgui::xt_dom_describe(node);
|
|
176
|
+
if (!target.empty()) {
|
|
177
|
+
xt_object_set(payload, xt_string_from_cstr("target"),
|
|
178
|
+
xt_string_from_cstr(target.c_str()));
|
|
179
|
+
}
|
|
167
180
|
}
|
|
168
181
|
return payload;
|
|
169
182
|
}
|
|
@@ -171,22 +184,25 @@ static xt_value xt_gui_pointer_payload(const char *type, XtGuiWindow *win, float
|
|
|
171
184
|
void xt_gui_dispatch_pointer(XtGuiWindow *win, const char *type, float x, float y, int button,
|
|
172
185
|
int clicks) {
|
|
173
186
|
if (win == NULL || !win->open) return;
|
|
174
|
-
xt_gui_update_hover(win, x, y);
|
|
175
187
|
const xtgui::Node *node = xt_gui_hit_node(win, x, y);
|
|
188
|
+
xt_gui_update_hover(win, node);
|
|
176
189
|
if (strcmp(type, "mousedown") == 0 && win->document != nullptr) {
|
|
177
|
-
if (win->document->setFocus(node))
|
|
190
|
+
if (win->document->setFocus(node)) {
|
|
191
|
+
win->geometry.dirty = 1;
|
|
192
|
+
win->present_dirty = 1;
|
|
193
|
+
}
|
|
178
194
|
}
|
|
179
|
-
xt_value payload = xt_gui_pointer_payload(type, win, x, y, button, clicks);
|
|
195
|
+
xt_value payload = xt_gui_pointer_payload(type, win, x, y, button, clicks, node);
|
|
180
196
|
if (node != nullptr) xt_gui_emit_dom_event(win, type, node, payload);
|
|
181
197
|
xt_gui_emit_payload(win, type, payload);
|
|
182
198
|
}
|
|
183
199
|
|
|
184
200
|
void xt_gui_dispatch_wheel(XtGuiWindow *win, float x, float y, float delta_x, float delta_y) {
|
|
185
201
|
if (win == NULL || !win->open) return;
|
|
186
|
-
|
|
202
|
+
const xtgui::Node *node = xt_gui_hit_node(win, x, y);
|
|
203
|
+
xt_value payload = xt_gui_pointer_payload("wheel", win, x, y, -1, 0, node);
|
|
187
204
|
xt_object_set(payload, xt_string_from_cstr("deltaX"), xt_number(delta_x));
|
|
188
205
|
xt_object_set(payload, xt_string_from_cstr("deltaY"), xt_number(delta_y));
|
|
189
|
-
const xtgui::Node *node = xt_gui_hit_node(win, x, y);
|
|
190
206
|
if (node != nullptr) xt_gui_emit_dom_event(win, "wheel", node, payload);
|
|
191
207
|
xt_gui_emit_payload(win, "wheel", payload);
|
|
192
208
|
/* Default action: scroll the page (content that fits does not move). */
|
|
@@ -234,12 +250,26 @@ void handle_event(const SDL_Event *event) {
|
|
|
234
250
|
win->height = event->window.data2;
|
|
235
251
|
if (win->document != NULL) win->document->restyle((float)win->width, (float)win->height);
|
|
236
252
|
win->geometry.dirty = 1;
|
|
253
|
+
win->present_dirty = 1;
|
|
237
254
|
/* Web 'resize', so a document can re-measure on a window change in every
|
|
238
255
|
* host (a browser already fires one). */
|
|
239
256
|
xt_gui_emit(win, "resize");
|
|
240
257
|
xt_gui_media_lists_notify(win);
|
|
241
258
|
return;
|
|
242
259
|
}
|
|
260
|
+
/* A window the compositor revealed (uncovered, restored, shown) must be
|
|
261
|
+
* redrawn even though nothing in the document changed: the frame loop only
|
|
262
|
+
* presents on demand. The request is charged to `exposes` (below) so
|
|
263
|
+
* `stats()` can separate it from a present the page caused. */
|
|
264
|
+
if (event->type == SDL_EVENT_WINDOW_EXPOSED || event->type == SDL_EVENT_WINDOW_SHOWN ||
|
|
265
|
+
event->type == SDL_EVENT_WINDOW_RESTORED) {
|
|
266
|
+
XtGuiWindow *win = xt_gui_find_by_window_id(event->window.windowID);
|
|
267
|
+
if (win != NULL) {
|
|
268
|
+
win->present_dirty = 1;
|
|
269
|
+
win->expose_pending = 1;
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
243
273
|
|
|
244
274
|
if (event->type == SDL_EVENT_MOUSE_MOTION) {
|
|
245
275
|
XtGuiWindow *win = xt_gui_find_by_window_id(event->motion.windowID);
|
|
@@ -309,6 +339,13 @@ void xt_gui_run_animation_frames(XtGuiWindow *win, double timestamp_ms) {
|
|
|
309
339
|
|
|
310
340
|
void xt_gui_render_window(XtGuiWindow *win) {
|
|
311
341
|
if (win == NULL || !win->open || win->window == NULL || g_device == NULL) return;
|
|
342
|
+
xtgui::xt_perf_frame_begin();
|
|
343
|
+
/* The events were dispatched before the frame began. A redraw the window
|
|
344
|
+
* system asked for is charged to `exposes` once this frame actually presents
|
|
345
|
+
* (several expose events coalesce into one present); the flag is consumed
|
|
346
|
+
* here because only this frame may claim it. */
|
|
347
|
+
bool window_requested = win->expose_pending != 0;
|
|
348
|
+
win->expose_pending = 0;
|
|
312
349
|
float viewport_width = 0;
|
|
313
350
|
float viewport_height = 0;
|
|
314
351
|
xt_gui_window_viewport(win, &viewport_width, &viewport_height);
|
|
@@ -318,77 +355,164 @@ void xt_gui_render_window(XtGuiWindow *win) {
|
|
|
318
355
|
double delta = win->last_frame_ms > 0.0 ? now - win->last_frame_ms : 0.0;
|
|
319
356
|
win->last_frame_ms = now;
|
|
320
357
|
/* Animation-frame callbacks run before layout, so any DOM mutation they make
|
|
321
|
-
* is picked up by this same frame.
|
|
358
|
+
* is picked up by this same frame. A page that asked for a frame expects it
|
|
359
|
+
* to be presented even when its callback changed nothing visible. */
|
|
360
|
+
if (!win->animation_frames.empty()) win->present_dirty = 1;
|
|
322
361
|
xt_gui_run_animation_frames(win, now);
|
|
323
362
|
/* A callback may have closed the window (directly or through the
|
|
324
363
|
* auto-close guard): `xt_gui_quit_window` released the document and the GPU
|
|
325
364
|
* geometry, so there is nothing left to lay out or paint. */
|
|
326
|
-
if (!win->open)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
365
|
+
if (!win->open) {
|
|
366
|
+
xtgui::xt_perf_frame_end();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
/* Reconcile the decoder/canvas tables with the live DOM only when the
|
|
370
|
+
* document changed (or on the first frame): walking the whole tree every
|
|
371
|
+
* frame was a per-frame cost proportional to the element count even when a
|
|
372
|
+
* page had no `<video>` or `<canvas>` at all. */
|
|
373
|
+
bool dom_changed = win->struct_dirty != 0 || win->sync_dirty != 0 || !win->ready ||
|
|
374
|
+
(win->document != nullptr && win->document->dirty());
|
|
375
|
+
if (dom_changed) {
|
|
376
|
+
xt_gui_media_sync(win);
|
|
377
|
+
xt_gui_canvas_sync(win);
|
|
378
|
+
win->sync_dirty = 0;
|
|
379
|
+
}
|
|
380
|
+
/* Playback advances every frame (it raises the media DOM events), but only a
|
|
381
|
+
* decoder that produced a frame asks to present. */
|
|
331
382
|
xt_gui_media_tick(win, now);
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
383
|
+
if (win->document != nullptr && win->document->advance(delta)) {
|
|
384
|
+
win->geometry.dirty = 1;
|
|
385
|
+
win->present_dirty = 1;
|
|
386
|
+
xtgui::g_perf_frame.transitionFrames += 1;
|
|
387
|
+
}
|
|
336
388
|
|
|
337
389
|
/* A DOM mutation from a handler invalidates the tree: restyle/relayout
|
|
338
390
|
* before (re)building the geometry. */
|
|
339
|
-
|
|
391
|
+
{
|
|
392
|
+
xtgui::PerfScope scope(&xtgui::g_perf_frame_ms.restyle);
|
|
393
|
+
xt_gui_flush_dom(win);
|
|
394
|
+
}
|
|
340
395
|
|
|
341
396
|
/* Rebuild the geometry only when the document or viewport changed. */
|
|
342
397
|
bool can_paint = xt_gui_renderer_ensure(g_device, win->window);
|
|
398
|
+
/* One command buffer for every upload this frame — geometry, glyph atlas,
|
|
399
|
+
* image textures, video frames and canvas bitmaps — so K animated canvases
|
|
400
|
+
* cost one submit, not K. */
|
|
401
|
+
XtGuiUploadBatch uploads;
|
|
402
|
+
xt_gui_upload_begin(&uploads, g_device);
|
|
343
403
|
if (can_paint && win->geometry.dirty && win->document != nullptr) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
404
|
+
{
|
|
405
|
+
xtgui::PerfScope scope(&xtgui::g_perf_frame_ms.paint);
|
|
406
|
+
/* Reuse the window's list: `clear()` keeps the payload vectors' capacity,
|
|
407
|
+
* so a repaint does not reallocate every array from zero. */
|
|
408
|
+
xtgui::DisplayList &list = win->display_list;
|
|
409
|
+
list.clear();
|
|
410
|
+
xtgui::xt_paint_build(win->document->layout().root(), list, viewport_width, viewport_height);
|
|
411
|
+
xt_paint_translate(list, -win->scroll_x, -win->scroll_y);
|
|
412
|
+
xtgui::g_perf_frame.paints += 1;
|
|
413
|
+
xtgui::g_perf_frame.paintOps += (uint64_t)list.ops.size();
|
|
414
|
+
{
|
|
415
|
+
xtgui::PerfScope upload_scope(&xtgui::g_perf_frame_ms.upload);
|
|
416
|
+
xt_gui_geometry_upload(g_device, &win->geometry, list, &uploads);
|
|
417
|
+
float density = SDL_GetWindowPixelDensity(win->window);
|
|
418
|
+
float text_scale = density > 0.0f ? density : 1.0f;
|
|
419
|
+
xt_gui_text_upload(g_device, &win->text_geometry, list, text_scale, &uploads);
|
|
420
|
+
xt_gui_image_upload(g_device, &win->image_geometry, list, text_scale, &uploads);
|
|
421
|
+
xt_gui_build_runs(&win->geometry, &win->text_geometry, &win->image_geometry, list);
|
|
422
|
+
}
|
|
423
|
+
xtgui::g_perf_frame.drawRuns += (uint64_t)win->geometry.runs.size();
|
|
424
|
+
}
|
|
353
425
|
win->geometry.dirty = 0;
|
|
354
426
|
}
|
|
355
427
|
|
|
356
428
|
/* Upload any new video frame before the render command buffer is recorded,
|
|
357
429
|
* so the draw samples the frame that just arrived. */
|
|
358
|
-
xt_gui_media_upload_frames(g_device);
|
|
430
|
+
bool uploaded = xt_gui_media_upload_frames(g_device, &uploads);
|
|
359
431
|
/* Canvases are CPU-drawn: push any new bitmap without a relayout. */
|
|
360
|
-
xt_gui_canvas_upload_frames(g_device);
|
|
432
|
+
if (xt_gui_canvas_upload_frames(g_device, &uploads)) uploaded = true;
|
|
433
|
+
xt_gui_upload_end(&uploads);
|
|
361
434
|
/* WebGL contexts record into their own command buffers; submit them first so
|
|
362
435
|
* the page's render pass samples this frame's drawing-buffer contents. */
|
|
363
436
|
xt_gui_webgl_submit_all();
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
437
|
+
if (uploaded || !win->webgl_canvases.empty()) win->present_dirty = 1;
|
|
438
|
+
|
|
439
|
+
/* Present only when something happened. A static document otherwise burned a
|
|
440
|
+
* swapchain acquire and a full-scene draw every frame. `XT_GUI_ALWAYS_PRESENT`
|
|
441
|
+
* forces the old behaviour for debugging a missed invalidation. */
|
|
442
|
+
static int always_present = -1;
|
|
443
|
+
if (always_present < 0) {
|
|
444
|
+
const char *value = SDL_getenv("XT_GUI_ALWAYS_PRESENT");
|
|
445
|
+
always_present = (value != nullptr && value[0] != '\0' && strcmp(value, "0") != 0) ? 1 : 0;
|
|
446
|
+
}
|
|
447
|
+
if (win->ready && win->present_dirty == 0 && always_present == 0) {
|
|
448
|
+
xtgui::g_perf_frame.skippedFrames += 1;
|
|
449
|
+
xtgui::xt_perf_frame_end();
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
win->present_dirty = 0;
|
|
453
|
+
|
|
368
454
|
bool firstFrame = false;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
455
|
+
{
|
|
456
|
+
/* Scoped: the timing must be recorded before `xt_perf_frame_end` copies
|
|
457
|
+
* the frame's phase timings. */
|
|
458
|
+
xtgui::PerfScope draw_scope(&xtgui::g_perf_frame_ms.draw);
|
|
459
|
+
SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(g_device);
|
|
460
|
+
if (cmd != NULL) {
|
|
461
|
+
SDL_GPUTexture *swapchain = NULL;
|
|
462
|
+
SDL_AcquireGPUSwapchainTexture(cmd, win->window, &swapchain, NULL, NULL);
|
|
463
|
+
if (swapchain != NULL) {
|
|
464
|
+
/* Attribute the present: a redraw the window system asked for, one a
|
|
465
|
+
* starved frame had to defer, or this frame's own work. Both foreign
|
|
466
|
+
* kinds carry work that was *not* done in this frame. */
|
|
467
|
+
if (window_requested) {
|
|
468
|
+
xtgui::g_perf_frame.exposes += 1;
|
|
469
|
+
} else if (win->present_deferred != 0) {
|
|
470
|
+
xtgui::g_perf_frame.deferred += 1;
|
|
471
|
+
}
|
|
472
|
+
win->present_deferred = 0;
|
|
473
|
+
xtgui::g_perf_frame.presents += 1;
|
|
474
|
+
SDL_GPUColorTargetInfo target;
|
|
475
|
+
memset(&target, 0, sizeof(target));
|
|
476
|
+
target.texture = swapchain;
|
|
477
|
+
target.clear_color.r = win->background[0];
|
|
478
|
+
target.clear_color.g = win->background[1];
|
|
479
|
+
target.clear_color.b = win->background[2];
|
|
480
|
+
target.clear_color.a = win->background[3];
|
|
481
|
+
target.load_op = SDL_GPU_LOADOP_CLEAR;
|
|
482
|
+
target.store_op = SDL_GPU_STOREOP_STORE;
|
|
483
|
+
SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(cmd, &target, 1, NULL);
|
|
484
|
+
if (can_paint) {
|
|
485
|
+
float density = SDL_GetWindowPixelDensity(win->window);
|
|
486
|
+
if (density <= 0.0f) density = 1.0f;
|
|
487
|
+
xt_gui_renderer_draw(cmd, pass, &win->geometry, &win->text_geometry,
|
|
488
|
+
&win->image_geometry, viewport_width, viewport_height, density);
|
|
489
|
+
}
|
|
490
|
+
SDL_EndGPURenderPass(pass);
|
|
491
|
+
firstFrame = !win->ready;
|
|
492
|
+
win->ready = 1;
|
|
493
|
+
} else {
|
|
494
|
+
/* No swapchain image this frame: SDL's non-blocking acquire skips a
|
|
495
|
+
* frame (rather than blocking) when the GPU still holds the image, and
|
|
496
|
+
* a minimized or hidden window has none at all. Dropping the frame here
|
|
497
|
+
* would leave the window showing the previous frame until something
|
|
498
|
+
* else dirtied the page, so keep the invalidation pending and present
|
|
499
|
+
* it on the next frame — the paint, the uploads and the run list are
|
|
500
|
+
* already built and retained, so the retry only re-records the draws. */
|
|
501
|
+
win->present_dirty = 1;
|
|
502
|
+
win->present_deferred = 1;
|
|
503
|
+
xtgui::g_perf_frame.starvedFrames += 1;
|
|
504
|
+
}
|
|
505
|
+
SDL_SubmitGPUCommandBuffer(cmd);
|
|
506
|
+
} else {
|
|
507
|
+
/* No command buffer from the device: same treatment as a starved frame —
|
|
508
|
+
* keep the invalidation so the page is shown once the device recovers. */
|
|
509
|
+
win->present_dirty = 1;
|
|
510
|
+
win->present_deferred = 1;
|
|
511
|
+
xtgui::g_perf_frame.starvedFrames += 1;
|
|
385
512
|
}
|
|
386
|
-
SDL_EndGPURenderPass(pass);
|
|
387
|
-
firstFrame = !win->ready;
|
|
388
|
-
win->ready = 1;
|
|
389
513
|
}
|
|
390
|
-
SDL_SubmitGPUCommandBuffer(cmd);
|
|
391
514
|
/* The first presented frame marks the window ready. Emitting after the
|
|
392
515
|
* submit lets a `ready` handler safely close/destroy the window. */
|
|
393
516
|
if (firstFrame) xt_gui_emit(win, "ready");
|
|
517
|
+
xtgui::xt_perf_frame_end();
|
|
394
518
|
}
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
#include "gui_internal.h"
|
|
21
21
|
|
|
22
|
+
#include "perf.h"
|
|
23
|
+
|
|
22
24
|
#include <cstring>
|
|
23
25
|
#include <string>
|
|
24
26
|
#include <unordered_set>
|
|
@@ -113,6 +115,7 @@ bool booleanAttr(const xtgui::Node *node, const char *name) {
|
|
|
113
115
|
|
|
114
116
|
void collectMedia(const xtgui::Node *node, std::vector<const xtgui::Node *> *out) {
|
|
115
117
|
if (node == nullptr) return;
|
|
118
|
+
xtgui::g_perf_frame.syncNodes += 1;
|
|
116
119
|
if (isMediaElement(node)) {
|
|
117
120
|
out->push_back(node);
|
|
118
121
|
return; // a media element's children are `<source>`/`<track>`, not media
|
|
@@ -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
|
}
|