介于 NanoVG 和 Skia 之间:C++17 可嵌入式 2D 渲染库 WhatsCanvas 全面介绍
原创整理 | 关键词:WhatsCanvas、C++17、2D 渲染、跨平台、Canvas、NanoVG、Skia
前言
做原生应用的童鞋应该都遇到过这样的纠结:
- 用 NanoVG 吧,API 简单、几十行就能画东西,但多语言文本、滤镜、图层这些能力基本得自己补;
- 用 Skia 吧,功能强大到什么都有,但体积大、构建复杂、学习成本高,很多项目根本用不到它 90% 的能力;
- 用 Qt/SDL 之类的框架自带的绘制能力吧,又被框架绑死了,想单独抽出来做离屏渲染、做服务端图片生成都不方便。
WhatsCanvas 就是冲着这个空档去的:一个用 C++17 编写的、面向原生应用的可嵌入式 2D 渲染库。它提供类似 HTML Canvas 的 Canvas / Paint / Path API,把多语言文本、图层滤镜、图片渲染、离屏渲染、像素回读这些"中间地带"的能力补齐了,同时不接管你的窗口、布局、事件循环,也不内置任何控件。
一句话定位:比 NanoVG 多得多,比 Skia 轻得多,源码可读、可验证、可裁剪。
- GitHub:https://github.com/ClarkWain/WhatsCanvas
- 在线文档:https://clarkwain.github.io/WhatsCanvas/
- 当前版本:0.2.0(pre-1.0)
- 许可证:MIT

目录
- [WhatsCanvas 是什么](#WhatsCanvas 是什么)
- [60 秒画出第一帧](#60 秒画出第一帧)
- 获取与集成:三种姿势
- [多后端架构:一个 API,五种后端](#多后端架构:一个 API,五种后端)
- 能力全景
- 文本渲染:这是重头戏
- 性能数据(附审计方法)
- 工程质量:不只是"能画像素"
- 选型建议:什么时候用它,什么时候别用
- 总结
一、WhatsCanvas 是什么
WhatsCanvas 是一个可嵌入式的 2D 渲染库,"可嵌入式"意味着它是你应用里的一个库,而不是一个框架:
- 公开 API 很小,就 16 个头文件(约 74 KiB),入口是
#include <wsc/wsc.h>; - 后端按 target 分离链接,你只用 CPU 渲染就只链
WhatsCanvas::Software,GPU 渲染链WhatsCanvas::OpenGL; - 它不管你的窗口创建、事件循环、控件、布局和无障碍,这些交给宿主应用;
- 它也不是 HTML Canvas 的源码兼容实现,只是 API 风格相似。
| 你关心的事项 | 当前答案 |
|---|---|
| 适用场景 | 原生应用自定义 UI、工具/数据界面、HUD、2D 游戏渲染层、服务端离屏出图、测试环境 |
| 语言 | C++17 |
| 渲染后端 | Software(纯 CPU)、OpenGL 3.3 Core、OpenGL ES 3.0(可选)、Vulkan(可选)、Metal(macOS/iOS) |
| 平台 | Windows / Linux / macOS 持续 CI;发布包覆盖 win64、linux x64、macOS universal |
| 文本能力 | 字体发现与 fallback、CJK/RTL、UAX #9 双向文本、换行省略、glyph atlas、COLR/CPAL v0 |
| 接入方式 | find_package、add_subdirectory、源码生成本地安装目录 |
| 许可证 | MIT |
需要完整 UI 框架的可以看它的下游项目 WhatsUI------基于 WhatsCanvas 的 C++17 保留模式 UI 库,带 Fluent 2 设计系统、控件、布局和输入处理。
二、60 秒画出第一帧
Software 后端不需要窗口、不需要 GL 上下文、不需要 GPU,是验证 API 最快的方式。整个例子:
cpp
#include <wsc/wsc.h>
int main()
{
auto canvas = wsc::Canvas::create(wsc::Canvas::Backend::Software, 256, 256);
if (!canvas || !canvas->initializeContext()) {
return 1;
}
canvas->beginFrame();
wsc::Paint fill;
fill.setColor(wsc::Color(40, 120, 240, 255));
fill.setAntiAlias(true);
canvas->drawRoundRect(wsc::RectF(40, 40, 176, 176), 24.0f, fill);
canvas->endFrame();
return canvas->savePixelsPPM("first.ppm") ? 0 : 1;
}
画一个蓝色圆角矩形,savePixelsPPM 直接落盘一张 PPM。程序在当前目录生成 first.ppm,用图片查看器打开或转成 PNG 就行。
这段代码不是随手写的,它以独立 CMake 项目的形式收在仓库的 examples/hello_world/ 里,开 demo 构建时会跟着主工程一起编译------README 里贴的代码保证能跑,这是很多库做不到的。
三、获取与集成:三种姿势
1. 预编译包
GitHub Releases 提供 whatscanvas-<platform>-release-<version>.zip(如 whatscanvas-win64-release-0.2.0.zip),目录结构:
text
include/wsc/ 公开头文件
lib/ 渲染库
bin/ shared 构建的运行时库
lib/cmake/WhatsCanvas/ find_package 配置
三个平台的包配置不完全一样,选型前注意:
| 发布资产 | 交付形式和 target | 字体/Vulkan 配置 |
|---|---|---|
| Windows x64 | shared;OpenGL / OpenGL ES / Software | FreeType、HarfBuzz 开启;Vulkan 选项开启 |
| Linux x64 | static;OpenGL / Software | FreeType、HarfBuzz 开启;Vulkan 关闭 |
| macOS universal | static;OpenGL / Software | FreeType、HarfBuzz 开启;Vulkan 关闭 |
2. CMake find_package
cmake
cmake_minimum_required(VERSION 3.16)
project(MyApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
find_package(WhatsCanvas 0.2.0 CONFIG REQUIRED)
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE WhatsCanvas::Software)
3. 源码 add_subdirectory / 从源码构建
cmake
set(WHATSCANVAS_BUILD_OPENGL ON CACHE BOOL "")
set(WHATSCANVAS_BUILD_SOFTWARE ON CACHE BOOL "")
set(WHATSCANVAS_BUILD_DEMO OFF CACHE BOOL "")
add_subdirectory(third_party/WhatsCanvas)
target_link_libraries(MyApp PRIVATE WhatsCanvas::OpenGL)
源码构建要求 CMake 3.16+、C++17 编译器、完整 Git submodule。仓库根目录提供了 build.bat(Windows/VS 2022)和 build.sh(macOS/Linux)一键脚本,加 --package 参数还能生成适配 find_package 的 Release 目录。
小提醒 :Windows 官方包是 shared 构建,运行前把包内 bin 加进 PATH,或者把 DLL 复制到 exe 旁边。
四、多后端架构:一个 API,五种后端
这是 WhatsCanvas 设计上比较有意思的地方:统一 API + 多后端 + 运行时 fallback。
| 后端 | CMake target | 默认状态 | 特点 |
|---|---|---|---|
| Software | WhatsCanvas::Software |
开启 | 纯 CPU 确定性参考实现,适合 headless、测试、截图、兜底 |
| OpenGL 3.3 Core | WhatsCanvas::OpenGL |
开启 | 桌面主 GL 路径,应用自己建上下文 |
| OpenGL ES 3.0 | WhatsCanvas::OpenGLES |
关闭 | 独立 target,移动平台用 |
| Vulkan | 编入 ::OpenGL |
关闭 | -DWHATSCANVAS_ENABLE_VULKAN=ON 启用,Win32 支持窗口呈现 |
| Metal | Apple 平台编入 ::OpenGL |
Apple 默认开启 | 离屏 + MTLTexture 互操作 + CAMetalLayer 呈现 |
所有后端都通过 Canvas::create(Backend, width, height) 创建,失败返回 nullptr,所以写 fallback 链特别自然:
cpp
using Backend = wsc::Canvas::Backend;
auto canvas = wsc::Canvas::create(
{Backend::Vulkan, Backend::Metal, Backend::OpenGL, Backend::Software}, width, height);
if (!canvas) {
return 1; // 所有后端都失败,优雅退出
}
一个 Vulkan、一个 Metal、一个 OpenGL、一个 Software,按优先级传进去,哪个能起来用哪个。对要"尽量用上 GPU、但不许因为没 GPU 而崩"的应用来说,这个模式很实用。
架构上它分了七层:公开 API → 命令记录(display list)→ 后端无关的图形模型 → 文本层 → 渲染抽象(IRenderer)→ 后端实现 → 平台层。文本走 ITextBackend 抽象,字体发现和 shaping 可插拔,不是写死在 canvas 核心里的。仓库 doc/architecture/ 下有完整的 ADR 记录,想抠源码的同学可以顺着读。
五、能力全景
| 领域 | 主要能力 | 代表 API |
|---|---|---|
| 几何与路径 | 点/线/矩形/圆角矩形/圆/椭圆/圆弧/曲线路径、fill/stroke 命中测试、虚线、路径效果 | drawPath、hitTestPathFill |
| Paint | 填充/描边、解析式抗锯齿、线性/径向多 stop 渐变、14 种混合模式、真高斯阴影、颜色矩阵 | Paint、setBlendMode、setShadowLayer |
| Canvas 状态 | save/restore、矩阵变换、矩形/抗锯齿路径裁剪、离屏层、quick reject | clipPath、saveLayer |
| 图片 | PNG/JPEG 解码、raw RGBA、外部纹理、九宫格、圆角/圆形裁剪、contain/cover、平铺 | Image、drawImageFit |
| 图层滤镜 | 内容/背景模糊、内阴影、毛玻璃、饱和度/亮度/对比度/颗粒、颜色矩阵、滤镜链 | ImageFilter、ImageFilterChain |
| 文字 | 系统字体、fallback、CJK/RTL、换行/省略号、描边/阴影/渐变文本、text-on-path | FontManager、drawTextBox、drawTextOnPath |
| 输出与互操作 | 离屏图片、GL framebuffer、外部 Vulkan image/Metal texture、同步/异步 RGBA 回读 | OutputTarget、readPixelsRGBAAsync |
| 诊断 | 像素 hash、PPM 导出、render stats、资源与 atlas 统计 | computePixelsHashRGBA、RenderStats |
几个值得单独说的点:
- 毛玻璃/内阴影/滤镜链 :
ImageFilterChain支持 blur → 颜色矩阵 → offset 这种链式组合,做现代 UI 效果(半透明面板、backdrop blur)不用自己写 shader; - 真高斯阴影 :不是"画个模糊的矩形"那种假阴影,是
setShadowLayer走的真高斯模型,文档里有完整的 shadow model 说明; - 外部纹理互操作 :
wrapExternalTexture可以把你自己的 GL texture / Vulkan image / Metal texture 包进来画,方便和已有渲染管线共存; - 异步像素回读 :
readPixelsRGBAAsync适合做截屏、离屏出图这类场景,不阻塞渲染线程。
仓库里的效果图(滤镜展示、文本展示)都是 OpenGL 后端直接从 framebuffer 回读的 1920×1080 实图,不是设计稿截图。
六、文本渲染:这是重头戏

NanoVG 这类库的文本基本是"英文够用",而 WhatsCanvas 的文本管线是按生产级做的:
- UTF-8 布局、UAX #29 扩展字素簇、按簇安全换行;
- CJK 无空格换行、换行与省略号;
- UAX #9 双向文本 (Unicode 17.0.0)------RTL 语言(阿拉伯语、希伯来语等)方向与顺序处理;已归档的一致性测试跑了 861,948 例,全部通过,0 跳过 0 失败;
- 字体发现与 fallback:Windows DirectWrite(可选 adapter)、macOS/iOS CoreText、Linux fontconfig 三平台系统字体枚举,带缓存刷新 API;
- 阿拉伯文、印度语系这类需要复杂字形替换/重排的文字,走 HarfBuzz OpenType shaping(GL 家族 target 默认开启);
- 栅格化默认走 FreeType (glyph 查找、metrics、kerning、栅格化),不可用时回退
stb_truetype; - glyph atlas 构建、COLR/CPAL v0 彩色字体支持(CBDT/CBLC、SBIX、SVG、COLR v1 在路线图上)。
两个默认开关(构建时):
WHATSCANVAS_ENABLE_FREETYPE_RASTERIZER=ON(默认开)WHATSCANVAS_ENABLE_OPENTYPE_SHAPING=ON(默认开)
注意一个坑 :关掉 shaping 或缺依赖时,排版会退化到 simple shaping + kerning,阿拉伯语这类文字会出问题,不能当成等价替代 。发布前用你自己的字体和文案跑一遍像素回归,仓库提供了 scripts/text_pixel_regression.* 脚本。
七、性能数据(附审计方法)
仓库里归档了一组画质对齐 的基准矩阵:Windows、Core i7-8700、GTX 1060、1920×1080、Release、OpenGL 3.3,对比对象是 NanoVG GL3(commit 固定为 ce3bf74):
| 场景 | 矩阵范围 | 归档结果 |
|---|---|---|
| 抗锯齿几何 | 256--4,096 个图形,静态/动态数据/动态结构 | 8 项领先、1 项持平;最大帧时间下降 26.7% |
| 图片 | 64--1,024 张,最多 32 纹理,圆角与状态变化 | 9/9 领先;最大帧时间下降 58.5% |
| 动态文字 | 64--1,024 次绘制,文本/字号/状态变化 | 9/9 领先;最大帧时间下降 32.0% |
总计 26 项领先、0 项落后、1 项持平,27 项像素质量验证全部通过。
我比较欣赏它审计的严谨度:每进程预热 5 帧、测量 30 帧,每个 cell 用 2 个 ABBA block 抵消漂移,每端 4 个新进程,10,000 次 bootstrap 出置信区间;逐帧 JSONL 明细、像素残差全部留在仓库里可复现。同时它明确写了"别外推"------这些数据只代表这台机器、这套驱动、这个后端,你在自己的 GPU 和工作负载上要复测。这种"敢给数据但不吹"的姿态,在库的介绍文章里不多见。
另外 Apple 平台也有数据:M3 Pro 上 256×256、128 矩形 × 200 帧的场景,Metal 1.66 ms/帧 vs OpenGL 2.45 ms/帧。
八、工程质量:不只是"能画像素"
选 pre-1.0 的库,工程验证体系比功能表更重要。WhatsCanvas 仓库里这一套是完整的:
- 三平台 CI:Windows(MSVC)、Linux(GCC + Mesa/Xvfb)、macOS 持续跑构建和单元测试;OpenGL ES、Vulkan、Metal 各有独立的构建/像素门禁;
- 像素回归:Software 后端 golden image 基线(确定性参考),OpenGL/GLES/Vulkan/Metal 滤镜结果跨后端对齐,严格 hash 回归 + 模糊 PPM 回归两档;
- 集成验证:package consumer 测试(模拟真实下游用发布包构建)、示例构建检查、API reference 文档时效性检查、版本一致性检查;
- 诊断设施:像素 hash、PPM 导出、render stats、资源/atlas 统计、确定性首帧时序;
- 文档:API Reference、API Stability(明确 0.x 阶段哪些承诺、哪些不承诺)、Feature Matrix(滤镜/文本/输出目标各自的支持矩阵)、Troubleshooting。
已知风险它自己也写清楚了,这里替大家划个重点:
- 版本仍是
0.2.x,升级前读 CHANGELOG + 跑 package consumer 测试; - Vulkan 是 opt-in,跨平台窗口呈现还在完善中;
- iOS 真机/模拟器 CI 和仓库内 iOS 示例还没补齐,移动端集成前要自己验证;
Canvas单实例不承诺并发访问,跨线程共享图片/字体/外部纹理没有公开约定;- GPU 实时渲染受驱动影响,确定性基线请用 Software,GPU 回归用容差比较。
九、选型建议:什么时候用它,什么时候别用
推荐用它:
- 原生应用里要自定义 UI、工具/数据界面、HUD、2D 游戏渲染层;
- 服务端/测试环境需要 CPU 离屏出图(Software 后端无 GPU 依赖);
- 需要多语言文本(CJK、RTL、复杂脚本 shaping)+ 常见 UI 效果(毛玻璃、阴影、渐变)的组合;
- 你看重截图确定性、像素级回归、源码可读性,愿意自己管窗口和事件。
建议另寻方案:
- 强依赖现成 UI 控件体系 → 直接用 WhatsUI、Qt、WinUI 之类;
- 要在浏览器里跑 → 等它的 WebAssembly/WebGL 2 / WebGPU 计划(尚未实现);
- 需要严格色彩管理、PDF/文档生成、复杂富文本编辑 → 不在当前支持范围;
- 需要一个 ABI 长期稳定的 1.0+ 成熟库 → 它还是 pre-1.0,按官方建议评估升级风险。
十、总结
WhatsCanvas 的价值不在于"又造了一个画 2D 的轮子",而在于它把 NanoVG 和 Skia 之间那段没人好好做的中间地带填上了:
- 统一的
Canvas/Paint/PathAPI,一个 API 跑五个后端,fallback 链一行代码; - 生产级文本管线(CJK/RTL/shaping/字体发现),86 万例 UAX #9 一致性测试全过;
- 滤镜、阴影、混合模式这些现代 UI 常用效果开箱即用;
- 完整的工程验证体系:三平台 CI、跨后端像素对齐、可审计的 benchmark 原始数据;
- MIT 许可,公开 API 小、后端可裁剪、源码可读。
当前 0.2.0 还不是 1.0,但就"可评估、可验证、可复现"这三点而言,它已经比很多标着 1.x 的库更诚实了。如果你有原生应用的 2D 渲染需求,特别是多语言文本 + 滤镜 + 离屏出图的组合,建议把 examples/hello_world 跑起来,用你自己的 workload 复测一遍性能矩阵,再下结论。
相关链接:
- 仓库:https://github.com/ClarkWain/WhatsCanvas
- 在线文档:https://clarkwain.github.io/WhatsCanvas/
- 下游 UI 框架 WhatsUI:https://github.com/ClarkWain/WhatsUI
- 性能方法学:
doc/PERFORMANCE_BENCHMARKS.md - NanoVG 基线原始数据:
benchmarks/baselines/nanovg-win-i7-8700-gtx1060/README.md
本文为技术介绍与整理,基于 WhatsCanvas 0.2.0 版本的公开仓库资料。性能数据引用自仓库归档的基准矩阵,具体环境见正文第七节,请勿直接外推。