xbintsc 0.5.38 → 0.5.42
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.
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/codegen/generator/module.js +23 -7
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/html.js +9 -2
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/tests/codegen/closures.test.js +7 -0
- package/dist/tests/codegen/closures.test.js.map +1 -1
- package/dist/tests/e2e/compile-objects-classes.test.js +31 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/gui-canvas-state.test.d.ts +9 -0
- package/dist/tests/e2e/gui-canvas-state.test.js +297 -0
- package/dist/tests/e2e/gui-canvas-state.test.js.map +1 -0
- package/dist/tests/e2e/gui-data-url.test.d.ts +10 -0
- package/dist/tests/e2e/gui-data-url.test.js +127 -0
- package/dist/tests/e2e/gui-data-url.test.js.map +1 -0
- package/dist/tests/e2e/gui-draw-perf.test.d.ts +22 -0
- package/dist/tests/e2e/gui-draw-perf.test.js +405 -0
- package/dist/tests/e2e/gui-draw-perf.test.js.map +1 -0
- package/dist/tests/e2e/gui-effects.test.js +96 -0
- package/dist/tests/e2e/gui-effects.test.js.map +1 -1
- package/dist/tests/e2e/gui-paint.test.js +77 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/dist/tests/e2e/gui-perf.test.d.ts +23 -0
- package/dist/tests/e2e/gui-perf.test.js +283 -0
- package/dist/tests/e2e/gui-perf.test.js.map +1 -0
- package/dist/tests/e2e/gui-script-import-order.test.js +9 -1
- package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl.test.js +4 -4
- package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.d.ts +8 -0
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js +336 -0
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.js +23 -0
- package/dist/tests/extensions/gui.test.js.map +1 -1
- package/doc/gui/api.md +28 -2
- package/doc/gui/canvas.md +114 -27
- package/doc/gui/known-issues.md +47 -19
- package/doc/gui/media.md +11 -2
- package/doc/gui/rendering.md +120 -18
- package/doc/gui/runtime.md +18 -3
- package/doc/gui/webgl.md +38 -19
- package/doc/gui.md +38 -5
- package/doc/zh-CN/gui/api.md +25 -2
- package/doc/zh-CN/gui/canvas.md +97 -26
- package/doc/zh-CN/gui/known-issues.md +32 -12
- package/doc/zh-CN/gui/media.md +9 -2
- package/doc/zh-CN/gui/rendering.md +89 -11
- package/doc/zh-CN/gui/runtime.md +13 -3
- package/doc/zh-CN/gui/webgl.md +27 -5
- package/doc/zh-CN/gui.md +25 -2
- package/package.json +7 -7
- package/runtime/ext_gui/browser.cpp +3 -1
- package/runtime/ext_gui/canvas.cpp +135 -89
- package/runtime/ext_gui/canvas.h +116 -0
- package/runtime/ext_gui/canvas_clip.cpp +143 -0
- package/runtime/ext_gui/canvas_composite.cpp +241 -0
- package/runtime/ext_gui/canvas_coverage.cpp +241 -0
- package/runtime/ext_gui/canvas_gradient.cpp +41 -4
- package/runtime/ext_gui/canvas_image_data.cpp +4 -0
- package/runtime/ext_gui/canvas_internal.h +34 -9
- package/runtime/ext_gui/canvas_raster.cpp +207 -178
- package/runtime/ext_gui/canvas_shadow.cpp +161 -0
- package/runtime/ext_gui/canvas_stroke.cpp +37 -9
- package/runtime/ext_gui/canvas_style.cpp +59 -0
- package/runtime/ext_gui/canvas_text.cpp +140 -0
- package/runtime/ext_gui/css.h +7 -0
- package/runtime/ext_gui/css_sheet.cpp +22 -0
- package/runtime/ext_gui/document.cpp +25 -1
- package/runtime/ext_gui/document.h +8 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +48 -15
- package/runtime/ext_gui/dom_api_canvas_image.cpp +9 -0
- package/runtime/ext_gui/dom_api_canvas_state.cpp +217 -0
- package/runtime/ext_gui/dom_api_internal.h +36 -37
- package/runtime/ext_gui/dom_api_offscreen.cpp +213 -0
- package/runtime/ext_gui/dom_api_proto.cpp +18 -0
- package/runtime/ext_gui/dom_api_webgl.cpp +19 -9
- package/runtime/ext_gui/dom_api_webgl_internal.h +58 -0
- package/runtime/ext_gui/dom_api_webgl_source.cpp +18 -0
- package/runtime/ext_gui/gradient_values.cpp +56 -49
- package/runtime/ext_gui/gui.cpp +5 -0
- package/runtime/ext_gui/gui_canvas.cpp +4 -0
- package/runtime/ext_gui/gui_engine.h +30 -0
- package/runtime/ext_gui/gui_events.cpp +185 -58
- package/runtime/ext_gui/gui_media.cpp +3 -0
- package/runtime/ext_gui/image.cpp +225 -1
- package/runtime/ext_gui/image.h +6 -4
- package/runtime/ext_gui/image_bitmap_browser.cpp +11 -0
- package/runtime/ext_gui/layout.cpp +4 -0
- package/runtime/ext_gui/paint.cpp +72 -3
- package/runtime/ext_gui/paint.h +11 -1
- package/runtime/ext_gui/paint_background.cpp +110 -12
- package/runtime/ext_gui/paint_effects.cpp +2 -1
- package/runtime/ext_gui/paint_internal.h +5 -1
- package/runtime/ext_gui/perf.cpp +171 -0
- package/runtime/ext_gui/perf.h +127 -0
- package/runtime/ext_gui/renderer.cpp +190 -54
- package/runtime/ext_gui/renderer.h +52 -14
- package/runtime/ext_gui/renderer_image.cpp +135 -65
- package/runtime/ext_gui/renderer_internal.h +41 -3
- package/runtime/ext_gui/renderer_layer.cpp +552 -0
- package/runtime/ext_gui/renderer_text.cpp +24 -12
- package/runtime/ext_gui/style.cpp +101 -30
- package/runtime/ext_gui/style.h +35 -4
- package/runtime/ext_gui/style_print.cpp +15 -1
- package/runtime/ext_gui/style_values.cpp +10 -0
- package/runtime/ext_gui/text.cpp +23 -4
- package/runtime/ext_gui/text.h +29 -0
- package/runtime/ext_gui/text_cache.cpp +118 -0
- package/runtime/ext_gui/text_glyph_cache.cpp +100 -0
- package/runtime/ext_gui/webgl.h +48 -146
- package/runtime/ext_gui/webgl_blit.cpp +12 -1
- package/runtime/ext_gui/webgl_blit_depth.cpp +270 -0
- package/runtime/ext_gui/webgl_buffer.cpp +59 -1
- package/runtime/ext_gui/webgl_buffer_objects.cpp +106 -9
- package/runtime/ext_gui/webgl_clear.cpp +3 -0
- package/runtime/ext_gui/webgl_context.cpp +29 -3
- package/runtime/ext_gui/webgl_draw.cpp +75 -270
- package/runtime/ext_gui/webgl_draw_buffers.cpp +1 -0
- package/runtime/ext_gui/webgl_draw_setup.cpp +321 -0
- package/runtime/ext_gui/webgl_draw_state.h +130 -0
- package/runtime/ext_gui/webgl_glsl_scan.cpp +3 -8
- package/runtime/ext_gui/webgl_objects.h +148 -0
- package/runtime/ext_gui/webgl_pipeline.cpp +8 -8
- package/runtime/ext_gui/webgl_program_internal.h +35 -0
- package/runtime/ext_gui/webgl_shader.cpp +22 -30
- package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
- package/runtime/ext_gui/window.cpp +56 -1
- package/src/codegen/generator/module.ts +22 -7
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/html.ts +9 -2
package/doc/gui.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# xbintsc GUI extension (self-hosted HTML/CSS renderer)
|
|
2
2
|
|
|
3
|
-
Status: **
|
|
3
|
+
Status: **M20** — features (M1–M15) are complete: HTML parsing, CSS selector
|
|
4
4
|
matching, the cascade, computed styles and layout (block, inline, Flexbox and
|
|
5
5
|
CSS positioning) are
|
|
6
6
|
in place, and the engine *paints*: it builds a display list of rectangles, images
|
|
@@ -40,10 +40,15 @@ them) can be translated, scaled and rotated without reflowing anything — and
|
|
|
40
40
|
[Animation](./gui/animation.md#css-transforms-m15).
|
|
41
41
|
**M17** adds real **WebGL 1.0**, and **M18** adds **WebGL 2.0**: `<canvas>.getContext("webgl2")` accepts `#version 300 es` shaders, vertex-array objects, instanced draws and sampler objects, all compiled to SPIR-V by the bundled glslang (and to MSL by the bundled SPIRV-Cross on Metal) and rendered through SDL_GPU. Geometry, lighting, texture sampling and blending run on the GPU and the page composites the colour texture without a readback — see [WebGL](./gui/webgl.md).
|
|
42
42
|
**M19** fills in the decorative CSS layer: `@media` queries, generated content
|
|
43
|
-
(`::before`/`::after`), multi-layer linear/radial backgrounds,
|
|
44
|
-
`clip-path
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
(`::before`/`::after`), multi-layer linear/radial/repeating backgrounds,
|
|
44
|
+
`text-shadow`, `clip-path`, `mix-blend-mode` (through an offscreen compositor)
|
|
45
|
+
and the `filter` colour/shadow effects. `backdrop-filter` still needs a backdrop
|
|
46
|
+
snapshot, and true group blur remains an approximation — see
|
|
47
|
+
[Rendering](./gui/rendering.md#backgrounds-generated-content-and-effects-m19).
|
|
48
|
+
**M20** makes large documents interactive: `@media` is evaluated once per rule,
|
|
49
|
+
layout and paint share a bounded shaping cache, and a frame only does (and
|
|
50
|
+
presents) the work a change requires — see
|
|
51
|
+
[Performance](./gui/rendering.md#performance-m20).
|
|
47
52
|
This overview records the locked decisions, the architecture, the milestone plan
|
|
48
53
|
and the current progress; the engine itself is documented by function in the
|
|
49
54
|
pages above.
|
|
@@ -342,6 +347,18 @@ engine were Rust.
|
|
|
342
347
|
offscreen backdrop pass (see [Known issues](./gui/known-issues.md)).
|
|
343
348
|
- E2E coverage in `tests/e2e/gui-effects.test.ts`; see
|
|
344
349
|
[Rendering](./gui/rendering.md#backgrounds-generated-content-and-effects-m19).
|
|
350
|
+
20. **M20 — rendering performance** ✅
|
|
351
|
+
- `@media` evaluated once per rule per restyle, generated content skipped for
|
|
352
|
+
sheets without pseudo-elements, and `:hover`/`:focus` restyle only when
|
|
353
|
+
the sheet tracks them.
|
|
354
|
+
- Bounded two-generation shaping cache shared by layout and paint, plus a
|
|
355
|
+
primary-face cache.
|
|
356
|
+
- Frame loop: reconcile media/canvas only when the document changed, present
|
|
357
|
+
only when something happened, and gate the whole pass behind it.
|
|
358
|
+
- Reused display list/vertex staging/transfer buffers, one upload submit per
|
|
359
|
+
dirty frame, and redundant render-state binds removed.
|
|
360
|
+
- `win.stats()`/`XT_GUI_PERF` counters and `tests/e2e/gui-perf.test.ts`; see
|
|
361
|
+
[Performance](./gui/rendering.md#performance-m20).
|
|
345
362
|
|
|
346
363
|
## Progress log
|
|
347
364
|
|
|
@@ -490,4 +507,20 @@ engine were Rust.
|
|
|
490
507
|
functions plus nine-tap blur/drop-shadow and emits `text-shadow` runs, and
|
|
491
508
|
`paint_clip.cpp`/`renderer_clip.cpp` clip geometry against triangulated
|
|
492
509
|
`clip-path` polygons. E2E: `tests/e2e/gui-effects.test.ts`.
|
|
510
|
+
- **M20** ✅ rendering performance. `@media` is evaluated once per rule per
|
|
511
|
+
restyle, generated content is skipped for sheets without `::before`/`::after`,
|
|
512
|
+
and `:hover`/`:focus` only restyle when the sheet tracks them
|
|
513
|
+
(`style.cpp`, `css_sheet.cpp`). A bounded two-generation shaping cache
|
|
514
|
+
(`text_cache.cpp`) serves layout and paint, so a word is shaped once instead
|
|
515
|
+
of on every layout pass and repaint; primary faces are cached by
|
|
516
|
+
(class, size) too. The frame loop reconciles decoders/canvases only when the
|
|
517
|
+
document changed and presents only when something happened
|
|
518
|
+
(`gui_events.cpp`), so a static page does no GPU work
|
|
519
|
+
(`XT_GUI_ALWAYS_PRESENT=1` restores the old behaviour for debugging). The
|
|
520
|
+
display list, vertex staging and transfer buffers are reused, a dirty frame's
|
|
521
|
+
uploads share one command buffer, and the renderer only re-binds state that
|
|
522
|
+
changed (`renderer*.cpp`, `gui_engine.h`). `win.stats()` reports per-frame
|
|
523
|
+
timings and cumulative work counters and `XT_GUI_PERF` prints them
|
|
524
|
+
(`perf.{h,cpp}`); `tests/e2e/gui-perf.test.ts` pins a 200-row list. See
|
|
525
|
+
[Performance](./gui/rendering.md#performance-m20).
|
|
493
526
|
|
package/doc/zh-CN/gui/api.md
CHANGED
|
@@ -91,8 +91,9 @@ requestAnimationFrame(frame);
|
|
|
91
91
|
| `Event`、`CustomEvent`、`ProgressEvent`、`MouseEvent`、`KeyboardEvent`、`PointerEvent`、`WheelEvent`、`FocusEvent`、`InputEvent`、`SubmitEvent` | 事件构造器;`init` 参数按原样保留(`detail`、`composed`、鼠标/键盘字段等) |
|
|
92
92
|
| `AbortController`、`AbortSignal` | 真实的 controller/signal 对,用于 `{ signal }` 移除监听器,支持 `abort()`、`aborted`、`reason`、`onabort` 与 `throwIfAborted()` |
|
|
93
93
|
| `HTMLCanvasElement`、`HTMLImageElement`、`WebGLRenderingContext`、`WebGL2RenderingContext`、`WebGLBuffer`/`WebGLTexture`/`WebGLShader`/`WebGLProgram`/`WebGLFramebuffer`/`WebGLRenderbuffer`/`WebGLUniformLocation`/`WebGLActiveInfo`/`WebGLVertexArrayObject`/`WebGLSampler`/`WebGLQuery`/`WebGLSync`/`WebGLTransformFeedback` | 真实的构造函数值。`canvas.getContext("webgl")` 与 `getContext("webgl2")` 返回 GPU 上下文(见 [WebGL](./webgl.md))。 |
|
|
94
|
-
| `ImageBitmap`、`createImageBitmap(source)` | 由已解码像素支撑的不可变位图(`<img>`、`<canvas>`、`<video>`
|
|
95
|
-
| `
|
|
94
|
+
| `ImageBitmap`、`createImageBitmap(source)` | 由已解码像素支撑的不可变位图(`<img>`、`<canvas>`、`<video>` 帧、`OffscreenCanvas` 与 `ImageData` 形状);promise 立即 settle,`texImage2D` 可直接接受该对象 |
|
|
95
|
+
| `OffscreenCanvas` | 真正的 2D 离屏位图,带 `getContext("2d")`、`width`/`height`、`transferToImageBitmap()` 与 `convertToBlob()`;`canvas.transferControlToOffscreen()` 会把元素位图交给它(见 [Canvas](./canvas.md))。离屏 WebGL 上下文不提供。 |
|
|
96
|
+
| `ImageData`、`XRWebGL*`、`Request`、`Headers`、`Response`、`ReadableStream`、`DOMParser` | `typeof` 探测返回 `"undefined"`(没有 XR/fetch/流式后端),让页面走自己的“不支持”分支,而不是调用空壳。Three.js 页面在本后端上使用真实渲染器(见[已知问题](./known-issues.md))。 |
|
|
96
97
|
| `requestAnimationFrame` / `cancelAnimationFrame` | 排入窗口的动画帧列表,见[运行时集成](./runtime.md) |
|
|
97
98
|
| `alert` / `confirm` / `prompt`、`matchMedia`、`getComputedStyle`、`escape` | 宿主实现。`matchMedia` 返回**实时**列表(按查询缓存):`matches` 在窗口尺寸变化时重新求值,翻转时触发 `change` 监听器。`confirm`/`prompt` 仅在 `XT_GUI_DIALOGS=1` 时使用原生对话框,否则把消息写到 stderr 并返回文档化的默认值(Annex B `escape` 按 UTF-16 码元编码,与 `decodeURIComponent(escape(bytes))` 一致) |
|
|
98
99
|
|
|
@@ -197,6 +198,28 @@ win.stats() // 引擎/运行时计数器(内存排
|
|
|
197
198
|
`detachedNodes` 或 `handles` 持续上升,说明程序移除节点的速度快于释放速度
|
|
198
199
|
(见[节点生命周期](./dom.md))。
|
|
199
200
|
|
|
201
|
+
同一个对象还报告上一帧做了什么以及程序累计做了多少工作,渲染开销就是靠它度量
|
|
202
|
+
(见[性能](./rendering.md#性能m20)):
|
|
203
|
+
|
|
204
|
+
| 字段 | 含义 |
|
|
205
|
+
| --- | --- |
|
|
206
|
+
| `frameMs`、`restyleMs`、`layoutMs`、`paintMs`、`uploadMs`、`drawMs` | 上一帧各阶段耗时(毫秒) |
|
|
207
|
+
| `frames`、`presents`、`exposes`、`deferred`、`skippedFrames`、`starvedFrames`、`transitionFrames` | 帧循环活动:`presents` 是提交呈现的帧数,`exposes` 是窗口系统用 expose/shown/restore 事件要求的呈现帧数,`deferred` 是取不到可绘制目标而被推迟的呈现帧数,`skippedFrames` 是无需提交的帧数,`starvedFrames` 是要求呈现却拿不到可绘制目标的帧数 |
|
|
208
|
+
| `restyles`、`restyleNodes`、`rulesEvaluated`、`mediaEvaluated`、`layouts`、`layoutBoxes` | 层叠与布局的工作量 |
|
|
209
|
+
| `textShapeCalls`、`textShapeHits`、`textCacheBytes`、`textCacheEntries` | 整形缓存的未命中/命中与占用 |
|
|
210
|
+
| `paints`、`paintOps`、`drawRuns`、`drawCalls`、`uploadSubmits`、`vertices`、`syncNodes` | 显示列表、绘制调用与上传的工作量 |
|
|
211
|
+
| `canvasDraws`、`canvasUploads`、`canvasUploadBytes` | Canvas 2D 绘制次数、纹理上传次数与拷贝字节数(局部重绘只上传脏矩形) |
|
|
212
|
+
| `webglDraws`、`webglPasses`、`webglPipelineMisses`、`webglBufferUploadBytes` | WebGL 绘制次数、打开的渲染 pass 数、新建管线数与缓冲拷贝字节数 |
|
|
213
|
+
|
|
214
|
+
除 `*Ms` 时间外,所有字段都是累计值,因此两次快照相减即得到一帧的工作量。
|
|
215
|
+
`restyleMs` 覆盖层叠**以及**它触发的布局过程(布局过程本身也单独报告为
|
|
216
|
+
`layoutMs`)。`presents - exposes - deferred` 才是仅由页面要求的呈现帧数:
|
|
217
|
+
expose/shown/restore 事件表示窗口内容已丢失、必须重绘,而具体时机由窗口服务器
|
|
218
|
+
决定;deferred 呈现则是重放某一帧在取不到交换链图像时已经构建好的内容,
|
|
219
|
+
`starvedFrames` 统计这些帧(该值持续增长说明窗口完全没有被更新)。
|
|
220
|
+
`XT_GUI_PERF=1` 会把同样的数字打印到 stderr(逐帧一行;`XT_GUI_PERF=<N>` 则每
|
|
221
|
+
N 帧取平均)。
|
|
222
|
+
|
|
200
223
|
它们被 `tests/e2e/gui-*.test.ts` 用来断言解析、选择器匹配、特异性、继承、
|
|
201
224
|
`!important` 与布局几何。它们在之后仍可用于调试。
|
|
202
225
|
|
package/doc/zh-CN/gui/canvas.md
CHANGED
|
@@ -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
|
-
`
|
|
16
|
-
|
|
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
|
|
46
|
-
|
|
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`、`
|
|
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
|
-
|
|
79
|
-
|
|
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,39 @@ 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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
167
|
+
(旋转/斜切/缩放)会被应用:目标矩形变为平行四边形,每个设备像素反查到源矩形。
|
|
168
|
+
`source` 可以是 `<img>` 元素(经图像模块解码)、另一个 `<canvas>`、
|
|
169
|
+
`OffscreenCanvas`、`ImageBitmap`,或——本引擎的扩展——直接传字符串路径/`data:` URL,
|
|
170
|
+
由图像模块解码,因此 SVG 或位图的 data URL 无需 `<img>` 就能绘制。矢量源按目标尺寸
|
|
171
|
+
(`dw`/`dh`)光栅化,放大时 SVG 依然清晰。`imageSmoothingEnabled` 为 `true`(默认)
|
|
172
|
+
时使用双线性采样,为 `false` 时使用最近邻。
|
|
110
173
|
|
|
111
174
|
测试钩子:`getImageData(x, y, w, h)` 把区域复制为 `{ width, height, data }`,其中
|
|
112
175
|
`data` 是按行主序的 RGBA8 字节数组。越界像素为透明。
|
|
113
176
|
|
|
114
|
-
图案:`createPattern(source, repetition?)` 接受 `<img
|
|
115
|
-
|
|
116
|
-
|
|
177
|
+
图案:`createPattern(source, repetition?)` 接受 `<img>`、`<canvas>`、
|
|
178
|
+
`OffscreenCanvas` 或 `ImageBitmap`,在用户空间中平铺(`repeat`、`repeat-x`、
|
|
179
|
+
`repeat-y`、`no-repeat`),因此平铺会跟随当前变换。把结果赋给
|
|
180
|
+
`fillStyle`/`strokeStyle`;重新赋颜色会替换掉图案。
|
|
117
181
|
|
|
118
182
|
图像数据与导出:`createImageData(width, height)` 分配一个空的
|
|
119
183
|
`{ width, height, data }`,`putImageData(imageData, dx, dy[, dirtyX, dirtyY,
|
|
120
|
-
dirtyWidth, dirtyHeight])`
|
|
121
|
-
影响)。`canvas.toDataURL(type?)` 导出 PNG data URL
|
|
122
|
-
type?)` 从微任务中交付 Blob 形状的 `{ type, size }
|
|
123
|
-
`__xt_gui_blob_data` 上;本运行时没有 `URL.createObjectURL`)。
|
|
184
|
+
dirtyWidth, dirtyHeight])` 直接把字节写入位图(不受变换、混合、裁剪或
|
|
185
|
+
`globalAlpha` 影响)。`canvas.toDataURL(type?)` 导出 PNG data URL,
|
|
186
|
+
`canvas.toBlob(callback, type?)` 从微任务中交付 Blob 形状的 `{ type, size }`
|
|
187
|
+
(字节在 `__xt_gui_blob_data` 上;本运行时没有 `URL.createObjectURL`)。
|
|
188
|
+
`OffscreenCanvas.convertToBlob()` 解析为同一形状,`transferToImageBitmap()` 对位图
|
|
189
|
+
做快照。
|
|
190
|
+
|
|
191
|
+
CSS 背景:`background-image: url(...)` 接受文件路径或 `data:` URL(位图载荷或 SVG
|
|
192
|
+
文档,base64 或百分号编码均可),因此可以先把 canvas 导出再作为 CSS 背景使用
|
|
193
|
+
(`element.style.backgroundImage = "url(" + canvas.toDataURL() + ")"`)。图像层会拉伸
|
|
194
|
+
到元素的边框盒;`background-size`、`background-position` 与平铺尚未建模。
|
|
124
195
|
|
|
125
196
|
## 尚未实现
|
|
126
197
|
|
|
127
|
-
|
|
128
|
-
`
|
|
129
|
-
|
|
198
|
+
本后端的平滑质量只有一种过滤(双线性)。离屏画布只支持 2D 上下文(WebGL 上下文需要
|
|
199
|
+
DOM 元素来维护纹理表)。旋转的字形位图仍不会随变换旋转,`strokeText` 仍用描边样式
|
|
200
|
+
填充而非生成轮廓。
|
|
130
201
|
|
|
131
202
|
另见[媒体](./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
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
21
|
+
(比较型 `sampler2DShadow` 采样深度纹理)、立方体贴图/环境贴图与 HDR 渲染目标
|
|
22
|
+
均已支持。
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
- **自动关闭时的偶发崩溃。** 设置 `XT_GUI_AUTOCLOSE_MS` 结束运行时,约五次中有两次会以原生
|
|
@@ -41,22 +43,40 @@
|
|
|
41
43
|
`var()` 自定义属性、`inset` 简写、`box-sizing`、`visibility`/`pointer-events`、
|
|
42
44
|
`@media` 查询、生成内容(`::before`/`::after`)、线性/径向多层背景、
|
|
43
45
|
`text-shadow`、`clip-path`(`inset`/`circle`/`ellipse`/`polygon`)以及 `filter`
|
|
44
|
-
的颜色函数。仍有限制:`
|
|
45
|
-
`
|
|
46
|
-
|
|
46
|
+
的颜色函数。仍有限制:`background-clip`、`background-position`/`size`/`repeat`
|
|
47
|
+
与 `backdrop-filter` 合成都未实现(`backdrop-filter` 会解析并参与层叠,但采样
|
|
48
|
+
背景需要离屏图层,因此不绘制);`mix-blend-mode` 与重复渐变已实现(见
|
|
49
|
+
[渲染](./rendering.md)),但组 `opacity` 是按图形 alpha 近似而非真正的组合成,
|
|
50
|
+
且 D3D12/DXIL 后端没有混合合成器,那里会退回普通 source-over 绘制;
|
|
47
51
|
`filter: blur()` 是按图形做九次偏移的近似,而不是真正的组模糊,重叠的子元素会加深;
|
|
48
52
|
`drop-shadow` 用盒子轮廓近似;最多只交叉两层嵌套 `clip-path`(更深时保留外侧两层);
|
|
49
53
|
图像/画布除图像着色器能携带的亮度与透明度染色外不做颜色滤镜。Canvas 纹理使用
|
|
50
|
-
最近邻采样。背景渐变仍是纯色带(线性 18 条、径向 32
|
|
51
|
-
|
|
54
|
+
最近邻采样。背景渐变仍是纯色带(线性 18 条、径向 32 个同心椭圆;重复渐变
|
|
55
|
+
64 条),柔和渐变可能出现轻微条带。
|
|
52
56
|
- **SVG 是 NanoSVG 的子集。** SVG *文件*(`<img src="logo.svg">`)与内联
|
|
53
57
|
`<svg>` 子树共用 vendored 的 NanoSVG 解析/光栅化器(`runtime/ext_gui/svg.cpp`、
|
|
54
58
|
`inline_svg.cpp`),它会渲染 `path`/`rect`/`circle`/`ellipse`/`line`/
|
|
55
59
|
`polyline`/`polygon`、组、`defs` 渐变以及基于 `class` 的 `<style>` 规则。
|
|
56
60
|
`<text>`、`<use>`、滤镜、遮罩与裁剪路径不会渲染,文档层叠的 CSS 也不会进入
|
|
57
|
-
图像内部(这里的 `fill`/`stroke` 是 SVG 表现属性,不是 CSS
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
图像内部(这里的 `fill`/`stroke` 是 SVG 表现属性,不是 CSS 属性)。因此只靠滤镜
|
|
62
|
+
出图的 SVG(`feTurbulence` 噪声、模糊等)会显示未经过滤的形状——常见情况是一个
|
|
63
|
+
纯色矩形——而不是滤镜结果。需要这些能力的矢量美术必须预先光栅化、用盒子绘制,
|
|
64
|
+
或限制在受支持的语法内。参见[媒体](./media.md#图像m6a)。
|
|
65
|
+
- **每次变更仍是整文档重算。** 一次 DOM 变更会重新计算层叠并重新布局整个文档
|
|
66
|
+
(没有增量子树失效),只影响绘制的变更(例如 hover 颜色)同样会重排。带生成
|
|
67
|
+
内容(`::before`/`::after`)的文档没有特殊处理。各阶段的实测开销由
|
|
68
|
+
`win.stats()`/`XT_GUI_PERF` 暴露,后续优化以它为依据;参见
|
|
69
|
+
[性能](./rendering.md#性能m20)。
|
|
70
|
+
- **绘制调用遵循画家顺序。** 只有当 run 在画家顺序上相邻且 pipeline、裁剪与
|
|
71
|
+
纹理都相同时才会合并,因此"背景与文本逐行交替"的列表每行要付一个图形 run 和
|
|
72
|
+
一个文本 run(见 `win.stats()` 的 `drawCalls`)。要减少绘制调用需要感知重叠的
|
|
73
|
+
合并器(文本可能被后续背景覆盖),目前未实现。实测:200 行基准页发出 400 次
|
|
74
|
+
绘制调用,绘制阶段约耗时 0.4 ms,因此它是"绘制调用占主导"的页面的后续优化项,
|
|
75
|
+
而不是当前的瓶颈。
|
|
76
|
+
- **文本整形缓存有界。** 两代、每代最多 2 MiB 字形 / 8,192 条条目保存
|
|
77
|
+
`(face, text) -> 字形`。不同单词数超过上限的文档在缓存轮换后会重新整形一部分;
|
|
78
|
+
`win.stats()` 的 `textCacheBytes`/`textShapeHits` 能看出它是否成为瓶颈。
|
|
79
|
+
元素自带的 `style="…"` 内联属性在该元素每次重算样式时都会重新解析。
|
|
60
80
|
|
|
61
81
|
## 待决问题
|
|
62
82
|
|
package/doc/zh-CN/gui/media.md
CHANGED
|
@@ -35,6 +35,13 @@ PNG/JPEG/BMP/GIF/TGA 解码为 RGBA8,并带有解码缓存与仅头文件的
|
|
|
35
35
|
(最近最少使用),动画文档不会让它无限增长。
|
|
36
36
|
- 解析结果按路径缓存;解析与光栅化器状态在 `xt_svg_shutdown`
|
|
37
37
|
(`xt_image_shutdown`)中释放。
|
|
38
|
+
- `data:` URL 在内存中解码,凡接受路径的地方都可使用:`<img src>`、CSS
|
|
39
|
+
`background-image` 与 `canvas.drawImage(url, …)`。既支持 `;base64` 形式,也支持
|
|
40
|
+
百分号编码(或原样)的载荷,`base64` 标志大小写不敏感。MIME 类型只用于分类:
|
|
41
|
+
`image/svg+xml`(或 MIME 为通用/缺失时按内容嗅探出 SVG)走 NanoSVG——data URL
|
|
42
|
+
里的 SVG 与 SVG 文件一样按绘制尺寸光栅化;PNG/JPEG/BMP/GIF/TGA 走
|
|
43
|
+
`stb_image`。NanoSVG 的子集限制仍然生效:只靠 `<filter>`(`feTurbulence` 等)
|
|
44
|
+
出图的 SVG 能解码、能布局,但滤镜本身会被忽略。
|
|
38
45
|
|
|
39
46
|
文档 CSS 只作用于 `<svg>` **盒子**——尺寸、外边距、背景、边框、`transform`、
|
|
40
47
|
opacity、`display`——与任何其他元素一样。盒子内部的艺术内容保持 SVG 语义:
|
|
@@ -45,8 +52,8 @@ opacity、`display`——与任何其他元素一样。盒子内部的艺术内
|
|
|
45
52
|
重写艺术内容的程序会为每个变体保留一张纹理;更好的做法是用 `transform`、
|
|
46
53
|
`opacity` 给盒子做动画,或在一小组文档之间切换。
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
图像仍需完成:`object-fit`、九宫格边框。(CSS `background-image: url(...)` 与
|
|
56
|
+
`data:` URL 已支持。)
|
|
50
57
|
|
|
51
58
|
两种 SVG 都受限于 NanoSVG 的子集:`path`、`rect`、`circle`、`ellipse`、
|
|
52
59
|
`line`、`polyline`、`polygon`、组(`g`)、带线性/径向渐变的 `defs` 以及基于
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
顺序都能保留到渲染器。`DisplayList::dump()` 为 `paintList()`/`paintCount()`
|
|
16
16
|
提供数据。
|
|
17
17
|
|
|
18
|
-
`opacity`
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
`opacity` 会乘到该盒子及其后代绘制的每个颜色 alpha 上(沿绘制链逐层相乘),
|
|
19
|
+
因此小数 opacity 以及 CSS 解析器接受的 `opacity` 过渡都能看到效果。这是按图形
|
|
20
|
+
逐个近似的,而不是离屏分组:半透明盒子的重叠子元素会各自合成,浏览器则先把整组
|
|
21
|
+
合成一次再应用 alpha。`opacity: 0` 仍会跳过整棵子树。把隐藏态写成
|
|
21
22
|
`opacity: 0` 来隐藏/显示一层,正是 `examples/gui/pelican-bike` 游戏结束遮罩的做法。
|
|
22
23
|
|
|
23
24
|
`runtime/ext_gui/renderer.{h,cpp}` 把该列表转换为每个窗口的批处理顶点缓冲
|
|
@@ -67,14 +68,20 @@
|
|
|
67
68
|
`background-color` 与每一条 `background-image` 层都会绘制。逗号分隔的层列表按
|
|
68
69
|
从底到上的顺序绘制(声明中的第一层在最上面),两种渐变均受支持:
|
|
69
70
|
|
|
70
|
-
- **线性**渐变在 18
|
|
71
|
-
`deg`/`grad`/`rad`/`turn
|
|
71
|
+
- **线性**渐变在 18 个位置采样(重复渐变 64 个)并输出为纯色矩形;支持
|
|
72
|
+
`to <side>`、`deg`/`grad`/`rad`/`turn`、百分比或长度(`px`/`em`/…)偏移、
|
|
73
|
+
`color <start> <end>` 简写与多色标(色带沿最近轴行走,因此斜角会退化为
|
|
72
74
|
最近的轴向);
|
|
73
|
-
- **径向**渐变是 32
|
|
74
|
-
transform
|
|
75
|
-
|
|
75
|
+
- **径向**渐变是 32 个同心椭圆(重复渐变 64 个)。每个都是单位半径的圆角矩形,
|
|
76
|
+
经图形管线的 transform 槽映射,因此圆/椭圆仍保留距离场抗锯齿。支持形状/尺寸
|
|
77
|
+
关键字(`circle`/`ellipse`、`closest-side`/`farthest-side`/`closest-corner`/
|
|
76
78
|
`farthest-corner`、显式半径)与 `at <position>`。
|
|
77
79
|
|
|
80
|
+
`repeating-linear-gradient()` 与 `repeating-radial-gradient()` 会在整个盒子上
|
|
81
|
+
从 0 平铺到最后一个色标的偏移量;色标列表不会被强制结束在 100%,因此
|
|
82
|
+
`red 0%, blue 10%` 每 10% 重复一次。普通渐变的最后一个色标若小于 100%,则剩余
|
|
83
|
+
区域保持该颜色,与 CSS 一致。
|
|
84
|
+
|
|
78
85
|
背景色位于所有层下方。若渐变色标带透明度,渐变也会半透明;色带近似可能产生
|
|
79
86
|
轻微条带,非常透明的多色标渐变会出现 alpha 叠加。
|
|
80
87
|
|
|
@@ -96,9 +103,21 @@ shader 改动,但边缘是硬边(没有解析抗锯齿)。支持两层嵌
|
|
|
96
103
|
**`filter`** 的颜色函数(`brightness`、`contrast`、`grayscale`、`sepia`、
|
|
97
104
|
`invert`、`saturate`、`hue-rotate`、`opacity`)会折进每个绘制颜色,因此盒子与
|
|
98
105
|
其后代都会看到滤镜。`blur()` 与 `drop-shadow()` 是显示列表层面的近似:模糊把每个
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
图形展开成九个半透明副本,投影用盒子/文字轮廓。这样普通绘制保持单渲染通道,代价
|
|
107
|
+
是组语义:被模糊/滤镜的组内重叠后代可能加深;`backdrop-filter` 无法采样元素背后
|
|
108
|
+
的内容,因此会解析并参与层叠,但不绘制。
|
|
109
|
+
|
|
110
|
+
**`mix-blend-mode`** 是唯一真正需要离屏图层的例外。非 `normal` 的盒子会被
|
|
111
|
+
`LayerBegin`/`LayerEnd` 标记包住(在 `paintList()` 中显示为 `layer blend=…` /
|
|
112
|
+
`end-layer`),渲染器把该组绘制到全视口离屏纹理,再用一个小型 GLSL 混合
|
|
113
|
+
shader 合成到场景上。全部十六种 CSS 模式(`multiply`、`screen`、`overlay`、
|
|
114
|
+
`darken`、`lighten`、`color-dodge`、`color-burn`、`hard-light`、`soft-light`、
|
|
115
|
+
`difference`、`exclusion`、`hue`、`saturation`、`color`、`luminosity`)都按
|
|
116
|
+
合成规范公式实现。shader 在运行时由内置 glslang 编译(Vulkan 用 SPIR-V),
|
|
117
|
+
再由 SPIRV-Cross 交叉编译为 MSL(Metal);每个嵌套层级的两张场景纹理跨帧复用,
|
|
118
|
+
并通过 ping-pong 让背景在写入时仍可采样。既不吃 SPIR-V 也不吃 MSL 的后端
|
|
119
|
+
(这里的 D3D12/DXIL 路径没有 shader 编译器)仍用普通 source-over 混合绘制该组。
|
|
120
|
+
`backdrop-filter` 依旧不绘制。
|
|
102
121
|
|
|
103
122
|
## 文本技术栈(M4b)
|
|
104
123
|
|
|
@@ -128,3 +147,62 @@ shader 改动,但边缘是硬边(没有解析抗锯齿)。支持两层嵌
|
|
|
128
147
|
- 布局使用这些真实度量进行文本宽度、断行与 `line-height: normal` 计算;
|
|
129
148
|
`measureText`/`fontMetrics` 把它们暴露给测试。
|
|
130
149
|
|
|
150
|
+
## 性能(M20)
|
|
151
|
+
|
|
152
|
+
引擎是保留模式管线:一次 DOM 变更会重算样式、重新布局、重建显示列表并上传
|
|
153
|
+
顶点数组;没有任何变化的帧不产生 CPU 或 GPU 工作。为了让上千个元素的文档保持
|
|
154
|
+
可交互,每个阶段都有预算,并且都可以测量。设置 `XT_GUI_PERF=1` 会逐帧打印一行
|
|
155
|
+
(`XT_GUI_PERF=<N>` 则每 N 帧聚合一行);`win.stats()` 返回同样的计数器,
|
|
156
|
+
`tests/e2e/gui-perf.test.ts` 断言的正是这些计数。
|
|
157
|
+
|
|
158
|
+
- **层叠。** 每次重算样式时,`@media` 前导条件**每条规则只求值一次**(过去是
|
|
159
|
+
每条规则 × 每个节点各解析一次,这是大文档上最大的一笔开销)。样式表里没有
|
|
160
|
+
`::before`/`::after` 规则时完全跳过生成内容;声明候选列表在深度优先遍历中
|
|
161
|
+
复用而不是每个元素分配一次;只有样式表真的包含 `:hover`/`:focus` 规则时,
|
|
162
|
+
这两个状态变化才会触发重算。
|
|
163
|
+
- **文本。** 布局与绘制共享一个有界的整形缓存(`text_cache.cpp`):
|
|
164
|
+
`(face, text) -> 字形 + 前进宽度`。它使用两代、每代最多 2 MiB 字形 / 8,192 条
|
|
165
|
+
条目;某一代写满时丢弃**备用**代并切换过去,因此热路径上没有 LRU 记账。
|
|
166
|
+
一个词只整形一次,之后每次布局和重绘都复用它。主字体查找也按
|
|
167
|
+
(字体族类别,字号)缓存。
|
|
168
|
+
- **帧循环。** 只有文档发生变化(或首帧)时才把媒体/画布表与 DOM 对齐,不再每帧
|
|
169
|
+
遍历整棵树。只有确实发生了事情才提交呈现(present)——DOM 变更、滚动、动画帧、
|
|
170
|
+
过渡、新的视频/画布帧、expose/resize 事件——因此静态页面完全不获取 swapchain、
|
|
171
|
+
也不绘制。expose 是唯一由窗口系统(而非页面)要求的呈现,`exposes` 统计这些帧,
|
|
172
|
+
因此即使窗口服务器很晚才映射窗口,`presents - exposes - deferred` 仍然只是
|
|
173
|
+
文档自身的工作量。取不到交换链图像的帧(GPU 仍持有图像时 SDL 的非阻塞获取会跳过
|
|
174
|
+
该帧,最小化的窗口则完全没有图像)不会丢弃这次失效,而是保留到下一帧再呈现,
|
|
175
|
+
这类呈现计为 `deferred`。调试"漏掉的失效"时可用 `XT_GUI_ALWAYS_PRESENT=1`
|
|
176
|
+
恢复旧的每帧提交。
|
|
177
|
+
- **绘制与上传。** 显示列表和顶点暂存数组挂在窗口中按帧清理,容量得以保留。
|
|
178
|
+
一个脏帧的所有顶点/图集上传共用一个命令缓冲区(一次提交),几何体的暂存
|
|
179
|
+
transfer buffer 被复用(上一帧的拷贝可能仍在飞行中,因此按 cycle 方式映射),
|
|
180
|
+
没有任何子元素带 z-index 时绘制会跳过排序和临时数组。
|
|
181
|
+
- **绘制调用。** 相邻且 pipeline、裁剪和纹理相同的 op 仍会合并为一个 run,
|
|
182
|
+
渲染器现在只在 pipeline / 顶点缓冲 / 裁剪矩形真正变化时才重新绑定。
|
|
183
|
+
`win.stats()` 里的 `drawCalls`/`drawMs` 报告 run 数量与其开销,它们是后续工作
|
|
184
|
+
(感知重叠的合并)的度量依据——在它落地之前,画家顺序要求背景与文本交替的
|
|
185
|
+
run 必须保持独立。
|
|
186
|
+
- **立即模式绘制表面。** `<canvas>` 2D 与 WebGL 由脚本驱动,开销按调用次数计
|
|
187
|
+
而不是按文档变更计;上面的计数器只覆盖保留模式管线。
|
|
188
|
+
`tests/e2e/gui-draw-perf.test.ts` 测量它们:在参考的 Windows x64/Vulkan 机器上,
|
|
189
|
+
一帧 1280×720、包含 5,000 个纯色 `fillRect`、200 条 8 点折线、300 次
|
|
190
|
+
`fillText` 和 200 次 1:1 `drawImage` 的场景从 231ms 降到 158ms(填充 4.3ms、
|
|
191
|
+
描边 151ms、文本 1.4ms、图像 1.6ms);2,000 次带逐次矩阵的 WebGL
|
|
192
|
+
`drawElements` 从 38.6ms 降到 27.5ms,同时每帧打开的渲染 pass 从 2,001 次
|
|
193
|
+
降到 1 次,向 4 MiB 缓冲写入 64 KiB 的 `bufferSubData` 不再重传整个缓冲。
|
|
194
|
+
100×100 的画布局部重绘现在只上传 40 KB,而不是整张 1.2 MB 位图。测试断言的是
|
|
195
|
+
计数器(每帧一次上传、稳定状态不新建管线、只传脏字节),因此墙钟数字仅供
|
|
196
|
+
参考,CI 上的软件 GPU 不会被误判。
|
|
197
|
+
|
|
198
|
+
已知限制:一次变更仍会对**整个**文档重算样式并重新布局(没有增量子树失效),
|
|
199
|
+
只影响绘制的变更(例如 hover 颜色)目前同样会重排;后续工作应以 `win.stats()`
|
|
200
|
+
的数据为依据。元素自带的 `style="…"` 内联属性在该元素每次重算样式时都会重新解析。
|
|
201
|
+
|
|
202
|
+
作为参照,`tests/e2e/gui-perf.test.ts` 会构建一个 200 行的列表(约 1,000 个元素、
|
|
203
|
+
400 个绘制 run)并打印实测数字,例如在 Windows x64/Vulkan 机器上修改一个
|
|
204
|
+
`textContent` 字段得到
|
|
205
|
+
`textFrame=5.2ms restyle=3.6ms layout=1.7ms paint=1.0ms upload=0.8ms draw=0.4ms`。
|
|
206
|
+
绝对值因机器而异;测试断言的是**计数器**(一次重算、一次布局、命中缓存而不是
|
|
207
|
+
重新整形、空闲帧不做任何工作),回归时变化的正是这些。
|
|
208
|
+
|
package/doc/zh-CN/gui/runtime.md
CHANGED
|
@@ -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
|
-
|
|
25
|
+
4. 调用 `xt_loop_poll(0)` 与 `xt_drain_microtasks()`,使 socket/定时器与
|
|
25
26
|
`await` 续体持续取得进展,
|
|
26
|
-
|
|
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
|
|