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.
- 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 +144 -4
- 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/api.md +1 -1
- package/doc/gui/canvas.md +6 -2
- package/doc/gui/known-issues.md +18 -10
- package/doc/gui/media.md +11 -2
- package/doc/gui/rendering.md +48 -19
- package/doc/gui.md +12 -8
- package/doc/zh-CN/gui/api.md +1 -1
- package/doc/zh-CN/gui/canvas.md +6 -4
- package/doc/zh-CN/gui/known-issues.md +12 -8
- package/doc/zh-CN/gui/media.md +9 -2
- package/doc/zh-CN/gui/rendering.md +37 -15
- package/doc/zh-CN/gui.md +7 -6
- 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 +69 -4
- package/runtime/ext_gui/paint.h +57 -1
- package/runtime/ext_gui/paint_background.cpp +197 -41
- 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_gradient.cpp +156 -0
- package/runtime/ext_gui/renderer_image.cpp +6 -1
- package/runtime/ext_gui/renderer_internal.h +48 -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
|
@@ -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.
|
|
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
|
|