禁用场景交互
事件处理由 QGraphicsView 引擎的大部分 CPU 使用负责。在每次鼠标移动时,视图都会向场景询问鼠标下的项目,它调用 QGraphicsItem::shape() 方法来检测交叉点。即使是禁用的物品也会发生这种情况。所以,如果你不需要你的场景与鼠标事件交互,你可以设置QGraphicsView::setIntenteractive(false)。就我而言,我的工具中有两种模式(测量和移动/旋转),其中场景基本上是静态的,所有编辑操作都由 QGraphicsView 执行。通过这样做,我能够将帧速率提高 30%,不幸的是 ViewportAnchor::AnchorUnderMouse 停止工作。
重用你的 QPainterPaths
在 QGraphicsItem 对象中缓存 QPainterPaths。构建和填充它可能非常缓慢。在我的情况下,读取文件需要 6 秒,因为我将具有 6000 个点的点云转换为具有多个矩形的 QPainterPath。你不会想不止一次这样做。
此外,在 Qt 5.13 中,现在可以保留 QPainterPaths 的内部矢量大小,避免在其增长时出现多个副本。
简化您的 QGraphicsItem::shape()
在鼠标事件期间多次调用此方法,即使该项目未启用。尝试使其尽可能高效。
有时,即使缓存 QPainterPath 也是不够的,因为场景执行的路径相交算法对于复杂的形状可能非常慢。在我的例子中,我返回了一个大约有 6000 个矩形的形状,而且速度很慢。在对点云进行下采样后,我能够将矩形的数量减少到 1000 个左右,这显着提高了性能,但仍然不理想,因为即使在项目被禁用时仍会调用 shape()。因此,我决定保留原始 QGraphicsItem:shape() (返回边界框矩形),并在启用项目时返回更复杂的缓存形状。它在移动鼠标时将帧速率提高了近 40%,但我仍然认为这是一个 hack,如果我想出更好的解决方案,我会更新这篇文章。尽管如此,在我的测试中,只要我保持它的边界框不变,我就没有任何问题。如果不是这种情况,您将不得不调用 prepareGeometryChange(),然后更新边界框和其他地方的形状缓存。
同时测试:光栅和 OpenGL 引擎
我期待 OpenGL 总是比光栅更好,如果你想要的只是出于明显的原因减少 CPU 使用率,这可能是正确的。但是,如果您只想增加每秒的帧数,尤其是在廉价/旧计算机中,那么也值得尝试测试光栅(默认的 QGraphicsView 视口)。在我的测试中,新的 QOpenGLWidget 比旧的 QGLWidget 稍快,但 FPS 的数量比使用 Raster 慢了近 20%。当然,它可以是特定于应用程序的,并且结果可能会根据您正在渲染的内容而有所不同。
将 FullViewportUpdate 与 OpenGL 一起使用,并更喜欢使用光栅的其他部分视口更新方法(尽管需要对项目进行更严格的边界矩形维护)。
尝试禁用/启用 VSync 以查看哪个更适合您:QSurfaceFormat::defaultFormat().setSwapInterval(0 或 1)。启用会降低帧速率,禁用会导致“撕裂”。
https://www.khronos.org/opengl/wiki/Swap_Interval
缓存复杂的 QGraphicsItems
如果您的 QGraphicsItem::paint 操作过于复杂并且同一类型大多是静态的,请尝试启用缓存。如果您没有对项目应用变换(如旋转),则使用 DeviceCoordinateCache,否则使用 ItemCoordinateCache。避免经常调用 QGraphicsItem::update() ,否则它可能比没有缓存时更慢。如果您需要更改项目中的某些内容,有两种选择:在子项中绘制它,或使用 QGraphicsView::drawForeground()。
对类似的 QPainter 绘图操作进行分组
优先使用drawLines而不是多次调用drawLine;偏爱drawPoints而不是drawPoint。使用 QVarLengthArray(使用堆栈,因此可以更快)或 QVector(使用堆)作为容器。避免经常更换画笔(我怀疑在使用 OpenGL 时更重要)。此外,QPoint 可以更快,并且比 QPointF 更小。
更喜欢使用修饰线绘制,避免透明度和抗锯齿
可以禁用抗锯齿功能,尤其是当您绘制的是水平线、垂直线或 45 度线(实际上这样看起来更好)或者您使用的是“视网膜”显示器时。
搜索热点
瓶颈可能出现在令人惊讶的地方。使用分析器(在 macOS 中我使用 Instruments/Time Profiler)或其他方法,如经过的计时器、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;
}
void MyGraphicsView::drawForeground(QPainter * painter, const QRectF & rect)
{
numFrames++;
//...
}
http://doc.qt.io/qt-4.8/qelapsedtimer.html
避免深拷贝
在对 QT 容器进行迭代时,使用 foreach(const auto& 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 芯片示例中一样,您不需要使用相同的详细绘图算法来绘制在屏幕上看起来非常小的东西。您可以为此任务使用 2 个不同的 QPainterPath 缓存对象,或者在我的情况下,拥有 2 个不同的点云向量(一个具有原始向量的简化子集,另一个具有补集)。所以,根据缩放级别,我画一个或两个。另一种选择是打乱你的点云并根据缩放级别仅绘制向量的前 n 个元素。仅最后一项技术就将我的帧速率从 5 fps 提高到 15 fps(在我最初有 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;
禁用滚动条
如果在滚动场景时视图闪烁,禁用滚动条可以解决此问题:
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
使用分组将鼠标控制的转换应用于多个项目
使用 QGraphicsScene::createItemGroup() 进行分组可以避免在转换过程中多次调用 QGraphicsItem::itemChange。仅在创建和销毁组时调用两次。
比较多个 Qt 版本
我还没有足够的时间来研究它,但至少在我当前的项目中,Qt 5.6.2(在 Mac OS 上)比 Qt 5.8 快得多。