xbintsc 0.5.38 → 0.5.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/codegen/generator/module.js +23 -7
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/html.js +9 -2
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/tests/codegen/closures.test.js +7 -0
- package/dist/tests/codegen/closures.test.js.map +1 -1
- package/dist/tests/e2e/compile-objects-classes.test.js +31 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/gui-canvas-state.test.d.ts +9 -0
- package/dist/tests/e2e/gui-canvas-state.test.js +297 -0
- package/dist/tests/e2e/gui-canvas-state.test.js.map +1 -0
- package/dist/tests/e2e/gui-data-url.test.d.ts +10 -0
- package/dist/tests/e2e/gui-data-url.test.js +127 -0
- package/dist/tests/e2e/gui-data-url.test.js.map +1 -0
- package/dist/tests/e2e/gui-draw-perf.test.d.ts +22 -0
- package/dist/tests/e2e/gui-draw-perf.test.js +405 -0
- package/dist/tests/e2e/gui-draw-perf.test.js.map +1 -0
- package/dist/tests/e2e/gui-effects.test.js +96 -0
- package/dist/tests/e2e/gui-effects.test.js.map +1 -1
- package/dist/tests/e2e/gui-paint.test.js +77 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/dist/tests/e2e/gui-perf.test.d.ts +23 -0
- package/dist/tests/e2e/gui-perf.test.js +283 -0
- package/dist/tests/e2e/gui-perf.test.js.map +1 -0
- package/dist/tests/e2e/gui-script-import-order.test.js +9 -1
- package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl.test.js +4 -4
- package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.d.ts +8 -0
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js +336 -0
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.js +23 -0
- package/dist/tests/extensions/gui.test.js.map +1 -1
- package/doc/gui/api.md +28 -2
- package/doc/gui/canvas.md +114 -27
- package/doc/gui/known-issues.md +47 -19
- package/doc/gui/media.md +11 -2
- package/doc/gui/rendering.md +120 -18
- package/doc/gui/runtime.md +18 -3
- package/doc/gui/webgl.md +38 -19
- package/doc/gui.md +38 -5
- package/doc/zh-CN/gui/api.md +25 -2
- package/doc/zh-CN/gui/canvas.md +97 -26
- package/doc/zh-CN/gui/known-issues.md +32 -12
- package/doc/zh-CN/gui/media.md +9 -2
- package/doc/zh-CN/gui/rendering.md +89 -11
- package/doc/zh-CN/gui/runtime.md +13 -3
- package/doc/zh-CN/gui/webgl.md +27 -5
- package/doc/zh-CN/gui.md +25 -2
- package/package.json +7 -7
- package/runtime/ext_gui/browser.cpp +3 -1
- package/runtime/ext_gui/canvas.cpp +135 -89
- package/runtime/ext_gui/canvas.h +116 -0
- package/runtime/ext_gui/canvas_clip.cpp +143 -0
- package/runtime/ext_gui/canvas_composite.cpp +241 -0
- package/runtime/ext_gui/canvas_coverage.cpp +241 -0
- package/runtime/ext_gui/canvas_gradient.cpp +41 -4
- package/runtime/ext_gui/canvas_image_data.cpp +4 -0
- package/runtime/ext_gui/canvas_internal.h +34 -9
- package/runtime/ext_gui/canvas_raster.cpp +207 -178
- package/runtime/ext_gui/canvas_shadow.cpp +161 -0
- package/runtime/ext_gui/canvas_stroke.cpp +37 -9
- package/runtime/ext_gui/canvas_style.cpp +59 -0
- package/runtime/ext_gui/canvas_text.cpp +140 -0
- package/runtime/ext_gui/css.h +7 -0
- package/runtime/ext_gui/css_sheet.cpp +22 -0
- package/runtime/ext_gui/document.cpp +25 -1
- package/runtime/ext_gui/document.h +8 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +48 -15
- package/runtime/ext_gui/dom_api_canvas_image.cpp +9 -0
- package/runtime/ext_gui/dom_api_canvas_state.cpp +217 -0
- package/runtime/ext_gui/dom_api_internal.h +36 -37
- package/runtime/ext_gui/dom_api_offscreen.cpp +213 -0
- package/runtime/ext_gui/dom_api_proto.cpp +18 -0
- package/runtime/ext_gui/dom_api_webgl.cpp +19 -9
- package/runtime/ext_gui/dom_api_webgl_internal.h +58 -0
- package/runtime/ext_gui/dom_api_webgl_source.cpp +18 -0
- package/runtime/ext_gui/gradient_values.cpp +56 -49
- package/runtime/ext_gui/gui.cpp +5 -0
- package/runtime/ext_gui/gui_canvas.cpp +4 -0
- package/runtime/ext_gui/gui_engine.h +30 -0
- package/runtime/ext_gui/gui_events.cpp +185 -58
- package/runtime/ext_gui/gui_media.cpp +3 -0
- package/runtime/ext_gui/image.cpp +225 -1
- package/runtime/ext_gui/image.h +6 -4
- package/runtime/ext_gui/image_bitmap_browser.cpp +11 -0
- package/runtime/ext_gui/layout.cpp +4 -0
- package/runtime/ext_gui/paint.cpp +72 -3
- package/runtime/ext_gui/paint.h +11 -1
- package/runtime/ext_gui/paint_background.cpp +110 -12
- package/runtime/ext_gui/paint_effects.cpp +2 -1
- package/runtime/ext_gui/paint_internal.h +5 -1
- package/runtime/ext_gui/perf.cpp +171 -0
- package/runtime/ext_gui/perf.h +127 -0
- package/runtime/ext_gui/renderer.cpp +190 -54
- package/runtime/ext_gui/renderer.h +52 -14
- package/runtime/ext_gui/renderer_image.cpp +135 -65
- package/runtime/ext_gui/renderer_internal.h +41 -3
- package/runtime/ext_gui/renderer_layer.cpp +552 -0
- package/runtime/ext_gui/renderer_text.cpp +24 -12
- package/runtime/ext_gui/style.cpp +101 -30
- package/runtime/ext_gui/style.h +35 -4
- package/runtime/ext_gui/style_print.cpp +15 -1
- package/runtime/ext_gui/style_values.cpp +10 -0
- package/runtime/ext_gui/text.cpp +23 -4
- package/runtime/ext_gui/text.h +29 -0
- package/runtime/ext_gui/text_cache.cpp +118 -0
- package/runtime/ext_gui/text_glyph_cache.cpp +100 -0
- package/runtime/ext_gui/webgl.h +48 -146
- package/runtime/ext_gui/webgl_blit.cpp +12 -1
- package/runtime/ext_gui/webgl_blit_depth.cpp +270 -0
- package/runtime/ext_gui/webgl_buffer.cpp +59 -1
- package/runtime/ext_gui/webgl_buffer_objects.cpp +106 -9
- package/runtime/ext_gui/webgl_clear.cpp +3 -0
- package/runtime/ext_gui/webgl_context.cpp +29 -3
- package/runtime/ext_gui/webgl_draw.cpp +75 -270
- package/runtime/ext_gui/webgl_draw_buffers.cpp +1 -0
- package/runtime/ext_gui/webgl_draw_setup.cpp +321 -0
- package/runtime/ext_gui/webgl_draw_state.h +130 -0
- package/runtime/ext_gui/webgl_glsl_scan.cpp +3 -8
- package/runtime/ext_gui/webgl_objects.h +148 -0
- package/runtime/ext_gui/webgl_pipeline.cpp +8 -8
- package/runtime/ext_gui/webgl_program_internal.h +35 -0
- package/runtime/ext_gui/webgl_shader.cpp +22 -30
- package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
- package/runtime/ext_gui/window.cpp +56 -1
- package/src/codegen/generator/module.ts +22 -7
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/html.ts +9 -2
package/doc/zh-CN/gui/webgl.md
CHANGED
|
@@ -21,11 +21,26 @@ if (gl == null) {
|
|
|
21
21
|
## 一帧如何到达屏幕
|
|
22
22
|
|
|
23
23
|
- 每个上下文拥有一张离屏 `R8G8B8A8_UNORM` 颜色纹理(绘图缓冲)和一张 `D32_FLOAT` 深度纹理,尺寸取自 canvas 的 `width` / `height` 属性。`canvas.width = …` 会重建它们并丢弃内容,与 DOM 规则一致。
|
|
24
|
-
- 上下文记录到自己的 SDL_GPU 命令缓冲。`clear`/`draw*` 保持一个渲染 pass
|
|
24
|
+
- 上下文记录到自己的 SDL_GPU 命令缓冲。`clear`/`draw*` 保持一个渲染 pass 打开;只有缓冲区/纹理上传或切换 framebuffer 才会结束它,并以 `LOAD` 开始新 pass,因此绘图缓冲在多次绘制之间保留,和 WebGL 一样;没有上传的绘制不会中断 pass。
|
|
25
25
|
- 每帧一次,在页面自己的渲染 pass 之前,引擎提交所有 WebGL 命令缓冲。随后图像管线把颜色纹理当作普通 `PaintImage` 采样,没有 CPU 拷贝。
|
|
26
26
|
- WebGL 原点在左下,SDL_GPU 渲染目标原点在左上,因此帧缓冲按自上而下存储:转换 `viewport`/`scissor`,`readPixels` 再把行翻回去,合成器看到的是正立的纹理。
|
|
27
27
|
- 固定功能状态变化(`blendFunc`、`depthFunc`、`cullFace`、`colorMask` 等)会改变所需的 `SDL_GPUGraphicsPipeline`,因此管线按(程序、顶点布局、状态)惰性创建,并缓存在程序上。
|
|
28
28
|
|
|
29
|
+
## 性能
|
|
30
|
+
|
|
31
|
+
重复绘制命中缓存,而不是每次重建:当前(程序、属性布局、固定功能状态、附件格式)
|
|
32
|
+
对应的管线、顶点布局与缓冲绑定只准备一次并复用(`webgl_draw_setup.cpp`);状态变化时
|
|
33
|
+
缓存未命中并新建管线。固定功能状态被哈希进 128 位键,而不是每次 draw 拼字符串;活动
|
|
34
|
+
属性在链接时解析一次。上传被前移到渲染 pass 之前:没有上传的绘制不会结束 pass,禁用
|
|
35
|
+
的顶点属性读取持久的常量缓冲而不是每次 draw 分配 GPU 缓冲,状态未变的绘制不重复绑定。
|
|
36
|
+
`bufferSubData` 只拷贝写入的字节区间,增长中的缓冲按 2 的幂扩容。
|
|
37
|
+
|
|
38
|
+
`win.stats()` 报告 `webglDraws`、`webglPasses`、`webglPipelineMisses` 与
|
|
39
|
+
`webglBufferUploadBytes`;`tests/e2e/gui-draw-perf.test.ts` 正是用它们固定行为
|
|
40
|
+
(2,000 次绘制只开一个 pass、稳定状态不再创建管线、4 MiB 缓冲只重传 64 KiB 区间)。
|
|
41
|
+
剩余的每次 draw 开销来自后端自身的提交;大量相同对象仍应由应用改用
|
|
42
|
+
`drawElementsInstanced`。
|
|
43
|
+
|
|
29
44
|
## 着色器:GLSL ES 到 SPIR-V(再到 MSL)
|
|
30
45
|
|
|
31
46
|
SDL_GPU 不能在运行时编译 GLSL,且每个后端只消费自己的格式,因此引擎静态链接了两个翻译器:
|
|
@@ -51,12 +66,16 @@ SDL_GPU 不能在运行时编译 GLSL,且每个后端只消费自己的格式
|
|
|
51
66
|
| 非采样器 uniform | 一个 `layout(std140) uniform XtUniforms { … }` 块 |
|
|
52
67
|
| `uniform sampler2D u_tex` | `layout(set = 2, binding = N) uniform sampler2D u_tex`(片元)或 set 0(顶点) |
|
|
53
68
|
| `gl_FragData[0]` | `xt_FragColor` |
|
|
69
|
+
| `gl_FragDepth` / `gl_FragDepthEXT` | 保留(SPIR-V `DepthReplacing`;MSL `[[depth(any)]]`) |
|
|
70
|
+
| `sampler2DShadow` / `samplerCubeShadow` / `sampler2DArrayShadow` | 保留;绑定为 SDL_GPU 比较采样器 |
|
|
54
71
|
| 缺少 `#version` / `#version 100` | `#version 310 es` |
|
|
55
72
|
|
|
56
73
|
实现限制:结构体 uniform、用户 uniform 块、预处理阶段无法确定长度的 uniform 数组,以及用非常量表达式索引的采样器数组会被拒绝(shader/link info log 会说明)。`#pragma unroll_loop_start` 循环会在改写前展开,因此 three.js 生成的 `sampler2D shadowMap[ NUM_DIR_LIGHT_SHADOWS ]` / `shadowMap[ i ]` 会变成常量索引。`#ifdef` 内的着色器代码可用;非采样器 uniform 声明会被移动到顶层,保证块保持无条件。两个阶段的非采样器 uniform 会合并成同一个块,并在每个阶段各声明一次(顶点 set 1、片元 set 3),与 SDL_GPU 的 Vulkan 描述符布局一致。在 Metal 上同一批声明会变成 `[[buffer(0)]]` 与 `[[texture(N)]]`/`[[sampler(N)]]`,正是 SDL_GPU Metal 后端绑定的槽位。`uniformMatrix4fv` 按列主序写入反射出的偏移;对采样器调用 `uniform1i` 时记录纹理单元。每次绘制都用动态 uniform 缓冲接口推送该块。
|
|
57
74
|
|
|
58
75
|
翻译器的限制:结构体 uniform、用户 uniform 块、预处理后仍无法确定长度的 uniform 数组、采样器数组与采样器类型不匹配的声明会被拒绝(着色器/链接日志会说明)。`#ifdef` 内的代码没问题;非采样器 uniform 声明会被移到外层顶层位置,保证块是无条件的。
|
|
59
76
|
|
|
77
|
+
片元着色器可以写 `gl_FragDepth`:glslang 会把 SPIR-V 标记为 `DepthReplacing`,SPIRV-Cross 会生成 `[[depth(any)]]`,因此两个后端上深度写入都会到达深度附件。比较采样器(`sampler2DShadow`、`samplerCubeShadow`、`sampler2DArrayShadow`)复用 SDL_GPU 采样器对象并设置 `enable_compare`;`TEXTURE_COMPARE_MODE` 与 `TEXTURE_COMPARE_FUNC` 从纹理读取(WebGL 2 也可从绑定的采样器对象读取)。
|
|
78
|
+
|
|
60
79
|
### GLSL ES 3.00(WebGL 2)
|
|
61
80
|
|
|
62
81
|
`#version 300 es` 源码除版本号提升外会被原样接受:顶层 `in`/`out` 声明会变成属性/`varying`,命名片元输出(`out vec4 fragColor;`)会变成 `xt_FragColor` 形式的输出,`texture()` / `textureLod()` 本就与 Vulkan 方言一致。多个片元输出会分配连续 location,但渲染 pass 只有一个颜色目标,因此只写入第一个。GLSL ES 3.00 提供 `gl_VertexID` 与 `gl_InstanceID`,实例化示例正是用它。
|
|
@@ -69,7 +88,7 @@ SDL_GPU 不能在运行时编译 GLSL,且每个后端只消费自己的格式
|
|
|
69
88
|
- **缓冲与顶点属性**:`createBuffer`、`bindBuffer`、`bufferData`、`bufferSubData`、`deleteBuffer`、`getBufferParameter`、`enableVertexAttribArray`、`disableVertexAttribArray`、`vertexAttribPointer`、`vertexAttrib1f`…`4f` / `1fv`…`4fv`、`getVertexAttrib`。`bufferData` 的 `usage` 提示(`STREAM_DRAW`/`STATIC_DRAW`/`DYNAMIC_DRAW`)会被记录,`getBufferParameter(…, BUFFER_USAGE)` 可读回。
|
|
70
89
|
- **绘制**:`drawArrays`、`drawElements`(UNSIGNED_BYTE、UNSIGNED_SHORT、UNSIGNED_INT;`TRIANGLE_FAN` 与 `LINE_LOOP` 会展开为列表)。`POINTS`、`LINES`、`LINE_STRIP`、`TRIANGLES`、`TRIANGLE_STRIP` 映射到对应的 SDL 图元。
|
|
71
90
|
- **纹理**:`createTexture`、`bindTexture`、`activeTexture`、`texParameteri`/`texParameterf`、`getTexParameter`、`texImage2D`(原始像素、`<img>`、`<canvas>`、`ImageData`)、`texSubImage2D`、`generateMipmap`、`deleteTexture`。支持 `UNPACK_FLIP_Y_WEBGL` 与 `UNPACK_PREMULTIPLY_ALPHA_WEBGL`。默认转换为 RGBA8;`RGBA`/`RGB`/`LUMINANCE`/`ALPHA`/`LUMINANCE_ALPHA` 配 `UNSIGNED_BYTE` 可用。浮点纹理(`RGBA32F`、`RGBA16F`、`RGB32F`、`RGB16F`、`RG32F`/`RG16F`、`R32F`/`R16F`,`FLOAT` 或 `HALF_FLOAT` 数据,以及 WebGL 1 的 `OES_texture_float`/`OES_texture_half_float`)映射到 SDL 的 RGBA32F/RGBA16F;深度纹理(`DEPTH_COMPONENT16/24/32F`、`DEPTH_STENCIL`、`DEPTH24_STENCIL8`)映射到 `D32_FLOAT`/`D32_FLOAT_S8_UINT`,可作为采样源或深度附件。压缩纹理通过 `compressedTexImage2D`/`compressedTexSubImage2D` 上传,支持 `WEBGL_compressed_texture_s3tc`(BC1/BC2/BC3)、`WEBGL_compressed_texture_s3tc_srgb` 与 `WEBGL_compressed_texture_astc`;扩展只在设备确实支持对应格式时公布。`texImage2D` 还接受 `<video>` 的最新解码帧与 `createImageBitmap` 产生的 `ImageBitmap`。立方体贴图(`bindTexture(TEXTURE_CUBE_MAP)` + 六个 `texImage2D` 面)与 3D/数组纹理(`texImage3D`/`texStorage3D`)可用对应采样器(`samplerCube`、`sampler3D`、`sampler2DArray`)采样;`texSubImage3D` 可更新 3D/数组纹理的子区域,`framebufferTextureLayer` 可把数组/立方体贴图的指定层作为颜色附件渲染。
|
|
72
|
-
- **Framebuffer**:`createFramebuffer`、`bindFramebuffer`(`FRAMEBUFFER`/`READ_FRAMEBUFFER`/`DRAW_FRAMEBUFFER`)、`framebufferTexture2D`(`COLOR_ATTACHMENT0`、`DEPTH_ATTACHMENT`、`DEPTH_STENCIL_ATTACHMENT`)、`checkFramebufferStatus`、`createRenderbuffer`、`bindRenderbuffer`、`renderbufferStorage`(深度/深度模板)、`framebufferRenderbuffer`、`delete*`,以及 `blitFramebuffer
|
|
91
|
+
- **Framebuffer**:`createFramebuffer`、`bindFramebuffer`(`FRAMEBUFFER`/`READ_FRAMEBUFFER`/`DRAW_FRAMEBUFFER`)、`framebufferTexture2D`(`COLOR_ATTACHMENT0`、`DEPTH_ATTACHMENT`、`DEPTH_STENCIL_ATTACHMENT`)、`checkFramebufferStatus`、`createRenderbuffer`、`bindRenderbuffer`、`renderbufferStorage`(深度/深度模板)、`framebufferRenderbuffer`、`delete*`,以及 `blitFramebuffer`(颜色走 SDL blit;深度走一个写 `gl_FragDepth` 的采样 pass;模板位被接受但不复制)。足以实现渲染到纹理、阴影贴图与后处理。
|
|
73
92
|
- **状态**:`enable`/`disable`/`isEnabled`(`BLEND`、`DEPTH_TEST`、`CULL_FACE`、`SCISSOR_TEST`、`STENCIL_TEST`)、`clearColor`/`clearDepth`/`clearStencil`、`clear`(开启 `SCISSOR_TEST` 时颜色、深度与模板都只清除 scissor 矩形)、`blendColor`、`blendFunc`/`blendFuncSeparate`、`blendEquation`/`blendEquationSeparate`、`depthFunc`、`depthMask`、`colorMask`、`stencilFunc`/`stencilFuncSeparate`、`stencilOp`/`stencilOpSeparate`、`stencilMask`/`stencilMaskSeparate`、`cullFace`、`frontFace`、`viewport`、`scissor`、`pixelStorei`、`finish`。模板测试真正参与光栅化,`clear(STENCIL_BUFFER_BIT)` 会清空模板附件;SDL_GPU 只有一份前后脸共用的 reference 与 compare/write mask,若前后脸设置不同,使用前脸值。
|
|
74
93
|
- **读回**:`readPixels`(RGBA/UNSIGNED_BYTE),可从默认帧缓冲或绑定的 RGBA8 framebuffer 读取;浮点/深度附件先采样到 RGBA8 目标再读回。它会阻塞到 GPU 完成;用于测试和截图,不要每帧调用。
|
|
75
94
|
|
|
@@ -91,7 +110,9 @@ WebGL 2 额外提供:
|
|
|
91
110
|
|
|
92
111
|
## 未实现
|
|
93
112
|
|
|
94
|
-
transform feedback
|
|
113
|
+
transform feedback(入口为空操作:SDL_GPU 没有 transform feedback 阶段)与 MSAA/采样缓冲(SDL_GPU 3.2 能解析多采样颜色目标,但其 Metal resolve 在 scissor 与实例化绘制下会返回未定义像素,因此上下文保持单采样,`SAMPLE_BUFFERS` 报告 0)。`blitFramebuffer` 复制颜色与深度;模板位被接受但不复制,因为 SDL_GPU 无法采样模板附件。
|
|
114
|
+
|
|
115
|
+
仍存在的着色器限制:结构体 uniform 与用户 uniform 块会被展平,但内建接口块(如重声明 `gl_PerVertex`)不在翻译子集内;使用它的着色器仍会以编译/链接日志失败,而不是静默出错。
|
|
95
116
|
|
|
96
117
|
## 平台
|
|
97
118
|
|
|
@@ -101,7 +122,8 @@ transform feedback(入口为空操作)与 MSAA/采样缓冲(SDL_GPU 的 Me
|
|
|
101
122
|
|
|
102
123
|
## 测试
|
|
103
124
|
|
|
104
|
-
- `tests/e2e/gui-webgl.test.ts` 编译 GLSL、绘制带纹理和深度测试的四边形、检查面剔除,并读回像素;它还覆盖混合常量色、动态缓冲 usage、顶点着色器纹理采样、`gl_FragDepth
|
|
125
|
+
- `tests/e2e/gui-webgl.test.ts` 编译 GLSL、绘制带纹理和深度测试的四边形、检查面剔除,并读回像素;它还覆盖混合常量色、动态缓冲 usage、顶点着色器纹理采样、`gl_FragDepth`、上下文丢失/恢复与立方体贴图。
|
|
126
|
+
- `tests/e2e/gui-webgl2-shadow-sampler.test.ts` 覆盖硬件 `sampler2DShadow` 比较、着色器写 `gl_FragDepth` 遮挡后续绘制,以及 `blitFramebuffer` 的部分深度复制。
|
|
105
127
|
- `tests/e2e/gui-webgl2.test.ts` 覆盖 WebGL 2 的着色器/状态新增能力:`#version 300 es` 着色器、VAO 往返、带 divisor 的实例化绘制、`texStorage2D`、`clearBufferfv`、缓冲拷贝、采样器对象、结构体 uniform 字段与 `drawingBufferStorage`。
|
|
106
128
|
- `tests/e2e/gui-webgl2-formats.test.ts` 覆盖模板、浮点/深度纹理、压缩纹理与 `ImageBitmap`。
|
|
107
129
|
- `tests/e2e/gui-webgl2-blit.test.ts` 覆盖带 scissor 的颜色/深度清除、framebuffer blit,以及局部 blit 之外必须保留的像素(在 Metal 上,若目标 load-op 丢弃这些像素,该测试就会失败)。
|
|
@@ -118,4 +140,4 @@ transform feedback(入口为空操作)与 MSAA/采样缓冲(SDL_GPU 的 Me
|
|
|
118
140
|
|
|
119
141
|
着色器改写器会把 three.js 的 `#pragma unroll_loop_start` 循环展开为常量索引(否则其采样器数组无法通过 Vulkan GLSL),`.js` 资源加载器则避免渲染器自带的展开器重复处理。`WebGLRenderer` 在本引擎真正的 WebGL 2 后端上原样运行,**不再打包 Canvas2D 回退**。已用 three.js r160 通过 SDL_GPU 验证:`MeshBasicMaterial`、`MeshStandardMaterial`、`MeshPhongMaterial`、`CanvasTexture`、线性雾、环境光/平行光/点光/聚光/半球光、`Sprite`、`Line`、`Points` 与 `InstancedMesh`。
|
|
120
142
|
|
|
121
|
-
本引擎子集之外的能力仍无法模拟:transform feedback 与 MSAA
|
|
143
|
+
本引擎子集之外的能力仍无法模拟:transform feedback 与 MSAA。立方体贴图/环境贴图、深度纹理、HDR/浮点渲染目标、压缩纹理与比较型 `sampler2DShadow` 采样都已可用(three.js 阴影贴图可在两个后端使用硬件深度比较)。需要 transform feedback 或 MSAA 的场景无法正确渲染;此前 Canvas2D 回退能显示的内容现在全部走 GPU。
|
package/doc/zh-CN/gui.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 语言 / Language:[English](../gui.md) | **简体中文**
|
|
4
4
|
|
|
5
|
-
状态:**
|
|
5
|
+
状态:**M20** —— 功能(M1–M15)已全部完成:HTML 解析、CSS 选择器匹配、层叠(cascade)、计算样式与布局(块级、行内、Flexbox 与 CSS 定位)均已就位,引擎还会**绘制**:它构建一个由矩形、图像与排版后的文本 run 组成的显示列表,并通过 SDL_GPU 渲染。输入事件会经过命中测试并投递给原生 TS 处理器,`:hover`/`:focus` 会被动态匹配,`<img>` 依据其固有尺寸确定大小并从纹理绘制——位图(PNG/JPEG/…)与 SVG 文件皆可,后者按绘制尺寸光栅化,因此矢量图保持清晰——CSS transition 会为绘制属性做动画。**M16** 让内联 `<svg>` 子树获得同样的处理:子树被序列化回 SVG 文档,并按绘制设备尺寸光栅化,因此图标不再需要单独的文件。**M8** 增加了交互式 DOM:具有稳定标识的元素句柄、变更操作(`appendChild`、`textContent`、`classList`、`style` 等)以及支持捕获/冒泡的元素级事件。**M9** 会提前编译 `<script>` 主体(内联与 `<script src>`)——参见 `gui-scripts.md`。**M10** 增加 `requestAnimationFrame` 以及若干 DOM 辅助方法。**M11** 增加 `position: relative`/`absolute`/`fixed` 及其偏移,`examples/gui/pelican-bike`(一个可玩的 2D 游戏)就构建在它之上。**M12** 明确了 DOM 节点生命周期(哪些变更释放、哪些保留),并让引擎内存有界:参见[节点生命周期](./gui/dom.md)与[内存与 GC](./gui/runtime.md)。**M13** 真正播放
|
|
6
6
|
`<video>`/`<audio>`——可选的 `media` 扩展用 vendored 的最小 FFmpeg 在主线程解码,
|
|
7
7
|
视频帧走图像纹理路径、音频进入 SDL 流,DOM 媒体 API 暴露
|
|
8
8
|
`play`/`pause`/`currentTime`/… 与媒体事件。只启用 `gui` 不会链接 FFmpeg。浏览器全局还带有真正可用的 `AudioContext`:`runtime/ext_gui/audio_mixer.cpp` 中的 48 kHz 混音器,以及 `decodeAudioData`(WAV 由引擎内解码,压缩格式经 `media`)——见 [GUI API](./gui/api.md#web-audio)。**M14** 增加了 `<canvas>`,配一个自研的 2D 软件光栅器(抗锯齿
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
渲染器,因此盒子(以及其中的文本、图像与子元素)可以被平移、缩放与旋转而
|
|
13
13
|
不触发任何重排——再加上 `transition: transform`,这些姿态可以动画(自绘的示例
|
|
14
14
|
`examples/gui/pelican-svg` 正是用它让矢量画动起来)。参见
|
|
15
|
-
[布局](./gui/layout.md#变换m15)与[动画](./gui/animation.md#css-transformm15)。**M17** 增加了真正的 **WebGL 1.0**,**M18** 增加了 **WebGL 2.0**:`<canvas>.getContext("webgl2")` 接受 `#version 300 es` 着色器、顶点数组对象、实例化绘制与采样器对象,全部由内置 glslang 编译为 SPIR-V(Metal 上再由内置 SPIRV-Cross 转成 MSL)并通过 SDL_GPU 渲染,几何、光照、纹理采样与混合都在 GPU 上完成,页面直接合成颜色纹理而无须读回——见 [WebGL](./gui/webgl.md)。**M19** 补齐 CSS 装饰层:`@media` 查询、生成内容(`::before`/`::after
|
|
15
|
+
[布局](./gui/layout.md#变换m15)与[动画](./gui/animation.md#css-transformm15)。**M17** 增加了真正的 **WebGL 1.0**,**M18** 增加了 **WebGL 2.0**:`<canvas>.getContext("webgl2")` 接受 `#version 300 es` 着色器、顶点数组对象、实例化绘制与采样器对象,全部由内置 glslang 编译为 SPIR-V(Metal 上再由内置 SPIRV-Cross 转成 MSL)并通过 SDL_GPU 渲染,几何、光照、纹理采样与混合都在 GPU 上完成,页面直接合成颜色纹理而无须读回——见 [WebGL](./gui/webgl.md)。**M19** 补齐 CSS 装饰层:`@media` 查询、生成内容(`::before`/`::after`)、线性/径向/重复多层背景、`text-shadow`、`clip-path`、`mix-blend-mode`(通过离屏合成器)与 `filter` 颜色/阴影效果。`backdrop-filter` 仍需要背景快照,真正的组模糊也仍是近似——见[绘制](./gui/rendering.md#背景生成内容与特效m19)。**M20** 让大文档保持可交互:`@media` 每条规则只求值一次,布局与绘制共享一个有界的整形缓存,一帧只做(也只提交)变更所要求的工作——见[性能](./gui/rendering.md#性能m20)。本总览记录了锁定决策、架构、里程碑计划与当前进度;引擎本身按功能拆分在以上各页中。
|
|
16
16
|
|
|
17
17
|
## 目录
|
|
18
18
|
|
|
@@ -273,6 +273,16 @@ FFmpeg `7.1`(供可选的 `media` 归档使用;可用 `SDL3_TAG` /
|
|
|
273
273
|
[已知问题](./gui/known-issues.md))。
|
|
274
274
|
- e2e 覆盖见 `tests/e2e/gui-effects.test.ts`;参见
|
|
275
275
|
[绘制](./gui/rendering.md#背景生成内容与特效m19)。
|
|
276
|
+
20. **M20 — 渲染性能** ✅
|
|
277
|
+
- `@media` 每次重算样式时每条规则只求值一次;没有伪元素的样式表跳过生成
|
|
278
|
+
内容;只有样式表跟踪 `:hover`/`:focus` 时才因它们重算样式。
|
|
279
|
+
- 有界的两代整形缓存同时服务布局与绘制,并缓存主字体。
|
|
280
|
+
- 帧循环:只在文档变化时对齐媒体/画布表,只在发生事情时提交呈现,并据此
|
|
281
|
+
决定是否执行整帧工作。
|
|
282
|
+
- 复用显示列表/顶点暂存/transfer buffer,一个脏帧只提交一次上传,去掉
|
|
283
|
+
冗余的渲染状态绑定。
|
|
284
|
+
- `win.stats()`/`XT_GUI_PERF` 计数器与 `tests/e2e/gui-perf.test.ts`;参见
|
|
285
|
+
[性能](./gui/rendering.md#性能m20)。
|
|
276
286
|
|
|
277
287
|
## 进度日志
|
|
278
288
|
|
|
@@ -402,4 +412,17 @@ FFmpeg `7.1`(供可选的 `media` 归档使用;可用 `SDL3_TAG` /
|
|
|
402
412
|
采样模糊/投影并生成 `text-shadow` run,`paint_clip.cpp`/`renderer_clip.cpp`
|
|
403
413
|
对三角化的 `clip-path` 多边形裁剪几何。e2e 见
|
|
404
414
|
`tests/e2e/gui-effects.test.ts`。
|
|
415
|
+
- **M20** ✅ 渲染性能。`@media` 每次重算样式时每条规则只求值一次;没有
|
|
416
|
+
`::before`/`::after` 的样式表完全跳过生成内容;只有样式表包含
|
|
417
|
+
`:hover`/`:focus` 规则时才因这两个状态重算样式(`style.cpp`、
|
|
418
|
+
`css_sheet.cpp`)。一个有界的两代整形缓存(`text_cache.cpp`)同时服务布局与
|
|
419
|
+
绘制,因此一个词只整形一次,而不是每次布局和重绘都整形一次;主字体也按
|
|
420
|
+
(类别,字号)缓存。帧循环只在文档变化时对齐解码器/画布,并且只在确实发生了
|
|
421
|
+
事情时提交呈现(`gui_events.cpp`),因此静态页面不产生任何 GPU 工作
|
|
422
|
+
(调试时可用 `XT_GUI_ALWAYS_PRESENT=1` 恢复旧行为)。显示列表、顶点暂存与
|
|
423
|
+
transfer buffer 都被复用,一个脏帧的多次上传共用一次命令缓冲区提交,渲染器
|
|
424
|
+
只在状态真正变化时重新绑定(`renderer*.cpp`、`gui_engine.h`)。
|
|
425
|
+
`win.stats()` 报告帧耗时与累计工作量计数器,`XT_GUI_PERF` 打印它们
|
|
426
|
+
(`perf.{h,cpp}`);`tests/e2e/gui-perf.test.ts` 用 200 行列表固化这些断言。
|
|
427
|
+
参见[性能](./gui/rendering.md#性能m20)。
|
|
405
428
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xbintsc",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.42",
|
|
4
4
|
"description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -72,12 +72,12 @@
|
|
|
72
72
|
"vitest": "^2.1.8"
|
|
73
73
|
},
|
|
74
74
|
"optionalDependencies": {
|
|
75
|
-
"@xbintsc/linux-x64": "0.5.
|
|
76
|
-
"@xbintsc/linux-arm64": "0.5.
|
|
77
|
-
"@xbintsc/darwin-x64": "0.5.
|
|
78
|
-
"@xbintsc/darwin-arm64": "0.5.
|
|
79
|
-
"@xbintsc/win32-x64": "0.5.
|
|
80
|
-
"@xbintsc/win32-arm64": "0.5.
|
|
75
|
+
"@xbintsc/linux-x64": "0.5.42",
|
|
76
|
+
"@xbintsc/linux-arm64": "0.5.42",
|
|
77
|
+
"@xbintsc/darwin-x64": "0.5.42",
|
|
78
|
+
"@xbintsc/darwin-arm64": "0.5.42",
|
|
79
|
+
"@xbintsc/win32-x64": "0.5.42",
|
|
80
|
+
"@xbintsc/win32-arm64": "0.5.42"
|
|
81
81
|
},
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=22"
|
|
@@ -337,6 +337,7 @@ xt_value type_proto(const std::string &name) {
|
|
|
337
337
|
if (name == "WebGLSync") return domapi::webgl_object_proto("sync");
|
|
338
338
|
if (name == "WebGLTransformFeedback") return domapi::webgl_object_proto("transformfeedback");
|
|
339
339
|
if (name == "Path2D") return domapi::path2d_proto();
|
|
340
|
+
if (name == "OffscreenCanvas") return domapi::offscreen_canvas_proto();
|
|
340
341
|
if (name == "ImageBitmap") {
|
|
341
342
|
/* A bitmap is a plain immutable object, not a DOM node: a node proto would
|
|
342
343
|
* expose the element width/height accessors and answer 0. */
|
|
@@ -383,6 +384,7 @@ xt_value host_type_construct(xt_value self, xt_value env, int32_t argc, xt_value
|
|
|
383
384
|
return domapi::event_construct(xt_arg(argc, argv, 0), xt_arg(argc, argv, 1));
|
|
384
385
|
}
|
|
385
386
|
if (name == "Path2D") return domapi::path2d_create(xt_arg(argc, argv, 0));
|
|
387
|
+
if (name == "OffscreenCanvas") return domapi::offscreen_canvas_create(argc, argv);
|
|
386
388
|
xt_value target = xt_object_new_with_proto(type_proto(name));
|
|
387
389
|
set_prop(target, "constructor", type_value(name));
|
|
388
390
|
return target;
|
|
@@ -518,7 +520,7 @@ xt_value xt_browser_type(int32_t argc, xt_value *argv) {
|
|
|
518
520
|
(void)argv;
|
|
519
521
|
if (argc < 1) return XT_UNDEFINED;
|
|
520
522
|
std::string name = cstr(xt_arg(argc, argv, 0));
|
|
521
|
-
if (name == "XRWebGLLayer" || name == "XRWebGLBinding" ||
|
|
523
|
+
if (name == "XRWebGLLayer" || name == "XRWebGLBinding" ||
|
|
522
524
|
name == "ImageData" || name == "Response" ||
|
|
523
525
|
name == "ReadableStream" || name == "Request" || name == "Headers" ||
|
|
524
526
|
name == "DOMParser") {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
#include <unordered_map>
|
|
17
17
|
|
|
18
18
|
#include "canvas_internal.h"
|
|
19
|
+
#include "perf.h"
|
|
19
20
|
|
|
20
21
|
namespace xtgui {
|
|
21
22
|
|
|
@@ -85,11 +86,16 @@ void Canvas2D::resize(int width, int height) {
|
|
|
85
86
|
image_.width = width;
|
|
86
87
|
image_.height = height;
|
|
87
88
|
image_.pixels.assign((size_t)width * (size_t)height * 4, 0);
|
|
89
|
+
/* The DOM resets the rendering context when the bitmap is resized; the clip
|
|
90
|
+
* region in particular has device-space dimensions and cannot survive. */
|
|
91
|
+
state_.clip.reset();
|
|
92
|
+
shadow_layer_.pixels.clear();
|
|
88
93
|
if (!alpha_) {
|
|
89
94
|
/* `{ alpha: false }`: the canvas has an opaque black backdrop, so cleared
|
|
90
95
|
* pixels are black rather than transparent. */
|
|
91
96
|
for (size_t i = 3; i < image_.pixels.size(); i += 4) image_.pixels[i] = 255;
|
|
92
97
|
}
|
|
98
|
+
mark_all_dirty();
|
|
93
99
|
generation_++;
|
|
94
100
|
}
|
|
95
101
|
|
|
@@ -292,6 +298,57 @@ void Canvas2D::to_device(float x, float y, float *outX, float *outY) const {
|
|
|
292
298
|
state_.transform.apply(x, y, outX, outY);
|
|
293
299
|
}
|
|
294
300
|
|
|
301
|
+
void Canvas2D::mark_dirty(float x0, float y0, float x1, float y1) {
|
|
302
|
+
if (x1 < x0) std::swap(x0, x1);
|
|
303
|
+
if (y1 < y0) std::swap(y0, y1);
|
|
304
|
+
const int ix0 = std::max(0, (int)std::floor(x0));
|
|
305
|
+
const int iy0 = std::max(0, (int)std::floor(y0));
|
|
306
|
+
const int ix1 = std::min(image_.width, (int)std::ceil(x1));
|
|
307
|
+
const int iy1 = std::min(image_.height, (int)std::ceil(y1));
|
|
308
|
+
if (ix1 <= ix0 || iy1 <= iy0) return;
|
|
309
|
+
if (dirty_.empty()) {
|
|
310
|
+
dirty_ = CanvasDirtyRect{ix0, iy0, ix1, iy1};
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
dirty_.x0 = std::min(dirty_.x0, ix0);
|
|
314
|
+
dirty_.y0 = std::min(dirty_.y0, iy0);
|
|
315
|
+
dirty_.x1 = std::max(dirty_.x1, ix1);
|
|
316
|
+
dirty_.y1 = std::max(dirty_.y1, iy1);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
void Canvas2D::mark_path_dirty(const std::vector<CanvasSubpath> &subpaths, float expand) {
|
|
320
|
+
bool have = false;
|
|
321
|
+
float minX = 0.0f, maxX = 0.0f, minY = 0.0f, maxY = 0.0f;
|
|
322
|
+
for (const CanvasSubpath &subpath : subpaths) {
|
|
323
|
+
for (const CanvasPoint &point : subpath) {
|
|
324
|
+
if (!have) {
|
|
325
|
+
minX = maxX = point.x;
|
|
326
|
+
minY = maxY = point.y;
|
|
327
|
+
have = true;
|
|
328
|
+
} else {
|
|
329
|
+
minX = std::min(minX, point.x);
|
|
330
|
+
maxX = std::max(maxX, point.x);
|
|
331
|
+
minY = std::min(minY, point.y);
|
|
332
|
+
maxY = std::max(maxY, point.y);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (!have) return;
|
|
337
|
+
mark_dirty(minX - expand, minY - expand, maxX + expand, maxY + expand);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
void Canvas2D::mark_all_dirty() {
|
|
341
|
+
dirty_ = CanvasDirtyRect{0, 0, image_.width, image_.height};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
float Canvas2D::stroke_extent() const {
|
|
345
|
+
float scale = state_.transform.scale();
|
|
346
|
+
if (!(scale > 0.0f)) scale = 1.0f;
|
|
347
|
+
float half = state_.line_width * scale * 0.5f;
|
|
348
|
+
if (!(half > 0.0f)) half = 0.5f;
|
|
349
|
+
return half * std::max(1.0f, state_.miter_limit) + 1.0f;
|
|
350
|
+
}
|
|
351
|
+
|
|
295
352
|
std::vector<CanvasSubpath> Canvas2D::device_subpaths() const {
|
|
296
353
|
return device_subpaths_of(path_);
|
|
297
354
|
}
|
|
@@ -320,6 +377,7 @@ CanvasPaint Canvas2D::make_paint(uint64_t gradient_id, uint64_t pattern_id,
|
|
|
320
377
|
paint.gradient = gradient_id == 0 ? nullptr : find_gradient(gradient_id);
|
|
321
378
|
paint.pattern = pattern_id == 0 ? nullptr : find_pattern(pattern_id);
|
|
322
379
|
paint.inverse = state_.transform.inverted();
|
|
380
|
+
paint.composite = state_.global_composite;
|
|
323
381
|
return paint;
|
|
324
382
|
}
|
|
325
383
|
|
|
@@ -332,6 +390,12 @@ CanvasPaint Canvas2D::stroke_paint() const {
|
|
|
332
390
|
}
|
|
333
391
|
|
|
334
392
|
void Canvas2D::fill(bool even_odd) {
|
|
393
|
+
if (begin_shadow_layer()) {
|
|
394
|
+
fill(even_odd);
|
|
395
|
+
end_shadow_layer();
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
g_perf_frame.canvasDraws += 1;
|
|
335
399
|
std::vector<CanvasSubpath> subpaths = device_subpaths();
|
|
336
400
|
std::vector<CanvasSubpath> loops;
|
|
337
401
|
for (CanvasSubpath &subpath : subpaths) {
|
|
@@ -339,12 +403,19 @@ void Canvas2D::fill(bool even_odd) {
|
|
|
339
403
|
loops.push_back(std::move(subpath));
|
|
340
404
|
}
|
|
341
405
|
if (loops.empty()) return;
|
|
406
|
+
mark_path_dirty(loops, 0.0f);
|
|
342
407
|
canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, fill_paint(),
|
|
343
|
-
state_.global_alpha, even_odd);
|
|
408
|
+
state_.global_alpha, even_odd, clip_mask());
|
|
344
409
|
generation_++;
|
|
345
410
|
}
|
|
346
411
|
|
|
347
412
|
void Canvas2D::fill_paths(const std::vector<CanvasSubpath> &paths, bool even_odd) {
|
|
413
|
+
if (begin_shadow_layer()) {
|
|
414
|
+
fill_paths(paths, even_odd);
|
|
415
|
+
end_shadow_layer();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
g_perf_frame.canvasDraws += 1;
|
|
348
419
|
std::vector<CanvasSubpath> subpaths = device_subpaths_of(paths);
|
|
349
420
|
std::vector<CanvasSubpath> loops;
|
|
350
421
|
for (CanvasSubpath &subpath : subpaths) {
|
|
@@ -352,13 +423,21 @@ void Canvas2D::fill_paths(const std::vector<CanvasSubpath> &paths, bool even_odd
|
|
|
352
423
|
loops.push_back(std::move(subpath));
|
|
353
424
|
}
|
|
354
425
|
if (loops.empty()) return;
|
|
426
|
+
mark_path_dirty(loops, 0.0f);
|
|
355
427
|
canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, fill_paint(),
|
|
356
|
-
state_.global_alpha, even_odd);
|
|
428
|
+
state_.global_alpha, even_odd, clip_mask());
|
|
357
429
|
generation_++;
|
|
358
430
|
}
|
|
359
431
|
|
|
360
432
|
void Canvas2D::stroke() {
|
|
433
|
+
if (begin_shadow_layer()) {
|
|
434
|
+
stroke();
|
|
435
|
+
end_shadow_layer();
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
g_perf_frame.canvasDraws += 1;
|
|
361
439
|
std::vector<CanvasSubpath> subpaths = device_subpaths();
|
|
440
|
+
mark_path_dirty(subpaths, stroke_extent());
|
|
362
441
|
const CanvasPaint paint = stroke_paint();
|
|
363
442
|
for (const CanvasSubpath &subpath : subpaths) {
|
|
364
443
|
if (subpath.size() < 2) continue;
|
|
@@ -370,7 +449,14 @@ void Canvas2D::stroke() {
|
|
|
370
449
|
}
|
|
371
450
|
|
|
372
451
|
void Canvas2D::stroke_paths(const std::vector<CanvasSubpath> &paths) {
|
|
452
|
+
if (begin_shadow_layer()) {
|
|
453
|
+
stroke_paths(paths);
|
|
454
|
+
end_shadow_layer();
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
g_perf_frame.canvasDraws += 1;
|
|
373
458
|
std::vector<CanvasSubpath> subpaths = device_subpaths_of(paths);
|
|
459
|
+
mark_path_dirty(subpaths, stroke_extent());
|
|
374
460
|
const CanvasPaint paint = stroke_paint();
|
|
375
461
|
for (const CanvasSubpath &subpath : subpaths) {
|
|
376
462
|
if (subpath.size() < 2) continue;
|
|
@@ -382,20 +468,30 @@ void Canvas2D::stroke_paths(const std::vector<CanvasSubpath> &paths) {
|
|
|
382
468
|
}
|
|
383
469
|
|
|
384
470
|
void Canvas2D::clear_rect(float x, float y, float width, float height) {
|
|
471
|
+
g_perf_frame.canvasDraws += 1;
|
|
385
472
|
float x0, y0, x1, y1;
|
|
386
473
|
to_device(x, y, &x0, &y0);
|
|
387
474
|
to_device(x + width, y + height, &x1, &y1);
|
|
388
|
-
|
|
475
|
+
mark_dirty(x0, y0, x1, y1);
|
|
476
|
+
canvas_detail::clear_rect(image_.pixels, image_.width, image_.height, x0, y0, x1, y1,
|
|
477
|
+
clip_mask());
|
|
389
478
|
generation_++;
|
|
390
479
|
}
|
|
391
480
|
|
|
392
481
|
void Canvas2D::fill_rect(float x, float y, float width, float height) {
|
|
482
|
+
if (begin_shadow_layer()) {
|
|
483
|
+
fill_rect(x, y, width, height);
|
|
484
|
+
end_shadow_layer();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
g_perf_frame.canvasDraws += 1;
|
|
393
488
|
if (state_.transform.axis_aligned()) {
|
|
394
489
|
float x0, y0, x1, y1;
|
|
395
490
|
to_device(x, y, &x0, &y0);
|
|
396
491
|
to_device(x + width, y + height, &x1, &y1);
|
|
492
|
+
mark_dirty(x0, y0, x1, y1);
|
|
397
493
|
canvas_detail::fill_rect(image_.pixels, image_.width, image_.height, x0, y0, x1, y1,
|
|
398
|
-
fill_paint(), state_.global_alpha);
|
|
494
|
+
fill_paint(), state_.global_alpha, clip_mask());
|
|
399
495
|
} else {
|
|
400
496
|
CanvasSubpath loop;
|
|
401
497
|
const float corners[4][2] = {
|
|
@@ -405,13 +501,20 @@ void Canvas2D::fill_rect(float x, float y, float width, float height) {
|
|
|
405
501
|
to_device(corner[0], corner[1], &point.x, &point.y);
|
|
406
502
|
loop.push_back(point);
|
|
407
503
|
}
|
|
504
|
+
mark_path_dirty(std::vector<CanvasSubpath>{loop}, 0.0f);
|
|
408
505
|
canvas_detail::fill_loop(image_.pixels, image_.width, image_.height, loop, fill_paint(),
|
|
409
|
-
state_.global_alpha);
|
|
506
|
+
state_.global_alpha, false, clip_mask());
|
|
410
507
|
}
|
|
411
508
|
generation_++;
|
|
412
509
|
}
|
|
413
510
|
|
|
414
511
|
void Canvas2D::stroke_rect(float x, float y, float width, float height) {
|
|
512
|
+
if (begin_shadow_layer()) {
|
|
513
|
+
stroke_rect(x, y, width, height);
|
|
514
|
+
end_shadow_layer();
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
g_perf_frame.canvasDraws += 1;
|
|
415
518
|
CanvasSubpath loop;
|
|
416
519
|
const float corners[5][2] = {
|
|
417
520
|
{x, y}, {x + width, y}, {x + width, y + height}, {x, y + height}, {x, y}};
|
|
@@ -421,103 +524,46 @@ void Canvas2D::stroke_rect(float x, float y, float width, float height) {
|
|
|
421
524
|
loop.push_back(point);
|
|
422
525
|
}
|
|
423
526
|
stroke_subpath(loop, true, stroke_paint());
|
|
527
|
+
mark_path_dirty(std::vector<CanvasSubpath>{loop}, stroke_extent());
|
|
424
528
|
generation_++;
|
|
425
529
|
}
|
|
426
530
|
|
|
427
|
-
float Canvas2D::measure_text(const std::string &text) const {
|
|
428
|
-
return xt_text_measure_width(text, state_.font);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
void Canvas2D::paint_text(const std::string &text, float x, float y, const CanvasPaint &paint,
|
|
432
|
-
bool stroke, float max_width) {
|
|
433
|
-
Font *font = xt_text_resolve(state_.font);
|
|
434
|
-
if (font == nullptr) return;
|
|
435
|
-
std::vector<ShapedGlyph> glyphs;
|
|
436
|
-
float width = xt_text_shape_run(text, font, glyphs);
|
|
437
|
-
float ascent = 0.0f, descent = 0.0f, lineHeight = 0.0f;
|
|
438
|
-
xt_text_metrics(state_.font, &ascent, &descent, &lineHeight);
|
|
439
|
-
|
|
440
|
-
/* The optional `maxWidth` condenses the run horizontally instead of clipping
|
|
441
|
-
* it: advances and glyph bitmaps are squeezed by the same factor. */
|
|
442
|
-
float horizontal = 1.0f;
|
|
443
|
-
if (max_width > 0.0f && width > max_width && width > 0.0001f) {
|
|
444
|
-
horizontal = max_width / width;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
float originX = x;
|
|
448
|
-
if (state_.text_align == 1) {
|
|
449
|
-
originX -= width * horizontal * 0.5f;
|
|
450
|
-
} else if (state_.text_align == 2) {
|
|
451
|
-
originX -= width * horizontal;
|
|
452
|
-
}
|
|
453
|
-
float originY = y;
|
|
454
|
-
switch (state_.text_baseline) {
|
|
455
|
-
case 1:
|
|
456
|
-
originY += ascent;
|
|
457
|
-
break;
|
|
458
|
-
case 2:
|
|
459
|
-
originY += (ascent - descent) * 0.5f;
|
|
460
|
-
break;
|
|
461
|
-
case 3:
|
|
462
|
-
originY -= descent;
|
|
463
|
-
break;
|
|
464
|
-
case 4:
|
|
465
|
-
originY += ascent * 0.8f;
|
|
466
|
-
break;
|
|
467
|
-
case 5:
|
|
468
|
-
originY -= descent;
|
|
469
|
-
break;
|
|
470
|
-
default:
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
float scale = state_.transform.scale();
|
|
475
|
-
if (scale <= 0.0001f) scale = 1.0f;
|
|
476
|
-
bool scaled = std::fabs(scale - 1.0f) > 0.01f;
|
|
477
|
-
|
|
478
|
-
float penX = 0.0f;
|
|
479
|
-
for (const ShapedGlyph &glyph : glyphs) {
|
|
480
|
-
float userX = originX + (penX + glyph.x_offset) * horizontal;
|
|
481
|
-
float userY = originY + glyph.y_offset;
|
|
482
|
-
float deviceX = 0.0f, deviceY = 0.0f;
|
|
483
|
-
to_device(userX, userY, &deviceX, &deviceY);
|
|
484
|
-
Font *face = glyph.font != nullptr ? glyph.font : font;
|
|
485
|
-
if (scaled) {
|
|
486
|
-
Font *sized = xt_text_scale(face, state_.font.pixel_size * scale);
|
|
487
|
-
if (sized != nullptr) face = sized;
|
|
488
|
-
}
|
|
489
|
-
GlyphImage image;
|
|
490
|
-
if (xt_text_rasterize(face, glyph.glyph, image) && image.width > 0 && image.height > 0) {
|
|
491
|
-
canvas_detail::blit_glyph(image_.pixels, image_.width, image_.height, image.pixels, image.width,
|
|
492
|
-
image.height, image.left, image.top, deviceX, deviceY, horizontal,
|
|
493
|
-
paint, state_.global_alpha);
|
|
494
|
-
}
|
|
495
|
-
penX += glyph.x_advance;
|
|
496
|
-
}
|
|
497
|
-
(void)stroke;
|
|
498
|
-
generation_++;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
void Canvas2D::fill_text(const std::string &text, float x, float y, float max_width) {
|
|
502
|
-
paint_text(text, x, y, fill_paint(), false, max_width);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
void Canvas2D::stroke_text(const std::string &text, float x, float y, float max_width) {
|
|
506
|
-
paint_text(text, x, y, stroke_paint(), true, max_width);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
531
|
void Canvas2D::draw_image(const Image &source, float dx, float dy, float dw, float dh, float sx,
|
|
510
532
|
float sy, float sw, float sh) {
|
|
533
|
+
if (begin_shadow_layer()) {
|
|
534
|
+
draw_image(source, dx, dy, dw, dh, sx, sy, sw, sh);
|
|
535
|
+
end_shadow_layer();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
g_perf_frame.canvasDraws += 1;
|
|
511
539
|
if (state_.transform.axis_aligned()) {
|
|
512
540
|
float x0, y0, x1, y1;
|
|
513
541
|
to_device(dx, dy, &x0, &y0);
|
|
514
542
|
to_device(dx + dw, dy + dh, &x1, &y1);
|
|
543
|
+
mark_dirty(x0, y0, x1, y1);
|
|
515
544
|
canvas_detail::draw_image(image_.pixels, image_.width, image_.height, source, x0, y0, x1 - x0,
|
|
516
|
-
y1 - y0, sx, sy, sw, sh, state_.global_alpha
|
|
545
|
+
y1 - y0, sx, sy, sw, sh, state_.global_alpha,
|
|
546
|
+
state_.image_smoothing, clip_mask());
|
|
517
547
|
} else {
|
|
548
|
+
float minX = 0.0f, minY = 0.0f, maxX = 0.0f, maxY = 0.0f;
|
|
549
|
+
const float corners[4][2] = {{dx, dy}, {dx + dw, dy}, {dx + dw, dy + dh}, {dx, dy + dh}};
|
|
550
|
+
for (int i = 0; i < 4; i++) {
|
|
551
|
+
float px = 0.0f, py = 0.0f;
|
|
552
|
+
to_device(corners[i][0], corners[i][1], &px, &py);
|
|
553
|
+
if (i == 0) {
|
|
554
|
+
minX = maxX = px;
|
|
555
|
+
minY = maxY = py;
|
|
556
|
+
} else {
|
|
557
|
+
minX = std::min(minX, px);
|
|
558
|
+
maxX = std::max(maxX, px);
|
|
559
|
+
minY = std::min(minY, py);
|
|
560
|
+
maxY = std::max(maxY, py);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
mark_dirty(minX, minY, maxX, maxY);
|
|
518
564
|
canvas_detail::draw_image_affine(image_.pixels, image_.width, image_.height, source, dx, dy,
|
|
519
565
|
dw, dh, sx, sy, sw, sh, state_.transform,
|
|
520
|
-
state_.global_alpha);
|
|
566
|
+
state_.global_alpha, state_.image_smoothing, clip_mask());
|
|
521
567
|
}
|
|
522
568
|
generation_++;
|
|
523
569
|
}
|