Qt: Improving QGraphicsView Performance

I recently wrote a Qt-based application in C++ that relies heavily on the QGraphicsView framework that made its first public appearance in Qt 4.2. QGraphicsView has been the target of some criticism for poor performance. I am a long-time user of Qt3’s QCanvas class which served me very well, in performance terms, but that lacked several features that made it both difficult to use and less pretty. Namely, it had poor support for mouse handling and item selection, and it had no support for anti-aliasing or alpha-level transparency. The one thing it did have, however, was speed, blazing fast speed. I could throw hundreds of QCanvasItems on the screen with no apparent slow down. Blazing speed.

Then along came QGraphicsView, which promised to be every bit as fast as QCanvas, but with a much more intuitive mouse handling, item selection, and animation API. It’s true, the QGraphicsView is miles ahead of QCanvas in that regard. You basically get all that stuff for free. It even sports multi-select using both meta keys and a rubber band, all built in. It’s fantastic. What QGraphicsView doesn’t provide, however, is speed. Over the past few months I’ve found a couple tricks that speed things up a bit. Read on for three tips that have helped me improve QGraphicsView performance.

Trick #1: Clipping

No, this isn’t the kind of clipping you hear when you turn up the volume on your wimpy headphones. This clipping refers to pixel painting. Imagine if you had to draw a full-page picture on a p piece of paper, but when you get done, you realize that you made a mistake on a small section in the center. It would be foolish to throw away the whole picture just to redraw this small part. So instead, you apply judicious use of your pencil’s eraser and redraw just that little part. Aren’t you proud of yourself?

When it comes to pixel painting, it’s usually a lot less code to just redraw the whole picture, rather than worry about which parts actually need to be redrawn. In QGraphicsView The problem is that when a QGraphicsItem moves over the top of another, and just slightly covers it, and then moves off again, you need to repaint just the part that was covered, and is now exposed. This is where clipping comes in.

In your custom QGraphicsItem-derived class, there is a paint() method that you are probably already familiar with, that looks like this:

void QGraphicsItem::paint( 
       QPainter*,
       const QStyleOptionGraphicsItem*,
       QWidget* );

A much-neglected parameter, the QStyleOptionGraphicsItem (what a mouthful) is very valuable in determining how much of your QGraphicsItem actually needs to be repainted. Check out the exposedRect member (yes, it’s a public member variable), and it will tell you what part of your item needs to be repainted.

This is where clipping comes in. By telling the QPainter object to clip the exposed rectangle of your item, it will ignore all pixel painting that happens outside of that rectangle. This saves lots of wasted painting, and will drastically speed up QGraphicsView operations like animations and drags. Here’s how you do it:

void
MyGraphicsItem::paint( QPainter *painter,
                       const QStyleOptionGraphicsItem *option,
                       QWidget* )
{
   // Here comes the magic:
   painter->setClipRect( option->exposedRect );

   //  All your custom painting code goes here.
   // ...
}

You will see the biggest speed up for objects that frequently have small exposed rectangles. Without setClipRect(), you would needlessly repaint the entire object, rather than just the affected region.

For more info on clipping, see the QPainter documentation on clipping.

Trick #2: Mind your bounding boxes

This is a simpler one. When you place a custom QGraphicsItem on a QGraphicsView, you have to provide a boundingRect() function. This tells the QGraphicsView how big your item is, and lets it figure out when to repaint your item (like when it comes on screen, or is temporarily obscured by another item). However, if you make a mistake and make your boundingRect() return a QRectF that is too big, you’re going to take a performance penalty. The reason is that QGraphicsView will repaint your item when it may not be necessary.

Also bare in mind that diagonal and curved lines have much larger bounding boxes than vertical and horizontal lines. For example, when you make an S-shaped line that passes near, but not directly over, other QGraphicsItems, it is possible that they will get repainted too often, simply because they collide with the line’s bounding rectangle. Remember that all bounding boxes in QGraphicsView are axis-aligned, and painting is based solely on these rectangles. It’s easy to confuse this with the QGraphicsItem::shape() function, which actually traces an outline of the item, but which is not used for painting (it’s used for mouse clicks, collisions and such).

Trick #3: Don’t overuse anti-aliasing

Anti-aliased lines look good, but horizontal and vertical lines don’t normally need to be anti-aliased. In fact, they usually look worse that way. The same is true for text. Apply anti-aliasing judiciously, because performance wise, it costs a lot more to render lines and text anti-aliased.

Conclusion: Coder beware

QGraphicsView is a great new framework for 2-D graphics in Qt with a really robust API. Its performance can make your app unusable if you aren’t careful. Applying these 3 tricks ought to help you squeeze a bit more action out of your code, and make your users happier.

By the way, Qt 4.3 promises a faster painting engine, which should help, but these tricks will still be applicable.

Happy coding.

10 comments to “Qt: Improving QGraphicsView Performance”

You can leave a reply or Trackback this post.
  1. Hi Dave,

    Thanks for your Tricks, they can help a lot,
    I would like to speek about avoiding painting :
    -Avoiding unecessary paintings is a great idea, the purpose is, i imagine, to save time in order to make drawing as ligth as possible.
    But it can become an illusion, like it is in Qt4, for certain cases, items with simple shapes, a lot of times is spent to avoid spending few milliseconds:

    in Qt4, there are two ways of redrawing :

    Using BSP tree, in wich case each modification to the draw should update the BSP tree, making the intersection computing between the items and the region to update unecessary.
    The classical way, in wich you need to compute this intersection.

    In the second case, Qt is very inefficient, as it compute the intersection between each item and each rect of the region to update
    imagine; in a qregion you can find 500 rects, so with 500 items, it makes
    250000 intersections to compute, do you really think it is faster then redrawing 500 items?
    To have a good idea about that, if you have a program with a lot of items, you can make an experience:
    Instead of QGraphicsView, use a sub class, MyFasterGraphicView :

    class MyFasterGraphicView :public QGraphicsView
    {
    Q_OBJECT
    public:
    MyFasterGraphicView( …);
    protected:

    void paintEvent ( QPaintEvent * event );

    };

    void MyFasterGraphicView::paintEvent ( QPaintEvent * event )
    {
    QPaintEvent *newEvent=new QPaintEvent(event->region().boundingRect());
    QGraphicsView::paintEvent(newEvent);
    delete newEvent;
    }

    So the question is:
    what is better? bad results with good intentions, or better results with bad workarounds?
    And keep me updated, :)
    Regards,

    Cyril
    Ps: this “workaround” have a great result with the collidingmices example delivered in the Qt4 distribution, at full screen

  2. Thanks for the comment Cyril.

    Yes, your approach works well when there are many moving QGraphicsItems on the screen at once, and works at the QGraphicsView level.

    On the other hand, my optimization was at the QGraphicsItem level, and works well when only a subset of items are moving.

    In fact, your optimization combined with mine would provide excellent handling of many, small QGraphicsItems moving all at once.

    Thanks!

    –Dave

  3. A thing worth mentioning, though it has nothing to do with “manual” optimization, is the fact that debug builds of Qt4 apps using QGraphicsView are damn slower than retail builds. Of course one expects a slowdown in debug builds but in my case it really looked like a design issue.

    However, thanks for your excellent article! Instant bookmark!

  4. http://Veeranjaneyulu says: -#1

    This is veeru , i am working on this same problem.

    So , i will try your solutin.

    Thanks

    Regards
    Veeru

  5. http://Andrew says: -#1

    Thanks for the suggestions. Another thing I’ve noticed is that drawing lines with line patterns can be very slow by default. The trick here is to enable “cosmetic line widths”. See QPen::setCosmetic(…);

    Unfortunately, that also means that line widths are no longer scaled with the graphics view, so we have to do that manually now for example in QGraphicsView::drawItems(…);

    This solution provided the performance boost we needed to make the whole graphics view framework usable for our needs.

  6. http://codercpp1981 says: -#1

    Hi Dave,

    I am facing a problem with QGraphicsView framework. I am using QGraphicsPixmapItem for drawing image on View. To prevent it from the zooming functionality i.e increasing it’s size when zoomedIn , I have used flag setFlag(QGraphicsItem::ItemIgnoresTransformations); but it’s not working.
    I have posted a thread on QTCenter Forum and for detailed description you can see it.

    http://www.qtcentre.org/forum/f-qt-programming-2/t-problem-with-qgraphicsview-and-itemignorestransformations-14400.html

    If you have any suggestion plz let me know.

    Thanks

  7. http://ssal says: -#1

    @Cyril : if I use your optimization technique in paintEvent and

    gvView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);

    Is it going to be contradictory ? Like one is try to paintEvent only the region where as other setting is to paint everything.

  8. Hi

    Thanks for this, also I read the comments, thank you, great things learned from you ;).

    Good luck!!

  9. http://Sven%20Anderson says: -#1

    It seems the setClipRect() is not necessary anymore. At least with QDeclerativeItem on QtEmbedded 4.8 (QWS) the painter in the paint() method is clipped anyways. I was not able paint something on the screen that is outside of option->exposedRect.

  10. Sven, you are probably right. A lot has changed since this article was written.