阶段一:Scene Graph 更新

此阶段进行 SceneGraph 更新 PaintNode 的流程

  • QSGRenderThread::sync 之后,调用 QQuickWindowPrivate::syncSceneGraph

  • QQuickWindowPrivate::syncSceneGraph 将更新 QQuickText 下的 QQuickTextNode 文本节点。如果文本节点不存在,会创建一个。

  • QQuickTextNode 创建后,或者更新 QQuickTextNode 脏节点时,对于 textFormat == Text.RichTextext,则会调用 addTextLayout(),添加一个绘制区域。

  • addTextLayout() 的过程中,将为待绘制的文字 按需 建立字形 (Glyph) 缓存。具体做法是:

    • 创建一个 QQuickTextEngine(用来生成该行中所有 文本块、格式块 的 QSG Node);

    • 使用 QQuickTextEngine::mergeFormats 分辨出 所有文本块;文本块 指的是 位置紧邻、格式一致文本 构成的一块。

      • 例如:【今天天气真好】会被按格式分为 3 个文本块 —— 黑字文本块 “今天”、绿底色的文本块 “天气”,黑字文本块 “真好”。

      • 每个文本块会与一个 QGlyphRun 进行关联;QGlyphRun 的职责是:将 一块 文字映射为 glyph index 列表;并且指导字形轮廓的按需载入。

    • 然后按行遍历整个 Text 控件,将 文本块、格式块 加入 QQuickTextEngine 的内部存储中。

      1. 首先使用 QQuickTextEngine::setCurrentLine 创建一行关联的一颗二叉排序树 (按照 文本块 的 x 坐标排序)。

      2. 然后使用 QQuickTextEngine::addGlyphsForRanges 创建该行的所有文本块,加入二叉树中。

      3. 遍历到下一行之前,会使用 QQuickTextEngine::processCurrentLine(图中没画)将树中的所有文本块、格式块各加入 Engine 维护的一个列表中,然后重建下一行的二叉树。

        • 该函数将该二叉树进行中序遍历(in-order traverse,即按照 x 从小到大的顺序遍历),加入 文本块列表 (QVector m_processedNodes) 中。

        • 中续遍历的过程中,按照相邻文本块的格式(decoration,即下划线、上划线、删除线),生成 格式块,放入 格式块列表 (QList QQuickTextEngine::m_lines) 中。

      4. TODO: 维护二叉排序树的目的是??有待研究

    • 按行遍历结束后,调用 addToSceneGraph(),遍历 文本块/格式块列表,创建它们的 QSG Node。

      1. 使用 QQuickTextNode::addGlyphs 创建文本块的 QSG Node。

        • 文本块默认创建的 QSG Node 是 QSGDistanceFieldGlyghNode(继承了 QSGGlyphNode)。

        • 创建 QSG Node 后,使用 QSGGlyphNode::setGlyphs 将字形 index 记入 QSG Node 中。

        • 在 setGlyphs 过程中,会调用 QSGDistanceFieldGlyghNode::populate 将字形从字体文件中 按需 读入并转换为 QPainterPath,然后存入缓存(之前读过的就不读了,只读新的),这个过程详见下一个板块。

      2. 使用 QQuickTextNode::addRectangleNode 创建下划线、上划线等格式块的 QSG Node。

        • 格式块默认的 QSG Node 是 QSGRectangleNode
    • addToSceneGraph 后,整个 QQuickText 的 update paint node 的流程结束。

FreeType 矢量读入

前面提到,在 setGlyphs 过程中,会调用 QSGDistanceFieldGlyghNode::populate 将字形从字体文件中 按需 读入并转换为 QPainterPath,然后存入缓存。

以下是查找 1 个 Glyph,并放入缓存中的流程。

  • 读字形是按需读入的,也就是说,之前读过的就不读了,只读新的。有一个全局的 lookup table(QSGDistanceFieldGlyphCache) 来做这件事。

  • 对于之前没有读入的字形,将调用 QSGDistanceFieldGlyphCache::glyphData(glyphIndex) 获取和转换字形的 QPainterPath。

    • 获取字形调用的是 QFontEngine::addGlyphsToPath。在 GNU Linux/Wayland 环境下,这个接口的实现在 QFontEngineFT 类中,它封装了 FreeType 的接口来获取字形的矢量路径,然后调用 QFreetypeFace 的工具接口将 FreeType 的矢量路径转换为 QPainterPath。

    • 获取到 QPainterPath 后,使用 QTransform 进行倍率的缩放。缩放完毕后,Glyph 查找的过程就结束了。

阶段二:绘制上屏

文本及格式作为 QSG Node,会随着 Scene Graph 的渲染而一起上屏。以下是将 QPainterPath 转为 Image 的过程

  1. QQuickWindowPrivate::renderSceneGraph - 渲染线程开始渲染 Scene Graph。

  2. 上述函数会调用:QSGDefaultRenderContext::renderNextFrame,其中又调用了 QSGRenderer::renderScene。真正 render 之前,会调用 QSGRenderer::preprocess。

  3. QSGRenderer::preprocess 中又调用了 QSGDefaultRenderContext::preprocess

  4. QSGDefaultRenderContext::preprocess 函数中,会遍历所有 m_glyphCaches,对 Glyph Cache 进行按需更新(调用的是 QSGDistanceFieldGlyphCache::update

  5. QSGDistanceFieldGlyphCache::update 函数中,将创建文本的 Texture

    1. 每个需要创建 Texture 的 Glyph,使用 QDistanceField 从 QPainterPath 创建 Texture。

    2. QDistanceField 用于高质量绘制 Distant Field Texture,广泛用于 Qt 框架中文本等基于 QPainterPath 的矢量图形的渲染。

    3. QDistanceField::makeDistanceField 是最终在内存上绘制 QPainterPath 的过程

  6. 上述绘制 Texture 是在 CPU 中进行的。在 CPU 生成 Texture 后,调用 QSGOpenGLDistanceFieldGlyphCache::storeGlyphs,将 Texture 通过下列 OpenGL 函数进行绑定:

    1. glBindTexture

    2. glTexSubImage2D

    3. glPixelStorei