xbintsc 0.5.42 → 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.
@@ -4,4 +4,4 @@
4
4
  * It lives apart from the command dispatch so the executable entry point stays
5
5
  * under the repository's per-file line budget; the text is data, not logic.
6
6
  */
7
- export declare const HELP = "xbintsc 0.5.42 - TypeScript binary compiler\n\nUsage:\n xbintsc build <file.ts> [options] Compile to a native binary\n xbintsc run <file.ts> [-- args] Compile and execute\n xbintsc check <file.ts> Parse, bind and check without linking\n xbintsc watch <file.ts> [options] Rebuild whenever the source tree changes\n xbintsc emit <file.ts> Print LLVM IR\n xbintsc doctor Report the resolved toolchain\n xbintsc version Print the version\n xbintsc help Show this message\n\nOptions:\n -o, --output <path> Output path\n --out <dir> Output directory (default: build/)\n --emit <kind> exe | obj | ir (default: exe)\n -O0..-O3 Optimization level (default: -O2)\n --ext <names> Comma separated extensions (e.g. node,gui,media)\n --ext-native <m> Register a C++/Rust extension from a JSON manifest\n (comma separated for several)\n --config <path> Use a project config (default: auto-detect\n xbintsc.config.json)\n --no-config Do not read any project config\n --icon <path> Embed an application icon (PNG/ICO/ICNS)\n --bundle macOS: also produce a .app bundle\n --app-name <name> Bundle / display name\n --app-id <id> macOS bundle identifier (e.g. com.example.demo)\n --force Ignore the incremental cache\n --verbose Print progress information\n --time Print per-phase build timings\n --debug Emit debug info (DWARF/CodeView) and keep frame pointers\n --check Run the semantic checker (default)\n --no-check Skip the semantic checker; only erase type syntax\n --lto Compile program and runtime to LTO bitcode; link with lld\n --pgo-generate [dir] Build an instrumented binary writing profiles to dir\n --pgo-use [dir] Build with the profiles collected from --pgo-generate\n --json Print diagnostics as a JSON array on stdout\n --no-hints Do not append the \"hint: \u2026 see <doc>\" line to failures\n\nxbintsc compiles a subset of TypeScript to a native binary. Types are erased;\nonly inference-free facts (names, redeclarations, call arity) are checked.\nNode built-ins need --ext node, packages in node_modules are bundled at build\ntime, and clang 16+ is required to link.\n\nInstalled as a project dependency (npm):\n npx xbintsc doctor\n npx xbintsc build <entry.ts> --out build\n npx xbintsc run <entry.ts>\nThe first build compiles the C runtime; later builds reuse the cache.\n\nDocs: doc/ai/README.md (task guide) - doc/ai/language-support.md (subset) -\ndoc/ai/troubleshooting.md (failures) - llms.txt (index of every document).\nWhen installed from npm, these live under node_modules/xbintsc/doc/ and\nnode_modules/xbintsc/doc/npm-package.md is the project guide.\n";
7
+ export declare const HELP = "xbintsc 0.5.43 - TypeScript binary compiler\n\nUsage:\n xbintsc build <file.ts> [options] Compile to a native binary\n xbintsc run <file.ts> [-- args] Compile and execute\n xbintsc check <file.ts> Parse, bind and check without linking\n xbintsc watch <file.ts> [options] Rebuild whenever the source tree changes\n xbintsc emit <file.ts> Print LLVM IR\n xbintsc doctor Report the resolved toolchain\n xbintsc version Print the version\n xbintsc help Show this message\n\nOptions:\n -o, --output <path> Output path\n --out <dir> Output directory (default: build/)\n --emit <kind> exe | obj | ir (default: exe)\n -O0..-O3 Optimization level (default: -O2)\n --ext <names> Comma separated extensions (e.g. node,gui,media)\n --ext-native <m> Register a C++/Rust extension from a JSON manifest\n (comma separated for several)\n --config <path> Use a project config (default: auto-detect\n xbintsc.config.json)\n --no-config Do not read any project config\n --icon <path> Embed an application icon (PNG/ICO/ICNS)\n --bundle macOS: also produce a .app bundle\n --app-name <name> Bundle / display name\n --app-id <id> macOS bundle identifier (e.g. com.example.demo)\n --force Ignore the incremental cache\n --verbose Print progress information\n --time Print per-phase build timings\n --debug Emit debug info (DWARF/CodeView) and keep frame pointers\n --check Run the semantic checker (default)\n --no-check Skip the semantic checker; only erase type syntax\n --lto Compile program and runtime to LTO bitcode; link with lld\n --pgo-generate [dir] Build an instrumented binary writing profiles to dir\n --pgo-use [dir] Build with the profiles collected from --pgo-generate\n --json Print diagnostics as a JSON array on stdout\n --no-hints Do not append the \"hint: \u2026 see <doc>\" line to failures\n\nxbintsc compiles a subset of TypeScript to a native binary. Types are erased;\nonly inference-free facts (names, redeclarations, call arity) are checked.\nNode built-ins need --ext node, packages in node_modules are bundled at build\ntime, and clang 16+ is required to link.\n\nInstalled as a project dependency (npm):\n npx xbintsc doctor\n npx xbintsc build <entry.ts> --out build\n npx xbintsc run <entry.ts>\nThe first build compiles the C runtime; later builds reuse the cache.\n\nDocs: doc/ai/README.md (task guide) - doc/ai/language-support.md (subset) -\ndoc/ai/troubleshooting.md (failures) - llms.txt (index of every document).\nWhen installed from npm, these live under node_modules/xbintsc/doc/ and\nnode_modules/xbintsc/doc/npm-package.md is the project guide.\n";
@@ -9,4 +9,4 @@
9
9
  * `main`/`master` only (see `.githooks/pre-commit`), so feature branches never
10
10
  * change this line and merging two of them cannot conflict here.
11
11
  */
12
- export declare const COMPILER_VERSION = "0.5.42";
12
+ export declare const COMPILER_VERSION = "0.5.43";
@@ -9,5 +9,5 @@
9
9
  * `main`/`master` only (see `.githooks/pre-commit`), so feature branches never
10
10
  * change this line and merging two of them cannot conflict here.
11
11
  */
12
- export const COMPILER_VERSION = "0.5.42";
12
+ export const COMPILER_VERSION = "0.5.43";
13
13
  //# sourceMappingURL=version.js.map
@@ -130,13 +130,56 @@ describe.skipIf(!guiAvailable)("gui extension — effects", () => {
130
130
  const list = stdout.slice(stdout.indexOf("list-start"), stdout.indexOf("list-end"));
131
131
  expect(value("image")).toContain("radial-gradient()");
132
132
  expect(value("image")).toContain("linear-gradient()");
133
- // Both layers are painted: the red radial core and its green outer ring
134
- // (the linear blue layer is underneath them).
133
+ // The radial layer is one rasterised gradient carrying its stops; the linear
134
+ // layer is still painted as solid bands.
135
+ expect(list).toContain("gradient=radial");
135
136
  expect(list).toContain("#ff0000ff");
136
137
  expect(list).toContain("#00ff00ff");
137
138
  expect(list).toMatch(/#0000[0-9a-f]{2}ff/);
138
- // Radial bands plus the linear strips.
139
- expect(Number(value("count"))).toBeGreaterThan(30);
139
+ // Eighteen linear strips plus the background colour: the gradient image is
140
+ // not a stack of rectangles.
141
+ expect(Number(value("count"))).toBe(19);
142
+ });
143
+ it("rasterises a translucent radial vignette as one gradient image", () => {
144
+ const { value, stdout } = harness.compileAndRun("vignette", `
145
+ import { createWindow, run } from "gui";
146
+
147
+ const HTML = \`
148
+ <html><head><style>
149
+ body { margin: 0; }
150
+ #scene { position: relative; width: 200px; height: 100px; background: #3366cc; }
151
+ #dim {
152
+ position: absolute; inset: -5%; z-index: 2; pointer-events: none;
153
+ background: radial-gradient(ellipse at center,
154
+ transparent 45%, rgba(0, 0, 0, 0.46) 76%, rgba(0, 0, 0, 0.88) 100%);
155
+ }
156
+ </style></head><body>
157
+ <div id="scene"><div id="dim"></div></div>
158
+ </body></html>
159
+ \`;
160
+
161
+ const win = createWindow({ title: "vignette", width: 400, height: 300 });
162
+ win.on("ready", () => {
163
+ console.log("image=" + win.computedStyle("#dim", "background-image"));
164
+ console.log("count=" + win.paintCount());
165
+ console.log("list-start");
166
+ console.log(win.paintList());
167
+ console.log("list-end");
168
+ win.close();
169
+ });
170
+ win.loadHTML(HTML);
171
+ run();
172
+ `);
173
+ const list = stdout.slice(stdout.indexOf("list-start"), stdout.indexOf("list-end"));
174
+ expect(value("image")).toContain("radial-gradient()");
175
+ // A single op whose stops keep the alpha ramp. Painting this as nested
176
+ // translucent ellipses composited the alpha once per band and turned the
177
+ // whole scene solid black.
178
+ expect((list.match(/gradient=radial/g) ?? []).length).toBe(1);
179
+ expect(list).toContain("#00000000@0.4500");
180
+ expect(list).toContain("#000000e0@1.0000");
181
+ // Only the scene background is a rectangle; the vignette is not bands.
182
+ expect(value("count")).toBe("1");
140
183
  });
141
184
  it("tiles repeating gradients across the whole box", () => {
142
185
  const { value, stdout } = harness.compileAndRun("repeating-gradient", `
@@ -231,6 +274,7 @@ describe.skipIf(!guiAvailable)("gui extension — effects", () => {
231
274
  `);
232
275
  const list = stdout.slice(stdout.indexOf("list-start"), stdout.indexOf("list-end"));
233
276
  expect(value("image")).toBe("repeating-radial-gradient()");
277
+ expect(list).toContain("gradient=repeating-radial");
234
278
  expect(list).toContain("#ff0000ff");
235
279
  expect(list).toContain("#0000ffff");
236
280
  });
@@ -1 +1 @@
1
- {"version":3,"file":"gui-effects.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-effects.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gui-effects.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-effects.test.ts"],"names":[],"mappings":"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"}
package/doc/gui/api.md CHANGED
@@ -187,7 +187,7 @@ win.queryCount(selector) // number of matching elements
187
187
  win.getBoundingClientRect(selector) // { x, y, width, height } (border box, transformed)
188
188
  win.documentTree() // serialized DOM (debugging)
189
189
  win.layoutTree() // serialized layout boxes (debugging)
190
- win.paintCount() // number of shapes in the display list
190
+ win.paintCount() // number of filled rectangles in the display list
191
191
  win.paintList() // serialized display list (debugging)
192
192
  win.inlineSvg(selector) // serialized inline <svg>: { markup, width, height, shapes }
193
193
  win.measureText(text, fontSize?, family?) // shaped advance width in pixels
@@ -72,9 +72,11 @@
72
72
  silhouettes; at most two nested `clip-path` shapes are intersected (deeper
73
73
  nesting keeps the outer two); and images/canvases are not colour-filtered
74
74
  except for the brightness/opacity tint the image shader can carry. Canvas
75
- textures are sampled nearest-neighbour. Background gradients remain solid
76
- bands (18 for linear, 32 concentric ellipses for radial; 64 for repeating
77
- gradients), so a soft gradient may show slight banding.
75
+ textures are sampled nearest-neighbour. A background linear gradient remains
76
+ solid bands (18 strips, 64 while repeating), so a large soft ramp may show
77
+ slight banding; a radial gradient is rasterised per pixel into a texture
78
+ (capped at 1024 px per edge, then stretched, so a hard stop in a very large
79
+ box comes out slightly soft).
78
80
  - **SVG is NanoSVG's subset.** SVG *files* (`<img src="logo.svg">`) and inline
79
81
  `<svg>` trees share the vendored NanoSVG parser/rasteriser
80
82
  (`runtime/ext_gui/svg.cpp`, `inline_svg.cpp`), which renders
@@ -87,12 +87,16 @@ topmost), and both gradient kinds are supported:
87
87
  length (`px`/`em`/…) offsets, the `color <start> <end>` shorthand and
88
88
  multi-stop colours work (the band walk is axis-aligned, so an oblique angle
89
89
  uses the nearest axis);
90
- - a **radial** gradient is 32 concentric ellipses (64 when repeating). Each is a
91
- unit-radius rounded rectangle mapped through the shape pipeline's transform
92
- slot, so a circle or ellipse keeps the distance-field antialiasing.
93
- Shape/size keywords (`circle`/`ellipse`, `closest-side`/`farthest-side`/
94
- `closest-corner`/`farthest-corner`, explicit radii) and `at <position>` are
95
- parsed.
90
+ - a **radial** gradient is resolved per pixel in `renderer_gradient.cpp` into one
91
+ RGBA texture that is drawn as a single image quad (`PaintGradient`). Nesting the
92
+ bands instead would composite their alpha once per band — since every band
93
+ covers the whole interior — so a vignette whose stops fade to `transparent`
94
+ painted an opaque disc over the scene. Shape/size keywords
95
+ (`circle`/`ellipse`, `closest-side`/`farthest-side`/`closest-corner`/
96
+ `farthest-corner`, explicit radii) and `at <position>` are parsed; a
97
+ keyword-sized ellipse keeps the box's aspect ratio and is scaled until it meets
98
+ the side or corner, as CSS does. Stops interpolate in premultiplied alpha, and
99
+ the last stop extends past the ending shape over the rest of the box.
96
100
 
97
101
  `repeating-linear-gradient()` and `repeating-radial-gradient()` tile the stop
98
102
  pattern from 0 to the last stop's offset across the whole box; the stop list is
@@ -101,8 +105,8 @@ gradient whose last stop is below 100% holds that colour for the rest of the box
101
105
  as in CSS.
102
106
 
103
107
  The background colour sits under every layer. Gradients are partially
104
- transparent if their stops are, and the band approximation can show banding and
105
- alpha stacking on a very translucent multi-stop gradient.
108
+ transparent if their stops are; a radial gradient is exact per pixel, while a
109
+ linear one still samples 18 bands and can show banding on a large soft ramp.
106
110
 
107
111
  **Generated content** needs no new display-list kind: `::before`/`::after` are
108
112
  ordinary layout boxes owned by the per-restyle arena in `style.cpp`, so their
package/doc/gui.md CHANGED
@@ -336,8 +336,9 @@ engine were Rust.
336
336
  - Generated content: `::before`/`::after` selectors, inherited pseudo styles,
337
337
  `content` strings/escapes/`attr()`, and synthetic layout boxes that never
338
338
  leak into DOM traversal.
339
- - Multi-layer `background-image` with linear and radial gradients, painted
340
- bottom-up as solid bands (18 strips / 32 concentric ellipses).
339
+ - Multi-layer `background-image` with linear gradients painted bottom-up as
340
+ solid bands (18 strips) and radial gradients rasterised per pixel into one
341
+ image texture (`renderer_gradient.cpp`).
341
342
  - `text-shadow` as shadow text runs, including a nine-tap blur approximate.
342
343
  - `clip-path` polygons (`inset`/`circle`/`ellipse`/`polygon`) resolved to
343
344
  device space, ear-clipped and intersected with shape geometry in
@@ -503,8 +504,10 @@ engine were Rust.
503
504
  pseudo styles into a per-restyle arena, resolves `content`, and layout inserts
504
505
  their boxes before/after the DOM children. `gradient_values.cpp` models a
505
506
  background layer list (linear/radial, shape/size/position/stops),
506
- `paint_background.cpp` paints it, `paint_effects.cpp` applies filter colour
507
- functions plus nine-tap blur/drop-shadow and emits `text-shadow` runs, and
507
+ `paint_background.cpp` resolves and paints it (radial layers become one
508
+ `PaintGradient`, which `renderer_gradient.cpp` rasterises into a texture),
509
+ `paint_effects.cpp` applies filter colour functions plus nine-tap
510
+ blur/drop-shadow and emits `text-shadow` runs, and
508
511
  `paint_clip.cpp`/`renderer_clip.cpp` clip geometry against triangulated
509
512
  `clip-path` polygons. E2E: `tests/e2e/gui-effects.test.ts`.
510
513
  - **M20** ✅ rendering performance. `@media` is evaluated once per rule per
@@ -175,7 +175,7 @@ win.queryCount(selector) // 匹配元素的数量
175
175
  win.getBoundingClientRect(selector) // { x, y, width, height }(边框盒,含变换)
176
176
  win.documentTree() // 序列化的 DOM(调试用)
177
177
  win.layoutTree() // 序列化的布局盒(调试用)
178
- win.paintCount() // 显示列表中图形的数量
178
+ win.paintCount() // 显示列表中填充矩形的数量
179
179
  win.paintList() // 序列化的显示列表(调试用)
180
180
  win.inlineSvg(selector) // 序列化的内联 <svg>:{ markup, width, height, shapes }
181
181
  win.measureText(text, fontSize?, family?) // 整形后的推进宽度(像素)
@@ -51,8 +51,9 @@
51
51
  `filter: blur()` 是按图形做九次偏移的近似,而不是真正的组模糊,重叠的子元素会加深;
52
52
  `drop-shadow` 用盒子轮廓近似;最多只交叉两层嵌套 `clip-path`(更深时保留外侧两层);
53
53
  图像/画布除图像着色器能携带的亮度与透明度染色外不做颜色滤镜。Canvas 纹理使用
54
- 最近邻采样。背景渐变仍是纯色带(线性 18 条、径向 32 个同心椭圆;重复渐变
55
- 64 条),柔和渐变可能出现轻微条带。
54
+ 最近邻采样。背景线性渐变仍是纯色带(18 条,重复渐变 64 条),较大的柔和渐变
55
+ 可能出现轻微条带;径向渐变则逐像素光栅化成纹理(每边最大 1024 px,超出后拉伸,
56
+ 因此超大盒子上的硬色标会略微变软)。
56
57
  - **SVG 是 NanoSVG 的子集。** SVG *文件*(`<img src="logo.svg">`)与内联
57
58
  `<svg>` 子树共用 vendored 的 NanoSVG 解析/光栅化器(`runtime/ext_gui/svg.cpp`、
58
59
  `inline_svg.cpp`),它会渲染 `path`/`rect`/`circle`/`ellipse`/`line`/
@@ -72,18 +72,21 @@
72
72
  `to <side>`、`deg`/`grad`/`rad`/`turn`、百分比或长度(`px`/`em`/…)偏移、
73
73
  `color <start> <end>` 简写与多色标(色带沿最近轴行走,因此斜角会退化为
74
74
  最近的轴向);
75
- - **径向**渐变是 32 个同心椭圆(重复渐变 64 个)。每个都是单位半径的圆角矩形,
76
- 经图形管线的 transform 槽映射,因此圆/椭圆仍保留距离场抗锯齿。支持形状/尺寸
77
- 关键字(`circle`/`ellipse`、`closest-side`/`farthest-side`/`closest-corner`/
78
- `farthest-corner`、显式半径)与 `at <position>`。
75
+ - **径向**渐变由 `renderer_gradient.cpp` 逐像素解算成一张 RGBA 纹理,作为单个
76
+ 图像四边形绘制(`PaintGradient`)。若沿用嵌套色带,由于每条色带都会覆盖整个
77
+ 内部区域,alpha 会叠加多次——淡出到 `transparent` 的暗角会把整个场景涂成实心
78
+ 黑盘。支持形状/尺寸关键字(`circle`/`ellipse`、`closest-side`/`farthest-side`/
79
+ `closest-corner`/`farthest-corner`、显式半径)与 `at <position>`;关键字尺寸
80
+ 的椭圆保持盒子的宽高比并缩放到刚好接触对应边或角,与 CSS 一致。色标在预乘
81
+ alpha 空间中插值,最后一个色标会延伸覆盖结束形状之外的剩余盒子。
79
82
 
80
83
  `repeating-linear-gradient()` 与 `repeating-radial-gradient()` 会在整个盒子上
81
84
  从 0 平铺到最后一个色标的偏移量;色标列表不会被强制结束在 100%,因此
82
85
  `red 0%, blue 10%` 每 10% 重复一次。普通渐变的最后一个色标若小于 100%,则剩余
83
86
  区域保持该颜色,与 CSS 一致。
84
87
 
85
- 背景色位于所有层下方。若渐变色标带透明度,渐变也会半透明;色带近似可能产生
86
- 轻微条带,非常透明的多色标渐变会出现 alpha 叠加。
88
+ 背景色位于所有层下方。若渐变色标带透明度,渐变也会半透明;径向渐变逐像素精确,
89
+ 线性渐变仍采样 18 条色带,较大的柔和渐变可能出现条带。
87
90
 
88
91
  **生成内容**不需要新的显示列表类型:`::before`/`::after` 是 `style.cpp` 中
89
92
  每次重算样式时重建的普通布局盒,因此其背景、边框、文本与 transform 都走同一条
package/doc/zh-CN/gui.md CHANGED
@@ -262,8 +262,8 @@ FFmpeg `7.1`(供可选的 `media` 归档使用;可用 `SDL3_TAG` /
262
262
  特性);`matchMedia()` 使用同一个求值器。
263
263
  - 生成内容:`::before`/`::after` 选择器、继承的伪样式、`content` 字符串/
264
264
  转义/`attr()`,以及不会泄漏到 DOM 遍历的合成布局盒。
265
- - 多层 `background-image` 的线性与径向渐变,自下而上按纯色带绘制
266
- (线性 18 条、径向 32 个同心椭圆)。
265
+ - 多层 `background-image`:线性渐变自下而上按纯色带绘制(18 条),径向渐变
266
+ 逐像素光栅化成一张图像纹理(`renderer_gradient.cpp`)。
267
267
  - `text-shadow` 作为阴影文本 run,并带九次采样模糊近似。
268
268
  - `clip-path` 多边形(`inset`/`circle`/`ellipse`/`polygon`)解析到设备空间,
269
269
  用耳切法三角化,并在 `renderer_clip.cpp` 中与图形几何求交——不需要模板/
@@ -408,9 +408,10 @@ FFmpeg `7.1`(供可选的 `media` 归档使用;可用 `SDL3_TAG` /
408
408
  识别 `::before`/`::after`。`style.cpp` 把伪样式算进每次重算样式时重建的
409
409
  arena、解析 `content`,布局在真实子元素前后插入它们的盒子。
410
410
  `gradient_values.cpp` 建模背景层列表(线性/径向、形状/尺寸/位置/色标),
411
- `paint_background.cpp` 绘制它,`paint_effects.cpp` 应用滤镜颜色函数与九次
412
- 采样模糊/投影并生成 `text-shadow` run,`paint_clip.cpp`/`renderer_clip.cpp`
413
- 对三角化的 `clip-path` 多边形裁剪几何。e2e 见
411
+ `paint_background.cpp` 解算并绘制它(径向层变成一个 `PaintGradient`,由
412
+ `renderer_gradient.cpp` 光栅化成纹理),`paint_effects.cpp` 应用滤镜颜色函数
413
+ 与九次采样模糊/投影并生成 `text-shadow` run,`paint_clip.cpp`/
414
+ `renderer_clip.cpp` 对三角化的 `clip-path` 多边形裁剪几何。e2e 见
414
415
  `tests/e2e/gui-effects.test.ts`。
415
416
  - **M20** ✅ 渲染性能。`@media` 每次重算样式时每条规则只求值一次;没有
416
417
  `::before`/`::after` 的样式表完全跳过生成内容;只有样式表包含
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xbintsc",
3
- "version": "0.5.42",
3
+ "version": "0.5.43",
4
4
  "description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -72,12 +72,12 @@
72
72
  "vitest": "^2.1.8"
73
73
  },
74
74
  "optionalDependencies": {
75
- "@xbintsc/linux-x64": "0.5.42",
76
- "@xbintsc/linux-arm64": "0.5.42",
77
- "@xbintsc/darwin-x64": "0.5.42",
78
- "@xbintsc/darwin-arm64": "0.5.42",
79
- "@xbintsc/win32-x64": "0.5.42",
80
- "@xbintsc/win32-arm64": "0.5.42"
75
+ "@xbintsc/linux-x64": "0.5.43",
76
+ "@xbintsc/linux-arm64": "0.5.43",
77
+ "@xbintsc/darwin-x64": "0.5.43",
78
+ "@xbintsc/darwin-arm64": "0.5.43",
79
+ "@xbintsc/win32-x64": "0.5.43",
80
+ "@xbintsc/win32-arm64": "0.5.43"
81
81
  },
82
82
  "engines": {
83
83
  "node": ">=22"
@@ -380,7 +380,22 @@ std::string DisplayList::dump() const {
380
380
  } else {
381
381
  const PaintImage &image = images[op.index];
382
382
  suffix = transformSuffix(image.transform);
383
- if (image.media_id != 0) {
383
+ if (image.gradient != nullptr) {
384
+ const PaintGradient &gradient = *image.gradient;
385
+ std::snprintf(buffer, sizeof(buffer),
386
+ "image x=%.1f y=%.1f w=%.1f h=%.1f gradient=%s center=%.1f,%.1f "
387
+ "radius=%.1f,%.1f stops=",
388
+ image.x, image.y, image.width, image.height,
389
+ gradient.repeating ? "repeating-radial" : "radial", gradient.center_x,
390
+ gradient.center_y, gradient.radius_x, gradient.radius_y);
391
+ text += prefix + buffer;
392
+ for (size_t stop = 0; stop < gradient.stops.size(); stop++) {
393
+ std::snprintf(buffer, sizeof(buffer), "%s%s@%.4f", stop == 0 ? "" : ",",
394
+ formatColor(gradient.stops[stop].color).c_str(),
395
+ gradient.stops[stop].offset);
396
+ text += buffer;
397
+ }
398
+ } else if (image.media_id != 0) {
384
399
  std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f media=%llu",
385
400
  image.x, image.y, image.width, image.height,
386
401
  (unsigned long long)image.media_id);
@@ -14,6 +14,7 @@
14
14
  #define XT_GUI_PAINT_H
15
15
 
16
16
  #include <cstdint>
17
+ #include <memory>
17
18
  #include <string>
18
19
  #include <vector>
19
20
 
@@ -24,6 +25,35 @@
24
25
 
25
26
  namespace xtgui {
26
27
 
28
+ /** One resolved colour stop of a gradient. `offset` is a fraction of the
29
+ * gradient line and non-decreasing, as in CSS. */
30
+ struct GradientStop {
31
+ float offset = 0.0f;
32
+ Color color;
33
+ };
34
+
35
+ /** A radial `background-image` layer resolved by the paint pass: the renderer
36
+ * rasterises it into one RGBA texture and draws that as a single image quad.
37
+ *
38
+ * Nesting translucent bands instead composites every band's alpha once per band
39
+ * — each band covers the whole interior — so a vignette whose stops fade to
40
+ * `transparent` turns into an opaque disc that hides the scene behind it.
41
+ * Resolving the ramp per pixel composites it exactly once. */
42
+ struct PaintGradient {
43
+ std::vector<GradientStop> stops;
44
+ /** Centre in box-local logical pixels; the drawn quad spans the whole box. */
45
+ float center_x = 0.0f;
46
+ float center_y = 0.0f;
47
+ float radius_x = 0.0f;
48
+ float radius_y = 0.0f;
49
+ /** `repeating-radial-gradient()`: the stop pattern restarts every `period`
50
+ * (the last stop's offset) instead of clamping at the outer ellipse. */
51
+ bool repeating = false;
52
+ float period = 1.0f;
53
+ /** Identity of the ramp (stops and geometry) for the renderer's texture cache. */
54
+ std::string key;
55
+ };
56
+
27
57
  /** A solid, optionally rounded, rectangle. Coordinates are viewport-relative. */
28
58
  struct PaintRect {
29
59
  float x = 0;
@@ -71,6 +101,10 @@ struct PaintImage {
71
101
  /** Modulating colour for the image shader (white normally; a filter's
72
102
  * multiplicative effect may tint it). */
73
103
  Color tint{1.0f, 1.0f, 1.0f, 1.0f};
104
+ /** A `background-image` radial gradient: the renderer rasterises it into the
105
+ * quad instead of loading `src`/`svg`. Its stops already carry the colour
106
+ * functions of the filter chain; `tint` still carries brightness/opacity. */
107
+ std::shared_ptr<const PaintGradient> gradient;
74
108
  };
75
109
 
76
110
  /** One triangle of a clip path in device-space logical pixels. The paint pass
@@ -144,6 +178,18 @@ struct DisplayList {
144
178
  std::string dump() const;
145
179
  };
146
180
 
181
+ namespace paint_detail {
182
+
183
+ /** Interpolate a gradient's colour at `t` (a fraction of the gradient line; any
184
+ * value when `repeating`). Stops interpolate in premultiplied alpha, as CSS
185
+ * does, so a `transparent` stop does not darken the ramp towards an opaque
186
+ * colour; the result is converted back to straight alpha, which is what both the
187
+ * band and the texture paths blend. */
188
+ Color gradientColorAt(const std::vector<GradientStop> &stops, bool repeating, float period,
189
+ float t);
190
+
191
+ } // namespace paint_detail
192
+
147
193
  /** Build the display list for a laid-out tree and append it to `out`. The
148
194
  * viewport size resolves `vw`/`vh` units inside `clip-path` and shadows; pass 0
149
195
  * to take it from the root box's containing block. */
@@ -1,18 +1,22 @@
1
1
  /*
2
2
  * xbintsc GUI engine — background layer painting.
3
3
  *
4
- * The renderer has one rounded-rectangle pipeline and no gradient shader, so a
5
- * gradient is emitted as a run of solid bands: 18 strips for a linear gradient
6
- * (64 while repeating) and 32 concentric ellipses for a radial one (64 while
7
- * repeating). Radial ellipses use the shape
8
- * pipeline's transform slot (a unit circle scaled to the ellipse radii), which
9
- * keeps the distance-field antialiasing intact.
4
+ * `background-color` is one rounded rectangle and a `background-image` layer is
5
+ * a gradient or an image. A linear gradient is emitted as a run of solid bands
6
+ * (18 strips, 64 while repeating). A radial gradient is emitted as a single
7
+ * `PaintGradient` that the renderer rasterises per pixel
8
+ * (`renderer_gradient.cpp`): stacking translucent radial bands composites their
9
+ * alpha once per band, which turns a vignette whose stops fade to `transparent`
10
+ * into an opaque disc.
10
11
  */
11
12
 
12
13
  #include "paint_internal.h"
13
14
 
14
15
  #include <algorithm>
15
16
  #include <cmath>
17
+ #include <cstdio>
18
+ #include <memory>
19
+ #include <string>
16
20
  #include <vector>
17
21
 
18
22
  namespace xtgui {
@@ -20,21 +24,60 @@ namespace paint_detail {
20
24
 
21
25
  namespace {
22
26
 
23
- struct ResolvedStop {
24
- float offset = 0.0f;
25
- Color color;
26
- };
27
+ /** Blend two stops in premultiplied alpha, as CSS specifies, and convert the
28
+ * result back to the straight alpha both pipelines blend. Without it a
29
+ * `transparent` stop (rgba(0,0,0,0)) drags the ramp towards black, so a
30
+ * `transparent -> white` gradient fades through grey. */
31
+ Color mixGradientStops(const Color &from, const Color &to, float amount) {
32
+ float alpha = from.a + (to.a - from.a) * amount;
33
+ Color out;
34
+ out.a = alpha;
35
+ if (alpha > 0.00001f) {
36
+ out.r = (from.r * from.a + (to.r * to.a - from.r * from.a) * amount) / alpha;
37
+ out.g = (from.g * from.a + (to.g * to.a - from.g * from.a) * amount) / alpha;
38
+ out.b = (from.b * from.a + (to.b * to.a - from.b * from.a) * amount) / alpha;
39
+ } else {
40
+ out.r = from.r;
41
+ out.g = from.g;
42
+ out.b = from.b;
43
+ }
44
+ out.valid = true;
45
+ return out;
46
+ }
47
+
48
+ } // namespace
49
+
50
+ Color gradientColorAt(const std::vector<GradientStop> &stops, bool repeating, float period,
51
+ float t) {
52
+ if (stops.empty()) return Color();
53
+ /* A repeating gradient restarts its stop pattern at the last stop's offset. */
54
+ if (repeating) {
55
+ if (period <= 0.00001f) return stops.back().color;
56
+ t -= std::floor(t / period) * period;
57
+ }
58
+ if (t <= stops.front().offset) return stops.front().color;
59
+ if (t >= stops.back().offset) return stops.back().color;
60
+ for (size_t i = 1; i < stops.size(); i++) {
61
+ if (t > stops[i].offset) continue;
62
+ float span = stops[i].offset - stops[i - 1].offset;
63
+ float u = span > 0.00001f ? (t - stops[i - 1].offset) / span : 0.0f;
64
+ return mixGradientStops(stops[i - 1].color, stops[i].color, u);
65
+ }
66
+ return stops.back().color;
67
+ }
68
+
69
+ namespace {
27
70
 
28
71
  /** Resolve the declaration's percentage/length offsets against the gradient
29
72
  * line, then distribute the omitted (`Unit::Auto`) ones between their
30
73
  * neighbours. The last explicit stop is preserved: for a normal gradient it
31
74
  * holds that colour, and for a repeating one it defines the period. */
32
- std::vector<ResolvedStop> resolveStops(const BackgroundLayer &layer, float line_length,
75
+ std::vector<GradientStop> resolveStops(const BackgroundLayer &layer, float line_length,
33
76
  const XtStyle &style, float viewportWidth,
34
77
  float viewportHeight) {
35
- std::vector<ResolvedStop> stops;
78
+ std::vector<GradientStop> stops;
36
79
  for (const BackgroundGradientStop &stop : layer.stops) {
37
- ResolvedStop resolved;
80
+ GradientStop resolved;
38
81
  resolved.color = stop.color;
39
82
  resolved.offset = -1.0f;
40
83
  if (stop.offset.unit == Unit::Percent) {
@@ -51,7 +94,7 @@ std::vector<ResolvedStop> resolveStops(const BackgroundLayer &layer, float line_
51
94
 
52
95
  /* Explicit offsets are non-decreasing, as in CSS. */
53
96
  float last = -1.0f;
54
- for (ResolvedStop &stop : stops) {
97
+ for (GradientStop &stop : stops) {
55
98
  if (stop.offset < 0.0f) continue;
56
99
  if (stop.offset < last) stop.offset = last;
57
100
  last = stop.offset;
@@ -87,33 +130,6 @@ std::vector<ResolvedStop> resolveStops(const BackgroundLayer &layer, float line_
87
130
  return stops;
88
131
  }
89
132
 
90
- Color gradientColorAt(const std::vector<ResolvedStop> &stops, bool repeating, float t) {
91
- if (stops.empty()) return Color();
92
- /* A repeating gradient restarts its stop pattern at the last stop's offset. */
93
- if (repeating) {
94
- float period = stops.back().offset;
95
- if (period <= 0.00001f) return stops.back().color;
96
- t -= std::floor(t / period) * period;
97
- }
98
- if (t <= stops.front().offset) return stops.front().color;
99
- if (t >= stops.back().offset) return stops.back().color;
100
- for (size_t i = 1; i < stops.size(); i++) {
101
- if (t > stops[i].offset) continue;
102
- float span = stops[i].offset - stops[i - 1].offset;
103
- float u = span > 0.00001f ? (t - stops[i - 1].offset) / span : 0.0f;
104
- Color a = stops[i - 1].color;
105
- Color b = stops[i].color;
106
- Color out;
107
- out.r = a.r + (b.r - a.r) * u;
108
- out.g = a.g + (b.g - a.g) * u;
109
- out.b = a.b + (b.b - a.b) * u;
110
- out.a = a.a + (b.a - a.a) * u;
111
- out.valid = true;
112
- return out;
113
- }
114
- return stops.back().color;
115
- }
116
-
117
133
  /** Intersect `clip` with the box's border box (backgrounds never paint outside
118
134
  * it, even when the box is not an overflow container). */
119
135
  PaintClip boxClip(const PaintClip &clip, const LayoutBox &box) {
@@ -160,11 +176,13 @@ void paintLinear(const LayoutBox &box, const XtStyle &style, const BackgroundLay
160
176
  bool reverse = (layer.angle > 260.0f && layer.angle < 280.0f) || layer.angle < 10.0f ||
161
177
  layer.angle > 350.0f;
162
178
  const float line_length = horizontal ? box.width : box.height;
163
- std::vector<ResolvedStop> stops = resolveStops(layer, line_length, style, viewportWidth,
179
+ std::vector<GradientStop> stops = resolveStops(layer, line_length, style, viewportWidth,
164
180
  viewportHeight);
181
+ if (stops.size() < 2) return;
182
+ const float period = stops.back().offset;
165
183
  for (int i = 0; i < strips; i++) {
166
184
  float t = ((float)i + 0.5f) / (float)strips;
167
- Color color = gradientColorAt(stops, layer.repeating, reverse ? 1.0f - t : t);
185
+ Color color = gradientColorAt(stops, layer.repeating, period, reverse ? 1.0f - t : t);
168
186
  color = filterColor(chain, color);
169
187
  if (color.a <= 0.0f) continue;
170
188
  if (horizontal) {
@@ -177,6 +195,18 @@ void paintLinear(const LayoutBox &box, const XtStyle &style, const BackgroundLay
177
195
  }
178
196
  }
179
197
 
198
+ /** Radii of a keyword-sized ellipse: it keeps the box's aspect ratio and is
199
+ * scaled until it meets the corner at `(px, py)` from the centre, so the 100%
200
+ * stop lands on that corner as it does in CSS. Taking the side distances instead
201
+ * left the corners of a `farthest-corner` gradient unpainted. */
202
+ void cornerEllipseRadii(float width, float height, float px, float py, float *rx, float *ry) {
203
+ float x = 2.0f * px / width;
204
+ float y = 2.0f * py / height;
205
+ float scale = std::sqrt(x * x + y * y);
206
+ *rx = scale * width * 0.5f;
207
+ *ry = scale * height * 0.5f;
208
+ }
209
+
180
210
  /** Default radii for `closest-side` / `farthest-side` / corner keywords. */
181
211
  void radialRadii(const LayoutBox &box, const BackgroundLayer &layer, const XtStyle &style,
182
212
  float viewportWidth, float viewportHeight, float *cx, float *cy, float *rx,
@@ -230,31 +260,45 @@ void radialRadii(const LayoutBox &box, const BackgroundLayer &layer, const XtSty
230
260
  *rx = *ry = radius;
231
261
  } else {
232
262
  switch (layer.size) {
233
- case RadialSize::ClosestCorner:
234
263
  case RadialSize::ClosestSide:
235
264
  *rx = std::min(left, right);
236
265
  *ry = std::min(top, bottom);
237
266
  break;
238
267
  case RadialSize::FarthestSide:
239
- case RadialSize::FarthestCorner:
240
- default:
241
268
  *rx = std::max(left, right);
242
269
  *ry = std::max(top, bottom);
243
270
  break;
271
+ case RadialSize::ClosestCorner:
272
+ cornerEllipseRadii(box.width, box.height, std::min(left, right), std::min(top, bottom), rx,
273
+ ry);
274
+ break;
275
+ case RadialSize::FarthestCorner:
276
+ default:
277
+ cornerEllipseRadii(box.width, box.height, std::max(left, right), std::max(top, bottom), rx,
278
+ ry);
279
+ break;
244
280
  }
245
281
  }
246
282
  *cx = centerX;
247
283
  *cy = centerY;
248
284
  }
249
285
 
250
- /** One filled ellipse: a unit-radius rounded rect mapped by a local scale. */
251
- void addEllipse(DisplayList &out, float cx, float cy, float rx, float ry, const Color &color,
252
- const PaintClip &clip, const Matrix2D &world) {
253
- if (rx <= 0.0f || ry <= 0.0f || color.a <= 0.0f) return;
254
- Matrix2D local = Matrix2D::translate(cx, cy)
255
- .multiply(Matrix2D::scale(rx, ry))
256
- .multiply(Matrix2D::translate(-cx, -cy));
257
- addRect(out, cx - 1.0f, cy - 1.0f, 2.0f, 2.0f, 1.0f, color, clip, world.multiply(local));
286
+ /** Stable identity of a rasterised gradient: the ramp plus its geometry. The
287
+ * renderer keys its texture cache by this string, so an unchanged declaration
288
+ * rasterises once. */
289
+ std::string gradientKey(const PaintGradient &gradient) {
290
+ char buffer[192];
291
+ std::string key;
292
+ for (const GradientStop &stop : gradient.stops) {
293
+ std::snprintf(buffer, sizeof(buffer), "%.4f/%.5f,%.5f,%.5f,%.5f;", stop.offset, stop.color.r,
294
+ stop.color.g, stop.color.b, stop.color.a);
295
+ key += buffer;
296
+ }
297
+ std::snprintf(buffer, sizeof(buffer), "%.3f,%.3f,%.3f,%.3f;%d;%.5f", gradient.center_x,
298
+ gradient.center_y, gradient.radius_x, gradient.radius_y,
299
+ gradient.repeating ? 1 : 0, gradient.period);
300
+ key += buffer;
301
+ return key;
258
302
  }
259
303
 
260
304
  void paintRadial(const LayoutBox &box, const XtStyle &style, const BackgroundLayer &layer,
@@ -267,18 +311,49 @@ void paintRadial(const LayoutBox &box, const XtStyle &style, const BackgroundLay
267
311
  float ry = 0.0f;
268
312
  radialRadii(box, layer, style, viewportWidth, viewportHeight, &cx, &cy, &rx, &ry);
269
313
  if (rx <= 0.0f || ry <= 0.0f) return;
270
- std::vector<ResolvedStop> stops = resolveStops(layer, std::max(rx, ry), style, viewportWidth,
314
+ std::vector<GradientStop> stops = resolveStops(layer, std::max(rx, ry), style, viewportWidth,
271
315
  viewportHeight);
272
- const int bands = layer.repeating ? 64 : 32;
273
- for (int i = bands; i >= 1; i--) {
274
- float t = (float)i / (float)bands;
275
- Color color = filterColor(chain, gradientColorAt(stops, layer.repeating, t));
276
- if (color.a <= 0.0f) continue;
277
- addEllipse(out, cx, cy, rx * t, ry * t, color, clip, box.world);
316
+ if (stops.size() < 2) return;
317
+ /* The colour functions fold into the stops (a per-stop application is exact
318
+ * for the linear ones); `brightness()` and `opacity()` stay out, because the
319
+ * image tint carries them exactly and leaving them out keeps the ramp — and
320
+ * therefore the texture cache key — stable while a fade animates. */
321
+ FilterChain stopChain = chain;
322
+ stopChain.opacity = 1.0f;
323
+ for (size_t i = stopChain.ops.size(); i-- > 0;) {
324
+ FilterKind kind = stopChain.ops[i].kind;
325
+ if (kind == FilterKind::Brightness || kind == FilterKind::Opacity) {
326
+ stopChain.ops.erase(stopChain.ops.begin() + (ptrdiff_t)i);
327
+ }
328
+ }
329
+ auto gradient = std::make_shared<PaintGradient>();
330
+ gradient->stops.reserve(stops.size());
331
+ bool visible = false;
332
+ for (size_t i = 0; i < stops.size(); i++) {
333
+ GradientStop resolved = stops[i];
334
+ resolved.color = filterColor(stopChain, resolved.color);
335
+ visible = visible || resolved.color.a > 0.0f;
336
+ gradient->stops.push_back(resolved);
278
337
  }
279
- /* Exact centre colour (the innermost band samples just off zero). */
280
- Color center = filterColor(chain, gradientColorAt(stops, layer.repeating, 0.0f));
281
- addEllipse(out, cx, cy, 0.6f, 0.6f, center, clip, box.world);
338
+ if (!visible) return;
339
+ gradient->center_x = cx - box.x;
340
+ gradient->center_y = cy - box.y;
341
+ gradient->radius_x = rx;
342
+ gradient->radius_y = ry;
343
+ gradient->repeating = layer.repeating;
344
+ gradient->period = stops.back().offset;
345
+ gradient->key = gradientKey(*gradient);
346
+
347
+ /* One quad for the whole layer: the renderer resolves the ramp per pixel, so
348
+ * the layer composites exactly once instead of once per translucent band. */
349
+ PaintImage image;
350
+ image.x = box.x;
351
+ image.y = box.y;
352
+ image.width = box.width;
353
+ image.height = box.height;
354
+ image.transform = box.world;
355
+ image.gradient = gradient;
356
+ addImage(out, std::move(image), clip, chain);
282
357
  }
283
358
 
284
359
  void paintImage(const LayoutBox &box, const BackgroundLayer &layer, const FilterChain &chain,
@@ -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
 
@@ -147,6 +148,10 @@ void layerRendererDestroy(SDL_GPUDevice *device);
147
148
  /* -- images --------------------------------------------------------------- */
148
149
 
149
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);
150
155
  /** Decode `src` (cached) and upload it as an RGBA texture. `pixel_width` /
151
156
  * `pixel_height` are the drawn size in device pixels; vector sources (SVG) are
152
157
  * rasterised at that size, raster sources ignore it. The texture cache is keyed
@@ -154,6 +159,23 @@ SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *
154
159
  SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src, int pixel_width,
155
160
  int pixel_height, XtGuiUploadBatch *batch);
156
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
+
157
179
  } // namespace renderer_detail
158
180
  } // namespace xtgui
159
181
 
@@ -9,4 +9,4 @@
9
9
  * `main`/`master` only (see `.githooks/pre-commit`), so feature branches never
10
10
  * change this line and merging two of them cannot conflict here.
11
11
  */
12
- export const COMPILER_VERSION = "0.5.42";
12
+ export const COMPILER_VERSION = "0.5.43";