xbintsc 0.5.37 → 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 (166) 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/codegen/generator/state.js +1 -0
  5. package/dist/src/codegen/generator/state.js.map +1 -1
  6. package/dist/src/codegen/generator/tables.js +1 -0
  7. package/dist/src/codegen/generator/tables.js.map +1 -1
  8. package/dist/src/driver/bundler/deferred.d.ts +63 -0
  9. package/dist/src/driver/bundler/deferred.js +279 -0
  10. package/dist/src/driver/bundler/deferred.js.map +1 -0
  11. package/dist/src/driver/bundler/merge.js +214 -2
  12. package/dist/src/driver/bundler/merge.js.map +1 -1
  13. package/dist/src/driver/runtime-objects.d.ts +1 -1
  14. package/dist/src/driver/runtime-objects.js +1 -0
  15. package/dist/src/driver/runtime-objects.js.map +1 -1
  16. package/dist/src/driver/version.d.ts +1 -1
  17. package/dist/src/driver/version.js +1 -1
  18. package/dist/src/extensions/gui/html.js +98 -15
  19. package/dist/src/extensions/gui/html.js.map +1 -1
  20. package/dist/src/extensions/gui/index.js +1 -0
  21. package/dist/src/extensions/gui/index.js.map +1 -1
  22. package/dist/src/extensions/gui/three.d.ts +4 -3
  23. package/dist/src/extensions/gui/three.js +4 -3
  24. package/dist/src/extensions/gui/three.js.map +1 -1
  25. package/dist/tests/codegen/closures.test.js +7 -0
  26. package/dist/tests/codegen/closures.test.js.map +1 -1
  27. package/dist/tests/e2e/compile-objects-classes.test.js +31 -0
  28. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  29. package/dist/tests/e2e/gui-canvas-state.test.d.ts +9 -0
  30. package/dist/tests/e2e/gui-canvas-state.test.js +297 -0
  31. package/dist/tests/e2e/gui-canvas-state.test.js.map +1 -0
  32. package/dist/tests/e2e/gui-draw-perf.test.d.ts +22 -0
  33. package/dist/tests/e2e/gui-draw-perf.test.js +405 -0
  34. package/dist/tests/e2e/gui-draw-perf.test.js.map +1 -0
  35. package/dist/tests/e2e/gui-perf.test.d.ts +23 -0
  36. package/dist/tests/e2e/gui-perf.test.js +283 -0
  37. package/dist/tests/e2e/gui-perf.test.js.map +1 -0
  38. package/dist/tests/e2e/gui-script-import-order.test.d.ts +10 -0
  39. package/dist/tests/e2e/gui-script-import-order.test.js +169 -0
  40. package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -0
  41. package/dist/tests/e2e/gui-webgl.test.js +4 -4
  42. package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
  43. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.d.ts +8 -0
  44. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js +336 -0
  45. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js.map +1 -0
  46. package/dist/tests/e2e/gui-webgl2-shadow.test.d.ts +10 -0
  47. package/dist/tests/e2e/gui-webgl2-shadow.test.js +108 -0
  48. package/dist/tests/e2e/gui-webgl2-shadow.test.js.map +1 -0
  49. package/dist/tests/e2e/url.test.d.ts +6 -0
  50. package/dist/tests/e2e/url.test.js +114 -0
  51. package/dist/tests/e2e/url.test.js.map +1 -0
  52. package/dist/tests/extensions/gui.test.js +45 -10
  53. package/dist/tests/extensions/gui.test.js.map +1 -1
  54. package/doc/gui/api.md +28 -2
  55. package/doc/gui/canvas.md +110 -27
  56. package/doc/gui/known-issues.md +32 -10
  57. package/doc/gui/rendering.md +77 -0
  58. package/doc/gui/runtime.md +18 -3
  59. package/doc/gui/webgl.md +58 -29
  60. package/doc/gui-scripts.md +19 -19
  61. package/doc/gui.md +43 -9
  62. package/doc/implemented/fetch.md +5 -0
  63. package/doc/implemented/modules.md +13 -0
  64. package/doc/implemented/stdlib.md +17 -0
  65. package/doc/unimplemented/deviations.md +1 -0
  66. package/doc/zh-CN/gui/api.md +25 -2
  67. package/doc/zh-CN/gui/canvas.md +95 -26
  68. package/doc/zh-CN/gui/known-issues.md +21 -4
  69. package/doc/zh-CN/gui/rendering.md +59 -0
  70. package/doc/zh-CN/gui/runtime.md +13 -3
  71. package/doc/zh-CN/gui/webgl.md +30 -8
  72. package/doc/zh-CN/gui-scripts.md +14 -13
  73. package/doc/zh-CN/gui.md +32 -8
  74. package/doc/zh-CN/implemented/fetch.md +4 -0
  75. package/doc/zh-CN/implemented/modules.md +10 -0
  76. package/doc/zh-CN/implemented/stdlib.md +15 -0
  77. package/doc/zh-CN/unimplemented/deviations.md +1 -0
  78. package/package.json +7 -7
  79. package/runtime/ext_gui/browser.cpp +3 -1
  80. package/runtime/ext_gui/canvas.cpp +135 -89
  81. package/runtime/ext_gui/canvas.h +116 -0
  82. package/runtime/ext_gui/canvas_clip.cpp +143 -0
  83. package/runtime/ext_gui/canvas_composite.cpp +241 -0
  84. package/runtime/ext_gui/canvas_coverage.cpp +241 -0
  85. package/runtime/ext_gui/canvas_gradient.cpp +41 -4
  86. package/runtime/ext_gui/canvas_image_data.cpp +4 -0
  87. package/runtime/ext_gui/canvas_internal.h +34 -9
  88. package/runtime/ext_gui/canvas_raster.cpp +207 -178
  89. package/runtime/ext_gui/canvas_shadow.cpp +161 -0
  90. package/runtime/ext_gui/canvas_stroke.cpp +37 -9
  91. package/runtime/ext_gui/canvas_style.cpp +59 -0
  92. package/runtime/ext_gui/canvas_text.cpp +140 -0
  93. package/runtime/ext_gui/css.h +7 -0
  94. package/runtime/ext_gui/css_sheet.cpp +22 -0
  95. package/runtime/ext_gui/document.cpp +25 -1
  96. package/runtime/ext_gui/document.h +8 -0
  97. package/runtime/ext_gui/dom_api_canvas.cpp +16 -1
  98. package/runtime/ext_gui/dom_api_canvas_image.cpp +9 -0
  99. package/runtime/ext_gui/dom_api_canvas_state.cpp +217 -0
  100. package/runtime/ext_gui/dom_api_internal.h +36 -37
  101. package/runtime/ext_gui/dom_api_offscreen.cpp +213 -0
  102. package/runtime/ext_gui/dom_api_proto.cpp +18 -0
  103. package/runtime/ext_gui/dom_api_webgl.cpp +29 -9
  104. package/runtime/ext_gui/dom_api_webgl2.cpp +33 -1
  105. package/runtime/ext_gui/dom_api_webgl_internal.h +58 -0
  106. package/runtime/ext_gui/dom_api_webgl_source.cpp +18 -0
  107. package/runtime/ext_gui/gradient_values.cpp +12 -1
  108. package/runtime/ext_gui/gui.cpp +5 -0
  109. package/runtime/ext_gui/gui_canvas.cpp +4 -0
  110. package/runtime/ext_gui/gui_engine.h +32 -0
  111. package/runtime/ext_gui/gui_events.cpp +182 -58
  112. package/runtime/ext_gui/gui_media.cpp +3 -0
  113. package/runtime/ext_gui/image.cpp +69 -0
  114. package/runtime/ext_gui/image_bitmap_browser.cpp +11 -0
  115. package/runtime/ext_gui/layout.cpp +4 -0
  116. package/runtime/ext_gui/paint.cpp +19 -0
  117. package/runtime/ext_gui/paint_background.cpp +18 -1
  118. package/runtime/ext_gui/perf.cpp +171 -0
  119. package/runtime/ext_gui/perf.h +127 -0
  120. package/runtime/ext_gui/renderer.cpp +109 -41
  121. package/runtime/ext_gui/renderer.h +33 -9
  122. package/runtime/ext_gui/renderer_image.cpp +135 -65
  123. package/runtime/ext_gui/renderer_internal.h +15 -3
  124. package/runtime/ext_gui/renderer_text.cpp +24 -12
  125. package/runtime/ext_gui/script.cpp +31 -0
  126. package/runtime/ext_gui/style.cpp +101 -30
  127. package/runtime/ext_gui/style.h +6 -3
  128. package/runtime/ext_gui/style_print.cpp +7 -1
  129. package/runtime/ext_gui/text.cpp +23 -4
  130. package/runtime/ext_gui/text.h +29 -0
  131. package/runtime/ext_gui/text_cache.cpp +118 -0
  132. package/runtime/ext_gui/text_glyph_cache.cpp +100 -0
  133. package/runtime/ext_gui/webgl.h +48 -146
  134. package/runtime/ext_gui/webgl_blit.cpp +12 -1
  135. package/runtime/ext_gui/webgl_blit_depth.cpp +270 -0
  136. package/runtime/ext_gui/webgl_buffer.cpp +59 -1
  137. package/runtime/ext_gui/webgl_buffer_objects.cpp +106 -9
  138. package/runtime/ext_gui/webgl_clear.cpp +3 -0
  139. package/runtime/ext_gui/webgl_context.cpp +29 -3
  140. package/runtime/ext_gui/webgl_draw.cpp +75 -270
  141. package/runtime/ext_gui/webgl_draw_buffers.cpp +1 -0
  142. package/runtime/ext_gui/webgl_draw_setup.cpp +321 -0
  143. package/runtime/ext_gui/webgl_draw_state.h +130 -0
  144. package/runtime/ext_gui/webgl_glsl.h +3 -0
  145. package/runtime/ext_gui/webgl_glsl_scan.cpp +3 -8
  146. package/runtime/ext_gui/webgl_glsl_unroll.cpp +283 -0
  147. package/runtime/ext_gui/webgl_msl.cpp +4 -0
  148. package/runtime/ext_gui/webgl_objects.h +148 -0
  149. package/runtime/ext_gui/webgl_pipeline.cpp +8 -8
  150. package/runtime/ext_gui/webgl_program_internal.h +35 -0
  151. package/runtime/ext_gui/webgl_shader.cpp +34 -36
  152. package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
  153. package/runtime/ext_gui/window.cpp +56 -1
  154. package/runtime/rt.h +3 -0
  155. package/runtime/xt_fetch/async.inc +60 -0
  156. package/runtime/xt_url_object.c +463 -0
  157. package/src/codegen/generator/module.ts +22 -7
  158. package/src/codegen/generator/state.ts +1 -0
  159. package/src/codegen/generator/tables.ts +1 -0
  160. package/src/driver/bundler/deferred.ts +351 -0
  161. package/src/driver/bundler/merge.ts +219 -2
  162. package/src/driver/runtime-objects.ts +1 -0
  163. package/src/driver/version.ts +1 -1
  164. package/src/extensions/gui/html.ts +100 -17
  165. package/src/extensions/gui/index.ts +1 -0
  166. package/src/extensions/gui/three.ts +4 -3
package/doc/gui/webgl.md CHANGED
@@ -23,8 +23,11 @@ display.
23
23
  Both **WebGL 1.0** and **WebGL 2.0** are available. A canvas keeps the first
24
24
  context kind it asks for: after `getContext("webgl2")` a later
25
25
  `getContext("webgl")` answers `null`, and the other way round. A WebGL 2 context
26
- also satisfies `instanceof WebGLRenderingContext` (its prototype inherits the
27
- WebGL 1 one) and adds the WebGL 2 constants and methods.
26
+ has its own `WebGL2RenderingContext` prototype (with a copy of the WebGL 1
27
+ constants and methods), so `gl instanceof WebGL2RenderingContext` is true while
28
+ `gl instanceof WebGLRenderingContext` is false, like a browser — libraries such
29
+ as three.js use exactly that check to pick their WebGL 2 path. `gl.constructor.name`
30
+ reports the type as well.
28
31
 
29
32
  ## How a frame reaches the screen
30
33
 
@@ -33,9 +36,10 @@ WebGL 1 one) and adds the WebGL 2 constants and methods.
33
36
  `height` attributes. `canvas.width = …` recreates them and discards the
34
37
  contents, like the DOM rule.
35
38
  - The context records into its own SDL_GPU command buffer. `clear`/`draw*`
36
- keep a render pass open; a buffer/texture upload or a framebuffer change ends
37
- it and starts a new pass with `LOAD`, so the drawing buffer persists between
38
- 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.
39
43
  - Once per frame, before the page's own render pass, the engine submits every
40
44
  WebGL command buffer. The image pipeline then samples the colour texture as
41
45
  an ordinary `PaintImage`, with no CPU copy.
@@ -54,8 +58,9 @@ format, so the engine ships two statically linked translators:
54
58
 
55
59
  - **glslang** compiles the rewritten GLSL to SPIR-V, which the Vulkan backend
56
60
  consumes directly;
57
- - **SPIRV-Cross** cross-compiles that same SPIR-V to Metal Shading Language text,
58
- which the Metal backend compiles at run time.
61
+ - **SPIRV-Cross** cross-compiles that same SPIR-V to Metal Shading Language 2.0
62
+ text (needed for arrays of textures/samplers), which the Metal backend
63
+ compiles at run time.
59
64
 
60
65
  Both live in `gui.a` / `gui.lib`; the Metal path reuses the SPIR-V module rather
61
66
  than growing a second GLSL front end, so both backends accept exactly the same
@@ -85,6 +90,8 @@ The translator (`runtime/ext_gui/webgl_glsl*.cpp`) handles GLSL ES 1.00 and
85
90
  | non-sampler uniforms | one `layout(std140) uniform XtUniforms { … }` block |
86
91
  | `uniform sampler2D u_tex` | `layout(set = 2, binding = N) uniform sampler2D u_tex` (fragment) or set 0 (vertex) |
87
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 |
88
95
  | a missing `#version` / `#version 100` | `#version 310 es` |
89
96
 
90
97
  Non-sampler uniforms are merged across both stages and emitted as one block in
@@ -96,9 +103,21 @@ reflected offsets; `uniform1i` on a sampler stores the texture unit. The block
96
103
  is pushed with the dynamic uniform-buffer API on every draw.
97
104
 
98
105
  Limits of the translator: struct-typed uniforms, user uniform blocks, uniform
99
- arrays whose size is not resolved by the preprocessor, sampler arrays are rejected (the link/shader info log says so). Shader code inside
100
- `#ifdef` is fine; non-sampler uniform declarations are moved to the enclosing
101
- top-level position so the block stays unconditional.
106
+ arrays whose size is not resolved by the preprocessor, and a sampler array
107
+ indexed by a non-constant expression are rejected (the link/shader info log
108
+ says so). A `#pragma unroll_loop_start` loop is expanded before rewriting, so
109
+ the `sampler2D shadowMap[ NUM_DIR_LIGHT_SHADOWS ]` / `shadowMap[ i ]` shape
110
+ three.js emits becomes constant-indexed. Shader code inside `#ifdef` is fine;
111
+ non-sampler uniform declarations are moved to the enclosing top-level position
112
+ so the block stays unconditional.
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).
102
121
 
103
122
  ### GLSL ES 3.00 (WebGL 2)
104
123
 
@@ -169,9 +188,10 @@ available from GLSL ES 3.00, which is what the instanced example uses.
169
188
  `framebufferTexture2D` (`COLOR_ATTACHMENT0`, `DEPTH_ATTACHMENT`,
170
189
  `DEPTH_STENCIL_ATTACHMENT`), `checkFramebufferStatus`, `createRenderbuffer`,
171
190
  `bindRenderbuffer`, `renderbufferStorage` (depth or depth-stencil),
172
- `framebufferRenderbuffer`, `delete*`, and `blitFramebuffer` (colour
173
- attachment; depth/stencil bits are accepted but not copied). This is enough
174
- 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.
175
195
  - **State**: `enable`/`disable`/`isEnabled` for `BLEND`, `DEPTH_TEST`,
176
196
  `CULL_FACE`, `SCISSOR_TEST` and `STENCIL_TEST`,
177
197
  `clearColor`/`clearDepth`/`clearStencil`, `clear` (with `SCISSOR_TEST` it
@@ -240,12 +260,17 @@ backend's `[0, 1]` clip space rather than GL's `[-1, 1] → [0, 1]` remap:
240
260
 
241
261
  ## Not implemented
242
262
 
243
- Transform feedback (the entry points are no-ops) and MSAA/sample buffers
244
- (SDL_GPU's Metal multisample resolve returns undefined pixels with scissor
245
- tests and instanced draws, so contexts stay single-sampled). `blitFramebuffer`
246
- copies the colour attachment only; depth/stencil bits are accepted but not
247
- copied. Shaders that use built-in blocks or `gl_FragDepth` are rejected with a
248
- 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.
249
274
 
250
275
  ## Platform
251
276
 
@@ -267,7 +292,10 @@ uses the 2D canvas or the DOM pays no run-time cost beyond the archive size.
267
292
  - `tests/e2e/gui-webgl.test.ts` compiles GLSL, draws a textured and a
268
293
  depth-tested quad, checks face culling and reads the pixels back; it also
269
294
  covers blend constants, dynamic buffer usage, vertex-shader texture sampling,
270
- `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.
271
299
  - `tests/e2e/gui-webgl2.test.ts` covers the WebGL 2 shader/state additions:
272
300
  `#version 300 es` shaders, a VAO round trip, instanced draws with a divisor,
273
301
  `texStorage2D`, `clearBufferfv`, buffer copies, sampler objects, struct-uniform
@@ -299,17 +327,18 @@ same assertions as the SPIR-V one.
299
327
 
300
328
  ## three.js
301
329
 
302
- The `.js` asset loader only patches three.js's shader unroller; `WebGLRenderer`
303
- runs unchanged on this engine's real WebGL 2 backend, so **no Canvas2D fallback
304
- is bundled any more**. Verified with three.js r160 through SDL_GPU:
330
+ The shader rewriter expands three.js's `#pragma unroll_loop_start` loops into
331
+ constant indices (its sampler arrays would otherwise fail Vulkan GLSL), and the
332
+ `.js` asset loader keeps the renderer's own unroller out of the way.
333
+ `WebGLRenderer` runs unchanged on this engine's real WebGL 2 backend, so **no
334
+ Canvas2D fallback is bundled any more**. Verified with three.js r160 through SDL_GPU:
305
335
  `MeshBasicMaterial`, `MeshStandardMaterial` and `MeshPhongMaterial`,
306
336
  `CanvasTexture`, linear fog, ambient/directional/point/spot/hemisphere lights,
307
337
  `Sprite`, `Line`, `Points` and `InstancedMesh`.
308
338
 
309
339
  Features outside this engine's subset still cannot be emulated: transform
310
- feedback and MSAA.
311
- Depth textures, float render targets and compressed textures are available, but
312
- three.js shadow maps still need comparative `sampler2DShadow` sampling, which
313
- is not translated yet. A scene that needs one of those will not render
314
- correctly; everything the previous Canvas2D fallback could show now goes
315
- 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.
@@ -155,17 +155,18 @@ export default __html;
155
155
  ### 2b. External `<script src>`
156
156
 
157
157
  `<script src="./app.ts">` is read relative to the HTML file, parsed, and split:
158
- its top-level `import`/`export … from` statements are **hoisted** to the
159
- generated module (with relative specifiers rewritten so they resolve from the
160
- HTML file's directory), and the remaining statements are wrapped in the script
161
- function. This is how an external script still runs at `loadHTML` time while
162
- being able to `import` other modules.
163
-
164
- Two limitations are enforced with clear errors rather than silent breakage:
165
-
166
- - a missing `src` file throws (turned into a bundler diagnostic), and
167
- - two `src` scripts that import the same local binding name collide; the user
168
- must alias one of them. (Non-conflicting or identical imports are fine.)
158
+ its top-level `import`/`export … from` statements are removed from the body, and
159
+ the remaining statements are wrapped in the script function. Relative imports
160
+ are **deferred**, like a browser module script: the loader emits a synthetic
161
+ `__xt_deferred_module` marker and the generated script calls the module wrapper
162
+ at `loadHTML` time. The bundler wraps the whole local import graph in functions
163
+ that return their exports (memoised by `__xt_defer`), so top-level module code
164
+ runs after the document exists and two scripts importing the same module share
165
+ one instance. Bare specifiers (`gui`, a package) stay eager.
166
+
167
+ A missing `src` file is still a clear error (turned into a bundler diagnostic),
168
+ and `src` URLs (`https://…`, `data:…`, `//host`) are left untouched — they are
169
+ not compile-time assets and never run.
169
170
 
170
171
  `src` URLs (`https://…`, `data:…`, `//host`) are left untouched — they are not
171
172
  compile-time assets and never run.
@@ -302,11 +303,11 @@ legacy `target` field over the handle.
302
303
  - **M9b** ✅ — gui `.html` loader (`src/extensions/gui/html.ts`), the
303
304
  `__registerScript` builtin, the script registry (`runtime/ext_gui/script.cpp`),
304
305
  `loadHTML` execution and `DOMContentLoaded`/`load`; e2e coverage.
305
- - **M9c** ✅ — `<script src>` files are read, their top-level imports are
306
- hoisted (specifiers rewritten to resolve from the HTML file) and their body
307
- is wrapped and registered; missing files and import-binding collisions
308
- surface as diagnostics. All scripts run in document order (effectively
309
- deferred). e2e coverage.
306
+ - **M9c** ✅ — `<script src>` files are read and their body is wrapped and
307
+ registered; relative imports are deferred and evaluated by a memoised
308
+ module wrapper at `loadHTML` time, so the import graph sees a parsed
309
+ document and shares one instance per module. Missing files surface as
310
+ diagnostics. All scripts run in document order. e2e coverage.
310
311
  - **M9d** *(optional)* — detect inline HTML in template literals passed to
311
312
  `win.loadHTML(...)` and transform them too (fragile; deferred).
312
313
  - **M10 — polish** ✅ — `requestAnimationFrame`/`cancelAnimationFrame` on the
@@ -330,9 +331,8 @@ legacy `target` field over the handle.
330
331
  network fetches) do not run. Documented.
331
332
  - **Template-literal HTML** passed to `win.loadHTML(…)` is not transformed in
332
333
  M9b (only imported assets are). M9d addresses this if needed.
333
- - **Import-binding collisions** between external `<script src>` files. Detected
334
- and reported (alias the import); a per-script rename with reference rewriting
335
- is possible later.
334
+ - **Top-level `await`** inside a deferred `<script src>` module is not
335
+ supported: the module body is wrapped in a synchronous init function.
336
336
  - **Assets.** `<script src>` is resolved relative to the HTML asset; URL `src`
337
337
  values are ignored. There is no fetch/network loading.
338
338
 
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.
@@ -219,10 +223,11 @@ engine were Rust.
219
223
  registered through the `__registerScript` builtin and replaced by
220
224
  `<script data-xt-id="<hash>">` markers; `win.loadHTML` runs the matching
221
225
  functions after parsing and fires `DOMContentLoaded` then `load`.
222
- - **M9c** ✅ — `<script src>` files are read relative to the HTML, their
223
- top-level imports are hoisted (specifiers rewritten from the HTML dir) and
224
- their body is wrapped/registered; missing files and import-binding
225
- collisions become diagnostics. Everything runs in document order.
226
+ - **M9c** ✅ — `<script src>` files are read relative to the HTML and their
227
+ body is wrapped/registered; relative imports are deferred to `loadHTML`
228
+ through a memoised module wrapper, so imported modules see the parsed
229
+ document and share state. Missing files become diagnostics. Everything
230
+ runs in document order.
226
231
  10. **M10 — polish** ✅
227
232
  - `win.requestAnimationFrame(fn)` / `win.cancelAnimationFrame(id)`; callbacks
228
233
  run at the top of each frame with the frame timestamp and may mutate the
@@ -341,6 +346,18 @@ engine were Rust.
341
346
  offscreen backdrop pass (see [Known issues](./gui/known-issues.md)).
342
347
  - E2E coverage in `tests/e2e/gui-effects.test.ts`; see
343
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).
344
361
 
345
362
  ## Progress log
346
363
 
@@ -387,10 +404,11 @@ engine were Rust.
387
404
  bodies into AOT functions registered at startup and run by `win.loadHTML`
388
405
  (before first layout; `DOMContentLoaded` then `load`). Unit tests in
389
406
  `tests/extensions/gui.test.ts`, e2e in `tests/e2e/gui-*.test.ts`.
390
- - **M9c** ✅ external `<script src>` files are read, their imports hoisted
391
- (specifiers rewritten from the HTML dir) and their body wrapped/registered;
392
- missing files and import-binding collisions are diagnostics. Loader errors are
393
- caught by `loadGraph` and reported as build errors.
407
+ - **M9c** ✅ external `<script src>` files are read and their body
408
+ wrapped/registered; relative imports are deferred to `loadHTML` through a
409
+ memoised module wrapper (so module-level DOM work sees the parsed document),
410
+ while missing files are diagnostics. Loader errors are caught by `loadGraph`
411
+ and reported as build errors.
394
412
  - **M10** ✅ `requestAnimationFrame`/`cancelAnimationFrame` on window handles
395
413
  (callbacks run with the frame timestamp before layout each frame) plus
396
414
  `offsetWidth`/`offsetHeight`/`contains` on element handles; e2e coverage in
@@ -488,4 +506,20 @@ engine were Rust.
488
506
  functions plus nine-tap blur/drop-shadow and emits `text-shadow` runs, and
489
507
  `paint_clip.cpp`/`renderer_clip.cpp` clip geometry against triangulated
490
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).
491
525
 
@@ -13,6 +13,11 @@ Location: `runtime/xt_fetch.c`, `runtime/xt_fetch/async.inc`, `runtime/xt_async_
13
13
  exchange, up to 20 redirects, and a 301/302/303 switches POST to GET.
14
14
  - It accepts `http://` URLs only — `https://` rejects with a `TypeError` because
15
15
  no TLS backend is linked.
16
+ - A reference with **no scheme** (a relative path) and `file://` URLs read a
17
+ local file, so a compiled program can load an asset from disk without an HTTP
18
+ server. Relative paths resolve against the working directory; `GET` / `HEAD`
19
+ are supported. On Windows the standard `file:///C:/dir/file` spelling and
20
+ `file://C:/dir/file` / `file://C:\dir\file` all name the same file.
16
21
  - Options: `method`, `headers` (plain object or `Headers`), `body` and `redirect`
17
22
  (`follow` / `manual` / `error`).
18
23
  - The resolved `Response`-shaped object exposes `ok` / `status` / `statusText` /
@@ -19,6 +19,19 @@ Location: `src/parser/parser.ts` (syntax), `src/driver/modules.ts` (bundling),
19
19
  - Imported bindings and namespace members are snapshots at module-evaluation time (no live bindings) — see [Unimplemented: module system](../unimplemented/modules.md).
20
20
  - Type-only imports and re-exports (`import type { T } from "..."`, `import { type T } from "..."`, `export type { T } from "..."`) are erased. They are **not** module edges: the target is not loaded or bundled, and a type-only cycle between modules is not a circular dependency. Use a side-effect import (`import "./mod"`) when a module must be evaluated without binding a name.
21
21
 
22
+ ## Deferred evaluation
23
+
24
+ - The `gui` `.html` asset loader marks relative `<script src>` imports with a
25
+ synthetic `__xt_deferred_module` binding. The bundler then wraps that module
26
+ and its local import graph in functions that return their exports, and the
27
+ generated script calls them after `loadHTML` (`__xt_defer` memoises each
28
+ module). This matches a browser module script, where the graph is evaluated
29
+ once the document exists.
30
+ - Modules also reachable through a normal (non-marker) import stay eager and get
31
+ a thin wrapper over their already-initialised exports.
32
+ - The wrapper body is synchronous: top-level `await` inside a deferred script
33
+ module is not supported.
34
+
22
35
  ## import.meta
23
36
 
24
37
  - `import.meta.url` / `.dirname` / `.filename`, resolved from the running executable's path.
@@ -55,6 +55,23 @@ own pages: [Collections](./collections.md), [Typed arrays and binary data](./typ
55
55
  - `Symbol(description)` primitives (`XT_OBJECT_KIND_SYMBOL`), the 13 well-known symbols (`Symbol.iterator`, `Symbol.asyncIterator`, `Symbol.match`, ...), `Symbol.for` / `Symbol.keyFor` global registry, `symbol.description` / `toString()` / `valueOf()`.
56
56
  - Symbols are valid property keys (`Object.getOwnPropertySymbols`, symbol keyed `get`/`set`/`in`/`delete`), are skipped by `Object.keys` / `values` / `entries` / `for...in` / `JSON.stringify`, and print as `Symbol(desc)`.
57
57
 
58
+ ## URL
59
+
60
+ - `URL` is implemented in `runtime/xt_url_object.c`: `new URL(input)` and
61
+ `new URL(input, base)` (the base may be another `URL`), resolving relative
62
+ references against a hierarchical `scheme://authority/path` base.
63
+ - A reference with **no scheme is a filesystem path**: `new URL("./model.glb")`
64
+ resolves against the working directory and yields a `file:` URL (an explicit
65
+ base is still honoured, so `new URL("./x", import.meta.url)` works too).
66
+ - Read-only `href`, `protocol`, `origin`, `host`, `hostname`, `port`,
67
+ `pathname`, `search`, `hash`, `searchParams` and `toString()` / `toJSON()`;
68
+ absolute inputs are kept as written, except that the Windows spellings of a
69
+ `file:` URL (`file://C:/dir` and `file://C:\dir`) are canonicalized to
70
+ `file:///C:/dir`, matching Node.
71
+ - Not a full WHATWG parser: path percent-encoding, IDNA, setters and live
72
+ `searchParams` synchronisation are not implemented — see
73
+ [Runtime deviations](../unimplemented/deviations.md).
74
+
58
75
  ## URLSearchParams
59
76
 
60
77
  - `URLSearchParams` is implemented in `runtime/xt_url.c`.
@@ -8,6 +8,7 @@ These features **compile and run**, but the result does not fully match ECMAScri
8
8
  | --- | --- |
9
9
  | RegExp byte-oriented matching | The engine matches UTF-8 bytes: literal multi-byte text works, but `.` and character classes consume one byte per repetition (`\d` / `\w` stay ASCII). Offsets returned by `exec` / `search` / replacement callbacks are converted to UTF-16 code units |
10
10
  | Global RegExp `lastIndex` | `test` / `exec` do not advance or honour a caller-set `lastIndex` for `/g` / `/y` regexes |
11
+ | `URL` parser | Read-only WHATWG subset: no path percent-encoding, IDNA, setters or live `searchParams`; a reference with no scheme resolves against the working directory as a `file:` URL |
11
12
  | String `normalize` | Not implemented |
12
13
  | First-class built-in methods | Implemented as unbound method values (see [Functions](./functions.md)); `fn.toString()` returns a placeholder rather than source text |
13
14
  | `for...in` | Enumerates own keys of objects / arrays / strings; does not include prototype-chain properties |
@@ -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
 
@@ -11,9 +11,11 @@ CSS 盒中。
11
11
 
12
12
  引擎侧的对象是 `xtgui::Canvas2D`(`runtime/ext_gui/canvas.{h,cpp}`)——一块 RGBA8 位图
13
13
  加上 2D 绘制状态。光栅器位于 `canvas_raster.cpp`,描边几何(端点、连接、虚线)位于
14
- `canvas_stroke.cpp`,渐变位于 `canvas_gradient.cpp`,绘制状态解析位于
15
- `canvas_style.cpp`;DOM 绑定位于 `dom_api_canvas.cpp` / `dom_api_canvas_extra.cpp`;
16
- 按窗口的生命周期位于 `gui_canvas.cpp`。
14
+ `canvas_stroke.cpp`,渐变位于 `canvas_gradient.cpp`,合成模式位于
15
+ `canvas_composite.cpp`,裁剪与命中测试位于 `canvas_clip.cpp`,阴影位于
16
+ `canvas_shadow.cpp`,绘制状态解析位于 `canvas_style.cpp`;DOM 绑定位于
17
+ `dom_api_canvas*.cpp` / `dom_api_offscreen.cpp`;按窗口的生命周期位于
18
+ `gui_canvas.cpp`。
17
19
 
18
20
  ## 获取上下文
19
21
 
@@ -31,6 +33,15 @@ const ctx = canvas.getContext("2d"); // 或 "webgl" 取得 GPU 上下文
31
33
  元素上,因此每次都返回**同一个**对象,绘制状态(变换、样式、路径)按 canvas 而非按调用
32
34
  保存。`ctx.canvas` 是所属元素的句柄。
33
35
 
36
+ `OffscreenCanvas` 提供同样的 2D 接口,但没有 DOM 元素:
37
+ `new OffscreenCanvas(width, height)` 分配一块由窗口拥有的位图,`getContext("2d")`
38
+ 返回的上下文其 `canvas` 指向离屏对象;`canvas.transferControlToOffscreen()` 会把一个
39
+ 活动元素的位图交给离屏对象(此后元素的 `getContext(…)` 返回 `null`)。
40
+ `drawImage`、`createPattern`、`createImageBitmap` 与 `texImage2D` 都接受
41
+ `OffscreenCanvas` 作为源。`transferToImageBitmap()` 返回 `ImageBitmap`,
42
+ `convertToBlob()` 解析为与 `toBlob` 相同的 PNG blob 形状(本运行时没有 worker,
43
+ 离屏画布也不提供 WebGL 上下文)。
44
+
34
45
  `getContext("2d", attributes)` 在首次调用时会尊重 `alpha`:传入 `{ alpha: false }`
35
46
  时位图以不透明黑色初始化(清除也回到不透明黑)。其余属性由
36
47
  `getContextAttributes()` 按规范默认值报告(`antialias: true`、`depth: true`、
@@ -42,8 +53,30 @@ const ctx = canvas.getContext("2d"); // 或 "webgl" 取得 GPU 上下文
42
53
  光栅器刻意保持精简且为自研(不依赖 Skia/Cairo)。它按每条像素行 **4 条垂直子扫描线**
43
54
  进行抗锯齿,并做一次**水平解析覆盖率**计算;路径默认采用**非零环绕规则**填充(因此当弧线
44
55
  以相反方向环绕时能形成空洞),`fill("evenodd")` 则改用奇偶规则。轴对齐矩形(变换中
45
- `b == 0 && c == 0`)走直接的覆盖率快速路径。混合为非预乘 source-over,并按
46
- `globalAlpha` 缩放。
56
+ `b == 0 && c == 0`)走直接的覆盖率快速路径。
57
+
58
+ 混合默认是非预乘 source-over。`globalCompositeOperation` 支持完整的 canvas 集合:
59
+ Porter-Duff 算子(`source-over`、`source-in`、`source-out`、`source-atop`、
60
+ `destination-over`、`destination-in`、`destination-out`、`destination-atop`、
61
+ `lighter`、`copy`、`xor`)以及可分离/不可分离混合模式(`multiply`、`screen`、
62
+ `overlay`、`darken`、`lighten`、`color-dodge`、`color-burn`、`hard-light`、
63
+ `soft-light`、`difference`、`exclusion`、`hue`、`saturation`、`color`、
64
+ `luminosity`)。抗锯齿与裁剪区域产生的部分覆盖率会在“未改动的目标”与“完整合成结果”
65
+ 之间插值,因此每个算子在部分覆盖的像素上都正确。
66
+
67
+ `clip()` 用当前路径(或 `Path2D` 加可选的 `"nonzero"`/`"evenodd"` 规则)与当前裁剪区
68
+ 求交。区域以设备空间覆盖率掩码保存,因此它固定在调用 `clip()` 时的变换下;
69
+ `save`/`restore` 会一并恢复,位图尺寸变化会清空它。所有填充、描边、文本、图像绘制与
70
+ `clearRect` 都遵守该掩码。
71
+
72
+ 阴影(`shadowColor`、`shadowBlur`、`shadowOffsetX`、`shadowOffsetY`)由绘制操作自身的
73
+ alpha 生成:先把操作渲染到临时图层,对其 alpha 做偏移与盒式模糊(三次可分离模糊),用
74
+ `shadowColor` 着色后合成,最后把未处理的图层画在其上。偏移与模糊使用画布(设备)单位,
75
+ 不受变换影响,符合 API 规定。`clearRect` 与 `putImageData` 从不产生阴影。
76
+
77
+ `isPointInPath(x, y[, fillRule])`、`isPointInPath(path, x, y[, fillRule])`、
78
+ `isPointInStroke(x, y)` 与 `isPointInStroke(path, x, y)` 在当前变换下对当前路径(或
79
+ 传入的 `Path2D`)做命中测试;描边命中测试使用到各展平线段的设备空间距离与当前线宽。
47
80
 
48
81
  填充或描边可以使用纯色,也可以使用 **`CanvasGradient`**。渐变的坐标属于用户空间,
49
82
  因此光栅器会先用当前变换的逆矩阵把每个设备像素映射回用户空间再采样——形状被平移、
@@ -54,6 +87,27 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
54
87
  `<video>`/`<audio>` 解码表结构相同)。当节点被丢弃(`loadHTML`、窗口关闭/退出,或丢弃
55
88
  子树的变更操作)时随之释放,并释放对应的 GPU 纹理。
56
89
 
90
+ ## 性能
91
+
92
+ canvas 由脚本重绘而不是引擎重绘,因此开销按绘制调用计。多元素场景下的热路径刻意
93
+ 保持少分支:
94
+
95
+ - 纯色不透明的 `fillRect` 直接按字节写入位图(小数边缘仍走抗锯齿路径),覆盖整张
96
+ 位图的 `clearRect` 退化为一次 `memset`;
97
+ - 路径覆盖率使用逐行**活动边表**光栅化,并且只物化每一行**实际覆盖的区间**——一条
98
+ 细对角线描边只花它自己的像素,而不是整个包围盒;
99
+ - 一次 `stroke()` 把所有线段四边形、连接与端点合并成一次非零环绕填充,而不是每段
100
+ 各做一次覆盖率计算(先统一每个多边形的绕向,重叠处才能覆盖而不是抵消);
101
+ - `fillText` 与布局共享整形缓存(`text_cache.cpp`),并使用有界两代字形位图缓存
102
+ (`text_glyph_cache.cpp`);
103
+ - 整数偏移、无缩放的 `drawImage` 逐像素只取一个纹素,不再跑双线性采样器。
104
+
105
+ 位图通过**脏矩形**到达屏幕:每次绘制把它的设备空间包围盒并入待上传区域,渲染器只
106
+ 拷贝该区域到 canvas 纹理。一帧内所有 canvas 与视频上传共用帧的唯一上传命令缓冲,
107
+ 因此 K 张动画 canvas 只需一次提交而不是 K 次。`getImageData` 始终读取 CPU 位图,
108
+ 不受影响。`tests/e2e/gui-draw-perf.test.ts` 测量这些多元素路径并固定计数器行为
109
+ (每帧一次上传、只传脏字节)。
110
+
57
111
  ## 支持的 API
58
112
 
59
113
  状态与变换:`save` / `restore`(会恢复变换、线宽、透明度、字体/文本设置**以及**
@@ -64,30 +118,37 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
64
118
  `translate(x, y); rotate(a)` 围绕平移后的原点旋转。
65
119
 
66
120
  样式与设置:`fillStyle` / `strokeStyle`(层叠所用的颜色解析器能识别的 CSS 颜色,或
67
- `CanvasGradient`)、`lineWidth`、`globalAlpha`、`lineCap`(`butt`/`round`/`square`)、
121
+ `CanvasGradient`)、`lineWidth`、`globalAlpha`、`globalCompositeOperation`、
122
+ `shadowColor` / `shadowBlur` / `shadowOffsetX` / `shadowOffsetY`、
123
+ `imageSmoothingEnabled` / `imageSmoothingQuality`、`lineCap`(`butt`/`round`/`square`)、
68
124
  `lineJoin`(`miter`/`round`/`bevel`)、`miterLimit`、`lineDashOffset`、`font`
69
125
  (CSS 字体简写,解析出字体族、字号与粗体/斜体——默认 `10px sans-serif`)、
70
126
  `textAlign`(`start`/`left`、`center`、`right`/`end`)与 `textBaseline`
71
127
  (`alphabetic`、`top`、`middle`、`bottom`、`hanging`、`ideographic`)。读取属性返回
72
- 上次赋予的值;赋予无法解析的颜色会保留原值。
128
+ 上次赋予的值;赋予无法解析的颜色会保留原值。`imageSmoothingEnabled` 默认为 `true`
129
+ (双线性采样);`imageSmoothingQuality` 报告 `low`、`medium` 或 `high`,本后端都使用
130
+ 同一种双线性过滤。
73
131
 
74
132
  渐变:`createLinearGradient(x0, y0, x1, y1)` 与
75
133
  `createRadialGradient(x0, y0, r0, x1, y1, r1)` 返回带
76
134
  `addColorStop(offset, color)` 的 `CanvasGradient`。色标是**活的**:在
77
135
  `fillStyle = gradient` 之后追加的色标会影响后续填充,且 `fillStyle`/`strokeStyle`
78
- 的读取返回同一个渐变句柄。径向渐变按到**外圆**圆心(`x1, y1`)的距离插值,因此目前
79
- 会忽略 `x0`/`y0`——请使用同心圆(`x0 == x1`、`y0 == y1`)。
136
+ 的读取返回同一个渐变句柄。径向渐变是通用的双圆锥:点上的颜色由插值圆
137
+ (`圆心 = c0 + t·(c1 − c0)`、`半径 = r0 + t·(r1 − r0)`)解出,因此内圆有偏移、两圆
138
+ 半径不同时都与浏览器一致。内圆内部的点采样色标 0。
80
139
 
81
140
  路径:`beginPath`、`closePath`、`moveTo`、`lineTo`、`rect`、`roundRect`、`arc`、
82
141
  `ellipse`、`quadraticCurveTo`、`bezierCurveTo`,然后
83
- `fill(fillRule?)` / `fill(path, fillRule?)`(多环;`"nonzero"` 或 `"evenodd"`)或
84
- `stroke(path?)`。贝塞尔与二次曲线段会被展平为线段;弧线与椭圆同样被展平。
142
+ `fill(fillRule?)` / `fill(path, fillRule?)`(多环;`"nonzero"` 或 `"evenodd"`)、
143
+ `stroke(path?)`、`clip(path?, fillRule?)` 以及 `isPointInPath`/`isPointInStroke`
144
+ 命中测试。贝塞尔与二次曲线段会被展平为线段;弧线与椭圆同样被展平。
85
145
  `roundRect` 接受单个半径数字,或 CSS 半径列表(`[all]`、`[tl, br]`、
86
146
  `[tl, tr, br]`、`[tl, tr, br, bl]`,元素为数字或 `{ x, y }` 对;圆角按圆弧绘制)。
87
147
 
88
148
  `Path2D` 支持同一组路径命令(`moveTo`、`lineTo`、`closePath`、`rect`、
89
149
  `roundRect`、`arc`、`ellipse`、`quadraticCurveTo`、`bezierCurveTo`、`addPath`),
90
- 并可传给 `fill`/`stroke`,因此一次构建的路径可以在不同变换下重复使用。
150
+ 并可传给 `fill`/`stroke`/`clip` 以及 `isPointIn*` 命中测试,因此一次构建的路径可以在
151
+ 不同变换下重复使用。
91
152
 
92
153
  矩形:`clearRect`、`fillRect`、`strokeRect`。
93
154
 
@@ -103,29 +164,37 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
103
164
 
104
165
  图像:`drawImage(source, …)` 支持 3 参数(`dx, dy`)、5 参数(`dx, dy, dw, dh`)与
105
166
  9 参数(本引擎为**目标优先**:`dx, dy, dw, dh, sx, sy, sw, sh`)。完整的仿射变换
106
- (旋转/斜切/缩放)会被应用:目标矩形变为平行四边形,每个设备像素反查到源矩形
107
- (最近邻采样)。`source` 可以是 `<img>` 元素(经图像
108
- 模块解码)或另一个 `<canvas>` 元素(取其当前位图)。采样为最近邻,因此把小图大幅放大
109
- 会有块状感——适合图标与像素画,不适合照片管线。
167
+ (旋转/斜切/缩放)会被应用:目标矩形变为平行四边形,每个设备像素反查到源矩形。
168
+ `source` 可以是 `<img>` 元素(经图像模块解码)、另一个 `<canvas>`、
169
+ `OffscreenCanvas` 或 `ImageBitmap`。`imageSmoothingEnabled` 为 `true`(默认)时使用
170
+ 双线性采样,为 `false` 时使用最近邻。
110
171
 
111
172
  测试钩子:`getImageData(x, y, w, h)` 把区域复制为 `{ width, height, data }`,其中
112
173
  `data` 是按行主序的 RGBA8 字节数组。越界像素为透明。
113
174
 
114
- 图案:`createPattern(source, repetition?)` 接受 `<img>` 或另一个 `<canvas>`,在用户
115
- 空间中平铺(`repeat`、`repeat-x`、`repeat-y`、`no-repeat`),因此平铺会跟随当前
116
- 变换。把结果赋给 `fillStyle`/`strokeStyle`;重新赋颜色会替换掉图案。
175
+ 图案:`createPattern(source, repetition?)` 接受 `<img>`、`<canvas>`、
176
+ `OffscreenCanvas` 或 `ImageBitmap`,在用户空间中平铺(`repeat`、`repeat-x`、
177
+ `repeat-y`、`no-repeat`),因此平铺会跟随当前变换。把结果赋给
178
+ `fillStyle`/`strokeStyle`;重新赋颜色会替换掉图案。
117
179
 
118
180
  图像数据与导出:`createImageData(width, height)` 分配一个空的
119
181
  `{ width, height, data }`,`putImageData(imageData, dx, dy[, dirtyX, dirtyY,
120
- dirtyWidth, dirtyHeight])` 直接把字节写入位图(不受变换、混合或 `globalAlpha`
121
- 影响)。`canvas.toDataURL(type?)` 导出 PNG data URL,`canvas.toBlob(callback,
122
- type?)` 从微任务中交付 Blob 形状的 `{ type, size }`(字节在
123
- `__xt_gui_blob_data` 上;本运行时没有 `URL.createObjectURL`)。
182
+ dirtyWidth, dirtyHeight])` 直接把字节写入位图(不受变换、混合、裁剪或
183
+ `globalAlpha` 影响)。`canvas.toDataURL(type?)` 导出 PNG data URL,
184
+ `canvas.toBlob(callback, type?)` 从微任务中交付 Blob 形状的 `{ type, size }`
185
+ (字节在 `__xt_gui_blob_data` 上;本运行时没有 `URL.createObjectURL`)。
186
+ `OffscreenCanvas.convertToBlob()` 解析为同一形状,`transferToImageBitmap()` 对位图
187
+ 做快照。
188
+
189
+ CSS 背景:`background-image: url(...)` 接受文件路径或 `data:` URL,因此可以先把
190
+ canvas 导出再作为 CSS 背景使用
191
+ (`element.style.backgroundImage = "url(" + canvas.toDataURL() + ")"`)。图像层会拉伸
192
+ 到元素的边框盒;`background-size`、`background-position` 与平铺尚未建模。
124
193
 
125
194
  ## 尚未实现
126
195
 
127
- 阴影、`clip()`、`globalCompositeOperation`、`isPointInPath`、可切换图像平滑的
128
- `drawImage`、把 `<canvas>` 用作 CSS `background-image`,以及离屏画布
129
- (`OffscreenCanvas`、`transferControlToOffscreen`)。径向渐变会忽略内圆偏移。
196
+ 本后端的平滑质量只有一种过滤(双线性)。离屏画布只支持 2D 上下文(WebGL 上下文需要
197
+ DOM 元素来维护纹理表)。旋转的字形位图仍不会随变换旋转,`strokeText` 仍用描边样式
198
+ 填充而非生成轮廓。
130
199
 
131
200
  另见[媒体](./media.md):`drawImage` 与纹理上传所共享的图像/视频/音频解码栈。