From 0129887195c7255f41515f72ceb213a38b98f72d Mon Sep 17 00:00:00 2001 From: Gunnar Sletta Date: Fri, 12 Sep 2014 11:21:36 +0200 Subject: Finally: A default animation driver which doesn't use walltime. I've wanted this for a long time, but the animation system didn't support it when mixed with timers/pauses. However, because of dfc8f8b5d4a02f33c7f9063c2a28450902a9d863 and 0db3ea4048fe572a256deb343ea5e64a55d98de9 to qtbase, it is now possible. Change-Id: Ic70c181ce49eae90276bd4f22a2d299061f96087 Reviewed-by: Michael Brasser --- src/quick/scenegraph/qsgcontext.cpp | 135 ++++++++++++++++++++++++- src/quick/scenegraph/qsgrenderloop.cpp | 18 ---- src/quick/scenegraph/qsgrenderloop_p.h | 2 - src/quick/scenegraph/qsgthreadedrenderloop.cpp | 1 - 4 files changed, 134 insertions(+), 22 deletions(-) (limited to 'src/quick') diff --git a/src/quick/scenegraph/qsgcontext.cpp b/src/quick/scenegraph/qsgcontext.cpp index fdf27085b9..83b221a8cc 100644 --- a/src/quick/scenegraph/qsgcontext.cpp +++ b/src/quick/scenegraph/qsgcontext.cpp @@ -50,6 +50,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,7 @@ #include #include +#include #include #include @@ -134,6 +136,135 @@ public: bool distanceFieldAntialiasingDecided; }; +static bool qsg_useConsistentTiming() +{ + static int use = -1; + if (use < 0) { + QByteArray fixed = qgetenv("QSG_FIXED_ANIMATION_STEP"); + use = !(fixed.isEmpty() || fixed == "no"); + qCDebug(QSG_LOG_INFO, "Using %s", bool(use) ? "fixed animation steps" : "sg animation driver"); + } + return bool(use); +} + +class QSGAnimationDriver : public QAnimationDriver +{ + Q_OBJECT +public: + enum Mode { + VSyncMode, + TimerMode + }; + + QSGAnimationDriver(QObject *parent) + : QAnimationDriver(parent) + , m_time(0) + , m_vsync(0) + , m_mode(VSyncMode) + , m_bad(0) + , m_reallyBad(0) + , m_good(0) + { + QScreen *screen = QGuiApplication::primaryScreen(); + if (screen && !qsg_useConsistentTiming()) { + m_vsync = 1000.0 / screen->refreshRate(); + if (m_vsync <= 0) + m_mode = TimerMode; + } else { + m_mode = TimerMode; + if (qsg_useConsistentTiming()) + QUnifiedTimer::instance(true)->setConsistentTiming(true); + } + if (m_mode == VSyncMode) + qCDebug(QSG_LOG_INFO, "Animation Driver: using vsync: %.2f ms", m_vsync); + else + qCDebug(QSG_LOG_INFO, "Animation Driver: using walltime"); + } + + void start() Q_DECL_OVERRIDE + { + m_time = 0; + m_timer.start(); + QAnimationDriver::start(); + } + + qint64 elapsed() const Q_DECL_OVERRIDE + { + return m_mode == VSyncMode + ? qint64(m_time) + : QAnimationDriver::elapsed(); + } + + void advance() Q_DECL_OVERRIDE + { + qint64 delta = m_timer.restart(); + + if (m_mode == VSyncMode) { + // If a frame is skipped, either because rendering was slow or because + // the QML was slow, we accept it and continue advancing with a single + // vsync tick. The reason for this is that by the time we notice this + // on the GUI thread, the temporal distortion has already gone to screen + // and by catching up, we will introduce a second distortion which will + // worse. We accept that the animation time falls behind wall time because + // it comes out looking better. + // Only when multiple bad frames are hit in a row, do we consider + // switching. A few really bad frames and we switch right away. For frames + // just above the vsync delta, we tolerate a bit more since a buffered + // driver can have vsync deltas on the form: 4, 21, 21, 2, 23, 16, and + // still manage to put the frames to screen at 16 ms intervals. In addition + // to that, we tolerate a 25% margin of error on the value of m_vsync + // reported from the system as this value is often not precise. + + m_time += m_vsync; + + if (delta > m_vsync * 5) { + ++m_reallyBad; + ++m_bad; + } else if (delta > m_vsync * 1.25) { + ++m_bad; + } else { + // reset counters on a good frame. + m_reallyBad = 0; + m_bad = 0; + } + + // rational for the 3 and 50. If we have several really bad frames + // in a row, that would indicate a huge performance problem and we should + // switch right away. For the case of m_bad, we're a bit more tolerant. + if (m_reallyBad > 3 || m_bad > 50) { + m_mode = TimerMode; + qCDebug(QSG_LOG_INFO, "animation driver switched to timer mode"); + } + + } else { + if (delta < 1.25 * m_vsync) { + ++m_good; + } else { + m_good = 0; + } + + // We've been solid for a while, switch back to vsync mode. Tolerance + // for switching back is lower than switching to timer mode, as we + // want to stay in vsync mode as much as possible. + if (m_good > 10 && !qsg_useConsistentTiming()) { + m_time = elapsed(); + m_mode = VSyncMode; + qCDebug(QSG_LOG_INFO, "animation driver switched to vsync mode"); + } + } + + advanceAnimation(); + } + + float m_time; + float m_vsync; + Mode m_mode; + QElapsedTimer m_timer; + int m_bad; + int m_reallyBad; + int m_good; +}; + class QSGTextureCleanupEvent : public QEvent { public: @@ -385,7 +516,7 @@ bool QSGContext::isDistanceFieldEnabled() const QAnimationDriver *QSGContext::createAnimationDriver(QObject *parent) { - return new QAnimationDriver(parent); + return new QSGAnimationDriver(parent); } QSGRenderContext::QSGRenderContext(QSGContext *context) @@ -726,4 +857,6 @@ void QSGRenderContext::initialize(QSGMaterialShader *shader) shader->initialize(); } +#include "qsgcontext.moc" + QT_END_NAMESPACE diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp index 4428918da5..720d9a6fd1 100644 --- a/src/quick/scenegraph/qsgrenderloop.cpp +++ b/src/quick/scenegraph/qsgrenderloop.cpp @@ -147,19 +147,6 @@ public: bool eventPending; }; -bool QSGRenderLoop::useConsistentTiming() -{ - bool bufferQueuing = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::BufferQueueingOpenGL); - // Enable fixed animation steps... - QByteArray fixed = qgetenv("QSG_FIXED_ANIMATION_STEP"); - bool fixedAnimationSteps = bufferQueuing; - if (fixed == "no") - fixedAnimationSteps = false; - else if (fixed.length()) - fixedAnimationSteps = true; - return fixedAnimationSteps; -} - QSGRenderLoop *QSGRenderLoop::instance() { if (!s_instance) { @@ -170,11 +157,6 @@ QSGRenderLoop *QSGRenderLoop::instance() s_instance = QSGContext::createWindowManager(); - if (useConsistentTiming()) { - QUnifiedTimer::instance(true)->setConsistentTiming(true); - qCDebug(QSG_LOG_INFO, "using fixed animation steps"); - } - if (!s_instance) { enum RenderLoopType { diff --git a/src/quick/scenegraph/qsgrenderloop_p.h b/src/quick/scenegraph/qsgrenderloop_p.h index daba3bc69d..e9b58c60ba 100644 --- a/src/quick/scenegraph/qsgrenderloop_p.h +++ b/src/quick/scenegraph/qsgrenderloop_p.h @@ -82,8 +82,6 @@ public: static QSGRenderLoop *instance(); static void setInstance(QSGRenderLoop *instance); - static bool useConsistentTiming(); - virtual bool interleaveIncubation() const { return false; } static void cleanup(); diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp index 7cce4593ab..ff32ccb358 100644 --- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp +++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -651,7 +651,6 @@ void QSGRenderThread::run() qCDebug(QSG_LOG_RENDERLOOP) << QSG_RT_PAD << "run()"; animatorDriver = sgrc->sceneGraphContext()->createAnimationDriver(0); animatorDriver->install(); - QUnifiedTimer::instance(true)->setConsistentTiming(QSGRenderLoop::useConsistentTiming()); if (QQmlDebugService::isDebuggingEnabled()) QQuickProfiler::registerAnimationCallback(); -- cgit v1.2.3