qt - 如何优化 QGraphicsView 的性能?

标签 qt optimization qgraphicsview qgraphicsscene qgraphicsitem

我正在使用 Qt 5.6.2 开发 CAD 应用程序,它需要在廉价计算机上运行,​​同时需要处理同一场景中的数千个项目。因此,我必须进行大量实验才能获得最佳性能。

我决定创建这篇文章来帮助他人和我自己,只要其他人也提供更多优化技巧。

我的文本仍在进行中,如果我发现更好的技术(或者我说了一些非常愚蠢的话),我可能会更新它。

最佳答案

禁用场景交互

事件处理由 QGraphicsView 引擎的 CPU 使用率的很大一部分负责。每次鼠标移动时, View 都会向场景询问鼠标下的项目,它调用 QGraphicsItem::shape() 方法来检测交叉点。即使禁用项目也会发生这种情况。所以,如果你不需要你的场景与鼠标事件交互,你可以设置 QGraphicsView::setIntenteractive(false)。就我而言,我的工具中有两种模式(测量和移动/旋转),其中场景基本上是静态的,所有编辑操作都由 QGraphicsView 执行。通过这样做,我能够将帧速率提高 30%,不幸的是 ViewportAnchor::AnchorUnderMouse 停止工作(修复它的一个技巧是重新启用交互并覆盖 QGraphicsView::mouseMoveEvent(QMouseEvent e) 以人为地按下其中之一鼠标按钮使用基于 e) 的新 QMouseEvent。

重用您的 QPainterPaths

在 QGraphicsItem 对象中缓存 QPainterPaths。构建和填充它可能非常缓慢。在我的情况下,读取文件需要 6 秒,因为我将具有 6000 个点的点云转换为具有多个矩形的 QPainterPath。你不会想要做不止一次。
此外,在 Qt 5.13 中,现在可以保留 QPainterPaths 的内部向量大小,避免随着它的增长而出现多个副本。

简化你的 QGraphicsItem::shape()

在鼠标事件期间多次调用此方法,即使该项目未启用。尝试使其尽可能高效。
有时,即使缓存 QPainterPath 也不够,因为场景执行的路径交叉算法对于复杂形状可能非常慢。在我的例子中,我返回了一个大约有 6000 个矩形的形状,而且速度很慢。在对点云进行下采样后,我能够将矩形的数量减少到 1000 左右,这显着提高了性能,但仍然不理想,因为即使项目被禁用,shape() 仍然被调用。因此,我决定保留原始的 QGraphicsItem:shape()(它返回边界框矩形),并在启用该项目时返回更复杂的缓存形状。它在将鼠标移动近 40% 时提高了帧速率,但我仍然认为这是一个黑客行为,如果我想出更好的解决方案,我会更新这篇文章。尽管如此,在我的测试中,只要我保持其边界框不变,我就没有任何问题。如果不是这种情况,则必须调用 prepareGeometryChange(),然后更新边界框和其他地方的形状缓存。

测试两者:光栅和 OpenGL 引擎

我期待 OpenGL 总是比光栅更好,如果你只是出于明显的原因想要减少 CPU 使用率,这可能是真的。但是,如果您只想增加每秒的帧数,尤其是在廉价/旧计算机中,那么也值得尝试测试光栅(默认的 QGraphicsView 视口(viewport))。在我的测试中,新的 QOpenGLWidget 比旧的 QGLWidget 稍快,但 FPS 的数量比使用 Raster 慢了近 20%。当然,它可以是特定于应用程序的,并且结果可能会根据您渲染的内容而有所不同。

将 FullViewportUpdate 与 OpenGL 一起使用,并更喜欢其他带有光栅的部分视口(viewport)更新方法(尽管需要更严格的边界矩形维护项目)。

尝试禁用/启用 VSync 以查看哪个更适合您:QSurfaceFormat::defaultFormat().setSwapInterval(0 或 1)。启用会降低帧率,禁用会导致“撕裂”。
https://www.khronos.org/opengl/wiki/Swap_Interval

缓存复杂的 QGraphicsItems

如果您的 QGraphicsItem::paint 操作过于复杂,并且大部分是静态的,请尝试启用缓存。如果您没有对项目或 ItemCoordinateCache 应用转换(如旋转),请使用 DeviceCoordinateCache。避免经常调用 QGraphicsItem::update() ,否则它会比没有缓存更慢。如果您需要更改项目中的某些内容,两个选项是:在子项中绘制它,或使用 QGraphicsView::drawForeground()。

群组类似 QPainter 绘图操作

比多次调用 drawLine 更喜欢 drawLine;比 drawPoint 更喜欢 drawPoints。使用 QVarLengthArray(使用堆栈,因此可以更快)或 QVector(使用堆)作为容器。避免经常更换画笔(我怀疑在使用 OpenGL 时更重要)。此外,QPoint 可以更快并且比 QPointF 小。

首选使用装饰线绘制,避免透明和抗锯齿

可以禁用抗锯齿,特别是如果您绘制的所有线条都是水平、垂直或 45 度线(它们实际上这样看起来更好),或者您使用的是“视网膜”显示器。

搜索热点

瓶颈可能出现在令人惊讶的地方。使用探查器(在 macOS 中我使用仪器/时间探查器)或其他方法,如经过计时器、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 Chips 示例中,您不需要使用相同的详细绘图算法来绘制在屏幕上看起来很小的东西。您可以为此任务使用 2 个不同的 QPainterPath 缓存对象,或者在我的情况下,具有 2 个不同的点云向量(一个具有原始向量的简化子集,另一个具有补充)。因此,根据缩放级别,我绘制一个或两个。另一种选择是根据缩放级别调整您的点云并仅绘制矢量的前 n 个元素。仅最后一项技术就将我的帧速率从 5fps 提高到 15fps(在我最初拥有 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;

禁用滚动条

如果滚动场景时 View 闪烁,禁用滚动条可以修复它:
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );

使用分组将鼠标控制的转换应用于多个项目

使用 QGraphicsScene::createItemGroup() 分组可以避免在转换过程中多次调用 QGraphicsItem::itemChange。它只在创建和销毁组时调用两次。

比较多个 Qt 版本

我还没有足够的时间来研究它,但至少在我当前的项目中,Qt 5.6.2(在 Mac OS 上)比 Qt 5.8 快得多。

关于qt - 如何优化 QGraphicsView 的性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43826317/

相关文章:

c++ - 有没有办法在 QT 中对 UI 应用程序进行单元测试

qt - 当我们使用 Qlabel 作为超链接文本时,如何在正常状态下删除超链接文本下方的行?

algorithm - 仿真和 Ad-Hoc 优化算法

python - 使用 PyQt 自定义 QDial notch ticks

c++ - 错误 : Cannot allocate an object

c++ - 使用 Visual Studio 2013 构建 Qt 5.2.1 的静态版本

qt - QStatemachine 和并行状态

javascript - 多个内联 JS 脚本会减慢页面的加载时间吗?

android - Android 应用程序的 SQLite 优化

python - 使用橡皮筋选择 QGraphicsItem