How can I optimize the performance of a QGraphicsView-based app?
Asked Answered
C

5

6

I have an app which is based on the Qt Graphics View framework.
It's a jigsaw puzzle game which basically cuts a pixmap into smaller pixmaps (puzzle pieces) and displays them as QGraphicsItems in a QGraphicsView. I want this app to run on smartphones and tablets. (It already runs on the Nokia N900 and some Symbian phones. Not optimized for Symbian^3 yet.)
The source is on Gitorious.

The items inherit QGraphicsItem and QObject, and have Q_PROPERTY macros for the pos() and rotation() of the QGraphicsItem to enable animating them with the Qt Animation framework.
I perform transformations on the items, such as scaling and rotating (the latter only in the in-development multitouch branch), and I also use QGraphicsDropShadowEffect on them.

I use a QGLWidget as viewport of the QGraphicsView in order to enable OpenGL acceleration for the app.

The problem is that despite being OpenGL-accelerated, the app is not smooth at all. (Especially the animations and especially since I added the rotation transform to the multitouch branch.) There are not many graphics items displayed, and there are no 3D operations or anything serious, just 2D drawing.
I'm not a graphics expert at all, so I have no idea why this app runs slowly. I've seen other games with lot more complicated effects run a lot smoother than this.

What's the secret? How could I optimize this app?

Cromlech answered 11/10, 2011 at 11:0 Comment(7)
Can you quantify the performance a) with and b) without animating the properties?Stulin
@genphault - What was the point of deleting "Thanks in advance for your answers"?Cromlech
@Stulin - I can't really quantify, all I can say is that the animations are sluggish, especially when I animate all items at once at the beginning of the game and when I drag/rotate items with bigger pixmaps.Cromlech
@Stulin - If you can give me an idea of how to quantify it, I'd be glad. :)Cromlech
The Qt Graphics Dojo blogs are a great resource about performance in this area.Paten
@Stulin - How can I get the FPS count out of my QGraphicsView?Cromlech
Examine the clock in the paint event.Stulin
C
7

Okay, I've waited for this long for a solution.

In the meantime, I've rewritten the app's UI in QML, and to my surprise, the performance is a LOT better and the app is very smooth now.

Some conclusions:

  • OpenGL acceleration is best when running in full-screen mode. Having the whole UI in a QDeclarativeView and setting its viewPort to a QGLWidget, and displaying it in fullscreen make this possible.
  • It seems that the overhead of QWidgets is a lot more than we had thought.
  • QML can perform a lot better than expected.
  • The impact of the QGraphicsDropshadowEffect was marginal, but I removed it and now I use a stroke effect instead. In the future, I might consider using QML shader effects.
  • It's worth to set all kinds of optimalization flags for the QDeclarativeView
  • Drawing items with alpha transparency performs a lot worse than drawing them without. Avoid alpha transparency whenever possible.
  • Rewriting a QGraphicsItem subclass to be a QDeclarativeItem subclass is really easy and worth the effort.

Source code is here.

Cromlech answered 23/1, 2012 at 19:41 Comment(0)
M
4

My answer is for people who, like I did for a while, implement render mode logic in their GraphicsItem::paint() method. For example :

GraphicsItem::paint(QPainter * painter, const QStyleOptionGraphicsItem*, QWidget*)
{
    QPen _pen ;
    const qreal normalPenWidthF = 1.5 ;
    if(isSelected()) {
        _pen.setColor(Settings::s_selectionColor) ;
        _pen.setWidthF(Settings::s_selectionWidth) ;
    }
    else
    if(isHovered()) {
        _pen.setColor(Settings::s_hoveredColor) ;
        _pen.setWidthF(Settings::s_selectionWidth) ;
    }
    else
    if(someOtherLogic()) {
        _pen.setColor(Settings::s_otherColor) ;
        _pen.setWidthF(normalPenWidthF) ;
    }
    else {
        _pen.setColor(TSPSettings::s_defaultColor) ;
        _pen.setWidthF(normalPenWidthF) ;
    }
    //
    painter->setPen(_pen) ;
    painter->setBrush(Qt::NoBrush) ;
    painter->drawEllipse(m_rect) ;
}

Here is how I achieved good QGraphicsView performance, even with large scenes involving multiple layers. It could even support dynamic clipping of shapes between layers.

  1. Custom GraphicsItems should inherit of QAbstractGraphicsShapeItem, so you have setPen() and setBrush() support.
  2. Expose an interface to update pen and brush, and use some logic to trigger an update only when needed.

.h

class AbstractGraphicsItem : public QAbstractGraphicsShapeItem
{
    private :
        bool m_hovered ;

    public :
        AbstractGraphicsItem() ;
        virtual ~AbstractGraphicsItem() ;

        bool isHovered() const { return m_hovered ; }
        void setIsHovered(bool state) ;

        // control render mode update
        virtual void updatePenAndBrush()=0 ;

    protected :
        virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) ;
        virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *e);
        virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *e);
};

.cpp

AbstractGraphicsItem::AbstractGraphicsItem()
    : QAbstractGraphicsShapeItem()
    , m_hovered(false)
{
}

AbstractGraphicsItem::~AbstractGraphicsItem()
{
}

void AbstractGraphicsItem::setHovered(bool state)
{
    if (h!=isHovered()) {
        m_hovered = h ;
        updatePenAndBrush() ;
        update() ;
    }
}

void AbstractGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent*)
{
    setHovered(true) ;
}

void AbstractGraphicsItem::hoverLeaveEvent(QGraphicsSceneHoverEvent*)
{
    setHovered(false) ;
}

QVariant AbstractGraphicsItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
    switch(change) {
        case ItemSelectedHasChanged :
            updatePenAndBrush() ;
            break ;
    }

    return QAbstractGraphicsShapeItem::itemChange(change, value);
}

And then your GraphicsItem (which inherits of AbstractGraphicsItem) becomes :

void GraphicsItem::updatePenAndBrush()
{
    QPen _pen ;
    if(isSelected()) {
        _pen.setColor(Settings::s_selectionColor) ;
        _pen.setWidthF(Settings::s_selectionWidth) ;
    } else
    if(isHovered()) {
        _pen.setColor(Settings::s_hoveredColor) ;
        _pen.setWidthF(Settings::s_selectionWidth) ;
    } else
    if(someOtherLogic()) {
        _pen.setColor(Settings::s_otherColor) ;
        _pen.setWidthF(normalPenWidthF) ;
    } else {
        _pen.setColor(Settings::s_defaultColor) ;
        _pen.setWidthF(normalPenWidthF) ;
    }
    _pen.setCosmetic(true) ;
    setPen(_pen) ;
}

void GraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem*, QWidget *)
{
    painter->setPen(pen()) ;
    painter->setBrush(brush()) ;
    painter->drawEllipse(s_rect) ;
}

The contents of the old GraphicsItem::paint() method is now in GraphicsItem::updatePenAndBrush() and is called every now and then, but not at every paint call. On the other side, the paint method gets to the basics. Obviously you'll have to call updatePenAndBrush() yourself, but it was not hard in my case. It's not the only thing I did to improve performance. I searched a lot, and there is a lot of tweaking possible for a Graphics View system, but with this one my app went from barely usable to real-time (finally!)

Megaphone answered 2/3, 2013 at 10:49 Comment(1)
Nice stuff! Although this is not what I was looking for, but thanks for sharing. :)Cromlech
H
2

Especially when you have moving items in a scene, QGraphicsScene's indexing may need some time to update its index, decreasing performance. You can tune the indexing by using setItemIndexMethod(). If you do not rely on items() or itemAt(), this may help increase performance.

However, this is a long shot. If you have only few items in your scene, the performance improvements may be minimal.

Hotel answered 11/10, 2011 at 11:11 Comment(5)
The number of the items depends on the size of the jigsaw which is user-settable. In most cases, it's below 30. (More would be too hard on small screens.)Cromlech
setItemIndexMethod(NoIndex); doesn't seem to cause any visible effects on the performance of my app.Cromlech
Testing this is very easy. Just perform a scene.setItemIndexMethod(QGraphicsScene::NoIndex); directly after scene construction. At least after that experiment you know if indexing is your bottleneck. The default behavior is creating (and updating) a BspTreeIndex.Hotel
Okay, looks like indexing is not your problem. Sorry. :(Hotel
I added setItemIndexMethod(NoIndex); directly to the constructor of my QGraphicsScene subclass. Doesn't seem to have any effect. :( But thanks for your answer still. :)Cromlech
B
2

Usually it is best to set the Graphicssystem to "raster" (final output will still be OpenGL because of the GL Widget as viewport). You don't mention it, but you can easily try if adding "-graphicssystem raster" to the command line yields any improvements.

Brail answered 11/10, 2011 at 11:54 Comment(1)
Setting the graphics system to raster has had no effect on the app's performance at all.Cromlech
L
2

From my own experience, graphics effects in QGraphicsItem is really memory and computation heavy. If you use them during the animated transitions, it could be the problem. You should take them off and see how much smoother it is then try to implement your own effects.

Legitimatize answered 11/10, 2011 at 17:10 Comment(1)
It is not smooth even without the effects.Cromlech

© 2022 - 2024 — McMap. All rights reserved.