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.
- 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-effects.test.js +48 -4
- package/dist/tests/e2e/gui-effects.test.js.map +1 -1
- package/doc/gui/api.md +1 -1
- package/doc/gui/known-issues.md +5 -3
- package/doc/gui/rendering.md +12 -8
- package/doc/gui.md +7 -4
- package/doc/zh-CN/gui/api.md +1 -1
- package/doc/zh-CN/gui/known-issues.md +3 -2
- package/doc/zh-CN/gui/rendering.md +9 -6
- package/doc/zh-CN/gui.md +6 -5
- package/package.json +7 -7
- package/runtime/ext_gui/paint.cpp +16 -1
- package/runtime/ext_gui/paint.h +46 -0
- package/runtime/ext_gui/paint_background.cpp +139 -64
- 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 +22 -0
- package/src/driver/version.ts +1 -1
package/dist/src/cli/help.d.ts
CHANGED
|
@@ -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.
|
|
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,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.
|
|
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
|
-
//
|
|
134
|
-
//
|
|
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
|
-
//
|
|
139
|
-
|
|
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
|
|
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
|
package/doc/gui/known-issues.md
CHANGED
|
@@ -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.
|
|
76
|
-
bands (18
|
|
77
|
-
|
|
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
|
package/doc/gui/rendering.md
CHANGED
|
@@ -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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
105
|
-
|
|
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
|
|
340
|
-
|
|
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
|
|
507
|
-
|
|
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
|
package/doc/zh-CN/gui/api.md
CHANGED
|
@@ -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
|
-
|
|
55
|
-
|
|
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
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
412
|
-
|
|
413
|
-
|
|
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.
|
|
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.
|
|
76
|
-
"@xbintsc/linux-arm64": "0.5.
|
|
77
|
-
"@xbintsc/darwin-x64": "0.5.
|
|
78
|
-
"@xbintsc/darwin-arm64": "0.5.
|
|
79
|
-
"@xbintsc/win32-x64": "0.5.
|
|
80
|
-
"@xbintsc/win32-arm64": "0.5.
|
|
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.
|
|
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);
|
package/runtime/ext_gui/paint.h
CHANGED
|
@@ -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
|
-
*
|
|
5
|
-
* gradient is emitted as a run of solid bands
|
|
6
|
-
* (64 while repeating)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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<
|
|
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<
|
|
78
|
+
std::vector<GradientStop> stops;
|
|
36
79
|
for (const BackgroundGradientStop &stop : layer.stops) {
|
|
37
|
-
|
|
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 (
|
|
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<
|
|
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
|
-
/**
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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<
|
|
314
|
+
std::vector<GradientStop> stops = resolveStops(layer, std::max(rx, ry), style, viewportWidth,
|
|
271
315
|
viewportHeight);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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.
|
|
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
|
|
package/src/driver/version.ts
CHANGED