如何优化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快得多。