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.
- 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-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-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 +110 -27
- package/doc/gui/known-issues.md +32 -10
- package/doc/gui/rendering.md +77 -0
- package/doc/gui/runtime.md +18 -3
- package/doc/gui/webgl.md +38 -19
- package/doc/gui.md +33 -1
- package/doc/zh-CN/gui/api.md +25 -2
- package/doc/zh-CN/gui/canvas.md +95 -26
- package/doc/zh-CN/gui/known-issues.md +21 -4
- package/doc/zh-CN/gui/rendering.md +59 -0
- 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 +16 -1
- 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 +12 -1
- 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 +182 -58
- package/runtime/ext_gui/gui_media.cpp +3 -0
- package/runtime/ext_gui/image.cpp +69 -0
- package/runtime/ext_gui/image_bitmap_browser.cpp +11 -0
- package/runtime/ext_gui/layout.cpp +4 -0
- package/runtime/ext_gui/paint.cpp +19 -0
- package/runtime/ext_gui/paint_background.cpp +18 -1
- package/runtime/ext_gui/perf.cpp +171 -0
- package/runtime/ext_gui/perf.h +127 -0
- package/runtime/ext_gui/renderer.cpp +109 -41
- package/runtime/ext_gui/renderer.h +33 -9
- package/runtime/ext_gui/renderer_image.cpp +135 -65
- package/runtime/ext_gui/renderer_internal.h +15 -3
- package/runtime/ext_gui/renderer_text.cpp +24 -12
- package/runtime/ext_gui/style.cpp +101 -30
- package/runtime/ext_gui/style.h +6 -3
- package/runtime/ext_gui/style_print.cpp +7 -1
- 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/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
|
|
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.
|
|
94
|
-
|
|
95
|
-
|
|
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"`)
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
109
|
-
be built once and replayed under different
|
|
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
|
-
|
|
134
|
-
`<canvas
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
143
|
-
`<canvas
|
|
144
|
-
`no-repeat`), so the tiles follow the
|
|
145
|
-
`fillStyle`/`strokeStyle`; assigning a
|
|
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
|
|
151
|
-
`canvas.toBlob(callback, type?)` delivers a Blob-shaped `{ type, size }`
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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.
|
package/doc/gui/known-issues.md
CHANGED
|
@@ -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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
package/doc/gui/rendering.md
CHANGED
|
@@ -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
|
+
|
package/doc/gui/runtime.md
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
25
|
+
4. calls `xt_loop_poll(0)` and `xt_drain_microtasks()` so sockets/timers and
|
|
24
26
|
`await` continuations keep making progress,
|
|
25
|
-
|
|
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
|
|
40
|
-
it and starts a new pass with `LOAD`, so the drawing buffer
|
|
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
|
-
|
|
182
|
-
for render-to-texture,
|
|
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
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
|
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
|
-
|
|
322
|
-
three.js shadow maps
|
|
323
|
-
|
|
324
|
-
|
|
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: **
|
|
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
|
|
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
|
|