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
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#include "canvas.h"
|
|
13
13
|
#include "image.h"
|
|
14
14
|
#include "media.h"
|
|
15
|
+
#include "perf.h"
|
|
15
16
|
#include "svg.h"
|
|
16
17
|
#include "webgl.h"
|
|
17
18
|
|
|
@@ -27,61 +28,98 @@ struct MediaTexture {
|
|
|
27
28
|
int width = 0;
|
|
28
29
|
int height = 0;
|
|
29
30
|
uint64_t generation = ~0ull;
|
|
31
|
+
/** Staging transfer buffer reused by every upload into this texture. */
|
|
32
|
+
SDL_GPUTransferBuffer *staging = nullptr;
|
|
33
|
+
uint32_t staging_capacity = 0;
|
|
30
34
|
};
|
|
31
35
|
|
|
32
36
|
static std::unordered_map<uint64_t, MediaTexture> g_media_textures;
|
|
33
37
|
|
|
34
38
|
/** One live `<canvas>` texture, keyed by the canvas id from `canvas.h`. It is
|
|
35
|
-
* re-uploaded in place whenever the canvas bumps its `generation
|
|
39
|
+
* re-uploaded in place whenever the canvas bumps its `generation`; only the
|
|
40
|
+
* canvas's dirty rectangle is copied. */
|
|
36
41
|
static std::unordered_map<uint64_t, MediaTexture> g_canvas_textures;
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
static void releaseStaging(SDL_GPUDevice *device, MediaTexture &entry) {
|
|
44
|
+
if (entry.staging != nullptr) SDL_ReleaseGPUTransferBuffer(device, entry.staging);
|
|
45
|
+
entry.staging = nullptr;
|
|
46
|
+
entry.staging_capacity = 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Upload the device-space rectangle `(x, y, w, h)` of an RGBA8 image into
|
|
50
|
+
* `texture`, through `entry`'s reused staging buffer. Because command buffers
|
|
51
|
+
* run in submission order and this copy is submitted before the frame that
|
|
52
|
+
* samples the texture, re-uploading a texture the previous frame drew is
|
|
53
|
+
* safe. With a batch the copy joins the frame's upload command buffer. */
|
|
54
|
+
static bool uploadPixels(SDL_GPUDevice *device, MediaTexture &entry, const void *pixels,
|
|
55
|
+
int image_width, int x, int y, int w, int h,
|
|
56
|
+
XtGuiUploadBatch *batch) {
|
|
57
|
+
if (entry.texture == nullptr || w <= 0 || h <= 0) return false;
|
|
58
|
+
const uint32_t bytes = (uint32_t)w * (uint32_t)h * 4u;
|
|
44
59
|
if (bytes == 0) return false;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
if (entry.staging == nullptr || entry.staging_capacity < bytes) {
|
|
61
|
+
releaseStaging(device, entry);
|
|
62
|
+
SDL_GPUTransferBufferCreateInfo transfer_info;
|
|
63
|
+
memset(&transfer_info, 0, sizeof(transfer_info));
|
|
64
|
+
transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
|
65
|
+
transfer_info.size = bytes;
|
|
66
|
+
entry.staging = SDL_CreateGPUTransferBuffer(device, &transfer_info);
|
|
67
|
+
entry.staging_capacity = entry.staging != nullptr ? bytes : 0;
|
|
68
|
+
if (entry.staging == nullptr) return false;
|
|
69
|
+
}
|
|
70
|
+
/* `cycle` rotates SDL's internal allocation while the previous frame's copy
|
|
71
|
+
* may still be in flight. */
|
|
72
|
+
void *mapped = SDL_MapGPUTransferBuffer(device, entry.staging, true);
|
|
73
|
+
if (mapped == nullptr) return false;
|
|
74
|
+
const unsigned char *source = (const unsigned char *)pixels;
|
|
75
|
+
unsigned char *destination = (unsigned char *)mapped;
|
|
76
|
+
for (int row = 0; row < h; row++) {
|
|
77
|
+
memcpy(destination + (size_t)row * (size_t)w * 4,
|
|
78
|
+
source + ((size_t)(y + row) * (size_t)image_width + (size_t)x) * 4,
|
|
79
|
+
(size_t)w * 4);
|
|
59
80
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
SDL_UnmapGPUTransferBuffer(device, entry.staging);
|
|
82
|
+
|
|
83
|
+
SDL_GPUCommandBuffer *cmd = xtgui::renderer_detail::uploadCommand(device, batch);
|
|
84
|
+
if (cmd == nullptr) return false;
|
|
85
|
+
SDL_GPUTextureTransferInfo source_info;
|
|
86
|
+
memset(&source_info, 0, sizeof(source_info));
|
|
87
|
+
source_info.transfer_buffer = entry.staging;
|
|
88
|
+
source_info.offset = 0;
|
|
89
|
+
source_info.pixels_per_row = (Uint32)w;
|
|
90
|
+
source_info.rows_per_layer = (Uint32)h;
|
|
91
|
+
SDL_GPUTextureRegion destination_region;
|
|
92
|
+
memset(&destination_region, 0, sizeof(destination_region));
|
|
93
|
+
destination_region.texture = entry.texture;
|
|
94
|
+
destination_region.x = (Uint32)x;
|
|
95
|
+
destination_region.y = (Uint32)y;
|
|
96
|
+
destination_region.mip_level = 0;
|
|
97
|
+
destination_region.layer = 0;
|
|
98
|
+
destination_region.w = (Uint32)w;
|
|
99
|
+
destination_region.h = (Uint32)h;
|
|
100
|
+
destination_region.d = 1;
|
|
74
101
|
SDL_GPUCopyPass *copy = SDL_BeginGPUCopyPass(cmd);
|
|
75
|
-
SDL_UploadToGPUTexture(copy, &
|
|
102
|
+
SDL_UploadToGPUTexture(copy, &source_info, &destination_region, false);
|
|
76
103
|
SDL_EndGPUCopyPass(copy);
|
|
77
|
-
|
|
78
|
-
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
|
104
|
+
xtgui::renderer_detail::uploadDone(cmd, batch);
|
|
79
105
|
return true;
|
|
80
106
|
}
|
|
81
107
|
|
|
108
|
+
/** One-shot full-texture upload (image/SVG decode), used when there is no
|
|
109
|
+
* long-lived texture entry to keep a staging buffer on. */
|
|
110
|
+
static bool uploadWholeTexture(SDL_GPUDevice *device, SDL_GPUTexture *texture, int width,
|
|
111
|
+
int height, const void *pixels, XtGuiUploadBatch *batch) {
|
|
112
|
+
MediaTexture temporary;
|
|
113
|
+
temporary.texture = texture;
|
|
114
|
+
const bool ok = uploadPixels(device, temporary, pixels, width, 0, 0, width, height, batch);
|
|
115
|
+
releaseStaging(device, temporary);
|
|
116
|
+
return ok;
|
|
117
|
+
}
|
|
118
|
+
|
|
82
119
|
/** Ensure the texture for `media_id` exists and holds the decoder's newest
|
|
83
120
|
* frame. Returns `nullptr` before the first frame is decoded. */
|
|
84
|
-
static SDL_GPUTexture *ensureMediaTexture(SDL_GPUDevice *device, uint64_t media_id
|
|
121
|
+
static SDL_GPUTexture *ensureMediaTexture(SDL_GPUDevice *device, uint64_t media_id,
|
|
122
|
+
XtGuiUploadBatch *batch) {
|
|
85
123
|
xtgui::MediaPlayer *player = xtgui::xt_media_by_id(media_id);
|
|
86
124
|
if (player == nullptr) return nullptr;
|
|
87
125
|
const xtgui::Image *image = player->frame();
|
|
@@ -106,8 +144,8 @@ static SDL_GPUTexture *ensureMediaTexture(SDL_GPUDevice *device, uint64_t media_
|
|
|
106
144
|
if (entry.texture == nullptr) return nullptr;
|
|
107
145
|
}
|
|
108
146
|
if (entry.generation != player->frame_generation()) {
|
|
109
|
-
if (uploadPixels(device, entry
|
|
110
|
-
|
|
147
|
+
if (uploadPixels(device, entry, image->pixels.data(), image->width, 0, 0, entry.width,
|
|
148
|
+
entry.height, batch)) {
|
|
111
149
|
entry.generation = player->frame_generation();
|
|
112
150
|
}
|
|
113
151
|
}
|
|
@@ -117,7 +155,8 @@ static SDL_GPUTexture *ensureMediaTexture(SDL_GPUDevice *device, uint64_t media_
|
|
|
117
155
|
/** Ensure the texture for `canvas_id` exists and holds the canvas's newest
|
|
118
156
|
* pixels. A WebGL canvas already owns its colour target, so it is returned
|
|
119
157
|
* directly and nothing is copied through the CPU. */
|
|
120
|
-
static SDL_GPUTexture *ensureCanvasTexture(SDL_GPUDevice *device, uint64_t canvas_id
|
|
158
|
+
static SDL_GPUTexture *ensureCanvasTexture(SDL_GPUDevice *device, uint64_t canvas_id,
|
|
159
|
+
XtGuiUploadBatch *batch) {
|
|
121
160
|
if (xtgui::WebGLContext *webgl = xtgui::xt_webgl_by_id(canvas_id)) {
|
|
122
161
|
return webgl->color_texture();
|
|
123
162
|
}
|
|
@@ -144,11 +183,24 @@ static SDL_GPUTexture *ensureCanvasTexture(SDL_GPUDevice *device, uint64_t canva
|
|
|
144
183
|
entry.generation = ~0ull;
|
|
145
184
|
if (entry.texture == nullptr) return nullptr;
|
|
146
185
|
}
|
|
147
|
-
if (entry.generation
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
186
|
+
if (entry.generation == canvas->generation()) return entry.texture;
|
|
187
|
+
const xtgui::Canvas2D::CanvasDirtyRect &dirty = canvas->pending_dirty();
|
|
188
|
+
/* A draw that changed no pixel (off canvas, zero coverage) must not pay for
|
|
189
|
+
* an upload: remember the generation and skip it. */
|
|
190
|
+
if (dirty.empty()) {
|
|
191
|
+
entry.generation = canvas->generation();
|
|
192
|
+
return entry.texture;
|
|
193
|
+
}
|
|
194
|
+
/* Capture the rectangle before `clear_dirty()` resets it. */
|
|
195
|
+
const int dirty_w = dirty.x1 - dirty.x0;
|
|
196
|
+
const int dirty_h = dirty.y1 - dirty.y0;
|
|
197
|
+
if (uploadPixels(device, entry, image.pixels.data(), image.width, dirty.x0, dirty.y0, dirty_w,
|
|
198
|
+
dirty_h, batch)) {
|
|
199
|
+
entry.generation = canvas->generation();
|
|
200
|
+
canvas->clear_dirty();
|
|
201
|
+
xtgui::g_perf_frame.canvasUploads += 1;
|
|
202
|
+
xtgui::g_perf_frame.canvasUploadBytes +=
|
|
203
|
+
(uint64_t)dirty_w * (uint64_t)dirty_h * 4u;
|
|
152
204
|
}
|
|
153
205
|
return entry.texture;
|
|
154
206
|
}
|
|
@@ -235,7 +287,8 @@ SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *
|
|
|
235
287
|
|
|
236
288
|
/** Create an RGBA texture holding `image` and upload its pixels. Returns null
|
|
237
289
|
* (after reporting the SDL error) when either step fails. */
|
|
238
|
-
SDL_GPUTexture *createImageTexture(SDL_GPUDevice *device, const xtgui::Image &image
|
|
290
|
+
SDL_GPUTexture *createImageTexture(SDL_GPUDevice *device, const xtgui::Image &image,
|
|
291
|
+
XtGuiUploadBatch *batch) {
|
|
239
292
|
SDL_GPUTextureCreateInfo info;
|
|
240
293
|
memset(&info, 0, sizeof(info));
|
|
241
294
|
info.type = SDL_GPU_TEXTURETYPE_2D;
|
|
@@ -251,8 +304,7 @@ SDL_GPUTexture *createImageTexture(SDL_GPUDevice *device, const xtgui::Image &im
|
|
|
251
304
|
fprintf(stderr, "xt_gui: image texture creation failed: %s\n", SDL_GetError());
|
|
252
305
|
return nullptr;
|
|
253
306
|
}
|
|
254
|
-
|
|
255
|
-
if (!uploadPixels(device, texture, image.width, image.height, image.pixels.data(), bytes)) {
|
|
307
|
+
if (!uploadWholeTexture(device, texture, image.width, image.height, image.pixels.data(), batch)) {
|
|
256
308
|
SDL_ReleaseGPUTexture(device, texture);
|
|
257
309
|
return nullptr;
|
|
258
310
|
}
|
|
@@ -263,7 +315,7 @@ SDL_GPUTexture *createImageTexture(SDL_GPUDevice *device, const xtgui::Image &im
|
|
|
263
315
|
* rasterised at the drawn pixel size and cached by that size; bitmaps decode at
|
|
264
316
|
* their intrinsic size and are cached by path. */
|
|
265
317
|
SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src, int pixel_width,
|
|
266
|
-
int pixel_height) {
|
|
318
|
+
int pixel_height, XtGuiUploadBatch *batch) {
|
|
267
319
|
bool vector_source = xtgui::xt_image_is_vector(src);
|
|
268
320
|
std::string key =
|
|
269
321
|
vector_source ? src + "@" + std::to_string(pixel_width) + "x" + std::to_string(pixel_height)
|
|
@@ -278,7 +330,7 @@ SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src
|
|
|
278
330
|
return nullptr;
|
|
279
331
|
}
|
|
280
332
|
|
|
281
|
-
SDL_GPUTexture *texture = createImageTexture(device, *image);
|
|
333
|
+
SDL_GPUTexture *texture = createImageTexture(device, *image, batch);
|
|
282
334
|
g_image_textures.emplace(key, texture);
|
|
283
335
|
return texture;
|
|
284
336
|
}
|
|
@@ -287,7 +339,8 @@ SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src
|
|
|
287
339
|
* size and upload it. The markup is the cache key: a static document therefore
|
|
288
340
|
* rasterises once per drawn size, and an edited one gets a fresh texture. */
|
|
289
341
|
SDL_GPUTexture *ensureInlineSvgTexture(SDL_GPUDevice *device, const std::string &markup,
|
|
290
|
-
int pixel_width, int pixel_height
|
|
342
|
+
int pixel_width, int pixel_height,
|
|
343
|
+
XtGuiUploadBatch *batch) {
|
|
291
344
|
std::string key = "inline-svg@" + std::to_string(pixel_width) + "x" +
|
|
292
345
|
std::to_string(pixel_height) + ":" + markup;
|
|
293
346
|
auto found = g_image_textures.find(key);
|
|
@@ -300,7 +353,7 @@ SDL_GPUTexture *ensureInlineSvgTexture(SDL_GPUDevice *device, const std::string
|
|
|
300
353
|
return nullptr;
|
|
301
354
|
}
|
|
302
355
|
|
|
303
|
-
SDL_GPUTexture *texture = createImageTexture(device, image);
|
|
356
|
+
SDL_GPUTexture *texture = createImageTexture(device, image, batch);
|
|
304
357
|
g_image_textures.emplace(key, texture);
|
|
305
358
|
return texture;
|
|
306
359
|
}
|
|
@@ -309,7 +362,7 @@ SDL_GPUTexture *ensureInlineSvgTexture(SDL_GPUDevice *device, const std::string
|
|
|
309
362
|
} // namespace xtgui
|
|
310
363
|
|
|
311
364
|
void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
312
|
-
const xtgui::DisplayList &list, float scale) {
|
|
365
|
+
const xtgui::DisplayList &list, float scale, XtGuiUploadBatch *batch) {
|
|
313
366
|
using namespace xtgui::renderer_detail;
|
|
314
367
|
geometry->op_counts.assign(list.images.size(), 0);
|
|
315
368
|
geometry->op_textures.assign(list.images.size(), nullptr);
|
|
@@ -318,7 +371,8 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
|
318
371
|
return;
|
|
319
372
|
}
|
|
320
373
|
if (scale <= 0.0f) scale = 1.0f;
|
|
321
|
-
std::vector<TextVertex> verts;
|
|
374
|
+
std::vector<TextVertex> &verts = g_image_staging;
|
|
375
|
+
verts.clear();
|
|
322
376
|
verts.reserve(list.images.size() * 6);
|
|
323
377
|
GlyphEntry full;
|
|
324
378
|
full.u0 = 0.0f;
|
|
@@ -337,13 +391,13 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
|
337
391
|
if (pixel_height < 1) pixel_height = 1;
|
|
338
392
|
SDL_GPUTexture *texture = nullptr;
|
|
339
393
|
if (image.canvas_id != 0) {
|
|
340
|
-
texture = ensureCanvasTexture(device, image.canvas_id);
|
|
394
|
+
texture = ensureCanvasTexture(device, image.canvas_id, batch);
|
|
341
395
|
} else if (image.media_id != 0) {
|
|
342
|
-
texture = ensureMediaTexture(device, image.media_id);
|
|
396
|
+
texture = ensureMediaTexture(device, image.media_id, batch);
|
|
343
397
|
} else if (!image.svg.empty()) {
|
|
344
|
-
texture = ensureInlineSvgTexture(device, image.svg, pixel_width, pixel_height);
|
|
398
|
+
texture = ensureInlineSvgTexture(device, image.svg, pixel_width, pixel_height, batch);
|
|
345
399
|
} else {
|
|
346
|
-
texture = ensureImageTexture(device, image.src, pixel_width, pixel_height);
|
|
400
|
+
texture = ensureImageTexture(device, image.src, pixel_width, pixel_height, batch);
|
|
347
401
|
}
|
|
348
402
|
if (texture == nullptr) continue;
|
|
349
403
|
uint32_t count = pushGlyph(verts, image.x, image.y, image.width, image.height, full,
|
|
@@ -351,7 +405,7 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
|
351
405
|
geometry->op_counts[op.index] = count;
|
|
352
406
|
geometry->op_textures[op.index] = texture;
|
|
353
407
|
}
|
|
354
|
-
uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex));
|
|
408
|
+
uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex), batch);
|
|
355
409
|
}
|
|
356
410
|
|
|
357
411
|
void xt_gui_image_textures_destroy(SDL_GPUDevice *device) {
|
|
@@ -362,17 +416,25 @@ void xt_gui_image_textures_destroy(SDL_GPUDevice *device) {
|
|
|
362
416
|
g_image_textures.clear();
|
|
363
417
|
for (auto &entry : g_media_textures) {
|
|
364
418
|
if (entry.second.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.second.texture);
|
|
419
|
+
releaseStaging(device, entry.second);
|
|
365
420
|
}
|
|
366
421
|
g_media_textures.clear();
|
|
367
422
|
for (auto &entry : g_canvas_textures) {
|
|
368
423
|
if (entry.second.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.second.texture);
|
|
424
|
+
releaseStaging(device, entry.second);
|
|
369
425
|
}
|
|
370
426
|
g_canvas_textures.clear();
|
|
371
427
|
}
|
|
372
428
|
|
|
373
|
-
|
|
429
|
+
bool xt_gui_media_upload_frames(SDL_GPUDevice *device, XtGuiUploadBatch *batch) {
|
|
374
430
|
using namespace xtgui::renderer_detail;
|
|
375
|
-
|
|
431
|
+
bool uploaded = false;
|
|
432
|
+
for (auto &entry : g_media_textures) {
|
|
433
|
+
const uint64_t before = entry.second.generation;
|
|
434
|
+
ensureMediaTexture(device, entry.first, batch);
|
|
435
|
+
uploaded = uploaded || entry.second.generation != before;
|
|
436
|
+
}
|
|
437
|
+
return uploaded;
|
|
376
438
|
}
|
|
377
439
|
|
|
378
440
|
void xt_gui_media_texture_release(SDL_GPUDevice *device, uint64_t media_id) {
|
|
@@ -380,12 +442,19 @@ void xt_gui_media_texture_release(SDL_GPUDevice *device, uint64_t media_id) {
|
|
|
380
442
|
auto it = g_media_textures.find(media_id);
|
|
381
443
|
if (it == g_media_textures.end()) return;
|
|
382
444
|
if (it->second.texture != nullptr) SDL_ReleaseGPUTexture(device, it->second.texture);
|
|
445
|
+
releaseStaging(device, it->second);
|
|
383
446
|
g_media_textures.erase(it);
|
|
384
447
|
}
|
|
385
448
|
|
|
386
|
-
|
|
449
|
+
bool xt_gui_canvas_upload_frames(SDL_GPUDevice *device, XtGuiUploadBatch *batch) {
|
|
387
450
|
using namespace xtgui::renderer_detail;
|
|
388
|
-
|
|
451
|
+
bool uploaded = false;
|
|
452
|
+
for (auto &entry : g_canvas_textures) {
|
|
453
|
+
const uint64_t before = xtgui::g_perf_frame.canvasUploads;
|
|
454
|
+
ensureCanvasTexture(device, entry.first, batch);
|
|
455
|
+
uploaded = uploaded || xtgui::g_perf_frame.canvasUploads != before;
|
|
456
|
+
}
|
|
457
|
+
return uploaded;
|
|
389
458
|
}
|
|
390
459
|
|
|
391
460
|
void xt_gui_canvas_texture_release(SDL_GPUDevice *device, uint64_t canvas_id) {
|
|
@@ -393,5 +462,6 @@ void xt_gui_canvas_texture_release(SDL_GPUDevice *device, uint64_t canvas_id) {
|
|
|
393
462
|
auto it = g_canvas_textures.find(canvas_id);
|
|
394
463
|
if (it == g_canvas_textures.end()) return;
|
|
395
464
|
if (it->second.texture != nullptr) SDL_ReleaseGPUTexture(device, it->second.texture);
|
|
465
|
+
releaseStaging(device, it->second);
|
|
396
466
|
g_canvas_textures.erase(it);
|
|
397
467
|
}
|
|
@@ -79,6 +79,18 @@ extern bool g_atlas_full_logged;
|
|
|
79
79
|
|
|
80
80
|
/* -- geometry helpers ----------------------------------------------------- */
|
|
81
81
|
|
|
82
|
+
/** Reused vertex staging arrays (one frame at a time, cleared on each upload).
|
|
83
|
+
* Keeping their capacity avoids reallocating a few megabytes per repaint. */
|
|
84
|
+
extern std::vector<Vertex> g_shape_staging;
|
|
85
|
+
extern std::vector<TextVertex> g_text_staging;
|
|
86
|
+
extern std::vector<TextVertex> g_image_staging;
|
|
87
|
+
|
|
88
|
+
/** The command buffer an upload records into: the frame's batch when given,
|
|
89
|
+
* otherwise a freshly acquired one that `uploadDone` submits. */
|
|
90
|
+
SDL_GPUCommandBuffer *uploadCommand(SDL_GPUDevice *device, XtGuiUploadBatch *batch);
|
|
91
|
+
/** Submit a command buffer acquired for a one-off upload (`batch == nullptr`). */
|
|
92
|
+
void uploadDone(SDL_GPUCommandBuffer *cmd, XtGuiUploadBatch *batch);
|
|
93
|
+
|
|
82
94
|
/** Push one rounded rectangle, clipped by the op's `clip-path` polygons (if
|
|
83
95
|
* any). Returns the number of vertices appended. */
|
|
84
96
|
uint32_t pushShape(std::vector<Vertex> &verts, const xtgui::PaintRect &rect,
|
|
@@ -95,14 +107,14 @@ uint32_t pushQuad(std::vector<TextVertex> &verts, const float corners_x[4],
|
|
|
95
107
|
const xtgui::Color &color, const xtgui::Matrix2D &transform,
|
|
96
108
|
const xtgui::PaintClip &clip, const xtgui::DisplayList &list);
|
|
97
109
|
void uploadVertices(SDL_GPUDevice *device, XtGuiGeometry *geometry, const void *data,
|
|
98
|
-
uint32_t count, uint32_t stride);
|
|
110
|
+
uint32_t count, uint32_t stride, XtGuiUploadBatch *batch);
|
|
99
111
|
|
|
100
112
|
/* -- text ----------------------------------------------------------------- */
|
|
101
113
|
|
|
102
114
|
bool ensureAtlasTexture(SDL_GPUDevice *device);
|
|
103
115
|
void markAtlasDirty(int x, int y, int width, int height);
|
|
104
116
|
bool ensureGlyph(SDL_GPUDevice *device, xtgui::Font *font, uint32_t glyph, GlyphEntry &out);
|
|
105
|
-
void flushAtlas(SDL_GPUDevice *device);
|
|
117
|
+
void flushAtlas(SDL_GPUDevice *device, XtGuiUploadBatch *batch);
|
|
106
118
|
SDL_GPUGraphicsPipeline *createTextPipeline(SDL_GPUDevice *device, SDL_Window *reference);
|
|
107
119
|
SDL_GPUSampler *createSampler(SDL_GPUDevice *device);
|
|
108
120
|
|
|
@@ -114,7 +126,7 @@ SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *
|
|
|
114
126
|
* rasterised at that size, raster sources ignore it. The texture cache is keyed
|
|
115
127
|
* by the resulting pixel size, so an SVG drawn at two sizes gets two textures. */
|
|
116
128
|
SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src, int pixel_width,
|
|
117
|
-
int pixel_height);
|
|
129
|
+
int pixel_height, XtGuiUploadBatch *batch);
|
|
118
130
|
|
|
119
131
|
} // namespace renderer_detail
|
|
120
132
|
} // namespace xtgui
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
#include "text.h"
|
|
14
14
|
|
|
15
|
+
#include "perf.h"
|
|
16
|
+
|
|
15
17
|
#include "renderer_shaders.h"
|
|
16
18
|
|
|
17
19
|
namespace xtgui {
|
|
@@ -114,7 +116,7 @@ bool ensureGlyph(SDL_GPUDevice *device, xtgui::Font *font, uint32_t glyph, Glyph
|
|
|
114
116
|
return true;
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
void flushAtlas(SDL_GPUDevice *device) {
|
|
119
|
+
void flushAtlas(SDL_GPUDevice *device, XtGuiUploadBatch *batch) {
|
|
118
120
|
if (!g_atlas_dirty || g_atlas_texture == nullptr) return;
|
|
119
121
|
g_atlas_dirty = false;
|
|
120
122
|
int x0 = g_atlas_dirty_x0;
|
|
@@ -141,7 +143,7 @@ void flushAtlas(SDL_GPUDevice *device) {
|
|
|
141
143
|
}
|
|
142
144
|
SDL_UnmapGPUTransferBuffer(device, transfer);
|
|
143
145
|
|
|
144
|
-
SDL_GPUCommandBuffer *cmd =
|
|
146
|
+
SDL_GPUCommandBuffer *cmd = uploadCommand(device, batch);
|
|
145
147
|
if (cmd == nullptr) {
|
|
146
148
|
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
|
147
149
|
return;
|
|
@@ -166,7 +168,7 @@ void flushAtlas(SDL_GPUDevice *device) {
|
|
|
166
168
|
SDL_GPUCopyPass *copy = SDL_BeginGPUCopyPass(cmd);
|
|
167
169
|
SDL_UploadToGPUTexture(copy, &source, &destination, false);
|
|
168
170
|
SDL_EndGPUCopyPass(copy);
|
|
169
|
-
|
|
171
|
+
uploadDone(cmd, batch);
|
|
170
172
|
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
|
171
173
|
}
|
|
172
174
|
|
|
@@ -270,7 +272,7 @@ SDL_GPUSampler *createSampler(SDL_GPUDevice *device) {
|
|
|
270
272
|
} // namespace xtgui
|
|
271
273
|
|
|
272
274
|
void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
273
|
-
const xtgui::DisplayList &list, float scale) {
|
|
275
|
+
const xtgui::DisplayList &list, float scale, XtGuiUploadBatch *batch) {
|
|
274
276
|
using namespace xtgui::renderer_detail;
|
|
275
277
|
if (!g_text_ok) {
|
|
276
278
|
geometry->vertex_count = 0;
|
|
@@ -278,7 +280,8 @@ void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
|
278
280
|
}
|
|
279
281
|
if (scale <= 0.0f) scale = 1.0f;
|
|
280
282
|
|
|
281
|
-
std::vector<TextVertex> verts;
|
|
283
|
+
std::vector<TextVertex> &verts = g_text_staging;
|
|
284
|
+
verts.clear();
|
|
282
285
|
geometry->op_counts.assign(list.texts.size(), 0);
|
|
283
286
|
for (const xtgui::PaintOp &op : list.ops) {
|
|
284
287
|
if (op.kind != xtgui::PaintKind::Text || op.index >= list.texts.size()) continue;
|
|
@@ -296,16 +299,25 @@ void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
|
296
299
|
if (glyph_scale < kMinGlyphScale) glyph_scale = kMinGlyphScale;
|
|
297
300
|
if (glyph_scale > kMaxGlyphScale) glyph_scale = kMaxGlyphScale;
|
|
298
301
|
|
|
299
|
-
|
|
300
|
-
|
|
302
|
+
const xtgui::ShapedRun *shaped = xtgui::xt_text_shape_cached(run.text, logical);
|
|
303
|
+
if (shaped == nullptr) continue;
|
|
301
304
|
float pen_x = run.x;
|
|
302
305
|
uint32_t before = (uint32_t)verts.size();
|
|
303
|
-
|
|
306
|
+
/* The scaled face is a per-run decision (the size and matrix are constant
|
|
307
|
+
* for the run); resolving it per glyph used to build a 512-byte key and
|
|
308
|
+
* hash it for every character on a HiDPI display. */
|
|
309
|
+
xtgui::Font *scaled_input = nullptr;
|
|
310
|
+
xtgui::Font *scaled_output = nullptr;
|
|
311
|
+
for (const xtgui::ShapedGlyph &glyph : shaped->glyphs) {
|
|
304
312
|
if (glyph.glyph != 0) {
|
|
305
313
|
xtgui::Font *face = glyph.font != nullptr ? glyph.font : logical;
|
|
306
314
|
if (glyph_scale != 1.0f) {
|
|
307
|
-
|
|
308
|
-
|
|
315
|
+
if (face != scaled_input) {
|
|
316
|
+
xtgui::Font *scaled = xtgui::xt_text_scale(face, run.font.pixel_size * glyph_scale);
|
|
317
|
+
scaled_input = face;
|
|
318
|
+
scaled_output = scaled != nullptr ? scaled : face;
|
|
319
|
+
}
|
|
320
|
+
face = scaled_output;
|
|
309
321
|
}
|
|
310
322
|
GlyphEntry entry;
|
|
311
323
|
if (ensureGlyph(device, face, glyph.glyph, entry)) {
|
|
@@ -323,6 +335,6 @@ void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
|
323
335
|
}
|
|
324
336
|
geometry->op_counts[op.index] = (uint32_t)verts.size() - before;
|
|
325
337
|
}
|
|
326
|
-
uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex));
|
|
327
|
-
flushAtlas(device);
|
|
338
|
+
uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex), batch);
|
|
339
|
+
flushAtlas(device, batch);
|
|
328
340
|
}
|
|
@@ -25,6 +25,14 @@ static std::unordered_map<std::string, xt_value> &script_registry() {
|
|
|
25
25
|
return registry;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** Deferred module id -> its exports object. A `<script src>`'s imports are
|
|
29
|
+
* evaluated on first use (after `loadHTML`) and then shared by every script
|
|
30
|
+
* that imports the same module. */
|
|
31
|
+
static std::unordered_map<std::string, xt_value> &deferred_registry() {
|
|
32
|
+
static std::unordered_map<std::string, xt_value> registry;
|
|
33
|
+
return registry;
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
extern "C" xt_value xt_register_script(int32_t argc, xt_value *argv) {
|
|
29
37
|
if (argc >= 2) {
|
|
30
38
|
xt_value id_value = xt_to_string(xt_arg(argc, argv, 0));
|
|
@@ -42,6 +50,29 @@ extern "C" xt_value xt_register_script(int32_t argc, xt_value *argv) {
|
|
|
42
50
|
|
|
43
51
|
void xt_gui_script_gc_scan(void) {
|
|
44
52
|
for (const auto &entry : script_registry()) xt_gc_mark_value(entry.second);
|
|
53
|
+
for (const auto &entry : deferred_registry()) xt_gc_mark_value(entry.second);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** `__xt_defer(id, init)`: evaluate `init` once and return its exports. The
|
|
57
|
+
* placeholder is published before the call so a cyclic import sees the same
|
|
58
|
+
* object instead of recursing. */
|
|
59
|
+
extern "C" xt_value xt_gui_defer_module(int32_t argc, xt_value *argv) {
|
|
60
|
+
if (argc < 2) return XT_UNDEFINED;
|
|
61
|
+
xt_value id_value = xt_to_string(xt_arg(argc, argv, 0));
|
|
62
|
+
const char *id = xt_string_data(id_value);
|
|
63
|
+
if (id == nullptr) return XT_UNDEFINED;
|
|
64
|
+
std::unordered_map<std::string, xt_value> ®istry = deferred_registry();
|
|
65
|
+
auto found = registry.find(std::string(id));
|
|
66
|
+
if (found != registry.end()) return found->second;
|
|
67
|
+
xt_gc_register_root_provider(xt_gui_script_gc_scan);
|
|
68
|
+
xt_value exports = xt_object_new();
|
|
69
|
+
registry[std::string(id)] = exports;
|
|
70
|
+
xt_value init = xt_arg(argc, argv, 1);
|
|
71
|
+
if (XT_IS_FUNCTION(init)) {
|
|
72
|
+
exports = xt_call_with_this(init, XT_UNDEFINED, 0, nullptr);
|
|
73
|
+
registry[std::string(id)] = exports;
|
|
74
|
+
}
|
|
75
|
+
return exports;
|
|
45
76
|
}
|
|
46
77
|
|
|
47
78
|
/** Collect `data-xt-id` markers from `<script>` elements, in document order. */
|