介于 NanoVG 和 Skia 之间:C++17 可嵌入式 2D 渲染库 WhatsCanvas 全面介绍

介于 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 轻得多,源码可读、可验证、可裁剪。

目录

  1. [WhatsCanvas 是什么](#WhatsCanvas 是什么)
  2. [60 秒画出第一帧](#60 秒画出第一帧)
  3. 获取与集成:三种姿势
  4. [多后端架构:一个 API,五种后端](#多后端架构:一个 API,五种后端)
  5. 能力全景
  6. 文本渲染:这是重头戏
  7. 性能数据(附审计方法)
  8. 工程质量:不只是"能画像素"
  9. 选型建议:什么时候用它,什么时候别用
  10. 总结

一、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_packageadd_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 命中测试、虚线、路径效果 drawPathhitTestPathFill
Paint 填充/描边、解析式抗锯齿、线性/径向多 stop 渐变、14 种混合模式、真高斯阴影、颜色矩阵 PaintsetBlendModesetShadowLayer
Canvas 状态 save/restore、矩阵变换、矩形/抗锯齿路径裁剪、离屏层、quick reject clipPathsaveLayer
图片 PNG/JPEG 解码、raw RGBA、外部纹理、九宫格、圆角/圆形裁剪、contain/cover、平铺 ImagedrawImageFit
图层滤镜 内容/背景模糊、内阴影、毛玻璃、饱和度/亮度/对比度/颗粒、颜色矩阵、滤镜链 ImageFilterImageFilterChain
文字 系统字体、fallback、CJK/RTL、换行/省略号、描边/阴影/渐变文本、text-on-path FontManagerdrawTextBoxdrawTextOnPath
输出与互操作 离屏图片、GL framebuffer、外部 Vulkan image/Metal texture、同步/异步 RGBA 回读 OutputTargetreadPixelsRGBAAsync
诊断 像素 hash、PPM 导出、render stats、资源与 atlas 统计 computePixelsHashRGBARenderStats

几个值得单独说的点:

  • 毛玻璃/内阴影/滤镜链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。

已知风险它自己也写清楚了,这里替大家划个重点:

  1. 版本仍是 0.2.x,升级前读 CHANGELOG + 跑 package consumer 测试;
  2. Vulkan 是 opt-in,跨平台窗口呈现还在完善中;
  3. iOS 真机/模拟器 CI 和仓库内 iOS 示例还没补齐,移动端集成前要自己验证;
  4. Canvas 单实例不承诺并发访问,跨线程共享图片/字体/外部纹理没有公开约定;
  5. 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/Path API,一个 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 复测一遍性能矩阵,再下结论。

相关链接:

本文为技术介绍与整理,基于 WhatsCanvas 0.2.0 版本的公开仓库资料。性能数据引用自仓库归档的基准矩阵,具体环境见正文第七节,请勿直接外推。

相关推荐
今日无bug1 天前
HTML5 Canvas:从画图到游戏开发
前端·canvas
敲敲敲敲暴你脑袋6 天前
升级啦!纯前端打包下载离线地图
javascript·gis·canvas
DsirNg8 天前
用原生 HTML5 视频与 Canvas 实现浏览器端视频抽帧封面
javascript·canvas·视频抽帧·前端实战·html5 video·浏览器 api
牧艺19 天前
cos-design BubbleField:用 Canvas 做一个「会呼吸」的深海气泡场
前端·webgl·canvas
牧艺23 天前
cos-design RippleWater & SmokeFog:水面涟漪与烟雾雾气怎么做
前端·canvas·视觉设计
牧艺25 天前
cos-design WeatherBackground:用 Canvas 做一个「会变天」的背景引擎
前端·canvas·视觉设计
随风一样自由1 个月前
【前端+Canvas+航天】长征十号乙火箭回收全流程动画
前端·canvas·长征十号乙
Mr.Daozhi1 个月前
从零构建 AI 学术论文助手(四):PDF.js 实时截图 + Gemini 视觉分析
javascript·人工智能·pdf·canvas·pdf.js·gemini
jump_jump2 个月前
网页 UI 终于能进游戏和 3D 场景了:HTML-in-Canvas 为什么重要
浏览器·three.js·canvas