xbintsc 0.5.41 → 0.5.43

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 (43) 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 +144 -4
  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/api.md +1 -1
  12. package/doc/gui/canvas.md +6 -2
  13. package/doc/gui/known-issues.md +18 -10
  14. package/doc/gui/media.md +11 -2
  15. package/doc/gui/rendering.md +48 -19
  16. package/doc/gui.md +12 -8
  17. package/doc/zh-CN/gui/api.md +1 -1
  18. package/doc/zh-CN/gui/canvas.md +6 -4
  19. package/doc/zh-CN/gui/known-issues.md +12 -8
  20. package/doc/zh-CN/gui/media.md +9 -2
  21. package/doc/zh-CN/gui/rendering.md +37 -15
  22. package/doc/zh-CN/gui.md +7 -6
  23. package/package.json +7 -7
  24. package/runtime/ext_gui/dom_api_canvas.cpp +42 -24
  25. package/runtime/ext_gui/gradient_values.cpp +44 -48
  26. package/runtime/ext_gui/gui_events.cpp +16 -13
  27. package/runtime/ext_gui/image.cpp +180 -25
  28. package/runtime/ext_gui/image.h +6 -4
  29. package/runtime/ext_gui/paint.cpp +69 -4
  30. package/runtime/ext_gui/paint.h +57 -1
  31. package/runtime/ext_gui/paint_background.cpp +197 -41
  32. package/runtime/ext_gui/paint_effects.cpp +2 -1
  33. package/runtime/ext_gui/paint_internal.h +5 -1
  34. package/runtime/ext_gui/renderer.cpp +81 -13
  35. package/runtime/ext_gui/renderer.h +19 -5
  36. package/runtime/ext_gui/renderer_gradient.cpp +156 -0
  37. package/runtime/ext_gui/renderer_image.cpp +6 -1
  38. package/runtime/ext_gui/renderer_internal.h +48 -0
  39. package/runtime/ext_gui/renderer_layer.cpp +552 -0
  40. package/runtime/ext_gui/style.h +29 -1
  41. package/runtime/ext_gui/style_print.cpp +10 -2
  42. package/runtime/ext_gui/style_values.cpp +10 -0
  43. package/src/driver/version.ts +1 -1
@@ -0,0 +1,156 @@
1
+ /*
2
+ * xbintsc GUI engine — radial `background-image` rasterisation.
3
+ *
4
+ * The shape pipeline fills one shape with one flat colour, and the bands a
5
+ * radial gradient used to be drawn as are nested ellipses: every band covers the
6
+ * whole interior, so each band's alpha composites over the previous ones and a
7
+ * vignette whose stops fade to `transparent` ends up opaque. The ramp is
8
+ * therefore resolved per pixel into an RGBA texture, which the image pipeline
9
+ * draws as one quad and composites exactly once.
10
+ */
11
+
12
+ #include "renderer_internal.h"
13
+
14
+ #include <algorithm>
15
+ #include <cmath>
16
+ #include <cstdio>
17
+ #include <string>
18
+ #include <unordered_map>
19
+
20
+ namespace xtgui {
21
+ namespace renderer_detail {
22
+
23
+ namespace {
24
+
25
+ /** A rasterised gradient, keyed by the paint pass's cache key plus the logical
26
+ * box size. The texture is re-rasterised in place when the device pixel size
27
+ * changes (a DPI change), so a resized gradient never leaves a stale texture. */
28
+ struct GradientTexture {
29
+ SDL_GPUTexture *texture = nullptr;
30
+ int width = 0;
31
+ int height = 0;
32
+ /** Set by the upload pass that draws this gradient; see `finishGradientUpload`. */
33
+ bool used = false;
34
+ };
35
+
36
+ std::unordered_map<std::string, GradientTexture> g_gradient_textures;
37
+
38
+ /** How many rasterised gradients that are no longer drawn are kept. A gradient's
39
+ * stops can animate (a `hue-rotate` loop, say), which would otherwise rasterise a
40
+ * fresh texture every frame and never free the old ones. */
41
+ constexpr size_t kMaxStaleGradients = 8;
42
+
43
+ /** Largest texture edge a gradient is rasterised at. A radial ramp is smooth, so
44
+ * a larger box is rasterised smaller and stretched: the sampled colours are
45
+ * unchanged (the stretch is uniform), only the resolution of the ramp drops. */
46
+ constexpr int kMaxGradientEdge = 1024;
47
+
48
+ unsigned char byteOf(float value) {
49
+ if (!(value > 0.0f)) return 0; // NaN and negatives
50
+ if (value > 1.0f) value = 1.0f;
51
+ return (unsigned char)(value * 255.0f + 0.5f);
52
+ }
53
+
54
+ /** Rasterise `gradient` into `out`: the box maps 1:1 onto the texture. The ramp
55
+ * covers the whole box — past the ending shape CSS keeps the last stop's colour
56
+ * rather than fading out — so there is no shape edge to antialias; the texels
57
+ * are sampled with linear filtering. */
58
+ void rasterizeGradient(const PaintGradient &gradient, float logical_width, float logical_height,
59
+ int pixel_width, int pixel_height, Image *out) {
60
+ out->width = pixel_width;
61
+ out->height = pixel_height;
62
+ out->pixels.assign((size_t)pixel_width * (size_t)pixel_height * 4u, 0);
63
+ const float pixel_x = logical_width / (float)pixel_width; // logical px per texel
64
+ const float pixel_y = logical_height / (float)pixel_height;
65
+ const float inv_rx = 1.0f / gradient.radius_x;
66
+ const float inv_ry = 1.0f / gradient.radius_y;
67
+ for (int y = 0; y < pixel_height; y++) {
68
+ const float local_y = ((float)y + 0.5f) * pixel_y;
69
+ unsigned char *row = out->pixels.data() + (size_t)y * (size_t)pixel_width * 4u;
70
+ for (int x = 0; x < pixel_width; x++) {
71
+ const float local_x = ((float)x + 0.5f) * pixel_x;
72
+ const float u = (local_x - gradient.center_x) * inv_rx;
73
+ const float v = (local_y - gradient.center_y) * inv_ry;
74
+ const float t = std::sqrt(u * u + v * v);
75
+ Color color =
76
+ paint_detail::gradientColorAt(gradient.stops, gradient.repeating, gradient.period, t);
77
+ if (color.a <= 0.0f) continue;
78
+ unsigned char *texel = row + (size_t)x * 4u;
79
+ texel[0] = byteOf(color.r);
80
+ texel[1] = byteOf(color.g);
81
+ texel[2] = byteOf(color.b);
82
+ texel[3] = byteOf(color.a);
83
+ }
84
+ }
85
+ }
86
+
87
+ /** The device size a gradient of `pixel_width` x `pixel_height` is rasterised at,
88
+ * preserving its aspect ratio under `kMaxGradientEdge`. */
89
+ void rasterSize(int pixel_width, int pixel_height, int *width, int *height) {
90
+ *width = std::max(1, pixel_width);
91
+ *height = std::max(1, pixel_height);
92
+ if (*width <= kMaxGradientEdge && *height <= kMaxGradientEdge) return;
93
+ float shrink = (float)kMaxGradientEdge / (float)std::max(*width, *height);
94
+ *width = std::max(1, (int)((float)*width * shrink + 0.5f));
95
+ *height = std::max(1, (int)((float)*height * shrink + 0.5f));
96
+ }
97
+
98
+ } // namespace
99
+
100
+ void beginGradientUpload() {
101
+ for (auto &entry : g_gradient_textures) entry.second.used = false;
102
+ }
103
+
104
+ void finishGradientUpload(SDL_GPUDevice *device) {
105
+ if (g_gradient_textures.size() <= kMaxStaleGradients) return;
106
+ /* The display list of the frame being built may hold pointers to the textures
107
+ * it used, so only gradients it did *not* use can go. */
108
+ for (auto it = g_gradient_textures.begin(); it != g_gradient_textures.end();) {
109
+ if (it->second.used) {
110
+ ++it;
111
+ continue;
112
+ }
113
+ if (it->second.texture != nullptr) SDL_ReleaseGPUTexture(device, it->second.texture);
114
+ it = g_gradient_textures.erase(it);
115
+ }
116
+ }
117
+
118
+ SDL_GPUTexture *ensureGradientTexture(SDL_GPUDevice *device, const xtgui::PaintImage &image,
119
+ int pixel_width, int pixel_height, XtGuiUploadBatch *batch) {
120
+ if (image.gradient == nullptr || image.width <= 0.0f || image.height <= 0.0f) return nullptr;
121
+ const xtgui::PaintGradient &gradient = *image.gradient;
122
+ if (gradient.radius_x <= 0.0f || gradient.radius_y <= 0.0f || gradient.stops.size() < 2) {
123
+ return nullptr;
124
+ }
125
+ int width = 0;
126
+ int height = 0;
127
+ rasterSize(pixel_width, pixel_height, &width, &height);
128
+ /* The key carries the ramp, its geometry and the logical box size: two boxes
129
+ * of different sizes can resolve the same px radius but not the same picture. */
130
+ char box[64];
131
+ std::snprintf(box, sizeof(box), "@%.3f,%.3f", image.width, image.height);
132
+ std::string key = gradient.key + box;
133
+ GradientTexture &entry = g_gradient_textures[key];
134
+ entry.used = true;
135
+ if (entry.texture != nullptr && entry.width == width && entry.height == height) {
136
+ return entry.texture;
137
+ }
138
+ Image raster;
139
+ rasterizeGradient(gradient, image.width, image.height, width, height, &raster);
140
+ SDL_GPUTexture *texture = createImageTexture(device, raster, batch);
141
+ if (entry.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.texture);
142
+ entry.texture = texture;
143
+ entry.width = texture != nullptr ? width : 0;
144
+ entry.height = texture != nullptr ? height : 0;
145
+ return texture;
146
+ }
147
+
148
+ void releaseGradientTextures(SDL_GPUDevice *device) {
149
+ for (auto &entry : g_gradient_textures) {
150
+ if (entry.second.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.second.texture);
151
+ }
152
+ g_gradient_textures.clear();
153
+ }
154
+
155
+ } // namespace renderer_detail
156
+ } // namespace xtgui
@@ -379,6 +379,7 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
379
379
  full.v0 = 0.0f;
380
380
  full.u1 = 1.0f;
381
381
  full.v1 = 1.0f;
382
+ beginGradientUpload();
382
383
  for (const xtgui::PaintOp &op : list.ops) {
383
384
  if (op.kind != xtgui::PaintKind::Image || op.index >= list.images.size()) continue;
384
385
  const xtgui::PaintImage &image = list.images[op.index];
@@ -390,7 +391,9 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
390
391
  if (pixel_width < 1) pixel_width = 1;
391
392
  if (pixel_height < 1) pixel_height = 1;
392
393
  SDL_GPUTexture *texture = nullptr;
393
- if (image.canvas_id != 0) {
394
+ if (image.gradient != nullptr) {
395
+ texture = ensureGradientTexture(device, image, pixel_width, pixel_height, batch);
396
+ } else if (image.canvas_id != 0) {
394
397
  texture = ensureCanvasTexture(device, image.canvas_id, batch);
395
398
  } else if (image.media_id != 0) {
396
399
  texture = ensureMediaTexture(device, image.media_id, batch);
@@ -405,11 +408,13 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
405
408
  geometry->op_counts[op.index] = count;
406
409
  geometry->op_textures[op.index] = texture;
407
410
  }
411
+ finishGradientUpload(device);
408
412
  uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex), batch);
409
413
  }
410
414
 
411
415
  void xt_gui_image_textures_destroy(SDL_GPUDevice *device) {
412
416
  using namespace xtgui::renderer_detail;
417
+ releaseGradientTextures(device);
413
418
  for (auto &entry : g_image_textures) {
414
419
  if (entry.second != nullptr) SDL_ReleaseGPUTexture(device, entry.second);
415
420
  }
@@ -14,6 +14,7 @@
14
14
  #include <unordered_map>
15
15
  #include <vector>
16
16
 
17
+ #include "image.h"
17
18
  #include "paint.h"
18
19
  #include "renderer.h"
19
20
 
@@ -68,6 +69,8 @@ struct GlyphKeyHash {
68
69
  };
69
70
 
70
71
  extern SDL_GPUTexture *g_atlas_texture;
72
+ extern SDL_GPUDevice *g_device;
73
+ extern SDL_GPUTextureFormat g_swapchain_format;
71
74
  extern std::vector<unsigned char> g_atlas_mirror;
72
75
  extern std::unordered_map<GlyphKey, GlyphEntry, GlyphKeyHash> g_glyphs;
73
76
  extern int g_atlas_x;
@@ -118,9 +121,37 @@ void flushAtlas(SDL_GPUDevice *device, XtGuiUploadBatch *batch);
118
121
  SDL_GPUGraphicsPipeline *createTextPipeline(SDL_GPUDevice *device, SDL_Window *reference);
119
122
  SDL_GPUSampler *createSampler(SDL_GPUDevice *device);
120
123
 
124
+ /* -- draw runs ------------------------------------------------------------ */
125
+
126
+ /** Draw the drawable runs in `[begin, end)` into an active pass. Layer markers
127
+ * are skipped. State is re-bound after a pass restart. */
128
+ void drawRuns(SDL_GPURenderPass *pass, const std::vector<XtGuiDrawRun> &runs, size_t begin,
129
+ size_t end, XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
130
+ int target_width, int target_height, float pixel_density);
131
+
132
+ /* -- mix-blend-mode layers ------------------------------------------------ */
133
+
134
+ /** True when the runtime blend pipeline was created (and the backend consumes
135
+ * SPIR-V or MSL). D3D12/DXIL has no shader compiler here, so it falls back to
136
+ * the ordinary single-pass painter. */
137
+ bool layerRendererReady(SDL_GPUDevice *device);
138
+ /** Render `list` through the offscreen layer compositor into `swapchain`.
139
+ * Returns false before recording any pass when the scene textures cannot be
140
+ * created, so the caller can fall back to the ordinary painter. */
141
+ bool drawLayeredScene(SDL_GPUCommandBuffer *cmd, SDL_GPUTexture *swapchain,
142
+ const float clear_color[4], XtGuiGeometry *shapes, XtGuiGeometry *texts,
143
+ XtGuiGeometry *images, const xtgui::DisplayList &list,
144
+ float viewport_width, float viewport_height, float pixel_density);
145
+ /** Release the blend pipeline and sampler. */
146
+ void layerRendererDestroy(SDL_GPUDevice *device);
147
+
121
148
  /* -- images --------------------------------------------------------------- */
122
149
 
123
150
  SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *reference);
151
+ /** Create an RGBA texture holding `image` and upload its pixels. Returns null
152
+ * (after reporting the SDL error) when either step fails. */
153
+ SDL_GPUTexture *createImageTexture(SDL_GPUDevice *device, const xtgui::Image &image,
154
+ XtGuiUploadBatch *batch);
124
155
  /** Decode `src` (cached) and upload it as an RGBA texture. `pixel_width` /
125
156
  * `pixel_height` are the drawn size in device pixels; vector sources (SVG) are
126
157
  * rasterised at that size, raster sources ignore it. The texture cache is keyed
@@ -128,6 +159,23 @@ SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *
128
159
  SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src, int pixel_width,
129
160
  int pixel_height, XtGuiUploadBatch *batch);
130
161
 
162
+ /* -- background gradients ------------------------------------------------- */
163
+
164
+ /** Rasterise a `PaintImage` radial gradient into an RGBA texture and cache it by
165
+ * the paint pass's key. The ramp is resolved per device pixel, so a translucent
166
+ * gradient composites once instead of once per band. Bracket a frame's uploads
167
+ * with `beginGradientUpload`/`finishGradientUpload` so textures the frame no
168
+ * longer draws can be reclaimed; returns null when it cannot be created. */
169
+ SDL_GPUTexture *ensureGradientTexture(SDL_GPUDevice *device, const xtgui::PaintImage &image,
170
+ int pixel_width, int pixel_height, XtGuiUploadBatch *batch);
171
+ /** Mark every cached gradient unused; `ensureGradientTexture` marks the ones the
172
+ * upload pass draws. */
173
+ void beginGradientUpload();
174
+ /** Release cached gradients the pass did not draw, keeping the cache bounded. */
175
+ void finishGradientUpload(SDL_GPUDevice *device);
176
+ /** Release every rasterised gradient texture. */
177
+ void releaseGradientTextures(SDL_GPUDevice *device);
178
+
131
179
  } // namespace renderer_detail
132
180
  } // namespace xtgui
133
181