我正在开发一个使用Qt 5.6.2的CAD应用程序,它需要在廉价的计算机上运行,同时它需要处理同一场景中的数千个项目。因此,我必须进行大量实验才能获得最佳性能。
我决定创建此帖子以帮助其他人和我自己,只要其他人也提供更多优化提示。
我的文字仍在进行中,如果我发现更好的技术(或者说我说的话非常愚蠢),我可能会更新它。
答案 0 :(得分:20)
禁用场景互动
事件处理由QGraphicsView引擎的大部分CPU使用负责。在每次鼠标移动时,视图会向场景询问鼠标下的项目,该项目调用QGraphicsItem :: shape()方法来检测交叉点。即使是残疾人物品也会发生。因此,如果您不需要场景与鼠标事件进行交互,则可以设置QGraphicsView :: setIntenteractive(false)。在我的情况下,我的工具中有两种模式(测量和移动/旋转),其中场景基本上是静态的,所有编辑操作都由QGraphicsView执行。通过这样做,我能够将帧速率提高30%,遗憾的是,ViewportAnchor :: AnchorUnderMouse停止工作(修复它的一个方法是重新启用交互并覆盖QGraphicsView :: mouseMoveEvent(QMouseEvent e)以人工按下其中一个鼠标按钮使用基于e)的新QMouseEvent。
重用您的QPainterPaths
在QGraphicsItem对象中缓存QPainterPaths。构建和填充它可能非常慢。在我的情况下,仅仅因为我将6000点的点云转换为具有多个矩形的QPainterPath,所以读取文件需要6秒钟。你不会想要多次这样做。
简化您的QGraphicsItem :: shape()
在鼠标事件期间,即使未启用该项,也会多次调用此方法。尽量使其尽可能高效。 有时,即使缓存QPainterPath也是不够的,因为由场景执行的路径交叉算法对于复杂的形状来说可能非常慢。在我的情况下,我返回一个大约6000个矩形的形状,它很慢。在对点云进行下采样后,我能够将矩形数量减少到1000左右,这显着提高了性能,但仍然不理想,因为即使项目被禁用,仍然会调用shape()。因此,我决定维护原始的QGraphicsItem:shape()(返回边界框矩形),并在启用项目时返回更复杂的缓存形状。它在移动鼠标时提高了帧速率近40%,但我仍然认为这是一个黑客,如果我想出一个更好的解决方案,它会更新这篇文章。尽管如此,在我的测试中,只要我保持其边界框不受影响,我就没有任何问题。如果不是这种情况,您必须调用prepareGeometryChange(),然后在其他位置更新边界框和形状缓存。
测试两者:Raster和OpenGL引擎
我原本期望OpenGL总是比光栅更好,如果你想要的只是因为显而易见的原因而减少CPU使用率,那可能就是这样。但是,如果您只想增加每秒的帧数,特别是在便宜/旧计算机上,那么值得尝试测试栅格(默认的QGraphicsView视口)。在我的测试中,新的QOpenGLWidget比旧的QGLWidget略快,但FPS的数量比使用Raster慢近20%。当然,它可以是特定于应用程序的,结果可能会有所不同,具体取决于您呈现的内容。
将FullViewportUpdate与OpenGL一起使用,并且更喜欢使用栅格的其他部分视口更新方法(虽然需要更严格的边界矩形维护项目)。
尝试禁用/启用VSync以查看哪个更适合您:QSurfaceFormat :: defaultFormat()。setSwapInterval(0或1)。启用可以降低帧速率,禁用可能导致“撕裂”#34;。 https://www.khronos.org/opengl/wiki/Swap_Interval
缓存复杂QGraphicsItems
如果您的QGraphicsItem :: paint操作过于复杂且相同类型大部分是静态的,请尝试启用缓存。如果不对项目或ItemCoordinateCache应用转换(如旋转),请使用DeviceCoordinateCache。避免经常调用QGraphicsItem :: update(),或者甚至比没有缓存时更慢。如果你需要更改项目中的某些内容,有两个选项:在子项中绘制它,或者使用QGraphicsView :: drawForeground()。
对类似的QPainter绘图操作进行分组
首选drawLines多次调用drawLine;赞成drawPoint的drawPoints。使用QVarLengthArray(使用堆栈,因此可以更快)或QVector(使用堆)作为容器。避免经常更换画笔(我怀疑使用OpenGL时更重要)。此外,QPoint可以更快,并且比QPointF小。
喜欢使用化妆品线绘图,避免透明度和抗锯齿
可以禁用抗锯齿,特别是如果您正在绘制的所有图像都是水平线,垂直线或45度线(它们实际上看起来更好)或者您正在使用"视网膜"显示。
搜索热点
在令人惊讶的地方可能会出现瓶颈。使用分析器或其他方法,如经过时间计时器,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
避免深层复制
在迭代QT容器时使用foreach(const auto&amp; item,items),const_iterator或items.at(i)而不是items [i],以避免分离。使用const运算符并尽可能多地调用const方法。总是尝试初始化(reserve())您的矢量/数组,并估计其实际大小。 https://www.slideshare.net/qtbynokia/optimizing-performance-in-qtbased-applications/37-Implicit_data_sharing_in_Qt
场景索引
对于具有少量项目和/或动态场景(带有动画)的场景,使用NoIndex,对于具有许多(主要是静态)项目的场景,使用BspTreeIndex。 BspTreeIndex允许在使用QGraphicsScene :: itemAt()方法时快速搜索。
针对不同缩放级别的不同绘制算法
与Qt 40000 Chips示例一样,您不需要使用相同的详细绘图算法来绘制在屏幕上看起来非常小的内容。您可以为此任务使用2个不同的QPainterPath缓存对象,或者在我的情况下,使用2个不同的点云向量(一个带有原始向量的简化子集,另一个带有补码)。因此,根据缩放级别,我绘制一个或两个。另一种选择是根据缩放级别对点云进行随机播放并仅绘制向量的n个第一个元素。最后一项技术单独将帧速率从5帧提高到15帧/秒(在我最初有100万点的场景中)。在QGraphicsItem :: painter()中使用类似于:
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快得多。
答案 1 :(得分:2)
我的应用程序虽然不是CAD程序,但它类似CAD,因为它允许用户构建空间中各种项目的“蓝图”,并允许用户添加他喜欢的任意数量的项目,一些用户的设计可能会变得非常拥挤和精心设计,同时会出现数百或数千个项目。
视图中的大多数项目或多或少都是静态的(即只有当用户点击/拖动它们时才会移动或更改外观,这很少)。但是场景中经常还会有一些前景项目不断动画并以20fps的速度移动。
为了避免必须定期重新渲染复杂的静态元素,我会将所有静态元素预渲染到QGraphicsView
的后台缓存中,只要它们中的任何一个发生变化,或者每当缩放/平移时QGraphicsView
的/ size设置更改,并将其排除在正常的前景视图重绘过程中。
这样,当有动态元素在QGraphicsView
处以20fps运行时,所有非常多且精细的静态对象都被绘制(通过QGraphicsScene::drawBackground()
中的代码)单次调用drawPixmap()
而不是必须在算法上单独重新呈现每个项目。然后可以以通常的方式将始终移动的元素绘制在顶部。
实施此操作涉及在setOptimizationFlag(IndirectPainting)
(s)上调用setCacheMode(CacheBackground)
和QGraphicsView
,并在任何静态项目的任何方面调用resetCachedContent()
更改(以便尽快重新渲染缓存的背景图像)。
唯一棘手的部分是让所有“背景”QGraphicsItems
在QGraphicsScene
的{{1}}回调中呈现,并且不会在通常的{{1}内呈现回调(通常比drawBackground()
更频繁地调用)。
在我的压力测试中,相对于“vanilla”QGraphicsScene::drawItems()
/ QGraphicsScene::drawBackground()
方法,这会将我的程序的稳态CPU使用率降低约50%(如果我使用的话,则减少约80%)通过QGraphicsScene
上的QGraphicsView
)来调用OpenGL。
唯一的缺点(除了增加的代码复杂性)是这种方法依赖于使用setViewport(new QOpenGLWidget
和QGraphicsView
,这两种方法都被Qt或多或少地弃用了,所以这种方法可能会不能继续使用未来的Qt版本。 (它确实起到了至少Qt 5.10.1的作用;这是我尝试过的最新Qt版本)
一些说明性代码:
QGraphicsView::setOptimizationFlag(IndirectPainting)