我正在使用 Qt 5.6.2 开发 CAD 应用程序,需要在廉价计算机上运行,同时需要处理同一场景中的数千个项目。因此,我不得不进行大量实验以获得最佳性能。
我决定创建这篇文章来帮助其他人和自己,同时也希望其他人能贡献更多的优化技巧。
我的文章仍然在进展中,如果我发现更好的技术(或者说了些愚蠢的话),我会更新它。
我正在使用 Qt 5.6.2 开发 CAD 应用程序,需要在廉价计算机上运行,同时需要处理同一场景中的数千个项目。因此,我不得不进行大量实验以获得最佳性能。
我决定创建这篇文章来帮助其他人和自己,同时也希望其他人能贡献更多的优化技巧。
我的文章仍然在进展中,如果我发现更好的技术(或者说了些愚蠢的话),我会更新它。
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;
}
void MyGraphicsView::drawForeground(QPainter * painter, const QRectF & rect)
{
numFrames++;
//...
}
http://doc.qt.io/qt-4.8/qelapsedtimer.html
避免深拷贝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;
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
使用分组将鼠标控制的变换应用于多个项目
使用QGraphicsScene::createItemGroup()进行分组可以避免在变换过程中多次调用QGraphicsItem::itemChange。只有在创建和销毁分组时才会调用两次。
比较多个Qt版本
我还没有足够的时间来调查,但至少在我的当前项目中,Qt 5.6.2(在Mac OS上)比Qt 5.8要快得多。
我的应用程序虽然不是CAD程序,但类似于CAD,因为它允许用户在空间中构建各种项目的“蓝图”,用户可以添加尽可能多的项目,有些用户的设计可能非常拥挤和复杂,同时存在数百或数千个项目。
视图中的大部分项目将是静态的(即它们仅在用户单击/拖动时移动或更改外观,这种情况很少发生)。但通常还会有一些前景项目在场景中不断地动画化并以20fps的速度移动。
为避免需要定期重新渲染复杂的静态元素,我将所有静态元素预先渲染到QGraphicsView
的背景缓存中,无论何时其中任何一个元素发生变化,或当QGraphicsView
的缩放/滚动/大小设置发生变化时,都要排除它们作为正常前景视图重绘过程的一部分进行渲染。
这样,当QGraphicsView
上运行着20fps的移动元素时,通过调用QGraphicsScene :: drawBackground()
中的代码,所有非常繁琐而复杂的静态对象都将绘制为单个调用drawPixmap()
,而不必算法地重新渲染每个项目。 然后,始终移动的元素可以以通常的方式在上面绘制。
实现这需要在QGraphicsView
上调用setOptimizationFlag(IndirectPainting)
和setCacheMode(CacheBackground)
,并且还需要在任何静态项目的任何方面发生更改时(使缓存的背景图像尽快重新渲染)对它们调用resetCachedContent()
。
唯一棘手的部分是让所有“背景” QGraphicsItems
在QGraphicsScene :: drawBackground()
回调中进行渲染,并且不要在通常比QGraphicsScene :: drawBackground()
更频繁调用的QGraphicsScene :: drawItems()
回调中进行渲染。
在我的压力测试中,与“基本” QGraphicsScene / QGraphicsView
方法相比,这将使我的程序的稳态CPU使用率降低约50%(如果我通过在QGraphicsView
上调用setViewport(new QOpenGLWidget)
来使用OpenGL,则降低约80%)。
唯一的缺点(除了增加的代码复杂性)是这种方法依赖于使用QGraphicsView :: setOptimizationFlag(IndirectPainting)
和QGraphicsView :: drawItems()
,而这两者均已被Qt基本弃用,因此这种方法未来可能无法继续使用Qt版本。(它至少在Qt 5.10.1中有效;那是我尝试过的最新Qt版本)。
代码示例:
void MyGraphicsScene :: drawBackground(QPainter * p, const QRectF & r)
{
if (_isInBackgroundUpdate == false) // anti-infinite-recursion guard
{
QGraphicsScene::drawBackground(p, r);
const QRectF rect = sceneRect();
p->fillRect(rect, backgroundBrush().color());
// Render the scene's static objects as pixels
// into the QGraphicsView's view-background-cache
this->_isInBackgroundUpdate = true; // anti-infinite-recursion guard
render(p, sceneRect());
this->_isInBackgroundUpdate = false;
}
}
// overridden to draw only the items appropriate to our current
// mode (foreground items OR background items but not both!)
void MyGraphicsScene :: drawItems(QPainter *painter, int numItems, QGraphicsItem *items[], const QStyleOptionGraphicsItem options[], QWidget *widget)
{
// Go through the items-list and only keep items that we are supposed to be
// drawing in this pass (either foreground or background, depending)
int count = 0;
for (int i=0; i<numItems; i++)
{
const bool isItemBackgroundItem = (_backgroundItemsTable.find(items[i]) != _backgroundItemsTable.end());
if (isItemBackgroundItem == this->_isInBackgroundUpdates) items[count++] = items[i];
}
QGraphicsScene::drawItems(painter, count, items, options, widget);
}