xbintsc 0.5.41 → 0.5.42
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/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/tests/e2e/gui-data-url.test.d.ts +10 -0
- package/dist/tests/e2e/gui-data-url.test.js +127 -0
- package/dist/tests/e2e/gui-data-url.test.js.map +1 -0
- package/dist/tests/e2e/gui-effects.test.js +96 -0
- package/dist/tests/e2e/gui-effects.test.js.map +1 -1
- package/dist/tests/e2e/gui-paint.test.js +77 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/doc/gui/canvas.md +6 -2
- package/doc/gui/known-issues.md +15 -9
- package/doc/gui/media.md +11 -2
- package/doc/gui/rendering.md +43 -18
- package/doc/gui.md +5 -4
- package/doc/zh-CN/gui/canvas.md +6 -4
- package/doc/zh-CN/gui/known-issues.md +11 -8
- package/doc/zh-CN/gui/media.md +9 -2
- package/doc/zh-CN/gui/rendering.md +30 -11
- package/doc/zh-CN/gui.md +1 -1
- package/package.json +7 -7
- package/runtime/ext_gui/dom_api_canvas.cpp +42 -24
- package/runtime/ext_gui/gradient_values.cpp +44 -48
- package/runtime/ext_gui/gui_events.cpp +16 -13
- package/runtime/ext_gui/image.cpp +180 -25
- package/runtime/ext_gui/image.h +6 -4
- package/runtime/ext_gui/paint.cpp +53 -3
- package/runtime/ext_gui/paint.h +11 -1
- package/runtime/ext_gui/paint_background.cpp +92 -11
- package/runtime/ext_gui/paint_effects.cpp +2 -1
- package/runtime/ext_gui/paint_internal.h +5 -1
- package/runtime/ext_gui/renderer.cpp +81 -13
- package/runtime/ext_gui/renderer.h +19 -5
- package/runtime/ext_gui/renderer_internal.h +26 -0
- package/runtime/ext_gui/renderer_layer.cpp +552 -0
- package/runtime/ext_gui/style.h +29 -1
- package/runtime/ext_gui/style_print.cpp +10 -2
- package/runtime/ext_gui/style_values.cpp +10 -0
- package/src/driver/version.ts +1 -1
|
@@ -27,6 +27,8 @@ SDL_GPUGraphicsPipeline *g_pipeline = nullptr;
|
|
|
27
27
|
SDL_GPUGraphicsPipeline *g_text_pipeline = nullptr;
|
|
28
28
|
SDL_GPUGraphicsPipeline *g_image_pipeline = nullptr;
|
|
29
29
|
SDL_GPUSampler *g_sampler = nullptr;
|
|
30
|
+
SDL_GPUDevice *g_device = nullptr;
|
|
31
|
+
SDL_GPUTextureFormat g_swapchain_format = SDL_GPU_TEXTUREFORMAT_INVALID;
|
|
30
32
|
int g_renderer_failed = 0;
|
|
31
33
|
bool g_text_ok = false;
|
|
32
34
|
std::unordered_map<std::string, SDL_GPUTexture *> g_image_textures;
|
|
@@ -146,6 +148,8 @@ bool xt_gui_renderer_ensure(SDL_GPUDevice *device, SDL_Window *reference) {
|
|
|
146
148
|
using namespace xtgui::renderer_detail;
|
|
147
149
|
if (g_pipeline != nullptr) return true;
|
|
148
150
|
if (g_renderer_failed) return false;
|
|
151
|
+
g_device = device;
|
|
152
|
+
g_swapchain_format = SDL_GetGPUSwapchainTextureFormat(device, reference);
|
|
149
153
|
const ShaderSource fill_vs = selectShader(device, ShaderProgram::Fill, false);
|
|
150
154
|
const ShaderSource fill_fs = selectShader(device, ShaderProgram::Fill, true);
|
|
151
155
|
if (!fill_vs.valid() || !fill_fs.valid()) {
|
|
@@ -242,6 +246,7 @@ bool xt_gui_renderer_ensure(SDL_GPUDevice *device, SDL_Window *reference) {
|
|
|
242
246
|
|
|
243
247
|
void xt_gui_renderer_destroy(SDL_GPUDevice *device) {
|
|
244
248
|
using namespace xtgui::renderer_detail;
|
|
249
|
+
xtgui::renderer_detail::layerRendererDestroy(device);
|
|
245
250
|
if (g_pipeline != nullptr) {
|
|
246
251
|
SDL_ReleaseGPUGraphicsPipeline(device, g_pipeline);
|
|
247
252
|
g_pipeline = nullptr;
|
|
@@ -275,6 +280,8 @@ void xt_gui_renderer_destroy(SDL_GPUDevice *device) {
|
|
|
275
280
|
g_atlas_full_logged = false;
|
|
276
281
|
g_text_ok = false;
|
|
277
282
|
g_renderer_failed = 0;
|
|
283
|
+
g_device = nullptr;
|
|
284
|
+
g_swapchain_format = SDL_GPU_TEXTUREFORMAT_INVALID;
|
|
278
285
|
}
|
|
279
286
|
|
|
280
287
|
void xt_gui_geometry_destroy(SDL_GPUDevice *device, XtGuiGeometry *geometry) {
|
|
@@ -286,6 +293,13 @@ void xt_gui_geometry_destroy(SDL_GPUDevice *device, XtGuiGeometry *geometry) {
|
|
|
286
293
|
SDL_ReleaseGPUTransferBuffer(device, geometry->staging);
|
|
287
294
|
geometry->staging = nullptr;
|
|
288
295
|
}
|
|
296
|
+
for (SDL_GPUTexture *texture : geometry->scene_textures) {
|
|
297
|
+
if (texture != nullptr) SDL_ReleaseGPUTexture(device, texture);
|
|
298
|
+
}
|
|
299
|
+
geometry->scene_textures.clear();
|
|
300
|
+
geometry->scene_width = 0;
|
|
301
|
+
geometry->scene_height = 0;
|
|
302
|
+
geometry->scene_format = SDL_GPU_TEXTUREFORMAT_INVALID;
|
|
289
303
|
geometry->staging_capacity = 0;
|
|
290
304
|
geometry->capacity_bytes = 0;
|
|
291
305
|
geometry->vertex_count = 0;
|
|
@@ -314,6 +328,15 @@ void xt_gui_build_runs(XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometr
|
|
|
314
328
|
shapes->runs.clear();
|
|
315
329
|
uint32_t cursor[3] = {0, 0, 0};
|
|
316
330
|
for (const xtgui::PaintOp &op : list.ops) {
|
|
331
|
+
if (op.kind == xtgui::PaintKind::LayerBegin ||
|
|
332
|
+
op.kind == xtgui::PaintKind::LayerEnd) {
|
|
333
|
+
XtGuiDrawRun marker;
|
|
334
|
+
marker.pipeline = op.kind == xtgui::PaintKind::LayerBegin ? XtGuiDrawRun::LayerBegin
|
|
335
|
+
: XtGuiDrawRun::LayerEnd;
|
|
336
|
+
marker.layer = op.kind == xtgui::PaintKind::LayerBegin ? op.index : 0;
|
|
337
|
+
shapes->runs.push_back(marker);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
317
340
|
int pipeline = (int)op.kind;
|
|
318
341
|
XtGuiGeometry *geometry = pipeline == 0 ? shapes : (pipeline == 1 ? texts : images);
|
|
319
342
|
if (op.index >= geometry->op_counts.size()) continue;
|
|
@@ -349,17 +372,13 @@ void xt_gui_build_runs(XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometr
|
|
|
349
372
|
}
|
|
350
373
|
}
|
|
351
374
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
float viewport_width, float viewport_height, float pixel_density) {
|
|
355
|
-
using namespace xtgui::renderer_detail;
|
|
356
|
-
if (viewport_width <= 0.0f || viewport_height <= 0.0f) return;
|
|
357
|
-
if (pixel_density <= 0.0f) pixel_density = 1.0f;
|
|
358
|
-
float viewport[2] = {viewport_width, viewport_height};
|
|
359
|
-
SDL_PushGPUVertexUniformData(cmd, 0, viewport, sizeof(viewport));
|
|
375
|
+
namespace xtgui {
|
|
376
|
+
namespace renderer_detail {
|
|
360
377
|
|
|
361
|
-
|
|
362
|
-
|
|
378
|
+
void drawRuns(SDL_GPURenderPass *pass, const std::vector<XtGuiDrawRun> &runs, size_t begin,
|
|
379
|
+
size_t end, XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
|
|
380
|
+
int target_width, int target_height, float pixel_density) {
|
|
381
|
+
if (pass == nullptr) return;
|
|
363
382
|
/* A run list alternates pipelines (background, text, background, …), so the
|
|
364
383
|
* same scissor rectangle and the same pipeline were re-bound for every run.
|
|
365
384
|
* Track what is bound and only issue the state change when it actually
|
|
@@ -389,7 +408,11 @@ void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
|
|
|
389
408
|
SDL_BindGPUFragmentSamplers(pass, 0, &texture_binding, 1);
|
|
390
409
|
bound_texture = texture;
|
|
391
410
|
};
|
|
392
|
-
for (
|
|
411
|
+
for (size_t i = begin; i < end && i < runs.size(); i++) {
|
|
412
|
+
const XtGuiDrawRun &run = runs[i];
|
|
413
|
+
if (run.pipeline == XtGuiDrawRun::LayerBegin || run.pipeline == XtGuiDrawRun::LayerEnd)
|
|
414
|
+
continue;
|
|
415
|
+
if (run.count == 0) continue;
|
|
393
416
|
SDL_Rect scissor;
|
|
394
417
|
scissor.x = 0;
|
|
395
418
|
scissor.y = 0;
|
|
@@ -426,7 +449,7 @@ void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
|
|
|
426
449
|
bindTexture(g_atlas_texture);
|
|
427
450
|
SDL_DrawGPUPrimitives(pass, run.count, 1, run.first, 0);
|
|
428
451
|
xtgui::g_perf_frame.drawCalls += 1;
|
|
429
|
-
} else {
|
|
452
|
+
} else if (run.pipeline == XtGuiDrawRun::Image) {
|
|
430
453
|
if (g_image_pipeline == nullptr || g_sampler == nullptr || images->buffer == nullptr ||
|
|
431
454
|
run.texture == nullptr)
|
|
432
455
|
continue;
|
|
@@ -437,7 +460,7 @@ void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
|
|
|
437
460
|
}
|
|
438
461
|
}
|
|
439
462
|
|
|
440
|
-
/* Restore the full-target scissor so the next
|
|
463
|
+
/* Restore the full-target scissor so the next pass starts clean. */
|
|
441
464
|
SDL_Rect full;
|
|
442
465
|
full.x = 0;
|
|
443
466
|
full.y = 0;
|
|
@@ -445,3 +468,48 @@ void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
|
|
|
445
468
|
full.h = target_height;
|
|
446
469
|
SDL_SetGPUScissor(pass, &full);
|
|
447
470
|
}
|
|
471
|
+
|
|
472
|
+
} // namespace renderer_detail
|
|
473
|
+
} // namespace xtgui
|
|
474
|
+
|
|
475
|
+
void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPUTexture *swapchain,
|
|
476
|
+
const float clear_color[4], XtGuiGeometry *shapes,
|
|
477
|
+
XtGuiGeometry *texts, XtGuiGeometry *images,
|
|
478
|
+
const xtgui::DisplayList &list, float viewport_width,
|
|
479
|
+
float viewport_height, float pixel_density) {
|
|
480
|
+
using namespace xtgui::renderer_detail;
|
|
481
|
+
if (cmd == nullptr || swapchain == nullptr) return;
|
|
482
|
+
if (viewport_width <= 0.0f || viewport_height <= 0.0f) return;
|
|
483
|
+
if (pixel_density <= 0.0f) pixel_density = 1.0f;
|
|
484
|
+
float viewport[2] = {viewport_width, viewport_height};
|
|
485
|
+
SDL_PushGPUVertexUniformData(cmd, 0, viewport, sizeof(viewport));
|
|
486
|
+
|
|
487
|
+
/* `mix-blend-mode` needs the backdrop as a texture; the blend pipeline is
|
|
488
|
+
* compiled at run time on the SPIR-V/MSL backends. Everywhere else (or if
|
|
489
|
+
* that compilation failed) the ordinary single pass still paints the
|
|
490
|
+
* unblended content rather than dropping it. */
|
|
491
|
+
if (!list.layers.empty() && layerRendererReady(g_device)) {
|
|
492
|
+
if (drawLayeredScene(cmd, swapchain, clear_color, shapes, texts, images, list, viewport_width,
|
|
493
|
+
viewport_height, pixel_density))
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
SDL_GPUColorTargetInfo target;
|
|
498
|
+
memset(&target, 0, sizeof(target));
|
|
499
|
+
target.texture = swapchain;
|
|
500
|
+
target.clear_color.r = clear_color != nullptr ? clear_color[0] : 0.0f;
|
|
501
|
+
target.clear_color.g = clear_color != nullptr ? clear_color[1] : 0.0f;
|
|
502
|
+
target.clear_color.b = clear_color != nullptr ? clear_color[2] : 0.0f;
|
|
503
|
+
target.clear_color.a = clear_color != nullptr ? clear_color[3] : 1.0f;
|
|
504
|
+
target.load_op = SDL_GPU_LOADOP_CLEAR;
|
|
505
|
+
target.store_op = SDL_GPU_STOREOP_STORE;
|
|
506
|
+
SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(cmd, &target, 1, nullptr);
|
|
507
|
+
if (pass == nullptr) return;
|
|
508
|
+
int target_width = (int)(viewport_width * pixel_density + 0.5f);
|
|
509
|
+
int target_height = (int)(viewport_height * pixel_density + 0.5f);
|
|
510
|
+
if (shapes != nullptr && texts != nullptr && images != nullptr) {
|
|
511
|
+
drawRuns(pass, shapes->runs, 0, shapes->runs.size(), shapes, texts, images, target_width,
|
|
512
|
+
target_height, pixel_density);
|
|
513
|
+
}
|
|
514
|
+
SDL_EndGPURenderPass(pass);
|
|
515
|
+
}
|
|
@@ -33,7 +33,9 @@ struct DisplayList;
|
|
|
33
33
|
* `clip_width <= 0` means the whole viewport. For image runs, `texture` is the
|
|
34
34
|
* bound texture. */
|
|
35
35
|
struct XtGuiDrawRun {
|
|
36
|
-
|
|
36
|
+
/** `LayerBegin`/`LayerEnd` are zero-count markers around a
|
|
37
|
+
* `mix-blend-mode` group; `layer` indexes `DisplayList::layers`. */
|
|
38
|
+
enum Pipeline { Shape = 0, Text = 1, Image = 2, LayerBegin = 3, LayerEnd = 4 };
|
|
37
39
|
int pipeline = Shape;
|
|
38
40
|
uint32_t first = 0;
|
|
39
41
|
uint32_t count = 0;
|
|
@@ -42,6 +44,7 @@ struct XtGuiDrawRun {
|
|
|
42
44
|
float clip_width = 0;
|
|
43
45
|
float clip_height = 0;
|
|
44
46
|
SDL_GPUTexture *texture = nullptr;
|
|
47
|
+
uint32_t layer = 0;
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
/** Per-window GPU geometry (vertex buffer + draw range). */
|
|
@@ -63,6 +66,12 @@ struct XtGuiGeometry {
|
|
|
63
66
|
/** Painter-order draw runs, built from the display list by
|
|
64
67
|
* `xt_gui_build_runs` and consumed by `xt_gui_renderer_draw`. */
|
|
65
68
|
std::vector<XtGuiDrawRun> runs;
|
|
69
|
+
/** Offscreen colour targets used by `mix-blend-mode` groups (two per nesting
|
|
70
|
+
* level). Reused across frames and tied to the swapchain size/format. */
|
|
71
|
+
std::vector<SDL_GPUTexture *> scene_textures;
|
|
72
|
+
uint32_t scene_width = 0;
|
|
73
|
+
uint32_t scene_height = 0;
|
|
74
|
+
SDL_GPUTextureFormat scene_format = SDL_GPU_TEXTUREFORMAT_INVALID;
|
|
66
75
|
};
|
|
67
76
|
|
|
68
77
|
/** One GPU command buffer shared by every upload a frame performs (shape, text,
|
|
@@ -91,10 +100,15 @@ void xt_gui_geometry_destroy(SDL_GPUDevice *device, XtGuiGeometry *geometry);
|
|
|
91
100
|
* immediately. */
|
|
92
101
|
void xt_gui_geometry_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
93
102
|
const xtgui::DisplayList &list, XtGuiUploadBatch *batch = nullptr);
|
|
94
|
-
/** Draw `
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
103
|
+
/** Draw the display list into `swapchain`, beginning and ending its own render
|
|
104
|
+
* passes. With `mix-blend-mode` groups the renderer draws them to offscreen
|
|
105
|
+
* textures and composites them with a runtime-compiled blend shader; without
|
|
106
|
+
* groups this is the original single clear-and-draw pass. */
|
|
107
|
+
void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPUTexture *swapchain,
|
|
108
|
+
const float clear_color[4], XtGuiGeometry *shapes,
|
|
109
|
+
XtGuiGeometry *texts, XtGuiGeometry *images,
|
|
110
|
+
const xtgui::DisplayList &list, float viewport_width,
|
|
111
|
+
float viewport_height, float pixel_density);
|
|
98
112
|
/** Shape `list.texts`, ensure their glyphs are in the atlas, and upload the
|
|
99
113
|
* text geometry. `scale` is the device pixel density (1.0 = logical pixels). */
|
|
100
114
|
void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
@@ -68,6 +68,8 @@ struct GlyphKeyHash {
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
extern SDL_GPUTexture *g_atlas_texture;
|
|
71
|
+
extern SDL_GPUDevice *g_device;
|
|
72
|
+
extern SDL_GPUTextureFormat g_swapchain_format;
|
|
71
73
|
extern std::vector<unsigned char> g_atlas_mirror;
|
|
72
74
|
extern std::unordered_map<GlyphKey, GlyphEntry, GlyphKeyHash> g_glyphs;
|
|
73
75
|
extern int g_atlas_x;
|
|
@@ -118,6 +120,30 @@ void flushAtlas(SDL_GPUDevice *device, XtGuiUploadBatch *batch);
|
|
|
118
120
|
SDL_GPUGraphicsPipeline *createTextPipeline(SDL_GPUDevice *device, SDL_Window *reference);
|
|
119
121
|
SDL_GPUSampler *createSampler(SDL_GPUDevice *device);
|
|
120
122
|
|
|
123
|
+
/* -- draw runs ------------------------------------------------------------ */
|
|
124
|
+
|
|
125
|
+
/** Draw the drawable runs in `[begin, end)` into an active pass. Layer markers
|
|
126
|
+
* are skipped. State is re-bound after a pass restart. */
|
|
127
|
+
void drawRuns(SDL_GPURenderPass *pass, const std::vector<XtGuiDrawRun> &runs, size_t begin,
|
|
128
|
+
size_t end, XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
|
|
129
|
+
int target_width, int target_height, float pixel_density);
|
|
130
|
+
|
|
131
|
+
/* -- mix-blend-mode layers ------------------------------------------------ */
|
|
132
|
+
|
|
133
|
+
/** True when the runtime blend pipeline was created (and the backend consumes
|
|
134
|
+
* SPIR-V or MSL). D3D12/DXIL has no shader compiler here, so it falls back to
|
|
135
|
+
* the ordinary single-pass painter. */
|
|
136
|
+
bool layerRendererReady(SDL_GPUDevice *device);
|
|
137
|
+
/** Render `list` through the offscreen layer compositor into `swapchain`.
|
|
138
|
+
* Returns false before recording any pass when the scene textures cannot be
|
|
139
|
+
* created, so the caller can fall back to the ordinary painter. */
|
|
140
|
+
bool drawLayeredScene(SDL_GPUCommandBuffer *cmd, SDL_GPUTexture *swapchain,
|
|
141
|
+
const float clear_color[4], XtGuiGeometry *shapes, XtGuiGeometry *texts,
|
|
142
|
+
XtGuiGeometry *images, const xtgui::DisplayList &list,
|
|
143
|
+
float viewport_width, float viewport_height, float pixel_density);
|
|
144
|
+
/** Release the blend pipeline and sampler. */
|
|
145
|
+
void layerRendererDestroy(SDL_GPUDevice *device);
|
|
146
|
+
|
|
121
147
|
/* -- images --------------------------------------------------------------- */
|
|
122
148
|
|
|
123
149
|
SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *reference);
|