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
package/doc/gui/api.md CHANGED
@@ -21,19 +21,55 @@ Methods implemented on a window handle: `setTitle` / `setSize` / `loadHTML` /
21
21
  `getHTML` / `setBackground` / `close` / `isOpen` / `on` / `off`, plus `driver()`,
22
22
  which reports the SDL_GPU backend in use (`"vulkan"`, `"direct3d12"`, `"metal"`,
23
23
  … — SDL picks the best one the device supports). The Web spellings are available
24
- too: `addEventListener` / `removeEventListener` are aliases of `on` / `off` (their
25
- `options` argument is accepted and ignored) and the `closed` getter mirrors
26
- `isOpen()`, so a document written against the browser API runs unchanged.
27
- Events emitted:
28
- `ready` (after the first presented frame), `load`, `close`, `resize` and the
29
- input events `mousemove`, `mousedown`, `mouseup`, `click`, `wheel`, `keydown`,
30
- `keyup`.
24
+ too: `addEventListener` / `removeEventListener` take the standard options bag
25
+ (`capture`, `once`, `passive`, `signal`), and `closed` mirrors `isOpen()`, so a
26
+ document written against the browser API runs unchanged.
27
+
28
+ Element listeners accept the same options: `{ once }` drops the listener after
29
+ its first call, `{ passive: true }` makes `preventDefault()` a no-op, and
30
+ `{ signal }` (an `AbortSignal` from `new AbortController()`) removes it on
31
+ abort, including a signal that was already aborted when the listener was added.
32
+ `element.dispatchEvent(event)` dispatches the *same* event object (identity is
33
+ observable), fills in `target` / `currentTarget` / `eventPhase` and records the
34
+ path for `event.composedPath()`. `new Event(...)`, `new CustomEvent(...)`,
35
+ `new MouseEvent(...)`, `new KeyboardEvent(...)`, `new PointerEvent(...)`,
36
+ `new WheelEvent(...)`, `new FocusEvent(...)`, `new InputEvent(...)` and
37
+ `new SubmitEvent(...)` keep every field of their `init` object (`detail`,
38
+ `composed`, the mouse/keyboard members, …). Window listeners take the same bag
39
+ (capture is part of listener identity; window delivery has no capture phase).
40
+
41
+ Window navigation is real: `scrollTo(x, y)` / `scrollTo({ left, top })` /
42
+ `scrollBy(...)` move the page viewport (clamped to the content bounds; painting
43
+ and hit testing follow it), and `scrollX` / `scrollY` / `pageXOffset` /
44
+ `pageYOffset` report it. `focus()` raises the OS window and sets
45
+ `document.hasFocus()`; `blur()` clears it. Events emitted:
46
+ `ready` (after the first presented frame), `load`, `close`, `resize`, `scroll`,
47
+ `focus`, `blur` and the input events `mousemove`, `mousedown`, `mouseup`,
48
+ `click`, `wheel`, `keydown`, `keyup`.
49
+
50
+ `createWindow(options)` honours more than the title and size:
51
+ `{ title, width, height, highDensity, resizable, borderless, alwaysOnTop,
52
+ hidden, fullscreen, x, y, opacity, maximized, minimized }` map to the matching
53
+ SDL window flags/calls instead of being ignored.
54
+
55
+ `window.confirm(message)` / `window.prompt(message, defaultValue)` never drop
56
+ their arguments: with `XT_GUI_DIALOGS=1` and a live window they show a native
57
+ message box; otherwise the message goes to stderr and the documented default is
58
+ returned (`confirm` → `false`, `prompt` → `defaultValue` or `""`).
31
59
 
32
60
  On Windows a GUI program is linked as a **Windows-subsystem** executable, so
33
61
  launching it does not flash a console window. That also means stdout/stderr go
34
62
  nowhere when it is started from Explorer; set `app.console: true` in
35
63
  `xbintsc.config.json` to keep the console subsystem (useful while debugging).
36
64
 
65
+ A host without a usable GPU cannot run the GUI at all. When SDL cannot open a
66
+ video driver or create a Metal/Vulkan/D3D12 device, the engine prints the reason
67
+ and exits with status **77** — the autotools "skip" convention, so CI and scripts
68
+ can tell "this machine has no GUI" from "the program failed". A window that
69
+ cannot be opened after that (an SDL window or GPU-claim error) makes
70
+ `createWindow` throw a JavaScript `Error`; it no longer returns an `undefined`
71
+ handle that fails a line later as `Cannot read properties of undefined`.
72
+
37
73
  ## Browser globals
38
74
 
39
75
  A document ported from the web rarely stops at `createWindow`. With `--ext gui`
@@ -54,12 +90,14 @@ requestAnimationFrame(frame);
54
90
  | `document` | that window's document handle |
55
91
  | `navigator` | `userAgent` (a desktop Chrome string ending in `xbintsc`), `platform`, `vendor`, `language(s)`, `hardwareConcurrency`, `maxTouchPoints`, `onLine`, `vibrate()` |
56
92
  | `localStorage` | an in-memory `getItem` / `setItem` / `removeItem` / `clear` / `key` / `length` store |
57
- | `AudioContext`, `webkitAudioContext` | a Web Audio graph builder (see below) |
58
- | `Event`, `CustomEvent`, `ProgressEvent`, … | event constructors |
93
+ | `AudioContext`, `webkitAudioContext` | a Web Audio graph with a real mixer and decoder (see below) |
94
+ | `Event`, `CustomEvent`, `ProgressEvent`, `MouseEvent`, `KeyboardEvent`, `PointerEvent`, `WheelEvent`, `FocusEvent`, `InputEvent`, `SubmitEvent` | event constructors; the `init` argument is kept as given (`detail`, `composed`, mouse/keyboard fields, …) |
95
+ | `AbortController`, `AbortSignal` | a real controller/signal pair for `{ signal }` listener removal, with `abort()`, `aborted`, `reason`, `onabort` and `throwIfAborted()` |
59
96
  | `HTMLCanvasElement`, `HTMLImageElement`, `WebGLRenderingContext`, `WebGL2RenderingContext`, `WebGLBuffer`/`WebGLTexture`/`WebGLShader`/`WebGLProgram`/`WebGLFramebuffer`/`WebGLRenderbuffer`/`WebGLUniformLocation`/`WebGLActiveInfo`/`WebGLVertexArrayObject`/`WebGLSampler`/`WebGLQuery`/`WebGLSync`/`WebGLTransformFeedback` | real constructor values. `canvas.getContext("webgl")` and `getContext("webgl2")` return GPU contexts (see [WebGL](./webgl.md)). |
60
- | `ImageBitmap`, `ImageData`, `OffscreenCanvas`, `XRWebGL*`, `Request`, `Headers`, `Response`, `ReadableStream`, `DOMParser` | `typeof` probes answer `"undefined"` (no XR/fetch/streaming backend), which lets a page take its "unsupported" branch instead of calling a no-op stub. Three.js pages use its real renderer on this backend (see [Known issues](./known-issues.md)). |
97
+ | `ImageBitmap`, `createImageBitmap(source)` | immutable bitmaps backed by decoded pixels (`<img>`, `<canvas>`, `<video>` frames and `ImageData` shapes); the promise resolves immediately and `texImage2D` accepts the result |
98
+ | `ImageData`, `OffscreenCanvas`, `XRWebGL*`, `Request`, `Headers`, `Response`, `ReadableStream`, `DOMParser` | `typeof` probes answer `"undefined"` (no XR/fetch/streaming backend), which lets a page take its "unsupported" branch instead of calling a no-op stub. Three.js pages use its real renderer on this backend (see [Known issues](./known-issues.md)). |
61
99
  | `requestAnimationFrame` / `cancelAnimationFrame` | queued on the window's frame list; see [Runtime integration](./runtime.md) |
62
- | `alert` / `confirm` / `prompt`, `matchMedia`, `getComputedStyle`, `escape` | host implementations (Annex B `escape` encodes per UTF-16 code unit, matching `decodeURIComponent(escape(bytes))`) |
100
+ | `alert` / `confirm` / `prompt`, `matchMedia`, `getComputedStyle`, `escape` | host implementations. `matchMedia` returns a **live** list (cached per query): `matches` is re-evaluated on resize and `change` listeners fire when it flips. `confirm`/`prompt` use a native dialog only with `XT_GUI_DIALOGS=1`; otherwise they report the message on stderr and return the documented default (Annex B `escape` encodes per UTF-16 code unit, matching `decodeURIComponent(escape(bytes))`) |
63
101
 
64
102
  `getElementById` / `querySelector` are answered even before `loadHTML` runs: the
65
103
  handle is *pending* and binds to the real node the first time it is used, so a
@@ -68,17 +106,56 @@ Once the page is parsed a miss answers `undefined`, like the browser.
68
106
 
69
107
  ### Web Audio
70
108
 
71
- `new AudioContext()` builds a graph but **does not mix or render audio**: there
72
- is no backend behind it. What is implemented is the surface a page needs to run
73
- and to read back its own state — `createGain` / `createOscillator` /
74
- `createBufferSource` / `createBiquadFilter` / `createDynamicsCompressor` /
75
- `createBuffer` (real `Float32Array` channels) / `decodeAudioData` (resolves with
76
- an empty buffer) / `resume` / `suspend` / `close`, `AudioParam` `value` plus
77
- `setValueAtTime` / `linearRampToValueAtTime` / `exponentialRampToValueAtTime` /
78
- `setTargetAtTime` / `cancelScheduledValues` / `cancelAndHoldAtTime`, and node
79
- `connect` (returns the destination, so chains chain) / `disconnect` / `start` /
80
- `stop`. `sampleRate` is 48000, `state` `"running"`, and `currentTime` advances
81
- only through the event loop.
109
+ `new AudioContext()` builds a real graph and mixes it: `runtime/ext_gui/audio_mixer.cpp`
110
+ renders 48 kHz stereo float in 128-frame quanta, multiple inputs are summed at
111
+ every node, and the first `start()`/`resume()` opens the default SDL playback
112
+ device (best effort — a headless host keeps rendering silently). The graph is
113
+ rendered on the SDL audio thread from native state; the page never has to pump
114
+ it, and no runtime value is touched off the main thread.
115
+
116
+ Implemented nodes: `createGain`, `createOscillator` (sine, square, sawtooth,
117
+ triangle), `createBufferSource` (buffer, loop, loopStart/loopEnd,
118
+ playbackRate/detune), `createBiquadFilter` (lowpass, highpass, bandpass,
119
+ lowshelf, highshelf, peaking, notch, allpass), `createDynamicsCompressor`,
120
+ `createStereoPanner`, `createConstantSource` and `createDelay(maxDelayTime)`.
121
+ `connect` returns the destination so chains chain (`src.connect(gain).connect(ctx.destination)`),
122
+ `disconnect()` / `disconnect(target)` update the native graph, and `start(when, offset, duration)` /
123
+ `stop(when)` are honoured on the graph's clock. Nodes outside this list
124
+ (`createAnalyser`, `createConvolver`, `createWaveShaper`, `createChannelSplitter`,
125
+ `createPeriodicWave`, …) are not provided, so their methods are simply absent.
126
+
127
+ Every `AudioParam` has a live `value` plus `setValueAtTime` /
128
+ `linearRampToValueAtTime` / `exponentialRampToValueAtTime` / `setTargetAtTime` /
129
+ `cancelScheduledValues` / `cancelAndHoldAtTime`. The automation timeline is
130
+ evaluated per sample, so a ramp keeps moving while the page is idle.
131
+ `currentTime` advances with the rendered quanta.
132
+
133
+ `createBuffer(channels, length, sampleRate)` returns an AudioBuffer whose
134
+ `getChannelData(i)` is a real `Float32Array`, so samples can be generated
135
+ procedurally. `decodeAudioData(bytes)` returns a promise for a decoded
136
+ AudioBuffer: WAV (PCM 8/16/24/32-bit, IEEE float 32/64, A-law and µ-law) is
137
+ decoded by the engine itself; MP3, AAC/M4A, Vorbis, Opus and FLAC need the
138
+ optional `media` extension (`--ext gui,media`), whose FFmpeg decoder is used as
139
+ the fallback. `bytes` may be an ArrayBuffer, a typed array, a Node `Buffer`, or
140
+ the raw byte string `fs.readFileSync` returns (this runtime has no dedicated
141
+ binary type, and that string's storage is exactly the file bytes). The result
142
+ is resampled to the context's 48 kHz, so `buffer.sampleRate` matches
143
+ `context.sampleRate`; the promise rejects with an `EncodingError` when nothing
144
+ can decode the bytes.
145
+
146
+ `resume()` / `suspend()` / `close()` control the device and the `state`
147
+ property; `sampleRate` is 48000, like the media decoder.
148
+
149
+ For tests and diagnostics an **offline** context renders synchronously:
150
+
151
+ ```ts
152
+ const ctx = new AudioContext({ __offline: true });
153
+ // … build the graph and start sources …
154
+ const samples = ctx.__render(480); // interleaved stereo Float32Array
155
+ ```
156
+
157
+ This private hook never opens a device and is not part of the Web Audio API; it
158
+ is what `tests/e2e/gui-audio.test.ts` uses to pin the mixer math.
82
159
 
83
160
  Input handlers receive a single payload object (lifecycle handlers receive none):
84
161
 
package/doc/gui/build.md CHANGED
@@ -58,12 +58,19 @@
58
58
  rather than through `make install`.
59
59
  - CI builds `gui.a`/`gui.lib` and `media.a`/`media.lib` before assembling the
60
60
  release archive so they ship inside `runtime/lib/<slug>/` (the release tarball
61
- copies the whole `runtime/` tree). The archives are built and the examples are
62
- run on Linux (under Xvfb, with the lavapipe software Vulkan driver) and macOS;
61
+ copies the whole `runtime/` tree). The same folders are packed into the
62
+ optional `@xbintsc/<slug>` npm package, so `--ext gui`/`--ext media` work from
63
+ an npm install on the hosts the release builds for. The archives are built and
64
+ the examples are run on Linux (under Xvfb, with the lavapipe software Vulkan
65
+ driver) and macOS;
63
66
  the Windows x64 job builds both archives and links a `gui,media` program, so
64
67
  the MSVC build is validated even though *running* the examples there stays
65
- provisional (the hosted runner has no GPU/Vulkan driver). Windows arm64 still
66
- skips both archives: FFmpeg's MSVC arm64 build is not validated yet.
68
+ provisional (the hosted runner has no GPU/Vulkan driver). The macOS run is
69
+ provisional too: the Intel runner's virtualised GPU lacks
70
+ `MTLGPUFamilyMac2`, so the engine exits **77** ("cannot run a GUI here") and
71
+ the job records a skip instead of compiling a suite that cannot present.
72
+ Windows arm64 still skips both archives: FFmpeg's MSVC arm64 build is not
73
+ validated yet.
67
74
  - The Windows CI steps call `.github/actions/vs-toolchain` →
68
75
  `scripts/setup-vs-toolchain.ps1`, which imports the same Visual Studio
69
76
  `vcvarsall` environment and bundled LLVM a local build uses (no separate
package/doc/gui/canvas.md CHANGED
@@ -36,12 +36,20 @@ a shared prototype. The handle is cached on the element, so every call returns
36
36
  the **same** object and the drawing state (transform, styles, path) is per
37
37
  canvas, not per call. `ctx.canvas` is the owning element handle.
38
38
 
39
+ `getContext("2d", attributes)` honours `alpha` on the first call: with
40
+ `{ alpha: false }` the bitmap starts (and clears) opaque black. The other
41
+ attributes are reported by `getContextAttributes()` at their specification
42
+ defaults (`antialias: true`, `depth: true`, `stencil: false`,
43
+ `premultipliedAlpha: true`, `preserveDrawingBuffer: false`, `colorSpace:
44
+ "srgb"`, …), so a feature test gets an honest answer.
45
+
39
46
  ## Drawing model
40
47
 
41
48
  The rasteriser is deliberately small and self-written (no Skia/Cairo). It
42
49
  anti-aliases with **4 vertical sub-scanlines** per pixel row and an **analytic
43
50
  horizontal coverage** pass, and fills paths with the **non-zero winding rule**
44
- (so `arc`-shaped holes work when wound the other way). An axis-aligned rectangle
51
+ (so `arc`-shaped holes work when wound the other way) unless
52
+ `fill("evenodd")` selects the even-odd rule. An axis-aligned rectangle
45
53
  (`b == 0 && c == 0` in the transform) takes a direct coverage fast path.
46
54
  Blending is non-premultiplied source-over, scaled by `globalAlpha`.
47
55
 
@@ -63,10 +71,11 @@ matching GPU texture.
63
71
 
64
72
  State and transforms: `save` / `restore` (which restore the transform, line
65
73
  width, alpha, font/text settings **and** the fill/stroke styles),
66
- `translate`, `rotate`, `scale`, `transform(a,b,c,d,e,f)`, `setTransform`,
67
- `resetTransform`, `getTransform` (returns an object with `a`…`f` and `is2D`).
68
- Transforms compose in canvas order: `translate(x, y); rotate(a)` rotates around
69
- the translated origin.
74
+ `translate`, `rotate`, `scale`, `transform(a,b,c,d,e,f)` / `transform(matrix)`,
75
+ `setTransform(a,b,c,d,e,f)` / `setTransform(matrix)`, `resetTransform`,
76
+ `getTransform` (returns an object with `a`…`f` and `is2D`). A matrix is a
77
+ `{ a, b, c, d, e, f }` object or a six-element array. Transforms compose in
78
+ canvas order: `translate(x, y); rotate(a)` rotates around the translated origin.
70
79
 
71
80
  Styles and settings: `fillStyle` / `strokeStyle` (CSS colors understood by the
72
81
  cascade's color parser, or a `CanvasGradient`), `lineWidth`, `globalAlpha`,
@@ -86,10 +95,18 @@ distance from the **outer** circle's centre (`x1, y1`), so `x0`/`y0` are
86
95
  currently ignored: use concentric circles (`x0 == x1`, `y0 == y1`).
87
96
 
88
97
  Paths: `beginPath`, `closePath`, `moveTo`, `lineTo`, `rect`, `roundRect`,
89
- `arc`, `ellipse`, `quadraticCurveTo`, `bezierCurveTo`, then `fill` (multi-loop,
90
- non-zero winding) or `stroke`. Bezier and quadratic segments are flattened into
91
- line segments; arcs and ellipses are flattened too. `roundRect` accepts one
92
- radius number (the array form is not implemented).
98
+ `arc`, `ellipse`, `quadraticCurveTo`, `bezierCurveTo`, then
99
+ `fill(fillRule?)` / `fill(path, fillRule?)` (multi-loop; `"nonzero"` or
100
+ `"evenodd"`) or `stroke(path?)`. Bezier and quadratic segments are flattened
101
+ into line segments; arcs and ellipses are flattened too. `roundRect` accepts a
102
+ single radius number or the CSS radius list (`[all]`, `[tl, br]`,
103
+ `[tl, tr, br]`, `[tl, tr, br, bl]`, numbers or `{ x, y }` pairs; corners are
104
+ drawn circular).
105
+
106
+ `Path2D` is implemented with the same path commands (`moveTo`, `lineTo`,
107
+ `closePath`, `rect`, `roundRect`, `arc`, `ellipse`, `quadraticCurveTo`,
108
+ `bezierCurveTo`, `addPath`) and can be passed to `fill`/`stroke`, so a path can
109
+ be built once and replayed under different transforms.
93
110
 
94
111
  Rectangles: `clearRect`, `fillRect`, `strokeRect`.
95
112
 
@@ -97,9 +114,12 @@ Dashes: `setLineDash(segments)` / `getLineDash()` / `lineDashOffset` implement
97
114
  the standard pattern rules (an odd-length list is duplicated, negative or
98
115
  non-finite entries are ignored, an all-zero list means a solid line).
99
116
 
100
- Text: `measureText(text)` (returns `{ width }`), `fillText(text, x, y)` and
101
- `strokeText(text, x, y)`. Text is shaped with HarfBuzz and rasterised with
102
- FreeType (the same stack as HTML text) at the transform's approximate scale.
117
+ Text: `measureText(text)` (returns `{ width }`), `fillText(text, x, y,
118
+ maxWidth?)` and `strokeText(text, x, y, maxWidth?)`. Text is shaped with
119
+ HarfBuzz and rasterised with FreeType (the same stack as HTML text) at the
120
+ transform's approximate scale. A positive `maxWidth` condenses advances and
121
+ glyph bitmaps horizontally so the run is at most that wide (as the canvas API
122
+ specifies) instead of being ignored.
103
123
  **Only translation and uniform scale are honoured** — a rotated or skewed
104
124
  transform still places each glyph at its transformed baseline point but does not
105
125
  rotate the glyph bitmaps. `strokeText` currently fills with the stroke style
@@ -119,14 +139,25 @@ Test hook: `getImageData(x, y, w, h)` copies a region to
119
139
  `{ width, height, data }` where `data` is a row-major RGBA8 byte array.
120
140
  Out-of-bounds pixels are transparent.
121
141
 
142
+ Patterns: `createPattern(source, repetition?)` accepts an `<img>` or another
143
+ `<canvas>` and tiles it in user space (`repeat`, `repeat-x`, `repeat-y`,
144
+ `no-repeat`), so the tiles follow the current transform. Assign the result to
145
+ `fillStyle`/`strokeStyle`; assigning a colour replaces it.
146
+
147
+ Image data and export: `createImageData(width, height)` allocates a blank
148
+ `{ width, height, data }`, and `putImageData(imageData, dx, dy[, dirtyX, dirtyY,
149
+ dirtyWidth, dirtyHeight])` copies bytes straight into the bitmap (no transform,
150
+ blending or `globalAlpha`). `canvas.toDataURL(type?)` exports a PNG data URL and
151
+ `canvas.toBlob(callback, type?)` delivers a Blob-shaped `{ type, size }` from a
152
+ microtask (the bytes are on `__xt_gui_blob_data`; there is no
153
+ `URL.createObjectURL`).
154
+
122
155
  ## Not implemented
123
156
 
124
- Patterns (`createPattern`), shadows, `clip()`, `globalCompositeOperation`,
125
- `putImageData`, `createImageData`, `isPointInPath`, `setTransform` with a
126
- `DOMMatrix`, `drawImage` with an image smoothing toggle, `toDataURL`/`toBlob`,
127
- `<canvas>` as a CSS `background-image`, and offscreen canvases
128
- (`OffscreenCanvas`, `transferControlToOffscreen`). `roundRect` takes a single
129
- radius, and radial gradients ignore the inner circle's offset.
157
+ Shadows, `clip()`, `globalCompositeOperation`, `isPointInPath`, `drawImage` with
158
+ an image smoothing toggle, `<canvas>` as a CSS `background-image`, and offscreen
159
+ canvases (`OffscreenCanvas`, `transferControlToOffscreen`).
160
+ Radial gradients ignore the inner circle's offset.
130
161
 
131
162
  See also [Media](./media.md) for the image/video/audio decode stack that
132
163
  `drawImage` and the texture upload share.
package/doc/gui/dom.md CHANGED
@@ -14,9 +14,12 @@ close rules (`li`, `dt`/`dd`, `option`, `p`, headings, table cells/rows).
14
14
  **CSS parser** (`runtime/ext_gui/css.{h,cpp}`): comments are stripped; `@media`
15
15
  blocks are parsed recursively (nested queries combine with `and`, and a comma
16
16
  list distributes over the inner query) while other at-rules are skipped. Rules
17
- support multiple selectors and declarations. Selectors: type, `.class`, `#id`,
18
- attribute (`=`, `~=`, `|=`, `^=`, `$=`, `*=`), the four combinators
19
- (descendant, child, adjacent and general sibling), the pseudo-classes
17
+ support multiple selectors and declarations; a selector list may be formatted
18
+ across any number of lines (line breaks are ordinary whitespace, so a wrapped
19
+ combinator stays a combinator), and a quoted attribute value may contain
20
+ commas, brackets or parentheses without splitting the list. Selectors: type,
21
+ `.class`, `#id`, attribute (`=`, `~=`, `|=`, `^=`, `$=`, `*=`), the four
22
+ combinators (descendant, child, adjacent and general sibling), the pseudo-classes
20
23
  `:first-child`, `:last-child`, `:only-child`, `:empty`, `:root`, `:not(...)`,
21
24
  `:nth-child(an+b)`, `:disabled`, `:checked`, plus the stateful `:hover` and
22
25
  `:focus` (see [Input](./input.md)), and the pseudo-elements `::before`/`::after`
@@ -69,18 +72,65 @@ created.textContent = "added";
69
72
  box.appendChild(created);
70
73
  box.removeChild(created);
71
74
 
75
+ // Parent/child helpers accept nodes and strings in one call.
76
+ box.append("text", doc.createElement("span"));
77
+ box.prepend(doc.createElement("b"));
78
+ box.insertAdjacentHTML("beforeend", "<i>tail</i>"); // beforebegin/afterbegin/beforeend/afterend
79
+ box.replaceChildren("fresh", doc.createElement("em"));
80
+
81
+ // Namespaces are recorded; foreign (SVG) element names keep their case.
82
+ const gradient = doc.createElementNS("http://www.w3.org/2000/svg", "svg:linearGradient");
83
+ console.log(gradient.namespaceURI, gradient.localName, gradient.prefix);
84
+
72
85
  inner.addEventListener("click", (e) => console.log(e.target.id, e.currentTarget.id));
73
86
  inner.click(); // synthesise a click at the element
74
87
  console.log(box.offsetWidth, box.offsetHeight); // rounded border box
75
88
  console.log(box.contains(inner)); // descendant test
76
89
  ```
77
90
 
91
+ ### Style properties, dataset, computed style and geometry
92
+
93
+ The inline `style` object is a CSSOM declaration block. Named access reads and
94
+ writes the same block as `setProperty` (`el.style.display = "none"`,
95
+ `el.style.backgroundColor = "#f00"`, camelCase mapped to the CSS spelling, and
96
+ `el.style["--accent"]` for custom properties); `setProperty(name, value,
97
+ priority)` stores `!important` when `priority` is `"important"` (any other
98
+ priority makes the call a no-op, and an empty value removes the property).
99
+ `getPropertyValue` / `getPropertyPriority` read it back, `removeProperty`
100
+ returns the previous value, `cssText` gets or replaces the whole block,
101
+ `style.length` / `style.item(i)` enumerate it, and an unset property answers
102
+ `""`.
103
+
104
+ `el.dataset` is a live `DOMStringMap` over the element's `data-*` attributes:
105
+ `dataset.fooBar` reads `data-foo-bar`, assignment writes the attribute, `delete`
106
+ removes it, and `Object.keys(dataset)` / `for...in` list only the data keys.
107
+
108
+ `getComputedStyle(el, pseudoElt)` answers from the **cascade** — the UA sheet,
109
+ author rules, inheritance and the inline declarations — rather than echoing the
110
+ inline block, so a stylesheet rule is visible. It exposes `getPropertyValue`,
111
+ `getPropertyPriority` and named properties (`cs.width`, `cs.backgroundColor`,
112
+ `cs["--accent"]`). Pass `"::before"`/`"::after"` to read generated content; any
113
+ other pseudo-element answers empty values. Every read flushes a pending restyle,
114
+ so a handle created early stays live after later mutations.
115
+
116
+ `getBoundingClientRect()` carries the legacy `top` / `right` / `bottom` / `left`
117
+ members next to `x` / `y` / `width` / `height`, and `document.title = …` writes
118
+ the document's `<title>` (creating the element, and a `<head>`, when the page
119
+ has none).
120
+
78
121
  Mutations mark the document dirty; the engine restyles + relayouts lazily (before
79
122
  the next read or frame). `inner = …`/`innerHTML = …` do **not** run scripts.
80
- Element events support capture and bubble phases, `stopPropagation`, `once`, and
81
- bubble up to `document`/`window`. The legacy `win.on(type, fn)` payload keeps its
82
- **string** `e.target` (`div#id.class`); the element `Event.target` is a handle
83
- whose descriptor matches the same string.
123
+ Element events support capture and bubble phases, `stopPropagation`, `once`,
124
+ `passive` and `signal` (an `AbortController`'s signal removes the listener), and
125
+ bubble up to `document`/`window`. `dispatchEvent` dispatches the same event
126
+ object and fills in `target`/`currentTarget`/`eventPhase`/`composedPath()`. The
127
+ legacy `win.on(type, fn)` payload keeps its **string** `e.target`
128
+ (`div#id.class`); the element `Event.target` is a handle whose descriptor
129
+ matches the same string.
130
+
131
+ `element.focus()` scrolls the element into view (pass `{ preventScroll: true }`
132
+ to keep the page offset), and `document.hasFocus()` reports the window state
133
+ toggled by `window.focus()` / `window.blur()`.
84
134
 
85
135
  ### Node lifetime
86
136
 
package/doc/gui/media.md CHANGED
@@ -112,6 +112,10 @@ platform media framework in the path either way.
112
112
  - **Threading.** The engine is single-threaded and not thread-aware, so all
113
113
  decoding happens on the main thread inside `xt_gui_media_tick` — no decode
114
114
  thread, no cross-thread GC exposure.
115
+ - **`decodeAudioData`.** The same FFmpeg build also decodes an in-memory audio
116
+ file for `AudioContext.decodeAudioData` (`runtime/ext_media/decode_audio.cpp`,
117
+ through the media table); WAV is handled by the GUI engine itself
118
+ (`audio_decode.cpp`), so only compressed formats need this extension.
115
119
  - **DOM API.** Element handles expose the media surface directly (see
116
120
  `runtime/ext_gui/dom_api_media.cpp` and the prototype in
117
121
  `dom_api_proto.cpp`): `play()` / `pause()` / `load()`, and the
package/doc/gui/webgl.md CHANGED
@@ -96,8 +96,7 @@ reflected offsets; `uniform1i` on a sampler stores the texture unit. The block
96
96
  is pushed with the dynamic uniform-buffer API on every draw.
97
97
 
98
98
  Limits of the translator: struct-typed uniforms, user uniform blocks, uniform
99
- arrays whose size is not resolved by the preprocessor, sampler arrays and cube
100
- maps are rejected (the link/shader info log says so). Shader code inside
99
+ arrays whose size is not resolved by the preprocessor, sampler arrays are rejected (the link/shader info log says so). Shader code inside
101
100
  `#ifdef` is fine; non-sampler uniform declarations are moved to the enclosing
102
101
  top-level position so the block stays unconditional.
103
102
 
@@ -115,8 +114,11 @@ available from GLSL ES 3.00, which is what the instanced example uses.
115
114
 
116
115
  - **Context**: `getContext("webgl")`, `canvas`, `drawingBufferWidth/Height`,
117
116
  `getParameter`, `getError`, `getSupportedExtensions`, `getExtension`,
118
- `getContextAttributes`, `isContextLost`, `getShaderPrecisionFormat`, and the
119
- stencil entry points (accepted; the depth target has no stencil attachment).
117
+ `getContextAttributes`, `isContextLost`, `getShaderPrecisionFormat`, and an
118
+ 8-bit stencil buffer (`getContextAttributes().stencil` is `true`,
119
+ `STENCIL_BITS` is 8). `WEBGL_lose_context`'s `loseContext`/`restoreContext`
120
+ really drop and rebuild the context (every object becomes invalid) and
121
+ dispatch `webglcontextlost`/`webglcontextrestored`.
120
122
  - **Shaders and programs**: `createShader`, `shaderSource`, `compileShader`,
121
123
  `getShaderParameter`, `getShaderInfoLog`, `getShaderSource`, `deleteShader`,
122
124
  `createProgram`, `attachShader`, `detachShader`, `linkProgram`,
@@ -131,7 +133,9 @@ available from GLSL ES 3.00, which is what the instanced example uses.
131
133
  `bufferSubData`, `deleteBuffer`, `getBufferParameter`,
132
134
  `enableVertexAttribArray`, `disableVertexAttribArray`,
133
135
  `vertexAttribPointer`, `vertexAttrib1f`…`4f` / `1fv`…`4fv`,
134
- `getVertexAttrib`.
136
+ `getVertexAttrib`. The `usage` hint from `bufferData`
137
+ (`STREAM_DRAW`/`STATIC_DRAW`/`DYNAMIC_DRAW`) is recorded and reported by
138
+ `getBufferParameter(…, BUFFER_USAGE)`.
135
139
  - **Drawing**: `drawArrays`, `drawElements` (UNSIGNED_BYTE, UNSIGNED_SHORT,
136
140
  UNSIGNED_INT; `TRIANGLE_FAN` and `LINE_LOOP` are expanded to lists).
137
141
  `POINTS`, `LINES`, `LINE_STRIP`, `TRIANGLES` and `TRIANGLE_STRIP` map to the
@@ -140,22 +144,50 @@ available from GLSL ES 3.00, which is what the instanced example uses.
140
144
  `texParameteri`/`texParameterf`, `getTexParameter`, `texImage2D` (raw pixels,
141
145
  `<img>`, `<canvas>` and `ImageData` sources), `texSubImage2D`,
142
146
  `generateMipmap`, `deleteTexture`. `UNPACK_FLIP_Y_WEBGL` and
143
- `UNPACK_PREMULTIPLY_ALPHA_WEBGL` are honoured. Everything is converted to
144
- RGBA8; `RGBA`, `RGB`, `LUMINANCE`, `ALPHA` and `LUMINANCE_ALPHA` with
145
- `UNSIGNED_BYTE` are accepted.
146
- - **Framebuffers**: `createFramebuffer`, `bindFramebuffer`,
147
- `framebufferTexture2D` (colour attachment), `checkFramebufferStatus`,
148
- `createRenderbuffer`, `bindRenderbuffer`, `renderbufferStorage` (depth),
149
- `framebufferRenderbuffer`, `delete*`. This is enough for render-to-texture,
150
- shadow maps and post-processing passes.
147
+ `UNPACK_PREMULTIPLY_ALPHA_WEBGL` are honoured. By default source data is
148
+ converted to RGBA8; `RGBA`, `RGB`, `LUMINANCE`, `ALPHA` and
149
+ `LUMINANCE_ALPHA` with `UNSIGNED_BYTE` are accepted. Float textures
150
+ (`RGBA32F`, `RGBA16F`, `RGB32F`, `RGB16F`, `RG32F`/`RG16F`, `R32F`/`R16F`
151
+ with `FLOAT` or `HALF_FLOAT` data, plus WebGL 1's
152
+ `OES_texture_float`/`OES_texture_half_float`) map to SDL's RGBA32F/RGBA16F,
153
+ and depth textures (`DEPTH_COMPONENT16/24/32F`, `DEPTH_STENCIL`,
154
+ `DEPTH24_STENCIL8`) map to `D32_FLOAT`/`D32_FLOAT_S8_UINT` and can be sampled
155
+ or used as depth attachments. Compressed textures upload through
156
+ `compressedTexImage2D`/`compressedTexSubImage2D` with
157
+ `WEBGL_compressed_texture_s3tc` (BC1/BC2/BC3),
158
+ `WEBGL_compressed_texture_s3tc_srgb` and `WEBGL_compressed_texture_astc`;
159
+ each extension is published only when the device can sample its formats.
160
+ `texImage2D` also accepts the latest decoded `<video>` frame and an
161
+ `ImageBitmap` produced by `createImageBitmap`. Cube maps
162
+ (`bindTexture(TEXTURE_CUBE_MAP)` plus six `texImage2D` faces) and 3D/array
163
+ textures (`texImage3D`/`texStorage3D`) are sampled through `samplerCube`,
164
+ `sampler3D` and `sampler2DArray`; `texSubImage3D` updates a sub-volume and
165
+ `framebufferTextureLayer` renders one array/cube layer as a colour
166
+ attachment.
167
+ - **Framebuffers**: `createFramebuffer`, `bindFramebuffer`
168
+ (`FRAMEBUFFER`/`READ_FRAMEBUFFER`/`DRAW_FRAMEBUFFER`),
169
+ `framebufferTexture2D` (`COLOR_ATTACHMENT0`, `DEPTH_ATTACHMENT`,
170
+ `DEPTH_STENCIL_ATTACHMENT`), `checkFramebufferStatus`, `createRenderbuffer`,
171
+ `bindRenderbuffer`, `renderbufferStorage` (depth or depth-stencil),
172
+ `framebufferRenderbuffer`, `delete*`, and `blitFramebuffer` (colour
173
+ attachment; depth/stencil bits are accepted but not copied). This is enough
174
+ for render-to-texture, shadow maps and post-processing passes.
151
175
  - **State**: `enable`/`disable`/`isEnabled` for `BLEND`, `DEPTH_TEST`,
152
- `CULL_FACE`, `SCISSOR_TEST` (stencil accepted but not applied),
153
- `clearColor`/`clearDepth`/`clearStencil`, `clear`, `blendFunc`/
154
- `blendFuncSeparate`, `blendEquation`/`blendEquationSeparate`, `depthFunc`,
155
- `depthMask`, `colorMask`, `cullFace`, `frontFace`, `viewport`, `scissor`,
156
- `pixelStorei`, `finish`.
176
+ `CULL_FACE`, `SCISSOR_TEST` and `STENCIL_TEST`,
177
+ `clearColor`/`clearDepth`/`clearStencil`, `clear` (with `SCISSOR_TEST` it
178
+ clears only the scissor rectangle, for colour, depth and stencil), `blendColor`,
179
+ `blendFunc`/`blendFuncSeparate`,
180
+ `blendEquation`/`blendEquationSeparate`, `depthFunc`,
181
+ `depthMask`, `colorMask`, `stencilFunc`/`stencilFuncSeparate`,
182
+ `stencilOp`/`stencilOpSeparate`, `stencilMask`/`stencilMaskSeparate`,
183
+ `cullFace`, `frontFace`, `viewport`, `scissor`,
184
+ `pixelStorei`, `finish`. The stencil test takes part in rasterisation and
185
+ `clear(STENCIL_BUFFER_BIT)` clears the stencil attachment; SDL_GPU exposes
186
+ one reference and one compare/write mask for both faces, so a front/back
187
+ pair that differs uses the front value.
157
188
  - **Readback**: `readPixels` (RGBA/UNSIGNED_BYTE) from the default framebuffer
158
- or a bound framebuffer. It blocks until the GPU finishes; use it for tests and
189
+ or a bound RGBA8 framebuffer; a float/depth attachment is sampled into an
190
+ RGBA8 target first. It blocks until the GPU finishes; use it for tests and
159
191
  screenshots, not per frame.
160
192
 
161
193
  WebGL 2 adds:
@@ -174,38 +206,46 @@ WebGL 2 adds:
174
206
  texture's own filter/wrap state for that unit.
175
207
  - **Immutable textures and buffer copies**: `texStorage2D` (the backend always
176
208
  allocates the full mip chain), `copyBufferSubData`, `getBufferSubData`.
209
+ - **Drawing buffer**: `drawingBufferStorage(width, height)` fixes the drawing
210
+ buffer size; `canvas.width`/`canvas.height` no longer rebuild it afterwards.
177
211
  - **Clears**: `clearBufferfv`, `clearBufferiv`, `clearBufferuiv`,
178
212
  `clearBufferfi`.
213
+ - **Multiple render targets**: `drawBuffers` addresses up to four colour
214
+ attachments and `readBuffer` selects the attachment `readPixels` reads;
215
+ `framebufferTexture2D` accepts `COLOR_ATTACHMENT0`…`3`, and a fragment
216
+ shader's multiple `out` variables are written in order.
179
217
  - **Queries and fences**: the objects and entry points exist
180
218
  (`createQuery`/`beginQuery`/`endQuery`/`getQueryParameter`,
181
219
  `fenceSync`/`clientWaitSync`/`waitSync`/`getSyncParameter`) and degrade
182
220
  gracefully: a fence reports `ALREADY_SIGNALED` and query results are 0.
183
- - **Uniform blocks**: `getUniformBlockIndex` answers `INVALID_INDEX` so a
184
- library falls back to individual uniforms; the block entry points are no-ops.
185
- - **Transform feedback, 3D textures and compressed formats**: the entry points
186
- are present as no-ops so feature-probing libraries do not throw.
221
+ - **Uniform blocks**: `getUniformBlockIndex`,
222
+ `getActiveUniformBlockParameter`, `getActiveUniformBlockName`,
223
+ `uniformBlockBinding`, `bindBufferBase`/`bindBufferRange` and
224
+ `getIndexedParameter`. Members are merged into the engine's single std140
225
+ block and copied from the bound buffer at draw time; scalars, vectors,
226
+ matrices and fixed-size arrays work, nested structs, sampler members and
227
+ blocks without an instance name are rejected.
228
+ - **Transform feedback**: the entry points are present as no-ops so
229
+ feature-probing libraries do not throw.
187
230
 
188
231
  Textures are created with a full mip chain; sampling clamps to level 0 until
189
232
  `generateMipmap` runs. Matrix vertex attributes consume the right number of
190
233
  consecutive locations and read one column per location. A disabled attribute
191
234
  is supplied from the last `vertexAttrib*f` value through a small constant
192
- buffer.
235
+ buffer. A vertex shader can sample textures just like a fragment shader:
236
+ `MAX_VERTEX_TEXTURE_IMAGE_UNITS` reports 16, vertex samplers live in set 0 and
237
+ are bound with `SDL_BindGPUVertexSamplers` on every draw. Depth values use the
238
+ backend's `[0, 1]` clip space rather than GL's `[-1, 1] → [0, 1]` remap:
239
+ `gl_Position.z = 0.5` stores depth `0.5` (real WebGL stores `0.75`).
193
240
 
194
241
  ## Not implemented
195
242
 
196
- Uniform buffer objects in shader source, transform feedback (the entry points
197
- are no-ops), cube maps, 3D/array textures, float/half textures, depth textures,
198
- compressed textures, multiple render targets (only the first fragment output is
199
- written), `blitFramebuffer`, MSAA/sample buffers, stencil operations (state is
200
- accepted, the stencil buffer is not written), constant blend colours,
201
- `drawingBufferStorage`, context loss, `bufferData` with a `usage` other than
202
- static/dynamic (the hint is ignored), `texImage2D` for
203
- `HTMLVideoElement`/`ImageBitmap` and scissored `clear` (a clear clears the whole
204
- attachment). Shaders that use built-in blocks, user uniform blocks or
205
- `gl_FragDepth` beyond the supported subset are rejected with a compile/link log.
206
-
207
- The engine advertises zero vertex texture units, so a well-behaved WebGL 1
208
- program will not try to sample a texture in a vertex shader.
243
+ Transform feedback (the entry points are no-ops) and MSAA/sample buffers
244
+ (SDL_GPU's Metal multisample resolve returns undefined pixels with scissor
245
+ tests and instanced draws, so contexts stay single-sampled). `blitFramebuffer`
246
+ copies the colour attachment only; depth/stencil bits are accepted but not
247
+ copied. Shaders that use built-in blocks or `gl_FragDepth` are rejected with a
248
+ compile/link log.
209
249
 
210
250
  ## Platform
211
251
 
@@ -225,10 +265,27 @@ uses the 2D canvas or the DOM pays no run-time cost beyond the archive size.
225
265
  ## Testing
226
266
 
227
267
  - `tests/e2e/gui-webgl.test.ts` compiles GLSL, draws a textured and a
228
- depth-tested quad, checks face culling and reads the pixels back.
229
- - `tests/e2e/gui-webgl2.test.ts` covers the WebGL 2 additions: `#version 300 es`
230
- shaders, a VAO round trip, instanced draws with a divisor, `texStorage2D`,
231
- `clearBufferfv`, buffer copies, sampler objects and struct-uniform fields.
268
+ depth-tested quad, checks face culling and reads the pixels back; it also
269
+ covers blend constants, dynamic buffer usage, vertex-shader texture sampling,
270
+ `gl_FragDepth` diagnostics, context loss/restore and cube maps.
271
+ - `tests/e2e/gui-webgl2.test.ts` covers the WebGL 2 shader/state additions:
272
+ `#version 300 es` shaders, a VAO round trip, instanced draws with a divisor,
273
+ `texStorage2D`, `clearBufferfv`, buffer copies, sampler objects, struct-uniform
274
+ fields and `drawingBufferStorage`.
275
+ - `tests/e2e/gui-webgl2-formats.test.ts` covers stencil, float/depth textures,
276
+ compressed textures and `ImageBitmap`.
277
+ - `tests/e2e/gui-webgl2-blit.test.ts` covers scissored colour/depth clears,
278
+ framebuffer blits, and the pixels around a partial blit that must survive
279
+ (a destination load-op that discards them fails there on Metal).
280
+ - `tests/e2e/gui-media.test.ts` also uploads the latest decoded `<video>` frame
281
+ as a texture and reads it back.
282
+ - `tests/e2e/gui-webgl2-3d.test.ts` covers 2D-array and 3D texture layer
283
+ sampling, `texSubImage3D` sub-volume updates and rendering to a colour
284
+ attachment layer.
285
+ - `tests/e2e/gui-webgl2-mrt.test.ts` writes two fragment outputs to two
286
+ colour attachments and reads each back.
287
+ - `tests/e2e/gui-webgl2-ubo.test.ts` binds a std140 block, updates its members
288
+ and verifies the drawn colour.
232
289
  - `examples/gui/webgl-cube` is a spinning, lit, textured cube; it runs in a
233
290
  browser unchanged and is covered by
234
291
  `tests/e2e/gui-example-webgl.test.ts`.
@@ -249,8 +306,10 @@ is bundled any more**. Verified with three.js r160 through SDL_GPU:
249
306
  `CanvasTexture`, linear fog, ambient/directional/point/spot/hemisphere lights,
250
307
  `Sprite`, `Line`, `Points` and `InstancedMesh`.
251
308
 
252
- Features outside this engine's subset still cannot be emulated: shadow maps
253
- (depth textures), cube textures/environment maps, HDR/float render targets,
254
- multiple render targets, transform feedback and compressed textures. A scene
255
- that needs one of those will not render correctly; everything the previous
256
- Canvas2D fallback could show now goes through the GPU.
309
+ Features outside this engine's subset still cannot be emulated: transform
310
+ feedback and MSAA.
311
+ Depth textures, float render targets and compressed textures are available, but
312
+ three.js shadow maps still need comparative `sampler2DShadow` sampling, which
313
+ is not translated yet. A scene that needs one of those will not render
314
+ correctly; everything the previous Canvas2D fallback could show now goes
315
+ through the GPU.