阶段一: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
的内部存储中。首先使用
QQuickTextEngine::setCurrentLine
创建一行关联的一颗二叉排序树 (按照 文本块 的 x 坐标排序)。然后使用
QQuickTextEngine::addGlyphsForRanges
创建该行的所有文本块,加入二叉树中。遍历到下一行之前,会使用
QQuickTextEngine::processCurrentLine
(图中没画)将树中的所有文本块、格式块各加入 Engine 维护的一个列表中,然后重建下一行的二叉树。该函数将该二叉树进行中序遍历(in-order traverse,即按照 x 从小到大的顺序遍历),加入 文本块列表 (QVector
m_processedNodes) 中。 中续遍历的过程中,按照相邻文本块的格式(decoration,即下划线、上划线、删除线),生成 格式块,放入 格式块列表 (QList
QQuickTextEngine::m_lines) 中。
TODO: 维护二叉排序树的目的是??有待研究。
按行遍历结束后,调用
addToSceneGraph()
,遍历 文本块/格式块列表,创建它们的 QSG Node。使用
QQuickTextNode::addGlyphs
创建文本块的 QSG Node。文本块默认创建的 QSG Node 是 QSGDistanceFieldGlyghNode(继承了 QSGGlyphNode)。
创建 QSG Node 后,使用
QSGGlyphNode::setGlyphs
将字形 index 记入 QSG Node 中。在 setGlyphs 过程中,会调用
QSGDistanceFieldGlyghNode::populate
将字形从字体文件中 按需 读入并转换为 QPainterPath,然后存入缓存(之前读过的就不读了,只读新的),这个过程详见下一个板块。
使用
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 的过程
QQuickWindowPrivate::renderSceneGraph
- 渲染线程开始渲染 Scene Graph。上述函数会调用:
QSGDefaultRenderContext::renderNextFrame
,其中又调用了QSGRenderer::renderScene
。真正 render 之前,会调用 QSGRenderer::preprocess。QSGRenderer::preprocess
中又调用了QSGDefaultRenderContext::preprocess
QSGDefaultRenderContext::preprocess
函数中,会遍历所有 m_glyphCaches,对 Glyph Cache 进行按需更新(调用的是QSGDistanceFieldGlyphCache::update
)QSGDistanceFieldGlyphCache::update
函数中,将创建文本的 Texture。每个需要创建 Texture 的 Glyph,使用
QDistanceField
从 QPainterPath 创建 Texture。QDistanceField
用于高质量绘制 Distant Field Texture,广泛用于 Qt 框架中文本等基于 QPainterPath 的矢量图形的渲染。QDistanceField::makeDistanceField
是最终在内存上绘制 QPainterPath 的过程。
上述绘制 Texture 是在 CPU 中进行的。在 CPU 生成 Texture 后,调用
QSGOpenGLDistanceFieldGlyphCache::storeGlyphs
,将 Texture 通过下列 OpenGL 函数进行绑定:glBindTexture
glTexSubImage2D
glPixelStorei