教是言词, 实不是道,道本无言, 言说是妄。------- 达摩


Qt 5提出了一个新的渲染底层,以替代Qt4时期的Graphics View,这个渲染底层就是Scene Graph。Scene Graph主要利用OpenGL ( ES )2的渲染优势,在2D和3D以非常流畅的速度进行渲染,满足日益增长的界面效果需求,同时Scene Graph预留了各种各样的接口,满足大家定义显示和渲染效果的需要。

该文章下面部分,主要来自Qt官方的说明文档:http://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.html 并结合了自己的理解。

在 Qt Quick 2 中使用了基于OpenGL ES 2.0或OpenGL 2.0的场景图来进行渲染。使用场景图进行绘图而不是传统的绘图机制(QPainter类似的)可以在下一帧被渲染之前,提前计算出将要渲染帧的全部信息。这就为进行优化创造了条件,例如可以进行的优化有:通过分批渲染来减小状态的切换;渲染时直接丢弃被覆盖的元素。

例如,假设一个用户界面包含一个10个item,每个item包括一个背景颜色,一个图标,一个文本。使用传统的绘图方法,需要30次的绘制调用(比如调用30次opengl),导致很多的状态切换(例如opengl是基于状态机的,每次状态的切花都会有一定的开销)。 如果用场景图的方法的话,只需要3次绘制,一次绘制所有的背景,然后是所有的图标,然后是所有的文本,从而大大减少调用次数,大大提高性能。

Qt5的场景图与Qt Quick 2.0紧密相连,不能单独使用。 QQuickWindow 类负责对场景图进行管理并将其渲染出来(负责将场景图交给opengl)。通过调用QQuickItem::updatePaintNode()函数,用户可以将自定义的图元添加到场景图中。

假设你在qml文件中定义了一系列的item,这些你定义的item最终被通过场景图表征出来,场景图中包含了足够的信息,利用这些信息可以将你定义的界面表示出来。 一旦为qml文件中定义的items建立了场景图,这个场景图便可以独立于这些item存在了,然后,可以有一个专门的线程来基于场景图的信息将界面渲染到显示器上(有些平台上没有单独的渲染线程),在这个渲染线程进行渲染的同时,GUI线程可以准备下一帧的数据了。

注意: Much of the information listed on this page is specific to the default OpenGL adaptation of the Qt Quick Scene graph. For more information about the different scene graph adaptations see Scene Graph Adaptations.


下面先来看一下场景图的结构吧。

Qt Quick Scene Graph Structure

QtQuick场景图其实是一个树结构,包含很多个节点。这个树是从何而来呢?官方说法:

The tree is built from QQuickItem types in the QML scene and internally the scene is then processed by a renderer which draws the scene.(QquickItem是最基本的qml C++类型)

即: QtQuick场景图源自于你在qml文件中写的那些基本的qml类型,例如Rectangle,如果从qml引擎开始分析的话,大体过程我想应该是这样的:首先,qml引擎加载qml文件,它会为你定义的每个基本类型创建对应的C++类对象,例如,为Rectangle创建QQuickRectangle类对象 (https://blog.csdn.net/qq_35865125/article/details/85869276 ),这些被创建的对象之间应该是有层次关系的,最终,这些对象被转换成场景图,场景图的每个节点对应的C++类是QSGNode。

Qml引擎创建的c++对象们(例如QQuickRectangle类对象)是如何被转换成场景图中的一个个QSGNode类对象的? -- 是通过调用QSGNode* QQuickItem::updatePaintNode()来完成的!(QquickItem类是QQuickRectangle等类的基类)  QquickItem类通过该函数将自己转换成QSGNode类对象,应该是将自己的位置属性,颜色属性等等信息都转到其返回的QSGNode*里面吧,具体参考官方文档对QquickItem的说明:http://doc.qt.io/qt-5/qquickitem.html#updatePaintNode 。至于何时调用updatePaintNode,本文后面会介绍。

认识下QquickItem:

The QQuickItem class provides the most basic of all visual items in Qt Quick.

All visual items in Qt Quick inherit from QQuickItem. Although a QQuickItem instance has no visual appearance, it defines all the attributes that are common across visual items, such as x and y position, width and height, anchoring and key handling support.

You can subclass QQuickItem to provide your own custom visual item that inherits these features.

它应该是QQuickRectangle类的基类,注意:QQuickRectangle这种基本的qml类型对应的类是用户不可见的。

场景图被建立起来之后,后面会被送给渲染线程将其渲染到显示器上。

文档上说:“The nodes themselves do not contain any active drawing code nor virtual paint() function”,即:场景图中的节点对应的类并不包含绘制函数,所以才需要渲染线程来将场景图画出来吧。

另外,虽然场景图一般是根据基本的qml类型建立起来的,用户是可以向场景图中添加自定义类型的,也可以添加3D模型。我使用过的添加自定义类型的方式:定义自己的类,继承自QQuickItem,然后,向qml中注册这个类,并在qml文件中使用这个类,这在介绍QQuickItem类的文档中有提到。是否可以在场景图生成之后,向其中添加自己生成的QSGNode节点?

qt场景图的Nodes:

对用户来说最重要的节点是QSGGeometryNode。可以使用该类定义用户图形,例如 可以定义几何形状,材料。几何形状可以用QSGGeometry类来定义,其可以描述形状或曲面(mesh),形状可以是直线,矩形,多边形,或 许许多多的不相连的小三角形,或 复杂的三维曲面。

一个节点可以有任何数量的子节点:“A node can have any number of children and geometry nodes will be rendered so they appear in child-order with parents behind their children.”

注意: This does not say anything about the actual rendering order in the renderer. Only the visual output is guaranteed。

用户可用的节点类型:

注意:“Custom nodes are added to the scene graph by subclassing QQuickItem::updatePaintNode() and setting the QQuickItem::ItemHasContents flag.”---- 这句话在强调如果你自己定义了继承自QQuickItem类型的子类,别忘了设置QQuickItem::ItemHasContents标识,设置了标识之后updatePainNode函数才会被触发,具体见官方文档对updatePainNode函数的说明。(qml引擎首先生成一个对象树,该树的节点都是基本的qml类型或者你自己定义的QQuickItem子类, 然后在渲染线程中,opengl要渲染之前,需要触发QQuickItem::UpdatPainNode函数得到场景图树结构)。

警告: “It is crucial that OpenGL operations and interaction with the scene graph happens exclusively on the render thread, primarily during the updatePaintNode() call. The rule of thumb is to only use classes with the "QSG" prefix inside the QQuickItem::updatePaintNode() function.” --- OpenGL与qt场景图之间的交互只能发生在渲染线程中,两者的交互是通过QQuickItem::updatePaintNode()函数来实现的,一个重要的原则是只在该函数中使用QSG为前缀的类。

看来,所谓的用户自己向qt场景图中添加自定义节点的方式是这样的:首先,需要自定义一个继承自QquickItem的类,在这个类中重写QQuickItem::updatePaintNode()函数,在该函数中生成QSGNode类型的节点并返回,,那些不能被用户看到的QquickRectangle类应该也是如此吧

更多信息,请移步 Scene Graph - Custom Geometry.

场景图Node的Preprocessing

Nodes have a virtual QSGNode::preprocess() function, which will be called before the scene graph is rendered. Node subclasses can set the flag QSGNode::UsePreprocess and override the QSGNode::preprocess() function to do final preparation of their node. For example, dividing a bezier curve(贝塞尔曲线) into the correct level of detail for the current scale factor or updating a section of a texture. ---可以重载函数QSGNode::preprocess()做一些处理,若要此函数在场景被渲染之前被执行一下子,需要设置一个标识。

场景图Node的所有权(Node Ownership)

Ownership of the nodes is either done explicitly by the creator or by the scene graph by setting the flag QSGNode::OwnedByParent. Assigning ownership to the scene graph is often preferable as it simplifies cleanup when the scene graph lives outside the GUI thread.

---设置了这个标识后,该节点的父节点被自动设置吧,根据的是你在qml文件中定义的层级结构吧。

Materials

The material describes how the interior of a geometry in a QSGGeometryNode is filled. It encapsulates an OpenGL shader program and provides ample flexibility in what can be achieved, though most of the Qt Quick items themselves only use very basic materials, such as solid color and texture fills.

material描述了QSGGeometryNode的几何图形的内部该如何被填充。它封装了一个OpenGL的着色程序并且很灵活。尽管大部分的Qt Quick item自己只使用一些很基础的material,比如使用纯色填充或是文本填充。

对于那些只是想在QML基本类型中使用自定义描影的的用户,可以直接在QML里面使用ShaderEffect类型。

更多内容请看: Scene Graph - Simple Material

Convenience Nodes

The scene graph API is very low-level and focuses on performance rather than convenience. Writing custom geometries and materials from scratch, even the most basic ones, requires a non-trivial amount of code. For this reason, the API includes a few convenience classes to make the most common custom nodes readily available.

  • QSGSimpleRectNode - a QSGGeometryNode subclass which defines a rectangular geometry with a solid color material.
  • QSGSimpleTextureNode - a QSGGeometryNode subclass which defines a rectangular geometry with a texture material.


Scene Graph and Rendering

场景图的渲染过程发生在QQuickWindow类的内部,对于这个渲染过程,qt没有提供public类型的API。但是,QquickWindow类在渲染过程中,允许执行用户自定以的代码,例如,渲染进行到某个接断时,会发出一个信号,用户接收到信号之后,可以去执行一段代码,这段代码可以是”add custom scene graph content” 或者 “render raw OpenGL content”。

For detailed description of how the scene graph renderer for OpenGL works, see Qt Quick Scene Graph OpenGL Renderer.该文档主要将opengl的知识,有一定基础的人可以看。

对于渲染,有三个变量: basic, windows, 和 threaded。其中,basic和windows对应单线程, threaded对应的是在一个专门的线程中执行场景图的渲染。针对不同的平台和使用的显卡驱动,Qt试图选择一个合适的变量。可以使用QSG_RENDER_LOOP环境变量来强制使用一个指定变量。想要确定哪种方式正在被使用,可以“enable the qt.scenegraph.general logging category”。

注意: 有些平台上,不能设置swap interval的值,如果swap interval的值为0,会导致渲染线程过快地运行动画,即画面更新太快,导致100%地占用cpu, 在这种平台上需要在环境中设置QSG_RENDER_LOOP=basic,来使用basic变量。      关于opegl的swap interval: https://www.khronos.org/opengl/wiki/Swap_Interval

The term "swap interval" itself refers to the number of v-blanks that must occur before the front and back frame buffers are swapped. A swap interval of 1 tells the GPU to wait for one v-blank before swapping the front and back buffers. A swap interval of 0 specifies that the GPU should never wait for v-blanks, thus performing buffer swaps as soon as possible when rendering for a frame is finished. Video drivers can override these values, forcing a swap interval of 1 or 0 depending on settings the user provided in the video card's control panel.

Threaded Render Loop ("threaded")—单独一个渲染线程

很多情况下,场景图的渲染发生在一个单独的线程里面,这可以增加多核系统的并行度,例如在渲染线程在swap interval时,影响不到其他线程的执行。这大大提高了性能,但是对gui线程和渲染线程之间的交互增加了一些限制。

The following is a simple outline of how a frame gets composed with the threaded render loop:

http://doc.qt.io/qt-5/images/sg-renderloop-threaded.jpg

  1. QML画面场景发生变化后(例如,动画,用户输入了东西),导致QQuickItem::update()被调用然后,gui线程会发送一个信号线程给渲染线程,要求它新搞一个frame。  ---(如果qml画面场景中有很多个item,只有一个item发生变化后,应该不会导致所有的item对应的QQuickItem::update()被调用)
  2. The render thread prepares to draw a new frame and makes the OpenGL context current and initiates a block on the GUI thread:: 渲染线程开始准备画一个新的frame, 设置opengl环境,并请求让gui线程阻塞。
  3. While the render thread is preparing the new frame, the GUI thread calls QQuickItem::updatePolish() to do final touch-up of items before they are rendered.::当渲染线程准备新的frame时,gui线程调用QQuickItem::updatePolish()来对各个item做最后的润色。
  4. GUI thread is blocked. ::gui线程被阻塞
  5. The QQuickWindow::beforeSynchronizing() signal is emitted. Applications can make direct connections (using Qt::DirectConnection) to this signal to do any preparation required before calls to QQuickItem::updatePaintNode(). ::渲染线程发出QQuickWindow::beforeSynchronizing()信号,用户可以在其他线程接受这个信号,在QQuickItem::updatePaintNode()被调用之前做一些事情。注意:connect该信号的方式必须是Qt::DirectConnection,以保证槽函数在渲染线程中被执行,以保证顺序。
  6. Synchronization of the QML state into the scene graph. This is done by calling the QQuickItem::updatePaintNode() function on all items that have changed since the previous frame. This is the only time the QML items and the nodes in the scene graph interact.::根据qml场景生成qt场景图,这是通过调用发生状态变化的qml item调用其QQuickItem::updatePaintNode()来实现的。这是唯一的qml items与qt场景图进行交互的地方。
  7. GUI thread block is released.::gui线程的阻塞停止,继续运行。
  8. The scene graph is rendered:
    1. The QQuickWindow::beforeRendering() signal is emitted. Applications can make direct connections (using Qt::DirectConnection) to this signal to use custom OpenGL calls which will then stack visually beneath the QML scene.  场景图开始被渲染之前,QquickWindow类会发出beforeRendering信号,用户可以接受信号,让渲染线程执行一段代码,例如可以调用OpenGl函数进行绘图,这时绘制的opengl图将会被后面生成的场景图对应的画面覆盖哦。
    2. Items that have specified QSGNode::UsePreprocess, will have their QSGNode::preprocess() function invoked. ::对一些场景图中的节点,触发QSGNode::preprocess()函数。
    3. The renderer processes the nodes and calls OpenGL functions.::渲染线程根据场景图中节点信息调用opengl函数,将画面搞出来!
    4. The QQuickWindow::afterRendering() signal is emitted. Applications can make direct connections (using Qt::DirectConnection) to this signal to use custom OpenGL calls which will then stack visually over the QML scene. ::场景图始被渲染完后,QquickWindow类会发出afterRendering信号,用户可以接受信号,让渲染线程执行一段代码,例如可以调用OpenGl函数进行绘图,这时绘制的opengl图将会覆盖后面生成的场景图对应的画面哦。
    5. The rendered frame is swapped and QQuickWindow::frameSwapped() is emitted.::渲染线程swap frame,发出frameSwapped信号。
  9. While the render thread is rendering, the GUI is free to advance animations, process events, etc.::gui线程和渲染线程继续高歌猛进。

在以下情况下,对于渲染,默认使用threaded变量:Windows with opengl32.dll, Linux with non-Mesa based drivers, macOS, mobile platforms, and Embedded Linux with EGLFS。也可以在环境中设置QSG_RENDER_LOOP=threaded来强制使用该变量。
Non-threaded Render Loops ("basic" and "windows") – gui线程内渲染

理解了上面的内容,对于gui线程内渲染的情况就容易理解了:

The non-threaded render loop is currently used by default on Windows with ANGLE or a non-default opengl32 implementation and Linux with Mesa drivers. For the latter this is mostly a precautionary measure, as not all combinations of OpenGL drivers and windowing systems have been tested. At the same time implementations like ANGLE or Mesa llvmpipe are not able to function properly with threaded rendering at all so not using threaded rendering is essential for these.

By default windows is used for non-threaded rendering on Windows with ANGLE, while basic is used for all other platforms when non-threaded rendering is needed.

Even when using the non-threaded render loop, you should write your code as if you are using the threaded renderer, as failing to do so will make the code non-portable.

The following is a simplified illustration of the frame rendering sequence in the non-threaded renderer.

http://doc.qt.io/qt-5/images/sg-renderloop-singlethreaded.jpg

Custom control over rendering with QquickRenderControl—使用QquickRenderControl来自己控制渲染过程

When using QQuickRenderControl, the responsibility for driving the rendering loop is transferred to the application. In this case no built-in render loop is used. Instead, it is up to the application to invoke the polish, synchronize and rendering steps at the appropriate time. It is possible to implement either a threaded or non-threaded behavior similar to the ones shown above.:: 使用该类时,驱动渲染线程的任务由qt用户自己完成。

Ref:

https://www.tuicool.com/articles/7jaYzq

https://blog.csdn.net/gamesdev/article/details/43063219

https://blog.csdn.net/gamesdev/article/details/43067265

https://blog.csdn.net/lainegates/article/details/50890551

转载于:https://www.cnblogs.com/butterflybay/p/10347854.html

Qt5 QtQuick系列----QtQuick的Secne Graph剖析(1)相关推荐

  1. NetworkX系列教程(1)-创建graph

    小书匠Graph图论 研究中经常涉及到图论的相关知识,而且常常面对某些术语时,根本不知道在说什么.前不久接触了NetworkX这个graph处理工具,发现这个工具已经解决绝大部分的图论问题(也许只是我 ...

  2. 【java集合框架源码剖析系列】java源码剖析之ArrayList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 本博客将从源码角度带领大家学习关于ArrayList的知识. 一ArrayList类的定义: public class Arr ...

  3. Java多线程系列(十):源码剖析AQS的实现原理

    在并发编程领域,AQS号称是并发同步组件的基石,很多并发同步组件都是基于AQS实现,所以想掌握好高并发编程,你需要掌握好AQS. 本篇主要通过对AQS的实现原理.数据模型.资源共享方式.获取锁的过程, ...

  4. 面试系列--自我了解和自我剖析

    考察应聘者能否客观地进行自我了解和自我剖析 (一) 你今年多大了? 问题分析: 这类问题至为关键的是要针对问题简洁明了的回答,不可拖泥带水,也不必再加什么说明. 完全不必再画蛇添足的说"我属 ...

  5. 【java集合框架源码剖析系列】java源码剖析之java集合中的折半插入排序算法

    注:关于排序算法,博主写过[数据结构排序算法系列]数据结构八大排序算法,基本上把所有的排序算法都详细的讲解过,而之所以单独将java集合中的排序算法拿出来讲解,是因为在阿里巴巴内推面试的时候面试官问过 ...

  6. Python爬虫之Scrapy框架系列(16)——深入剖析request和response类

    目录: Request和Response类: 1. 深入剖析Request类: 利用request.meta传递参数 拓展一:FormRequest类 2. 深入剖析Response类: Reques ...

  7. 【NC】NC6系列金额计算处理逻辑剖析

    对于ERP系统来说,管控的是整个企业的业务和财务数据,实际生产和运行过程中,除了尾差外,我们不允许出现任何形式的的数据运算错误,所以也就对系统的计算提出了很大的挑战. 单方面的加减乘除,对于任何系统来 ...

  8. python 中指针_【系列】Python源码剖析(base 2.7.18)Note之初见“对象”

    P姓"对象"到底长什么样?且听我来扒一下~ 1.1.1对象机制基石--PyObject Python中一切皆对象,即面向对象理论中的"类"和"对象&q ...

  9. 内的 对象 获取 键值对_前端系列——Object对象数据类型详细剖析

    问题: 普通对象有哪些操作? 什么是数组对象? 回答: 1--解答:普通对象是用{}包裹起来的,由零到多组属性值和属性名组成的一种数据类型.属性是描述当前对象特征的,属性名是当前具备特征的名称,属性值 ...

  10. Python爬虫之Scrapy框架系列(18)——深入剖析中间件及实战使用

    目录: 1.下载中间件: (1)终端获取下载中间件状态信息的命令: (2)下载中间件的API: (3)中间件的项目应用:通过添加中间件设置UA代理及IP代理 ①在middlewares.py中间件文件 ...

最新文章

  1. Spring基础专题——第九章(基础注解编程——上)
  2. Python时间转换函数:时间转化为时间戳、时间戳转化为时间、当前日期、当前时间、星期几、前面或者后面多少天、年、月、日等
  3. DJANGO获取用户访问IP
  4. 【洛谷 P1896】[SCOI2005]互不侵犯(状压dp)
  5. Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制--转载
  6. HDU - 4135 Co-prime(容斥原理)
  7. mongodb 启动时的警告问题
  8. 前端学习(2130):编译作用域的概念
  9. Linux查看所有用户及用户管理命令
  10. 微软企业库mysql分页存储_使用微软企业库,非分页sql语句得到分页数据方法
  11. Java把科学计数法转换为字符串
  12. cut out数据增强_ChIP-Seq数据分析(PE型)
  13. verilog的描述风格
  14. 远程桌面连接服务器显示内部错误,解决远程桌面连接出现了内部错误
  15. python人工智能之:多边形矩阵热图程序实战篇(二)
  16. Swoole进阶——02 内存之Table
  17. 新款 iMac2021上手体验,这次你心动了吗
  18. Memory Hierarchy-计算机各级存储器速度对比
  19. Centos 系统优化
  20. 网络规划设计师怎么样

热门文章

  1. SQL SERVER 2000日期处理(转)
  2. ArcGIS案例学习笔记_3_2_CAD数据导入建库
  3. 获取工程的exe文件的所在目录
  4. 7.3数据类型及内置方法(一)
  5. commons-logging slf4j log4j 区别
  6. 整理python笔记001(列表(深浅copy),元祖,字典,集合)
  7. python 详解re模块
  8. Linux下七牛云存储qrsync命令行上传同步工具
  9. 一个桌面系统的微信公众号开发
  10. 在Crystal Report 8中调用Oracle的procedure储存过程,并取得返回的类型为Sys_Refcursor数据表...