xbintsc 0.5.45 → 0.5.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +5 -4
  3. package/README.zh-CN.md +4 -3
  4. package/dist/src/cli/extensions.d.ts +24 -0
  5. package/dist/src/cli/extensions.js +62 -0
  6. package/dist/src/cli/extensions.js.map +1 -0
  7. package/dist/src/cli/help.d.ts +1 -1
  8. package/dist/src/cli/help.js +3 -1
  9. package/dist/src/cli/help.js.map +1 -1
  10. package/dist/src/cli/hints.js +8 -3
  11. package/dist/src/cli/hints.js.map +1 -1
  12. package/dist/src/cli/main.js +21 -28
  13. package/dist/src/cli/main.js.map +1 -1
  14. package/dist/src/diagnostics/diagnostic.d.ts +3 -1
  15. package/dist/src/diagnostics/diagnostic.js +2 -0
  16. package/dist/src/diagnostics/diagnostic.js.map +1 -1
  17. package/dist/src/driver/build-key.d.ts +2 -0
  18. package/dist/src/driver/build-key.js +1 -0
  19. package/dist/src/driver/build-key.js.map +1 -1
  20. package/dist/src/driver/compiler.js +61 -8
  21. package/dist/src/driver/compiler.js.map +1 -1
  22. package/dist/src/driver/config.d.ts +4 -0
  23. package/dist/src/driver/config.js +2 -0
  24. package/dist/src/driver/config.js.map +1 -1
  25. package/dist/src/driver/link-features.d.ts +37 -0
  26. package/dist/src/driver/link-features.js +123 -0
  27. package/dist/src/driver/link-features.js.map +1 -0
  28. package/dist/src/driver/options.d.ts +10 -0
  29. package/dist/src/driver/runtime-objects.d.ts +5 -0
  30. package/dist/src/driver/runtime-objects.js +20 -1
  31. package/dist/src/driver/runtime-objects.js.map +1 -1
  32. package/dist/src/driver/usage.d.ts +23 -0
  33. package/dist/src/driver/usage.js +353 -0
  34. package/dist/src/driver/usage.js.map +1 -0
  35. package/dist/src/driver/version.d.ts +1 -1
  36. package/dist/src/driver/version.js +1 -1
  37. package/dist/src/extensions/catalog.js +1 -2
  38. package/dist/src/extensions/catalog.js.map +1 -1
  39. package/dist/src/extensions/gui/features.d.ts +34 -0
  40. package/dist/src/extensions/gui/features.js +165 -0
  41. package/dist/src/extensions/gui/features.js.map +1 -0
  42. package/dist/src/extensions/gui/index.js +10 -4
  43. package/dist/src/extensions/gui/index.js.map +1 -1
  44. package/dist/src/extensions/link-feature.d.ts +70 -0
  45. package/dist/src/extensions/link-feature.js +18 -0
  46. package/dist/src/extensions/link-feature.js.map +1 -0
  47. package/dist/src/extensions/registry.d.ts +24 -4
  48. package/dist/src/extensions/registry.js +26 -3
  49. package/dist/src/extensions/registry.js.map +1 -1
  50. package/dist/src/index.d.ts +3 -2
  51. package/dist/src/index.js +0 -1
  52. package/dist/src/index.js.map +1 -1
  53. package/dist/tests/cli/hints.test.js +5 -7
  54. package/dist/tests/cli/hints.test.js.map +1 -1
  55. package/dist/tests/driver/link-features.test.js +105 -0
  56. package/dist/tests/driver/link-features.test.js.map +1 -0
  57. package/dist/tests/driver/usage.test.d.ts +1 -0
  58. package/dist/tests/driver/usage.test.js +88 -0
  59. package/dist/tests/driver/usage.test.js.map +1 -0
  60. package/dist/tests/e2e/gui-audio.test.js +4 -4
  61. package/dist/tests/e2e/gui-audio.test.js.map +1 -1
  62. package/dist/tests/e2e/gui-css-coverage.test.d.ts +9 -0
  63. package/dist/tests/e2e/gui-css-coverage.test.js +333 -0
  64. package/dist/tests/e2e/gui-css-coverage.test.js.map +1 -0
  65. package/dist/tests/e2e/gui-css-features.test.d.ts +6 -0
  66. package/dist/tests/e2e/gui-css-features.test.js +270 -0
  67. package/dist/tests/e2e/gui-css-features.test.js.map +1 -0
  68. package/dist/tests/e2e/gui-effects.test.js +92 -11
  69. package/dist/tests/e2e/gui-effects.test.js.map +1 -1
  70. package/dist/tests/e2e/gui-feature-linking.test.d.ts +9 -0
  71. package/dist/tests/e2e/gui-feature-linking.test.js +68 -0
  72. package/dist/tests/e2e/gui-feature-linking.test.js.map +1 -0
  73. package/dist/tests/e2e/gui-font-face.test.d.ts +10 -0
  74. package/dist/tests/e2e/gui-font-face.test.js +142 -0
  75. package/dist/tests/e2e/gui-font-face.test.js.map +1 -0
  76. package/dist/tests/e2e/gui-fonts.test.d.ts +10 -0
  77. package/dist/tests/e2e/gui-fonts.test.js +150 -0
  78. package/dist/tests/e2e/gui-fonts.test.js.map +1 -0
  79. package/dist/tests/e2e/gui-helpers.d.ts +10 -1
  80. package/dist/tests/e2e/gui-helpers.js +6 -0
  81. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  82. package/dist/tests/e2e/gui-layout.test.js +7 -3
  83. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  84. package/dist/tests/e2e/gui-media-disabled.test.d.ts +5 -4
  85. package/dist/tests/e2e/gui-media-disabled.test.js +7 -6
  86. package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -1
  87. package/dist/tests/e2e/gui-media.test.d.ts +5 -4
  88. package/dist/tests/e2e/gui-media.test.js +9 -9
  89. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  90. package/dist/tests/e2e/gui-paint.test.js +5 -2
  91. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  92. package/dist/tests/e2e/gui-shaping.test.d.ts +5 -0
  93. package/dist/tests/e2e/gui-shaping.test.js +47 -0
  94. package/dist/tests/e2e/gui-shaping.test.js.map +1 -0
  95. package/dist/tests/e2e/gui-track-sizing.test.d.ts +6 -0
  96. package/dist/tests/e2e/gui-track-sizing.test.js +173 -0
  97. package/dist/tests/e2e/gui-track-sizing.test.js.map +1 -0
  98. package/dist/tests/e2e/gui-transform.test.js +3 -2
  99. package/dist/tests/e2e/gui-transform.test.js.map +1 -1
  100. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.d.ts +18 -0
  101. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js +120 -0
  102. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js.map +1 -0
  103. package/dist/tests/extensions/gui-features.test.d.ts +1 -0
  104. package/dist/tests/extensions/gui-features.test.js +175 -0
  105. package/dist/tests/extensions/gui-features.test.js.map +1 -0
  106. package/doc/ai/cli.md +5 -1
  107. package/doc/ai/extensions.md +56 -26
  108. package/doc/ai/troubleshooting.md +10 -7
  109. package/doc/ai/zh-CN/cli.md +5 -1
  110. package/doc/ai/zh-CN/extensions.md +48 -22
  111. package/doc/ai/zh-CN/troubleshooting.md +6 -5
  112. package/doc/gui/animation.md +39 -9
  113. package/doc/gui/api.md +2 -1
  114. package/doc/gui/build.md +137 -19
  115. package/doc/gui/canvas.md +1 -1
  116. package/doc/gui/dom.md +20 -4
  117. package/doc/gui/known-issues.md +63 -26
  118. package/doc/gui/layout.md +31 -8
  119. package/doc/gui/media.md +13 -7
  120. package/doc/gui/rendering.md +114 -39
  121. package/doc/gui/runtime.md +5 -0
  122. package/doc/gui/webgl.md +24 -6
  123. package/doc/gui.md +110 -47
  124. package/doc/implemented/cli.md +2 -0
  125. package/doc/implemented/extensions.md +9 -3
  126. package/doc/npm-package.md +4 -4
  127. package/doc/xbintsc.config.schema.json +10 -0
  128. package/doc/zh-CN/gui/animation.md +33 -10
  129. package/doc/zh-CN/gui/api.md +2 -1
  130. package/doc/zh-CN/gui/build.md +110 -16
  131. package/doc/zh-CN/gui/canvas.md +1 -1
  132. package/doc/zh-CN/gui/dom.md +16 -3
  133. package/doc/zh-CN/gui/known-issues.md +45 -15
  134. package/doc/zh-CN/gui/layout.md +23 -6
  135. package/doc/zh-CN/gui/media.md +11 -6
  136. package/doc/zh-CN/gui/rendering.md +84 -26
  137. package/doc/zh-CN/gui/runtime.md +3 -0
  138. package/doc/zh-CN/gui/webgl.md +3 -3
  139. package/doc/zh-CN/gui.md +92 -41
  140. package/doc/zh-CN/implemented/cli.md +2 -0
  141. package/doc/zh-CN/implemented/extensions.md +9 -3
  142. package/doc/zh-CN/npm-package.md +4 -3
  143. package/package.json +7 -7
  144. package/runtime/ext_gui/animation_values.cpp +425 -0
  145. package/runtime/ext_gui/animation_values.h +64 -0
  146. package/runtime/ext_gui/browser.cpp +22 -15
  147. package/runtime/ext_gui/browser_loop.cpp +13 -1
  148. package/runtime/ext_gui/canvas.h +1 -1
  149. package/runtime/ext_gui/canvas_raster.cpp +1 -1
  150. package/runtime/ext_gui/canvas_text.cpp +1 -1
  151. package/runtime/ext_gui/css.cpp +172 -21
  152. package/runtime/ext_gui/css.h +75 -4
  153. package/runtime/ext_gui/css_match.cpp +4 -0
  154. package/runtime/ext_gui/css_sheet.cpp +218 -2
  155. package/runtime/ext_gui/document.cpp +42 -192
  156. package/runtime/ext_gui/document.h +25 -12
  157. package/runtime/ext_gui/document_animation.cpp +428 -0
  158. package/runtime/ext_gui/dom_api_canvas.cpp +15 -8
  159. package/runtime/ext_gui/dom_api_canvas_extra.cpp +44 -20
  160. package/runtime/ext_gui/dom_api_canvas_image.cpp +3 -1
  161. package/runtime/ext_gui/dom_api_node_read.cpp +10 -0
  162. package/runtime/ext_gui/dom_api_offscreen.cpp +3 -1
  163. package/runtime/ext_gui/dom_api_style.cpp +7 -0
  164. package/runtime/ext_gui/dom_api_webgl_internal.h +4 -0
  165. package/runtime/ext_gui/dom_api_webgl_source.cpp +5 -2
  166. package/runtime/ext_gui/effect_values.cpp +245 -21
  167. package/runtime/ext_gui/gpu_bridge.c +28 -0
  168. package/runtime/ext_gui/gpu_feature.cpp +30 -0
  169. package/runtime/ext_gui/gpu_shader.cpp +22 -0
  170. package/runtime/ext_gui/gpu_shader_api.h +50 -0
  171. package/runtime/ext_gui/gradient_values.cpp +24 -0
  172. package/runtime/ext_gui/grid_values.cpp +23 -0
  173. package/runtime/ext_gui/gui.cpp +6 -37
  174. package/runtime/ext_gui/gui_canvas.cpp +14 -6
  175. package/runtime/ext_gui/gui_cursor.cpp +50 -0
  176. package/runtime/ext_gui/gui_engine.h +1 -18
  177. package/runtime/ext_gui/gui_events.cpp +19 -6
  178. package/runtime/ext_gui/gui_internal.h +3 -0
  179. package/runtime/ext_gui/gui_webgl.cpp +25 -10
  180. package/runtime/ext_gui/image_api.cpp +18 -0
  181. package/runtime/ext_gui/image_api.h +43 -0
  182. package/runtime/ext_gui/image_bitmap_browser.cpp +20 -11
  183. package/runtime/ext_gui/image_bridge.c +22 -0
  184. package/runtime/ext_gui/image_feature.cpp +107 -0
  185. package/runtime/ext_gui/inline_svg.cpp +95 -8
  186. package/runtime/ext_gui/inline_svg.h +5 -1
  187. package/runtime/ext_gui/layout.cpp +15 -9
  188. package/runtime/ext_gui/layout.h +8 -2
  189. package/runtime/ext_gui/layout_flex.cpp +134 -29
  190. package/runtime/ext_gui/layout_flow.cpp +58 -7
  191. package/runtime/ext_gui/layout_flow_internal.h +39 -4
  192. package/runtime/ext_gui/layout_grid.cpp +31 -0
  193. package/runtime/ext_gui/layout_inline.cpp +26 -2
  194. package/runtime/ext_gui/layout_vertical.cpp +196 -0
  195. package/runtime/ext_gui/media.cpp +4 -2
  196. package/runtime/ext_gui/media.h +3 -3
  197. package/runtime/ext_gui/media_api.h +7 -6
  198. package/runtime/ext_gui/paint.cpp +67 -9
  199. package/runtime/ext_gui/paint.h +61 -8
  200. package/runtime/ext_gui/paint_backdrop.cpp +211 -0
  201. package/runtime/ext_gui/paint_background.cpp +175 -97
  202. package/runtime/ext_gui/paint_effects.cpp +12 -6
  203. package/runtime/ext_gui/paint_internal.h +27 -0
  204. package/runtime/ext_gui/paint_outline.cpp +44 -0
  205. package/runtime/ext_gui/paint_shadow.cpp +368 -0
  206. package/runtime/ext_gui/renderer.cpp +15 -7
  207. package/runtime/ext_gui/renderer.h +11 -2
  208. package/runtime/ext_gui/renderer_backdrop.cpp +374 -0
  209. package/runtime/ext_gui/renderer_bitmap.cpp +82 -0
  210. package/runtime/ext_gui/renderer_gradient.cpp +51 -17
  211. package/runtime/ext_gui/renderer_image.cpp +19 -7
  212. package/runtime/ext_gui/renderer_internal.h +21 -0
  213. package/runtime/ext_gui/renderer_layer.cpp +37 -6
  214. package/runtime/ext_gui/renderer_text.cpp +2 -1
  215. package/runtime/ext_gui/stb_truetype.h +5077 -0
  216. package/runtime/ext_gui/style.cpp +62 -8
  217. package/runtime/ext_gui/style.h +111 -4
  218. package/runtime/ext_gui/style_cascade.cpp +83 -0
  219. package/runtime/ext_gui/style_internal.h +13 -1
  220. package/runtime/ext_gui/style_length.cpp +92 -0
  221. package/runtime/ext_gui/style_print.cpp +83 -2
  222. package/runtime/ext_gui/style_svg.cpp +40 -0
  223. package/runtime/ext_gui/style_text.cpp +302 -0
  224. package/runtime/ext_gui/style_ui.cpp +150 -0
  225. package/runtime/ext_gui/style_values.cpp +76 -62
  226. package/runtime/ext_gui/style_values_internal.h +23 -0
  227. package/runtime/ext_gui/text.cpp +27 -499
  228. package/runtime/ext_gui/text.h +18 -9
  229. package/runtime/ext_gui/text_api.h +45 -0
  230. package/runtime/ext_gui/text_bridge.c +23 -0
  231. package/runtime/ext_gui/text_cache.cpp +12 -3
  232. package/runtime/ext_gui/text_font_db.cpp +427 -0
  233. package/runtime/ext_gui/text_font_paths.cpp +250 -0
  234. package/runtime/ext_gui/text_font_paths.h +39 -0
  235. package/runtime/ext_gui/text_font_sfnt.cpp +396 -0
  236. package/runtime/ext_gui/text_font_sfnt.h +44 -0
  237. package/runtime/ext_gui/text_fonts.cpp +348 -0
  238. package/runtime/ext_gui/text_fonts.h +78 -0
  239. package/runtime/ext_gui/text_fonts_internal.h +77 -0
  240. package/runtime/ext_gui/text_glyph_cache.cpp +2 -2
  241. package/runtime/ext_gui/text_hb.cpp +438 -0
  242. package/runtime/ext_gui/text_stb.cpp +369 -0
  243. package/runtime/ext_gui/transform.cpp +107 -9
  244. package/runtime/ext_gui/transform.h +5 -0
  245. package/runtime/ext_gui/webgl_api.cpp +19 -0
  246. package/runtime/ext_gui/webgl_api.h +53 -0
  247. package/runtime/ext_gui/webgl_bridge.c +25 -0
  248. package/runtime/ext_gui/webgl_draw_setup.cpp +42 -1
  249. package/runtime/ext_gui/webgl_feature.cpp +82 -0
  250. package/runtime/ext_gui/webgl_feature.h +28 -0
  251. package/runtime/ext_gui/webgl_msl.cpp +41 -0
  252. package/runtime/ext_gui/webgl_msl.h +6 -0
  253. package/runtime/ext_gui/webgl_shader.cpp +40 -32
  254. package/runtime/ext_gui/window.cpp +7 -7
  255. package/runtime/ext_media/decode_audio.cpp +1 -1
  256. package/runtime/ext_media/media.cpp +1 -1
  257. package/runtime/ext_media/media_bridge.c +7 -7
  258. package/scripts/build-gui.ts +97 -98
  259. package/scripts/check-file-length.ts +1 -0
  260. package/scripts/gui-build-options.ts +122 -0
  261. package/src/cli/extensions.ts +72 -0
  262. package/src/cli/help.ts +3 -1
  263. package/src/cli/hints.ts +9 -3
  264. package/src/cli/main.ts +21 -27
  265. package/src/diagnostics/diagnostic.ts +2 -0
  266. package/src/driver/build-key.ts +3 -0
  267. package/src/driver/compiler.ts +63 -8
  268. package/src/driver/config.ts +6 -0
  269. package/src/driver/link-features.ts +163 -0
  270. package/src/driver/options.ts +10 -0
  271. package/src/driver/runtime-objects.ts +26 -1
  272. package/src/driver/usage.ts +376 -0
  273. package/src/driver/version.ts +1 -1
  274. package/src/extensions/catalog.ts +1 -2
  275. package/src/extensions/gui/features.ts +166 -0
  276. package/src/extensions/gui/index.ts +10 -4
  277. package/src/extensions/link-feature.ts +72 -0
  278. package/src/extensions/registry.ts +45 -4
  279. package/src/index.ts +3 -2
  280. package/dist/src/extensions/media/index.d.ts +0 -28
  281. package/dist/src/extensions/media/index.js +0 -59
  282. package/dist/src/extensions/media/index.js.map +0 -1
  283. package/dist/tests/extensions/media.test.js +0 -63
  284. package/dist/tests/extensions/media.test.js.map +0 -1
  285. package/src/extensions/media/index.ts +0 -65
  286. /package/dist/tests/{extensions/media.test.d.ts → driver/link-features.test.d.ts} +0 -0
@@ -0,0 +1,374 @@
1
+ /*
2
+ * xbintsc GUI engine — `backdrop-filter` compositor.
3
+ *
4
+ * The backdrop is the scene painted before the element, which only the renderer
5
+ * sees. `drawBackdropPass` samples a copy of that scene (the caller ping-pongs a
6
+ * scratch scene texture), applies the Gaussian and the combined colour matrix in
7
+ * one runtime-compiled shader, and writes the result back through the element's
8
+ * rounded border-box mask. Split from `renderer_layer.cpp` so the blend
9
+ * compositor and this one stay independently readable.
10
+ */
11
+
12
+ #include "renderer_internal.h"
13
+
14
+ #include <algorithm>
15
+ #include <cmath>
16
+ #include <cstdio>
17
+ #include <cstring>
18
+ #include <string>
19
+ #include <vector>
20
+
21
+ #include "gpu_shader_api.h"
22
+ #include "perf.h"
23
+ #include "webgl_glsl.h"
24
+
25
+ namespace xtgui {
26
+ namespace renderer_detail {
27
+
28
+ namespace {
29
+
30
+ /** Compile one GLSL stage into the format this device consumes. Mirrors the
31
+ * helper in `renderer_layer.cpp`; both shaders are compiled at run time because
32
+ * they are not part of the precompiled WebGL blobs. */
33
+ bool prepareStage(const std::string &source, bool fragment, SDL_GPUShaderFormat formats,
34
+ std::vector<uint32_t> *spirv, std::string *text, const Uint8 **code,
35
+ size_t *code_size, std::string *entry, std::string *log) {
36
+ const GpuShaderApi *api = gpu_shader_api();
37
+ if (api == nullptr) {
38
+ *log = "shader translation was not linked (the 'gpu' link feature is inactive)";
39
+ return false;
40
+ }
41
+ GlslShaderInfo info;
42
+ api->scan_glsl(source, fragment, &info);
43
+ if (!info.unsupported.empty()) {
44
+ *log = info.unsupported;
45
+ return false;
46
+ }
47
+ GlslLinkLayout layout;
48
+ layout.varyings["v_uv"] = 0;
49
+ if (fragment) {
50
+ layout.samplers["u_backdrop"] = 0;
51
+ layout.block_members = info.uniforms;
52
+ }
53
+ const std::string rewritten = api->rewrite_glsl(source, fragment, layout, info);
54
+ if (!api->compile_glsl(rewritten, fragment, spirv, log)) return false;
55
+
56
+ if ((formats & SDL_GPU_SHADERFORMAT_MSL) != 0) {
57
+ if (!api->cross_compile_msl(*spirv, fragment, text, entry, log)) return false;
58
+ *code = (const Uint8 *)text->data();
59
+ *code_size = text->size();
60
+ return true;
61
+ }
62
+ if ((formats & SDL_GPU_SHADERFORMAT_SPIRV) != 0) {
63
+ *code = (const Uint8 *)spirv->data();
64
+ *code_size = spirv->size() * sizeof(uint32_t);
65
+ *entry = "main";
66
+ return true;
67
+ }
68
+ *log = "this GPU backend consumes neither Metal nor Vulkan shaders";
69
+ return false;
70
+ }
71
+
72
+ // `v_uv` comes from the shared full-screen vertex shader.
73
+ const char *const kBlendVS = R"GLSL(#version 300 es
74
+ precision highp float;
75
+
76
+ out vec2 v_uv;
77
+
78
+ void main() {
79
+ vec2 corners[6] = vec2[6](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0),
80
+ vec2(1.0, -1.0), vec2(1.0, 1.0), vec2(-1.0, 1.0));
81
+ vec2 p = corners[gl_VertexIndex];
82
+ v_uv = vec2(p.x * 0.5 + 0.5, 0.5 - p.y * 0.5);
83
+ gl_Position = vec4(p, 0.0, 1.0);
84
+ }
85
+ )GLSL";
86
+
87
+ /* `backdrop-filter`: samples the scene painted so far with a separable-looking
88
+ * 13x13 Gaussian (taps spread to cover sigma) and one combined colour matrix,
89
+ * then masks the result to the element's rounded border box. The scene has
90
+ * already been copied to the sampled texture by a copy pass, so this shader
91
+ * never reads the target it writes. */
92
+ const char *const kBackdropFS = R"GLSL(#version 300 es
93
+ precision highp float;
94
+
95
+ in vec2 v_uv;
96
+ uniform sampler2D u_backdrop;
97
+ uniform vec4 u_rect; // x, y, width, height in target pixels
98
+ uniform vec4 u_mask; // corner radius, sigma, target width, target height
99
+ uniform vec4 u_texel; // 1/target width, 1/target height
100
+ uniform vec4 u_row0; // colour matrix row 0 + offset
101
+ uniform vec4 u_row1;
102
+ uniform vec4 u_row2;
103
+ uniform vec4 u_extra; // alpha multiplier
104
+ out vec4 fragColor;
105
+
106
+ float roundedRectDistance(vec2 point, vec4 rect, float radius) {
107
+ vec2 half_size = rect.zw * 0.5;
108
+ vec2 centre = rect.xy + half_size;
109
+ radius = min(radius, min(half_size.x, half_size.y));
110
+ vec2 q = abs(point - centre) - (half_size - vec2(radius));
111
+ return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - radius;
112
+ }
113
+
114
+ void main() {
115
+ float sigma = u_mask.y;
116
+ vec4 sampled;
117
+ if (sigma > 0.05) {
118
+ float step = max(1.0, sigma * 3.0 / 6.0);
119
+ float total = 0.0;
120
+ vec4 sum = vec4(0.0);
121
+ for (int y = -6; y <= 6; y++) {
122
+ for (int x = -6; x <= 6; x++) {
123
+ float distance2 = float(x * x + y * y) * step * step;
124
+ float weight = exp(-distance2 / (2.0 * sigma * sigma));
125
+ vec4 texel = texture(u_backdrop, v_uv + vec2(float(x), float(y)) * step * u_texel.xy);
126
+ /* Premultiply before averaging: blurring straight alpha would pull
127
+ * transparent black into the edges of a shape. */
128
+ sum += vec4(texel.rgb * texel.a, texel.a) * weight;
129
+ total += weight;
130
+ }
131
+ }
132
+ vec4 blended = sum / total;
133
+ sampled = vec4(blended.a > 0.00001 ? blended.rgb / blended.a : vec3(0.0), blended.a);
134
+ } else {
135
+ sampled = texture(u_backdrop, v_uv);
136
+ }
137
+ vec3 rgb = sampled.rgb;
138
+ vec3 filtered = vec3(dot(u_row0.xyz, rgb) + u_row0.w,
139
+ dot(u_row1.xyz, rgb) + u_row1.w,
140
+ dot(u_row2.xyz, rgb) + u_row2.w);
141
+ filtered = clamp(filtered, 0.0, 1.0);
142
+ vec2 position = vec2(v_uv.x * u_mask.z, v_uv.y * u_mask.w);
143
+ float distance = roundedRectDistance(position, u_rect, u_mask.x);
144
+ float coverage = clamp(0.5 - distance, 0.0, 1.0);
145
+ if (coverage <= 0.0) discard;
146
+ fragColor = vec4(filtered, clamp(sampled.a, 0.0, 1.0) * u_extra.x * coverage);
147
+ }
148
+ )GLSL";
149
+
150
+ } // namespace
151
+
152
+ SDL_GPUGraphicsPipeline *g_backdrop_pipeline = nullptr;
153
+ SDL_GPUSampler *g_backdrop_sampler = nullptr;
154
+ bool g_backdrop_failed = false;
155
+
156
+ bool ensureBackdropPipeline(SDL_GPUDevice *device) {
157
+ if (g_backdrop_pipeline != nullptr) return true;
158
+ if (g_backdrop_failed) return false;
159
+ if (device == nullptr || g_swapchain_format == SDL_GPU_TEXTUREFORMAT_INVALID) return false;
160
+ const SDL_GPUTextureUsageFlags usage =
161
+ SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
162
+ if (!SDL_GPUTextureSupportsFormat(device, g_swapchain_format, SDL_GPU_TEXTURETYPE_2D, usage)) {
163
+ g_backdrop_failed = true;
164
+ return false;
165
+ }
166
+ const SDL_GPUShaderFormat formats = SDL_GetGPUShaderFormats(device);
167
+ if ((formats & (SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_SPIRV)) == 0) {
168
+ g_backdrop_failed = true;
169
+ return false;
170
+ }
171
+
172
+ std::string log;
173
+ std::vector<uint32_t> vs_spirv;
174
+ std::vector<uint32_t> fs_spirv;
175
+ std::string vs_text;
176
+ std::string fs_text;
177
+ const Uint8 *vs_code = nullptr;
178
+ const Uint8 *fs_code = nullptr;
179
+ size_t vs_size = 0;
180
+ size_t fs_size = 0;
181
+ std::string vs_entry;
182
+ std::string fs_entry;
183
+ if (!prepareStage(kBlendVS, false, formats, &vs_spirv, &vs_text, &vs_code, &vs_size, &vs_entry,
184
+ &log) ||
185
+ !prepareStage(kBackdropFS, true, formats, &fs_spirv, &fs_text, &fs_code, &fs_size,
186
+ &fs_entry, &log)) {
187
+ fprintf(stderr, "xt_gui: backdrop-filter shader unavailable: %s\n", log.c_str());
188
+ g_backdrop_failed = true;
189
+ return false;
190
+ }
191
+
192
+ const SDL_GPUShaderFormat shader_format =
193
+ (formats & SDL_GPU_SHADERFORMAT_MSL) != 0 ? SDL_GPU_SHADERFORMAT_MSL
194
+ : SDL_GPU_SHADERFORMAT_SPIRV;
195
+ SDL_GPUShaderCreateInfo vs_info;
196
+ std::memset(&vs_info, 0, sizeof(vs_info));
197
+ vs_info.code_size = vs_size;
198
+ vs_info.code = vs_code;
199
+ vs_info.entrypoint = vs_entry.c_str();
200
+ vs_info.format = shader_format;
201
+ vs_info.stage = SDL_GPU_SHADERSTAGE_VERTEX;
202
+ SDL_GPUShader *vs = SDL_CreateGPUShader(device, &vs_info);
203
+
204
+ SDL_GPUShaderCreateInfo fs_info;
205
+ std::memset(&fs_info, 0, sizeof(fs_info));
206
+ fs_info.code_size = fs_size;
207
+ fs_info.code = fs_code;
208
+ fs_info.entrypoint = fs_entry.c_str();
209
+ fs_info.format = shader_format;
210
+ fs_info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
211
+ fs_info.num_uniform_buffers = 1;
212
+ fs_info.num_samplers = 1;
213
+ SDL_GPUShader *fs = SDL_CreateGPUShader(device, &fs_info);
214
+
215
+ if (vs == nullptr || fs == nullptr) {
216
+ fprintf(stderr, "xt_gui: backdrop-filter shader creation failed: %s\n", SDL_GetError());
217
+ if (vs != nullptr) SDL_ReleaseGPUShader(device, vs);
218
+ if (fs != nullptr) SDL_ReleaseGPUShader(device, fs);
219
+ g_backdrop_failed = true;
220
+ return false;
221
+ }
222
+
223
+ SDL_GPUColorTargetDescription target;
224
+ std::memset(&target, 0, sizeof(target));
225
+ target.format = g_swapchain_format;
226
+ target.blend_state.enable_blend = true;
227
+ target.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
228
+ target.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
229
+ target.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
230
+ target.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
231
+ target.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
232
+ target.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
233
+
234
+ SDL_GPUGraphicsPipelineCreateInfo info;
235
+ std::memset(&info, 0, sizeof(info));
236
+ info.vertex_shader = vs;
237
+ info.fragment_shader = fs;
238
+ info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
239
+ info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
240
+ info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;
241
+ info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
242
+ info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1;
243
+ info.target_info.color_target_descriptions = &target;
244
+ info.target_info.num_color_targets = 1;
245
+ g_backdrop_pipeline = SDL_CreateGPUGraphicsPipeline(device, &info);
246
+ SDL_ReleaseGPUShader(device, vs);
247
+ SDL_ReleaseGPUShader(device, fs);
248
+ if (g_backdrop_pipeline == nullptr) {
249
+ fprintf(stderr, "xt_gui: backdrop-filter pipeline creation failed: %s\n", SDL_GetError());
250
+ g_backdrop_failed = true;
251
+ return false;
252
+ }
253
+
254
+ SDL_GPUSamplerCreateInfo sampler_info;
255
+ std::memset(&sampler_info, 0, sizeof(sampler_info));
256
+ sampler_info.min_filter = SDL_GPU_FILTER_LINEAR;
257
+ sampler_info.mag_filter = SDL_GPU_FILTER_LINEAR;
258
+ sampler_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
259
+ sampler_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
260
+ sampler_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
261
+ sampler_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
262
+ g_backdrop_sampler = SDL_CreateGPUSampler(device, &sampler_info);
263
+ if (g_backdrop_sampler == nullptr) {
264
+ fprintf(stderr, "xt_gui: backdrop-filter sampler creation failed: %s\n", SDL_GetError());
265
+ SDL_ReleaseGPUGraphicsPipeline(device, g_backdrop_pipeline);
266
+ g_backdrop_pipeline = nullptr;
267
+ g_backdrop_failed = true;
268
+ return false;
269
+ }
270
+ return true;
271
+ }
272
+
273
+ struct BackdropParams {
274
+ float rect[4];
275
+ float mask[4];
276
+ float texel[4];
277
+ float row0[4];
278
+ float row1[4];
279
+ float row2[4];
280
+ float extra[4];
281
+ };
282
+
283
+ /** Filter `source` (the scene painted so far) into the active target only
284
+ * inside the element's rounded rect. The target already holds the same scene,
285
+ * so the fragment's source-over blend replaces the region with the filtered
286
+ * version while leaving rounded-corner coverage to antialias the edge. */
287
+ void drawBackdropPass(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
288
+ SDL_GPUTexture *source, const DisplayList &list,
289
+ const XtGuiDrawRun &run, int target_width, int target_height,
290
+ float pixel_density) {
291
+ if (cmd == nullptr || pass == nullptr || source == nullptr ||
292
+ g_backdrop_pipeline == nullptr || g_backdrop_sampler == nullptr)
293
+ return;
294
+ if (run.backdrop >= list.backdrops.size()) return;
295
+ const xtgui::PaintBackdrop &backdrop = list.backdrops[run.backdrop];
296
+ if (backdrop.width <= 0.0f || backdrop.height <= 0.0f) return;
297
+
298
+ const float density = pixel_density;
299
+ float left = backdrop.x * density;
300
+ float top = backdrop.y * density;
301
+ float right = (backdrop.x + backdrop.width) * density;
302
+ float bottom = (backdrop.y + backdrop.height) * density;
303
+ if (run.clip_width > 0.0f && run.clip_height > 0.0f) {
304
+ left = std::max(left, run.clip_x * density);
305
+ top = std::max(top, run.clip_y * density);
306
+ right = std::min(right, (run.clip_x + run.clip_width) * density);
307
+ bottom = std::min(bottom, (run.clip_y + run.clip_height) * density);
308
+ }
309
+ int x0 = (int)std::floor(left);
310
+ int y0 = (int)std::floor(top);
311
+ int x1 = (int)std::ceil(right);
312
+ int y1 = (int)std::ceil(bottom);
313
+ if (x0 < 0) x0 = 0;
314
+ if (y0 < 0) y0 = 0;
315
+ if (x1 > target_width) x1 = target_width;
316
+ if (y1 > target_height) y1 = target_height;
317
+ if (x1 <= x0 || y1 <= y0) return;
318
+
319
+ SDL_BindGPUGraphicsPipeline(pass, g_backdrop_pipeline);
320
+ SDL_GPUTextureSamplerBinding binding;
321
+ binding.texture = source;
322
+ binding.sampler = g_backdrop_sampler;
323
+ SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
324
+
325
+ BackdropParams params;
326
+ std::memset(&params, 0, sizeof(params));
327
+ params.rect[0] = backdrop.x * density;
328
+ params.rect[1] = backdrop.y * density;
329
+ params.rect[2] = backdrop.width * density;
330
+ params.rect[3] = backdrop.height * density;
331
+ params.mask[0] = backdrop.radius * density;
332
+ params.mask[1] = backdrop.blur * density;
333
+ params.mask[2] = (float)target_width;
334
+ params.mask[3] = (float)target_height;
335
+ params.texel[0] = 1.0f / (float)target_width;
336
+ params.texel[1] = 1.0f / (float)target_height;
337
+ for (int i = 0; i < 3; i++) {
338
+ params.row0[i] = backdrop.color[i];
339
+ params.row1[i] = backdrop.color[3 + i];
340
+ params.row2[i] = backdrop.color[6 + i];
341
+ }
342
+ params.row0[3] = backdrop.offset[0];
343
+ params.row1[3] = backdrop.offset[1];
344
+ params.row2[3] = backdrop.offset[2];
345
+ params.extra[0] = backdrop.alpha;
346
+ SDL_PushGPUFragmentUniformData(cmd, 0, &params, sizeof(params));
347
+
348
+ SDL_Rect scissor;
349
+ scissor.x = x0;
350
+ scissor.y = y0;
351
+ scissor.w = x1 - x0;
352
+ scissor.h = y1 - y0;
353
+ SDL_SetGPUScissor(pass, &scissor);
354
+ SDL_DrawGPUPrimitives(pass, 6, 1, 0, 0);
355
+ xtgui::g_perf_frame.drawCalls += 1;
356
+ }
357
+
358
+
359
+ bool backdropRendererReady(SDL_GPUDevice *device) { return ensureBackdropPipeline(device); }
360
+
361
+ void backdropRendererDestroy(SDL_GPUDevice *device) {
362
+ if (g_backdrop_pipeline != nullptr) {
363
+ SDL_ReleaseGPUGraphicsPipeline(device, g_backdrop_pipeline);
364
+ g_backdrop_pipeline = nullptr;
365
+ }
366
+ if (g_backdrop_sampler != nullptr) {
367
+ SDL_ReleaseGPUSampler(device, g_backdrop_sampler);
368
+ g_backdrop_sampler = nullptr;
369
+ }
370
+ g_backdrop_failed = false;
371
+ }
372
+
373
+ } // namespace renderer_detail
374
+ } // namespace xtgui
@@ -0,0 +1,82 @@
1
+ /*
2
+ * xbintsc GUI engine — renderer textures for CPU-rasterised shadows.
3
+ *
4
+ * `paint_shadow.cpp` produces the blurred RGBA mask; this file uploads it once
5
+ * and caches the GPU texture by the mask's paint key. A shadow whose colour
6
+ * changes (the common animation) reuses the same mask and only the draw's tint
7
+ * differs, so the expensive rasterisation and upload happen on the first frame.
8
+ */
9
+
10
+ #include "renderer_internal.h"
11
+
12
+ #include <cstdio>
13
+ #include <string>
14
+ #include <unordered_map>
15
+
16
+ namespace xtgui {
17
+ namespace renderer_detail {
18
+
19
+ namespace {
20
+
21
+ struct ShadowTexture {
22
+ SDL_GPUTexture *texture = nullptr;
23
+ int width = 0;
24
+ int height = 0;
25
+ bool used = false;
26
+ };
27
+
28
+ std::unordered_map<std::string, ShadowTexture> g_shadow_textures;
29
+
30
+ /** Keep a few stale masks for animated shadows that change shape every frame;
31
+ * the rest are released as soon as a paint pass stops drawing them. */
32
+ constexpr size_t kMaxStaleShadowTextures = 16;
33
+
34
+ } // namespace
35
+
36
+ void beginBitmapUpload() {
37
+ for (auto &entry : g_shadow_textures) entry.second.used = false;
38
+ }
39
+
40
+ void finishBitmapUpload(SDL_GPUDevice *device) {
41
+ if (g_shadow_textures.size() <= kMaxStaleShadowTextures) return;
42
+ for (auto it = g_shadow_textures.begin(); it != g_shadow_textures.end();) {
43
+ if (it->second.used) {
44
+ ++it;
45
+ continue;
46
+ }
47
+ if (it->second.texture != nullptr) SDL_ReleaseGPUTexture(device, it->second.texture);
48
+ it = g_shadow_textures.erase(it);
49
+ }
50
+ }
51
+
52
+ SDL_GPUTexture *ensureBitmapTexture(SDL_GPUDevice *device, const xtgui::PaintBitmap &bitmap,
53
+ XtGuiUploadBatch *batch) {
54
+ if (bitmap.width <= 0 || bitmap.height <= 0 || bitmap.pixels.empty()) return nullptr;
55
+ std::string key = bitmap.key + "#" + std::to_string(bitmap.width) + "x" +
56
+ std::to_string(bitmap.height);
57
+ ShadowTexture &entry = g_shadow_textures[key];
58
+ entry.used = true;
59
+ if (entry.texture != nullptr && entry.width == bitmap.width && entry.height == bitmap.height) {
60
+ return entry.texture;
61
+ }
62
+ xtgui::Image image;
63
+ image.width = bitmap.width;
64
+ image.height = bitmap.height;
65
+ image.pixels = bitmap.pixels;
66
+ SDL_GPUTexture *texture = createImageTexture(device, image, batch);
67
+ if (entry.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.texture);
68
+ entry.texture = texture;
69
+ entry.width = texture != nullptr ? bitmap.width : 0;
70
+ entry.height = texture != nullptr ? bitmap.height : 0;
71
+ return texture;
72
+ }
73
+
74
+ void releaseBitmapTextures(SDL_GPUDevice *device) {
75
+ for (auto &entry : g_shadow_textures) {
76
+ if (entry.second.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.second.texture);
77
+ }
78
+ g_shadow_textures.clear();
79
+ }
80
+
81
+ } // namespace renderer_detail
82
+ } // namespace xtgui
@@ -40,21 +40,28 @@ std::unordered_map<std::string, GradientTexture> g_gradient_textures;
40
40
  * fresh texture every frame and never free the old ones. */
41
41
  constexpr size_t kMaxStaleGradients = 8;
42
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
43
+ /** Largest texture edge a gradient is rasterised at. A ramp is smooth, so a
44
+ * larger box is rasterised smaller and stretched: the sampled colours are
45
45
  * unchanged (the stretch is uniform), only the resolution of the ramp drops. */
46
46
  constexpr int kMaxGradientEdge = 1024;
47
47
 
48
+ /** The same cap for a linear ramp, which is one texel thick across its line and
49
+ * therefore cheap at any length. A hairline stop (`calc(100% - 1px)`) wants the
50
+ * full device resolution of a wide window, so the 2D cap would soften it. */
51
+ constexpr int kMaxLinearGradientEdge = 4096;
52
+
48
53
  unsigned char byteOf(float value) {
49
54
  if (!(value > 0.0f)) return 0; // NaN and negatives
50
55
  if (value > 1.0f) value = 1.0f;
51
56
  return (unsigned char)(value * 255.0f + 0.5f);
52
57
  }
53
58
 
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. */
59
+ /** Rasterise `gradient` into `out`: the box maps 1:1 onto the texture (the
60
+ * caller may have shrunk one axis, see `rasterSize`). The ramp covers the whole
61
+ * box — past the last stop CSS holds that colour rather than fading out — so
62
+ * there is no shape edge to antialias; the texels are sampled with linear
63
+ * filtering. A linear ramp is constant across the gradient line, so a walk
64
+ * along it is one row (or one column) of texels rather than a full box. */
58
65
  void rasterizeGradient(const PaintGradient &gradient, float logical_width, float logical_height,
59
66
  int pixel_width, int pixel_height, Image *out) {
60
67
  out->width = pixel_width;
@@ -62,16 +69,29 @@ void rasterizeGradient(const PaintGradient &gradient, float logical_width, float
62
69
  out->pixels.assign((size_t)pixel_width * (size_t)pixel_height * 4u, 0);
63
70
  const float pixel_x = logical_width / (float)pixel_width; // logical px per texel
64
71
  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;
72
+ const bool linear = gradient.kind == PaintGradient::Kind::Linear;
73
+ const float inv_rx = linear || gradient.radius_x <= 0.0f ? 1.0f : 1.0f / gradient.radius_x;
74
+ const float inv_ry = linear || gradient.radius_y <= 0.0f ? 1.0f : 1.0f / gradient.radius_y;
75
+ const float inv_length =
76
+ linear && gradient.length > 0.00001f ? 1.0f / gradient.length : 0.0f;
67
77
  for (int y = 0; y < pixel_height; y++) {
68
78
  const float local_y = ((float)y + 0.5f) * pixel_y;
69
79
  unsigned char *row = out->pixels.data() + (size_t)y * (size_t)pixel_width * 4u;
70
80
  for (int x = 0; x < pixel_width; x++) {
71
81
  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);
82
+ float t = 0.0f;
83
+ if (linear) {
84
+ /* A linear ramp is the position projected onto the gradient line. */
85
+ t = ((local_x - gradient.start_x) * gradient.dir_x +
86
+ (local_y - gradient.start_y) * gradient.dir_y) *
87
+ inv_length;
88
+ } else {
89
+ /* A radial ramp is the distance from the centre, normalised by the
90
+ * ending ellipse. */
91
+ const float u = (local_x - gradient.center_x) * inv_rx;
92
+ const float v = (local_y - gradient.center_y) * inv_ry;
93
+ t = std::sqrt(u * u + v * v);
94
+ }
75
95
  Color color =
76
96
  paint_detail::gradientColorAt(gradient.stops, gradient.repeating, gradient.period, t);
77
97
  if (color.a <= 0.0f) continue;
@@ -85,12 +105,12 @@ void rasterizeGradient(const PaintGradient &gradient, float logical_width, float
85
105
  }
86
106
 
87
107
  /** 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) {
108
+ * preserving its aspect ratio under `max_edge`. */
109
+ void rasterSize(int pixel_width, int pixel_height, int max_edge, int *width, int *height) {
90
110
  *width = std::max(1, pixel_width);
91
111
  *height = std::max(1, pixel_height);
92
- if (*width <= kMaxGradientEdge && *height <= kMaxGradientEdge) return;
93
- float shrink = (float)kMaxGradientEdge / (float)std::max(*width, *height);
112
+ if (*width <= max_edge && *height <= max_edge) return;
113
+ float shrink = (float)max_edge / (float)std::max(*width, *height);
94
114
  *width = std::max(1, (int)((float)*width * shrink + 0.5f));
95
115
  *height = std::max(1, (int)((float)*height * shrink + 0.5f));
96
116
  }
@@ -119,12 +139,26 @@ SDL_GPUTexture *ensureGradientTexture(SDL_GPUDevice *device, const xtgui::PaintI
119
139
  int pixel_width, int pixel_height, XtGuiUploadBatch *batch) {
120
140
  if (image.gradient == nullptr || image.width <= 0.0f || image.height <= 0.0f) return nullptr;
121
141
  const xtgui::PaintGradient &gradient = *image.gradient;
122
- if (gradient.radius_x <= 0.0f || gradient.radius_y <= 0.0f || gradient.stops.size() < 2) {
142
+ if (gradient.stops.size() < 2) return nullptr;
143
+ const bool linear = gradient.kind == xtgui::PaintGradient::Kind::Linear;
144
+ if (linear) {
145
+ if (gradient.length <= 0.00001f) return nullptr;
146
+ } else if (gradient.radius_x <= 0.0f || gradient.radius_y <= 0.0f) {
123
147
  return nullptr;
124
148
  }
149
+ int across = pixel_width;
150
+ int down = pixel_height;
151
+ if (linear) {
152
+ /* A linear ramp does not change along the box's other axis, so one texel of
153
+ * it is enough: only the axis the ramp runs along needs resolution. */
154
+ if (std::fabs(gradient.dir_y) < 0.0001f) down = 1;
155
+ else if (std::fabs(gradient.dir_x) < 0.0001f) across = 1;
156
+ }
157
+ const bool one_dimensional = linear && (across == 1 || down == 1);
125
158
  int width = 0;
126
159
  int height = 0;
127
- rasterSize(pixel_width, pixel_height, &width, &height);
160
+ rasterSize(across, down, one_dimensional ? kMaxLinearGradientEdge : kMaxGradientEdge, &width,
161
+ &height);
128
162
  /* The key carries the ramp, its geometry and the logical box size: two boxes
129
163
  * of different sizes can resolve the same px radius but not the same picture. */
130
164
  char box[64];
@@ -11,10 +11,11 @@
11
11
 
12
12
  #include "canvas.h"
13
13
  #include "image.h"
14
+ #include "image_api.h"
14
15
  #include "media.h"
15
16
  #include "perf.h"
16
17
  #include "svg.h"
17
- #include "webgl.h"
18
+ #include "webgl_api.h"
18
19
 
19
20
  #include "renderer_shaders.h"
20
21
 
@@ -157,8 +158,10 @@ static SDL_GPUTexture *ensureMediaTexture(SDL_GPUDevice *device, uint64_t media_
157
158
  * directly and nothing is copied through the CPU. */
158
159
  static SDL_GPUTexture *ensureCanvasTexture(SDL_GPUDevice *device, uint64_t canvas_id,
159
160
  XtGuiUploadBatch *batch) {
160
- if (xtgui::WebGLContext *webgl = xtgui::xt_webgl_by_id(canvas_id)) {
161
- return webgl->color_texture();
161
+ if (const XtWebglApi *webgl = xt_webgl_api()) {
162
+ if (void *texture = webgl->color_texture(canvas_id)) {
163
+ return static_cast<SDL_GPUTexture *>(texture);
164
+ }
162
165
  }
163
166
  xtgui::Canvas2D *canvas = xtgui::xt_canvas_by_id(canvas_id);
164
167
  if (canvas == nullptr) return nullptr;
@@ -316,14 +319,16 @@ SDL_GPUTexture *createImageTexture(SDL_GPUDevice *device, const xtgui::Image &im
316
319
  * their intrinsic size and are cached by path. */
317
320
  SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src, int pixel_width,
318
321
  int pixel_height, XtGuiUploadBatch *batch) {
319
- bool vector_source = xtgui::xt_image_is_vector(src);
322
+ const XtImageApi *images = xt_image_api();
323
+ if (images == nullptr) return nullptr;
324
+ bool vector_source = images->is_vector(src);
320
325
  std::string key =
321
326
  vector_source ? src + "@" + std::to_string(pixel_width) + "x" + std::to_string(pixel_height)
322
327
  : src;
323
328
  auto found = g_image_textures.find(key);
324
329
  if (found != g_image_textures.end()) return found->second;
325
330
 
326
- const xtgui::Image *image = xtgui::xt_image_load(src, pixel_width, pixel_height);
331
+ const xtgui::Image *image = images->load(src, pixel_width, pixel_height);
327
332
  if (image == nullptr || image->width <= 0 || image->height <= 0) {
328
333
  fprintf(stderr, "xt_gui: could not decode image '%s'\n", src.c_str());
329
334
  g_image_textures.emplace(key, nullptr);
@@ -346,8 +351,10 @@ SDL_GPUTexture *ensureInlineSvgTexture(SDL_GPUDevice *device, const std::string
346
351
  auto found = g_image_textures.find(key);
347
352
  if (found != g_image_textures.end()) return found->second;
348
353
 
354
+ const XtImageApi *images = xt_image_api();
355
+ if (images == nullptr) return nullptr;
349
356
  xtgui::Image image;
350
- if (!xtgui::xt_svg_markup_render(markup, pixel_width, pixel_height, &image)) {
357
+ if (!images->svg_markup_render(markup, pixel_width, pixel_height, &image)) {
351
358
  fprintf(stderr, "xt_gui: could not rasterise inline SVG\n");
352
359
  g_image_textures.emplace(key, nullptr);
353
360
  return nullptr;
@@ -380,6 +387,7 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
380
387
  full.u1 = 1.0f;
381
388
  full.v1 = 1.0f;
382
389
  beginGradientUpload();
390
+ beginBitmapUpload();
383
391
  for (const xtgui::PaintOp &op : list.ops) {
384
392
  if (op.kind != xtgui::PaintKind::Image || op.index >= list.images.size()) continue;
385
393
  const xtgui::PaintImage &image = list.images[op.index];
@@ -391,7 +399,9 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
391
399
  if (pixel_width < 1) pixel_width = 1;
392
400
  if (pixel_height < 1) pixel_height = 1;
393
401
  SDL_GPUTexture *texture = nullptr;
394
- if (image.gradient != nullptr) {
402
+ if (image.bitmap != nullptr) {
403
+ texture = ensureBitmapTexture(device, *image.bitmap, batch);
404
+ } else if (image.gradient != nullptr) {
395
405
  texture = ensureGradientTexture(device, image, pixel_width, pixel_height, batch);
396
406
  } else if (image.canvas_id != 0) {
397
407
  texture = ensureCanvasTexture(device, image.canvas_id, batch);
@@ -409,12 +419,14 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
409
419
  geometry->op_textures[op.index] = texture;
410
420
  }
411
421
  finishGradientUpload(device);
422
+ finishBitmapUpload(device);
412
423
  uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex), batch);
413
424
  }
414
425
 
415
426
  void xt_gui_image_textures_destroy(SDL_GPUDevice *device) {
416
427
  using namespace xtgui::renderer_detail;
417
428
  releaseGradientTextures(device);
429
+ releaseBitmapTextures(device);
418
430
  for (auto &entry : g_image_textures) {
419
431
  if (entry.second != nullptr) SDL_ReleaseGPUTexture(device, entry.second);
420
432
  }