Skia字体渲染核心:drawGlyphs详解

文章摘要

Skia 的 drawGlyphs 是字体渲染的核心环节,负责将 Minikin/Harfbuzz 布局后的字形转换为像素并绘制到画布。其主要流程包括:接收字形信息(glyph id、位置、字体),通过 SkCanvas::drawGlyphs 构造 SkGlyphRun,再由 SkDraw 进行栅格化(获取字形轮廓或位图,计算位置,最终绘制)。Skia 利用字形缓存(SkGlyphCache)优化性能,支持 CPU/GPU 渲染、抗锯齿等效果,并通过 SkFont/SkTypeface 与 Minikin 实现数据衔接。整个过程高效整合了文本布局与图形渲染的关键功能。

Skia 的 drawGlyphs 实现是 Skia 字体渲染管线的核心环节之一。它负责将 Minikin/Harfbuzz 布局好的字形(glyph id)、位置、字体等信息,最终栅格化并绘制到画布上。下面详细介绍 Skia drawGlyphs 的实现原理、关键流程、源码入口和与 Minikin 的衔接。


1. Skia 字体渲染基本流程

  1. 接收字形信息
    Minikin/Harfbuzz 负责文本分段、字形选择、字形定位,输出 glyph id、字体、位置等信息。
  2. Skia 负责绘制
    Skia 接收这些信息,通过 drawGlyphs 或相关 API,将字形渲染到 SkCanvas 上。

2. drawGlyphs 的典型调用链

2.1 Java 层

  • Canvas.drawText()TextView 等最终会走到 native 层的 SkCanvas::drawGlyphs

2.2 Native 层

  • MinikinUtils::doLayout() 调用 Minikin 布局,获得 glyph id、位置、字体。
  • 通过 JNI/NDK 传递到 Skia。

2.3 Skia 层

  • SkCanvas::drawGlyphs() 是核心入口。
  • 其参数包括:
    • glyphs(字形 id 数组)
    • count(字形数量)
    • positions(每个字形的坐标)
    • font(SkFont,封装 SkTypeface、字号、hinting、抗锯齿等)
    • paint(SkPaint,封装颜色、样式等)

3. Skia drawGlyphs 实现细节

3.1 关键源码入口

3.2 主要流程

  1. SkCanvas::drawGlyphs

    void SkCanvas::drawGlyphs(int count, const uint16_t glyphs[], const SkPoint positions[],
                              const SkFont& font, const SkPaint& paint) {
        // 构造 SkGlyphRun
        SkGlyphRun glyphRun(font, SkSpan<const uint16_t>(glyphs, count), SkSpan<const SkPoint>(positions, count));
        // 构造 SkGlyphRunList
        SkGlyphRunList glyphRunList(glyphRun, ...);
        // 交给 SkDraw 处理
        this->onDrawGlyphRunList(glyphRunList, paint);
    }
    
  2. SkDraw::drawGlyphRunList

    • 遍历每个 glyph,查找字体缓存(SkGlyphCache),获得字形轮廓或位图。
    • 计算每个字形的实际绘制位置。
    • 根据 paint 设置(抗锯齿、阴影、描边等)进行栅格化。
    • 绘制到目标 surface(CPU bitmap 或 GPU 纹理)。
  3. 字形栅格化与缓存

    • Skia 会缓存常用字形的位图(glyph cache),提升性能。
    • 支持 subpixel positioning、LCD 渲染、hinting 等。
  4. 硬件加速

    • 如果开启 GPU 加速,Skia 会将字形数据上传到 GPU,使用 OpenGL/Vulkan 渲染。

4. Skia drawGlyphs 与 Minikin 的衔接

  • Minikin 负责文本到 glyph 的 mapping 和布局,Skia 只负责“画”。
  • Minikin 通过 MinikinFontSkia 提供 SkTypeface 给 Skia。
  • Skia 通过 SkFont/SkTypeface 访问字体文件,获得字形轮廓。

5. 伪代码流程

// 1. Minikin 布局
std::vector<uint16_t> glyphs;
std::vector<SkPoint> positions;
SkTypeface* typeface = ...; // MinikinFontSkia

// 2. Skia 绘制
SkFont font(typeface, textSize);
canvas->drawGlyphs(glyphs.size(), glyphs.data(), positions.data(), font, paint);

6. 相关源码片段

SkCanvas::drawGlyphs

void SkCanvas::drawGlyphs(int count, const uint16_t glyphs[], const SkPoint positions[],
                          const SkFont& font, const SkPaint& paint) {
    // 构造 SkGlyphRun
    SkGlyphRun glyphRun(font, SkSpan<const uint16_t>(glyphs, count), SkSpan<const SkPoint>(positions, count));
    SkGlyphRunList glyphRunList(glyphRun, ...);
    this->onDrawGlyphRunList(glyphRunList, paint);
}

SkDraw::drawGlyphRunList

void SkDraw::drawGlyphRunList(const SkGlyphRunList& glyphRunList, const SkPaint& paint) {
    for (const SkGlyphRun& run : glyphRunList) {
        // 查找字体缓存
        // 获取字形轮廓或位图
        // 计算位置
        // 绘制到 surface
    }
}

7. 总结

  • Minikin 负责文本到 glyph 的 mapping、字体选择、布局。
  • SkiadrawGlyphs 负责字形的栅格化和绘制,支持 CPU/GPU 渲染、缓存、抗锯齿等。
  • 两者通过 SkTypeface/SkFont/MinikinFontSkia 进行衔接。

8. 参考资料


下面详细介绍 Skia 的字形栅格化(rasterization)缓存机制(glyph cache)GPU 渲染细节,并结合源码和实际流程说明。


1. Skia 字形栅格化(Glyph Rasterization)

1.1 基本流程

  • 字形栅格化是指将字体的矢量轮廓(outline)转换为像素位图(bitmap)或距离场(SDF),以便在屏幕上显示。
  • Skia 支持 TrueType、OpenType 等字体格式,能处理各种 hinting、抗锯齿、subpixel 等效果。

1.2 主要步骤

  1. 获取字形轮廓

    • 通过 SkTypeface 读取字体文件,获得指定 glyph id 的矢量轮廓(如贝塞尔曲线)。
    • 相关接口:SkTypeface::getPath(glyphID, &SkPath)
  2. 栅格化为位图

    • Skia 使用自己的 rasterizer(如 SkScan)将矢量轮廓转换为像素。
    • 支持灰度、LCD(子像素)、SDF(距离场)等多种格式。
    • 可选 hinting、抗锯齿、subpixel positioning。
  3. 生成最终像素数据

    • 结果是一个 bitmap 或 alpha mask,供后续绘制。

1.3 相关源码


2. Skia 字形缓存机制(Glyph Cache)

2.1 作用

  • 字体渲染中,字形栅格化是高开销操作。Skia 通过字形缓存(glyph cache)避免重复栅格化,提高性能。

2.2 主要结构

  • SkGlyphCache:每种字体、字号、hinting、渲染参数组合对应一个缓存。
  • SkGlyph:缓存单个 glyph 的 bitmap、metrics、轮廓等。

2.3 工作流程

  1. 查找缓存
    • 绘制前,Skia 先查找 SkGlyphCache 是否已有目标 glyph 的位图。
  2. 命中缓存
    • 直接取出 bitmap,跳过栅格化。
  3. 未命中
    • 通过 SkScalerContext 进行栅格化,并将结果写入 SkGlyphCache。
  4. 缓存管理
    • Skia 会定期清理不常用的缓存,防止内存膨胀。

2.4 相关源码


3. Skia GPU 渲染细节

3.1 GPU 字体渲染的挑战

  • 字形数据复杂,上传 GPU 成本高。
  • 需要高效管理字形纹理,支持动态扩展、回收。

3.2 Skia 的 GPU 字体渲染方案

3.2.1 字形纹理图集(Atlas)

  • Skia 将常用字形的 bitmap 打包到一个或多个大纹理(atlas)中。
  • 每个 glyph 在 atlas 中有自己的位置和 UV 坐标。

3.2.2 渲染流程

  1. 字形栅格化
    • 首次遇到 glyph 时,CPU 端栅格化为 bitmap。
  2. 上传到 GPU
    • 将 bitmap 拷贝到 atlas 纹理的空闲区域。
  3. 记录 UV 坐标
    • 记录 glyph 在 atlas 中的偏移和大小。
  4. 批量绘制
    • 绘制时,使用一个四边形(quad)+ UV 坐标,批量渲染多个 glyph。
    • 支持 OpenGL/Vulkan/Metal 等后端。

3.2.3 SDF/Path Rendering

  • 对于大字号或特殊效果,Skia 也支持 SDF(Signed Distance Field)或直接用 GPU 路径渲染(如 SkiaPathRender)。

3.2.4 缓存与回收

  • Atlas 空间有限,Skia 会根据 LRU 等策略回收不常用的 glyph 区域。

3.3 相关源码


4. 伪代码流程

// 1. 查找/创建 SkGlyphCache
SkGlyphCache* cache = SkStrikeCache::FindOrCreateStrike(font, paint);

// 2. 查找 glyph
SkGlyph* glyph = cache->getGlyph(glyphID);

// 3. 如果没有 bitmap,栅格化
if (!glyph->hasImage()) {
    SkScalerContext* scaler = cache->getScalerContext();
    scaler->getImage(glyph);
}

// 4. 如果是 GPU 渲染,上传到 atlas
if (useGPU) {
    GrAtlas* atlas = getOrCreateAtlas();
    atlas->addGlyph(glyph->image());
}

// 5. 绘制
drawGlyphQuad(atlas, glyph->uv(), position);

5. 总结

  • 字形栅格化:Skia 支持多种字体格式和渲染模式,矢量转 bitmap。
  • 缓存机制:SkGlyphCache/SkStrikeCache 避免重复栅格化,提升性能。
  • GPU 渲染:通过 atlas 管理字形纹理,批量渲染,支持 SDF/Path 等高级效果。

6. 参考资料


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你一身傲骨怎能输

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值