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 文档

Bitmap 与像素操作

Bitmap 是 tgfx 中处理 CPU 侧像素读写、管理栅格内存的核心容器。它是像素数据的物理“持有者”,负责维护从内存分配到 GPU 同步的完整生命周期。

在现代渲染流水线中,虽然大部分绘制由 GPU 完成,但 CPU 侧的像素操作依然不可或缺:

  • 物理所有权:不同于 Image 的逻辑引用,Bitmap 是真实的内存管理者。它通过引用计数(PixelRef)确保内存的安全释放。
  • 可变性 (Mutable):它是唯一允许开发者直接通过指针修改内容的容器。
  • 桥梁作用:它作为“数据代理”,连接了磁盘解码出的字节流与 GPU 纹理上传接口。

1. Bitmap 内存管理

1.1 分配策略 (allocPixels)

Bitmap 的核心能力始于其对内存的灵活分配方案。

  • 构造初始化:也可以通过构造函数直接初始化:Bitmap(width, height, alphaOnly, tryHardware, colorSpace),效果等价于默认构造加 allocPixels()。
  • allocPixels(w, h, alphaOnly, tryHardware, colorSpace):
    • alphaOnly:如果开启,系统仅分配单通道 Alpha 内存(每像素 1 字节)。这在处理字体笔迹或模糊算法的中间掩码(Mask)时,能减少 75% 的内存压力。
    • tryHardware:这是移动端性能优化的关键开关。开启后,tgfx 会优先向系统申请 AHardwareBuffer (Android) 或 CVPixelBuffer (iOS)。

1.2 物理载体 PixelRef 与引用计数

Bitmap 内部并不直接管理 void* 指针,而是通过 PixelRef 包装。

  • 共享机制:多个 Bitmap 实例可以共享同一个 PixelRef。这允许开发者在不拷贝内存的情况下,创建出指向原图不同区域的多个 Bitmap 子视图。
  • 生命周期:只要还有一个 Bitmap 引用该 PixelRef,物理内存就不会被销毁。

1.3 HardwareBuffer 深度解析

HardwareBuffer 是连接 CPU 像素操作与 GPU 高性能渲染的“桥梁”,实现了一份内存,两端共享。

  • 平台映射:在 Android 上映射为 AHardwareBuffer,在 iOS/macOS 上映射为 CVPixelBuffer,在 Windows 上映射为 ID3D11Texture2D。
  • 零拷贝 (Zero-Copy) 机制:
    • CPU 侧:通过 lockPixels() 将硬件内存映射到进程地址空间,实现直接读写。
    • GPU 侧:GPU 纹理通过 EGLImage 或 IOSurface 直接绑定到该物理内存。
    • 优势:规避了传统的 glTexImage2D 像素拷贝过程,极大降低了在大尺寸图片下的纹理上传耗时和总线带宽消耗。
  • 性能权衡:硬件层内存虽然支持零拷贝显示,但在 CPU 侧调用 lockPixels() 会引发底层缓存一致性同步(Cache Flush),因此对于纯 CPU 的像素算法,不建议盲目开启 tryHardware。

2. Pixmap:轻量级逻辑视图

Pixmap 是一个极其轻量的、不管理生命周期的观察者结构体。

2.1 结构与职责

  • 构造初始化:Pixmap 也支持直接从 Bitmap 构造:Pixmap pixmap(bitmap),会自动绑定像素指针并在析构时解锁。
  • 逻辑构成:由 ImageInfo (宽/高/步进/格式) 和一个裸指针 (void*) 组成。
  • 设计动机:在函数调用链中传递像素时,使用 Pixmap 可以避免频繁创建 Bitmap 或 shared_ptr 带来的开销。它仅仅是告诉系统:“请按照这个格式,去读写这个地址开头的内存”。

2.2 视图协作

  • 下取子集 (makeSubset):Pixmap 的 makeSubset 操作仅涉及到指针偏移量和宽高的修改,不产生任何像素拷贝,性能开销几乎为零。
  • 格式转换 (readPixels):它是跨格式处理的神器。你可以用一个 BGRA 格式的 Pixmap 调用 readPixels 写入一个 Gray8 格式的目标,内部会自动完成像素重采样和亮度计算。

3. 像素格式详解 (ImageInfo)

3.1 ColorType:色彩空间布局

描述了比特位如何映射到物理颜色通道:

格式每像素字节典型应用场景
RGBA_88884 字节跨平台最通用的标准格式。
BGRA_88884 字节iOS/macOS 渲染管线的原生偏好格式。
RGB_5652 字节不透明图像的紧凑格式,内存占用低。
ALPHA_81 字节用于图像遮罩、字体、手写笔迹效果。
Gray_81 字节灰度图像,单通道亮度信息。
RGBA_F168 字节HDR (高动态范围) 支持,适合超高色彩精度的专业处理。
RGBA_10101024 字节10 位色彩通道 + 2 位 Alpha,适合宽色域显示。

3.2 AlphaType:透明度解释权

  • Opaque:完全不透明,忽略 Alpha 通道。
  • Unpremultiplied:R/G/B 分量独立。常用于保存图片文件。
  • Premultiplied (预乘模式 - 核心推荐):
    • 数学动机:在经典的 Porter-Duff 混合公式中,最终颜色计算为 Src + Dst * (1 - SrcA)。如果 Src 已经是预乘后的(RGB * A),则公式内少了一次除法运算。
    • 渲染优势:避免了在进行线性缩放或滤镜处理时,边缘处因 Alpha 值为 0 导致的黑色杂边问题。
    • 注意:在手动修改像素值时,必须确保 RGB 分量的值小于等于其对应的 Alpha 分量。

4. Bitmap ↔ Image 的互转机制

4.1 从 Bitmap 到 Image (包装)

这是将 CPU 侧处理好的像素提交给渲染管线的标准方式。

  • API:Image::MakeFrom(bitmap)。
  • 行为:创建一个不可变的包装器,两者共享底层像素内存。
  • 写时拷贝 (COW):为了保证 Image 的不可变性,如果生成的 Image 尚未销毁,而你又对原 Bitmap 调用了 lockPixels() 获取写权限或执行了写入操作,TGFX 会自动为 Bitmap 执行一次全量像素克隆(Clone)。

代码示例:

// 创建 Bitmap 并填充数据
Bitmap bitmap;
bitmap.allocPixels(200, 200);
// ... 对 bitmap 进行 CPU 绘图或算法处理 ...

// 包装为 Image 供 Canvas 绘制
auto image = Image::MakeFrom(bitmap);
canvas->drawImage(image, 0, 0);

4.2 从 Image 到 Bitmap (回读)

由于 Image 可能是由文件、Picture 或 GPU 纹理组成的,它不一定直接持有 CPU 像素。因此,通常需要通过 Surface 进行中转回读。

代码示例:

// 1. 创建一个与 Image 同尺寸的 Surface
auto surface = Surface::Make(context, image->width(), image->height());
auto canvas = surface->getCanvas();

// 2. 将 Image 绘制到 Surface 上
canvas->drawImage(image, 0, 0);
context->flushAndSubmit(true); // 确保 GPU 渲染完成

// 3. 准备目标 Bitmap 并回读像素
Bitmap bitmap;
bitmap.allocPixels(image->width(), image->height());
surface->readPixels(bitmap.info(), bitmap.lockPixels());
bitmap.unlockPixels();

5. 高性能异步像素回读

在高性能应用中,从 GPU 获取像素数据(Readback)通常是性能瓶颈。传统的同步读取会强制 CPU 等待 GPU 完成所有待处理任务并同步内存,导致显著的卡顿。

5.1 协作链路:Surface::asyncReadPixels()

为了解决同步阻塞问题,TGFX 提供了异步回读机制。该机制允许 CPU 发起指令后立即继续执行其他任务,让 GPU 在后台异步搬运像素。

协作链路: asyncReadPixels (发起请求) → SurfaceReadback (状态持有) → isReady (非阻塞轮询) → lockPixels (访问数据)

  1. 发起请求:调用 surface->asyncReadPixels(rect)。此操作不会立即读取像素,而是向渲染管线提交一个下载任务,并返回一个 SurfaceReadback 对象。
  2. GPU 搬运 (GPU → CPU):GPU 在空闲时会将指定的矩形区域像素从显存下载到 CPU 可见的映射缓冲区(或 PBO)中。
  3. 非阻塞查询:通过 readback->isReady(context) 检查数据是否已就绪。这通常在主循环或后续帧中进行,不会阻塞当前线程。
  4. 结果获取:调用 readback->lockPixels() 获取像素指针。
    • 注意:如果调用时 isReady() 为 false,此方法会退化为同步阻塞,直到数据同步完成。

5.2 同步与异步对比

特性readPixels() (同步)asyncReadPixels() (异步)
CPU 行为立即阻塞,直到 GPU 任务全部完成。立即返回,继续执行后续 CPU 代码。
渲染流水线强制冲刷(Stall),破坏 GPU 并行性。与 GPU 渲染任务并行执行。
延迟 (Latency)较低(立即获取当前帧结果)。较高(通常需 1-2 帧后获取结果)。
吞吐量 (Throughput)低。极高,支持高频连续抓帧。

5.3 适用场景

  • 截图导出 (One-off Export): 如果用户点击“保存到相册”,且此时没有后续动画任务,使用同步 readPixels() 逻辑更简单。
  • 录屏帧捕获 (Screen Recording): 在 60fps 录屏场景下,必须使用 asyncReadPixels()。通过“在第 N 帧发起请求,第 N+2 帧提取数据”的策略,可以完全消除回读带来的掉帧。
  • 实时算法分析 (Video Analysis): 当需要对渲染结果进行实时人脸识别或滤镜分析时,异步读取能保证 UI 交互的流畅性。

6. 代码实战:像素手动处理

以下示例展示了从 Surface 异步读取像素,并利用 Pixmap 视图进行反色处理的完整逻辑:

void ProcessPixels(Surface* surface) {
    // 1. 发起异步请求
    auto readback = surface->asyncReadPixels(Rect::MakeWH(100, 100));
    surface->getContext()->flushAndSubmit();

    // 2. 开发者可以在此处做其他 CPU 工作...

    // 3. 准备 Bitmap 容器
    Bitmap bitmap;
    bitmap.allocPixels(100, 100);

    // 4. 提取结果(带懒阻塞逻辑)
    const void* gpuPixels = readback->lockPixels(surface->getContext());
    if (gpuPixels) {
        // 利用 Pixmap 视图工具进行内存写入
        bitmap.writePixels(readback->info(), gpuPixels);
        
        // 5. 手动遍历像素(反色算法)
        uint32_t* row = static_cast<uint32_t*>(bitmap.lockPixels());
        size_t rb = bitmap.rowBytes();
        for (int y = 0; y < 100; ++y) {
            for (int x = 0; x < 100; ++x) {
                row[x] ^= 0x00FFFFFF; // 修改 RGB 通道
            }
            row = (uint32_t*)((uint8_t*)row + rb);
        }
        bitmap.unlockPixels();
    }
    
    // 6. 安全清理
    readback->unlockPixels(surface->getContext());
}

渲染结果对比

原始图像 (Original)处理后图像 (Inverted)
Bitmap OriginalBitmap Result

7. 最佳实践与性能提示

  • 步进安全提示:在手动遍历像素时,严禁使用 y * width * bpp。必须使用 rowBytes(),因为不同系统可能对内存行进行字节对齐(Alignment),rowBytes 会比 width * bytesPerPixel 大。
  • HardwareBuffer 同步代价:开启 tryHardware 的 Bitmap 在 CPU 访问时,底层需要清理 L1/L2 缓存以确保数据一致性。如果是纯 CPU 处理算法,不涉及 GPU 共享,建议关闭 tryHardware。
  • 预乘混合:如果你的算法涉及到 Alpha 调整,请始终在非预乘(Unpremultiplied)下计算,完成后再转回预乘模式绘制。
← Image图像编解码 →
  • 1. Bitmap 内存管理
    • 1.1 分配策略 (allocPixels)
    • 1.2 物理载体 PixelRef 与引用计数
    • 1.3 HardwareBuffer 深度解析
  • 2. Pixmap:轻量级逻辑视图
    • 2.1 结构与职责
    • 2.2 视图协作
  • 3. 像素格式详解 (ImageInfo)
    • 3.1 ColorType:色彩空间布局
    • 3.2 AlphaType:透明度解释权
  • 4. Bitmap ↔ Image 的互转机制
    • 4.1 从 Bitmap 到 Image (包装)
    • 4.2 从 Image 到 Bitmap (回读)
  • 5. 高性能异步像素回读
    • 5.1 协作链路:Surface::asyncReadPixels()
    • 5.2 同步与异步对比
    • 5.3 适用场景
  • 6. 代码实战:像素手动处理
    • 渲染结果对比
  • 7. 最佳实践与性能提示
公司地址:广东省深圳市南山区海天二路33号腾讯滨海大厦Copyright © 2018 - 2026 Tencent. All Rights Reserved.联系电话:0755-86013388隐私政策