xbintsc 0.5.32 → 0.5.37

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 (222) hide show
  1. package/README.md +10 -7
  2. package/README.zh-CN.md +10 -8
  3. package/dist/scripts/check-version.d.ts +27 -0
  4. package/dist/scripts/check-version.js +71 -0
  5. package/dist/scripts/check-version.js.map +1 -0
  6. package/dist/scripts/package-platform.d.ts +58 -0
  7. package/dist/scripts/package-platform.js +142 -0
  8. package/dist/scripts/package-platform.js.map +1 -0
  9. package/dist/scripts/verify-platform-package.d.ts +34 -0
  10. package/dist/scripts/verify-platform-package.js +161 -0
  11. package/dist/scripts/verify-platform-package.js.map +1 -0
  12. package/dist/src/cli/help.d.ts +1 -1
  13. package/dist/src/cli/main.js +3 -1
  14. package/dist/src/cli/main.js.map +1 -1
  15. package/dist/src/driver/runtime-lib.d.ts +29 -4
  16. package/dist/src/driver/runtime-lib.js +68 -10
  17. package/dist/src/driver/runtime-lib.js.map +1 -1
  18. package/dist/src/driver/version.d.ts +1 -1
  19. package/dist/src/driver/version.js +1 -1
  20. package/dist/src/extensions/gui/index.js +18 -5
  21. package/dist/src/extensions/gui/index.js.map +1 -1
  22. package/dist/src/extensions/media/index.js +4 -3
  23. package/dist/src/extensions/media/index.js.map +1 -1
  24. package/dist/src/parser/context.d.ts +41 -0
  25. package/dist/src/parser/context.js +89 -4
  26. package/dist/src/parser/context.js.map +1 -1
  27. package/dist/src/parser/literals.js +6 -12
  28. package/dist/src/parser/literals.js.map +1 -1
  29. package/dist/tests/driver/platform-package.test.d.ts +1 -0
  30. package/dist/tests/driver/platform-package.test.js +49 -0
  31. package/dist/tests/driver/platform-package.test.js.map +1 -0
  32. package/dist/tests/driver/runtime-lib.test.js +51 -4
  33. package/dist/tests/driver/runtime-lib.test.js.map +1 -1
  34. package/dist/tests/e2e/gui-audio.test.d.ts +14 -0
  35. package/dist/tests/e2e/gui-audio.test.js +287 -0
  36. package/dist/tests/e2e/gui-audio.test.js.map +1 -0
  37. package/dist/tests/e2e/gui-browser.test.js +65 -0
  38. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  39. package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
  40. package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
  41. package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
  42. package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
  43. package/dist/tests/e2e/gui-dom.test.js +215 -0
  44. package/dist/tests/e2e/gui-dom.test.js.map +1 -1
  45. package/dist/tests/e2e/gui-events.test.js +112 -0
  46. package/dist/tests/e2e/gui-events.test.js.map +1 -1
  47. package/dist/tests/e2e/gui-helpers.d.ts +7 -0
  48. package/dist/tests/e2e/gui-helpers.js +1 -0
  49. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  50. package/dist/tests/e2e/gui-media.test.js +78 -0
  51. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  52. package/dist/tests/e2e/gui-style.test.js +55 -0
  53. package/dist/tests/e2e/gui-style.test.js.map +1 -1
  54. package/dist/tests/e2e/gui-webgl.test.js +258 -0
  55. package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
  56. package/dist/tests/e2e/gui-webgl2-3d.test.d.ts +4 -0
  57. package/dist/tests/e2e/gui-webgl2-3d.test.js +264 -0
  58. package/dist/tests/e2e/gui-webgl2-3d.test.js.map +1 -0
  59. package/dist/tests/e2e/gui-webgl2-blit.test.d.ts +10 -0
  60. package/dist/tests/e2e/gui-webgl2-blit.test.js +262 -0
  61. package/dist/tests/e2e/gui-webgl2-blit.test.js.map +1 -0
  62. package/dist/tests/e2e/gui-webgl2-formats.test.d.ts +6 -0
  63. package/dist/tests/e2e/gui-webgl2-formats.test.js +395 -0
  64. package/dist/tests/e2e/gui-webgl2-formats.test.js.map +1 -0
  65. package/dist/tests/e2e/gui-webgl2-mrt.test.d.ts +4 -0
  66. package/dist/tests/e2e/gui-webgl2-mrt.test.js +105 -0
  67. package/dist/tests/e2e/gui-webgl2-mrt.test.js.map +1 -0
  68. package/dist/tests/e2e/gui-webgl2-ubo.test.d.ts +8 -0
  69. package/dist/tests/e2e/gui-webgl2-ubo.test.js +112 -0
  70. package/dist/tests/e2e/gui-webgl2-ubo.test.js.map +1 -0
  71. package/dist/tests/e2e/gui-webgl2.test.js +45 -1
  72. package/dist/tests/e2e/gui-webgl2.test.js.map +1 -1
  73. package/dist/tests/extensions/gui-index.test.js +1 -0
  74. package/dist/tests/extensions/gui-index.test.js.map +1 -1
  75. package/dist/tests/extensions/media.test.js +1 -0
  76. package/dist/tests/extensions/media.test.js.map +1 -1
  77. package/dist/tests/parser/expressions.test.js +30 -0
  78. package/dist/tests/parser/expressions.test.js.map +1 -1
  79. package/dist/tests/platform-tarball.test.d.ts +1 -0
  80. package/dist/tests/platform-tarball.test.js +111 -0
  81. package/dist/tests/platform-tarball.test.js.map +1 -0
  82. package/dist/tests/version.test.js +6 -0
  83. package/dist/tests/version.test.js.map +1 -1
  84. package/doc/ai/contributing.md +11 -0
  85. package/doc/ai/extensions.md +7 -4
  86. package/doc/ai/troubleshooting.md +7 -5
  87. package/doc/ai/zh-CN/contributing.md +9 -0
  88. package/doc/ai/zh-CN/extensions.md +4 -2
  89. package/doc/ai/zh-CN/troubleshooting.md +5 -3
  90. package/doc/gui/api.md +99 -22
  91. package/doc/gui/build.md +11 -4
  92. package/doc/gui/canvas.md +49 -18
  93. package/doc/gui/dom.md +57 -7
  94. package/doc/gui/media.md +4 -0
  95. package/doc/gui/webgl.md +105 -46
  96. package/doc/gui.md +7 -3
  97. package/doc/npm-package.md +55 -27
  98. package/doc/requirements.md +6 -2
  99. package/doc/zh-CN/gui/api.md +84 -14
  100. package/doc/zh-CN/gui/build.md +7 -2
  101. package/doc/zh-CN/gui/canvas.md +44 -19
  102. package/doc/zh-CN/gui/dom.md +48 -4
  103. package/doc/zh-CN/gui/media.md +3 -0
  104. package/doc/zh-CN/gui/webgl.md +22 -16
  105. package/doc/zh-CN/gui.md +3 -2
  106. package/doc/zh-CN/npm-package.md +44 -23
  107. package/doc/zh-CN/requirements.md +4 -1
  108. package/package.json +17 -1
  109. package/runtime/ext_gui/audio.cpp +365 -153
  110. package/runtime/ext_gui/audio_buffer.cpp +166 -0
  111. package/runtime/ext_gui/audio_buffer.h +37 -0
  112. package/runtime/ext_gui/audio_decode.cpp +219 -0
  113. package/runtime/ext_gui/audio_decode.h +39 -0
  114. package/runtime/ext_gui/audio_device.cpp +87 -0
  115. package/runtime/ext_gui/audio_effects.cpp +163 -0
  116. package/runtime/ext_gui/audio_effects.h +25 -0
  117. package/runtime/ext_gui/audio_internal.h +30 -0
  118. package/runtime/ext_gui/audio_mixer.cpp +494 -0
  119. package/runtime/ext_gui/audio_mixer.h +238 -0
  120. package/runtime/ext_gui/audio_param.cpp +104 -0
  121. package/runtime/ext_gui/audio_param_object.cpp +155 -0
  122. package/runtime/ext_gui/browser.cpp +45 -50
  123. package/runtime/ext_gui/browser_abort.cpp +249 -0
  124. package/runtime/ext_gui/browser_dialogs.cpp +76 -0
  125. package/runtime/ext_gui/browser_media.cpp +176 -0
  126. package/runtime/ext_gui/browser_navigation.cpp +130 -0
  127. package/runtime/ext_gui/canvas.cpp +88 -30
  128. package/runtime/ext_gui/canvas.h +74 -9
  129. package/runtime/ext_gui/canvas_image_data.cpp +143 -0
  130. package/runtime/ext_gui/canvas_internal.h +8 -5
  131. package/runtime/ext_gui/canvas_pattern.cpp +90 -0
  132. package/runtime/ext_gui/canvas_raster.cpp +31 -9
  133. package/runtime/ext_gui/canvas_style.cpp +4 -2
  134. package/runtime/ext_gui/css_selector.cpp +21 -3
  135. package/runtime/ext_gui/dom.h +4 -0
  136. package/runtime/ext_gui/dom_api.cpp +1 -0
  137. package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
  138. package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
  139. package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
  140. package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
  141. package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
  142. package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
  143. package/runtime/ext_gui/dom_api_document.cpp +58 -5
  144. package/runtime/ext_gui/dom_api_events.cpp +93 -17
  145. package/runtime/ext_gui/dom_api_internal.h +71 -2
  146. package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
  147. package/runtime/ext_gui/dom_api_node.cpp +40 -19
  148. package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
  149. package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
  150. package/runtime/ext_gui/dom_api_proto.cpp +35 -0
  151. package/runtime/ext_gui/dom_api_style.cpp +223 -13
  152. package/runtime/ext_gui/dom_api_webgl.cpp +7 -0
  153. package/runtime/ext_gui/dom_api_webgl2.cpp +151 -36
  154. package/runtime/ext_gui/dom_api_webgl2_draw.cpp +3 -3
  155. package/runtime/ext_gui/dom_api_webgl_buffer.cpp +6 -2
  156. package/runtime/ext_gui/dom_api_webgl_compressed.cpp +167 -0
  157. package/runtime/ext_gui/dom_api_webgl_framebuffer.cpp +160 -0
  158. package/runtime/ext_gui/dom_api_webgl_shader.cpp +4 -4
  159. package/runtime/ext_gui/dom_api_webgl_source.cpp +161 -0
  160. package/runtime/ext_gui/dom_api_webgl_state.cpp +169 -23
  161. package/runtime/ext_gui/dom_api_webgl_texture.cpp +276 -236
  162. package/runtime/ext_gui/dom_api_webgl_ubo.cpp +81 -0
  163. package/runtime/ext_gui/gui.cpp +103 -14
  164. package/runtime/ext_gui/gui_engine.h +56 -4
  165. package/runtime/ext_gui/gui_events.cpp +80 -9
  166. package/runtime/ext_gui/image_bitmap.cpp +28 -0
  167. package/runtime/ext_gui/image_bitmap.h +26 -0
  168. package/runtime/ext_gui/image_bitmap_browser.cpp +136 -0
  169. package/runtime/ext_gui/listener_options.h +43 -0
  170. package/runtime/ext_gui/media.cpp +32 -0
  171. package/runtime/ext_gui/media.h +13 -0
  172. package/runtime/ext_gui/media_api.h +23 -0
  173. package/runtime/ext_gui/paint.cpp +28 -0
  174. package/runtime/ext_gui/paint.h +4 -0
  175. package/runtime/ext_gui/webgl.h +152 -18
  176. package/runtime/ext_gui/webgl_blit.cpp +95 -0
  177. package/runtime/ext_gui/webgl_buffer.cpp +178 -263
  178. package/runtime/ext_gui/webgl_buffer_objects.cpp +270 -0
  179. package/runtime/ext_gui/webgl_clear.cpp +142 -0
  180. package/runtime/ext_gui/webgl_compressed.cpp +180 -0
  181. package/runtime/ext_gui/webgl_context.cpp +241 -156
  182. package/runtime/ext_gui/webgl_draw.cpp +68 -3
  183. package/runtime/ext_gui/webgl_draw_buffers.cpp +75 -0
  184. package/runtime/ext_gui/webgl_glsl.h +40 -0
  185. package/runtime/ext_gui/webgl_glsl_compile.cpp +6 -0
  186. package/runtime/ext_gui/webgl_glsl_rewrite.cpp +18 -0
  187. package/runtime/ext_gui/webgl_glsl_scan.cpp +20 -11
  188. package/runtime/ext_gui/webgl_glsl_scan_block.cpp +126 -0
  189. package/runtime/ext_gui/webgl_glsl_tokens.h +28 -0
  190. package/runtime/ext_gui/webgl_pipeline.cpp +71 -19
  191. package/runtime/ext_gui/webgl_program_blocks.cpp +58 -0
  192. package/runtime/ext_gui/webgl_program_internal.h +6 -0
  193. package/runtime/ext_gui/webgl_shader.cpp +13 -1
  194. package/runtime/ext_gui/webgl_state.cpp +228 -0
  195. package/runtime/ext_gui/webgl_texture.cpp +144 -17
  196. package/runtime/ext_gui/webgl_texture_3d.cpp +215 -0
  197. package/runtime/ext_gui/webgl_uniform_block.h +31 -0
  198. package/runtime/ext_gui/webgl_webgl2.cpp +58 -5
  199. package/runtime/ext_gui/window.cpp +14 -65
  200. package/runtime/ext_gui/window_events.cpp +125 -0
  201. package/runtime/ext_media/decode_audio.cpp +308 -0
  202. package/runtime/ext_media/media_bridge.c +2 -0
  203. package/runtime/rt.h +43 -0
  204. package/runtime/rt_internal.h +4 -0
  205. package/runtime/xt_array_buffer.c +30 -0
  206. package/runtime/xt_builtins.c +0 -21
  207. package/runtime/xt_containers/access.inc +94 -2
  208. package/runtime/xt_containers/objects.inc +29 -31
  209. package/runtime/xt_typed_array/elements.inc +13 -0
  210. package/scripts/build-media.ts +14 -6
  211. package/scripts/build-runtime.ts +21 -16
  212. package/scripts/check-version.ts +39 -9
  213. package/scripts/package-platform.ts +183 -0
  214. package/scripts/set-platform-version.ts +62 -0
  215. package/scripts/verify-platform-package.ts +182 -0
  216. package/src/cli/main.ts +5 -1
  217. package/src/driver/runtime-lib.ts +73 -9
  218. package/src/driver/version.ts +1 -1
  219. package/src/extensions/gui/index.ts +18 -5
  220. package/src/extensions/media/index.ts +4 -3
  221. package/src/parser/context.ts +99 -4
  222. package/src/parser/literals.ts +6 -12
@@ -0,0 +1,43 @@
1
+ /*
2
+ * xbintsc GUI engine — the `addEventListener` options bag.
3
+ *
4
+ * Element and window listeners accept the standard third argument:
5
+ * `capture` / `once` / `passive` (a boolean third argument is the legacy
6
+ * `capture` spelling) and `signal` (an `AbortSignal`; an aborted signal means
7
+ * the listener must not run again). Both delivery sites parse it here so the
8
+ * two paths cannot drift apart.
9
+ */
10
+ #ifndef XT_GUI_LISTENER_OPTIONS_H
11
+ #define XT_GUI_LISTENER_OPTIONS_H
12
+
13
+ #include "gui_engine.h"
14
+
15
+ struct XtGuiListenerOptions {
16
+ bool capture = false;
17
+ bool once = false;
18
+ bool passive = false;
19
+ xt_value signal = XT_UNDEFINED;
20
+ };
21
+
22
+ /** True when `signal` is an already-aborted `AbortSignal`. */
23
+ inline bool xt_gui_signal_aborted(xt_value signal) {
24
+ if (!XT_IS_OBJECT(signal)) return false;
25
+ xt_value aborted = xt_object_get_cstr(signal, "aborted");
26
+ return XT_IS_UNDEFINED(aborted) ? false : (xt_truthy(aborted) != 0);
27
+ }
28
+
29
+ inline XtGuiListenerOptions xt_gui_listener_options(xt_value options) {
30
+ XtGuiListenerOptions parsed;
31
+ if (XT_IS_OBJECT(options)) {
32
+ parsed.capture = xt_truthy(xt_object_get_cstr(options, "capture")) != 0;
33
+ parsed.once = xt_truthy(xt_object_get_cstr(options, "once")) != 0;
34
+ parsed.passive = xt_truthy(xt_object_get_cstr(options, "passive")) != 0;
35
+ xt_value signal = xt_object_get_cstr(options, "signal");
36
+ if (XT_IS_OBJECT(signal)) parsed.signal = signal;
37
+ } else if (XT_IS_BOOL(options)) {
38
+ parsed.capture = xt_truthy(options) != 0;
39
+ }
40
+ return parsed;
41
+ }
42
+
43
+ #endif /* XT_GUI_LISTENER_OPTIONS_H */
@@ -55,6 +55,38 @@ void xt_media_shutdown() {
55
55
  if (g_api != nullptr && g_api->shutdown != nullptr) g_api->shutdown();
56
56
  }
57
57
 
58
+ bool xt_media_decode_audio(const uint8_t* data, size_t size, DecodedAudio* out,
59
+ std::string* error) {
60
+ if (g_api == nullptr || g_api->decode_audio == nullptr) {
61
+ if (error) {
62
+ *error = "cannot decode this audio format; enable the 'media' extension (pass --ext media)";
63
+ }
64
+ return false;
65
+ }
66
+ XtMediaAudioBuffer decoded = {0, 0, 0, nullptr};
67
+ char message[kErrorSize] = {0};
68
+ if (g_api->decode_audio(data, size, &decoded, message, sizeof(message)) == 0 ||
69
+ decoded.samples == nullptr) {
70
+ if (decoded.samples != nullptr && g_api->free_audio != nullptr) {
71
+ g_api->free_audio(decoded.samples);
72
+ }
73
+ if (error) *error = message[0] != '\0' ? message : "cannot decode audio data";
74
+ return false;
75
+ }
76
+ if (decoded.channels <= 0 || decoded.frames < 0 || decoded.sample_rate <= 0) {
77
+ if (g_api->free_audio != nullptr) g_api->free_audio(decoded.samples);
78
+ if (error) *error = "decoder produced an empty audio buffer";
79
+ return false;
80
+ }
81
+ out->channels = decoded.channels;
82
+ out->sample_rate = decoded.sample_rate;
83
+ out->frames = static_cast<uint32_t>(decoded.frames);
84
+ size_t count = static_cast<size_t>(decoded.frames) * static_cast<size_t>(decoded.channels);
85
+ out->interleaved.assign(decoded.samples, decoded.samples + count);
86
+ if (g_api->free_audio != nullptr) g_api->free_audio(decoded.samples);
87
+ return true;
88
+ }
89
+
58
90
  } // namespace xtgui
59
91
 
60
92
  extern "C" void xt_media_install(const XtMediaApi* api) { g_api = api; }
@@ -35,11 +35,24 @@
35
35
  #include <memory>
36
36
  #include <string>
37
37
 
38
+ #include "audio_decode.h"
38
39
  #include "image.h"
39
40
  #include "media_api.h"
40
41
 
41
42
  namespace xtgui {
42
43
 
44
+ /**
45
+ * Decode an in-memory audio file (`decodeAudioData`).
46
+ *
47
+ * The engine has its own WAV decoder (`audio_decode.cpp`); this entry point is
48
+ * the fallback for compressed containers and codecs, which only the optional
49
+ * `media` extension can decode. Without that extension it fails with a message
50
+ * naming the extension. Implemented in `media.cpp`, which forwards to the
51
+ * backend table installed by the media bridge.
52
+ */
53
+ bool xt_media_decode_audio(const uint8_t* data, size_t size, DecodedAudio* out,
54
+ std::string* error);
55
+
43
56
  /** DOM events a media element may fire after `tick()`. */
44
57
  enum MediaEvent {
45
58
  kMediaEventLoadedMetadata = 1 << 0,
@@ -40,10 +40,30 @@ typedef XtMediaPlayerHandle (*XtMediaByIdFn)(uint64_t id);
40
40
  /** Tear down every decoder and process-wide media state. */
41
41
  typedef void (*XtMediaShutdownFn)(void);
42
42
 
43
+ /** Interleaved float PCM returned by the memory decoder.
44
+ *
45
+ * `samples` is allocated by the backend and must be released with the table's
46
+ * `free_audio` entry point (both archives use the same static CRT). */
47
+ typedef struct XtMediaAudioBuffer {
48
+ int channels;
49
+ int sample_rate;
50
+ int frames;
51
+ float *samples;
52
+ } XtMediaAudioBuffer;
53
+
54
+ /** Decode an in-memory audio file (`decodeAudioData`). Returns 1 on success and
55
+ * fills `out`; on failure returns 0 and copies a message into `error`. */
56
+ typedef int (*XtMediaDecodeAudioFn)(const unsigned char *data, size_t size,
57
+ XtMediaAudioBuffer *out, char *error, size_t error_size);
58
+ /** Release the `samples` a successful `decode_audio` allocated. */
59
+ typedef void (*XtMediaFreeAudioFn)(void *samples);
60
+
43
61
  typedef struct XtMediaApi {
44
62
  XtMediaOpenFn open;
45
63
  XtMediaByIdFn by_id;
46
64
  XtMediaShutdownFn shutdown;
65
+ XtMediaDecodeAudioFn decode_audio;
66
+ XtMediaFreeAudioFn free_audio;
47
67
  } XtMediaApi;
48
68
 
49
69
  /** Install a decoder table. Implemented by the GUI engine; called once by the
@@ -57,6 +77,9 @@ XtMediaPlayerHandle xt_media_backend_open(const char *path, char *error,
57
77
  size_t error_size, void *audio_sink);
58
78
  XtMediaPlayerHandle xt_media_backend_by_id(uint64_t id);
59
79
  void xt_media_backend_shutdown(void);
80
+ int xt_media_backend_decode_audio(const unsigned char *data, size_t size,
81
+ XtMediaAudioBuffer *out, char *error, size_t error_size);
82
+ void xt_media_backend_free_audio(void *samples);
60
83
 
61
84
  #ifdef __cplusplus
62
85
  }
@@ -246,6 +246,34 @@ void xt_paint_build(const LayoutBox *root, DisplayList &out, float viewportWidth
246
246
  paintBox(root, out, none, chain, viewportWidth, viewportHeight);
247
247
  }
248
248
 
249
+ void xt_paint_translate(DisplayList &list, float dx, float dy) {
250
+ if (dx == 0.0f && dy == 0.0f) return;
251
+ for (PaintRect &rect : list.rects) {
252
+ rect.x += dx;
253
+ rect.y += dy;
254
+ }
255
+ for (PaintText &text : list.texts) {
256
+ text.x += dx;
257
+ text.baseline += dy;
258
+ }
259
+ for (PaintImage &image : list.images) {
260
+ image.x += dx;
261
+ image.y += dy;
262
+ }
263
+ for (PaintOp &op : list.ops) {
264
+ op.clip.x += dx;
265
+ op.clip.y += dy;
266
+ }
267
+ for (ClipPathGeometry &path : list.clip_paths) {
268
+ for (ClipTriangle &triangle : path.triangles) {
269
+ for (int i = 0; i < 3; i++) {
270
+ triangle.x[i] += dx;
271
+ triangle.y[i] += dy;
272
+ }
273
+ }
274
+ }
275
+ }
276
+
249
277
  std::string DisplayList::dump() const {
250
278
  std::string text;
251
279
  char buffer[320];
@@ -140,6 +140,10 @@ struct DisplayList {
140
140
  void xt_paint_build(const LayoutBox *root, DisplayList &out, float viewportWidth = 0.0f,
141
141
  float viewportHeight = 0.0f);
142
142
 
143
+ /** Shift every shape and clip in `list` by `(dx, dy)`. Used for page scroll:
144
+ * the layout stays at document coordinates and painting moves the viewport. */
145
+ void xt_paint_translate(DisplayList &list, float dx, float dy);
146
+
143
147
  } // namespace xtgui
144
148
 
145
149
  #endif /* XT_GUI_PAINT_H */
@@ -33,11 +33,13 @@
33
33
  #include "webgl_constants.h"
34
34
  #include "webgl2_constants.h"
35
35
  #include "webgl_glsl.h"
36
+ #include "webgl_uniform_block.h"
36
37
 
37
38
  namespace xtgui {
38
39
 
39
40
  constexpr int kWebGLMaxAttribs = 16;
40
41
  constexpr int kWebGLMaxTextureUnits = 16;
42
+ constexpr int kWebGLMaxDrawBuffers = 4;
41
43
 
42
44
  struct WebGLShader {
43
45
  uint32_t id = 0;
@@ -64,6 +66,7 @@ struct WebGLProgram {
64
66
  bool linked = false;
65
67
  std::string log;
66
68
  std::vector<WebGLUniform> uniforms;
69
+ std::vector<WebGLUniformBlock> blocks;
67
70
  std::vector<GlslUniformDecl> attribs;
68
71
  std::unordered_map<std::string, int> attrib_locations; // name -> location
69
72
  std::unordered_map<std::string, int> attrib_bindings; // bindAttribLocation overrides
@@ -87,6 +90,8 @@ struct WebGLBuffer {
87
90
  SDL_GPUBuffer *gpu = nullptr;
88
91
  uint32_t capacity = 0;
89
92
  bool dirty = true;
93
+ /** `bufferData` usage hint, reported by `getBufferParameter`. */
94
+ int usage = GL_STATIC_DRAW;
90
95
  };
91
96
 
92
97
  struct WebGLTexture {
@@ -94,9 +99,20 @@ struct WebGLTexture {
94
99
  int width = 0;
95
100
  int height = 0;
96
101
  int levels = 1;
102
+ /** The binding target this texture was created for. */
103
+ int target = GL_TEXTURE_2D;
104
+ /** Layers for a cube/2D-array texture, depth for a 3D texture. */
105
+ int layers = 1;
97
106
  SDL_GPUTexture *gpu = nullptr;
98
107
  SDL_GPUTextureFormat format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
99
- bool as_target = false;
108
+ /** Depth(-stencil) formats can only be depth attachments. */
109
+ bool depth = false;
110
+ /** Block-compressed textures are sample-only, with one mip level. */
111
+ bool compressed = false;
112
+ /** Usage flags the current `gpu` texture was created with. */
113
+ uint32_t usage = 0;
114
+ bool as_target = false; // bound as a colour attachment
115
+ bool as_depth_target = false; // bound as a depth attachment
100
116
  bool has_mipmaps = false;
101
117
  int min_filter = 0;
102
118
  int mag_filter = 0;
@@ -117,11 +133,16 @@ struct WebGLRenderbuffer {
117
133
 
118
134
  struct WebGLFramebuffer {
119
135
  uint32_t id = 0;
120
- uint32_t color_texture = 0;
136
+ uint32_t color_textures[kWebGLMaxDrawBuffers] = {};
137
+ /** Layer rendered to for an array/cube colour attachment. */
138
+ int color_layers[kWebGLMaxDrawBuffers] = {};
139
+ /** `COLOR_ATTACHMENTi` or `NONE` per draw-buffer slot. */
140
+ int draw_buffers[kWebGLMaxDrawBuffers] = {GL_COLOR_ATTACHMENT0, GL_NONE, GL_NONE, GL_NONE};
141
+ uint32_t depth_texture = 0;
121
142
  uint32_t depth_renderbuffer = 0;
122
143
  int width = 0;
123
144
  int height = 0;
124
- SDL_GPUTexture *depth = nullptr; // owned when no renderbuffer supplies one
145
+ SDL_GPUTexture *depth = nullptr; // owned when no renderbuffer/texture supplies one
125
146
  };
126
147
 
127
148
  /** Per-attribute array state. */
@@ -138,9 +159,7 @@ struct WebGLAttribState {
138
159
  float current[4] = {0.0f, 0.0f, 0.0f, 1.0f};
139
160
  };
140
161
 
141
- /** A WebGL 2 vertex-array object: the attribute table plus its element buffer.
142
- * SDL_GPU has no VAO concept, so binding one restores this state into the live
143
- * `attribs_` array. */
162
+ /** A WebGL 2 vertex-array object; binding one restores this state. */
144
163
  struct WebGLVertexArray {
145
164
  uint32_t id = 0;
146
165
  WebGLAttribState attribs[kWebGLMaxAttribs];
@@ -165,6 +184,17 @@ struct WebGLSync {
165
184
  uint32_t id = 0;
166
185
  };
167
186
 
187
+ /** One face's stencil test and update state. */
188
+ struct WebGLStencilFace {
189
+ int func = GL_ALWAYS;
190
+ int ref = 0;
191
+ uint32_t value_mask = 0xFFFFFFFFu; // compare mask
192
+ uint32_t write_mask = 0xFFFFFFFFu;
193
+ int fail = GL_KEEP;
194
+ int zfail = GL_KEEP;
195
+ int zpass = GL_KEEP;
196
+ };
197
+
168
198
  /** The fixed-function state a pipeline depends on. */
169
199
  struct WebGLDrawState {
170
200
  bool blend = false;
@@ -177,10 +207,19 @@ struct WebGLDrawState {
177
207
  bool depth_test = false;
178
208
  bool depth_write = true;
179
209
  int depth_func = GL_LESS;
210
+ bool stencil = false;
211
+ WebGLStencilFace stencil_front;
212
+ WebGLStencilFace stencil_back;
180
213
  bool cull = false;
181
214
  int cull_face = GL_BACK;
182
215
  int front_face = GL_CCW;
183
216
  bool color_mask[4] = {true, true, true, true};
217
+ /** Attachment formats of the bound target; pipelines are format-specific. */
218
+ int color_formats[kWebGLMaxDrawBuffers] = {
219
+ SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
220
+ SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM};
221
+ int color_target_count = 1;
222
+ int depth_format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT;
184
223
  };
185
224
 
186
225
  class WebGLContext {
@@ -195,11 +234,15 @@ class WebGLContext {
195
234
  /** Resize the drawing buffer; GL semantics discard its contents. */
196
235
  void resize(int width, int height);
197
236
 
198
- /** End the current pass and submit the queued commands (called once per
199
- * frame, before the page's own render pass samples the colour texture). */
237
+ /** End the pass and submit (once per frame, before sampling). */
200
238
  void submit();
201
239
  SDL_GPUTexture *color_texture();
202
240
  bool lost() const { return lost_; }
241
+ /** Lose/restore the GPU device, dropping every WebGL object. */
242
+ void lose_context();
243
+ void restore_context();
244
+ /** `drawingBufferStorage`: fix the size across canvas resizes. */
245
+ void drawing_buffer_storage(int width, int height);
203
246
 
204
247
  /* -- object lookups ---------------------------------------------------- */
205
248
  WebGLShader *shader(uint32_t id);
@@ -223,10 +266,20 @@ class WebGLContext {
223
266
  void color_mask(bool r, bool g, bool b, bool a);
224
267
  void depth_mask(bool write) { state_.depth_write = write; }
225
268
  void depth_func(int func) { state_.depth_func = func; }
269
+ void stencil_func(int func, int ref, uint32_t mask);
270
+ void stencil_func_separate(int face, int func, int ref, uint32_t mask);
271
+ void stencil_op(int fail, int zfail, int zpass);
272
+ void stencil_op_separate(int face, int fail, int zfail, int zpass);
273
+ void stencil_mask(uint32_t mask);
274
+ void stencil_mask_separate(int face, uint32_t mask);
275
+ const WebGLStencilFace &stencil_face(int face) const;
276
+ int stencil_reference() const; // the value the GPU test sees
226
277
  void blend_func(int src, int dst);
227
278
  void blend_func_separate(int src_rgb, int dst_rgb, int src_alpha, int dst_alpha);
228
279
  void blend_equation(int mode);
229
280
  void blend_equation_separate(int rgb, int alpha);
281
+ void blend_color(float r, float g, float b, float a);
282
+ void blend_color_value(float out[4]) const;
230
283
  void cull_face(int mode);
231
284
  void front_face(int mode);
232
285
  void viewport(int x, int y, int w, int h);
@@ -261,8 +314,7 @@ class WebGLContext {
261
314
  int attrib_location(uint32_t program, const std::string &name);
262
315
  void bind_attrib_location(uint32_t program, int index, const std::string &name);
263
316
 
264
- /* Uniform uploads: `location` is an index into the program's uniform table
265
- * (the DOM layer wraps it in the `WebGLUniformLocation` handle). */
317
+ /* Uniform uploads; `location` indexes the program's uniform table. */
266
318
  void uniform_floats(uint32_t program, int location, int components, const double *values,
267
319
  int count);
268
320
  void uniform_ints(uint32_t program, int location, int components, const int *values, int count);
@@ -314,6 +366,12 @@ class WebGLContext {
314
366
  void delete_sync(uint32_t id);
315
367
  bool is_sync(uint32_t id) const;
316
368
  uint32_t uniform_block_index(uint32_t program, const std::string &name) const;
369
+ WebGLUniformBlock *uniform_block(uint32_t program, uint32_t index);
370
+ int uniform_block_parameter(uint32_t program, uint32_t index, int pname) const;
371
+ std::string uniform_block_name(uint32_t program, uint32_t index) const;
372
+ void uniform_block_binding(uint32_t program, uint32_t index, int binding);
373
+ void bind_buffer_base(int target, int index, uint32_t buffer, uint32_t offset, uint32_t size);
374
+ uint32_t uniform_buffer_binding(int index) const;
317
375
 
318
376
  /* -- textures ---------------------------------------------------------- */
319
377
  uint32_t create_texture();
@@ -324,18 +382,50 @@ class WebGLContext {
324
382
  int tex_parameter_value(uint32_t id, int pname);
325
383
  bool tex_image_rgba(uint32_t id, int width, int height, const uint8_t *rgba);
326
384
  bool tex_sub_image_rgba(uint32_t id, int x, int y, int width, int height, const uint8_t *rgba);
385
+ /** Level-0 upload; `pixels` is tightly packed RGBA or expanded already. */
386
+ bool tex_image_format(uint32_t id, int width, int height, SDL_GPUTextureFormat format,
387
+ const uint8_t *pixels, int layer = -1);
388
+ bool tex_sub_image_format(uint32_t id, int x, int y, int width, int height,
389
+ const uint8_t *pixels, int layer = 0);
327
390
  bool generate_mipmap(uint32_t id);
328
- /** WebGL 2 `texStorage2D`: allocate immutable storage at `width`×`height`.
329
- * The backend always allocates the full mip chain, so `levels` is a hint. */
391
+ /** `texStorage2D`; the backend allocates the full mip chain. */
330
392
  bool tex_storage_2d(uint32_t id, int levels, int width, int height);
393
+ bool tex_storage_format(uint32_t id, int levels, int width, int height,
394
+ SDL_GPUTextureFormat format);
395
+ /** Allocate (and optionally upload) a 3D or 2D-array texture. */
396
+ bool tex_storage_3d(uint32_t id, int levels, int width, int height, int depth,
397
+ SDL_GPUTextureFormat format, int target);
398
+ bool tex_image_3d(uint32_t id, int width, int height, int depth,
399
+ SDL_GPUTextureFormat format, const uint8_t *pixels, int target);
400
+ bool tex_sub_image_3d(uint32_t id, int x, int y, int z, int width, int height, int depth,
401
+ const uint8_t *pixels);
402
+ /** Upload a block-compressed region. */
403
+ bool tex_image_compressed(uint32_t id, int width, int height, SDL_GPUTextureFormat format,
404
+ int x, int y, int region_width, int region_height,
405
+ const uint8_t *data, uint32_t size);
406
+ /** Whether the device can sample `internalformat` (compressed formats). */
407
+ bool supports_texture_format(int internalformat);
331
408
  int texture_width(uint32_t id);
409
+ int texture_height(uint32_t id);
410
+ /** The SDL_GPU format of a texture (RGBA8 before `texImage2D`). */
411
+ int texture_format(uint32_t id);
332
412
 
333
413
  /* -- framebuffers ------------------------------------------------------ */
334
414
  uint32_t create_framebuffer();
335
415
  void delete_framebuffer(uint32_t id);
336
- void bind_framebuffer(uint32_t id);
337
- bool framebuffer_texture(uint32_t framebuffer, uint32_t texture);
416
+ void bind_framebuffer(int target, uint32_t id);
417
+ uint32_t bound_read_framebuffer_id() const { return read_framebuffer_; }
418
+ bool blit_framebuffer(const int source[4], const int destination[4], int mask, int filter);
419
+ bool framebuffer_texture(uint32_t framebuffer, uint32_t texture,
420
+ int attachment = GL_COLOR_ATTACHMENT0);
421
+ bool framebuffer_texture_layer(uint32_t framebuffer, uint32_t texture, int attachment,
422
+ int layer);
338
423
  bool framebuffer_status(uint32_t id);
424
+ /** WebGL 2 `drawBuffers` / `readBuffer`. */
425
+ void draw_buffers(int count, const int *buffers);
426
+ void read_buffer(int mode) { read_buffer_ = mode; }
427
+ int read_buffer_value() const { return read_buffer_; }
428
+ int draw_buffer_value(int index);
339
429
  uint32_t create_renderbuffer();
340
430
  void delete_renderbuffer(uint32_t id);
341
431
  void bind_renderbuffer(uint32_t id) { bound_renderbuffer_ = id; }
@@ -357,6 +447,9 @@ class WebGLContext {
357
447
  std::string string_parameter(int pname);
358
448
  uint32_t bound_buffer(int target) const;
359
449
  uint32_t bound_texture(int unit) const;
450
+ uint32_t bound_cube_texture(int unit) const;
451
+ uint32_t bound_array_texture(int unit) const;
452
+ uint32_t bound_3d_texture(int unit) const;
360
453
  uint32_t current_program_id() const { return current_program_; }
361
454
  uint32_t bound_framebuffer_id() const { return bound_framebuffer_; }
362
455
  uint32_t bound_renderbuffer_id() const { return bound_renderbuffer_; }
@@ -377,9 +470,24 @@ class WebGLContext {
377
470
  void ensure_command();
378
471
  void end_pass();
379
472
  void flush_uploads();
380
- void begin_pass(bool clear_color, bool clear_depth);
473
+ void begin_pass(bool clear_color, bool clear_depth, bool clear_stencil);
474
+ /** Scissored depth/stencil clear via a masked fullscreen triangle. */
475
+ void scissored_clear(int mask);
476
+ /** The 1×1 texture a scissored colour clear blits from. */
477
+ SDL_GPUTexture *clear_color_texture();
478
+ SDL_GPUTexture *framebuffer_color(uint32_t id);
479
+ void framebuffer_size(uint32_t id, int *width, int *height);
480
+ /** Release every GPU handle this context owns, keeping the CPU-side object
481
+ * table. Used by the destructor and by `lose_context`. */
482
+ void release_gpu_resources();
381
483
  SDL_GPUTexture *target_color();
382
484
  SDL_GPUTexture *target_depth();
485
+ int target_color_format();
486
+ int target_depth_format();
487
+ /** The texture rendered to for draw-buffer slot `slot`, or null. */
488
+ SDL_GPUTexture *draw_color_texture(int slot);
489
+ int draw_color_format(int slot);
490
+ int draw_color_layer(int slot);
383
491
  SDL_GPUTexture *ensure_texture_gpu(WebGLTexture *texture, bool as_target);
384
492
  SDL_GPUSampler *sampler_for(WebGLTexture *texture);
385
493
  SDL_GPUBuffer *ensure_buffer_gpu(WebGLBuffer *buffer);
@@ -415,15 +523,22 @@ class WebGLContext {
415
523
  SDL_GPUTexture *depth_ = nullptr;
416
524
  SDL_GPUTexture *fallback_texture_ = nullptr;
417
525
  SDL_GPUSampler *fallback_sampler_ = nullptr;
526
+ /** 1×1 RGBA8 source for a scissored colour clear. */
527
+ SDL_GPUTexture *clear_texture_ = nullptr;
528
+ /** Internal program/buffer for a scissored depth/stencil clear. */
529
+ uint32_t clear_program_ = 0;
530
+ uint32_t clear_buffer_ = 0;
418
531
 
419
532
  float clear_color_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
420
533
  float clear_depth_ = 1.0f;
421
534
  int clear_stencil_ = 0;
535
+ float blend_color_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
536
+ /** Set by `drawingBufferStorage`; canvas resizes then leave the size. */
537
+ bool fixed_storage_ = false;
422
538
  WebGLDrawState state_;
423
539
  int viewport_[4] = {0, 0, 0, 0};
424
540
  int scissor_box_[4] = {0, 0, 0, 0};
425
541
  bool scissor_test_ = false;
426
- bool stencil_test_ = false;
427
542
 
428
543
  std::unordered_map<uint32_t, std::unique_ptr<WebGLShader>> shaders_;
429
544
  std::unordered_map<uint32_t, std::unique_ptr<WebGLProgram>> programs_;
@@ -435,16 +550,27 @@ class WebGLContext {
435
550
  std::unordered_map<uint32_t, std::unique_ptr<WebGLSampler>> samplers_;
436
551
  std::unordered_map<uint32_t, std::unique_ptr<WebGLQuery>> queries_;
437
552
  std::unordered_map<uint32_t, std::unique_ptr<WebGLSync>> syncs_;
438
- /** GPU buffers created for this context's transient draws (constant vertex
439
- * attributes, expanded index data); released with the context. */
553
+ /** GPU buffers for transient draws (constant attributes, expanded indices). */
440
554
  std::vector<SDL_GPUBuffer *> temp_buffers_;
441
555
 
442
556
  uint32_t current_program_ = 0;
443
557
  uint32_t bound_array_ = 0;
444
558
  uint32_t bound_element_ = 0;
559
+ uint32_t bound_uniform_ = 0;
560
+ uint32_t bound_transform_feedback_ = 0;
445
561
  uint32_t bound_framebuffer_ = 0;
562
+ uint32_t read_framebuffer_ = 0;
563
+ int read_buffer_ = GL_BACK;
564
+ int default_draw_buffers_[kWebGLMaxDrawBuffers] = {GL_BACK, GL_NONE, GL_NONE, GL_NONE};
446
565
  uint32_t bound_renderbuffer_ = 0;
566
+ WebGLUniformBinding uniform_bindings_[16];
447
567
  uint32_t bound_textures_[kWebGLMaxTextureUnits] = {};
568
+ uint32_t bound_cube_textures_[kWebGLMaxTextureUnits] = {};
569
+ uint32_t bound_array_textures_[kWebGLMaxTextureUnits] = {};
570
+ uint32_t bound_3d_textures_[kWebGLMaxTextureUnits] = {};
571
+ /** The target of the most recent `bindTexture` on each unit, so the existing
572
+ * per-unit entry points operate on the object the page just bound. */
573
+ int active_texture_target_[kWebGLMaxTextureUnits] = {};
448
574
  uint32_t bound_samplers_[kWebGLMaxTextureUnits] = {};
449
575
  uint32_t bound_vertex_array_ = 0;
450
576
  WebGLVertexArray default_vertex_array_;
@@ -461,6 +587,14 @@ class WebGLContext {
461
587
  * a `<canvas>`). */
462
588
  WebGLContext *xt_webgl_by_id(uint64_t id);
463
589
 
590
+ /** Map a WebGL internal format to the SDL_GPU texture format the engine uses
591
+ * (RGBA8 for formats without a special backend representation). */
592
+ SDL_GPUTextureFormat webgl_storage_format(int internalformat);
593
+
594
+ /** Map a WebGL compressed-texture internal format to its SDL_GPU format, or
595
+ * `SDL_GPU_TEXTUREFORMAT_INVALID` for anything else. */
596
+ SDL_GPUTextureFormat webgl_compressed_format(int internalformat);
597
+
464
598
  } // namespace xtgui
465
599
 
466
600
  #endif /* XT_GUI_WEBGL_H */
@@ -0,0 +1,95 @@
1
+ /*
2
+ * xbintsc GUI engine — `blitFramebuffer`.
3
+ *
4
+ * SDL_GPU's blit copies a colour texture region to another colour texture, so
5
+ * the colour bit maps directly. Depth/stencil blits are not supported by the
6
+ * backend; those bits are ignored (the call is still accepted, like a driver
7
+ * that cannot expand the blit). Sources must be single-sampled, which is what
8
+ * this engine allocates today.
9
+ */
10
+
11
+ #include "webgl.h"
12
+
13
+ #include <algorithm>
14
+ #include <cstring>
15
+
16
+ #include "gui_internal.h"
17
+
18
+ namespace xtgui {
19
+
20
+ bool WebGLContext::blit_framebuffer(const int source[4], const int destination[4], int mask,
21
+ int filter) {
22
+ if ((mask & GL_COLOR_BUFFER_BIT) == 0) return true;
23
+ end_pass();
24
+ ensure_command();
25
+ if (cmd_ == nullptr) return false;
26
+ SDL_GPUTexture *source_texture = framebuffer_color(read_framebuffer_);
27
+ SDL_GPUTexture *destination_texture = framebuffer_color(bound_framebuffer_);
28
+ if (source_texture == nullptr || destination_texture == nullptr ||
29
+ source_texture == destination_texture) {
30
+ return false;
31
+ }
32
+ int source_width = 0;
33
+ int source_height = 0;
34
+ int destination_width = 0;
35
+ int destination_height = 0;
36
+ framebuffer_size(read_framebuffer_, &source_width, &source_height);
37
+ framebuffer_size(bound_framebuffer_, &destination_width, &destination_height);
38
+ if (source_width <= 0 || source_height <= 0 || destination_width <= 0 ||
39
+ destination_height <= 0) {
40
+ return false;
41
+ }
42
+ int sx0 = source[0];
43
+ int sy0 = source[1];
44
+ int sx1 = source[2];
45
+ int sy1 = source[3];
46
+ int dx0 = destination[0];
47
+ int dy0 = destination[1];
48
+ int dx1 = destination[2];
49
+ int dy1 = destination[3];
50
+ if (sx1 < sx0) std::swap(sx0, sx1);
51
+ if (sy1 < sy0) std::swap(sy0, sy1);
52
+ if (dx1 < dx0) std::swap(dx0, dx1);
53
+ if (dy1 < dy0) std::swap(dy0, dy1);
54
+ sx0 = std::max(0, std::min(sx0, source_width));
55
+ sx1 = std::max(0, std::min(sx1, source_width));
56
+ sy0 = std::max(0, std::min(sy0, source_height));
57
+ sy1 = std::max(0, std::min(sy1, source_height));
58
+ dx0 = std::max(0, std::min(dx0, destination_width));
59
+ dx1 = std::max(0, std::min(dx1, destination_width));
60
+ dy0 = std::max(0, std::min(dy0, destination_height));
61
+ dy1 = std::max(0, std::min(dy1, destination_height));
62
+ if (sx1 <= sx0 || sy1 <= sy0 || dx1 <= dx0 || dy1 <= dy0) return true;
63
+
64
+ SDL_GPUBlitRegion source_region;
65
+ memset(&source_region, 0, sizeof(source_region));
66
+ source_region.texture = source_texture;
67
+ source_region.x = (Uint32)sx0;
68
+ /* GL's origin is bottom-left; SDL's regions are top-down. */
69
+ source_region.y = (Uint32)(source_height - sy1);
70
+ source_region.w = (Uint32)(sx1 - sx0);
71
+ source_region.h = (Uint32)(sy1 - sy0);
72
+
73
+ SDL_GPUBlitRegion destination_region;
74
+ memset(&destination_region, 0, sizeof(destination_region));
75
+ destination_region.texture = destination_texture;
76
+ destination_region.x = (Uint32)dx0;
77
+ destination_region.y = (Uint32)(destination_height - dy1);
78
+ destination_region.w = (Uint32)(dx1 - dx0);
79
+ destination_region.h = (Uint32)(dy1 - dy0);
80
+
81
+ SDL_GPUBlitInfo info;
82
+ memset(&info, 0, sizeof(info));
83
+ info.source = source_region;
84
+ info.destination = destination_region;
85
+ /* A blit only writes the destination rectangle; everything outside it keeps
86
+ * its previous contents, as `blitFramebuffer` requires. DONT_CARE lets a
87
+ * backend (Metal's MTLLoadActionDontCare) discard the attachment and leave
88
+ * those pixels undefined, so load it. */
89
+ info.load_op = SDL_GPU_LOADOP_LOAD;
90
+ info.filter = filter == GL_LINEAR ? SDL_GPU_FILTER_LINEAR : SDL_GPU_FILTER_NEAREST;
91
+ SDL_BlitGPUTexture(cmd_, &info);
92
+ return true;
93
+ }
94
+
95
+ } // namespace xtgui