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.

Write a Reply or Comment

Your email address will not be published.