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.
Files changed (39) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/driver/version.d.ts +1 -1
  3. package/dist/src/driver/version.js +1 -1
  4. package/dist/tests/e2e/gui-data-url.test.d.ts +10 -0
  5. package/dist/tests/e2e/gui-data-url.test.js +127 -0
  6. package/dist/tests/e2e/gui-data-url.test.js.map +1 -0
  7. package/dist/tests/e2e/gui-effects.test.js +96 -0
  8. package/dist/tests/e2e/gui-effects.test.js.map +1 -1
  9. package/dist/tests/e2e/gui-paint.test.js +77 -0
  10. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  11. package/doc/gui/canvas.md +6 -2
  12. package/doc/gui/known-issues.md +15 -9
  13. package/doc/gui/media.md +11 -2
  14. package/doc/gui/rendering.md +43 -18
  15. package/doc/gui.md +5 -4
  16. package/doc/zh-CN/gui/canvas.md +6 -4
  17. package/doc/zh-CN/gui/known-issues.md +11 -8
  18. package/doc/zh-CN/gui/media.md +9 -2
  19. package/doc/zh-CN/gui/rendering.md +30 -11
  20. package/doc/zh-CN/gui.md +1 -1
  21. package/package.json +7 -7
  22. package/runtime/ext_gui/dom_api_canvas.cpp +42 -24
  23. package/runtime/ext_gui/gradient_values.cpp +44 -48
  24. package/runtime/ext_gui/gui_events.cpp +16 -13
  25. package/runtime/ext_gui/image.cpp +180 -25
  26. package/runtime/ext_gui/image.h +6 -4
  27. package/runtime/ext_gui/paint.cpp +53 -3
  28. package/runtime/ext_gui/paint.h +11 -1
  29. package/runtime/ext_gui/paint_background.cpp +92 -11
  30. package/runtime/ext_gui/paint_effects.cpp +2 -1
  31. package/runtime/ext_gui/paint_internal.h +5 -1
  32. package/runtime/ext_gui/renderer.cpp +81 -13
  33. package/runtime/ext_gui/renderer.h +19 -5
  34. package/runtime/ext_gui/renderer_internal.h +26 -0
  35. package/runtime/ext_gui/renderer_layer.cpp +552 -0
  36. package/runtime/ext_gui/style.h +29 -1
  37. package/runtime/ext_gui/style_print.cpp +10 -2
  38. package/runtime/ext_gui/style_values.cpp +10 -0
  39. 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
- void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
353
- XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
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
- int target_width = (int)(viewport_width * pixel_density + 0.5f);
362
- int target_height = (int)(viewport_height * pixel_density + 0.5f);
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 (const XtGuiDrawRun &run : shapes->runs) {
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 frame starts clean. */
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
- enum Pipeline { Shape = 0, Text = 1, Image = 2 };
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 `geometry` inside an active render pass. */
95
- void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
96
- XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
97
- float viewport_width, float viewport_height, float pixel_density);
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);