Как я могу оптимизировать производительность приложения на основе QGraphicsView?

У меня есть приложение, основанное на фреймворке Qt Graphics View.
Это игра-головоломка, которая в основном разрезает растровое изображение на более мелкие пиксельные карты (части головоломки) и отображает их как QGraphicsItems в QGraphicsView. Я хочу, чтобы это приложение работало на смартфонах и планшетах. (Он уже работает на Nokia N900 и некоторых телефонах Symbian. Еще не оптимизирован для Symbian ^ 3.)
Источник: в Gitorious.

Элементы наследуют QGraphicsItem и QObject и имеют макросы Q_PROPERTY для pos() и rotation() QGraphicsItem, чтобы можно было анимировать их с помощью среды Qt Animation.
Я выполняю преобразования элементов, такие как масштабирование и вращение (последнее только в находящаяся в разработке ветка мультитач), и я также использую на них QGraphicsDropShadowEffect.

Я использую QGLWidget в качестве окна просмотра QGraphicsView, чтобы включить ускорение OpenGL для приложения.

Проблема в том, что, несмотря на ускорение OpenGL, приложение не работает плавно. (Особенно анимации, особенно с тех пор, как я добавил преобразование вращения в ветвь мультитач.) Отображается не так много графических элементов, нет 3D-операций или чего-то серьезного, только 2D-рисование.
Я не эксперт по графике вообще, поэтому я понятия не имею, почему это приложение работает медленно. Я видел, как другие игры с гораздо более сложными эффектами работали намного плавнее, чем эта.

В чем секрет? Как мне оптимизировать это приложение?


person Venemo    schedule 11.10.2011    source источник
comment
Можете ли вы количественно оценить производительность а) с и б) без анимации свойств?   -  person spraff    schedule 11.10.2011
comment
@genphault - Какой смысл удалять Заранее спасибо за ответы?   -  person Venemo    schedule 11.10.2011
comment
@spraff - я не могу точно определить количественно, все, что я могу сказать, это то, что анимация медленная, особенно когда я анимирую все элементы сразу в начале игры и когда я перетаскиваю / вращаю элементы с большими пиксельными картами.   -  person Venemo    schedule 11.10.2011
comment
@spraff - Если вы можете дать мне представление о том, как это количественно оценить, я был бы рад. :)   -  person Venemo    schedule 11.10.2011
comment
Блоги Qt Graphics Dojo - отличный ресурс о производительности в этой области.   -  person Clare Macrae    schedule 12.10.2011
comment
@spraff - Как я могу получить количество кадров в секунду из моего QGraphicsView?   -  person Venemo    schedule 12.10.2011
comment
Осмотрите часы в событии покраски.   -  person spraff    schedule 12.10.2011


Ответы (5)


Хорошо, я так долго ждал решения.

Тем временем я переписал пользовательский интерфейс приложения на QML, и, к моему удивлению, производительность НАМНОГО лучше, и теперь приложение работает очень плавно.

Некоторые выводы:

  • Ускорение OpenGL лучше всего при работе в полноэкранном режиме. Это стало возможным благодаря наличию всего пользовательского интерфейса в QDeclarativeView и настройке его viewPort на QGLWidget и его отображению в полноэкранном режиме.
  • Кажется, что накладные расходы на QWidgets намного больше, чем мы думали.
  • QML может работать намного лучше, чем ожидалось.
  • Влияние QGraphicsDropshadowEffect было незначительным, но я удалил его и теперь использую эффект обводки. В будущем я мог бы рассмотреть возможность использования шейдерных эффектов QML.
  • Стоит установить всевозможные флаги оптимизации для QDeclarativeView
  • Рисование элементов с альфа-прозрачностью работает намного хуже, чем рисование без них. По возможности избегайте альфа-прозрачности.
  • Переписать подкласс QGraphicsItem в подкласс QDeclarativeItem действительно просто и стоит затраченных усилий.

Исходный код здесь.

person Venemo    schedule 23.01.2012

Мой ответ предназначен для людей, которые, как и я некоторое время, реализуют логику режима рендеринга в своем GraphicsItem::paint() методе. Например :

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) ;
}

Вот как я добился хорошей производительности QGraphicsView даже с большими сценами, включающими несколько слоев. Он может даже поддерживать динамическое обрезание фигур между слоями.

  1. Пользовательские объекты GraphicsItem должны наследовать от QAbstractGraphicsShapeItem, поэтому у вас есть поддержка setPen () и setBrush ().
  2. Предоставьте интерфейс для обновления пера и кисти и используйте некоторую логику для запуска обновления только при необходимости.

.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);
}

И тогда ваш GraphicsItem (который наследует AbstractGraphicsItem) становится:

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) ;
}

Содержимое старого GraphicsItem::paint() метода теперь находится в GraphicsItem::updatePenAndBrush() и вызывается то и дело, но не при каждом вызове рисования. С другой стороны, метод рисования сводится к основам. Очевидно, вам придется позвонить updatePenAndBrush() самостоятельно, но в моем случае это было несложно. Это не единственное, что я сделал для повышения производительности. Я много искал, и есть много возможностей для настройки системы графического представления, но с этим мое приложение перешло от почти непригодного к использованию в режиме реального времени (наконец-то!)

person azf    schedule 02.03.2013
comment
Хорошая штука! Хотя это не то, что я искал, но спасибо, что поделились. :) - person Venemo; 02.03.2013

Если в сцене есть движущиеся элементы, для индексации QGraphicsScene может потребоваться время обновить его индекс, снижая производительность. Вы можете настроить индексацию с помощью setItemIndexMethod (). Если вы не полагаетесь на items () или itemAt (), это может помочь повысить производительность.

Однако это долгий путь. Если в вашей сцене всего несколько элементов, улучшение производительности может быть минимальным.

person grefab    schedule 11.10.2011
comment
Количество элементов зависит от размера лобзика, который задает пользователь. В большинстве случаев оно ниже 30 (на маленьких экранах больше было бы слишком сложно). - person Venemo; 11.10.2011
comment
setItemIndexMethod(NoIndex);, похоже, не оказывает видимого влияния на производительность моего приложения. - person Venemo; 11.10.2011
comment
Проверить это очень просто. Просто выполните scene.setItemIndexMethod(QGraphicsScene::NoIndex); сразу после построения сцены. По крайней мере, после этого эксперимента вы узнаете, является ли индексирование вашим узким местом. По умолчанию создается (и обновляется) BspTreeIndex. - person grefab; 11.10.2011
comment
Ладно, похоже, индексация не твоя проблема. Прости. :( - person grefab; 11.10.2011
comment
Я добавил setItemIndexMethod(NoIndex); прямо в конструктор моего подкласса QGraphicsScene. Похоже, не имеет никакого эффекта. :( Но все же спасибо за ответ. :) - person Venemo; 11.10.2011

Обычно лучше установить Graphicssystem на «растровую» (конечный результат все равно будет OpenGL из-за GL Widget в качестве области просмотра). Вы не упоминаете об этом, но вы можете легко попробовать, дает ли добавление "-graphicssystem raster" в командную строку какие-либо улучшения.

person Steffen    schedule 11.10.2011
comment
Установка растровой графической системы никак не повлияла на производительность приложения. - person Venemo; 11.10.2011

По моему собственному опыту, графические эффекты в QGraphicsItem действительно требуют много памяти и вычислений. Если вы используете их во время анимированных переходов, это может быть проблемой. Вы должны снять их и посмотреть, насколько они плавнее, а затем попытаться реализовать свои собственные эффекты.

person Stephen Chu    schedule 11.10.2011
comment
Не гладко даже без эффектов. - person Venemo; 11.10.2011