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

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

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

›架构设计

快速开始

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

API 参考与概述

    绘图基础

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

    几何与变换

    • 几何与变换

    图像与像素

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

    文本渲染

    • 文本与字体

    着色与效果

    • 着色与效果

    图层系统

    • 图层系统

    进阶主题

    • 自定义 Shader
    • 色彩管理

架构设计

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

API 文档

  • API 文档

GPU Hairline 极细描边

Hairline(极细描边)是 tgfx 中专门用于渲染宽度为 1 像素或更细线条的 GPU 加速方案。该方案绕过常规的三角化流程,直接在 GPU 上通过 Coverage 计算实现亚像素级抗锯齿,在保持高质量渲染的同时大幅提升性能。

1. 方案概述

1.1 设计目标

Hairline 渲染方案专为以下两个核心场景设计:

  1. Hairline 描边模式:无论画布如何缩放,始终按 1 物理像素 宽度渲染线条,常用于 UI 分隔线、网格线等需要保持视觉一致性的场景

  2. 亚像素描边降级:当缩放后实际描边宽度小于 1 像素时,自动按 1 物理像素 渲染,同时 降低透明度 以模拟更细的视觉效果,实现平滑的缩放动画过渡

这两个场景中,曲线渲染采用 Loop-Blinn 算法,相比传统三角化方案具有更高的渲染性能:

  • CPU 端三角面化计算量极低:每条曲线仅需生成固定数量的顶点,无需传统方案中基于曲率的迭代细分与拟合计算
  • 顶点数据量大幅减少:更小的 Vertex Buffer 带来更低的显存占用,同时减少 CPU 到 GPU 的数据传输带宽消耗

1.2 技术特点

优势:曲线渲染在 Shader 中完成,CPU 端三角面化计算更简单

  • 普通描边:需要为每条路径生成完整的描边轮廓,描边轮廓基于曲率拟合成直线三角面
  • Hairline 渲染:基于"一条曲线一组三角面"的简化策略
    • 直线:6 个顶点形成菱形区域(2 个内部顶点 + 4 个外部顶点)
    • 曲线:5 个顶点形成五边形区域(1 个内部顶点 + 4 个外部顶点)
  • Stroke Path 无需宽度计算,也无需处理复杂的连接点(join)和端点(cap)计算
  • 三角面化计算量显著减少,CPU 开销更低

劣势:GPU 端渲染批次更多

  • 普通描边:所有顶点在一个 Vertex Buffer 中,一次渲染批次完成绘制
  • Hairline 渲染:直线和曲线分别处理,使用两个独立的 Vertex Buffer
    • Buffer 1:所有直线段的三角面
    • Buffer 2:所有曲线段的三角面
    • 需要两次渲染批次(Draw Call),GPU 调度开销增加

性能权衡

维度普通描边Hairline 渲染影响
CPU 三角面化复杂(曲线按直线剖分拟合成三角形)简单(一条曲线生成一组三角面)Hairline 优势
Vertex Buffer单个 Buffer两个 Buffer(直线/曲线)普通描边优势
渲染批次1 次 Draw Call2 次 Draw Call普通描边优势
GPU 光栅化CPU 生成轮廓后 GPU 填充GPU Shader 直接计算曲线和抗锯齿Hairline 优势

综合结论:尽管 Hairline 增加了渲染批次,但 CPU 端计算简化 + GPU Shader 优化带来的性能收益远超过额外 Draw Call 的开销,尤其在复杂路径和单线程场景下优势明显。

1.3 核心架构

Hairline 渲染采用分治策略,将路径分解为直线段(Line)和二次贝塞尔曲线(Quad)两类几何体,分别由专用的 DrawOp 处理。路径分解规则如下:

  • 直线段:保持原始几何形态,直接生成直线三角面
  • 曲线段:三次贝塞尔曲线(Cubic)和圆锥曲线(Conic)统一转换为二次贝塞尔曲线(Quad),简化 Shader 实现复杂度
Path (Line / Quad / Cubic / Conic)
  │
  ▼
HairlineTriangulator (CPU)
  │
  ├──► Line ───────────────────────────► Line Vertices ──► HairlineLineOp ──► GPU Shader
  │
  └──► Quad / Cubic / Conic ──► Quad ──► Quad Vertices ──► HairlineQuadOp ──► GPU Shader

1.4 数据流程图

Hairline Rendering Pipeline

1.5 Loop-Blinn 算法原理

Hairline 方案中二次曲线的渲染采用 Loop-Blinn 算法,该算法由 Charles Loop 和 Jim Blinn 在 SIGGRAPH 2005 论文 "Resolution Independent Curve Rendering using Programmable Graphics Hardware" 中提出。

核心思想:将二次贝塞尔曲线转换为隐式方程形式,在 Fragment Shader 中直接求解曲线内外关系,无需传统的曲线细分拟合。 该算法基于一个关键的数学性质:所有二次贝塞尔曲线在射影几何下是等价的,即任意二次曲线都可以通过射影变换映射到同一条规范曲线 $u^2 - v = 0$。GPU 的透视校正插值天然实现了这一逆映射,使得 Shader 只需在规范空间中完成简单的求值运算。

隐式方程:对于二次贝塞尔曲线,定义隐式方程 $F(u,v) = u^2 - v$:

  • $F < 0$:点在曲线内侧
  • $F = 0$:点在曲线上
  • $F > 0$:点在曲线外侧

UV 坐标映射:在 CPU 端为曲线控制点分配特殊的纹理坐标 $(u, v)$,GPU 硬件自动对这些坐标进行插值。Fragment Shader 仅需一次乘法和一次减法即可完成曲线求值,计算效率极高。

抗锯齿:利用屏幕空间偏导数(dFdx/dFdy)计算隐式函数的梯度,将 $F$ 值归一化为像素距离,实现亚像素级平滑过渡。

参考文献:

  • GPU Gems 3 - Chapter 25: Rendering Vector Art on the GPU
  • Loop & Blinn, SIGGRAPH 2005 Paper (PDF)

2. Vertex Buffer 布局

2.1 Line Vertex Buffer 布局

每条直线段生成 6 个顶点、构建 6 个三角形,形成以线段为中心的菱形包围区域。其中 p0、p1 位于线段两端点,作为内层顶点(coverage = 1.0);p2~p5 沿线段法线方向向外扩展 1 个像素,形成外层顶点(coverage = 0.0)。GPU 在光栅化时对 coverage 进行线性插值,实现从线段中心向边缘 1 像素范围内的平滑过渡,完成抗锯齿效果。

Line Vertex Layout

数据结构:

struct LineVertex {
    Point pos;       // 8 bytes (x, y)
    float coverage;  // 4 bytes
};
// Total: 12 bytes per vertex
// 6 vertices per line segment = 72 bytes per line

三角形构建:

6 个顶点通过索引构建 6 个三角形,共 18 个索引。三角形划分策略确保每个三角形至少包含一个内层顶点(p0 或 p1),使 coverage 插值能够正确覆盖整个菱形区域:

索引列表: (p0,p2,p4), (p0,p4,p1), (p1,p4,p5), (p1,p5,p3), (p0,p1,p3), (p0,p3,p2)

三角形分布:
- 左端区域: (p0,p2,p4) - 覆盖左侧外扩三角
- 中央上部: (p0,p4,p1) - 连接两端点与上外层
- 右上区域: (p1,p4,p5) - 覆盖右上外扩
- 右端区域: (p1,p5,p3) - 覆盖右侧外扩三角
- 中央下部: (p0,p1,p3) - 连接两端点与下外层
- 左下区域: (p0,p3,p2) - 覆盖左下外扩

2.2 Quad Vertex Buffer 布局

每条二次贝塞尔曲线生成 5 个顶点、构建 3 个三角形,形成包围曲线的五边形区域。其中 a0、a1 为曲线两端点,b0 为曲线控制点(凸包顶点),这三个顶点位于曲线本身;c0、c1 分别从端点沿曲线法线方向向外扩展 1 个像素,形成抗锯齿边缘。GPU 通过 Loop-Blinn 算法在 Fragment Shader 中计算每个片元到曲线的隐式距离,实现亚像素级精度的抗锯齿效果。

Quad Vertex Layout

数据结构:

struct BezierVertex {
    Point pos;        // 8 bytes (x, y) - 顶点位置
    Point quadCoord;  // 8 bytes (u, v) - Loop-Blinn 隐式方程坐标
};
// Total: 16 bytes per vertex
// 5 vertices per quad segment = 80 bytes per quad

三角形构建:

5 个顶点通过索引构建 3 个三角形,共 9 个索引。三角形划分确保完整覆盖曲线的凸包区域及外扩抗锯齿边缘:

索引列表: (a0, a1, b0), (b0, c1, c0), (a1, c1, b0)

三角形分布:
- 曲线凸包: (a0, a1, b0) - 覆盖曲线主体区域,Loop-Blinn 计算在此三角形内进行
- 左侧外扩: (b0, c1, c0) - 覆盖控制点到左端点的抗锯齿边缘  
- 右侧外扩: (a1, c1, b0) - 覆盖右端点到控制点的抗锯齿边缘

3. GPU 端片元着色

3.1 直线段片元着色

对于直线段,CPU 预计算每个顶点到线条中心的距离(edge distance),存入顶点属性。Fragment Shader 只需简单插值和 clamp 操作:

// Vertex Shader: 传递边缘距离
varying float vEdgeDistance;
vEdgeDistance = aEdgeDistance;  // 从顶点属性获取

// Fragment Shader: 计算 Coverage
float edgeAlpha = abs(vEdgeDistance);
edgeAlpha = clamp(edgeAlpha, 0.0, 1.0);
gl_FragColor = uColor * vec4(edgeAlpha);

3.2 二次曲线片元着色(Loop-Blinn 算法)

二次曲线采用 Loop-Blinn 隐式方程(参见 1.5 节原理说明)在 Fragment Shader 中计算覆盖率:

// Fragment Shader: Loop-Blinn 曲线抗锯齿
vec2 duvdx = dFdx(vHairQuadEdge.xy);
vec2 duvdy = dFdy(vHairQuadEdge.xy);

// 计算隐式函数梯度 ∇F
vec2 gF = vec2(
    2.0 * vHairQuadEdge.x * duvdx.x - duvdx.y,
    2.0 * vHairQuadEdge.x * duvdy.x - duvdy.y
);

// 计算隐式函数值 F = u² - v
float F = vHairQuadEdge.x * vHairQuadEdge.x - vHairQuadEdge.y;

// 归一化距离并转换为 Coverage
float edgeAlpha = sqrt(F * F / dot(gF, gF));
edgeAlpha = max(1.0 - edgeAlpha, 0.0);

4. 直线与曲线分治策略

4.1 分离设计的动机

HairlineLineOp 和 HairlineQuadOp 采用分离设计,主要基于以下考虑:

维度HairlineLineOp (直线)HairlineQuadOp (曲线)
顶点布局12 bytes (pos + coverage)24 bytes (pos + quadCoord)
顶点数量每段 6 顶点每段 5 顶点
索引数量每段 18 索引每段 9 索引
Shader 复杂度简单(线性插值)复杂(dFdx/dFdy + 隐式方程)
GPU 指令数~10 条~30 条

4.2 分离带来的优势

  1. Shader 编译优化:GPU 驱动可以为不同复杂度的 Shader 生成更高效的机器码
  2. 顶点带宽优化:直线段不需要额外的 UV 坐标,减少 50% 顶点数据量
  3. 批处理效率:相同类型的几何体可以合并绘制调用
  4. 调试便利性:问题更容易定位到具体的几何类型

4.3 路径分解流程

// PathDecomposer 分解逻辑
switch (segment.verb) {
    case PathVerb::Line:
        // 直接添加到 lines_ 容器
        processLine(segment.points);
        break;
    case PathVerb::Quad:
        // 检测退化并分类
        if (IsDegenQuadOrConic(points)) {
            // 退化为直线
            addToLines(points);
        } else {
            // 保持为曲线
            addToQuads(points);
        }
        break;
    case PathVerb::Cubic:
        // 转换为多个二次曲线
        auto quads = ConvertCubicToQuads(points);
        for (auto& quad : quads) {
            addToQuads(quad);
        }
        break;
}

5. GPUHairlineProxy 与缓存机制

5.1 GPUHairlineProxy 结构

GPUHairlineProxy 是 Hairline 渲染的核心代理类,封装了直线和曲线两个独立的顶点缓冲区:

class GPUHairlineProxy {
  Matrix drawingMatrix;                        // 绘制变换矩阵
  std::shared_ptr<GPUBufferProxy> lineVertexProxy;  // 直线顶点缓冲代理
  std::shared_ptr<GPUBufferProxy> quadVertexProxy;  // 曲线顶点缓冲代理
};

5.2 Buffer 缓存与复用机制

// ProxyProvider::createGPUHairlineProxy
auto uniqueKey = shape->getUniqueKey();

// 为直线和曲线分别生成缓存键
auto lineVertexKey = UniqueKey::Append(uniqueKey, &LineVertexType, 1);
auto quadVertexKey = UniqueKey::Append(uniqueKey, &QuadVertexType, 1);

// 查找缓存
auto lineVertexProxy = findOrWrapGPUBufferProxy(lineVertexKey);
auto quadVertexProxy = findOrWrapGPUBufferProxy(quadVertexKey);

// 缓存命中时直接复用
if (lineVertexProxy || quadVertexProxy) {
    return std::make_shared<GPUHairlineProxy>(...);
}

// 缓存未命中,创建异步任务生成数据
auto rasterizer = std::make_unique<HairlineTriangulator>(shape, hasCap);
dataSource = DataSource<HairlineBuffer>::Async(std::move(rasterizer));

6. 与普通描边方案对比

6.1 技术路线对比

特性Hairline 渲染普通描边
曲线处理Shader 直接计算(Loop-Blinn)CPU 端直线拟合
三角面化简单(一条曲线一组三角面)复杂(曲率细分 + 轮廓生成)
顶点数据量较小较大(4-10 倍)
Vertex Buffer两个(直线 + 曲线)单个
Draw Call2 次1 次
抗锯齿计算GPU Shader(Coverage)CPU 生成轮廓 + GPU 填充

6.2 优势分析

Hairline 方案核心优势:

  1. CPU 三角面化简化:一条曲线只需生成一组三角面,无需处理 join/cap/轮廓生成等复杂几何计算
  2. GPU Shader 曲线拟合零成本:三角形光栅化中 Shader 决定曲线像素是否显示,贝塞尔曲线在 GPU 中并行计算,开销极低
  3. 更小的顶点数据量:相比普通描边减少 4-10 倍的顶点数据,降低 GPU 内存带宽和上传开销
  4. 单线程场景优势巨大:CPU 计算简化 + GPU 并行,完全绕过单线程瓶颈,适用于嵌入式设备、WebAssembly 等受限场景
  5. 抗锯齿开关性能一致:是否抗锯齿在 Shader 中计算,对性能几乎无影响

渲染批次增加的开销可忽略:

  • 虽然 Hairline 需要 2 次 Draw Call(直线 + 曲线),普通描边只需 1 次
  • 实际场景中:CPU 三角面化简化的收益 >> Draw Call 增加的开销
  • GPU 并行能力强,额外的渲染批次对整体性能影响微乎其微

普通描边方案优势:

  1. 单次 Draw Call:所有顶点在同一 Buffer,渲染批次更少
  2. 更简单的 Shader:无需 dFdx/dFdy 指令和隐式方程计算
  3. 多线程友好:复杂的三角面化计算可充分利用多线程并行

6.3 适用场景

  • 使用 Hairline:1 像素描边、缩放后宽度小于 1 像素的描边、单线程/低端设备、需要高质量抗锯齿的细线
  • 使用普通描边:宽度大于 1 像素的描边、需要处理复杂 join/cap 样式的场景

7. 代码结构

7.1 核心文件清单

src/
├── core/
│   ├── HairlineTriangulator.h      # CPU 端路径分解器接口
│   └── HairlineTriangulator.cpp    # 路径分解与顶点生成实现
│
├── gpu/
│   ├── proxies/
│   │   └── GPUHairlineProxy.h      # GPU 缓冲代理
│   │
│   ├── ops/
│   │   ├── HairlineLineOp.h/cpp    # 直线段 DrawOp
│   │   └── HairlineQuadOp.h/cpp    # 曲线段 DrawOp
│   │
│   ├── processors/
│   │   ├── HairlineLineGeometryProcessor.h/cpp   # 直线几何处理器
│   │   └── HairlineQuadGeometryProcessor.h/cpp   # 曲线几何处理器
│   │
│   ├── glsl/processors/
│   │   ├── GLSLHairlineLineGeometryProcessor.h/cpp  # 直线 GLSL Shader
│   │   └── GLSLHairlineQuadGeometryProcessor.h/cpp  # 曲线 GLSL Shader
│   │
│   └── tasks/
│       └── HairlineBufferUploadTask.h/cpp  # 异步缓冲上传任务
← 文字图集渲染广色域渲染 →
  • 1. 方案概述
    • 1.1 设计目标
    • 1.2 技术特点
    • 1.3 核心架构
    • 1.4 数据流程图
    • 1.5 Loop-Blinn 算法原理
  • 2. Vertex Buffer 布局
    • 2.1 Line Vertex Buffer 布局
    • 2.2 Quad Vertex Buffer 布局
  • 3. GPU 端片元着色
    • 3.1 直线段片元着色
    • 3.2 二次曲线片元着色(Loop-Blinn 算法)
  • 4. 直线与曲线分治策略
    • 4.1 分离设计的动机
    • 4.2 分离带来的优势
    • 4.3 路径分解流程
  • 5. GPUHairlineProxy 与缓存机制
    • 5.1 GPUHairlineProxy 结构
    • 5.2 Buffer 缓存与复用机制
  • 6. 与普通描边方案对比
    • 6.1 技术路线对比
    • 6.2 优势分析
    • 6.3 适用场景
  • 7. 代码结构
    • 7.1 核心文件清单
公司地址:广东省深圳市南山区海天二路33号腾讯滨海大厦Copyright © 2018 - 2026 Tencent. All Rights Reserved.联系电话:0755-86013388隐私政策