How to improve QGraphicsView performance in a 2D static scene with many items? (no way to solve it?)
-
If understood correctly, QGraphicsView is supposed to handle million items efficiently.
In my application, I only have some few thousand and the performance is already very bad. When the View is showing the whole scene, zooming, hoverEnvents and any other stuff becomes impossible.
I have tried to create a parent-child relationship among items, and try different optimization flags, but still the same result. I really hope that I'm making some stupid mistake, but after several days looking for a way to solve the problem, I did not find any solution.
I would really appreciate any help!
This reproduces my problem:
@
import sys
import random
from PyQt4.QtGui import *NO_INDEX = False OPTIMAZE = False ITEM_COORD_CACHE = False ITEM_DEVICE_CACHE = False NESTED_ITEMS = False class TestItem(QGraphicsEllipseItem): def paint(self, painter, option, index): return QGraphicsEllipseItem.paint(self, painter, option, index) def hoverEnterEvent (self, e): self.setBrush(QBrush(QColor("orange"))) def hoverLeaveEvent(self,e): self.setBrush(QBrush(None)) if __name__ == '__main__': n = int(sys.argv[1]) # Number of items. With 5000 I already # have performance problems app = QApplication(sys.argv) scene = QGraphicsScene() # Populates scene prev = None for i in xrange(n): # Random geometry and position r1 = random.randint(10, 100) r2 = random.randint(10, 100) x = random.randint(0, 500) y = random.randint(0, 500) item = TestItem(x, y, r1*2, r2*2) item.setAcceptsHoverEvents(True) if NESTED_ITEMS: # Creates a parent child structure among items if not prev: scene.addItem(item) else: item.setParentItem(prev) prev = item else: scene.addItem(item) if ITEM_COORD_CACHE: item.setCacheMode(QGraphicsItem.ItemCoordinateCache) elif ITEM_DEVICE_CACHE: item.setCacheMode(QGraphicsItem.DeviceCoordinateCache) # Creates View view = QGraphicsView(scene) # Sets basic Flags for nice rendering view.setRenderHints(QPainter.Antialiasing or QPainter.SmoothPixmapTransform) if NO_INDEX: view.setItemIndexMethod(QGraphicsScene.NoIndex); if OPTIMAZE: view.setOptimizationFlags(QGraphicsView.DontAdjustForAntialiasing or QGraphicsView.DontClipPainter or QGraphicsView.DontSavePainterState) view.show() sys.exit(app.exec_())@
- Intel(R) Xeon(R) CPU E5410 @ 2.33GHz
- nVidia Corporation G84 [Quadro FX 1700]
- Ubuntu 9.04 64 bits
- qt4 4.5.3
- python-qt4 4.6
-
I converted your sample to C++ (see below) and ran it through a profiler. Your main bottleneck is QPainter::drawEllipse().
The expensive parts, especially, seem to be QPainterPath::toFillPolygons() and QTesselator::tesselate().
This isn't especially surprising, as with thousands of items, they all overlap, meaning hover and unhover triggers for all of them, which means a full redraw of that item each time.
@#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsEllipseItem>
#include <QDebug>bool NO_INDEX = false;
bool OPTIMAZE = false;
bool ITEM_COORD_CACHE = false;
bool ITEM_DEVICE_CACHE = false;
bool NESTED_ITEMS = false;class TestItem : public QGraphicsEllipseItem
{
public:
TestItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent = 0) : QGraphicsEllipseItem(x, y, width, height, parent)
{} void paint(QPainter *painter, const QStyleOptionGraphicsItem * option, QWidget *index) { return QGraphicsEllipseItem::paint(painter, option, index); } void hoverEnterEvent(QGraphicsSceneHoverEvent *e) { this->setBrush(QBrush(QColor("orange"))); } void hoverLeaveEvent(QGraphicsSceneHoverEvent *e) { this->setBrush(QBrush("red")); }
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QGraphicsScene scene;// Populates scene QGraphicsItem *prev = NULL; int n = atoi(argv[1]); qDebug() << "Adding " << n << " items"; for (int i = 0; i != n; ++i) { // Random geometry and position int r1 = rand() % 100 + 10; int r2 = rand() % 100 + 10; int x = rand() % 500; int y = rand() % 500; QGraphicsItem *item = new TestItem(x, y, r1*2, r2*2); item->setAcceptsHoverEvents(true); if (NESTED_ITEMS) { // Creates a parent child structure among items if (!prev) scene.addItem(item); else item->setParentItem(prev); prev = item; } else scene.addItem(item); if (ITEM_COORD_CACHE) item->setCacheMode(QGraphicsItem::ItemCoordinateCache); else if (ITEM_DEVICE_CACHE) item->setCacheMode(QGraphicsItem::DeviceCoordinateCache); } // Creates View QGraphicsView view; view.setScene(&scene); // Sets basic Flags for nice rendering view.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
// if (NO_INDEX)
// view.setItemIndexMethod(QGraphicsScene::NoIndex);if (OPTIMAZE) view.setOptimizationFlags(QGraphicsView::DontAdjustForAntialiasing | QGraphicsView::DontClipPainter | QGraphicsView::DontSavePainterState); view.show(); return app.exec();
}@
-
As for how to improve your specific example... I don't know. One idea might be to not immediately setBrush() on hover/unhover, but set a timer to trigger the change 50ms or so later, which would mean that the cursor might have moved to a different item.
Stacking order is also an idea, i.e. make it only affect the topmost item, but that's probably going to be complicated.