How can I optimize the performance of a QGraphicsView-based app?
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 asQGraphicsItem
s 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.
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?
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.
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.
- Custom GraphicsItems should inherit of QAbstractGraphicsShapeItem, so you have setPen() and setBrush() support.
- 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!)
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.
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.
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.
精彩评论