Qt5 QtQuick系列----QtQuick的Secne Graph剖析(1)

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


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

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

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

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

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

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

注意: 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.windows


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

Qt Quick Scene Graph Structure

QtQuick场景图实际上是一个树结构,包含不少个节点。这个树是从何而来呢?官方说法:ide

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类对象 (http://www.javashuo.com/article/p-xblwjazw-bb.html ),这些被建立的对象之间应该是有层次关系的,最终,这些对象被转换成场景图,场景图的每一个节点对应的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.

 



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

相关文章
相关标签/搜索