教是言词, 实不是道,道本无言, 言说是妄。------- 达摩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
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节点?
对用户来讲最重要的节点是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文件中定义的层级结构吧。
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
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.
场景图的渲染过程发生在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:
在如下状况下,对于渲染,默认使用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.
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