
作者开源的项目PixUI是一个跨端的UI库,底层依赖Skia进行像素级界面绘制。之前使用的Skia版本较旧一直没有更新,这次准备更新至较新的版本,在更新过程中发现Skia的Graphite支持已经可以使用了,所以这次准备先在Web端尝试使用WebGPU来驱动界面绘制。
一、Ganesh vs Graphite
简单来说,Ganesh 是 Skia 传统的、基于状态机的渲染引擎,而 Graphite 是 Skia 下一代的、为现代图形 API(如 Vulkan, Metal,
Dawn)重新设计的渲染引擎。以下是它们在架构、设计理念、目标 API 以及性能特性方面的详细对比:
1. 核心设计哲学
Ganesh:
- 设计思想 :基于"状态机"(State Machine)的概念,深受 OpenGL 的影响。在 Ganesh 中,通过不断改变全局状态(如 glBindTexture,
glUseProgram)来驱动渲染。 - 工作方式:它高度依赖于对当前渲染状态的维护和跟踪(State Tracking)。当要画一个图形时,引擎会检查当前状态与目标状态是否一致,如果不一致,则发出状态切换命令。
Graphite:
- 设计思想:基于"显式状态/描述符"(Explicit State/Descriptors)的概念,深受现代图形 API(Vulkan, Metal, WebGPU)的影响。
- 工作方式 :不再强调"更改状态",而是强调"提供描述符"。它将渲染逻辑与状态管理解耦,通过构造完整的渲染指令(Render
Pass/Command
Buffer)来驱动硬件,更加符合现代 GPU 的工作模式。
2. 对图形 API 的支持
Ganesh:
- 首选/灵魂 API :OpenGL。虽然它也可以通过抽象层支持 Vulkan 或 Metal,但其内部逻辑(如状态跟踪机制)是为 OpenGL 这种隐含状态驱动的
API 量身定做的。 - 痛点 :在面对 Vulkan 或 Metal 等需要显式管理资源(如 Pipeline State Objects, PSOs)的 API
时,Ganesh的性能开销往往来自于繁重的状态同步和兼容层转换。
Graphite:
- 首选 API:Vulkan, Metal, Dawn (WebGPU)。
- 优势:Graphite 从底层设计就是为了这些"现代 API"而生的。它原生理解什么是 Pipeline State、什么是 Render Pass、什么是
Descriptor Set。这使得它在这些 API 上能发挥出极高的效率,减少了 CPU 端转换开销。
3. 状态管理与性能
| 特性 | Ganesh | Graphite |
|---|---|---|
| 状态管理方式 | 隐式/跟踪式。引擎需要记住当前是什么状态,并在每次绘制前比对。 | 显式/对象式。状态被封装在不可变的对象(如 Pipeline)中,直接提交。 |
| CPU 开销 | 由于需要进行大量的状态比对(State Tracking),在复杂的场景下 CPU 开销较大。 | 通过预先构建 Pipeline 等对象,减少了运行时对状态的判断和维护开销。 |
| 多线程支持 | 较弱。由于全局状态或上下文绑定的限制,很难在多个线程中并行录制渲染命令。 | 极强。Graphite 的设计目标之一就是支持多线程录制(Multi-threaded Command Recording),这在现代大型应用中至关重要。 |
4. 架构层级对比
Ganesh 的流程:
SkCanvas → Ganesh Backend (进行繁重的状态跟踪/同步) → Graphics API (如 OpenGL)。
Graphite 的流程:
SkCanvas → Graphite Recording (生成独立的、线程安全的命令序列) → Graphite Execution (将命令转化为现代 API 的资源对象) →
Graphics API (如 Vulkan/Metal)。
二、踩坑记录
1. SkSL转换WGSL非法函数名错误问题
Skia引擎在转换SkSL语言至WebGPU的WGSL时报错非法函数名,发现转换后的函数名如 fn 1DBlurXXX()
是WGSL不支持的,暂手动修改Skia源文件: src/core/SkKnownRuntimeEffects.h如下所示数字打头的名称加任一字符使函数名有效。
c++
#define SK_ALL_STABLEKEYS(M, M1, M2) \
M2(Invalid, Start) \
M1(F1DBlurBase) \
M2(F1DBlur4, F1DBlurBase) \
M(F1DBlur8) \
M(F1DBlur12) \
M(F1DBlur16) \
M(F1DBlur20) \
M(F1DBlur28) \
M1(F2DBlurBase) \
M2(F2DBlur4, F2DBlurBase) \
M(F2DBlur8) \
M(F2DBlur12) \
M(F2DBlur16) \
M(F2DBlur20) \
M(F2DBlur28) \
2. 画界面卡顿问题
刚开始画界面出现严重卡顿,如下火焰图所示,绘制一帧要几十毫秒时间。光盯着mono_get_frame_info
,一直以为是Skia底层抛出了什么异常,浪费了很多调试时间。最后发现是托管层在包装Skia对象句柄时调用了Enviroment.StackTrace
,去掉即正常了。

三、如何体验
1. clone项目
shell
git clone https://github.com/enjoycode/PixUI.git
git submodule update --init --recursive
2. 修改项目文件 PixUI.Drawing.Skia.csproj和PixUI.Platform.Blazor.csproj
xml
<SkiaUseGraphite>True</SkiaUseGraphite>
如果是False使用原来的Ganesh引擎
3. 启动PixUI.Platform.Blazor项目即可在浏览器内打开Demo
四、本篇小结
本次更新还未充分利用Graphite的多线程能力,有待后续进一步完善。作者个人能力实在有限Bug在所难免,如有问题请邮件联系或Github Issue,欢迎感兴趣的小伙伴们加入共同完善,当然更欢迎赞助项目或给作者介绍工作(目前找工作中)。