我正在開發一個使用Qt 5.6.2的CAD應用程序,它需要在便宜的計算機上運行,同時它需要處理同一場景中的數千個項目。因此,爲了獲得最佳性能,我必須進行大量試驗。如何優化QGraphicsView的性能?
我決定創建這篇文章來幫助他人,也是我自己,只要其他人也貢獻更多的優化技巧。
我的文本仍在進行中,如果我發現更好的技術(或者說我真的很愚蠢),我可能會更新它。
我正在開發一個使用Qt 5.6.2的CAD應用程序,它需要在便宜的計算機上運行,同時它需要處理同一場景中的數千個項目。因此,爲了獲得最佳性能,我必須進行大量試驗。如何優化QGraphicsView的性能?
我決定創建這篇文章來幫助他人,也是我自己,只要其他人也貢獻更多的優化技巧。
我的文本仍在進行中,如果我發現更好的技術(或者說我真的很愚蠢),我可能會更新它。
禁用場景互動
事件處理是由發動機的QGraphicsView的CPU佔用率的很大一部分責任。在每次鼠標移動時,視圖會詢問場景中鼠標下的項目,該項目調用QGraphicsItem :: shape()方法來檢測交集。即使是禁用物品也會發生。所以,如果你不需要你的場景與鼠標事件交互,你可以設置QGraphicsView :: setIntenteractive(false)。在我的情況下,我的工具中有兩種模式(測量和移動/旋轉),場景基本上是靜態的,所有編輯操作都是由QGraphicsView執行的。通過這樣做,我能夠將幀速率提高30%,不幸的是ViewportAnchor :: AnchorUnderMouse停止工作(修復它的一個竅門是重新啓用交互並重寫QGraphicsView :: mouseMoveEvent(QMouseEvent e)來人爲地按下其中一個鼠標按鈕使用基於e)的新QMouseEvent。
重用你QPainterPaths
緩存您的QGraphicsItem對象內部的QPainterPaths。構建和填充它可能非常緩慢。在我的情況下,讀取文件需要6秒鐘,因爲我將6000點的點雲轉換爲具有多個矩形的QPainterPath。你不會想要多做一次。
簡化您的QGraphicsItem ::形狀()
這種方法是在鼠標事件多次調用,即使不啓用該項目。儘量讓它儘可能高效。 有時,即使緩存QPainterPath也是不夠的,因爲由場景執行的路徑交集算法對於複雜形狀可能非常緩慢。在我的情況下,我正在返回一個大約6000個矩形的形狀,而且速度很慢。下采樣點雲後,我能夠將矩形數量減少到1000個左右,這大大提高了性能,但仍然不理想,因爲即使在禁用項目時shape()仍然被調用。因此,我決定保留原始QGraphicsItem:shape()(它返回邊界框矩形),並在啓用該項目時返回更復雜的緩存形狀。它在將鼠標移動近40%時提高了幀頻,但我仍然認爲這是一種黑客攻擊,如果我想出更好的解決方案,它會更新這篇文章。儘管如此,在我的測試中,只要保持邊界框不變,我就沒有任何問題。如果不是這種情況,則必須調用prepareGeometryChange(),然後更新其他位置的邊界框和形狀緩存。
測試兩個:光柵和OpenGL引擎
我期待的是OpenGL的總是比光柵更好,如果你想要的是減少的原因很明顯CPU使用率也可能是真實的。但是,如果您只想增加每秒幀數(特別是在廉價/舊電腦中),則還需要嘗試測試光柵(默認QGraphicsView視口)。在我的測試中,新的QOpenGLWidget比舊的QGLWidget稍快,但FPS的數量比使用Raster慢了近20%。當然,它可以是特定於應用程序的,並且結果可能因您所呈現的內容而有所不同。
在OpenGL中使用FullViewportUpdate,並優先使用柵格的其他部分視口更新方法(需要更嚴格的邊界矩形來維護項目)。
嘗試禁用/啓用VSync以查看哪一個更適合您:QSurfaceFormat :: defaultFormat().setSwapInterval(0或1)。啓用可以降低幀頻並禁用會導致「撕裂」。 https://www.khronos.org/opengl/wiki/Swap_Interval
緩存複雜QGraphicsItems
如果您的QGraphicsItem ::繪製操作過於複雜,在同一類型的大多是靜態的,嘗試啓用緩存。如果您沒有將變換(如旋轉)應用到項目或ItemCoordinateCache,則使用DeviceCoordinateCache。避免經常調用QGraphicsItem :: update(),或者它可能比沒有緩存更慢。如果您需要更改項目中的某些內容,則有兩個選項:將其繪製到子項中,或使用QGraphicsView :: drawForeground()。
集團類似的QPainter繪製操作
身高drawLines在調用的drawLine多次;青睞drawPoints over drawPoint。使用QVarLengthArray(使用堆棧,所以可以更快)或QVector(使用堆)作爲容器。避免經常更換筆刷(我懷疑在使用OpenGL時更重要)。此外,QPoint可以更快並且小於QPointF。
宗教繪畫使用化妝品系列,避免透明度和抗鋸齒
抗鋸齒可以被禁用,特別是如果你正在繪製的水平,垂直或45deg線(他們實際的觀看效果更好),或者你正在使用一個「視網膜」顯示器。
搜索熱點
瓶頸可能在令人驚訝的地方發生。使用探查器或其他方法,如經過計時器,qDebug或FPS計數器(我把它放在我的QGraphicsView :: drawForeground中)來幫助定位它們。不要讓你的代碼醜陋,試圖優化你不確定的東西,如果它們是熱點或不是。一個FPS計數器的例子(儘量保持25以上):
MyGraphicsView:: MyGraphicsView(){
...
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(oneSecTimeout()));
timer->setInterval(1000);
timer->start();
}
void MyGraphicsView::oneSecTimeout()
{
frameRate=(frameRate+numFrames)/2;
qInfo() << frameRate;
numFrames=0;
}
http://doc.qt.io/qt-4.8/qelapsedtimer.html
避免深拷貝
使用的foreach(const的汽車&項目,項目),爲const_iterator或項目.at(i)代替項[i],當迭代QT容器時,避免分離。儘可能使用const運算符並調用const方法。總是嘗試初始化(保留())你的向量/數組,並對其實際大小進行很好的估計。 https://www.slideshare.net/qtbynokia/optimizing-performance-in-qtbased-applications/37-Implicit_data_sharing_in_Qt
場景索引
青睞NOINDEX與幾個項目和/或動態場景(與動畫),以及BspTreeIndex的場景有許多(大多是靜態的)項目的場景。 BspTreeIndex在使用QGraphicsScene :: itemAt()方法時允許快速搜索。
不同的油漆算法不同的縮放級別
如Qt的40000芯片舉例來說,你並不需要使用相同的詳圖算法得出的東西會看起來非常小屏幕。你可以爲這個任務使用2個不同的QPainterPath緩存對象,或者在我的情況下,有2個不同的點雲矢量(一個帶有原始向量的簡化子集,另一個帶有補碼)。所以,根據縮放級別,我繪製一個或兩個。另一種選擇是根據縮放級別,將您的點雲進行洗牌並僅繪製矢量的n個第一元素。單靠最後一項技術,我的幀速率從5幀提高到15幀(在我原來有1百萬分的場景中)。請使用您的QGraphicsItem ::畫家()是這樣的:
const qreal lod = option->levelOfDetailFromTransform(painter->worldTransform());
const int n = qMin(pointCloud.size(), pointCloud.size() * lod/0.08);
painter->drawPoints(pointCloud.constData(), n);
特大型你的QGraphicsScene :: sceneRect()
如果你不斷增加的大小場景的矩形,重建索引可以凍結你的應用很短的時間。爲了避免這種情況,你可以設置一個固定的大小或添加和刪除臨時矩形迫使現場增加一個更大的初始大小:
auto marginRect = addRect(sceneRect().adjusted(-25000, -25000, 25000, 25000));
sceneRect(); // hack to force update of scene bounding box
delete marginRect;
禁用Scroolbars
如果視圖閃爍當你雙擊自動滾屏場景,禁用scroolbars能解決這個問題:
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
應用鼠標控制的轉換多個項目使用分組
使用QGraphicsScene :: createItemGroup()進行分組避免了在轉換過程中多次調用QGraphicsItem :: itemChange。只有在組創建和銷燬時纔會調用兩次。
比較多的Qt版本
我沒有足夠的時間來調查過,但在我的當前項目至少,Qt的5.6.2(在Mac OS)比Qt的5.8快得多。