xbintsc 0.5.31 → 0.5.32

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 (181) hide show
  1. package/README.md +16 -10
  2. package/dist/src/binder/references.js +14 -1
  3. package/dist/src/binder/references.js.map +1 -1
  4. package/dist/src/cli/help.d.ts +1 -1
  5. package/dist/src/codegen/generator/module.d.ts +1 -0
  6. package/dist/src/codegen/generator/module.js +54 -0
  7. package/dist/src/codegen/generator/module.js.map +1 -1
  8. package/dist/src/codegen/generator/statements/variables.js +6 -4
  9. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  10. package/dist/src/driver/version.d.ts +1 -1
  11. package/dist/src/driver/version.js +1 -1
  12. package/dist/src/extensions/gui/css.d.ts +26 -0
  13. package/dist/src/extensions/gui/css.js +100 -0
  14. package/dist/src/extensions/gui/css.js.map +1 -0
  15. package/dist/src/extensions/gui/html.js +9 -2
  16. package/dist/src/extensions/gui/html.js.map +1 -1
  17. package/dist/src/extensions/gui/index.js +16 -2
  18. package/dist/src/extensions/gui/index.js.map +1 -1
  19. package/dist/src/extensions/gui/three.d.ts +16 -0
  20. package/dist/src/extensions/gui/three.js +22 -0
  21. package/dist/src/extensions/gui/three.js.map +1 -0
  22. package/dist/src/extensions/media/index.d.ts +6 -5
  23. package/dist/src/extensions/media/index.js +12 -14
  24. package/dist/src/extensions/media/index.js.map +1 -1
  25. package/dist/tests/e2e/compile-builtins.test.js +24 -0
  26. package/dist/tests/e2e/compile-builtins.test.js.map +1 -1
  27. package/dist/tests/e2e/compile-objects-classes.test.js +23 -0
  28. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  29. package/dist/tests/e2e/gui-browser.test.js +1 -1
  30. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  31. package/dist/tests/e2e/gui-effects.test.d.ts +6 -0
  32. package/dist/tests/e2e/gui-effects.test.js +262 -0
  33. package/dist/tests/e2e/gui-effects.test.js.map +1 -0
  34. package/dist/tests/e2e/gui-example-webgl.test.d.ts +10 -0
  35. package/dist/tests/e2e/gui-example-webgl.test.js +75 -0
  36. package/dist/tests/e2e/gui-example-webgl.test.js.map +1 -0
  37. package/dist/tests/e2e/gui-example-webgl2.test.d.ts +8 -0
  38. package/dist/tests/e2e/gui-example-webgl2.test.js +72 -0
  39. package/dist/tests/e2e/gui-example-webgl2.test.js.map +1 -0
  40. package/dist/tests/e2e/gui-layout.test.js +48 -0
  41. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  42. package/dist/tests/e2e/gui-media.test.js +5 -4
  43. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  44. package/dist/tests/e2e/gui-webgl.test.d.ts +11 -0
  45. package/dist/tests/e2e/gui-webgl.test.js +179 -0
  46. package/dist/tests/e2e/gui-webgl.test.js.map +1 -0
  47. package/dist/tests/e2e/gui-webgl2.test.d.ts +10 -0
  48. package/dist/tests/e2e/gui-webgl2.test.js +238 -0
  49. package/dist/tests/e2e/gui-webgl2.test.js.map +1 -0
  50. package/dist/tests/extensions/gui.test.js +41 -0
  51. package/dist/tests/extensions/gui.test.js.map +1 -1
  52. package/dist/tests/extensions/media.test.js +7 -3
  53. package/dist/tests/extensions/media.test.js.map +1 -1
  54. package/doc/ai/extensions.md +29 -9
  55. package/doc/ai/troubleshooting.md +12 -8
  56. package/doc/ai/zh-CN/extensions.md +26 -8
  57. package/doc/ai/zh-CN/troubleshooting.md +10 -6
  58. package/doc/gui/api.md +3 -2
  59. package/doc/gui/build.md +72 -16
  60. package/doc/gui/canvas.md +11 -3
  61. package/doc/gui/dom.md +33 -18
  62. package/doc/gui/known-issues.md +57 -24
  63. package/doc/gui/layout.md +18 -0
  64. package/doc/gui/media.md +17 -13
  65. package/doc/gui/rendering.md +50 -3
  66. package/doc/gui/webgl.md +256 -0
  67. package/doc/gui.md +108 -17
  68. package/doc/npm-package.md +6 -4
  69. package/doc/requirements.md +18 -9
  70. package/doc/zh-CN/gui/api.md +3 -2
  71. package/doc/zh-CN/gui/build.md +64 -15
  72. package/doc/zh-CN/gui/canvas.md +9 -2
  73. package/doc/zh-CN/gui/dom.md +20 -7
  74. package/doc/zh-CN/gui/known-issues.md +45 -19
  75. package/doc/zh-CN/gui/layout.md +14 -0
  76. package/doc/zh-CN/gui/media.md +15 -11
  77. package/doc/zh-CN/gui/rendering.md +41 -4
  78. package/doc/zh-CN/gui/webgl.md +115 -0
  79. package/doc/zh-CN/gui.md +89 -14
  80. package/doc/zh-CN/npm-package.md +9 -6
  81. package/doc/zh-CN/requirements.md +12 -6
  82. package/llms.txt +2 -0
  83. package/package.json +1 -1
  84. package/runtime/ext_gui/browser.cpp +39 -13
  85. package/runtime/ext_gui/canvas.cpp +3 -18
  86. package/runtime/ext_gui/canvas_internal.h +10 -0
  87. package/runtime/ext_gui/canvas_raster.cpp +60 -0
  88. package/runtime/ext_gui/clip_values.cpp +246 -0
  89. package/runtime/ext_gui/css.cpp +94 -0
  90. package/runtime/ext_gui/css.h +22 -1
  91. package/runtime/ext_gui/css_match.cpp +3 -0
  92. package/runtime/ext_gui/css_selector.cpp +9 -0
  93. package/runtime/ext_gui/css_sheet.cpp +89 -45
  94. package/runtime/ext_gui/document.cpp +4 -1
  95. package/runtime/ext_gui/document.h +2 -0
  96. package/runtime/ext_gui/dom.h +16 -0
  97. package/runtime/ext_gui/dom_api.cpp +1 -0
  98. package/runtime/ext_gui/dom_api_canvas.cpp +17 -80
  99. package/runtime/ext_gui/dom_api_canvas_extra.cpp +130 -1
  100. package/runtime/ext_gui/dom_api_canvas_pending.cpp +41 -0
  101. package/runtime/ext_gui/dom_api_internal.h +259 -0
  102. package/runtime/ext_gui/dom_api_webgl.cpp +376 -0
  103. package/runtime/ext_gui/dom_api_webgl2.cpp +434 -0
  104. package/runtime/ext_gui/dom_api_webgl2_draw.cpp +269 -0
  105. package/runtime/ext_gui/dom_api_webgl_buffer.cpp +212 -0
  106. package/runtime/ext_gui/dom_api_webgl_rt.h +30 -0
  107. package/runtime/ext_gui/dom_api_webgl_shader.cpp +443 -0
  108. package/runtime/ext_gui/dom_api_webgl_state.cpp +444 -0
  109. package/runtime/ext_gui/dom_api_webgl_texture.cpp +445 -0
  110. package/runtime/ext_gui/effect_values.cpp +312 -0
  111. package/runtime/ext_gui/gradient_values.cpp +476 -0
  112. package/runtime/ext_gui/grid_values.cpp +230 -0
  113. package/runtime/ext_gui/gui_canvas.cpp +50 -2
  114. package/runtime/ext_gui/gui_engine.h +27 -0
  115. package/runtime/ext_gui/gui_events.cpp +4 -1
  116. package/runtime/ext_gui/gui_media.cpp +1 -1
  117. package/runtime/ext_gui/gui_webgl.cpp +83 -0
  118. package/runtime/ext_gui/layout.cpp +21 -0
  119. package/runtime/ext_gui/layout_flow.cpp +81 -4
  120. package/runtime/ext_gui/layout_flow_internal.h +31 -0
  121. package/runtime/ext_gui/layout_grid.cpp +374 -0
  122. package/runtime/ext_gui/media.h +1 -1
  123. package/runtime/ext_gui/media_api.h +2 -2
  124. package/runtime/ext_gui/media_query.cpp +211 -0
  125. package/runtime/ext_gui/paint.cpp +111 -77
  126. package/runtime/ext_gui/paint.h +33 -3
  127. package/runtime/ext_gui/paint_background.cpp +229 -0
  128. package/runtime/ext_gui/paint_clip.cpp +268 -0
  129. package/runtime/ext_gui/paint_effects.cpp +324 -0
  130. package/runtime/ext_gui/paint_internal.h +85 -0
  131. package/runtime/ext_gui/renderer.cpp +8 -31
  132. package/runtime/ext_gui/renderer_clip.cpp +196 -0
  133. package/runtime/ext_gui/renderer_image.cpp +13 -8
  134. package/runtime/ext_gui/renderer_internal.h +15 -8
  135. package/runtime/ext_gui/renderer_text.cpp +6 -31
  136. package/runtime/ext_gui/style.cpp +343 -20
  137. package/runtime/ext_gui/style.h +144 -3
  138. package/runtime/ext_gui/style_print.cpp +134 -1
  139. package/runtime/ext_gui/style_values.cpp +152 -126
  140. package/runtime/ext_gui/style_values_internal.h +35 -0
  141. package/runtime/ext_gui/webgl.h +466 -0
  142. package/runtime/ext_gui/webgl2_constants.h +274 -0
  143. package/runtime/ext_gui/webgl_buffer.cpp +565 -0
  144. package/runtime/ext_gui/webgl_constants.h +317 -0
  145. package/runtime/ext_gui/webgl_context.cpp +466 -0
  146. package/runtime/ext_gui/webgl_draw.cpp +495 -0
  147. package/runtime/ext_gui/webgl_glsl.h +125 -0
  148. package/runtime/ext_gui/webgl_glsl_compile.cpp +431 -0
  149. package/runtime/ext_gui/webgl_glsl_rewrite.cpp +349 -0
  150. package/runtime/ext_gui/webgl_glsl_scan.cpp +581 -0
  151. package/runtime/ext_gui/webgl_msl.cpp +62 -0
  152. package/runtime/ext_gui/webgl_msl.h +33 -0
  153. package/runtime/ext_gui/webgl_pipeline.cpp +274 -0
  154. package/runtime/ext_gui/webgl_program_internal.h +18 -0
  155. package/runtime/ext_gui/webgl_shader.cpp +582 -0
  156. package/runtime/ext_gui/webgl_texture.cpp +293 -0
  157. package/runtime/ext_gui/webgl_uniform.cpp +167 -0
  158. package/runtime/ext_gui/webgl_webgl2.cpp +224 -0
  159. package/runtime/ext_media/media.cpp +1 -1
  160. package/runtime/ext_media/media_bridge.c +9 -3
  161. package/runtime/rt_internal.h +1 -0
  162. package/runtime/xt_containers/access.inc +8 -0
  163. package/runtime/xt_regex/api.inc +2 -0
  164. package/runtime/xt_regex/matcher.inc +16 -3
  165. package/runtime/xt_regex/parser.inc +8 -2
  166. package/runtime/xt_regex/types.inc +3 -0
  167. package/runtime/xt_stdlib2/regexp-match.inc +60 -21
  168. package/runtime/xt_stdlib2/regexp.inc +1 -0
  169. package/runtime/xt_typed_array.c +1 -1
  170. package/scripts/build-gui.ts +90 -3
  171. package/scripts/build-media.ts +323 -58
  172. package/scripts/setup-vs-toolchain.ps1 +152 -0
  173. package/src/binder/references.ts +13 -1
  174. package/src/codegen/generator/module.ts +50 -0
  175. package/src/codegen/generator/statements/variables.ts +6 -3
  176. package/src/driver/version.ts +1 -1
  177. package/src/extensions/gui/css.ts +113 -0
  178. package/src/extensions/gui/html.ts +9 -2
  179. package/src/extensions/gui/index.ts +16 -2
  180. package/src/extensions/gui/three.ts +25 -0
  181. package/src/extensions/media/index.ts +13 -16
@@ -8,34 +8,60 @@
8
8
  (被 gitignore;按需获取)。媒体解码位于 `runtime/ext_media/`,单独构建。
9
9
  - `npm run gui`(`scripts/build-gui.ts`)获取固定版本的 SDL3(`SDL3_TAG`,
10
10
  默认 `release-3.2.10`),构建静态 SDL3,获取并构建静态 FreeType
11
- (`FREETYPE_VERSION`)与 HarfBuzz(`HARFBUZZ_VERSION`),编译引擎,并把
11
+ (`FREETYPE_VERSION`)与 HarfBuzz(`HARFBUZZ_VERSION`),并获取、构建静态
12
+ **glslang**(`GLSLANG_VERSION`,默认 `16.6.0`)、固定版本的 SPIR-V 头文件
13
+ (`SPIRV_HEADERS_VERSION`)以及 **SPIRV-Cross**(`SPIRV_CROSS_VERSION`,默认
14
+ `vulkan-sdk-1.4.313.0`)。glslang 是 WebGL 背后的运行时 GLSL ES 编译器,
15
+ SPIRV-Cross 把它的 SPIR-V 转成 Metal 后端使用的 MSL(见
16
+ [WebGL](./webgl.md)),两者都与其他库一样合并进归档。随后编译引擎,并把
12
17
  所有内容合并到 `runtime/lib/<os>-<arch>/gui.a`(Windows 上 MSVC ABI 则为
13
- `gui.lib`),与 `core.a` 采用相同约定,静态打包三者。合并使用 macOS 上的
18
+ `gui.lib`),与 `core.a` 采用相同约定,静态打包这些库。合并使用 macOS 上的
14
19
  `libtool` 与其他平台上的 `ar -M`(GNU/LLVM);CMake 归档会在构建根目录或
15
20
  `Release/` 下按任一生成器风格被发现。
16
21
  - `npm run media`(`scripts/build-media.ts`)构建可选的 **FFmpeg** 后端。它获取
17
22
  FFmpeg(`FFMPEG_VERSION`,默认 `7.1`),配置一个只启用媒体元素所需
18
23
  demuxer/parser/decoder 的最小静态构建,用它编译
19
24
  `runtime/ext_media/media.cpp`,并把所有内容合并到
20
- `runtime/lib/<os>-<arch>/media.a`。`gui.a` **不会**链接 FFmpeg:程序通过
21
- `--ext media` 显式启用播放能力。
25
+ `runtime/lib/<os>-<arch>/media.a`(Windows 上 MSVC ABI 则为 `media.lib`)。
26
+ `gui.a` **不会**链接 FFmpeg:程序通过 `--ext media` 显式启用播放能力。
22
27
  - `src/extensions/gui/index.ts` 通过 `nativeObjects()` 暴露引擎归档(经由
23
28
  `findRuntimeLibrary`,因此 `.a`/`.lib` 都可用),并通过 `linkerFlags()`
24
- 暴露操作系统框架。`src/extensions/media/index.ts` 对 `media.a` 做同样的暴露,
25
- 并把注册桥(`runtime/ext_media/media_bridge.c`)作为普通 runtime 源编译。
29
+ 暴露操作系统框架。`src/extensions/media/index.ts` 对 `media.a`/`media.lib`
30
+ 做同样的暴露,并把注册桥(`runtime/ext_media/media_bridge.c`)作为普通
31
+ runtime 源编译。
26
32
  - `scripts/build-gui.ts` 使用与驱动**相同**的 clang
27
33
  (`src/driver/toolchain-provider.ts`);Windows 上就是 Visual Studio 自带的、
28
34
  面向 MSVC 的 LLVM,并配套使用其同目录下的 `clang++`/`llvm-ar` 来配置 CMake、
29
35
  编译引擎和合并归档。脚本不提供 `gcc`/`g++` 回退,Windows 上也始终使用
30
36
  `llvm-ar`,因此归档不会混入其他目标文件格式或 C++ 运行时。Visual Studio
31
37
  工具链按绝对路径查找,无需开发者命令提示符。Windows 上 SDL_GPU 会选择 Vulkan
32
- 后端,不需要 DXIL/`dxc` 工具链。`scripts/build-media.ts` 对 FFmpeg 与播放器
33
- 使用同一套已解析的工具链。
34
- - CI 在组装发布归档之前构建 `gui.a` 与 `media.a`,因此它们会随
35
- `runtime/lib/<slug>/` 一起发布(发布 tarball 会复制整个 `runtime/` 树)。
38
+ 后端,不需要 DXIL/`dxc` 工具链。
39
+ - `scripts/build-media.ts` 使用同一套已解析的工具链。Windows 上它走 FFmpeg
40
+ 自带的 MSVC 模式:`configure --toolchain=msvc --cc=clang-cl` 把静态 FFmpeg
41
+ 编译为使用静态 MSVC CRT 的 COFF 对象,`clang++` 编译播放器,`llvm-ar`
42
+ 把 COFF 归档合并成 `media.lib`。编译器链完全来自 Visual Studio
43
+ (clang-cl、`cl.exe`、`lib.exe`、`llvm-ar`)。FFmpeg 的
44
+ configure 是 POSIX shell 脚本、Makefile 是 GNU make 格式,因此仍需要这两个
45
+ 构建驱动:shell 来自 Git for Windows 自带的 bash(克隆 SDL3 本来就需要
46
+ Git),make 是原生 GNU make(如 Chocolatey 的 `make`,或用 `xbintsc_MAKE`
47
+ 指定)。脚本会把 configure 写出的源码路径改写成盘符路径,并跳过依赖提取,
48
+ 因为原生 make 无法原样传递 MSVC `/showIncludes` 的 awk 管道。
49
+ `cl.exe`/`lib.exe` 只被 configure 的工具链探测使用,由脚本从 Visual Studio
50
+ 安装目录解析,不需要开发者命令提示符。`xbintsc_SHELL` 与 `xbintsc_MAKE`
51
+ 可覆盖自动发现的 shell/make。头文件与库由脚本复制到
52
+ `vendor/ffmpeg-build-msvc/dist`,而不是执行 `make install`。
53
+ - CI 在组装发布归档之前构建 `gui.a`/`gui.lib` 与 `media.a`/`media.lib`,因此它们
54
+ 会随 `runtime/lib/<slug>/` 一起发布(发布 tarball 会复制整个 `runtime/` 树)。
36
55
  归档在 Linux(Xvfb 下,使用 lavapipe 软件 Vulkan 驱动)与 macOS 上构建并运行
37
- 示例;Windows 为试验性,因为托管 runner 没有 GPU/Vulkan 驱动,且 media 归档
38
- 目前仅支持 POSIX。
56
+ 示例;Windows x64 也会构建两个归档并链接一个 `gui,media` 程序,因此 MSVC
57
+ 构建在 CI 中得到验证,只是*运行*示例仍为试验性(托管 runner 没有 GPU/Vulkan
58
+ 驱动)。Windows arm64 仍跳过两个归档(FFmpeg 的 MSVC arm64 构建尚未验证)。
59
+ - Windows CI 通过 `.github/actions/vs-toolchain` →
60
+ `scripts/setup-vs-toolchain.ps1` 使用与本地构建相同的 Visual Studio
61
+ `vcvarsall` 环境和自带的 LLVM(不再单独安装 Chocolatey LLVM)。媒体构建时该
62
+ action 还会通过 Chocolatey 安装 GNU make 并把 `xbintsc_MAKE` 指向它;FFmpeg
63
+ 源码与已安装的 `vendor/ffmpeg-build-msvc/dist` 前缀按脚本哈希缓存。
64
+ configure/make 构建树本身不缓存(其中记录了另一台机器的绝对路径与编译选项)。
39
65
  - 现有的缓存指纹已经会对 `nativeObjects()` 的内容与 `runtimeSources()` 的内容
40
66
  做哈希,因此重建任一归档都会自动使缓存二进制失效。
41
67
  - CMake 构建树位于其源码**旁边**(`vendor/sdl-build`、`vendor/freetype-build`、
@@ -57,6 +83,26 @@ npm run media # 可选:构建 media.a(首次会获取并构建 FFmpeg
57
83
  xbintsc run examples/gui/hello.ts --ext gui
58
84
  ```
59
85
 
86
+ Windows 上同样的命令在普通 PowerShell 中即可产出 `gui.lib` 与 `media.lib`,
87
+ 不需要开发者命令提示符。Visual Studio 安装提供面向 MSVC 的 `clang++`、
88
+ `clang-cl`、`cl.exe` 与 `lib.exe`;`npm run media` 还需要 POSIX shell 与
89
+ GNU make 来运行 FFmpeg 的 `configure`:shell 来自 Git for Windows(克隆
90
+ SDL3 本来就需要 Git),GNU make 使用原生二进制——可 `choco install make`,
91
+ 或用 `xbintsc_MAKE` 指定。脚本会指出缺失的工具,并支持
92
+ `xbintsc_SHELL` / `xbintsc_MAKE` 覆盖。
93
+
94
+ 如果需要显式选择这套工具链(例如在全新终端中,或想与 CI 使用同一个编译器),
95
+ 运行:
96
+
97
+ ```powershell
98
+ pwsh -File scripts/setup-vs-toolchain.ps1
99
+ ```
100
+
101
+ 它会导入 `vcvarsall`(MSVC、Windows SDK、MSBuild),并把 `xbintsc_CLANG` 指向
102
+ 该 Visual Studio 安装自带的 LLVM。GitHub Actions 通过
103
+ `.github/actions/vs-toolchain` 调用同一个脚本,因此 CI 不再单独安装
104
+ Chocolatey/独立 LLVM,CI、本地构建与预编译归档共用同一条编译链。
105
+
60
106
  设置 `XT_GUI_AUTOCLOSE_MS=<n>` 可在 `n` 毫秒后关闭所有窗口,e2e 测试
61
107
  (`tests/e2e/gui-*.test.ts`)用它来无头运行。
62
108
 
@@ -72,11 +118,13 @@ xbintsc run examples/gui/hello.ts --ext gui
72
118
 
73
119
  这些测试套件在 Windows 上默认跳过(归档可以在本机通过 Vulkan 运行,但 CI
74
120
  runner 没有可用的 GPU 驱动)。如果本机有 `gui.lib` 与可用的 GPU,可用
75
- `xbintsc_GUI_TESTS=1` 选择启用:
121
+ `xbintsc_GUI_TESTS=1` 选择启用;如果还运行过 `npm run media`,媒体套件也会在
122
+ Windows 上走真实的解码路径:
76
123
 
77
124
  ```bat
78
125
  set xbintsc_GUI_TESTS=1
79
126
  npx vitest run tests/e2e/gui-layout.test.ts tests/e2e/gui-example.test.ts
127
+ npx vitest run tests/e2e/gui-media.test.ts
80
128
  ```
81
129
 
82
130
  在无头 Linux 机器上,安装 SDL3 构建头文件,并在 Xvfb 与软件 Vulkan 驱动下
@@ -90,8 +138,9 @@ xvfb-run -a --server-args="-screen 0 1280x720x24" \
90
138
  npx tsx src/cli/main.ts run examples/gui/hello.ts --ext gui
91
139
  ```
92
140
 
93
- 媒体 e2e 套件(`tests/e2e/gui-media.test.ts`)只有在 `gui.a` 与 `media.a` 都
94
- 存在时才会运行;`tests/e2e/gui-media-disabled.test.ts` 覆盖仅 `gui.a` 的构建。
141
+ 媒体 e2e 套件(`tests/e2e/gui-media.test.ts`)只有在 `gui.a`/`gui.lib` 与
142
+ `media.a`/`media.lib` 都存在时才会运行;`tests/e2e/gui-media-disabled.test.ts`
143
+ 覆盖仅 GUI 归档的构建。
95
144
 
96
145
  包清单集中在 `scripts/install-gui-deps.sh`(`apt-get install -y clang cmake
97
146
  libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxi-dev libxinerama-dev
@@ -19,9 +19,14 @@ CSS 盒中。
19
19
 
20
20
  ```ts
21
21
  const canvas: any = document.getElementById("c");
22
- const ctx = canvas.getContext("2d"); // 其他上下文类型返回 null
22
+ const ctx = canvas.getContext("2d"); // 或 "webgl" 取得 GPU 上下文
23
23
  ```
24
24
 
25
+ 一个 canvas 只有一种上下文。`getContext("webgl")` 或 `getContext("webgl2")`
26
+ 返回 WebGL 上下文(见 [WebGL](./webgl.md));一旦 canvas 有了 2D 上下文,
27
+ 再申请任一 WebGL 类型都会返回 `null`,反之亦然。从未申请过上下文的 canvas
28
+ 保持透明。
29
+
25
30
  `getContext("2d")` 返回一个上下文句柄,其方法与访问器位于共享原型上。该句柄会缓存在
26
31
  元素上,因此每次都返回**同一个**对象,绘制状态(变换、样式、路径)按 canvas 而非按调用
27
32
  保存。`ctx.canvas` 是所属元素的句柄。
@@ -81,7 +86,9 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
81
86
  而非生成轮廓。
82
87
 
83
88
  图像:`drawImage(source, …)` 支持 3 参数(`dx, dy`)、5 参数(`dx, dy, dw, dh`)与
84
- 9 参数(`dx, dy, dw, dh, sx, sy, sw, sh`)形式。`source` 可以是 `<img>` 元素(经图像
89
+ 9 参数(本引擎为**目标优先**:`dx, dy, dw, dh, sx, sy, sw, sh`)。完整的仿射变换
90
+ (旋转/斜切/缩放)会被应用:目标矩形变为平行四边形,每个设备像素反查到源矩形
91
+ (最近邻采样)。`source` 可以是 `<img>` 元素(经图像
85
92
  模块解码)或另一个 `<canvas>` 元素(取其当前位图)。采样为最近邻,因此把小图大幅放大
86
93
  会有块状感——适合图标与像素画,不适合照片管线。
87
94
 
@@ -12,12 +12,14 @@ doctype 跳过,实体解码(命名 + 十进制/十六进制),空元素
12
12
  (`<style>`/`<script>`),以及常见的隐式闭合规则(`li`、`dt`/`dd`、`option`、
13
13
  `p`、标题、表格单元格/行)。
14
14
 
15
- **CSS 解析器**(`runtime/ext_gui/css.{h,cpp}`):注释与 at-rule 暂时跳过,
16
- 支持多选择器的规则以及声明。选择器:类型、`.class`、`#id`、属性
15
+ **CSS 解析器**(`runtime/ext_gui/css.{h,cpp}`):会剥离注释;`@media` 块递归解析
16
+ (嵌套查询用 `and` 合并,逗号列表会分配到内层查询),其他 at-rule 跳过。规则支持
17
+ 多选择器与声明。选择器:类型、`.class`、`#id`、属性
17
18
  (`=`、`~=`、`|=`、`^=`、`$=`、`*=`)、四种组合器(后代、子、相邻兄弟与
18
- 通用兄弟)以及伪类 `:first-child`、`:last-child`、`:only-child`、`:empty`、
19
- `:root`、`:not(...)`、`:nth-child(an+b)`、`:disabled`、`:checked`,再加上
20
- 有状态的 `:hover` 与 `:focus`(见[输入](./input.md))。值:长度
19
+ 通用兄弟)、伪类 `:first-child`、`:last-child`、`:only-child`、`:empty`、
20
+ `:root`、`:not(...)`、`:nth-child(an+b)`、`:disabled`、`:checked`、有状态的
21
+ `:hover` 与 `:focus`(见[输入](./input.md)),以及伪元素
22
+ `::before`/`::after`(也接受旧式 `:before`/`:after`)。值:长度
21
23
  (`px`、`%`、`em`、`rem`、`vw`、`vh`、`pt`、`pc`、`in`、`cm`、`mm`、`q`)、
22
24
  颜色(十六进制、`rgb()`/`rgba()`、一个命名子集)、数字、关键字与简写
23
25
  (`margin`/`padding`/`border`/`flex`)。`transform`
@@ -25,14 +27,25 @@ doctype 跳过,实体解码(命名 + 十进制/十六进制),空元素
25
27
  `scale`/`scale3d`/`scaleX`/`scaleY`、`rotate`/`rotateZ`、`matrix`;`3d`
26
28
  拼写的 `z` 参数会被丢弃)与 `transform-origin`
27
29
  会被解析进计算样式:前者是操作列表,后者是两个(未解析的)原点长度;不支持的
28
- 函数会让整条声明失效(见[布局](./layout.md#变换m15))。
30
+ 函数会让整条声明失效(见[布局](./layout.md#变换m15))。M19 增加了
31
+ `background-image` 层列表(线性/径向)、`clip-path`
32
+ (`inset`/`circle`/`ellipse`/`polygon`)、`filter`/`backdrop-filter` 函数列表、
33
+ `text-shadow` 与 `content`。
29
34
 
30
35
  **层叠与计算样式**(`runtime/ext_gui/style.{h,cpp}`):一份小的内置 UA
31
36
  样式表,作者规则按 `(!important, 特异性, 源码顺序)` 排序,然后是内联
32
37
  `style=""`(特异性最高,但非 `!important` 的内联会输给 `!important`),
33
- 再加上文本属性的 CSS 继承。相对长度保持未解析状态直到布局阶段,`font-size`
38
+ 再加上文本属性的 CSS 继承。每次重算样式都会按当前视口过滤 `@media` 规则,
39
+ 因此调整窗口大小会切换它们。相对长度保持未解析状态直到布局阶段,`font-size`
34
40
  除外(相对*父级*字号解析)以及 `line-height`。
35
41
 
42
+ `::before`/`::after` 的样式在原始元素之后计算:选择器最后一个复合选择器必须写
43
+ 伪元素,其余部分对原始元素匹配(因此 `.card.info::before` 可用)。伪样式继承
44
+ 原始元素的计算样式,`content` 会被解析(字符串、转义、`attr()`、引号),
45
+ 生成的合成节点存放在每次重算样式时重建的 arena 中。布局把它的盒子插在真实子元素
46
+ 之前/之后;遍历、查询、`innerHTML` 与 `elementIndex()` 永远看不到它,命中测试
47
+ 则指向原始元素。
48
+
36
49
  ## 交互式 DOM(M8)
37
50
 
38
51
  `win.document` 返回文档句柄;元素句柄具有稳定标识,并提供可读写的属性、
@@ -6,12 +6,19 @@
6
6
 
7
7
  ## 已知问题
8
8
 
9
- - **没有 WebGL。** `getContext("webgl")` / `getContext("webgl2")` 返回 `null`,
10
- `WebGL*` / `WebGL2RenderingContext` / `XRWebGL*` 全局为 `undefined`,因此像 Three.js
11
- 场景这样的 WebGL 页面会以库自身的 “Error creating WebGL context” 失败,应当显示自己的
12
- 降级面板。只有 `getContext("2d")` 由光栅器支撑(见[画布](./canvas.md))。移植页面
13
- 的其余部分——模块图、DOM 句柄、事件、[API](./api.md) 中的浏览器全局、帧泵——都能工作,
14
- 所以 Three.js 程序**可以编译并启动**,只是无法绘制。
9
+ - **WebGL 1.0 与 2.0 运行在 GPU 上,但只实现了各自的子集。**
10
+ `canvas.getContext("webgl")` / `"webgl2"` 返回真正的 GPU 上下文(见
11
+ [WebGL](./webgl.md)):GLSL ES 着色器由内置 glslang 翻译为 SPIR-V——Vulkan
12
+ 后端直接消费,Metal 上再由内置 SPIRV-Cross 交叉编译为 MSL——绘图缓冲
13
+ 是一张离屏颜色纹理,页面直接合成而无须读回。WebGL 2 增加了 VAO、实例化、
14
+ 整数属性、采样器对象、`texStorage2D` 与缓冲拷贝。Uniform 块、transform
15
+ feedback、立方体贴图、3D 纹理、浮点/深度纹理、压缩纹理、多渲染目标、
16
+ `blitFramebuffer`、MSAA 与模板操作尚未实现;`clear` 会忽略 scissor 矩形。
17
+ 在两种格式都不接受的 SDL_GPU 后端(Direct3D 12,需要 DXIL)上两种上下文都为
18
+ `null`。`.js` 资源加载器现在只修补 three.js 的着色器循环展开;
19
+ `WebGLRenderer` 直接运行在真实后端上,不再有 Canvas2D 回退。阴影贴图
20
+ (深度纹理)、立方体贴图/环境贴图与 HDR 渲染目标仍未支持。
21
+
15
22
 
16
23
  - **自动关闭时的偶发崩溃。** 设置 `XT_GUI_AUTOCLOSE_MS` 结束运行时,约五次中有两次会以原生
17
24
  访问违例(`0xC0000005`)退出,其余运行与正常退出无异。该问题只在**动画帧循环运行中**复现
@@ -30,6 +37,18 @@
30
37
  `tests/e2e/gui-example-browser.test.ts` 中。
31
38
  - **`display: none -> flex` 的重排。** 通过类名切换让隐藏元素显示在 flex 容器上时,也曾观察
32
39
  到同样的崩溃;`examples/gui/pelican-bike` 改用 `opacity` 显示游戏结束遮罩。
40
+ - **CSS 覆盖范围是有意为之的子集。** 已实现 Grid、`clamp()`/`min()`/`max()`/`env()`、
41
+ `var()` 自定义属性、`inset` 简写、`box-sizing`、`visibility`/`pointer-events`、
42
+ `@media` 查询、生成内容(`::before`/`::after`)、线性/径向多层背景、
43
+ `text-shadow`、`clip-path`(`inset`/`circle`/`ellipse`/`polygon`)以及 `filter`
44
+ 的颜色函数。仍有限制:`mix-blend-mode`、`background-clip`、
45
+ `background-position`/`size`/`repeat`、重复渐变与 `backdrop-filter` 合成都未实现
46
+ (`backdrop-filter` 会解析并参与层叠,但采样背景需要离屏图层,因此不绘制);
47
+ `filter: blur()` 是按图形做九次偏移的近似,而不是真正的组模糊,重叠的子元素会加深;
48
+ `drop-shadow` 用盒子轮廓近似;最多只交叉两层嵌套 `clip-path`(更深时保留外侧两层);
49
+ 图像/画布除图像着色器能携带的亮度与透明度染色外不做颜色滤镜。Canvas 纹理使用
50
+ 最近邻采样。背景渐变仍是纯色带(线性 18 条、径向 32 个同心椭圆),柔和渐变可能
51
+ 出现轻微条带。
33
52
  - **SVG 是 NanoSVG 的子集。** SVG *文件*(`<img src="logo.svg">`)与内联
34
53
  `<svg>` 子树共用 vendored 的 NanoSVG 解析/光栅化器(`runtime/ext_gui/svg.cpp`、
35
54
  `inline_svg.cpp`),它会渲染 `path`/`rect`/`circle`/`ellipse`/`line`/
@@ -55,20 +74,27 @@
55
74
  相同的、面向 MSVC 的 clang(`PATH` 上没有更新版本时就是 Visual Studio 自带的
56
75
  LLVM)。
57
76
  - Windows:`scripts/build-gui.ts` 会生成 MSVC 兼容的 `gui.lib`
58
- (COFF 对象 + `ar -M`/`llvm-ar`),但尚未在 CI 中验证,因此 Windows 步骤
59
- 为试验性(job 保持绿色),`package` 会跳过它;在开发者机器上构建并运行
60
- `examples/gui/pelican-svg` 与 `examples/gui/pelican-bike` 是可以的。那里
61
- SDL_GPU 会选择 **Vulkan** 后端(`win.driver()` 返回 `"vulkan"`),因此归档
62
- 不需要 DXIL/`dxc` 工具链;托管 Windows runner 没有 GPU/Vulkan 驱动,这才是
63
- CI 运行仍为试验性的原因。
64
- - **Windows 上的媒体:** `media` 扩展目前仅支持 POSIX。GUI 引擎本身可在
65
- Windows 上构建,但没有该扩展时 `<video>`/`<audio>` 为空操作(打开失败并触发
66
- `error`),`--ext media` 会给出可操作的错误而不是继续链接。启用它需要
67
- MSVC 兼容的 FFmpeg 构建,推迟到 Windows GUI 路径本身验证之后。
68
- `<video controls>` 同样推迟——预期模式是 TS + CSS 叠层,如
69
- `examples/gui/media.ts`。
77
+ (COFF 对象 + `ar -M`/`llvm-ar`)。Windows x64 的 CI job 使用 Visual Studio
78
+ 工具链(`.github/actions/vs-toolchain` 导入 `vcvarsall` 并采用 VS 自带的
79
+ LLVM)构建它、链接一个 `gui,media` 程序,并随 x64 发布归档一起发布。示例在
80
+ 托管 runner 上的*运行*仍为试验性,因为那里没有 GPU/Vulkan 驱动(SDL_GPU 在
81
+ Windows 上选择 **Vulkan** 后端,因此不需要 DXIL/`dxc` 工具链)。Windows
82
+ arm64 仍跳过两个归档。在开发者机器上构建并运行
83
+ `examples/gui/pelican-svg` 与 `examples/gui/pelican-bike` 是可以的。
84
+ - **Windows:x64 在 CI 中构建,arm64 与运行时验证仍为试验性。**
85
+ `scripts/build-media.ts` 用 FFmpeg 的 MSVC 工具链
86
+ (`--toolchain=msvc --cc=clang-cl`)生成 `media.lib`,其 COFF 对象与静态 CRT
87
+ 和 `gui.lib` 一致,`--ext media` 在 Windows 上可以正常链接并播放。构建它需要
88
+ POSIX shell 运行 FFmpeg 的 `configure` 以及 GNU make;shell 来自 Git for
89
+ Windows(克隆 SDL3 本来就需要 Git),make 是原生 GNU make(Chocolatey 的
90
+ `make`,或 `xbintsc_MAKE`)。CI 会显式安装 make,在
91
+ Windows x64 上使用与本地构建相同的 Visual Studio `vcvarsall` + 自带 LLVM
92
+ 构建 `gui.lib` 与 `media.lib`,链接 `gui,media` 程序,并把两个归档放进
93
+ Windows x64 发布包;示例的*运行*仍因托管 runner 没有 GPU/Vulkan 驱动而保持
94
+ 试验性。Windows arm64 仍跳过两个归档;`<video controls>` 同样推迟——预期模式
95
+ 是 TS + CSS 叠层,如 `examples/gui/media.ts`。
70
96
  - **D3D12 着色器:** SPIR-V(Vulkan)与 DXIL(Direct3D 12)二进制块均已构建并
71
97
  内嵌,非 Metal 路径不再跳过几何绘制。但在 **SDL 3.2.10** 上 DXIL 仍不可用:
72
98
  其 D3D12 后端会拒绝任何着色器中声明了 uniform buffer 的图形管线,而引擎正是
73
99
  用这种方式传视口。Windows 实际会选择 Vulkan,因此不受影响;若将来强制走
74
- D3D12,需在 SDL 升级后重新确认。
100
+ D3D12,需在 SDL 升级后重新确认。
@@ -28,6 +28,20 @@
28
28
  flex 项的外边距参与其外框尺寸,因此
29
29
  负的 `margin-top` 会把该项上移,而不是被忽略。项之间的纯空白文本
30
30
  (源码缩进)不会成为 flex 项。
31
+ - **Grid** —— `display: grid`,支持 `grid-template-columns` /
32
+ `grid-template-rows`(固定长度、`auto`、`<n>fr`、`minmax(...)`、
33
+ `repeat(...)`)、`gap`/`row-gap`/`column-gap`、按行自动放置、简单的
34
+ `grid-column`/`grid-row` 线定位,以及 `align/justify-items` 与
35
+ `align/justify-self`(含 `place-items`/`place-self`)。隐式行/列由项目撑开;
36
+ 单单元格网格配合 `place-items: center` 可把卡片居中到整个覆盖层。项目测量是
37
+ 两遍布局(先固有尺寸、再最终放置),因此两遍之间会清空文本片段。
38
+ - **CSS 值函数与自定义属性** —— `clamp(min, value, max)`、`min(...)` 与
39
+ `max(...)` 在计算样式中保留参数,布局时结合元素字号与视口(`vw`/`vh`)求值;
40
+ 桌面窗口中 `env(safe-area-inset-*)` 解析为 0。`var(--name[, fallback])` 在
41
+ 层叠期间从继承的自定义属性替换。`inset` 是四值 `top/right/bottom/left`
42
+ 简写;`box-sizing: border-box` 会从显式宽高里扣除 padding 与边框;
43
+ `visibility: hidden` 跳过绘制与命中测试;`pointer-events: none` 使盒子不接收输入
44
+ (后代可用 `auto` 重新启用)。
31
45
  - **定位** —— `position: relative` 会按 `top`/`right`/`bottom`/`left` 偏移该盒子
32
46
  及其子树,但不改变它在流中占用的空间;`position: absolute`(以及针对视口解析
33
47
  的 `fixed`)使其脱离文档流,并相对最近的已定位祖先的 padding box 定位(没有
@@ -57,17 +57,21 @@ opacity、`display`——与任何其他元素一样。盒子内部的艺术内
57
57
 
58
58
  媒体播放横跨两个扩展,因此只做界面的 GUI 程序不必为用不到的解码器付出体积:
59
59
 
60
- - **`gui`**(`gui.a`)负责 `<video>`/`<audio>` 元素、DOM API、渲染器集成与 SDL
61
- 音频 sink,但**不含 FFmpeg**。`runtime/ext_gui/media.{h,cpp}` 声明播放器接口,
62
- 并通过媒体后端在启动时安装的函数表分发(`runtime/ext_gui/media_api.h`)。
63
- - **`media`**(`media.a`、`runtime/ext_media/`)是可选的解码后端:
64
- `runtime/ext_media/media.cpp` 封装静态 vendored 的 **FFmpeg** 构建,
65
- `runtime/ext_media/media_bridge.c` 把它注册进引擎。用 `--ext media`(或
66
- `--ext gui,media`)启用。`media` 依赖 `gui`;注册表会拒绝错误的组合。
60
+ - **`gui`**(`gui.a`/`gui.lib`)负责 `<video>`/`<audio>` 元素、DOM API、渲染器
61
+ 集成与 SDL 音频 sink,但**不含 FFmpeg**。`runtime/ext_gui/media.{h,cpp}`
62
+ 声明播放器接口,并通过媒体后端在启动时安装的函数表分发
63
+ (`runtime/ext_gui/media_api.h`)。
64
+ - **`media`**(`media.a`,Windows 上 MSVC ABI 则为 `media.lib`;
65
+ `runtime/ext_media/`)是可选的解码后端:`runtime/ext_media/media.cpp`
66
+ 封装静态 vendored 的 **FFmpeg** 构建,`runtime/ext_media/media_bridge.c`
67
+ 把它注册进引擎。用 `--ext media`(或 `--ext gui,media`)启用。`media`
68
+ 依赖 `gui`;注册表会拒绝错误的组合。
67
69
 
68
70
  没有 `media` 扩展时,元素仍然存在、DOM API 仍可调用,但打开源会失败并触发
69
- `error`。Windows 目前也是如此:该归档仅支持 POSIX。两条路径都没有 WebView,也
70
- 没有平台媒体框架。
71
+ `error`。任何没有构建该归档的宿主(先运行 `npm run media`)都是如此;
72
+ Windows 已不再特殊:`media.lib` 是用 clang-cl 为 MSVC ABI 编译的 FFmpeg,
73
+ 因此可以和 `gui.lib` 一起链接并正常播放。两条路径都没有 WebView,也没有平台
74
+ 媒体框架。
71
75
 
72
76
  - **解码。** `avformat_open_input` 打开 `src`(是路径——接受 `file://`,但与
73
77
  图像不同,媒体路径*不做*百分号解码),为每种媒体选取最佳流,软件解码器
@@ -83,8 +87,8 @@ opacity、`display`——与任何其他元素一样。盒子内部的艺术内
83
87
  `xtgui::AudioSink`。GUI 在 `gui_media.cpp` 中用 push 模式(`NULL` 回调)的
84
88
  `SDL_OpenAudioDeviceStream` 实现该 sink,在前方缓冲约 0.25 秒,格式为
85
89
  48 kHz 立体声 `SDL_AUDIO_F32`。`volume` 映射到 `SDL_SetAudioStreamGain`;
86
- 音频是尽力而为,无设备时(无头 CI)跳过。把 sink 留在 `gui.a` 中意味着
87
- `media.a` 只依赖 FFmpeg。
90
+ 音频是尽力而为,无设备时(无头 CI)跳过。把 sink 留在 GUI 归档中意味着
91
+ `media.a`/`media.lib` 只依赖 FFmpeg。
88
92
  - **线程。** 引擎是单线程且非线程感知,因此所有解码都在主线程的
89
93
  `xt_gui_media_tick` 内完成——没有解码线程,也没有跨线程 GC 暴露。
90
94
  - **DOM API。** 元素句柄直接暴露媒体接口(见
@@ -10,9 +10,10 @@
10
10
  `DisplayList`:一个按画家顺序排列的 `ops` 列表,包含**矩形**(背景与四条实心
11
11
  边框,带 `border-radius`)、**文本 run**(每个携带其文本、颜色与已解析的
12
12
  `FontSpec`)与**图像**,背后由并列的负载数组支撑。每个 op 还携带当前生效的裁剪
13
- 矩形(最近的 `overflow: hidden` 祖先的 padding box),因此 `overflow` 裁剪与
14
- `z-index` 顺序都能保留到渲染器。`DisplayList::dump()` 为
15
- `paintList()`/`paintCount()` 提供数据。
13
+ 矩形(最近的 `overflow: hidden` 祖先的 padding box)以及最多两个指向显示列表
14
+ 三角化 `clip-path` 多边形的下标,因此 `overflow`/`clip-path` 裁剪与 `z-index`
15
+ 顺序都能保留到渲染器。`DisplayList::dump()` 为 `paintList()`/`paintCount()`
16
+ 提供数据。
16
17
 
17
18
  `opacity` 只在两端生效:计算值为 `0` 的盒子(及其整棵子树)不绘制任何内容,
18
19
  大于 `0` 的一律按完全不透明绘制。图形只携带其颜色自身的 alpha,因此没有部分
@@ -61,7 +62,43 @@
61
62
 
62
63
  窗口背景(`setBackground`)是渲染通道的清除色。
63
64
 
64
- M4 仍需完成:渐变。
65
+ ### 背景、生成内容与特效(M19)
66
+
67
+ `background-color` 与每一条 `background-image` 层都会绘制。逗号分隔的层列表按
68
+ 从底到上的顺序绘制(声明中的第一层在最上面),两种渐变均受支持:
69
+
70
+ - **线性**渐变在 18 个位置采样并输出为纯色矩形;支持 `to <side>`、
71
+ `deg`/`grad`/`rad`/`turn`、百分比与多色标(色带沿最近轴行走,因此斜角会退化为
72
+ 最近的轴向);
73
+ - **径向**渐变是 32 个同心椭圆。每个都是单位半径的圆角矩形,经图形管线的
74
+ transform 槽映射,因此圆/椭圆仍保留距离场抗锯齿。支持形状/尺寸关键字
75
+ (`circle`/`ellipse`、`closest-side`/`farthest-side`/`closest-corner`/
76
+ `farthest-corner`、显式半径)与 `at <position>`。
77
+
78
+ 背景色位于所有层下方。若渐变色标带透明度,渐变也会半透明;色带近似可能产生
79
+ 轻微条带,非常透明的多色标渐变会出现 alpha 叠加。
80
+
81
+ **生成内容**不需要新的显示列表类型:`::before`/`::after` 是 `style.cpp` 中
82
+ 每次重算样式时重建的普通布局盒,因此其背景、边框、文本与 transform 都走同一条
83
+ 绘制路径。`content` 支持字符串、`attr()` 与 open/close quote。
84
+
85
+ **`text-shadow`** 会作为阴影 `PaintText` run 排在真实 run 之前,因此文本管线
86
+ 无需新 shader 就能绘制。带模糊的阴影是围绕偏移量展开的九个半透明副本(与
87
+ `filter: blur()` 相同的近似)。
88
+
89
+ **`clip-path`** 在 `paint_clip.cpp` 中解析为设备空间多边形(每个点都应用盒子的
90
+ transform),用耳切法三角化,并存入 `DisplayList::clip_paths`。每个 op 的
91
+ `PaintClip` 最多携带两个路径下标;`renderer_clip.cpp` 在上传时把每个凸的图形/
92
+ 字形/图像四边形与这些三角形求交。即使 `polygon()` 是凹多边形也正确,无需模板或
93
+ shader 改动,但边缘是硬边(没有解析抗锯齿)。支持两层嵌套路径求交;更深时保留
94
+ 最外两层。
95
+
96
+ **`filter`** 的颜色函数(`brightness`、`contrast`、`grayscale`、`sepia`、
97
+ `invert`、`saturate`、`hue-rotate`、`opacity`)会折进每个绘制颜色,因此盒子与
98
+ 其后代都会看到滤镜。`blur()` 与 `drop-shadow()` 是显示列表层面的近似:模糊把每个
99
+ 图形展开成九个半透明副本,投影用盒子/文字轮廓。这样保持单渲染通道(没有离屏
100
+ 渲染目标),代价是组语义:被模糊/滤镜的组内重叠后代可能加深;`backdrop-filter`
101
+ 无法采样元素背后的内容,因此会解析并参与层叠,但不绘制。
65
102
 
66
103
  ## 文本技术栈(M4b)
67
104
 
@@ -0,0 +1,115 @@
1
+ # WebGL(由 GPU 绘制的 `<canvas>`)
2
+
3
+ > 语言 / Language:[English](../../gui/webgl.md) | **简体中文**
4
+
5
+ > 属于 [GUI 扩展](../gui.md);引擎总览、已定决策与路线图都在那里。
6
+
7
+ `<canvas>` 可以申请 WebGL 上下文,而不是 2D 上下文:
8
+
9
+ ```ts
10
+ const canvas: any = document.getElementById("scene");
11
+ const gl: any = canvas.getContext("webgl2"); // 或 "webgl" / "experimental-webgl"
12
+ if (gl == null) {
13
+ // 该平台的 GPU 后端无法消费 WebGL 的着色器
14
+ }
15
+ ```
16
+
17
+ 2D 上下文把图形光栅化到 CPU 位图再上传为纹理;WebGL 上下文则**在 GPU 上渲染**:顶点变换、光照、纹理采样、深度测试与混合全部在 SDL_GPU 管线中执行(Windows/Linux 上是 Vulkan,macOS 上是 Metal)。页面直接合成该上下文的颜色纹理,因此显示时不需要把像素读回 CPU。
18
+
19
+ 同时提供 **WebGL 1.0** 与 **WebGL 2.0**。一个 canvas 会固定为它第一次申请的上下文类型:先调用 `getContext("webgl2")` 之后,`getContext("webgl")` 返回 `null`,反之亦然。WebGL 2 上下文也满足 `instanceof WebGLRenderingContext`(其原型继承 WebGL 1 原型),并在其上增加 WebGL 2 的常量与方法。
20
+
21
+ ## 一帧如何到达屏幕
22
+
23
+ - 每个上下文拥有一张离屏 `R8G8B8A8_UNORM` 颜色纹理(绘图缓冲)和一张 `D32_FLOAT` 深度纹理,尺寸取自 canvas 的 `width` / `height` 属性。`canvas.width = …` 会重建它们并丢弃内容,与 DOM 规则一致。
24
+ - 上下文记录到自己的 SDL_GPU 命令缓冲。`clear`/`draw*` 保持一个渲染 pass 打开;缓冲区/纹理上传或切换 framebuffer 会结束它,并以 `LOAD` 开始新 pass,因此绘图缓冲在多次绘制之间保留,和 WebGL 一样。
25
+ - 每帧一次,在页面自己的渲染 pass 之前,引擎提交所有 WebGL 命令缓冲。随后图像管线把颜色纹理当作普通 `PaintImage` 采样,没有 CPU 拷贝。
26
+ - WebGL 原点在左下,SDL_GPU 渲染目标原点在左上,因此帧缓冲按自上而下存储:转换 `viewport`/`scissor`,`readPixels` 再把行翻回去,合成器看到的是正立的纹理。
27
+ - 固定功能状态变化(`blendFunc`、`depthFunc`、`cullFace`、`colorMask` 等)会改变所需的 `SDL_GPUGraphicsPipeline`,因此管线按(程序、顶点布局、状态)惰性创建,并缓存在程序上。
28
+
29
+ ## 着色器:GLSL ES 到 SPIR-V(再到 MSL)
30
+
31
+ SDL_GPU 不能在运行时编译 GLSL,且每个后端只消费自己的格式,因此引擎静态链接了两个翻译器:
32
+
33
+ - **glslang** 把改写后的 GLSL 编译为 SPIR-V,Vulkan 后端直接消费;
34
+ - **SPIRV-Cross** 把同一份 SPIR-V 交叉编译为 Metal Shading Language 文本,Metal 后端在运行时编译它。
35
+
36
+ 两者都打进 `gui.a` / `gui.lib`;Metal 路径复用 SPIR-V 模块而不是再写一套 GLSL 前端,因此两个后端接受完全相同的着色器子集。流程是:
37
+
38
+ 1. `shaderSource` 保存 GLSL ES 文本;`compileShader` 先翻译一次,供 `COMPILE_STATUS` / `getShaderInfoLog` 使用。
39
+ 2. `linkProgram` 用共享的属性/`varying` 位置表再翻译两个阶段,用 glslang 编译,然后创建 `SDL_GPUShader`:Vulkan 上直接用 SPIR-V,Metal 上用 SPIRV-Cross 产出的 MSL 文本。
40
+ 3. 反射生成的 SPIR-V,取得 uniform 块精确的 std140 偏移、数组步长与矩阵步长——源码里数组长度可以写宏,glslang 已经解析好了。SPIRV-Cross 会在 MSL 结构体中保留这些偏移,因此一份 uniform 布局同时服务两个后端。
41
+
42
+ 翻译器(`runtime/ext_gui/webgl_glsl*.cpp`)处理 GLSL ES 1.00 与 3.00:
43
+
44
+ | WebGL 写法 | 改写为 |
45
+ | --- | --- |
46
+ | `attribute vec3 a_pos` | `layout(location = N) in vec3 a_pos` |
47
+ | `varying vec2 v_uv` | `layout(location = N) out/in vec2 v_uv`(共享的名字 → 位置表) |
48
+ | `gl_FragColor` | `layout(location = 0) out highp vec4 xt_FragColor` |
49
+ | `texture2D`、`textureCube`、`texture2DProj` | `texture`、`texture`、`textureProj` |
50
+ | `texture2DLodEXT` / `textureCubeLodEXT` | `textureLod` |
51
+ | 非采样器 uniform | 一个 `layout(std140) uniform XtUniforms { … }` 块 |
52
+ | `uniform sampler2D u_tex` | `layout(set = 2, binding = N) uniform sampler2D u_tex`(片元)或 set 0(顶点) |
53
+ | `gl_FragData[0]` | `xt_FragColor` |
54
+ | 缺少 `#version` / `#version 100` | `#version 310 es` |
55
+
56
+ 两个阶段的非采样器 uniform 会合并成同一个块,并在每个阶段各声明一次(顶点 set 1、片元 set 3),与 SDL_GPU 的 Vulkan 描述符布局一致。在 Metal 上同一批声明会变成 `[[buffer(0)]]` 与 `[[texture(N)]]`/`[[sampler(N)]]`,正是 SDL_GPU Metal 后端绑定的槽位。`uniformMatrix4fv` 按列主序写入反射出的偏移;对采样器调用 `uniform1i` 时记录纹理单元。每次绘制都用动态 uniform 缓冲接口推送该块。
57
+
58
+ 翻译器的限制:结构体 uniform、用户 uniform 块、预处理后仍无法确定长度的 uniform 数组、采样器数组与立方体贴图都会被拒绝(着色器/链接日志会说明)。`#ifdef` 内的代码没问题;非采样器 uniform 声明会被移到外层顶层位置,保证块是无条件的。
59
+
60
+ ### GLSL ES 3.00(WebGL 2)
61
+
62
+ `#version 300 es` 源码除版本号提升外会被原样接受:顶层 `in`/`out` 声明会变成属性/`varying`,命名片元输出(`out vec4 fragColor;`)会变成 `xt_FragColor` 形式的输出,`texture()` / `textureLod()` 本就与 Vulkan 方言一致。多个片元输出会分配连续 location,但渲染 pass 只有一个颜色目标,因此只写入第一个。GLSL ES 3.00 提供 `gl_VertexID` 与 `gl_InstanceID`,实例化示例正是用它。
63
+
64
+ ## 支持的 API
65
+
66
+ - **上下文**:`getContext("webgl")`、`canvas`、`drawingBufferWidth/Height`、`getParameter`、`getError`、`getSupportedExtensions`、`getExtension`、`getContextAttributes`、`isContextLost`、`getShaderPrecisionFormat`,以及模板入口(接受状态;深度目标没有模板附件)。
67
+ - **着色器与程序**:`createShader`、`shaderSource`、`compileShader`、`getShaderParameter`、`getShaderInfoLog`、`getShaderSource`、`deleteShader`、`createProgram`、`attachShader`、`detachShader`、`linkProgram`、`getProgramParameter`、`getProgramInfoLog`、`getAttachedShaders`、`validateProgram`、`deleteProgram`、`useProgram`、`getActiveUniform`/`getActiveAttrib`、`getUniformLocation`、`getAttribLocation`、`bindAttribLocation`。
68
+ - **Uniform**:`uniform1f`…`uniform4f`、`uniform1i`…`uniform4i`、`uniform1fv`…`uniform4fv`、`uniform1iv`…`uniform4iv`、`uniformMatrix2fv`/`3fv`/`4fv`、`getUniform`。
69
+ - **缓冲与顶点属性**:`createBuffer`、`bindBuffer`、`bufferData`、`bufferSubData`、`deleteBuffer`、`getBufferParameter`、`enableVertexAttribArray`、`disableVertexAttribArray`、`vertexAttribPointer`、`vertexAttrib1f`…`4f` / `1fv`…`4fv`、`getVertexAttrib`。
70
+ - **绘制**:`drawArrays`、`drawElements`(UNSIGNED_BYTE、UNSIGNED_SHORT、UNSIGNED_INT;`TRIANGLE_FAN` 与 `LINE_LOOP` 会展开为列表)。`POINTS`、`LINES`、`LINE_STRIP`、`TRIANGLES`、`TRIANGLE_STRIP` 映射到对应的 SDL 图元。
71
+ - **纹理**:`createTexture`、`bindTexture`、`activeTexture`、`texParameteri`/`texParameterf`、`getTexParameter`、`texImage2D`(原始像素、`<img>`、`<canvas>`、`ImageData`)、`texSubImage2D`、`generateMipmap`、`deleteTexture`。支持 `UNPACK_FLIP_Y_WEBGL` 与 `UNPACK_PREMULTIPLY_ALPHA_WEBGL`。全部转换为 RGBA8;接受 `RGBA`、`RGB`、`LUMINANCE`、`ALPHA`、`LUMINANCE_ALPHA` 配 `UNSIGNED_BYTE`。
72
+ - **Framebuffer**:`createFramebuffer`、`bindFramebuffer`、`framebufferTexture2D`(颜色附件)、`checkFramebufferStatus`、`createRenderbuffer`、`bindRenderbuffer`、`renderbufferStorage`(深度)、`framebufferRenderbuffer`、`delete*`。足以实现渲染到纹理、阴影贴图与后处理。
73
+ - **状态**:`enable`/`disable`/`isEnabled`(`BLEND`、`DEPTH_TEST`、`CULL_FACE`、`SCISSOR_TEST`;模板状态接受但不生效)、`clearColor`/`clearDepth`/`clearStencil`、`clear`、`blendFunc`/`blendFuncSeparate`、`blendEquation`/`blendEquationSeparate`、`depthFunc`、`depthMask`、`colorMask`、`cullFace`、`frontFace`、`viewport`、`scissor`、`pixelStorei`、`finish`。
74
+ - **读回**:`readPixels`(RGBA/UNSIGNED_BYTE),可从默认帧缓冲或绑定的 framebuffer 读取。它会阻塞到 GPU 完成;用于测试和截图,不要每帧调用。
75
+
76
+ WebGL 2 额外提供:
77
+
78
+ - **顶点数组对象**:`createVertexArray`、`bindVertexArray`、`deleteVertexArray`、`isVertexArray`。SDL_GPU 没有 VAO,绑定一个会恢复保存下来的属性表及其元素缓冲。
79
+ - **实例化**:`drawArraysInstanced`、`drawElementsInstanced`、`drawRangeElements`、`vertexAttribDivisor`。divisor 为 1 映射到 SDL 的按实例输入频率;SDL 3.2 保留更大的步进值,因此大于 1 的 divisor 会被钳制为 1。
80
+ - **整数属性与 uniform**:`vertexAttribIPointer`、`vertexAttribI4*` 系列、`uniform1ui`…`uniform4ui`。
81
+ - **采样器对象**:`createSampler`、`bindSampler`、`deleteSampler`、`samplerParameteri/f`、`getSamplerParameter`;绑定的采样器会覆盖该单元上纹理自身的过滤/环绕状态。
82
+ - **不可变纹理与缓冲拷贝**:`texStorage2D`(后端始终分配完整 mip 链)、`copyBufferSubData`、`getBufferSubData`。
83
+ - **清除**:`clearBufferfv`、`clearBufferiv`、`clearBufferuiv`、`clearBufferfi`。
84
+ - **查询与栅栏**:对象与入口都存在(`createQuery`/`beginQuery`/`endQuery`/`getQueryParameter`、`fenceSync`/`clientWaitSync`/`waitSync`/`getSyncParameter`),行为是优雅降级:栅栏报告 `ALREADY_SIGNALED`,查询结果为 0。
85
+ - **Uniform 块**:`getUniformBlockIndex` 返回 `INVALID_INDEX`,让库回退到独立 uniform;块相关入口为空操作。
86
+ - **Transform feedback、3D 纹理与压缩格式**:入口存在但为空操作,避免特性探测类库抛错。
87
+
88
+ 纹理创建时带完整 mip 链;在 `generateMipmap` 之前采样被钳制到第 0 层。矩阵顶点属性会占用相应数量的连续 location,每个 location 读取一列。未启用的属性由最近一次 `vertexAttrib*f` 的值通过一个小常量缓冲提供。
89
+
90
+ ## 未实现
91
+
92
+ 着色器源码里的 uniform buffer 对象、transform feedback(入口为空操作)、立方体贴图、3D/数组纹理、浮点/半浮点纹理、深度纹理、压缩纹理、多渲染目标(只写第一个片元输出)、`blitFramebuffer`、MSAA/采样缓冲、模板操作(状态接受,但不写模板缓冲)、常量混合颜色、`drawingBufferStorage`、上下文丢失、`bufferData` 的非 static/dynamic `usage`(提示被忽略)、`texImage2D` 对 `HTMLVideoElement`/`ImageBitmap` 的支持,以及带 scissor 的 `clear`(clear 会清空整个附件)。使用内建块、用户 uniform 块或超出支持范围的 `gl_FragDepth` 的着色器会以编译/链接日志报错。
93
+
94
+ 引擎报告的顶点纹理单元数量为 0,因此行为良好的 WebGL 1 程序不会在顶点着色器里采样纹理。
95
+
96
+ ## 平台
97
+
98
+ 管线按 SDL_GPU 设备接受的着色器格式创建:Vulkan 上用 **SPIR-V**(`SDL_GPU_SHADERFORMAT_SPIRV`,Windows/Linux 选择的后端),macOS 上用 **MSL**(`SDL_GPU_SHADERFORMAT_MSL`,Metal 后端)。两个后端共用同一个 GLSL 前端,因此在一个后端能编译的着色器在另一个上也能编译。如果设备的后端两种格式都不接受——Direct3D 12 需要 DXIL——`getContext("webgl")` 与 `getContext("webgl2")` 都会返回 `null`;2D canvas 与页面其余部分仍然正常。
99
+
100
+ `npm run gui` 会拉取并构建 glslang 与 SPIRV-Cross,合并进 `gui.a` / `gui.lib`(见[打包与构建](./build.md));只用 2D canvas 或 DOM 的程序除了归档体积之外没有运行时开销。
101
+
102
+ ## 测试
103
+
104
+ - `tests/e2e/gui-webgl.test.ts` 编译 GLSL、绘制带纹理和深度测试的四边形、检查面剔除,并读回像素。
105
+ - `tests/e2e/gui-webgl2.test.ts` 覆盖 WebGL 2 的新增能力:`#version 300 es` 着色器、VAO 往返、带 divisor 的实例化绘制、`texStorage2D`、`clearBufferfv`、缓冲拷贝、采样器对象与结构体 uniform 字段。
106
+ - `examples/gui/webgl-cube` 是一个旋转、受光、带纹理的立方体;它在浏览器中原样运行,并由 `tests/e2e/gui-example-webgl.test.ts` 覆盖。
107
+ - `examples/gui/webgl2-cubes` 用一次绘制调用画出 64 个实例化立方体,使用 `#version 300 es` 与 VAO;由 `tests/e2e/gui-example-webgl2.test.ts` 覆盖。
108
+
109
+ 这些用例会在宿主选择的后端上运行:Linux(与 Windows)上是 Vulkan,macOS 上是 Metal,因此 SPIRV-Cross → MSL 路径与 SPIR-V 路径受同一组断言保护。
110
+
111
+ ## three.js
112
+
113
+ `.js` 资源加载器现在只修补 three.js 的着色器循环展开;`WebGLRenderer` 在本引擎真正的 WebGL 2 后端上原样运行,**不再打包 Canvas2D 回退**。已用 three.js r160 通过 SDL_GPU 验证:`MeshBasicMaterial`、`MeshStandardMaterial`、`MeshPhongMaterial`、`CanvasTexture`、线性雾、环境光/平行光/点光/聚光/半球光、`Sprite`、`Line`、`Points` 与 `InstancedMesh`。
114
+
115
+ 本引擎子集之外的能力仍无法模拟:阴影贴图(深度纹理)、立方体贴图/环境贴图、HDR/浮点渲染目标、多渲染目标、transform feedback 与压缩纹理。需要这些能力的场景无法正确渲染;此前 Canvas2D 回退能显示的内容现在全部走 GPU。