TGFX - 腾讯开源的轻量级 2D 渲染引擎

TGFX - 腾讯开源的轻量级 2D 渲染引擎

  • 首页
  • 下载
  • 文档
  • 案例
  • CN
  • GitHub
  • 论坛交流
  • Languages iconCN
    • EN

›架构设计

快速开始

  • TGFX 简介
  • 平台与后端支持
  • 环境准备与编译
  • Hello2D 示例

API 参考与概述

    绘图基础

    • Canvas Overview
    • Paint Overview
    • Path Overview
    • BlendMode Overview
    • Picture 录制与回放

    几何与变换

    • 几何与变换

    图像与像素

    • Image
    • Bitmap 与像素操作
    • 图像编解码
    • 视频与外部纹理

    文本渲染

    • 文本与字体

    着色与效果

    • 着色与效果

    图层系统

    • 图层系统

    进阶主题

    • 自定义 Shader
    • 色彩管理

架构设计

  • 渲染管线
  • GPU 硬件抽象层
  • 图层渲染系统
  • 缓存系统
  • 文字图集渲染
  • GPU Hairline 极细描边
  • 广色域渲染
  • SIMD 加速

API 文档

  • API 文档

SIMD 加速

TGFX 在 CPU 端的性能热点路径中使用了 SIMD(Single Instruction, Multiple Data)向量化加速。与手写平台特定 intrinsics(如 SSE/NEON)不同,TGFX 基于 Google 的 Highway 库实现了跨平台 SIMD 抽象——编译时自动生成多种 ISA 版本,运行时动态选择最优实现。

Highway 动态分发机制

Highway 是 Google 开发的 C++ SIMD 库,其核心理念是「编写一次,在所有 ISA 上运行」。TGFX 的每个 SIMD 加速文件都遵循相同的代码组织模式:

编译时:多 ISA 版本生成
// ① 声明当前文件路径(foreach_target.h 需要据此重新 include 自身)
#undef HWY_TARGET_INCLUDE
#define HWY_TARGET_INCLUDE "core/RectSIMD.cpp"

// ② 根据目标平台的所有可用 ISA,多次 include 当前文件
#include "hwy/foreach_target.h"

// ③ 引入 Highway 核心 API
#include "hwy/highway.h"

// ④ 在目标命名空间中编写 SIMD 实现
HWY_BEFORE_NAMESPACE();
namespace tgfx {
namespace HWY_NAMESPACE {     // 每次 include 会展开为不同的 ISA 命名空间
namespace hn = hwy::HWY_NAMESPACE;

// SIMD 实现代码...

}  // namespace HWY_NAMESPACE
}  // namespace tgfx
HWY_AFTER_NAMESPACE();

foreach_target.h 会根据编译目标平台的 CPU 架构,多次 #include 当前源文件。每次 include 时 HWY_NAMESPACE 展开为不同的 ISA 命名空间(如 N_SSE4、N_AVX2、N_NEON 等),从而在一个 .cpp 文件中生成多份针对不同指令集的机器码。

运行时:动态选择最优 ISA
#if HWY_ONCE  // 只在最后一次 include 时编译
namespace tgfx {
// 注册所有 ISA 版本的函数指针表
HWY_EXPORT(SetBoundsHWYImpl);

// 公开 API:运行时自动分发到当前 CPU 支持的最优版本
bool Rect::setBounds(const Point pts[], int count) {
    return HWY_DYNAMIC_DISPATCH(SetBoundsHWYImpl)(this, pts, count);
}
}  // namespace tgfx
#endif

HWY_EXPORT 宏为每个 SIMD 函数生成一个包含所有 ISA 版本函数指针的静态表。HWY_DYNAMIC_DISPATCH 在首次调用时通过 CPUID(x86)或辅助向量(ARM)探测当前 CPU 支持的最高指令集,然后缓存选择结果,后续调用直接跳转到最优版本,没有运行时开销。

支持的指令集
平台支持的 ISA(由高到低)
x86_64AVX-512 → AVX2 → SSE4.1 → SSE2 → 标量回退
ARM64NEON(默认可用) → 标量回退
ARM32NEON(视 CPU 而定) → 标量回退
WASMWASM SIMD → 标量回退

Highway 确保了即使在不支持 SIMD 的平台上也能正确运行——最差情况下回退到标量实现,功能完全一致。

加速场景一:Rect 边界计算

RectSIMD.cpp 加速了 Rect::setBounds() 方法——从一组 Point 数组中计算最小包围矩形。

加速原理

利用 128-bit 向量同时处理两个 Point(4 个 float),通过 Min/Max 指令并行归约:

输入: pts[0]=(x0,y0), pts[1]=(x1,y1), pts[2]=(x2,y2), ...
                │
                ▼
    ┌─────────────────────────┐
    │ 128-bit 向量: [x0, y0, x1, y1] │
    │  同时比较两个点的 x 和 y    │
    └─────────────────────────┘
                │
      Min / Max 归约
                │
                ▼
    min = [minX_lo, minY_lo, minX_hi, minY_hi]
    max = [maxX_lo, maxY_lo, maxX_hi, maxY_hi]
                │
                ▼
    rect = (min(minX_lo, minX_hi),
            min(minY_lo, minY_hi),
            max(maxX_lo, maxX_hi),
            max(maxY_lo, maxY_hi))

实现中还通过 accum = Mul(accum, xy) 累乘检测 NaN/Inf——如果任何输入值不是有限数,乘以零后不会等于零(NaN 的特性),从而用 Eq(accum*0, 0) 一步检测所有非有限值。

应用场景

Rect::setBounds() 是 Path 边界计算的底层方法,被 Path::getBounds()、Path::computeTightBounds() 等高频方法调用。在复杂路径(如 SVG 导入的数千控制点路径)中,这个优化能显著减少边界计算耗时。

加速场景二:矩阵运算

MatrixSIMD.cpp 加速了 Matrix 类的核心运算,覆盖了矩阵变换的所有情况:

方法SIMD 实现加速原理
Matrix::TransPoints()TransPointsHWYImpl交错向量 [tx, ty, tx, ty] 批量加法,一次处理 N/2 个点
Matrix::ScalePoints()ScalePointsHWYImplMulAdd(src, [sx,sy,...], [tx,ty,...]) 融合乘加
Matrix::AffinePoints()AffinePointsHWYImplReverse2 交换 xy,实现 skew 乘加:src*scale + swz*skew + trans
Matrix::PerspPoints()PerspPointsHWYImpl同时计算 x'、y' 和 w,用 Div 做透视除法
Matrix::mapRect()MapRectHWYImpl将 LTRB 视为 4-float 向量,Min/Max 直接得到变换后的包围盒
Matrix::ConcatMatrix()ConcatMatrixHWYImpl行列向量化:ReduceSum(Mul(row, col)) 求每个结果元素
Matrix::mapHomogeneous()MapHomogeneousHWYImpl3 列向量的 MulAdd 链
矩阵类型分级优化

MapRectHWYImpl 根据矩阵类型选择不同的加速路径:

  1. 纯平移(TranslateMask):LTRB + [tx, ty, tx, ty],然后 Min/Max 确保 LTRB 顺序正确(处理负缩放)
  2. 缩放+平移(isScaleTranslate()):MulAdd(LTRB, [sx,sy,sx,sy], [tx,ty,tx,ty]),同样需要 Min/Max 规范化
  3. 透视(PerspectiveMask):先用 MapHomogeneousHWYImpl 变换四角到齐次坐标,然后 ProjectCornerWithClip 处理 w 平面裁剪,最终四角 Min 得到包围盒
  4. 仿射:变换四角后调用 Rect::setBounds() 归约
透视裁剪

透视变换中,某些点可能落在摄像机后方(w < 0)。MapRectPerspective 通过 ClipEdgeToW0Plane 将边与 w = 1/2^14 平面求交,避免除以接近零的 w 值导致的数值爆炸。整个裁剪过程也使用 SIMD 向量运算——交点坐标以 (x, y, -x, -y) 格式存储,利用 Min 指令同时更新包围盒的四个边界。

加速场景三:向量运算

VecSIMD.cpp 为 Vec4 类型的所有运算提供 SIMD 加速。Vec4 是 TGFX 的四分量向量类型,广泛用于 Layer 系统的属性动画计算(如 3D 合成中的齐次坐标变换)。

加速的运算
运算类别加速方法
算术+、-、*、/、-(取负)、标量乘/除
比较>=(GreaterEqual)、<(LessThan)
逻辑Or、And、Any、All、IfThenElse
数学Abs、Sqrt、Min、Max

每个运算都是 128-bit 固定宽度(Full128<float>),恰好容纳 4 个 float 分量,实现一条指令完成四分量运算。

无分支条件选择

IfThenElseVec4HWYImpl 实现了无分支的四分量条件选择:

const auto mask = hn::Ne(vc, hn::Zero(d));  // cond != 0 → true
hn::IfThenElse(mask, vt, ve);               // true 选 t,false 选 e

这在 Layer 动画系统中用于混合计算——例如根据插值因子在两组属性值间选择,避免了标量代码中的四次分支判断。

加速场景四:Tile 排序

TileSortCompareFuncSIMD.cpp 加速了分块渲染中的 Tile 排序比较函数。

背景

TGFX 的 Layer 系统采用分块渲染策略(tiled rendering)。DisplayList 和 TileCache 在渲染时需要按 Tile 到鼠标/焦点位置的距离排序,优先渲染用户关注区域。排序比较函数在大量 Tile 的场景中会被调用 O(N log N) 次。

加速原理

标量版本需要分别计算两个 Tile 中心到焦点的距离(各 2 次乘法 + 1 次加法 + 减法),共 10+ 次浮点运算。SIMD 版本将两个 Tile 的坐标打包进一个 128-bit 向量,一条指令同时完成:

// 将两个 Tile 的坐标打包为 [a.x, a.y, b.x, b.y]
auto res = Add(ConvertTo(df, [a.first, a.second, b.first, b.second]), Set(df, 0.5f));
// 减去焦点 [cx, cy, cx, cy],得到偏移
res = MulSub(res, Set(df, tileSize), [center.x, center.y, center.x, center.y]);
// 平方
res = Mul(res, res);
// 交换相邻对并求和:[dx²+dy², dx²+dy², dx²+dy², dx²+dy²]
res = Add(Reverse2(df, res), res);
// 比较 resVec[0] vs resVec[2]
return resVec[0] < resVec[2];  // (或 > 用于降序)

整个距离计算和比较在 5 条 SIMD 指令内完成,且完全无分支。

加速场景五:Box 降采样

BoxFilterDownsampleSIMD.cpp 是最复杂的 SIMD 加速场景,用于 ImageCodec 解码时的图像降采样。当图像解码后尺寸大于目标尺寸时,BoxFilterDownsample 使用面积平均法缩小图像。

缩放倍率分级

根据缩放比(必须为 2 的幂)选择不同的 SIMD 实现:

缩放比实现函数向量化策略
×2ResizeAreaFastx2SIMDFuncImplLoadInterleaved2 解交错相邻像素对
×4ResizeAreaFastx4SIMDFuncImpl1ch: LoadInterleaved4 四路解交错;4ch: 复用 ×16 通用路径
×8ResizeAreaFastx8SIMDFuncImpl1ch: 逐行 Load + PromoteTo 累加;4ch: 复用 ×16 通用路径
×16ResizeAreaFastx16SimdFuncImpl通用 N×N 分块累加
≥32ResizeAreaFastxNSimdFuncImpl32-bit 累加器防溢出
数据类型提升防溢出

小缩放比(≤16)使用 uint16_t 累加器,大缩放比(≥32)升级到 uint32_t。以 4 通道 ×16 为例:

uint8_t 像素 ──Load──→ [16 × uint8]
                          │
              PromoteLowerTo / PromoteUpperTo
                          │
                    [8 × uint16] × 2
                          │
                    逐行累加 Add
                          │
              LowerHalf + UpperHalf 归约
                          │
              ShiftRightSame (除以 scale²)
                          │
                DemoteTo ──→ [4 × uint8] 输出

位移代替除法(>> shiftNum,其中 shiftNum = 2 × log2(scale))是整数降采样的经典优化,加上 padding = scale² / 2 实现四舍五入。

非整数缩放比的加速

当缩放比不是整数时,使用加权面积平均算法。Mul 和 MulAdd 两个辅助函数也通过 Highway 加速——它们对 float 累加缓冲区进行批量标量乘法和乘加操作,用于垂直方向的加权混合。这些函数使用 ScalableTag<float>(自适应向量宽度),在 AVX2 平台上可以一次处理 8 个 float。

代码组织总览

TGFX 的所有 SIMD 加速代码集中在 5 个文件中:

文件加速目标主要调用者
src/core/RectSIMD.cppRect::setBounds()Path::getBounds()、路径边界计算
src/core/MatrixSIMD.cppMatrix 点变换、矩阵乘法、mapRect所有需要矩阵变换的绘制路径
src/core/VecSIMD.cppVec4 全套运算Layer 动画系统、3D 合成
src/core/utils/TileSortCompareFuncSIMD.cppTile 距离排序DisplayList、TileCache
src/core/BoxFilterDownsampleSIMD.cppBox 滤波降采样ImageCodec 解码缩放

这些文件都遵循相同的 Highway 模板结构:foreach_target.h → 多 ISA 实现 → HWY_EXPORT + HWY_DYNAMIC_DISPATCH。公开 API 的调用者无需关心 SIMD 细节——Rect::setBounds()、Matrix::mapPoints() 等方法内部透明地分发到最优实现。

性能提升数据

以下是 SIMD 优化在各平台上相对标量实现的性能提升百分比(越高越好,负值表示因额外开销导致性能下降)。

mapPoints / mapRect / setBounds
方法矩阵类型WebAndroidiOSMacWindowsOHOS
mapPointstranslate53%76%69%24%83%36%
mapPointsscale39%73%63%20%77%18%
mapPointsaffine19%75%63%18%83%−2%
mapRecttranslate22%68%60%28%66%69%
mapRectscale17%67%55%21%64%59%
mapRectaffine−129%33%−14%−90%−110%−94%
setBounds—47%69%81%83%50%81%

说明:mapRect 仿射变换(即 skew 不为 0)在部分平台出现负值,原因是仿射 mapRect 需要先将矩形展开为四角再逐点变换,SIMD 路径引入了额外的 corner 展开和 setBounds 归约步骤,在某些平台的标量分支预测优化较好时反而不如直接的标量实现。不过实际业务中affine场景并不常见,绝大多数矩阵变换属于 translate 或 scale 类型,因此整体收益仍然显著。

BoxFilter 降采样(Mac 平台,缩放倍率为 2 的幂)
缩放倍率性能提升
×285%
×474%
×882%
×1686%
×3264%
×6462%

说明:BoxFilter 性能数据仅在 Mac 平台测量。小缩放倍率(×2、×16)提升最为显著,因为 SIMD 向量宽度与数据块大小匹配度最高;大缩放倍率(×32、×64)由于需要升级到 32-bit 累加器且循环迭代次数增多,提升幅度相对有所回落。

与直接使用 intrinsics 的对比

维度Highway(TGFX 的方案)手写 intrinsics
跨平台一份代码自动覆盖 x86/ARM/WASM每个平台需要独立实现
多 ISA自动生成 SSE4/AVX2/AVX-512 等多版本需要手动用 #ifdef 管理
运行时分发HWY_DYNAMIC_DISPATCH 自动探测需要手写 CPUID 探测 + 函数指针
代码量5 个文件约 1200 行同等功能预计需要 3000-5000 行
维护成本Highway 版本升级即可获得新 ISA 支持每个新 ISA 都需要手写适配
性能接近 intrinsics(Highway 不引入额外抽象层)理论最优

TGFX 选择 Highway 的核心考量是可维护性:用接近手写性能的代价,换取了跨平台单一代码库和自动 ISA 适配能力。

← 广色域渲染API 文档 →
公司地址:广东省深圳市南山区海天二路33号腾讯滨海大厦Copyright © 2018 - 2026 Tencent. All Rights Reserved.联系电话:0755-86013388隐私政策