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
package/doc/gui/canvas.md CHANGED
@@ -36,6 +36,16 @@ a shared prototype. The handle is cached on the element, so every call returns
36
36
  the **same** object and the drawing state (transform, styles, path) is per
37
37
  canvas, not per call. `ctx.canvas` is the owning element handle.
38
38
 
39
+ `OffscreenCanvas` has the same 2D surface without a DOM element:
40
+ `new OffscreenCanvas(width, height)` allocates a bitmap owned by the window,
41
+ `getContext("2d")` returns a context whose `canvas` is the offscreen object, and
42
+ `canvas.transferControlToOffscreen()` hands a live element's bitmap to one
43
+ (`element.getContext(…)` then answers `null`). `drawImage`, `createPattern`,
44
+ `createImageBitmap` and `texImage2D` accept an `OffscreenCanvas` as a source.
45
+ `transferToImageBitmap()` returns an `ImageBitmap` and `convertToBlob()`
46
+ resolves to the same PNG blob shape as `toBlob` (there is no worker or
47
+ `OffscreenCanvas` WebGL context in this runtime).
48
+
39
49
  `getContext("2d", attributes)` honours `alpha` on the first call: with
40
50
  `{ alpha: false }` the bitmap starts (and clears) opaque black. The other
41
51
  attributes are reported by `getContextAttributes()` at their specification
@@ -51,7 +61,36 @@ horizontal coverage** pass, and fills paths with the **non-zero winding rule**
51
61
  (so `arc`-shaped holes work when wound the other way) unless
52
62
  `fill("evenodd")` selects the even-odd rule. An axis-aligned rectangle
53
63
  (`b == 0 && c == 0` in the transform) takes a direct coverage fast path.
54
- Blending is non-premultiplied source-over, scaled by `globalAlpha`.
64
+ Blending uses non-premultiplied source-over by default. `globalCompositeOperation`
65
+ selects the full canvas set: the Porter-Duff operators (`source-over`,
66
+ `source-in`, `source-out`, `source-atop`, `destination-over`,
67
+ `destination-in`, `destination-out`, `destination-atop`, `lighter`, `copy`,
68
+ `xor`) and the separable/non-separable blend modes (`multiply`, `screen`,
69
+ `overlay`, `darken`, `lighten`, `color-dodge`, `color-burn`, `hard-light`,
70
+ `soft-light`, `difference`, `exclusion`, `hue`, `saturation`, `color`,
71
+ `luminosity`). Coverage from anti-aliasing and the clip region interpolates
72
+ between the untouched destination and the composited result, so a partially
73
+ covered pixel is correct for every operator.
74
+
75
+ `clip()` intersects the current clip region with the current path (or a
76
+ `Path2D` and an optional `"nonzero"`/`"evenodd"` rule). The region is stored as
77
+ a device-space coverage mask, so it follows the transform in effect when
78
+ `clip()` was called; `save`/`restore` restore it and resizing the bitmap clears
79
+ it. Every fill, stroke, text run, image draw and `clearRect` honours the mask.
80
+
81
+ Shadows (`shadowColor`, `shadowBlur`, `shadowOffsetX`, `shadowOffsetY`) are
82
+ painted from the operation's own alpha: the draw is first rendered into a
83
+ scratch layer, its alpha is offset and box-blurred (three separable passes),
84
+ tinted with `shadowColor`, composited, and the unfiltered layer is drawn over
85
+ it. The offset and blur are in canvas (device) units and are not affected by
86
+ the transform, as the API specifies. `clearRect` and `putImageData` never cast
87
+ a shadow.
88
+
89
+ `isPointInPath(x, y[, fillRule])`, `isPointInPath(path, x, y[, fillRule])`,
90
+ `isPointInStroke(x, y)` and `isPointInStroke(path, x, y)` hit-test the current
91
+ path (or the supplied `Path2D`) under the current transform; stroke hit testing
92
+ uses the device-space distance to each flattened segment and the current line
93
+ width.
55
94
 
56
95
  A fill or stroke paints with a solid colour **or a `CanvasGradient`**. Gradient
57
96
  coordinates are user-space coordinates, so the rasteriser maps each device pixel
@@ -67,6 +106,33 @@ decode table). It is released when the node is dropped (`loadHTML`, window
67
106
  close/quit, or a mutation that discards the subtree), which also releases the
68
107
  matching GPU texture.
69
108
 
109
+ ## Performance
110
+
111
+ A canvas is redrawn by the script, not by the engine, so its cost is per drawing
112
+ call. With many elements the hot paths are deliberately branch-light:
113
+
114
+ - a solid, opaque `fillRect` is a byte store into the bitmap (fractional edges
115
+ still take the anti-aliased path), and `clearRect` over the whole bitmap is a
116
+ single `memset`;
117
+ - path coverage is rasterised with a per-row **active-edge list**, and only the
118
+ **covered span of each row** is materialised — a thin diagonal stroke costs
119
+ its own pixels, not its bounding box;
120
+ - one `stroke()` batches all of its segment quads, joins and caps into a single
121
+ non-zero fill instead of one coverage pass per piece (each piece's winding is
122
+ normalised first, so overlapping pieces cover rather than cancel);
123
+ - `fillText` shares the shaped-run cache with layout (`text_cache.cpp`) and uses
124
+ a bounded two-generation glyph bitmap cache (`text_glyph_cache.cpp`);
125
+ - `drawImage` at integer offsets with no scaling copies one texel per pixel
126
+ instead of running the bilinear sampler.
127
+
128
+ The bitmap reaches the screen through a **dirty rectangle**: every draw unions
129
+ its device-space bounds, and the renderer copies only that region into the
130
+ canvas texture. All of a frame's canvas and video uploads share the frame's one
131
+ upload command buffer, so K animated canvases cost one submit rather than K.
132
+ `getImageData` always reads the CPU bitmap, so it is unaffected.
133
+ `tests/e2e/gui-draw-perf.test.ts` measures the multi-element paths and pins the
134
+ counter behaviour (one upload per frame, only the dirty bytes).
135
+
70
136
  ## Supported API
71
137
 
72
138
  State and transforms: `save` / `restore` (which restore the transform, line
@@ -79,34 +145,43 @@ canvas order: `translate(x, y); rotate(a)` rotates around the translated origin.
79
145
 
80
146
  Styles and settings: `fillStyle` / `strokeStyle` (CSS colors understood by the
81
147
  cascade's color parser, or a `CanvasGradient`), `lineWidth`, `globalAlpha`,
148
+ `globalCompositeOperation`, `shadowColor` / `shadowBlur` / `shadowOffsetX` /
149
+ `shadowOffsetY`, `imageSmoothingEnabled` / `imageSmoothingQuality`,
82
150
  `lineCap` (`butt`/`round`/`square`), `lineJoin` (`miter`/`round`/`bevel`),
83
151
  `miterLimit`, `lineDashOffset`, `font` (a CSS font shorthand resolving to
84
152
  family, size and bold/italic — default `10px sans-serif`), `textAlign`
85
153
  (`start`/`left`, `center`, `right`/`end`) and `textBaseline` (`alphabetic`,
86
154
  `top`, `middle`, `bottom`, `hanging`, `ideographic`). Reading a property returns
87
155
  the value last assigned; assigning an unparseable color keeps the previous one.
156
+ `imageSmoothingEnabled` defaults to `true` (bilinear sampling);
157
+ `imageSmoothingQuality` reports `low`, `medium` or `high` and selects the same
158
+ bilinear filter in this backend.
88
159
 
89
160
  Gradients: `createLinearGradient(x0, y0, x1, y1)` and
90
161
  `createRadialGradient(x0, y0, r0, x1, y1, r1)` return a `CanvasGradient` with
91
162
  `addColorStop(offset, color)`. Stops stay live, so a stop added after
92
163
  `fillStyle = gradient` affects later fills, and the `fillStyle`/`strokeStyle`
93
- getter returns the same gradient handle. Radial gradients interpolate by
94
- distance from the **outer** circle's centre (`x1, y1`), so `x0`/`y0` are
95
- currently ignored: use concentric circles (`x0 == x1`, `y0 == y1`).
164
+ getter returns the same gradient handle. A radial gradient is the general
165
+ two-circle cone: the colour at a point is resolved from the interpolated circle
166
+ (`centre = c0 + t·(c1 − c0)`, `radius = r0 + t·(r1 − r0)`), so an offset inner
167
+ circle and different radii behave like a browser's. A point inside the inner
168
+ circle samples stop 0.
96
169
 
97
170
  Paths: `beginPath`, `closePath`, `moveTo`, `lineTo`, `rect`, `roundRect`,
98
171
  `arc`, `ellipse`, `quadraticCurveTo`, `bezierCurveTo`, then
99
172
  `fill(fillRule?)` / `fill(path, fillRule?)` (multi-loop; `"nonzero"` or
100
- `"evenodd"`) or `stroke(path?)`. Bezier and quadratic segments are flattened
101
- into line segments; arcs and ellipses are flattened too. `roundRect` accepts a
102
- single radius number or the CSS radius list (`[all]`, `[tl, br]`,
173
+ `"evenodd"`), `stroke(path?)`, `clip(path?, fillRule?)` and the
174
+ `isPointInPath`/`isPointInStroke` hit tests. Bezier and quadratic segments are
175
+ flattened into line segments; arcs and ellipses are flattened too. `roundRect`
176
+ accepts a single radius number or the CSS radius list (`[all]`, `[tl, br]`,
103
177
  `[tl, tr, br]`, `[tl, tr, br, bl]`, numbers or `{ x, y }` pairs; corners are
104
178
  drawn circular).
105
179
 
106
180
  `Path2D` is implemented with the same path commands (`moveTo`, `lineTo`,
107
181
  `closePath`, `rect`, `roundRect`, `arc`, `ellipse`, `quadraticCurveTo`,
108
- `bezierCurveTo`, `addPath`) and can be passed to `fill`/`stroke`, so a path can
109
- be built once and replayed under different transforms.
182
+ `bezierCurveTo`, `addPath`) and can be passed to `fill`/`stroke`/`clip` and the
183
+ `isPointIn*` hit tests, so a path can be built once and replayed under different
184
+ transforms.
110
185
 
111
186
  Rectangles: `clearRect`, `fillRect`, `strokeRect`.
112
187
 
@@ -129,35 +204,43 @@ Images: `drawImage(source, …)` in the 3-argument (`dx, dy`), 5-argument
129
204
  (`dx, dy, dw, dh`) and 9-argument (`dx, dy, dw, dh, sx, sy, sw, sh`; this
130
205
  engine's 9-argument form is destination-first) forms. A full affine transform
131
206
  (rotation/skew/scale) is honoured: the destination rectangle becomes a
132
- parallelogram and every device pixel is inverse-mapped to the source rectangle
133
- (nearest-neighbour). `source` may be an `<img>` element (decoded through the image module) or another
134
- `<canvas>` element (its current bitmap). Sampling is nearest-neighbour, so a
135
- large upscale of a small source is blocky — fine for icons and pixel art, not a
136
- photo pipeline.
207
+ parallelogram and every device pixel is inverse-mapped to the source rectangle.
208
+ `source` may be an `<img>` element (decoded through the image module), another
209
+ `<canvas>`, an `OffscreenCanvas`, or an `ImageBitmap`. Sampling is bilinear when
210
+ `imageSmoothingEnabled` is `true` (the default) and nearest-neighbour when it is
211
+ `false`.
137
212
 
138
213
  Test hook: `getImageData(x, y, w, h)` copies a region to
139
214
  `{ width, height, data }` where `data` is a row-major RGBA8 byte array.
140
215
  Out-of-bounds pixels are transparent.
141
216
 
142
- Patterns: `createPattern(source, repetition?)` accepts an `<img>` or another
143
- `<canvas>` and tiles it in user space (`repeat`, `repeat-x`, `repeat-y`,
144
- `no-repeat`), so the tiles follow the current transform. Assign the result to
145
- `fillStyle`/`strokeStyle`; assigning a colour replaces it.
217
+ Patterns: `createPattern(source, repetition?)` accepts an `<img>`,
218
+ `<canvas>`, `OffscreenCanvas` or `ImageBitmap` and tiles it in user space
219
+ (`repeat`, `repeat-x`, `repeat-y`, `no-repeat`), so the tiles follow the
220
+ current transform. Assign the result to `fillStyle`/`strokeStyle`; assigning a
221
+ colour replaces it.
146
222
 
147
223
  Image data and export: `createImageData(width, height)` allocates a blank
148
224
  `{ width, height, data }`, and `putImageData(imageData, dx, dy[, dirtyX, dirtyY,
149
225
  dirtyWidth, dirtyHeight])` copies bytes straight into the bitmap (no transform,
150
- blending or `globalAlpha`). `canvas.toDataURL(type?)` exports a PNG data URL and
151
- `canvas.toBlob(callback, type?)` delivers a Blob-shaped `{ type, size }` from a
152
- microtask (the bytes are on `__xt_gui_blob_data`; there is no
153
- `URL.createObjectURL`).
226
+ blending, clip or `globalAlpha`). `canvas.toDataURL(type?)` exports a PNG data
227
+ URL and `canvas.toBlob(callback, type?)` delivers a Blob-shaped `{ type, size }`
228
+ from a microtask (the bytes are on `__xt_gui_blob_data`; there is no
229
+ `URL.createObjectURL`). `OffscreenCanvas.convertToBlob()` resolves to the same
230
+ shape and `transferToImageBitmap()` snapshots the bitmap.
231
+
232
+ CSS backgrounds: `background-image: url(...)` accepts a file path or a `data:`
233
+ URL, so a canvas can be used as a CSS background by exporting it first
234
+ (`element.style.backgroundImage = "url(" + canvas.toDataURL() + ")"`). An image
235
+ layer is stretched to the element's border box; `background-size`,
236
+ `background-position` and tiling are not modelled.
154
237
 
155
238
  ## Not implemented
156
239
 
157
- Shadows, `clip()`, `globalCompositeOperation`, `isPointInPath`, `drawImage` with
158
- an image smoothing toggle, `<canvas>` as a CSS `background-image`, and offscreen
159
- canvases (`OffscreenCanvas`, `transferControlToOffscreen`).
160
- Radial gradients ignore the inner circle's offset.
161
-
240
+ Smoothing quality has one filter (bilinear) on this backend. Offscreen canvases
241
+ support the 2D context only (a WebGL context needs a DOM element for its
242
+ texture table). Text under a rotated or skewed transform places glyphs on the
243
+ transformed baseline but does not rotate the glyph bitmaps, and `strokeText`
244
+ fills with the stroke style rather than outlining.
162
245
  See also [Media](./media.md) for the image/video/audio decode stack that
163
246
  `drawImage` and the texture upload share.
@@ -11,16 +11,17 @@
11
11
  glslang — consumed directly by the Vulkan backend, or cross-compiled to MSL by
12
12
  the bundled SPIRV-Cross for Metal — and the drawing buffer is an offscreen
13
13
  colour texture that the page composites without a readback. WebGL 2 adds VAOs,
14
- instancing, integer attributes, sampler objects, `texStorage2D` and buffer
15
- copies. Uniform blocks, transform feedback, cube maps, 3D textures,
16
- float/depth textures, compressed textures, multiple render targets,
17
- `blitFramebuffer`, MSAA and stencil operations are not implemented; `clear`
18
- ignores the scissor rectangle. On an SDL_GPU backend that accepts neither
19
- SPIR-V nor MSL (Direct3D 12, which would need DXIL) both context kinds are
20
- `null`. The `.js` asset loader now patches only three.js's shader unroller;
21
- `WebGLRenderer` runs on the real backend and there is no Canvas2D fallback.
22
- Shadow maps (depth textures), cube textures/environment maps and HDR render
23
- targets remain unsupported.
14
+ instancing, integer attributes, sampler objects, `texStorage2D`, buffer copies,
15
+ uniform blocks, cube/3D/array textures, float/depth/compressed textures,
16
+ multiple render targets and scissored clears. `blitFramebuffer` copies colour
17
+ and depth; transform feedback, MSAA and stencil blits are not implemented
18
+ (SDL_GPU has no transform-feedback stage, its Metal multisample resolve is
19
+ unreliable, and a stencil attachment is not sampleable). On an SDL_GPU backend
20
+ that accepts neither SPIR-V nor MSL (Direct3D 12, which would need DXIL) both
21
+ context kinds are `null`. The `.js` asset loader now patches only three.js's
22
+ shader unroller; `WebGLRenderer` runs on the real backend and there is no
23
+ Canvas2D fallback. Shadow maps (comparative `sampler2DShadow` targeting depth
24
+ textures), cube textures/environment maps and HDR render targets are supported.
24
25
 
25
26
 
26
27
 
@@ -80,6 +81,27 @@
80
81
  not CSS properties here). Vector art that needs those has to be rasterised
81
82
  beforehand, drawn from boxes, or restricted to the supported syntax. See
82
83
  [Media](./media.md#images-m6a).
84
+ - **Rendering is still whole-document per change.** A DOM mutation recomputes
85
+ the cascade and lays out the complete document again (there is no incremental
86
+ subtree invalidation), and a change that only affects paint (a hover colour,
87
+ for example) reflows too. Documents with generated content
88
+ (`::before`/`::after`) are not special-cased. The measured cost of each stage
89
+ is exposed by `win.stats()`/`XT_GUI_PERF`, which is what a follow-up would be
90
+ keyed off; see [Performance](./rendering.md#performance-m20).
91
+ - **Draw calls follow painter order.** Runs only merge when they are
92
+ consecutive in painter order and share pipeline, clip and texture, so a list
93
+ whose rows alternate a background and text costs one shape run and one text
94
+ run per row (`drawCalls` in `win.stats()`). Reordering for fewer draw calls
95
+ needs an overlap-aware batcher (text may be covered by a later background) and
96
+ is not implemented. Measured: the 200-row perf page issues 400 draw calls and
97
+ spends about 0.4 ms in the draw phase, so this is a follow-up for
98
+ draw-call-dominated pages rather than the current bottleneck.
99
+ - **The text shaping cache is bounded.** Two generations of up to 2 MiB of
100
+ glyphs / 8,192 entries hold `(face, text) -> glyphs`. A document with more
101
+ distinct words than that re-shapes some of them after a cache rotation;
102
+ `textCacheBytes`/`textShapeHits` in `win.stats()` show whether it is the
103
+ bottleneck. Inline `style="…"` attributes are re-parsed on each restyle of
104
+ the element that carries them.
83
105
 
84
106
  ## Open questions
85
107
 
@@ -160,3 +160,80 @@ cascades but paints nothing.
160
160
  - Layout uses these real metrics for text widths, line breaking and
161
161
  `line-height: normal`; `measureText`/`fontMetrics` expose them to tests.
162
162
 
163
+ ## Performance (M20)
164
+
165
+ The engine is a retained-mode pipeline: a DOM mutation restyles the document,
166
+ lays it out, rebuilds the display list and uploads vertex arrays; a frame that
167
+ changes nothing does no CPU or GPU work. A document with thousands of elements
168
+ is kept interactive by budgeting each stage, and every stage is measured. Set
169
+ `XT_GUI_PERF=1` to print one line per frame (or `XT_GUI_PERF=<N>` for an
170
+ aggregated line every N frames); `win.stats()` returns the same counters, which
171
+ is what `tests/e2e/gui-perf.test.ts` asserts on.
172
+
173
+ - **Cascade.** An `@media` prelude is evaluated **once per rule** per restyle
174
+ (it used to be parsed once per rule *and* node, which was the largest cost on
175
+ a large document). A sheet without `::before`/`::after` rules skips generated
176
+ content entirely, the declaration candidate list is reused across the
177
+ depth-first walk instead of allocated per element, and `:hover`/`:focus` only
178
+ restyle when the sheet actually has such a rule.
179
+ - **Text.** Layout and paint share a bounded shaping cache (`text_cache.cpp`):
180
+ `(face, text) -> glyphs + advance`. It uses two generations of up to 2 MiB of
181
+ glyphs / 8,192 entries; when a generation is full the *spare* one is dropped
182
+ and becomes current, so the hot path has no LRU bookkeeping. A word is shaped
183
+ once and reused by every later layout pass and repaint that draws it. Primary
184
+ face lookups are cached by (family class, size) as well.
185
+ - **Frame loop.** The media/canvas tables are reconciled with the DOM only when
186
+ the document changed (or on the first frame) instead of walking the tree every
187
+ frame. A frame presents only when something happened — a mutation, a scroll,
188
+ an animation frame, a transition, a new video/canvas frame, an expose/resize
189
+ event — so a static page performs no swapchain acquire and no draw at all.
190
+ An expose is the one present the window system asks for rather than the page,
191
+ and `exposes` counts those frames, so `presents - exposes - deferred` stays
192
+ the document's own work even when a window server maps the window late.
193
+ A frame that cannot get a swapchain image (SDL's non-blocking acquire skips a
194
+ frame while the GPU still holds the image, and a minimized window has none)
195
+ keeps its invalidation pending instead of dropping it, so the paint is
196
+ presented on the next frame; those presents are counted as `deferred`.
197
+ `XT_GUI_ALWAYS_PRESENT=1` restores the old always-present behaviour when
198
+ debugging a missed invalidation.
199
+ - **Paint and upload.** The display list and the vertex staging arrays are kept
200
+ on the window and cleared per repaint, so their capacity survives. All of a
201
+ dirty frame's vertex/atlas uploads share one command buffer (one submit),
202
+ the geometry's staging transfer buffer is reused (cycled while the previous
203
+ copy may still be in flight), and paint skips the z-index sort and temporary
204
+ vector when no child stacks.
205
+ - **Draw.** Consecutive ops with the same pipeline, clip and texture still merge
206
+ into one run, and the renderer now only re-binds a pipeline/vertex
207
+ buffer/scissor when it actually changes. `drawCalls`/`drawMs` in `win.stats()`
208
+ report the run count and its cost; they are the metric for the remaining
209
+ follow-up (an overlap-aware batching pass) — painter order requires
210
+ alternating background and text runs to stay separate until that lands.
211
+ - **Immediate-mode surfaces.** `<canvas>` 2D and WebGL draws are script-driven,
212
+ so their cost is per call rather than per document change; the counters above
213
+ cover the retained pipeline only. `tests/e2e/gui-draw-perf.test.ts` measures
214
+ them: on the reference Windows x64/Vulkan machine a 1280×720 frame with 5,000
215
+ solid `fillRect`s, 200 eight-point strokes, 300 `fillText` runs and 200 1:1
216
+ `drawImage`s fell from 231ms to 158ms (fill 4.3ms, stroke 151ms, text 1.4ms,
217
+ image 1.6ms), and 2,000 WebGL `drawElements` calls with a per-draw matrix went
218
+ from 38.6ms to 27.5ms while the render passes opened per frame dropped from
219
+ 2,001 to 1 and a 64 KiB `bufferSubData` into a 4 MiB buffer stopped
220
+ re-uploading the whole buffer. A 100×100 canvas redraw now uploads 40 KB
221
+ rather than the whole 1.2 MB bitmap. The suite asserts the counters (one
222
+ upload per frame, no steady-state pipeline creation, only the dirty bytes), so
223
+ the wall-clock numbers stay informational and a software GPU in CI is not
224
+ penalised.
225
+
226
+ Known limits: a mutation still restyles and relayouts the **whole** document
227
+ (there is no incremental subtree invalidation), and a paint-only change (for
228
+ example a hover colour) currently reflows too; `win.stats()` is what a follow-up
229
+ would be keyed off. Inline `style="…"` attributes are re-parsed on every restyle
230
+ of the element that carries them.
231
+
232
+ As a reference point, `tests/e2e/gui-perf.test.ts` builds a 200-row list (about
233
+ 1,000 elements, 400 draw runs) and prints the numbers it measured, e.g.
234
+ `textFrame=5.2ms restyle=3.6ms layout=1.7ms paint=1.0ms upload=0.8ms draw=0.4ms`
235
+ for a one-field `textContent` change on a Windows x64/Vulkan machine. The
236
+ absolute values are host-specific; the test asserts the *counters* (one restyle,
237
+ one layout, cache hits instead of shapes, no work on an idle frame), which are
238
+ what a regression would change.
239
+
@@ -18,11 +18,26 @@ expose an explicit `run()`; **M2 uses the explicit `run()`** so the window is a
18
18
  plain native call the TypeScript program controls. Each tick it:
19
19
 
20
20
  1. pumps SDL window/input events for every window,
21
- 2. builds/uploads the display list when it changed and renders every open
21
+ 2. reconciles the decoder/canvas tables with the DOM **only when the document
22
+ changed** (or on the first frame),
23
+ 3. builds/uploads the display list when it changed and renders every open
22
24
  window (clear pass + shape geometry + text geometry),
23
- 3. calls `xt_loop_poll(0)` and `xt_drain_microtasks()` so sockets/timers and
25
+ 4. calls `xt_loop_poll(0)` and `xt_drain_microtasks()` so sockets/timers and
24
26
  `await` continuations keep making progress,
25
- 4. repeats until all windows close or `quit()` is called.
27
+ 5. repeats until all windows close or `quit()` is called.
28
+
29
+ A frame **presents only when something happened**: a DOM mutation, a scroll, a
30
+ queued animation frame, a running transition, a new video/canvas frame, an
31
+ expose/resize event, or the first frame. A static document therefore performs no
32
+ swapchain acquire and no draw while the loop keeps servicing events, timers and
33
+ promises; `XT_GUI_ALWAYS_PRESENT=1` restores the old always-present behaviour
34
+ when debugging a missed invalidation. An expose/shown/restore event is the one
35
+ present the *window system* asks for rather than the document, so it is counted
36
+ as `exposes` in `win.stats()`; a present a starved frame had to postpone (no
37
+ swapchain image was available) is counted as `deferred`, and
38
+ `presents - exposes - deferred` is the work the page caused. `XT_GUI_PERF=1`
39
+ prints what each frame did (see
40
+ [Performance](./rendering.md#performance-m20)).
26
41
 
27
42
  The `xt_loop_set_main` hook remains available for hosts that want to own the
28
43
  loop themselves.
package/doc/gui/webgl.md CHANGED
@@ -36,9 +36,10 @@ reports the type as well.
36
36
  `height` attributes. `canvas.width = …` recreates them and discards the
37
37
  contents, like the DOM rule.
38
38
  - The context records into its own SDL_GPU command buffer. `clear`/`draw*`
39
- keep a render pass open; a buffer/texture upload or a framebuffer change ends
40
- it and starts a new pass with `LOAD`, so the drawing buffer persists between
41
- draws just like WebGL's.
39
+ keep a render pass open; only a buffer/texture upload or a framebuffer
40
+ change ends it and starts a new pass with `LOAD`, so the drawing buffer
41
+ persists between draws just like WebGL's and a draw that has nothing to
42
+ upload never breaks the pass.
42
43
  - Once per frame, before the page's own render pass, the engine submits every
43
44
  WebGL command buffer. The image pipeline then samples the colour texture as
44
45
  an ordinary `PaintImage`, with no CPU copy.
@@ -89,6 +90,8 @@ The translator (`runtime/ext_gui/webgl_glsl*.cpp`) handles GLSL ES 1.00 and
89
90
  | non-sampler uniforms | one `layout(std140) uniform XtUniforms { … }` block |
90
91
  | `uniform sampler2D u_tex` | `layout(set = 2, binding = N) uniform sampler2D u_tex` (fragment) or set 0 (vertex) |
91
92
  | `gl_FragData[0]` | `xt_FragColor` |
93
+ | `gl_FragDepth` / `gl_FragDepthEXT` | kept (SPIR-V `DepthReplacing`; MSL `[[depth(any)]]`) |
94
+ | `sampler2DShadow` / `samplerCubeShadow` / `sampler2DArrayShadow` | kept; bound as an SDL_GPU comparison sampler |
92
95
  | a missing `#version` / `#version 100` | `#version 310 es` |
93
96
 
94
97
  Non-sampler uniforms are merged across both stages and emitted as one block in
@@ -108,6 +111,14 @@ three.js emits becomes constant-indexed. Shader code inside `#ifdef` is fine;
108
111
  non-sampler uniform declarations are moved to the enclosing top-level position
109
112
  so the block stays unconditional.
110
113
 
114
+ A fragment shader may write `gl_FragDepth`; glslang marks the SPIR-V module
115
+ `DepthReplacing` and SPIRV-Cross emits `[[depth(any)]]`, so the write reaches the
116
+ depth attachment on both backends. The comparative samplers
117
+ (`sampler2DShadow`, `samplerCubeShadow`, `sampler2DArrayShadow`) use the same
118
+ SDL_GPU sampler objects with `enable_compare` set; `TEXTURE_COMPARE_MODE` and
119
+ `TEXTURE_COMPARE_FUNC` are read from the texture (or from a bound sampler
120
+ object in WebGL 2).
121
+
111
122
  ### GLSL ES 3.00 (WebGL 2)
112
123
 
113
124
  A `#version 300 es` source is accepted as-is apart from the version bump:
@@ -177,9 +188,10 @@ available from GLSL ES 3.00, which is what the instanced example uses.
177
188
  `framebufferTexture2D` (`COLOR_ATTACHMENT0`, `DEPTH_ATTACHMENT`,
178
189
  `DEPTH_STENCIL_ATTACHMENT`), `checkFramebufferStatus`, `createRenderbuffer`,
179
190
  `bindRenderbuffer`, `renderbufferStorage` (depth or depth-stencil),
180
- `framebufferRenderbuffer`, `delete*`, and `blitFramebuffer` (colour
181
- attachment; depth/stencil bits are accepted but not copied). This is enough
182
- for render-to-texture, shadow maps and post-processing passes.
191
+ `framebufferRenderbuffer`, `delete*`, and `blitFramebuffer` (colour through
192
+ SDL's blit; depth through a sampling pass that writes `gl_FragDepth`; stencil
193
+ bits are accepted but not copied). This is enough for render-to-texture,
194
+ shadow maps and post-processing passes.
183
195
  - **State**: `enable`/`disable`/`isEnabled` for `BLEND`, `DEPTH_TEST`,
184
196
  `CULL_FACE`, `SCISSOR_TEST` and `STENCIL_TEST`,
185
197
  `clearColor`/`clearDepth`/`clearStencil`, `clear` (with `SCISSOR_TEST` it
@@ -248,12 +260,17 @@ backend's `[0, 1]` clip space rather than GL's `[-1, 1] → [0, 1]` remap:
248
260
 
249
261
  ## Not implemented
250
262
 
251
- Transform feedback (the entry points are no-ops) and MSAA/sample buffers
252
- (SDL_GPU's Metal multisample resolve returns undefined pixels with scissor
253
- tests and instanced draws, so contexts stay single-sampled). `blitFramebuffer`
254
- copies the colour attachment only; depth/stencil bits are accepted but not
255
- copied. Shaders that use built-in blocks or `gl_FragDepth` are rejected with a
256
- compile/link log.
263
+ Transform feedback (the entry points are no-ops: SDL_GPU has no transform
264
+ feedback stage) and MSAA/sample buffers (SDL_GPU 3.2 can resolve multisampled
265
+ colour targets, but its Metal resolve returns undefined pixels with scissor
266
+ tests and instanced draws, so contexts stay single-sampled and `SAMPLE_BUFFERS`
267
+ reports 0). `blitFramebuffer` copies colour and depth; stencil bits are accepted
268
+ but not copied because SDL_GPU cannot sample a stencil attachment.
269
+
270
+ Shader limitations that remain: struct-typed uniforms and user uniform blocks
271
+ are flattened, but built-in interface blocks (`gl_PerVertex` redeclarations) are
272
+ not part of the translated subset. A shader that uses one still fails with a
273
+ compile/link log rather than silently misbehaving.
257
274
 
258
275
  ## Platform
259
276
 
@@ -275,7 +292,10 @@ uses the 2D canvas or the DOM pays no run-time cost beyond the archive size.
275
292
  - `tests/e2e/gui-webgl.test.ts` compiles GLSL, draws a textured and a
276
293
  depth-tested quad, checks face culling and reads the pixels back; it also
277
294
  covers blend constants, dynamic buffer usage, vertex-shader texture sampling,
278
- `gl_FragDepth` diagnostics, context loss/restore and cube maps.
295
+ `gl_FragDepth`, context loss/restore and cube maps.
296
+ - `tests/e2e/gui-webgl2-shadow-sampler.test.ts` covers hardware
297
+ `sampler2DShadow` comparisons, a shader-written `gl_FragDepth` that occludes a
298
+ later draw, and a partial `blitFramebuffer` depth copy.
279
299
  - `tests/e2e/gui-webgl2.test.ts` covers the WebGL 2 shader/state additions:
280
300
  `#version 300 es` shaders, a VAO round trip, instanced draws with a divisor,
281
301
  `texStorage2D`, `clearBufferfv`, buffer copies, sampler objects, struct-uniform
@@ -317,9 +337,8 @@ Canvas2D fallback is bundled any more**. Verified with three.js r160 through SDL
317
337
  `Sprite`, `Line`, `Points` and `InstancedMesh`.
318
338
 
319
339
  Features outside this engine's subset still cannot be emulated: transform
320
- feedback and MSAA.
321
- Depth textures, float render targets and compressed textures are available, but
322
- three.js shadow maps still need comparative `sampler2DShadow` sampling, which
323
- is not translated yet. A scene that needs one of those will not render
324
- correctly; everything the previous Canvas2D fallback could show now goes
325
- through the GPU.
340
+ feedback and MSAA. Depth textures, float render targets and compressed textures
341
+ are available, and comparative `sampler2DShadow` sampling is translated, so
342
+ three.js shadow maps can use the hardware depth compare on both backends. A
343
+ scene that needs transform feedback or MSAA will not render correctly;
344
+ everything the previous Canvas2D fallback could show now goes through the GPU.
package/doc/gui.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xbintsc GUI extension (self-hosted HTML/CSS renderer)
2
2
 
3
- Status: **M19** — features (M1–M15) are complete: HTML parsing, CSS selector
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
@@ -44,6 +44,10 @@ them) can be translated, scaled and rotated without reflowing anything — and
44
44
  `clip-path` and the `filter` colour/shadow effects. `backdrop-filter` and true
45
45
  group blur need an offscreen compositor and remain documented approximations or
46
46
  unsupported — see [Rendering](./gui/rendering.md#backgrounds-generated-content-and-effects-m19).
47
+ **M20** makes large documents interactive: `@media` is evaluated once per rule,
48
+ layout and paint share a bounded shaping cache, and a frame only does (and
49
+ presents) the work a change requires — see
50
+ [Performance](./gui/rendering.md#performance-m20).
47
51
  This overview records the locked decisions, the architecture, the milestone plan
48
52
  and the current progress; the engine itself is documented by function in the
49
53
  pages above.
@@ -342,6 +346,18 @@ engine were Rust.
342
346
  offscreen backdrop pass (see [Known issues](./gui/known-issues.md)).
343
347
  - E2E coverage in `tests/e2e/gui-effects.test.ts`; see
344
348
  [Rendering](./gui/rendering.md#backgrounds-generated-content-and-effects-m19).
349
+ 20. **M20 — rendering performance** ✅
350
+ - `@media` evaluated once per rule per restyle, generated content skipped for
351
+ sheets without pseudo-elements, and `:hover`/`:focus` restyle only when
352
+ the sheet tracks them.
353
+ - Bounded two-generation shaping cache shared by layout and paint, plus a
354
+ primary-face cache.
355
+ - Frame loop: reconcile media/canvas only when the document changed, present
356
+ only when something happened, and gate the whole pass behind it.
357
+ - Reused display list/vertex staging/transfer buffers, one upload submit per
358
+ dirty frame, and redundant render-state binds removed.
359
+ - `win.stats()`/`XT_GUI_PERF` counters and `tests/e2e/gui-perf.test.ts`; see
360
+ [Performance](./gui/rendering.md#performance-m20).
345
361
 
346
362
  ## Progress log
347
363
 
@@ -490,4 +506,20 @@ engine were Rust.
490
506
  functions plus nine-tap blur/drop-shadow and emits `text-shadow` runs, and
491
507
  `paint_clip.cpp`/`renderer_clip.cpp` clip geometry against triangulated
492
508
  `clip-path` polygons. E2E: `tests/e2e/gui-effects.test.ts`.
509
+ - **M20** ✅ rendering performance. `@media` is evaluated once per rule per
510
+ restyle, generated content is skipped for sheets without `::before`/`::after`,
511
+ and `:hover`/`:focus` only restyle when the sheet tracks them
512
+ (`style.cpp`, `css_sheet.cpp`). A bounded two-generation shaping cache
513
+ (`text_cache.cpp`) serves layout and paint, so a word is shaped once instead
514
+ of on every layout pass and repaint; primary faces are cached by
515
+ (class, size) too. The frame loop reconciles decoders/canvases only when the
516
+ document changed and presents only when something happened
517
+ (`gui_events.cpp`), so a static page does no GPU work
518
+ (`XT_GUI_ALWAYS_PRESENT=1` restores the old behaviour for debugging). The
519
+ display list, vertex staging and transfer buffers are reused, a dirty frame's
520
+ uploads share one command buffer, and the renderer only re-binds state that
521
+ changed (`renderer*.cpp`, `gui_engine.h`). `win.stats()` reports per-frame
522
+ timings and cumulative work counters and `XT_GUI_PERF` prints them
523
+ (`perf.{h,cpp}`); `tests/e2e/gui-perf.test.ts` pins a 200-row list. See
524
+ [Performance](./gui/rendering.md#performance-m20).
493
525
 
@@ -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>` 帧与 `ImageData` 形状);promise 立即 settle,`texImage2D` 可直接接受该对象 |
95
- | `ImageData`、`OffscreenCanvas`、`XRWebGL*`、`Request`、`Headers`、`Response`、`ReadableStream`、`DOMParser` | `typeof` 探测返回 `"undefined"`(没有 XR/fetch/流式后端),让页面走自己的“不支持”分支,而不是调用空壳。Three.js 页面在本后端上使用真实渲染器(见[已知问题](./known-issues.md))。 |
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