Qt Quick 渲染之 Scene Graph 详解

作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

简介

在 Qt widget 中的渲染方式是传统的命令式绘图系统,使用 QPainter 依次为每个界面元素渲染,不断地重复渲染状态。而在 Qt Quick 中修改了界面的渲染方式,自 Qt Quick 2 起统一使用 OpenGL ES 2.0 或 OpenGL 2.0 来渲染界面。这样做的好处是,所有要渲染的界面元素均在最后统一提供给 OpenGL,极大减少状态切换时间和渲染时间。就像写文件,把要写的内容存在缓存后统一写入文件,会比每次写入都重复打开关闭文件要快很多

Scene Graph 场景图与 Qt Quick 2.0 紧密相关,不能单独使用,场景图由 QQuickWindow 类管理和渲染。如果想在 c++ 中把自定义的 Item 添加到场景中,那么就需要继承 QQuickItem 并调用 setFlag(ItemHasContents, true);QQuickItem::updatePaintNode()。本文主要介绍场景图的使用和一些基础元素,这里会涉及到一些图形学的基本概念,例如:材质、纹理、光栅化、图元等,建议参考相关资料,这里不做深入解释

场景结构

场景图由许多预定义的节点类型组成,每种类型都有专门的用途。尽管我们将其称为场景图,但其本质是由继承于 QQuickItem 的类型的节点组成的节点树。该树根据场景中的 QQuickItem 类型构建,然后在内部对该场景进行渲染,最终呈现该场景。只要用户使用 QML 的 Item 及其子类来构建界面,QQuickWindow 就获得场景树,会维护这个场景树,并最终回收资源。场景树对于只使用 QML 和 Javascript 的开发人员来说是完全透明的

节点

类名 描述
QSGNode 场景中所有节点的基类
QSGGeometryNode 表达几何信息(形状、材质)的节点
QSGClipNode 表达裁剪信息的节点
QSGOpacityNode 表达透明信息的节点
QSGTransformNode 表达矩阵操作信息(旋转、平移、缩放)的节点

用户最常用的场景结点是 QSGGeometryNode。它可以实现自定义图形中的几何形体和光学材质。使用 QSGGeometry 可以定义几何坐标,并描述形状或者图元网格。材质定义了像素被如何填充

更多详情请参考官方文档:Scene Graph - Custom Geometry

预处理

节点具有虚函数QSGNode::preprocess(),该函数将在渲染之前被调用。节点子类可以设置标志QSGNode::UsePreprocess并重写QSGNode::preprocess()函数以对其节点进行预处理,为渲染做准备

节点所有权

节点的所有权归创建者,或者设置标志QSGNode::OwnedByParent后归场景图。通常我们会将所有权分配给场景图,因为这样开发人员可以不必关心资源回收的问题

材质

材质描述如何填充 QSGGeometryNode 中几何图形的内部。它封装了一段 OpenGL 着色代码 (Shader Program),提供了足够的灵活性让开发人员决定如何着色。仅管目前大多数 Item 对象使用了非常基本的材质,例如纯色和纹理填充。如果开发人员想修改材质,在QML代码中使用 ShaderEffect 就可以实现一些特定效果。更高级地需使用C++代码

类名 描述
QSGFlatColorMaterial 单色材质类
QSGMaterial 保存着色程序状态的类
QSGMaterialShader OpenGL着色代码类
QSGMaterialType 和QSGMaterial配合使用时的唯一标识符
QSGOpaqueTextureMaterial 不透明纹理材质类
QSGSimpleMaterial 和QSGSimpleMateralShader配合使用时的着色状态保存类的模板类
QSGSimpleMaterialShader 场景着色程序的基类
QSGTextureMaterial 纹理材质类
QSGVertexColorMaterial 顶点材质类

更多详情请参考官方文档:Scene Graph - Simple Material

便捷的节点

场景图 API 是一套偏底层的接口,专注于性能而不是易用性。从头开始编写自定义的几何图形和材质,即使是最基本的几何图形和材质,也需要大量的代码。因此场景图 API 包含了一些节点类,使得常用的自定义节点可以快速开发

类名 描述
QSGSimpleRectNode 继承于QSGGeometryNode,定义了一个被赋予单色材质的矩形图形的节点类
QSGSimpleTextureNode 继承于QSGGeometryNode,定义了一个被赋予纹理材质的矩形图形的节点类

场景图渲染

场景图的渲染发生在 QQuickWindow 类的内部,外部无法访问。但在渲染管线上有几个点可以供开发人员插入自定义节点代码或直接访问OpenGL

更多详情请参考官方文档:Qt Quick Scene Graph Default Renderer

渲染循环

场景渲染有三种方式:基本渲染循环(basic),窗口渲染循环(windows) 和线程渲染循环(threaded)。其中 basic 和 windows 是单线程,而 threaded 是指定线程内渲染。Qt会根据情况自动选择使用哪种方式。当性能不满足,或者出于测试考虑时,可以强制启动QSG_RENDER_LOOP。想知道具体是哪种方式,需要在启动应用时添加参数将QSG_INFO设置为1。要验证使用哪个渲染循环,请启用qt.scenegraph.general日志类别

注意: windows 和 threaded 方式非常依赖于 OpenGL 将交换间隔设定为1。一些显卡驱动允许用户覆盖或关闭这个值,并忽略Qt的修改请求。但如果没有这个设置,会导致交换间隔太短,CPU满负荷运转。如果知道系统不能自动调整vsync-based,请手动设置QSG_RENDER_LOOP=basic来启动 basic 渲染方式

使用 QQuickRenderControl 自定义渲染控制

如果不想使用系统渲染,开发人员可以使用QQuickRenderControl来完全控制渲染过程。此时,美化、同步和渲染都由应用来控制

混合使用场景和OpenGL

场景提供两种方式来整合 OpenGL 内容:直接使用 OpenGL 代码或者直接在场景中添加纹理节点
注意:不管理采用哪种方式,都要保存使用同一个OpenGL环境,否则将产生未知错误
注意:渲染代码要做到线程安全,因为渲染线程很可能不在主(GUI)线程中

直接使用OpenGL代码

通过槽连接QQuickWindow::beforeRendering()QQuickWindow::afterRendering(),让 OpenGL 在场景渲染前或后来执行 OpenGL 代码。其结果就是 OpenGL 内容在场景内容下(被覆盖)或者在其上。这种方式的优点是不需额外使用帧缓存或者内存,缺点是 OpenGL 渲染时机固定。这种方式不能手动 update 元素内容,只能通过在这个元素区域内有其他元素发生变化(比如在QML中调用这个元素区域内一个不可见元素的 visible 属性变一变)才会更新,这是其一大缺点

更多详情请参考官方文档:Scene Graph - OpenGL Under QML

向场景添加纹理节点

这种方式需借助于QQuickFramebufferObject类,场景会将此节点添加到其中。只要给当前元素设置QQuickItem::setFlag(ItemHasContents),Scene Graph 就会调用 updatePainedNode更新这个元素内容。这种方式与Scene Graph的结合性最好

更多详情请参考官方示例:Scene Graph - Rendering FBOs in a thread

日志

类名 描述
qt.scenegraph.time.texture 纹理上传的耗时
qt.scenegraph.time.compilation 编译着色器耗时
qt.scenegraph.time.renderer 渲染器不同步骤耗时
qt.scenegraph.time.renderloop 渲染循环不同阶段耗时
qt.scenegraph.time.glyph 准备字形的距离场耗时
qt.scenegraph.info 场景图和图形栈中的常规信息
qt.scenegraph.renderloop 渲染循环相关的信息。这个日志模式是Qt开发者主要使用的

猜你喜欢

转载自blog.csdn.net/qq_34139994/article/details/119990800