I'm developing a CAD application using Qt 5.6.2 that is required to run in cheap computers at the same time that it needs to handle thousands of items in the same scene. Because of that, I had to make a lot of experimentations in order to get the best performance.
I decided to create this post to help others and also myself, as long as other people also contribute with more optimization tips.
My text is still a work in progress and I may update it if I discover better techniques (or that I said something really stupid).
Disable Scene Interaction
Event handling is responsible by a good part of the CPU usage by the QGraphicsView engine. At each mouse movement the view asks the scene for the items under the mouse, which calls the QGraphicsItem::shape() method to detect intersections. It happens even with disabled items. So, if you don't need your scene to interact with mouse events, you can set QGraphicsView::setIntenteractive(false). In my case, I had two modes in my tool (measurement and move/rotate) where the scene was basically static and all edit operations were executed by QGraphicsView instead. By doing that, I was able to increase the frame rate in 30%, unfortunately ViewportAnchor::AnchorUnderMouse stopped working.
Reuse Your QPainterPaths
Cache your QPainterPaths inside your QGraphicsItem object. Constructing and populating it can be very slow. In my case it was taking 6 seconds to read a file just because I was converting a point-cloud with 6000 points into a QPainterPath with multiple rectangles. You won't want to do it more than once.
Also, in Qt 5.13 it is now possible to reserve the QPainterPaths 's internal vector size, avoiding multiple copies as it grows.
Simplify your QGraphicsItem::shape()
This method is called multiple times during mouse events, even if the item is not enabled. Try to make it as efficient as possible.
Sometimes, even caching the QPainterPath is not enough since the path intersection algorithm, executed by the scene, can be very slow for complex shapes. In my case I was returning a shape with around 6000 rectangles and it was pretty slow. After downsampling the point-cloud I was able to reduce the number of rectangles to around 1000, that improved the performance significantly but still wasn't ideal since shape() was still being called even when the item was disabled. Because of that I decided to maintain the original QGraphicsItem:shape() (which returns the bounding box rectangle) and just return the more complex cached shape when the item is enabled. It improved the frame-rate when moving the mouse in almost 40% but I still think it is a hack and will update this post if I come up with a better solution. Despite that, in my tests I didn't have any issue as long as I keep its bounding box untouched. If it is not the case, you'll have to call prepareGeometryChange() and then update the bounding box and the shape caches elsewhere.
Test Both: Raster and OpenGL engines
I was expecting that OpenGL would always be better than raster, and it is probably true if all you want is to reduce CPU usage for obvious reasons. However, if all you want is to increase the number of frames per second, especially in cheap/old computers, it worth trying to test raster as well (the default QGraphicsView viewport). In my tests, the new QOpenGLWidget was slightly faster than the old QGLWidget, but the number of FPS was almost 20% slower than using Raster. Of course, it can be application specific and the result can be different depending on what you are rendering.
Use FullViewportUpdate with OpenGL and prefer other partial viewport update method with raster (requires a more strict bounding rectangle maintaining of the items though).
Try to disable/enable VSync to see which one works better for you: QSurfaceFormat::defaultFormat().setSwapInterval(0 or 1). Enabling can reduce the frame rate and disabling can cause "tearing".
https://www.khronos.org/opengl/wiki/Swap_Interval
Cache Complex QGraphicsItems
If your QGraphicsItem::paint operation is too complex and at the same type mostly static, try to enable caching. Use DeviceCoordinateCache if you are not applying transformation (like rotation) to the items or ItemCoordinateCache otherwise. Avoid call QGraphicsItem::update() very often, or it can be even slower than without caching. If you need to change something in your item, two options are: to draw it in a child, or use QGraphicsView::drawForeground().
Group Similar QPainter Drawing Operations
Prefer drawLines over calling drawLine multiple times; favor drawPoints over drawPoint. Use QVarLengthArray (uses stack, so can be faster) or QVector (uses heap) as container. Avoid change the brush very often (I suspect it is more important when using OpenGL). Also QPoint can be faster and is smaller than QPointF.
Prefer Drawing Using Cosmetic Lines, Avoiding Transparency and Antialiasing
Antialiasing can be disabled, especially if all you are drawing are horizontal, vertical or 45deg lines (they actually look better this way) or you are using a "retina" display.
Search for Hotspots
Bottlenecks may occur in surprising places. Use a profiler (in macOS I use Instruments/Time Profiler) or other methods like elapsed timer, qDebug or FPS counter (I put it in my QGraphicsView::drawForeground) to help locate them. Don't make your code ugly trying to optimize things you are not sure if they are hotspots or not. Example of a FPS counter (try to keep it above 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
Avoid Deep-copy
Use foreach(const auto& item, items), const_iterator or items.at(i) instead of items[i], when iterating over QT containers, to avoid detachment. Use const operator and call const methods as much as possible. Always try to initialize (reserve() ) your vectors/arrays with a good estimation of its actual size.
https://www.slideshare.net/qtbynokia/optimizing-performance-in-qtbased-applications/37-Implicit_data_sharing_in_Qt
Scene Indexing
Favor NoIndex for scenes with few items and/or dynamic scenes (with animations), and BspTreeIndex for scenes with many (mostly static) items. BspTreeIndex allows fast searching when using QGraphicsScene::itemAt() method.
Different Paint Algorithms for Different Zoom Levels
As in the Qt 40000 Chips example, you don't need to use the same detailed drawing algorithm to draw things that will look very small at the screen. You can use 2 different QPainterPath cached objects for this task, or as in my case, to have 2 different point-cloud vectors (one with a simplified subset of the original vector, and another with the complement). So, depending on the zoom level, I draw one or both. Another option is to shuffle your point-cloud and draw just the n first elements of the vector, according to the zoom level. This last technique alone increased my frame rate from 5 to 15fps (in a scene where I had originally 1 million points). Use in your QGraphicsItem::painter() something like:
const qreal lod = option->levelOfDetailFromTransform(painter->worldTransform());
const int n = qMin(pointCloud.size(), pointCloud.size() * lod/0.08);
painter->drawPoints(pointCloud.constData(), n);
Oversize Your QGraphicsScene::sceneRect()
If you are constantly increasing your scene rectangle in size, reindexing can freeze your application for a short period of time. To avoid that, you can set a fixed size or add and remove a temporary rectangle to force the scene to increase to a bigger initial size:
auto marginRect = addRect(sceneRect().adjusted(-25000, -25000, 25000, 25000));
sceneRect(); // hack to force update of scene bounding box
delete marginRect;
Disable the Scroolbars
If the view is flickering when you scrool the scene, disabling the scroolbars can fix it:
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
Apply Mouse-controlled Transformations to Multiple Items Using Grouping
Grouping with QGraphicsScene::createItemGroup() avoids calling QGraphicsItem::itemChange multiple times during the transformation. It is only called twice when the group is created and destroyed.
Compare multiple Qt versions
I didn't have enough time to investigate it yet, but in my current project at least, Qt 5.6.2 (on Mac OS) was much faster than Qt 5.8.
My application, while not exactly a CAD program, is CAD-like in that it allows the user to construct a "blueprint" of various items in a space, and the user is allowed to add as many items as he likes, and some users' designs can get quite crowded and elaborate, with hundreds or thousands of items present at once.
Most of the items in the view are going to be more or less static (i.e. they move or change appearance only when the user clicks/drags on them, which is rarely). But there are often also a few foreground items in the scene that are constantly animated and moving around at 20fps.
To avoid having to re-render complex static elements on a regular basis, I pre-render all the static elements into the QGraphicsView's background-cache whenever any of them change, or whenever the zoom/pan/size settings of the QGraphicsView change, and exclude them from being rendered as part of the normal foreground-view-redrawing process.
That way, when there are moving elements running around the QGraphicsView at 20fps, all of the very-numerous-and-elaborate-static-objects are drawn (by the code in QGraphicsScene::drawBackground()) via a single call to drawPixmap() rather than having to algorithmically re-render each item individually. The always-moving elements can then be drawn on top in the usual fashion.
Implementing this involves calling setOptimizationFlag(IndirectPainting) and setCacheMode(CacheBackground) on the QGraphicsView(s), and also calling resetCachedContent() on them whenever any aspect of the any of the static-items changes (so that the cached-background-image will be re-rendered ASAP).
The only tricky part is getting all of the "background" QGraphicsItems to render inside the QGraphicsScene's drawBackground() callback, and also to not render inside the usual QGraphicsScene::drawItems() callback (which is typically called much more often than QGraphicsScene::drawBackground()).
In my stress-test this reduces my program's steady-state CPU usage by about 50% relative to the "vanilla" QGraphicsScene/QGraphicsView approach (and by about 80% if I'm using OpenGL via calling setViewport(new QOpenGLWidget) on my QGraphicsView).
The only downside (other than added code complexity) is that this approach relies on using QGraphicsView::setOptimizationFlag(IndirectPainting) and QGraphicsView::drawItems(), both of which have been more-or-less deprecated by Qt, so this approach might not continue to work with future Qt versions. (It does work at least as far as Qt 5.10.1 though; that's the latest Qt version I've tried it with)
Some illustrative code:
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);
}
I have a QAbstractListModel with custom objects as items. Each object has a QImage that is loaded from a database. I use ListView in QML to visualize it but I do not see any mean to represent QImage in the delegate. Image primitive seems to accept only URLs.
Is the only way for me to show QImages is to create a QQuickImageProvider with some custom system of a URL per element (looks like a total overkill)?
I think QQuickImageProvider is the proper way.
Also, I think you can use the word 'overkill' if you know exactly how the Qt internals work. Otherwise it's just guessing.
AFAIK there is a complex caching system of images (and other data) underneath, so once an image pixmap is loaded (and doesn't change) data retrieval is immediate. So no overkill at all, since in any case at some point you need to load those QImage, but just once.
I believe a QQuickImageProvider provides pointers to the cached data, and not the whole rasterized data every time. Moreover blitting operations are nowadays performed with hardware accelerations, so it's a single operation taking a fraction of millisecond.
In other words you end up having:
give me image with url "image://xyz"
Qt looks up in the cache and returns the data pointer or performs a full load of the image if not found
the QML renderer passes the data array to OpenGL
one single blit operation (microseconds) and you have it on screen
A QML ShaderEffect will bind a QImage to a GLSL Sampler2D. See the list of "how properties are mapped to GLSL uniform variables" in the ShaderEffect docs. Needs a few lines of GLSL writing in the ShaderEffect to pass pixels through, (will update this post with an example sometime, when I have access to my code), but the result is a fast, QSG-friendly all-QML element which can render QImages.
I need to write a std::vector< double > values to a qGraphicsScene. (values between 0-1, each element represents a pixel - grayscale)
Later i want to access the pixels of the image for replace the color (i don't have time to replace the whole image)
thx. for the answer!
If you're wanting to do such low level modification, I'd recommend taking a look at the QImage class. Members such as QImage::setPixel will give you access to individual pixels for modification.
If you need this kind of functionality on a QGraphicsScene, then you could draw to the QImage and then convert that to a QPixmap (with QPixmap::convertFromImage) for use with a QGraphicsPixmapItem, and then place the QGraphicsPixmapItem onto the scene.
You may want to take a look at the generic Qt containers, such as QVector as well.
I draw frequency spectrum of WAV file inside QImage (example: http://savepic.net/2350314.jpg). The WAV file may be long enough to not fit into screen considering good time resolution.
I need to be able to srcoll through entire file fast enough, possibly without filesystem reading operations.
So i have to keep large QImage in memory for fast scrolling. Another desigion would be slower, because it would require me to redraw QImage (QImages) every time user scrolls a screen.
Assuming the desigion with keeping large QImage in memory (1024x50000, for example) i must be able to display some part of that large QImage in the program window.
What is the solution with lowest cost? Using QScrollArea or maybe using QPainter method drawImage() with offset arguments?
I would definitely build a small custom widget and reimplement its paint() method with a QPainter (and scrolling with offsets etc).
Using QPixmap for showing the needed parts of the image should be faster then natively drawing (a part of) a QImage.
I am developing a program that must calculate a color of each point on a 1024x1024 picture using a special algorithm. The color of a point represents some value. So each point is independent of other points and must be drawn separately. I do not have to refresh the picture too frequently. Actually, I need to display it only once.
What is the fastest approach to drawing separate pixels in Qt?
Can I get some sort of "screen memory" and write all the picture as an array of 4-byte sets, representing each pixel as 4 bytes in that memory?
The QImage class is optimized for pixel manipulation. You can instantiate one with the requred size and then either set the pixels individually setPixel, or access the raw data and manipulate them in place via bits(). Just be sure to use the correct format (e.g. RGBA values or color indices for 8-bit images)
The fastest solution may be to create a QImage, manipulate it (set the pixels) and then get Qt to draw it.
The QImage class is for fast IO, from the manual:
The QImage class provides a hardware-independent image representation that allows direct access to the pixel data, and can be used as a paint device.
The QImage class supports several image formats described by the Format enum. These include monochrome, 8-bit, 32-bit and alpha-blended images which are available in all versions of Qt 4.x.
There is information on pixel manipulation in the Detailed Description section.
To display it the simplest way would be to convert it to a pixmap with QPixmap::fromImage and then put it in a label with QLabel::setPixmap.
For more control, you could sub-class QWidget, overload the paintEvent, and draw the QImage with a QPainter with QPainter::drawImage.
You might try to use an OpenGL widget and the glDrawPixels function.