xbintsc 0.5.38 → 0.5.41

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 (117) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/codegen/generator/module.js +23 -7
  3. package/dist/src/codegen/generator/module.js.map +1 -1
  4. package/dist/src/driver/version.d.ts +1 -1
  5. package/dist/src/driver/version.js +1 -1
  6. package/dist/src/extensions/gui/html.js +9 -2
  7. package/dist/src/extensions/gui/html.js.map +1 -1
  8. package/dist/tests/codegen/closures.test.js +7 -0
  9. package/dist/tests/codegen/closures.test.js.map +1 -1
  10. package/dist/tests/e2e/compile-objects-classes.test.js +31 -0
  11. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  12. package/dist/tests/e2e/gui-canvas-state.test.d.ts +9 -0
  13. package/dist/tests/e2e/gui-canvas-state.test.js +297 -0
  14. package/dist/tests/e2e/gui-canvas-state.test.js.map +1 -0
  15. package/dist/tests/e2e/gui-draw-perf.test.d.ts +22 -0
  16. package/dist/tests/e2e/gui-draw-perf.test.js +405 -0
  17. package/dist/tests/e2e/gui-draw-perf.test.js.map +1 -0
  18. package/dist/tests/e2e/gui-perf.test.d.ts +23 -0
  19. package/dist/tests/e2e/gui-perf.test.js +283 -0
  20. package/dist/tests/e2e/gui-perf.test.js.map +1 -0
  21. package/dist/tests/e2e/gui-script-import-order.test.js +9 -1
  22. package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -1
  23. package/dist/tests/e2e/gui-webgl.test.js +4 -4
  24. package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
  25. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.d.ts +8 -0
  26. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js +336 -0
  27. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js.map +1 -0
  28. package/dist/tests/extensions/gui.test.js +23 -0
  29. package/dist/tests/extensions/gui.test.js.map +1 -1
  30. package/doc/gui/api.md +28 -2
  31. package/doc/gui/canvas.md +110 -27
  32. package/doc/gui/known-issues.md +32 -10
  33. package/doc/gui/rendering.md +77 -0
  34. package/doc/gui/runtime.md +18 -3
  35. package/doc/gui/webgl.md +38 -19
  36. package/doc/gui.md +33 -1
  37. package/doc/zh-CN/gui/api.md +25 -2
  38. package/doc/zh-CN/gui/canvas.md +95 -26
  39. package/doc/zh-CN/gui/known-issues.md +21 -4
  40. package/doc/zh-CN/gui/rendering.md +59 -0
  41. package/doc/zh-CN/gui/runtime.md +13 -3
  42. package/doc/zh-CN/gui/webgl.md +27 -5
  43. package/doc/zh-CN/gui.md +25 -2
  44. package/package.json +7 -7
  45. package/runtime/ext_gui/browser.cpp +3 -1
  46. package/runtime/ext_gui/canvas.cpp +135 -89
  47. package/runtime/ext_gui/canvas.h +116 -0
  48. package/runtime/ext_gui/canvas_clip.cpp +143 -0
  49. package/runtime/ext_gui/canvas_composite.cpp +241 -0
  50. package/runtime/ext_gui/canvas_coverage.cpp +241 -0
  51. package/runtime/ext_gui/canvas_gradient.cpp +41 -4
  52. package/runtime/ext_gui/canvas_image_data.cpp +4 -0
  53. package/runtime/ext_gui/canvas_internal.h +34 -9
  54. package/runtime/ext_gui/canvas_raster.cpp +207 -178
  55. package/runtime/ext_gui/canvas_shadow.cpp +161 -0
  56. package/runtime/ext_gui/canvas_stroke.cpp +37 -9
  57. package/runtime/ext_gui/canvas_style.cpp +59 -0
  58. package/runtime/ext_gui/canvas_text.cpp +140 -0
  59. package/runtime/ext_gui/css.h +7 -0
  60. package/runtime/ext_gui/css_sheet.cpp +22 -0
  61. package/runtime/ext_gui/document.cpp +25 -1
  62. package/runtime/ext_gui/document.h +8 -0
  63. package/runtime/ext_gui/dom_api_canvas.cpp +16 -1
  64. package/runtime/ext_gui/dom_api_canvas_image.cpp +9 -0
  65. package/runtime/ext_gui/dom_api_canvas_state.cpp +217 -0
  66. package/runtime/ext_gui/dom_api_internal.h +36 -37
  67. package/runtime/ext_gui/dom_api_offscreen.cpp +213 -0
  68. package/runtime/ext_gui/dom_api_proto.cpp +18 -0
  69. package/runtime/ext_gui/dom_api_webgl.cpp +19 -9
  70. package/runtime/ext_gui/dom_api_webgl_internal.h +58 -0
  71. package/runtime/ext_gui/dom_api_webgl_source.cpp +18 -0
  72. package/runtime/ext_gui/gradient_values.cpp +12 -1
  73. package/runtime/ext_gui/gui.cpp +5 -0
  74. package/runtime/ext_gui/gui_canvas.cpp +4 -0
  75. package/runtime/ext_gui/gui_engine.h +30 -0
  76. package/runtime/ext_gui/gui_events.cpp +182 -58
  77. package/runtime/ext_gui/gui_media.cpp +3 -0
  78. package/runtime/ext_gui/image.cpp +69 -0
  79. package/runtime/ext_gui/image_bitmap_browser.cpp +11 -0
  80. package/runtime/ext_gui/layout.cpp +4 -0
  81. package/runtime/ext_gui/paint.cpp +19 -0
  82. package/runtime/ext_gui/paint_background.cpp +18 -1
  83. package/runtime/ext_gui/perf.cpp +171 -0
  84. package/runtime/ext_gui/perf.h +127 -0
  85. package/runtime/ext_gui/renderer.cpp +109 -41
  86. package/runtime/ext_gui/renderer.h +33 -9
  87. package/runtime/ext_gui/renderer_image.cpp +135 -65
  88. package/runtime/ext_gui/renderer_internal.h +15 -3
  89. package/runtime/ext_gui/renderer_text.cpp +24 -12
  90. package/runtime/ext_gui/style.cpp +101 -30
  91. package/runtime/ext_gui/style.h +6 -3
  92. package/runtime/ext_gui/style_print.cpp +7 -1
  93. package/runtime/ext_gui/text.cpp +23 -4
  94. package/runtime/ext_gui/text.h +29 -0
  95. package/runtime/ext_gui/text_cache.cpp +118 -0
  96. package/runtime/ext_gui/text_glyph_cache.cpp +100 -0
  97. package/runtime/ext_gui/webgl.h +48 -146
  98. package/runtime/ext_gui/webgl_blit.cpp +12 -1
  99. package/runtime/ext_gui/webgl_blit_depth.cpp +270 -0
  100. package/runtime/ext_gui/webgl_buffer.cpp +59 -1
  101. package/runtime/ext_gui/webgl_buffer_objects.cpp +106 -9
  102. package/runtime/ext_gui/webgl_clear.cpp +3 -0
  103. package/runtime/ext_gui/webgl_context.cpp +29 -3
  104. package/runtime/ext_gui/webgl_draw.cpp +75 -270
  105. package/runtime/ext_gui/webgl_draw_buffers.cpp +1 -0
  106. package/runtime/ext_gui/webgl_draw_setup.cpp +321 -0
  107. package/runtime/ext_gui/webgl_draw_state.h +130 -0
  108. package/runtime/ext_gui/webgl_glsl_scan.cpp +3 -8
  109. package/runtime/ext_gui/webgl_objects.h +148 -0
  110. package/runtime/ext_gui/webgl_pipeline.cpp +8 -8
  111. package/runtime/ext_gui/webgl_program_internal.h +35 -0
  112. package/runtime/ext_gui/webgl_shader.cpp +22 -30
  113. package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
  114. package/runtime/ext_gui/window.cpp +56 -1
  115. package/src/codegen/generator/module.ts +22 -7
  116. package/src/driver/version.ts +1 -1
  117. package/src/extensions/gui/html.ts +9 -2
@@ -11,9 +11,11 @@ CSS 盒中。
11
11
 
12
12
  引擎侧的对象是 `xtgui::Canvas2D`(`runtime/ext_gui/canvas.{h,cpp}`)——一块 RGBA8 位图
13
13
  加上 2D 绘制状态。光栅器位于 `canvas_raster.cpp`,描边几何(端点、连接、虚线)位于
14
- `canvas_stroke.cpp`,渐变位于 `canvas_gradient.cpp`,绘制状态解析位于
15
- `canvas_style.cpp`;DOM 绑定位于 `dom_api_canvas.cpp` / `dom_api_canvas_extra.cpp`;
16
- 按窗口的生命周期位于 `gui_canvas.cpp`。
14
+ `canvas_stroke.cpp`,渐变位于 `canvas_gradient.cpp`,合成模式位于
15
+ `canvas_composite.cpp`,裁剪与命中测试位于 `canvas_clip.cpp`,阴影位于
16
+ `canvas_shadow.cpp`,绘制状态解析位于 `canvas_style.cpp`;DOM 绑定位于
17
+ `dom_api_canvas*.cpp` / `dom_api_offscreen.cpp`;按窗口的生命周期位于
18
+ `gui_canvas.cpp`。
17
19
 
18
20
  ## 获取上下文
19
21
 
@@ -31,6 +33,15 @@ const ctx = canvas.getContext("2d"); // 或 "webgl" 取得 GPU 上下文
31
33
  元素上,因此每次都返回**同一个**对象,绘制状态(变换、样式、路径)按 canvas 而非按调用
32
34
  保存。`ctx.canvas` 是所属元素的句柄。
33
35
 
36
+ `OffscreenCanvas` 提供同样的 2D 接口,但没有 DOM 元素:
37
+ `new OffscreenCanvas(width, height)` 分配一块由窗口拥有的位图,`getContext("2d")`
38
+ 返回的上下文其 `canvas` 指向离屏对象;`canvas.transferControlToOffscreen()` 会把一个
39
+ 活动元素的位图交给离屏对象(此后元素的 `getContext(…)` 返回 `null`)。
40
+ `drawImage`、`createPattern`、`createImageBitmap` 与 `texImage2D` 都接受
41
+ `OffscreenCanvas` 作为源。`transferToImageBitmap()` 返回 `ImageBitmap`,
42
+ `convertToBlob()` 解析为与 `toBlob` 相同的 PNG blob 形状(本运行时没有 worker,
43
+ 离屏画布也不提供 WebGL 上下文)。
44
+
34
45
  `getContext("2d", attributes)` 在首次调用时会尊重 `alpha`:传入 `{ alpha: false }`
35
46
  时位图以不透明黑色初始化(清除也回到不透明黑)。其余属性由
36
47
  `getContextAttributes()` 按规范默认值报告(`antialias: true`、`depth: true`、
@@ -42,8 +53,30 @@ const ctx = canvas.getContext("2d"); // 或 "webgl" 取得 GPU 上下文
42
53
  光栅器刻意保持精简且为自研(不依赖 Skia/Cairo)。它按每条像素行 **4 条垂直子扫描线**
43
54
  进行抗锯齿,并做一次**水平解析覆盖率**计算;路径默认采用**非零环绕规则**填充(因此当弧线
44
55
  以相反方向环绕时能形成空洞),`fill("evenodd")` 则改用奇偶规则。轴对齐矩形(变换中
45
- `b == 0 && c == 0`)走直接的覆盖率快速路径。混合为非预乘 source-over,并按
46
- `globalAlpha` 缩放。
56
+ `b == 0 && c == 0`)走直接的覆盖率快速路径。
57
+
58
+ 混合默认是非预乘 source-over。`globalCompositeOperation` 支持完整的 canvas 集合:
59
+ Porter-Duff 算子(`source-over`、`source-in`、`source-out`、`source-atop`、
60
+ `destination-over`、`destination-in`、`destination-out`、`destination-atop`、
61
+ `lighter`、`copy`、`xor`)以及可分离/不可分离混合模式(`multiply`、`screen`、
62
+ `overlay`、`darken`、`lighten`、`color-dodge`、`color-burn`、`hard-light`、
63
+ `soft-light`、`difference`、`exclusion`、`hue`、`saturation`、`color`、
64
+ `luminosity`)。抗锯齿与裁剪区域产生的部分覆盖率会在“未改动的目标”与“完整合成结果”
65
+ 之间插值,因此每个算子在部分覆盖的像素上都正确。
66
+
67
+ `clip()` 用当前路径(或 `Path2D` 加可选的 `"nonzero"`/`"evenodd"` 规则)与当前裁剪区
68
+ 求交。区域以设备空间覆盖率掩码保存,因此它固定在调用 `clip()` 时的变换下;
69
+ `save`/`restore` 会一并恢复,位图尺寸变化会清空它。所有填充、描边、文本、图像绘制与
70
+ `clearRect` 都遵守该掩码。
71
+
72
+ 阴影(`shadowColor`、`shadowBlur`、`shadowOffsetX`、`shadowOffsetY`)由绘制操作自身的
73
+ alpha 生成:先把操作渲染到临时图层,对其 alpha 做偏移与盒式模糊(三次可分离模糊),用
74
+ `shadowColor` 着色后合成,最后把未处理的图层画在其上。偏移与模糊使用画布(设备)单位,
75
+ 不受变换影响,符合 API 规定。`clearRect` 与 `putImageData` 从不产生阴影。
76
+
77
+ `isPointInPath(x, y[, fillRule])`、`isPointInPath(path, x, y[, fillRule])`、
78
+ `isPointInStroke(x, y)` 与 `isPointInStroke(path, x, y)` 在当前变换下对当前路径(或
79
+ 传入的 `Path2D`)做命中测试;描边命中测试使用到各展平线段的设备空间距离与当前线宽。
47
80
 
48
81
  填充或描边可以使用纯色,也可以使用 **`CanvasGradient`**。渐变的坐标属于用户空间,
49
82
  因此光栅器会先用当前变换的逆矩阵把每个设备像素映射回用户空间再采样——形状被平移、
@@ -54,6 +87,27 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
54
87
  `<video>`/`<audio>` 解码表结构相同)。当节点被丢弃(`loadHTML`、窗口关闭/退出,或丢弃
55
88
  子树的变更操作)时随之释放,并释放对应的 GPU 纹理。
56
89
 
90
+ ## 性能
91
+
92
+ canvas 由脚本重绘而不是引擎重绘,因此开销按绘制调用计。多元素场景下的热路径刻意
93
+ 保持少分支:
94
+
95
+ - 纯色不透明的 `fillRect` 直接按字节写入位图(小数边缘仍走抗锯齿路径),覆盖整张
96
+ 位图的 `clearRect` 退化为一次 `memset`;
97
+ - 路径覆盖率使用逐行**活动边表**光栅化,并且只物化每一行**实际覆盖的区间**——一条
98
+ 细对角线描边只花它自己的像素,而不是整个包围盒;
99
+ - 一次 `stroke()` 把所有线段四边形、连接与端点合并成一次非零环绕填充,而不是每段
100
+ 各做一次覆盖率计算(先统一每个多边形的绕向,重叠处才能覆盖而不是抵消);
101
+ - `fillText` 与布局共享整形缓存(`text_cache.cpp`),并使用有界两代字形位图缓存
102
+ (`text_glyph_cache.cpp`);
103
+ - 整数偏移、无缩放的 `drawImage` 逐像素只取一个纹素,不再跑双线性采样器。
104
+
105
+ 位图通过**脏矩形**到达屏幕:每次绘制把它的设备空间包围盒并入待上传区域,渲染器只
106
+ 拷贝该区域到 canvas 纹理。一帧内所有 canvas 与视频上传共用帧的唯一上传命令缓冲,
107
+ 因此 K 张动画 canvas 只需一次提交而不是 K 次。`getImageData` 始终读取 CPU 位图,
108
+ 不受影响。`tests/e2e/gui-draw-perf.test.ts` 测量这些多元素路径并固定计数器行为
109
+ (每帧一次上传、只传脏字节)。
110
+
57
111
  ## 支持的 API
58
112
 
59
113
  状态与变换:`save` / `restore`(会恢复变换、线宽、透明度、字体/文本设置**以及**
@@ -64,30 +118,37 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
64
118
  `translate(x, y); rotate(a)` 围绕平移后的原点旋转。
65
119
 
66
120
  样式与设置:`fillStyle` / `strokeStyle`(层叠所用的颜色解析器能识别的 CSS 颜色,或
67
- `CanvasGradient`)、`lineWidth`、`globalAlpha`、`lineCap`(`butt`/`round`/`square`)、
121
+ `CanvasGradient`)、`lineWidth`、`globalAlpha`、`globalCompositeOperation`、
122
+ `shadowColor` / `shadowBlur` / `shadowOffsetX` / `shadowOffsetY`、
123
+ `imageSmoothingEnabled` / `imageSmoothingQuality`、`lineCap`(`butt`/`round`/`square`)、
68
124
  `lineJoin`(`miter`/`round`/`bevel`)、`miterLimit`、`lineDashOffset`、`font`
69
125
  (CSS 字体简写,解析出字体族、字号与粗体/斜体——默认 `10px sans-serif`)、
70
126
  `textAlign`(`start`/`left`、`center`、`right`/`end`)与 `textBaseline`
71
127
  (`alphabetic`、`top`、`middle`、`bottom`、`hanging`、`ideographic`)。读取属性返回
72
- 上次赋予的值;赋予无法解析的颜色会保留原值。
128
+ 上次赋予的值;赋予无法解析的颜色会保留原值。`imageSmoothingEnabled` 默认为 `true`
129
+ (双线性采样);`imageSmoothingQuality` 报告 `low`、`medium` 或 `high`,本后端都使用
130
+ 同一种双线性过滤。
73
131
 
74
132
  渐变:`createLinearGradient(x0, y0, x1, y1)` 与
75
133
  `createRadialGradient(x0, y0, r0, x1, y1, r1)` 返回带
76
134
  `addColorStop(offset, color)` 的 `CanvasGradient`。色标是**活的**:在
77
135
  `fillStyle = gradient` 之后追加的色标会影响后续填充,且 `fillStyle`/`strokeStyle`
78
- 的读取返回同一个渐变句柄。径向渐变按到**外圆**圆心(`x1, y1`)的距离插值,因此目前
79
- 会忽略 `x0`/`y0`——请使用同心圆(`x0 == x1`、`y0 == y1`)。
136
+ 的读取返回同一个渐变句柄。径向渐变是通用的双圆锥:点上的颜色由插值圆
137
+ (`圆心 = c0 + t·(c1 − c0)`、`半径 = r0 + t·(r1 − r0)`)解出,因此内圆有偏移、两圆
138
+ 半径不同时都与浏览器一致。内圆内部的点采样色标 0。
80
139
 
81
140
  路径:`beginPath`、`closePath`、`moveTo`、`lineTo`、`rect`、`roundRect`、`arc`、
82
141
  `ellipse`、`quadraticCurveTo`、`bezierCurveTo`,然后
83
- `fill(fillRule?)` / `fill(path, fillRule?)`(多环;`"nonzero"` 或 `"evenodd"`)或
84
- `stroke(path?)`。贝塞尔与二次曲线段会被展平为线段;弧线与椭圆同样被展平。
142
+ `fill(fillRule?)` / `fill(path, fillRule?)`(多环;`"nonzero"` 或 `"evenodd"`)、
143
+ `stroke(path?)`、`clip(path?, fillRule?)` 以及 `isPointInPath`/`isPointInStroke`
144
+ 命中测试。贝塞尔与二次曲线段会被展平为线段;弧线与椭圆同样被展平。
85
145
  `roundRect` 接受单个半径数字,或 CSS 半径列表(`[all]`、`[tl, br]`、
86
146
  `[tl, tr, br]`、`[tl, tr, br, bl]`,元素为数字或 `{ x, y }` 对;圆角按圆弧绘制)。
87
147
 
88
148
  `Path2D` 支持同一组路径命令(`moveTo`、`lineTo`、`closePath`、`rect`、
89
149
  `roundRect`、`arc`、`ellipse`、`quadraticCurveTo`、`bezierCurveTo`、`addPath`),
90
- 并可传给 `fill`/`stroke`,因此一次构建的路径可以在不同变换下重复使用。
150
+ 并可传给 `fill`/`stroke`/`clip` 以及 `isPointIn*` 命中测试,因此一次构建的路径可以在
151
+ 不同变换下重复使用。
91
152
 
92
153
  矩形:`clearRect`、`fillRect`、`strokeRect`。
93
154
 
@@ -103,29 +164,37 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
103
164
 
104
165
  图像:`drawImage(source, …)` 支持 3 参数(`dx, dy`)、5 参数(`dx, dy, dw, dh`)与
105
166
  9 参数(本引擎为**目标优先**:`dx, dy, dw, dh, sx, sy, sw, sh`)。完整的仿射变换
106
- (旋转/斜切/缩放)会被应用:目标矩形变为平行四边形,每个设备像素反查到源矩形
107
- (最近邻采样)。`source` 可以是 `<img>` 元素(经图像
108
- 模块解码)或另一个 `<canvas>` 元素(取其当前位图)。采样为最近邻,因此把小图大幅放大
109
- 会有块状感——适合图标与像素画,不适合照片管线。
167
+ (旋转/斜切/缩放)会被应用:目标矩形变为平行四边形,每个设备像素反查到源矩形。
168
+ `source` 可以是 `<img>` 元素(经图像模块解码)、另一个 `<canvas>`、
169
+ `OffscreenCanvas` 或 `ImageBitmap`。`imageSmoothingEnabled` 为 `true`(默认)时使用
170
+ 双线性采样,为 `false` 时使用最近邻。
110
171
 
111
172
  测试钩子:`getImageData(x, y, w, h)` 把区域复制为 `{ width, height, data }`,其中
112
173
  `data` 是按行主序的 RGBA8 字节数组。越界像素为透明。
113
174
 
114
- 图案:`createPattern(source, repetition?)` 接受 `<img>` 或另一个 `<canvas>`,在用户
115
- 空间中平铺(`repeat`、`repeat-x`、`repeat-y`、`no-repeat`),因此平铺会跟随当前
116
- 变换。把结果赋给 `fillStyle`/`strokeStyle`;重新赋颜色会替换掉图案。
175
+ 图案:`createPattern(source, repetition?)` 接受 `<img>`、`<canvas>`、
176
+ `OffscreenCanvas` 或 `ImageBitmap`,在用户空间中平铺(`repeat`、`repeat-x`、
177
+ `repeat-y`、`no-repeat`),因此平铺会跟随当前变换。把结果赋给
178
+ `fillStyle`/`strokeStyle`;重新赋颜色会替换掉图案。
117
179
 
118
180
  图像数据与导出:`createImageData(width, height)` 分配一个空的
119
181
  `{ width, height, data }`,`putImageData(imageData, dx, dy[, dirtyX, dirtyY,
120
- dirtyWidth, dirtyHeight])` 直接把字节写入位图(不受变换、混合或 `globalAlpha`
121
- 影响)。`canvas.toDataURL(type?)` 导出 PNG data URL,`canvas.toBlob(callback,
122
- type?)` 从微任务中交付 Blob 形状的 `{ type, size }`(字节在
123
- `__xt_gui_blob_data` 上;本运行时没有 `URL.createObjectURL`)。
182
+ dirtyWidth, dirtyHeight])` 直接把字节写入位图(不受变换、混合、裁剪或
183
+ `globalAlpha` 影响)。`canvas.toDataURL(type?)` 导出 PNG data URL,
184
+ `canvas.toBlob(callback, type?)` 从微任务中交付 Blob 形状的 `{ type, size }`
185
+ (字节在 `__xt_gui_blob_data` 上;本运行时没有 `URL.createObjectURL`)。
186
+ `OffscreenCanvas.convertToBlob()` 解析为同一形状,`transferToImageBitmap()` 对位图
187
+ 做快照。
188
+
189
+ CSS 背景:`background-image: url(...)` 接受文件路径或 `data:` URL,因此可以先把
190
+ canvas 导出再作为 CSS 背景使用
191
+ (`element.style.backgroundImage = "url(" + canvas.toDataURL() + ")"`)。图像层会拉伸
192
+ 到元素的边框盒;`background-size`、`background-position` 与平铺尚未建模。
124
193
 
125
194
  ## 尚未实现
126
195
 
127
- 阴影、`clip()`、`globalCompositeOperation`、`isPointInPath`、可切换图像平滑的
128
- `drawImage`、把 `<canvas>` 用作 CSS `background-image`,以及离屏画布
129
- (`OffscreenCanvas`、`transferControlToOffscreen`)。径向渐变会忽略内圆偏移。
196
+ 本后端的平滑质量只有一种过滤(双线性)。离屏画布只支持 2D 上下文(WebGL 上下文需要
197
+ DOM 元素来维护纹理表)。旋转的字形位图仍不会随变换旋转,`strokeText` 仍用描边样式
198
+ 填充而非生成轮廓。
130
199
 
131
200
  另见[媒体](./media.md):`drawImage` 与纹理上传所共享的图像/视频/音频解码栈。
@@ -11,13 +11,15 @@
11
11
  [WebGL](./webgl.md)):GLSL ES 着色器由内置 glslang 翻译为 SPIR-V——Vulkan
12
12
  后端直接消费,Metal 上再由内置 SPIRV-Cross 交叉编译为 MSL——绘图缓冲
13
13
  是一张离屏颜色纹理,页面直接合成而无须读回。WebGL 2 增加了 VAO、实例化、
14
- 整数属性、采样器对象、`texStorage2D` 与缓冲拷贝。Uniform 块、transform
15
- feedback、立方体贴图、3D 纹理、浮点/深度纹理、压缩纹理、多渲染目标、
16
- `blitFramebuffer`、MSAA 与模板操作尚未实现;`clear` 会忽略 scissor 矩形。
14
+ 整数属性、采样器对象、`texStorage2D`、缓冲拷贝、Uniform 块、立方体/3D/数组
15
+ 纹理、浮点/深度/压缩纹理、多渲染目标与 scissor 清除。`blitFramebuffer` 复制
16
+ 颜色与深度;transform feedback、MSAA 与模板 blit 尚未实现(SDL_GPU 没有
17
+ transform feedback 阶段,其 Metal 多采样 resolve 不可靠,模板附件也无法采样)。
17
18
  在两种格式都不接受的 SDL_GPU 后端(Direct3D 12,需要 DXIL)上两种上下文都为
18
19
  `null`。`.js` 资源加载器现在只修补 three.js 的着色器循环展开;
19
20
  `WebGLRenderer` 直接运行在真实后端上,不再有 Canvas2D 回退。阴影贴图
20
- (深度纹理)、立方体贴图/环境贴图与 HDR 渲染目标仍未支持。
21
+ (比较型 `sampler2DShadow` 采样深度纹理)、立方体贴图/环境贴图与 HDR 渲染目标
22
+ 均已支持。
21
23
 
22
24
 
23
25
  - **自动关闭时的偶发崩溃。** 设置 `XT_GUI_AUTOCLOSE_MS` 结束运行时,约五次中有两次会以原生
@@ -57,6 +59,21 @@
57
59
  图像内部(这里的 `fill`/`stroke` 是 SVG 表现属性,不是 CSS 属性)。需要这些
58
60
  能力的矢量美术必须预先光栅化、用盒子绘制,或限制在受支持的语法内。参见
59
61
  [媒体](./media.md#图像m6a)。
62
+ - **每次变更仍是整文档重算。** 一次 DOM 变更会重新计算层叠并重新布局整个文档
63
+ (没有增量子树失效),只影响绘制的变更(例如 hover 颜色)同样会重排。带生成
64
+ 内容(`::before`/`::after`)的文档没有特殊处理。各阶段的实测开销由
65
+ `win.stats()`/`XT_GUI_PERF` 暴露,后续优化以它为依据;参见
66
+ [性能](./rendering.md#性能m20)。
67
+ - **绘制调用遵循画家顺序。** 只有当 run 在画家顺序上相邻且 pipeline、裁剪与
68
+ 纹理都相同时才会合并,因此"背景与文本逐行交替"的列表每行要付一个图形 run 和
69
+ 一个文本 run(见 `win.stats()` 的 `drawCalls`)。要减少绘制调用需要感知重叠的
70
+ 合并器(文本可能被后续背景覆盖),目前未实现。实测:200 行基准页发出 400 次
71
+ 绘制调用,绘制阶段约耗时 0.4 ms,因此它是"绘制调用占主导"的页面的后续优化项,
72
+ 而不是当前的瓶颈。
73
+ - **文本整形缓存有界。** 两代、每代最多 2 MiB 字形 / 8,192 条条目保存
74
+ `(face, text) -> 字形`。不同单词数超过上限的文档在缓存轮换后会重新整形一部分;
75
+ `win.stats()` 的 `textCacheBytes`/`textShapeHits` 能看出它是否成为瓶颈。
76
+ 元素自带的 `style="…"` 内联属性在该元素每次重算样式时都会重新解析。
60
77
 
61
78
  ## 待决问题
62
79
 
@@ -128,3 +128,62 @@ shader 改动,但边缘是硬边(没有解析抗锯齿)。支持两层嵌
128
128
  - 布局使用这些真实度量进行文本宽度、断行与 `line-height: normal` 计算;
129
129
  `measureText`/`fontMetrics` 把它们暴露给测试。
130
130
 
131
+ ## 性能(M20)
132
+
133
+ 引擎是保留模式管线:一次 DOM 变更会重算样式、重新布局、重建显示列表并上传
134
+ 顶点数组;没有任何变化的帧不产生 CPU 或 GPU 工作。为了让上千个元素的文档保持
135
+ 可交互,每个阶段都有预算,并且都可以测量。设置 `XT_GUI_PERF=1` 会逐帧打印一行
136
+ (`XT_GUI_PERF=<N>` 则每 N 帧聚合一行);`win.stats()` 返回同样的计数器,
137
+ `tests/e2e/gui-perf.test.ts` 断言的正是这些计数。
138
+
139
+ - **层叠。** 每次重算样式时,`@media` 前导条件**每条规则只求值一次**(过去是
140
+ 每条规则 × 每个节点各解析一次,这是大文档上最大的一笔开销)。样式表里没有
141
+ `::before`/`::after` 规则时完全跳过生成内容;声明候选列表在深度优先遍历中
142
+ 复用而不是每个元素分配一次;只有样式表真的包含 `:hover`/`:focus` 规则时,
143
+ 这两个状态变化才会触发重算。
144
+ - **文本。** 布局与绘制共享一个有界的整形缓存(`text_cache.cpp`):
145
+ `(face, text) -> 字形 + 前进宽度`。它使用两代、每代最多 2 MiB 字形 / 8,192 条
146
+ 条目;某一代写满时丢弃**备用**代并切换过去,因此热路径上没有 LRU 记账。
147
+ 一个词只整形一次,之后每次布局和重绘都复用它。主字体查找也按
148
+ (字体族类别,字号)缓存。
149
+ - **帧循环。** 只有文档发生变化(或首帧)时才把媒体/画布表与 DOM 对齐,不再每帧
150
+ 遍历整棵树。只有确实发生了事情才提交呈现(present)——DOM 变更、滚动、动画帧、
151
+ 过渡、新的视频/画布帧、expose/resize 事件——因此静态页面完全不获取 swapchain、
152
+ 也不绘制。expose 是唯一由窗口系统(而非页面)要求的呈现,`exposes` 统计这些帧,
153
+ 因此即使窗口服务器很晚才映射窗口,`presents - exposes - deferred` 仍然只是
154
+ 文档自身的工作量。取不到交换链图像的帧(GPU 仍持有图像时 SDL 的非阻塞获取会跳过
155
+ 该帧,最小化的窗口则完全没有图像)不会丢弃这次失效,而是保留到下一帧再呈现,
156
+ 这类呈现计为 `deferred`。调试"漏掉的失效"时可用 `XT_GUI_ALWAYS_PRESENT=1`
157
+ 恢复旧的每帧提交。
158
+ - **绘制与上传。** 显示列表和顶点暂存数组挂在窗口中按帧清理,容量得以保留。
159
+ 一个脏帧的所有顶点/图集上传共用一个命令缓冲区(一次提交),几何体的暂存
160
+ transfer buffer 被复用(上一帧的拷贝可能仍在飞行中,因此按 cycle 方式映射),
161
+ 没有任何子元素带 z-index 时绘制会跳过排序和临时数组。
162
+ - **绘制调用。** 相邻且 pipeline、裁剪和纹理相同的 op 仍会合并为一个 run,
163
+ 渲染器现在只在 pipeline / 顶点缓冲 / 裁剪矩形真正变化时才重新绑定。
164
+ `win.stats()` 里的 `drawCalls`/`drawMs` 报告 run 数量与其开销,它们是后续工作
165
+ (感知重叠的合并)的度量依据——在它落地之前,画家顺序要求背景与文本交替的
166
+ run 必须保持独立。
167
+ - **立即模式绘制表面。** `<canvas>` 2D 与 WebGL 由脚本驱动,开销按调用次数计
168
+ 而不是按文档变更计;上面的计数器只覆盖保留模式管线。
169
+ `tests/e2e/gui-draw-perf.test.ts` 测量它们:在参考的 Windows x64/Vulkan 机器上,
170
+ 一帧 1280×720、包含 5,000 个纯色 `fillRect`、200 条 8 点折线、300 次
171
+ `fillText` 和 200 次 1:1 `drawImage` 的场景从 231ms 降到 158ms(填充 4.3ms、
172
+ 描边 151ms、文本 1.4ms、图像 1.6ms);2,000 次带逐次矩阵的 WebGL
173
+ `drawElements` 从 38.6ms 降到 27.5ms,同时每帧打开的渲染 pass 从 2,001 次
174
+ 降到 1 次,向 4 MiB 缓冲写入 64 KiB 的 `bufferSubData` 不再重传整个缓冲。
175
+ 100×100 的画布局部重绘现在只上传 40 KB,而不是整张 1.2 MB 位图。测试断言的是
176
+ 计数器(每帧一次上传、稳定状态不新建管线、只传脏字节),因此墙钟数字仅供
177
+ 参考,CI 上的软件 GPU 不会被误判。
178
+
179
+ 已知限制:一次变更仍会对**整个**文档重算样式并重新布局(没有增量子树失效),
180
+ 只影响绘制的变更(例如 hover 颜色)目前同样会重排;后续工作应以 `win.stats()`
181
+ 的数据为依据。元素自带的 `style="…"` 内联属性在该元素每次重算样式时都会重新解析。
182
+
183
+ 作为参照,`tests/e2e/gui-perf.test.ts` 会构建一个 200 行的列表(约 1,000 个元素、
184
+ 400 个绘制 run)并打印实测数字,例如在 Windows x64/Vulkan 机器上修改一个
185
+ `textContent` 字段得到
186
+ `textFrame=5.2ms restyle=3.6ms layout=1.7ms paint=1.0ms upload=0.8ms draw=0.4ms`。
187
+ 绝对值因机器而异;测试断言的是**计数器**(一次重算、一次布局、命中缓存而不是
188
+ 重新整形、空闲帧不做任何工作),回归时变化的正是这些。
189
+
@@ -19,11 +19,21 @@ GUI 引擎既可以通过 `xt_loop_set_main` 注册自己的循环,也可以
19
19
  普通原生调用。每次 tick 它都会:
20
20
 
21
21
  1. 为每个窗口泵送 SDL 窗口/输入事件,
22
- 2. 当显示列表变化时构建/上传它,并渲染每个打开的窗口(清除通道 + 图形几何
22
+ 2. **只在文档发生变化(或首帧)时**把解码器/画布表与 DOM 对齐,
23
+ 3. 当显示列表变化时构建/上传它,并渲染每个打开的窗口(清除通道 + 图形几何
23
24
  + 文本几何),
24
- 3. 调用 `xt_loop_poll(0)` 与 `xt_drain_microtasks()`,使 socket/定时器与
25
+ 4. 调用 `xt_loop_poll(0)` 与 `xt_drain_microtasks()`,使 socket/定时器与
25
26
  `await` 续体持续取得进展,
26
- 4. 重复直到所有窗口关闭或调用 `quit()`。
27
+ 5. 重复直到所有窗口关闭或调用 `quit()`。
28
+
29
+ 只有**确实发生了事情**帧才会呈现:DOM 变更、滚动、已排队的动画帧、正在运行的
30
+ 过渡、新的视频/画布帧、expose/resize 事件,或首帧。因此静态文档不会获取
31
+ swapchain、也不会绘制,而循环仍在处理事件、定时器与 promise。expose/shown/restore
32
+ 事件是唯一由窗口系统(而非文档)要求的呈现,它们在 `win.stats()` 中计为
33
+ `exposes`;取不到交换链图像而被推迟的呈现计为 `deferred`,`presents - exposes -
34
+ deferred` 才是页面自身造成的工作。调试"漏掉的失效"时可用
35
+ `XT_GUI_ALWAYS_PRESENT=1` 恢复旧的每帧提交行为。`XT_GUI_PERF=1` 会打印每一帧
36
+ 做了哪些工作(见[性能](./rendering.md#性能m20))。
27
37
 
28
38
  `xt_loop_set_main` 钩子仍保留给希望自行掌控循环的宿主。
29
39
 
@@ -21,11 +21,26 @@ if (gl == null) {
21
21
  ## 一帧如何到达屏幕
22
22
 
23
23
  - 每个上下文拥有一张离屏 `R8G8B8A8_UNORM` 颜色纹理(绘图缓冲)和一张 `D32_FLOAT` 深度纹理,尺寸取自 canvas 的 `width` / `height` 属性。`canvas.width = …` 会重建它们并丢弃内容,与 DOM 规则一致。
24
- - 上下文记录到自己的 SDL_GPU 命令缓冲。`clear`/`draw*` 保持一个渲染 pass 打开;缓冲区/纹理上传或切换 framebuffer 会结束它,并以 `LOAD` 开始新 pass,因此绘图缓冲在多次绘制之间保留,和 WebGL 一样。
24
+ - 上下文记录到自己的 SDL_GPU 命令缓冲。`clear`/`draw*` 保持一个渲染 pass 打开;只有缓冲区/纹理上传或切换 framebuffer 才会结束它,并以 `LOAD` 开始新 pass,因此绘图缓冲在多次绘制之间保留,和 WebGL 一样;没有上传的绘制不会中断 pass。
25
25
  - 每帧一次,在页面自己的渲染 pass 之前,引擎提交所有 WebGL 命令缓冲。随后图像管线把颜色纹理当作普通 `PaintImage` 采样,没有 CPU 拷贝。
26
26
  - WebGL 原点在左下,SDL_GPU 渲染目标原点在左上,因此帧缓冲按自上而下存储:转换 `viewport`/`scissor`,`readPixels` 再把行翻回去,合成器看到的是正立的纹理。
27
27
  - 固定功能状态变化(`blendFunc`、`depthFunc`、`cullFace`、`colorMask` 等)会改变所需的 `SDL_GPUGraphicsPipeline`,因此管线按(程序、顶点布局、状态)惰性创建,并缓存在程序上。
28
28
 
29
+ ## 性能
30
+
31
+ 重复绘制命中缓存,而不是每次重建:当前(程序、属性布局、固定功能状态、附件格式)
32
+ 对应的管线、顶点布局与缓冲绑定只准备一次并复用(`webgl_draw_setup.cpp`);状态变化时
33
+ 缓存未命中并新建管线。固定功能状态被哈希进 128 位键,而不是每次 draw 拼字符串;活动
34
+ 属性在链接时解析一次。上传被前移到渲染 pass 之前:没有上传的绘制不会结束 pass,禁用
35
+ 的顶点属性读取持久的常量缓冲而不是每次 draw 分配 GPU 缓冲,状态未变的绘制不重复绑定。
36
+ `bufferSubData` 只拷贝写入的字节区间,增长中的缓冲按 2 的幂扩容。
37
+
38
+ `win.stats()` 报告 `webglDraws`、`webglPasses`、`webglPipelineMisses` 与
39
+ `webglBufferUploadBytes`;`tests/e2e/gui-draw-perf.test.ts` 正是用它们固定行为
40
+ (2,000 次绘制只开一个 pass、稳定状态不再创建管线、4 MiB 缓冲只重传 64 KiB 区间)。
41
+ 剩余的每次 draw 开销来自后端自身的提交;大量相同对象仍应由应用改用
42
+ `drawElementsInstanced`。
43
+
29
44
  ## 着色器:GLSL ES 到 SPIR-V(再到 MSL)
30
45
 
31
46
  SDL_GPU 不能在运行时编译 GLSL,且每个后端只消费自己的格式,因此引擎静态链接了两个翻译器:
@@ -51,12 +66,16 @@ SDL_GPU 不能在运行时编译 GLSL,且每个后端只消费自己的格式
51
66
  | 非采样器 uniform | 一个 `layout(std140) uniform XtUniforms { … }` 块 |
52
67
  | `uniform sampler2D u_tex` | `layout(set = 2, binding = N) uniform sampler2D u_tex`(片元)或 set 0(顶点) |
53
68
  | `gl_FragData[0]` | `xt_FragColor` |
69
+ | `gl_FragDepth` / `gl_FragDepthEXT` | 保留(SPIR-V `DepthReplacing`;MSL `[[depth(any)]]`) |
70
+ | `sampler2DShadow` / `samplerCubeShadow` / `sampler2DArrayShadow` | 保留;绑定为 SDL_GPU 比较采样器 |
54
71
  | 缺少 `#version` / `#version 100` | `#version 310 es` |
55
72
 
56
73
  实现限制:结构体 uniform、用户 uniform 块、预处理阶段无法确定长度的 uniform 数组,以及用非常量表达式索引的采样器数组会被拒绝(shader/link info log 会说明)。`#pragma unroll_loop_start` 循环会在改写前展开,因此 three.js 生成的 `sampler2D shadowMap[ NUM_DIR_LIGHT_SHADOWS ]` / `shadowMap[ i ]` 会变成常量索引。`#ifdef` 内的着色器代码可用;非采样器 uniform 声明会被移动到顶层,保证块保持无条件。两个阶段的非采样器 uniform 会合并成同一个块,并在每个阶段各声明一次(顶点 set 1、片元 set 3),与 SDL_GPU 的 Vulkan 描述符布局一致。在 Metal 上同一批声明会变成 `[[buffer(0)]]` 与 `[[texture(N)]]`/`[[sampler(N)]]`,正是 SDL_GPU Metal 后端绑定的槽位。`uniformMatrix4fv` 按列主序写入反射出的偏移;对采样器调用 `uniform1i` 时记录纹理单元。每次绘制都用动态 uniform 缓冲接口推送该块。
57
74
 
58
75
  翻译器的限制:结构体 uniform、用户 uniform 块、预处理后仍无法确定长度的 uniform 数组、采样器数组与采样器类型不匹配的声明会被拒绝(着色器/链接日志会说明)。`#ifdef` 内的代码没问题;非采样器 uniform 声明会被移到外层顶层位置,保证块是无条件的。
59
76
 
77
+ 片元着色器可以写 `gl_FragDepth`:glslang 会把 SPIR-V 标记为 `DepthReplacing`,SPIRV-Cross 会生成 `[[depth(any)]]`,因此两个后端上深度写入都会到达深度附件。比较采样器(`sampler2DShadow`、`samplerCubeShadow`、`sampler2DArrayShadow`)复用 SDL_GPU 采样器对象并设置 `enable_compare`;`TEXTURE_COMPARE_MODE` 与 `TEXTURE_COMPARE_FUNC` 从纹理读取(WebGL 2 也可从绑定的采样器对象读取)。
78
+
60
79
  ### GLSL ES 3.00(WebGL 2)
61
80
 
62
81
  `#version 300 es` 源码除版本号提升外会被原样接受:顶层 `in`/`out` 声明会变成属性/`varying`,命名片元输出(`out vec4 fragColor;`)会变成 `xt_FragColor` 形式的输出,`texture()` / `textureLod()` 本就与 Vulkan 方言一致。多个片元输出会分配连续 location,但渲染 pass 只有一个颜色目标,因此只写入第一个。GLSL ES 3.00 提供 `gl_VertexID` 与 `gl_InstanceID`,实例化示例正是用它。
@@ -69,7 +88,7 @@ SDL_GPU 不能在运行时编译 GLSL,且每个后端只消费自己的格式
69
88
  - **缓冲与顶点属性**:`createBuffer`、`bindBuffer`、`bufferData`、`bufferSubData`、`deleteBuffer`、`getBufferParameter`、`enableVertexAttribArray`、`disableVertexAttribArray`、`vertexAttribPointer`、`vertexAttrib1f`…`4f` / `1fv`…`4fv`、`getVertexAttrib`。`bufferData` 的 `usage` 提示(`STREAM_DRAW`/`STATIC_DRAW`/`DYNAMIC_DRAW`)会被记录,`getBufferParameter(…, BUFFER_USAGE)` 可读回。
70
89
  - **绘制**:`drawArrays`、`drawElements`(UNSIGNED_BYTE、UNSIGNED_SHORT、UNSIGNED_INT;`TRIANGLE_FAN` 与 `LINE_LOOP` 会展开为列表)。`POINTS`、`LINES`、`LINE_STRIP`、`TRIANGLES`、`TRIANGLE_STRIP` 映射到对应的 SDL 图元。
71
90
  - **纹理**:`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` 可用。浮点纹理(`RGBA32F`、`RGBA16F`、`RGB32F`、`RGB16F`、`RG32F`/`RG16F`、`R32F`/`R16F`,`FLOAT` 或 `HALF_FLOAT` 数据,以及 WebGL 1 的 `OES_texture_float`/`OES_texture_half_float`)映射到 SDL 的 RGBA32F/RGBA16F;深度纹理(`DEPTH_COMPONENT16/24/32F`、`DEPTH_STENCIL`、`DEPTH24_STENCIL8`)映射到 `D32_FLOAT`/`D32_FLOAT_S8_UINT`,可作为采样源或深度附件。压缩纹理通过 `compressedTexImage2D`/`compressedTexSubImage2D` 上传,支持 `WEBGL_compressed_texture_s3tc`(BC1/BC2/BC3)、`WEBGL_compressed_texture_s3tc_srgb` 与 `WEBGL_compressed_texture_astc`;扩展只在设备确实支持对应格式时公布。`texImage2D` 还接受 `<video>` 的最新解码帧与 `createImageBitmap` 产生的 `ImageBitmap`。立方体贴图(`bindTexture(TEXTURE_CUBE_MAP)` + 六个 `texImage2D` 面)与 3D/数组纹理(`texImage3D`/`texStorage3D`)可用对应采样器(`samplerCube`、`sampler3D`、`sampler2DArray`)采样;`texSubImage3D` 可更新 3D/数组纹理的子区域,`framebufferTextureLayer` 可把数组/立方体贴图的指定层作为颜色附件渲染。
72
- - **Framebuffer**:`createFramebuffer`、`bindFramebuffer`(`FRAMEBUFFER`/`READ_FRAMEBUFFER`/`DRAW_FRAMEBUFFER`)、`framebufferTexture2D`(`COLOR_ATTACHMENT0`、`DEPTH_ATTACHMENT`、`DEPTH_STENCIL_ATTACHMENT`)、`checkFramebufferStatus`、`createRenderbuffer`、`bindRenderbuffer`、`renderbufferStorage`(深度/深度模板)、`framebufferRenderbuffer`、`delete*`,以及 `blitFramebuffer`(颜色附件;深度/模板位被接受但不复制)。足以实现渲染到纹理、阴影贴图与后处理。
91
+ - **Framebuffer**:`createFramebuffer`、`bindFramebuffer`(`FRAMEBUFFER`/`READ_FRAMEBUFFER`/`DRAW_FRAMEBUFFER`)、`framebufferTexture2D`(`COLOR_ATTACHMENT0`、`DEPTH_ATTACHMENT`、`DEPTH_STENCIL_ATTACHMENT`)、`checkFramebufferStatus`、`createRenderbuffer`、`bindRenderbuffer`、`renderbufferStorage`(深度/深度模板)、`framebufferRenderbuffer`、`delete*`,以及 `blitFramebuffer`(颜色走 SDL blit;深度走一个写 `gl_FragDepth` 的采样 pass;模板位被接受但不复制)。足以实现渲染到纹理、阴影贴图与后处理。
73
92
  - **状态**:`enable`/`disable`/`isEnabled`(`BLEND`、`DEPTH_TEST`、`CULL_FACE`、`SCISSOR_TEST`、`STENCIL_TEST`)、`clearColor`/`clearDepth`/`clearStencil`、`clear`(开启 `SCISSOR_TEST` 时颜色、深度与模板都只清除 scissor 矩形)、`blendColor`、`blendFunc`/`blendFuncSeparate`、`blendEquation`/`blendEquationSeparate`、`depthFunc`、`depthMask`、`colorMask`、`stencilFunc`/`stencilFuncSeparate`、`stencilOp`/`stencilOpSeparate`、`stencilMask`/`stencilMaskSeparate`、`cullFace`、`frontFace`、`viewport`、`scissor`、`pixelStorei`、`finish`。模板测试真正参与光栅化,`clear(STENCIL_BUFFER_BIT)` 会清空模板附件;SDL_GPU 只有一份前后脸共用的 reference 与 compare/write mask,若前后脸设置不同,使用前脸值。
74
93
  - **读回**:`readPixels`(RGBA/UNSIGNED_BYTE),可从默认帧缓冲或绑定的 RGBA8 framebuffer 读取;浮点/深度附件先采样到 RGBA8 目标再读回。它会阻塞到 GPU 完成;用于测试和截图,不要每帧调用。
75
94
 
@@ -91,7 +110,9 @@ WebGL 2 额外提供:
91
110
 
92
111
  ## 未实现
93
112
 
94
- transform feedback(入口为空操作)与 MSAA/采样缓冲(SDL_GPU 的 Metal 多采样 resolve 在 scissor 与实例化绘制下返回未定义像素,因此上下文保持单采样)。`blitFramebuffer` 只复制颜色附件,深度/模板位被接受但不复制。使用内建块或 `gl_FragDepth` 的着色器会以编译/链接日志报错。
113
+ transform feedback(入口为空操作:SDL_GPU 没有 transform feedback 阶段)与 MSAA/采样缓冲(SDL_GPU 3.2 能解析多采样颜色目标,但其 Metal resolve 在 scissor 与实例化绘制下会返回未定义像素,因此上下文保持单采样,`SAMPLE_BUFFERS` 报告 0)。`blitFramebuffer` 复制颜色与深度;模板位被接受但不复制,因为 SDL_GPU 无法采样模板附件。
114
+
115
+ 仍存在的着色器限制:结构体 uniform 与用户 uniform 块会被展平,但内建接口块(如重声明 `gl_PerVertex`)不在翻译子集内;使用它的着色器仍会以编译/链接日志失败,而不是静默出错。
95
116
 
96
117
  ## 平台
97
118
 
@@ -101,7 +122,8 @@ transform feedback(入口为空操作)与 MSAA/采样缓冲(SDL_GPU 的 Me
101
122
 
102
123
  ## 测试
103
124
 
104
- - `tests/e2e/gui-webgl.test.ts` 编译 GLSL、绘制带纹理和深度测试的四边形、检查面剔除,并读回像素;它还覆盖混合常量色、动态缓冲 usage、顶点着色器纹理采样、`gl_FragDepth` 诊断、上下文丢失/恢复与立方体贴图。
125
+ - `tests/e2e/gui-webgl.test.ts` 编译 GLSL、绘制带纹理和深度测试的四边形、检查面剔除,并读回像素;它还覆盖混合常量色、动态缓冲 usage、顶点着色器纹理采样、`gl_FragDepth`、上下文丢失/恢复与立方体贴图。
126
+ - `tests/e2e/gui-webgl2-shadow-sampler.test.ts` 覆盖硬件 `sampler2DShadow` 比较、着色器写 `gl_FragDepth` 遮挡后续绘制,以及 `blitFramebuffer` 的部分深度复制。
105
127
  - `tests/e2e/gui-webgl2.test.ts` 覆盖 WebGL 2 的着色器/状态新增能力:`#version 300 es` 着色器、VAO 往返、带 divisor 的实例化绘制、`texStorage2D`、`clearBufferfv`、缓冲拷贝、采样器对象、结构体 uniform 字段与 `drawingBufferStorage`。
106
128
  - `tests/e2e/gui-webgl2-formats.test.ts` 覆盖模板、浮点/深度纹理、压缩纹理与 `ImageBitmap`。
107
129
  - `tests/e2e/gui-webgl2-blit.test.ts` 覆盖带 scissor 的颜色/深度清除、framebuffer blit,以及局部 blit 之外必须保留的像素(在 Metal 上,若目标 load-op 丢弃这些像素,该测试就会失败)。
@@ -118,4 +140,4 @@ transform feedback(入口为空操作)与 MSAA/采样缓冲(SDL_GPU 的 Me
118
140
 
119
141
  着色器改写器会把 three.js 的 `#pragma unroll_loop_start` 循环展开为常量索引(否则其采样器数组无法通过 Vulkan GLSL),`.js` 资源加载器则避免渲染器自带的展开器重复处理。`WebGLRenderer` 在本引擎真正的 WebGL 2 后端上原样运行,**不再打包 Canvas2D 回退**。已用 three.js r160 通过 SDL_GPU 验证:`MeshBasicMaterial`、`MeshStandardMaterial`、`MeshPhongMaterial`、`CanvasTexture`、线性雾、环境光/平行光/点光/聚光/半球光、`Sprite`、`Line`、`Points` 与 `InstancedMesh`。
120
142
 
121
- 本引擎子集之外的能力仍无法模拟:transform feedback 与 MSAA;立方体贴图/环境贴图、深度纹理、HDR/浮点渲染目标与压缩纹理已经可用。需要这些能力的场景无法正确渲染;此前 Canvas2D 回退能显示的内容现在全部走 GPU。
143
+ 本引擎子集之外的能力仍无法模拟:transform feedback 与 MSAA。立方体贴图/环境贴图、深度纹理、HDR/浮点渲染目标、压缩纹理与比较型 `sampler2DShadow` 采样都已可用(three.js 阴影贴图可在两个后端使用硬件深度比较)。需要 transform feedback 或 MSAA 的场景无法正确渲染;此前 Canvas2D 回退能显示的内容现在全部走 GPU。
package/doc/zh-CN/gui.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 语言 / Language:[English](../gui.md) | **简体中文**
4
4
 
5
- 状态:**M19** —— 功能(M1–M15)已全部完成:HTML 解析、CSS 选择器匹配、层叠(cascade)、计算样式与布局(块级、行内、Flexbox 与 CSS 定位)均已就位,引擎还会**绘制**:它构建一个由矩形、图像与排版后的文本 run 组成的显示列表,并通过 SDL_GPU 渲染。输入事件会经过命中测试并投递给原生 TS 处理器,`:hover`/`:focus` 会被动态匹配,`<img>` 依据其固有尺寸确定大小并从纹理绘制——位图(PNG/JPEG/…)与 SVG 文件皆可,后者按绘制尺寸光栅化,因此矢量图保持清晰——CSS transition 会为绘制属性做动画。**M16** 让内联 `<svg>` 子树获得同样的处理:子树被序列化回 SVG 文档,并按绘制设备尺寸光栅化,因此图标不再需要单独的文件。**M8** 增加了交互式 DOM:具有稳定标识的元素句柄、变更操作(`appendChild`、`textContent`、`classList`、`style` 等)以及支持捕获/冒泡的元素级事件。**M9** 会提前编译 `<script>` 主体(内联与 `<script src>`)——参见 `gui-scripts.md`。**M10** 增加 `requestAnimationFrame` 以及若干 DOM 辅助方法。**M11** 增加 `position: relative`/`absolute`/`fixed` 及其偏移,`examples/gui/pelican-bike`(一个可玩的 2D 游戏)就构建在它之上。**M12** 明确了 DOM 节点生命周期(哪些变更释放、哪些保留),并让引擎内存有界:参见[节点生命周期](./gui/dom.md)与[内存与 GC](./gui/runtime.md)。**M13** 真正播放
5
+ 状态:**M20** —— 功能(M1–M15)已全部完成:HTML 解析、CSS 选择器匹配、层叠(cascade)、计算样式与布局(块级、行内、Flexbox 与 CSS 定位)均已就位,引擎还会**绘制**:它构建一个由矩形、图像与排版后的文本 run 组成的显示列表,并通过 SDL_GPU 渲染。输入事件会经过命中测试并投递给原生 TS 处理器,`:hover`/`:focus` 会被动态匹配,`<img>` 依据其固有尺寸确定大小并从纹理绘制——位图(PNG/JPEG/…)与 SVG 文件皆可,后者按绘制尺寸光栅化,因此矢量图保持清晰——CSS transition 会为绘制属性做动画。**M16** 让内联 `<svg>` 子树获得同样的处理:子树被序列化回 SVG 文档,并按绘制设备尺寸光栅化,因此图标不再需要单独的文件。**M8** 增加了交互式 DOM:具有稳定标识的元素句柄、变更操作(`appendChild`、`textContent`、`classList`、`style` 等)以及支持捕获/冒泡的元素级事件。**M9** 会提前编译 `<script>` 主体(内联与 `<script src>`)——参见 `gui-scripts.md`。**M10** 增加 `requestAnimationFrame` 以及若干 DOM 辅助方法。**M11** 增加 `position: relative`/`absolute`/`fixed` 及其偏移,`examples/gui/pelican-bike`(一个可玩的 2D 游戏)就构建在它之上。**M12** 明确了 DOM 节点生命周期(哪些变更释放、哪些保留),并让引擎内存有界:参见[节点生命周期](./gui/dom.md)与[内存与 GC](./gui/runtime.md)。**M13** 真正播放
6
6
  `<video>`/`<audio>`——可选的 `media` 扩展用 vendored 的最小 FFmpeg 在主线程解码,
7
7
  视频帧走图像纹理路径、音频进入 SDL 流,DOM 媒体 API 暴露
8
8
  `play`/`pause`/`currentTime`/… 与媒体事件。只启用 `gui` 不会链接 FFmpeg。浏览器全局还带有真正可用的 `AudioContext`:`runtime/ext_gui/audio_mixer.cpp` 中的 48 kHz 混音器,以及 `decodeAudioData`(WAV 由引擎内解码,压缩格式经 `media`)——见 [GUI API](./gui/api.md#web-audio)。**M14** 增加了 `<canvas>`,配一个自研的 2D 软件光栅器(抗锯齿
@@ -12,7 +12,7 @@
12
12
  渲染器,因此盒子(以及其中的文本、图像与子元素)可以被平移、缩放与旋转而
13
13
  不触发任何重排——再加上 `transition: transform`,这些姿态可以动画(自绘的示例
14
14
  `examples/gui/pelican-svg` 正是用它让矢量画动起来)。参见
15
- [布局](./gui/layout.md#变换m15)与[动画](./gui/animation.md#css-transformm15)。**M17** 增加了真正的 **WebGL 1.0**,**M18** 增加了 **WebGL 2.0**:`<canvas>.getContext("webgl2")` 接受 `#version 300 es` 着色器、顶点数组对象、实例化绘制与采样器对象,全部由内置 glslang 编译为 SPIR-V(Metal 上再由内置 SPIRV-Cross 转成 MSL)并通过 SDL_GPU 渲染,几何、光照、纹理采样与混合都在 GPU 上完成,页面直接合成颜色纹理而无须读回——见 [WebGL](./gui/webgl.md)。**M19** 补齐 CSS 装饰层:`@media` 查询、生成内容(`::before`/`::after`)、线性/径向多层背景、`text-shadow`、`clip-path` 与 `filter` 颜色/阴影效果。`backdrop-filter` 与真正的组模糊需要离屏合成器,仍为有文档说明的近似或未支持——见[绘制](./gui/rendering.md#背景生成内容与特效m19)。本总览记录了锁定决策、架构、里程碑计划与当前进度;引擎本身按功能拆分在以上各页中。
15
+ [布局](./gui/layout.md#变换m15)与[动画](./gui/animation.md#css-transformm15)。**M17** 增加了真正的 **WebGL 1.0**,**M18** 增加了 **WebGL 2.0**:`<canvas>.getContext("webgl2")` 接受 `#version 300 es` 着色器、顶点数组对象、实例化绘制与采样器对象,全部由内置 glslang 编译为 SPIR-V(Metal 上再由内置 SPIRV-Cross 转成 MSL)并通过 SDL_GPU 渲染,几何、光照、纹理采样与混合都在 GPU 上完成,页面直接合成颜色纹理而无须读回——见 [WebGL](./gui/webgl.md)。**M19** 补齐 CSS 装饰层:`@media` 查询、生成内容(`::before`/`::after`)、线性/径向多层背景、`text-shadow`、`clip-path` 与 `filter` 颜色/阴影效果。`backdrop-filter` 与真正的组模糊需要离屏合成器,仍为有文档说明的近似或未支持——见[绘制](./gui/rendering.md#背景生成内容与特效m19)。**M20** 让大文档保持可交互:`@media` 每条规则只求值一次,布局与绘制共享一个有界的整形缓存,一帧只做(也只提交)变更所要求的工作——见[性能](./gui/rendering.md#性能m20)。本总览记录了锁定决策、架构、里程碑计划与当前进度;引擎本身按功能拆分在以上各页中。
16
16
 
17
17
  ## 目录
18
18
 
@@ -273,6 +273,16 @@ FFmpeg `7.1`(供可选的 `media` 归档使用;可用 `SDL3_TAG` /
273
273
  [已知问题](./gui/known-issues.md))。
274
274
  - e2e 覆盖见 `tests/e2e/gui-effects.test.ts`;参见
275
275
  [绘制](./gui/rendering.md#背景生成内容与特效m19)。
276
+ 20. **M20 — 渲染性能** ✅
277
+ - `@media` 每次重算样式时每条规则只求值一次;没有伪元素的样式表跳过生成
278
+ 内容;只有样式表跟踪 `:hover`/`:focus` 时才因它们重算样式。
279
+ - 有界的两代整形缓存同时服务布局与绘制,并缓存主字体。
280
+ - 帧循环:只在文档变化时对齐媒体/画布表,只在发生事情时提交呈现,并据此
281
+ 决定是否执行整帧工作。
282
+ - 复用显示列表/顶点暂存/transfer buffer,一个脏帧只提交一次上传,去掉
283
+ 冗余的渲染状态绑定。
284
+ - `win.stats()`/`XT_GUI_PERF` 计数器与 `tests/e2e/gui-perf.test.ts`;参见
285
+ [性能](./gui/rendering.md#性能m20)。
276
286
 
277
287
  ## 进度日志
278
288
 
@@ -402,4 +412,17 @@ FFmpeg `7.1`(供可选的 `media` 归档使用;可用 `SDL3_TAG` /
402
412
  采样模糊/投影并生成 `text-shadow` run,`paint_clip.cpp`/`renderer_clip.cpp`
403
413
  对三角化的 `clip-path` 多边形裁剪几何。e2e 见
404
414
  `tests/e2e/gui-effects.test.ts`。
415
+ - **M20** ✅ 渲染性能。`@media` 每次重算样式时每条规则只求值一次;没有
416
+ `::before`/`::after` 的样式表完全跳过生成内容;只有样式表包含
417
+ `:hover`/`:focus` 规则时才因这两个状态重算样式(`style.cpp`、
418
+ `css_sheet.cpp`)。一个有界的两代整形缓存(`text_cache.cpp`)同时服务布局与
419
+ 绘制,因此一个词只整形一次,而不是每次布局和重绘都整形一次;主字体也按
420
+ (类别,字号)缓存。帧循环只在文档变化时对齐解码器/画布,并且只在确实发生了
421
+ 事情时提交呈现(`gui_events.cpp`),因此静态页面不产生任何 GPU 工作
422
+ (调试时可用 `XT_GUI_ALWAYS_PRESENT=1` 恢复旧行为)。显示列表、顶点暂存与
423
+ transfer buffer 都被复用,一个脏帧的多次上传共用一次命令缓冲区提交,渲染器
424
+ 只在状态真正变化时重新绑定(`renderer*.cpp`、`gui_engine.h`)。
425
+ `win.stats()` 报告帧耗时与累计工作量计数器,`XT_GUI_PERF` 打印它们
426
+ (`perf.{h,cpp}`);`tests/e2e/gui-perf.test.ts` 用 200 行列表固化这些断言。
427
+ 参见[性能](./gui/rendering.md#性能m20)。
405
428
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xbintsc",
3
- "version": "0.5.38",
3
+ "version": "0.5.41",
4
4
  "description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -72,12 +72,12 @@
72
72
  "vitest": "^2.1.8"
73
73
  },
74
74
  "optionalDependencies": {
75
- "@xbintsc/linux-x64": "0.5.38",
76
- "@xbintsc/linux-arm64": "0.5.38",
77
- "@xbintsc/darwin-x64": "0.5.38",
78
- "@xbintsc/darwin-arm64": "0.5.38",
79
- "@xbintsc/win32-x64": "0.5.38",
80
- "@xbintsc/win32-arm64": "0.5.38"
75
+ "@xbintsc/linux-x64": "0.5.41",
76
+ "@xbintsc/linux-arm64": "0.5.41",
77
+ "@xbintsc/darwin-x64": "0.5.41",
78
+ "@xbintsc/darwin-arm64": "0.5.41",
79
+ "@xbintsc/win32-x64": "0.5.41",
80
+ "@xbintsc/win32-arm64": "0.5.41"
81
81
  },
82
82
  "engines": {
83
83
  "node": ">=22"
@@ -337,6 +337,7 @@ xt_value type_proto(const std::string &name) {
337
337
  if (name == "WebGLSync") return domapi::webgl_object_proto("sync");
338
338
  if (name == "WebGLTransformFeedback") return domapi::webgl_object_proto("transformfeedback");
339
339
  if (name == "Path2D") return domapi::path2d_proto();
340
+ if (name == "OffscreenCanvas") return domapi::offscreen_canvas_proto();
340
341
  if (name == "ImageBitmap") {
341
342
  /* A bitmap is a plain immutable object, not a DOM node: a node proto would
342
343
  * expose the element width/height accessors and answer 0. */
@@ -383,6 +384,7 @@ xt_value host_type_construct(xt_value self, xt_value env, int32_t argc, xt_value
383
384
  return domapi::event_construct(xt_arg(argc, argv, 0), xt_arg(argc, argv, 1));
384
385
  }
385
386
  if (name == "Path2D") return domapi::path2d_create(xt_arg(argc, argv, 0));
387
+ if (name == "OffscreenCanvas") return domapi::offscreen_canvas_create(argc, argv);
386
388
  xt_value target = xt_object_new_with_proto(type_proto(name));
387
389
  set_prop(target, "constructor", type_value(name));
388
390
  return target;
@@ -518,7 +520,7 @@ xt_value xt_browser_type(int32_t argc, xt_value *argv) {
518
520
  (void)argv;
519
521
  if (argc < 1) return XT_UNDEFINED;
520
522
  std::string name = cstr(xt_arg(argc, argv, 0));
521
- if (name == "XRWebGLLayer" || name == "XRWebGLBinding" || name == "OffscreenCanvas" ||
523
+ if (name == "XRWebGLLayer" || name == "XRWebGLBinding" ||
522
524
  name == "ImageData" || name == "Response" ||
523
525
  name == "ReadableStream" || name == "Request" || name == "Headers" ||
524
526
  name == "DOMParser") {