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

相關文章
相關標籤/搜索