Стабилизировать частоту вызовов QWidget::paintEvent()

Насколько я понимаю, paintEvent() выполняется в "основном цикле" QApplication объект, и может тратить время на выполнение своих внутренних системных задач, задерживая выполнение помещенных в очередь слотов или другие события.

Но что, если мне нужно воспроизвести очень плавную анимацию, и я замечаю периодические задержки основного цикла в этой анимации? Могу ли я создать отдельный специальный очень стабильный "основной цикл" и переназначить paintEvent() зовет к этому?

PS Да, GPU, OpenGL и другие приятные технологии были изобретены для плавной анимации, похожей на игру, я знаю, я знаю.

Моя программа: http://www.youtube.com/watch?v=KRk_LNd7EBg

Решение

Стабилизация частоты вызовов paintEvent(), которую я ищу, GPU, OpenGL или аппаратный vsync мне не помогут! Проблема нормальная, пока я не вычислю положение пикселя в целых числах. Там всегда будут импульсы скорости движения пикселей. Чтобы решить мою "проблему", мне нужно измерить координаты в действительных числах (double, float) и реализовать алгоритм сглаживания.

1 ответ

Что вам нужно сделать, это то, что вы хотите, но в обратном направлении. Вы предлагаете специальный "стабильный" основной цикл. Вместо этого вы хотите делать все, кроме GUI, "вещи" в потоке GUI. Это сделает основной цикл событий "стабильным".

update() добавляет заказ как "пожалуйста перекрасить!" в основной цикл, но основной цикл может быть занят, поэтому анимация будет отставать

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

Сам по себе Qt не перегружает основной цикл обработки событий ненужными задачами, если вы не попросите его сделать это. То, что вы хотите, это обработать все, кроме взаимодействия с GUI, в другом потоке. Такие вещи, как доступ к сети, доступ к файлам, даже QSettings доступ - все это должно быть сделано в QObjectкоторые живут в рабочем потоке. Только основной поток GUI должен обрабатывать взаимодействие с пользователем, и только в минимальной манере - он должен делать только то, что непосредственно необходимо, чтобы реагировать на события и перерисовывать вещи. Любая другая обработка должна выполняться вне потока GUI. Вот как вы получаете плавную анимацию.

Еще одна важная вещь заключается в том, что ваши анимации должны управляться в реальном времени, а не предполагаемым временем. Таким образом, когда вы шагаете анимацию, вы должны использовать QElapsedTime измерить, сколько времени прошло с последнего шага, и использовать это время для расчета анимированных переменных. QAbstractAnimation и друзья уже справляются с этим для вас. Если вы не используете их, вам нужно сделать это самостоятельно.

Я догадываюсь, что ваш код просто плохой и делает что-то не-Qt-идиоматическим образом, и поэтому страдает. Вероятно, есть простые архитектурные причины того, почему это не гладко.

Ниже приведен простой пример того, как вы можете сделать это в QWidget, Обратите внимание на заметное отсутствие всего, что связано со временем, кроме расчета FPS. Это красота Qt. paintEvent() запрашивает анимацию currentValue() непосредственно. Это также может хранить значение в newValue() используйте его вместо слота, хотя это оставляет возможность задержки между временем вычисления значения и временем использования значения, скажем, из-за вытеснения.

Я предоставил пример, который использует Graphics View Framework в другом ответе.

В случае вашего приложения вы должны выбрать, где в осциллограмме визуализировать спектр на основе QElapsedTime так как вы начали воспроизведение. Это все, что нужно.

Пример поддерживает Qt 4/5 и использует QOpenGLWidget на Qt 5.4 и позже вместо тогдашнего устаревшего QGLWidget,

Скриншот

// https://github.com/KubaO/stackrun/tree/master/questions/widget-animation-18531776
#include <QtGlobal>
#if QT_VERSION >= QT_VERSION_CHECK(5,4,0)
#include <QtWidgets>
typedef QOpenGLWidget GLWidget;
#elif QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
typedef QGLWidget GLWidget;
#else // Qt 4
#include <QtGui>
#include <QtOpenGL>
typedef QGLWidget GLWidget;
#endif

class Widget: public GLWidget
{
    QElapsedTimer m_timer;
    struct Animation : public QVariantAnimation {
       void updateCurrentValue(const QVariant &) {}
    } m_anim;
    QPolygonF m_polygon;
    qreal m_fps;
    void paintEvent(QPaintEvent *) {
        const qreal t = 0.05;
        qreal iFps = 1E9/m_timer.nsecsElapsed();
        m_fps = (1.0-t)*m_fps + t*iFps;
        int len = qMin(height(), width());
        QPainter p(this);
        p.drawText(rect(), QString("%1,%2 FPS").arg(m_fps, 0, 'f', 0).arg(iFps, 0, 'f', 0));
        p.translate(width()/2.0, height()/2.0);
        p.scale(len*.8, len*.8);
        p.rotate(m_anim.currentValue().toReal());
        p.setPen(QPen(Qt::darkBlue, 0.1));
        p.drawPolygon(m_polygon);
        p.end();
        m_timer.restart();
    }
public:
    Widget(QWidget *parent = 0) : GLWidget(parent), m_fps(0.0) {
        m_anim.setDuration(2000);
        m_anim.setStartValue(0);
        m_anim.setEndValue(360);
        m_anim.setEasingCurve(QEasingCurve::InBounce);
        m_anim.setLoopCount(-1);
        m_anim.start();
        m_polygon.resize(4);
        m_polygon[0] = QPointF(-0.3,  0);
        m_polygon[1] = QPointF(-0.5,  0.3);
        m_polygon[2] = QPointF( 0.5,  0);
        m_polygon[3] = QPointF(-0.5, -0.3);
        setAutoFillBackground(true);
        connect(&m_anim, SIGNAL(valueChanged(QVariant)), SLOT(update()));
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}
Другие вопросы по тегам