QtQuick基礎教程(四)---場景渲染(Scene Graph)
在Qt5之前,GUI開發使用的是現在的QtWidgets,已經被大家所熟悉,ui描述介面佈局組合C++細節實現,實現效果很好,介面開發的速度還算中肯。在不具備開發自定義UI又需跨平臺(C++原始碼)的組織和個人面前,幾乎是不二選擇。但在移動開發面前不靈了,因為QtWidgets本質是使用平臺繪圖工具來繪繪製介面,而每個介面的繪製都需要各自有自己的繪製狀態,這個消耗對於手機這類移動平臺來說是不可忽視的
Qt Quick 一大特色在於其改變了介面渲染方式,自Qt Quick 2起統一使用OpenGL ES 2.0 或者 OpenGL 2.0 來渲染介面。這樣做的好處是,所有要渲染的介面元素均在最後統一提供給OpenGL,極大減少狀態切換時間和渲染時間(相比於之前使用QPainter依次為每個介面元素渲染,不斷地重複渲染狀態)。舉例來講,就像寫檔案,把要寫的內容存在快取後統一寫入檔案,一定比每次寫入都重複開啟關閉檔案要快。
既然說到場景,總得有個管理類,它就是
再繼續深入說下場景的好處。場景是QML檔案中Item物件的圖形表達,一個包含要顯示的所有資訊又完全獨立於Item的結構。也就是說,一旦Item物件把圖形引數傳遞給場景,之後的渲染再和Item無關了,Item物件就是用來傳圖形引數的。除此之外,在很多平臺上,渲染執行緒和主純種(GUI執行緒)是不同的兩個執行緒,兩個可以並行著來,這點上又提升了效率。
場景結構
場景由預定義好的型別的物件組成,其本質是由繼承於QQuickItem的型別的節點組成的節點樹。這個節點樹類似於圖形引擎中的場景樹,不過這個場景樹目前主要用於處理二維內容(不排隊Qt公司有自己做圖形引擎的想法)。場景樹將圖形內容提供給OpenGL來統一渲染,自己不做任何渲染工作,即使是在vitual paint()函式內。
只要使用者使用QML的Item及其子類來構建介面,QQuickWindow就獲得場景樹,會維護這個場景樹,並最終回收資源。場景樹對於只使用QML和Javascript的開發人員來說是完全透明的。
注意:Scene Graph的場景樹每次渲染時都會做裁剪,沒有改變的場景是不更新的
MyItem { // 自定義類,繼承於QQuickItem
id: item
Rectangle{
MouseArea {
onClicked: {
item.calc() // 自定義計算過程,更新要顯示的內容。但計算後介面內容不會改變,因為Scene Graph檢查這塊時沒有發現變化!!!
}
}
}
}
改為如下就可以自動更新:
MyItem { // 自定義類,繼承於QQuickItem
id: item
Item {id: invisibleItem}
function forceUpdate(){
invisileItem.visible = !invisileItem.visible
}
Rectangle{
MouseArea {
onClicked: {
item.calc()
forceUpdate() //Scene Graph檢查場景有變化,於是更新場景。僅管實際介面上沒有變化
}
}
}
}
節點
最常用的場景結點是QSGGeometryNode,它是用來為圖形定義幾何形體和光學材質的。其中QSGGeometryNode使用QSGGeometry來表達幾何形體(點、線、多邊形及3D圖形)。材質定義了畫素被如何填充。
目前可用節點包括:
類名 | 描述 |
---|---|
QSGClipNode | 表達裁剪資訊的節點 |
QSGGeometryNode | 表達幾何資訊的節點 |
QSGNode | 場景中所有節點的基類 |
QSGOpacityNode | 表達透明資訊 |
QSGTransformNode | 表達場景中矩陣操作資訊 |
注意:一般來講,OpenGL的渲染程序不同於當前(GUI)執行緒,其新增節點的是通過QQuickItem::updatePaintNode()
。所以(C++)自定義節點是通過繼承QQuickItem,重寫updatePaintNode()
並設定 QQuickItem::ItemHasContents
來新增到場景的。而最好的定義節點內容辦法是在QQuickItem::updatePaintNode()
中使用以QSG
開頭的類。
更多詳情請見Scene Graph - Custom Geometry。
預處理
場景節點會在渲染前通過QSGNode::preprocess()
被統一呼叫。開發人員需要繼承QSGNode
(或其子節點),重寫QSGNode::preprocess()
,並設定QSGNode::UsePreprocess
為渲染做準備。
節點所有權
節點所有權是通過顯示在構造時指定或通過設定QSGNode::OwnedByParent
來指定。一般來講,將節點將給場景樹管理是比較好,因為開發人員不必關心資源回收問題。
材質
材質確定了幾何圖形在螢幕上的點如何被著色。它封裝了一段OpenGL著色程式碼(Shader Program),提供了足夠的靈活性讓開發人員決定如何著色。僅管目前大多數Item
物件使用最簡單的著色程式碼。
如何開發人員想修改材質,在QML程式碼中使用ShaderEffect就可以實現一些特定效果。更高階地需使用C++程式碼。
目前可用的材質類如下。
類名 | 描述 |
---|---|
QSGFlatColorMaterial | 單色材質類 |
QSGMaterial | 儲存著色程式狀態的類 |
QSGMaterialShader | OpenGL著色程式碼類 |
QSGMaterialType | 和QSGMaterial配合使用時的唯一識別符號 |
QSGOpaqueTextureMaterial | 不透明紋理材質類 |
QSGSimpleMaterial | 和QSGSimpleMateralShader配合使用時的著色狀態儲存類的模板類 |
QSGSimpleMaterialShader | 場景著色程式的基類 |
QSGTextureMaterial | 紋理材質類 |
QSGVertexColorMaterial | 頂點材質類 |
易於使用的節點
場景中所有節點的設計初衷是更好的效能,而不是易用。而為了實現好的著色效果,需要非常多的程式碼。因此,兩個類被設計來簡化工作:
類名 | 描述 |
---|---|
QSGSimpleRectNode | 繼承於QSGGeometryNode,定義了一個被賦予單色材質的矩形圖形的節點類 |
QSGSimpleTextureNode | 繼承於QSGGeometryNode,定義了一個被賦予紋理材質的矩形圖形的節點類 |
場景和渲染
場景渲染是在QQuickWindow內部完成的,外部無法訪問。但在渲染管線上有幾個點可以供開發人員插入自定義節點程式碼或直接訪問OpenGL。
渲染如何具體工作請參照Qt Quick Scene Graph Renderer。
場景渲染有三種方式:basic, windows 和 threaded。其中basic 和 windows 是單執行緒,而threaded是指定執行緒內渲染。Qt會根據情況自動選擇使用哪種方式。當效能不滿足,或者出於測試考慮時,可以強制啟動QSG_RENDER_LOOP
。想知道具體是哪種方式,需要在啟動應用時新增引數將QSG_INFO
設定為1.
注意: windows 和 threaded方式非常依賴於OpenGL將交換間隔設定為1.一些顯示卡驅動允許使用者覆蓋或關閉這個值,並忽略Qt的修改請求。但如果沒有這個設定,會導致交換間隔太短,CPU滿負荷運轉。如果知道系統不能自動調整vsync-based
,請手動設定QSG_RENDER_LOOP=basic
來啟動basic渲染方式。
threaded渲染方式
threaded渲染方式使用獨立執行緒來渲染,由於使用多執行緒,效能得到顯著提升,且介面反饋更加流暢。其簡易示意圖如下:
非threaded渲染方式 (“basic” and “windows”)
這兩種方式使用單執行緒渲染,但寫程式碼時要按照threaded渲染方式來寫,方便日後移植。其簡易示意圖如下:
使用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,thread方式可參考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 | 記錄準備glyphs圖的時間 |
qt.scenegraph.info | 記錄所以資訊 |
qt.scenegraph.renderloop | 記錄沉浸迴圈狀態資訊 |