Qt 如何优化QGraphicsView';她的表现如何?

Qt 如何优化QGraphicsView';她的表现如何?,qt,optimization,qgraphicsview,qgraphicsscene,qgraphicsitem,Qt,Optimization,Qgraphicsview,Qgraphicsscene,Qgraphicsitem,我正在使用Qt5.6.2开发一个CAD应用程序,它需要在廉价计算机上运行,同时需要在同一场景中处理数千个项目。因此,为了获得最佳性能,我不得不进行大量实验 我决定创建这个帖子是为了帮助别人,也为了帮助我自己,只要其他人也提供更多的优化技巧 我的文本仍在进行中,如果我发现更好的技术(或者我说了一些非常愚蠢的话),我可能会更新它。禁用场景交互 事件处理由QGraphicsView引擎的CPU使用率的很大一部分负责。每次移动鼠标时,视图都会向场景询问鼠标下的项目,这会调用QGraphicsItem::

我正在使用Qt5.6.2开发一个CAD应用程序,它需要在廉价计算机上运行,同时需要在同一场景中处理数千个项目。因此,为了获得最佳性能,我不得不进行大量实验

我决定创建这个帖子是为了帮助别人,也为了帮助我自己,只要其他人也提供更多的优化技巧


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

禁用场景交互

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

重复使用QpainterPath

将QpainterPath缓存在QGraphicsSitem对象中。构建和填充它可能非常缓慢。在我的例子中,读取一个文件需要6秒钟,因为我正在将一个包含6000个点的点云转换为一个包含多个矩形的QPainterPath。你不会想做不止一次。 此外,在Qt5.13中,现在可以保留QpainterPath的内部向量大小,避免在其增长时出现多个副本

简化QGraphicsItem::shape()

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

同时测试:光栅和OpenGL引擎

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

使用OpenGL的FullViewportUpdate,更喜欢使用光栅的其他部分视口更新方法(尽管需要更严格的项目边界矩形)

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

缓存复杂的QGraphics站点

如果您的QGraphicsItem::paint操作太复杂,并且在同一类型下大多是静态的,请尝试启用缓存。如果未将变换(如旋转)应用于项,请使用DeviceCoordinateCache;否则,请使用ItemCoordinateCache。避免经常调用QGraphicsItem::update(),否则可能会比不使用缓存时更慢。如果需要更改项目中的某些内容,有两个选项:在子项中绘制,或使用QGraphicsView::drawForeground()

将类似的QPainter绘图操作分组

比起多次呼叫抽绳,更喜欢抽绳;支持支取点而不是支取点。使用QVarLengthArray(使用堆栈,因此可以更快)或QVector(使用堆)作为容器。避免经常更换画笔(我怀疑这在使用OpenGL时更为重要)。此外,QPoint可以更快,并且比QPointF更小

更喜欢使用装饰线绘制,避免透明度和抗锯齿

可以禁用抗锯齿,尤其是当您绘制的是水平线、垂直线或45度线(它们实际上以这种方式看起来更好)或使用“视网膜”显示时

搜索热点

瓶颈可能发生在令人惊讶的地方。使用探查器(在macOS中,我使用仪器/时间探查器)或其他方法,如运行计时器、qDebug或FPS计数器(我将其放在QGraphicsView::drawForeground中)来帮助定位它们。不要让你的代码难看试图优化你不确定它们是否是热点的东西。FPS计数器示例(尝试将其保持在25以上):

加大QGraphicscene::scen直立()的尺寸

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++;
    //...
}
const qreal lod = option->levelOfDetailFromTransform(painter->worldTransform());
const int n = qMin(pointCloud.size(), pointCloud.size() * lod/0.08);
painter->drawPoints(pointCloud.constData(), n);
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 );
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);
}