diff options
Diffstat (limited to 'src/quick/util/qquickanimatorjob.cpp')
-rw-r--r-- | src/quick/util/qquickanimatorjob.cpp | 424 |
1 files changed, 248 insertions, 176 deletions
diff --git a/src/quick/util/qquickanimatorjob.cpp b/src/quick/util/qquickanimatorjob.cpp index 1176cf1ff7..5dd16407b8 100644 --- a/src/quick/util/qquickanimatorjob.cpp +++ b/src/quick/util/qquickanimatorjob.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Gunnar Sletta <gunnar@sletta.org> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -54,12 +55,42 @@ QT_BEGIN_NAMESPACE +struct QQuickTransformAnimatorHelperStore +{ + QHash<QQuickItem *, QQuickTransformAnimatorJob::Helper *> store; + QMutex mutex; + + QQuickTransformAnimatorJob::Helper *acquire(QQuickItem *item) { + mutex.lock(); + QQuickTransformAnimatorJob::Helper *helper = store.value(item); + if (!helper) { + helper = new QQuickTransformAnimatorJob::Helper(); + helper->item = item; + store[item] = helper; + } else { + ++helper->ref; + } + mutex.unlock(); + return helper; + } + + void release(QQuickTransformAnimatorJob::Helper *helper) { + mutex.lock(); + if (--helper->ref == 0) { + store.remove(helper->item); + delete helper; + } + mutex.unlock(); + } +}; +Q_GLOBAL_STATIC(QQuickTransformAnimatorHelperStore, qquick_transform_animatorjob_helper_store); + QQuickAnimatorProxyJob::QQuickAnimatorProxyJob(QAbstractAnimationJob *job, QObject *item) - : m_controller(0) - , m_job(job) + : m_controller(nullptr) , m_internalState(State_Stopped) - , m_jobManagedByController(false) { + m_job.reset(job); + m_isRenderThreadProxy = true; m_animation = qobject_cast<QQuickAbstractAnimation *>(item); @@ -87,57 +118,58 @@ QQuickAnimatorProxyJob::QQuickAnimatorProxyJob(QAbstractAnimationJob *job, QObje QQuickItem *item = qobject_cast<QQuickItem *>(ctx); if (item->window()) setWindow(item->window()); - - qmlobject_connect(item, QQuickItem, SIGNAL(windowChanged(QQuickWindow*)), this, QQuickAnimatorProxyJob, SLOT(windowChanged(QQuickWindow*))); + connect(item, &QQuickItem::windowChanged, this, &QQuickAnimatorProxyJob::windowChanged); } } QQuickAnimatorProxyJob::~QQuickAnimatorProxyJob() { - deleteJob(); - if (m_controller) - m_controller->proxyWasDestroyed(this); -} - -void QQuickAnimatorProxyJob::deleteJob() -{ - if (m_job) { - // If we have a controller, we might have posted the job to be started - // so delete it through the controller to clean up properly. - if (m_controller) - m_controller->deleteJob(m_job); - - // We explicitly delete the job if the animator controller has never touched - // it. If it has, it will have ownership as well. - else if (!m_jobManagedByController) - delete m_job; - m_job = 0; - } + if (m_job && m_controller) + m_controller->cancel(m_job); + m_job.reset(); } QObject *QQuickAnimatorProxyJob::findAnimationContext(QQuickAbstractAnimation *a) { QObject *p = a->parent(); - while (p != 0 && qobject_cast<QQuickWindow *>(p) == 0 && qobject_cast<QQuickItem *>(p) == 0) + while (p != nullptr && qobject_cast<QQuickWindow *>(p) == nullptr && qobject_cast<QQuickItem *>(p) == nullptr) p = p->parent(); return p; } void QQuickAnimatorProxyJob::updateCurrentTime(int) { + // We do a simple check here to see if the animator has run and stopped on + // the render thread. isPendingStart() will perform a check against jobs + // that have been scheduled for start, but that will not yet have entered + // the actual running state. + // Secondly, we make an unprotected read of the job's state to figure out + // if it is running, but this is ok, since we're only reading the state + // and if the render thread should happen to be writing it concurrently, + // we might get the wrong value for this update, but then we'll simply + // pick it up on the next iterationm when the job is stopped and render + // thread is no longer using it. + if (m_internalState == State_Running + && !m_controller->isPendingStart(m_job) + && !m_job->isRunning()) { + stop(); + } } void QQuickAnimatorProxyJob::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State) { if (m_state == Running) { m_internalState = State_Starting; - if (m_controller) - m_controller->startJob(this, m_job); + if (m_controller) { + m_internalState = State_Running; + m_controller->start(m_job); + } + } else if (newState == Stopped) { syncBackCurrentValues(); m_internalState = State_Stopped; if (m_controller) { - m_controller->stopJob(this, m_job); + m_controller->cancel(m_job); } } } @@ -154,69 +186,54 @@ void QQuickAnimatorProxyJob::windowChanged(QQuickWindow *window) setWindow(window); } -void QQuickAnimatorProxyJob::controllerWasDeleted() -{ - m_controller = 0; - m_job = 0; -} - void QQuickAnimatorProxyJob::setWindow(QQuickWindow *window) { if (!window) { - stop(); - deleteJob(); - - // Upon leaving a window, we reset the controller. This means that - // animators will only enter the Starting phase and won't be making - // calls to QQuickAnimatorController::startjob(). - if (m_controller) - m_controller->proxyWasDestroyed(this); - m_controller = 0; + if (m_job && m_controller) + m_controller->cancel(m_job); + m_controller = nullptr; } else if (!m_controller && m_job) { m_controller = QQuickWindowPrivate::get(window)->animationController; if (window->isSceneGraphInitialized()) readyToAnimate(); else - connect(window, SIGNAL(sceneGraphInitialized()), this, SLOT(sceneGraphInitialized())); + connect(window, &QQuickWindow::sceneGraphInitialized, this, &QQuickAnimatorProxyJob::sceneGraphInitialized); } } void QQuickAnimatorProxyJob::sceneGraphInitialized() { + disconnect(m_controller->window(), &QQuickWindow::sceneGraphInitialized, this, &QQuickAnimatorProxyJob::sceneGraphInitialized); readyToAnimate(); - disconnect(this, SLOT(sceneGraphInitialized())); } void QQuickAnimatorProxyJob::readyToAnimate() { - if (m_internalState == State_Starting) - m_controller->startJob(this, m_job); -} - -void QQuickAnimatorProxyJob::startedByController() -{ - m_internalState = State_Running; + Q_ASSERT(m_controller); + if (m_internalState == State_Starting) { + m_internalState = State_Running; + m_controller->start(m_job); + } } static void qquick_syncback_helper(QAbstractAnimationJob *job) { if (job->isRenderThreadJob()) { - QQuickAnimatorJob *a = static_cast<QQuickAnimatorJob *>(job); - // Sync back only those jobs that actually have been running - if (a->controller() && a->hasBeenRunning()) - a->writeBack(); + static_cast<QQuickAnimatorJob *>(job)->writeBack(); + } else if (job->isGroup()) { QAnimationGroupJob *g = static_cast<QAnimationGroupJob *>(job); for (QAbstractAnimationJob *a = g->firstChild(); a; a = a->nextSibling()) qquick_syncback_helper(a); } + } void QQuickAnimatorProxyJob::syncBackCurrentValues() { if (m_job) - qquick_syncback_helper(m_job); + qquick_syncback_helper(m_job.data()); } QQuickAnimatorJob::QQuickAnimatorJob() @@ -228,7 +245,6 @@ QQuickAnimatorJob::QQuickAnimatorJob() , m_duration(0) , m_isTransform(false) , m_isUniform(false) - , m_hasBeenRunning(false) { m_isRenderThreadJob = true; } @@ -244,13 +260,16 @@ qreal QQuickAnimatorJob::progress(int time) const { return m_easing.valueForProgress((m_duration == 0) ? qreal(1) : qreal(time) / qreal(m_duration)); } + qreal QQuickAnimatorJob::value() const { - qreal v; - m_controller->lock(); - v = m_value; - m_controller->unlock(); - return v; + qreal value = m_to; + if (m_controller) { + m_controller->lock(); + value = m_value; + m_controller->unlock(); + } + return value; } void QQuickAnimatorJob::setTarget(QQuickItem *target) @@ -263,62 +282,81 @@ void QQuickAnimatorJob::initialize(QQuickAnimatorController *controller) m_controller = controller; } -void QQuickAnimatorJob::targetWasDeleted() -{ - m_target = 0; - m_controller = 0; -} - QQuickTransformAnimatorJob::QQuickTransformAnimatorJob() - : m_helper(0) + : m_helper(nullptr) { m_isTransform = true; } QQuickTransformAnimatorJob::~QQuickTransformAnimatorJob() { - if (m_helper && --m_helper->ref == 0) { - // The only condition for not having a controller is when target was - // destroyed, in which case we have neither m_helper nor m_contorller. - Q_ASSERT(m_controller); - Q_ASSERT(m_helper->item); - m_controller->m_transforms.remove(m_helper->item); - delete m_helper; - } + if (m_helper) + qquick_transform_animatorjob_helper_store()->release(m_helper); } -void QQuickTransformAnimatorJob::initialize(QQuickAnimatorController *controller) +void QQuickTransformAnimatorJob::setTarget(QQuickItem *item) { - QQuickAnimatorJob::initialize(controller); + // In the extremely unlikely event that the target of an animator has been + // changed into a new item that sits in the exact same pointer address, we + // want to force syncing it again. + if (m_helper && m_target) + m_helper->wasSynced = false; + QQuickAnimatorJob::setTarget(item); +} - if (m_controller) { - bool newHelper = m_helper == 0; - m_helper = m_controller->m_transforms.value(m_target); - if (!m_helper) { - m_helper = new Helper(); - m_helper->item = m_target; - m_controller->m_transforms.insert(m_target, m_helper); - QObject::connect(m_target, SIGNAL(destroyed(QObject*)), m_controller, SLOT(itemDestroyed(QObject*)), Qt::DirectConnection); - } else { - if (newHelper) // only add reference the first time around.. - ++m_helper->ref; - // Make sure leftovers from previous runs are being used... - m_helper->wasSynced = false; - } - m_helper->sync(); +void QQuickTransformAnimatorJob::preSync() +{ + // If the target has changed or become null, release and reset the helper + if (m_helper && (m_helper->item != m_target || !m_target)) { + qquick_transform_animatorjob_helper_store()->release(m_helper); + m_helper = nullptr; } + + if (!m_target) + return; + + if (!m_helper) { + m_helper = qquick_transform_animatorjob_helper_store()->acquire(m_target); + + // This is a bit superfluous, but it ends up being simpler than the + // alternative. When an item happens to land on the same address as a + // previous item, that helper might not have been fully cleaned up by + // the time it gets taken back into use. As an alternative to storing + // connections to each and every item's QObject::destroyed() and + // having to clean those up afterwards, we simply sync all helpers on + // the first run. The sync is only done once for the run of an + // animation and it is a fairly light function (compared to storing + // potentially thousands of connections and managing their lifetime. + m_helper->wasSynced = false; + } + + m_helper->sync(); } -void QQuickTransformAnimatorJob::nodeWasDestroyed() +void QQuickTransformAnimatorJob::postSync() { - if (m_helper) - m_helper->node = 0; + Q_ASSERT((m_helper != nullptr) == (m_target != nullptr)); // If there is a target, there should also be a helper, ref: preSync + Q_ASSERT(!m_helper || m_helper->item == m_target); // If there is a helper, it should point to our target + + if (!m_target || !m_helper) { + invalidate(); + return; + } + + QQuickItemPrivate *d = QQuickItemPrivate::get(m_target); + if (d->extra.isAllocated() + && d->extra->layer + && d->extra->layer->enabled()) { + d = QQuickItemPrivate::get(d->extra->layer->m_effectSource); + } + + m_helper->node = d->itemNode(); } -void QQuickTransformAnimatorJob::targetWasDeleted() +void QQuickTransformAnimatorJob::invalidate() { - m_helper = 0; - QQuickAnimatorJob::targetWasDeleted(); + if (m_helper) + m_helper->node = nullptr; } void QQuickTransformAnimatorJob::Helper::sync() @@ -366,7 +404,7 @@ void QQuickTransformAnimatorJob::Helper::sync() } } -void QQuickTransformAnimatorJob::Helper::apply() +void QQuickTransformAnimatorJob::Helper::commit() { if (!wasChanged || !node) return; @@ -382,7 +420,11 @@ void QQuickTransformAnimatorJob::Helper::apply() wasChanged = false; } - +void QQuickTransformAnimatorJob::commit() +{ + if (m_helper) + m_helper->commit(); +} void QQuickXAnimatorJob::writeBack() { @@ -392,7 +434,10 @@ void QQuickXAnimatorJob::writeBack() void QQuickXAnimatorJob::updateCurrentTime(int time) { - if (!m_controller) +#if QT_CONFIG(opengl) + Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread()); +#endif + if (!m_helper) return; m_value = m_from + (m_to - m_from) * progress(time); @@ -408,7 +453,10 @@ void QQuickYAnimatorJob::writeBack() void QQuickYAnimatorJob::updateCurrentTime(int time) { - if (!m_controller) +#if QT_CONFIG(opengl) + Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread()); +#endif + if (!m_helper) return; m_value = m_from + (m_to - m_from) * progress(time); @@ -416,14 +464,87 @@ void QQuickYAnimatorJob::updateCurrentTime(int time) m_helper->wasChanged = true; } +void QQuickScaleAnimatorJob::writeBack() +{ + if (m_target) + m_target->setScale(value()); +} + +void QQuickScaleAnimatorJob::updateCurrentTime(int time) +{ +#if QT_CONFIG(opengl) + Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread()); +#endif + if (!m_helper) + return; + + m_value = m_from + (m_to - m_from) * progress(time); + m_helper->scale = m_value; + m_helper->wasChanged = true; +} + + +QQuickRotationAnimatorJob::QQuickRotationAnimatorJob() + : m_direction(QQuickRotationAnimator::Numerical) +{ +} + +extern QVariant _q_interpolateShortestRotation(qreal &f, qreal &t, qreal progress); +extern QVariant _q_interpolateClockwiseRotation(qreal &f, qreal &t, qreal progress); +extern QVariant _q_interpolateCounterclockwiseRotation(qreal &f, qreal &t, qreal progress); + +void QQuickRotationAnimatorJob::updateCurrentTime(int time) +{ +#if QT_CONFIG(opengl) + Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread()); +#endif + if (!m_helper) + return; + + float t = progress(time); + + switch (m_direction) { + case QQuickRotationAnimator::Clockwise: + m_value = _q_interpolateClockwiseRotation(m_from, m_to, t).toFloat(); + // The logic in _q_interpolateClockwise comes out a bit wrong + // for the case of X->0 where 0<X<360. It ends on 360 which it + // shouldn't. + if (t == 1) + m_value = m_to; + break; + case QQuickRotationAnimator::Counterclockwise: + m_value = _q_interpolateCounterclockwiseRotation(m_from, m_to, t).toFloat(); + break; + case QQuickRotationAnimator::Shortest: + m_value = _q_interpolateShortestRotation(m_from, m_to, t).toFloat(); + break; + case QQuickRotationAnimator::Numerical: + m_value = m_from + (m_to - m_from) * t; + break; + } + m_helper->rotation = m_value; + m_helper->wasChanged = true; +} + +void QQuickRotationAnimatorJob::writeBack() +{ + if (m_target) + m_target->setRotation(value()); +} + + QQuickOpacityAnimatorJob::QQuickOpacityAnimatorJob() - : m_opacityNode(0) + : m_opacityNode(nullptr) { } -void QQuickOpacityAnimatorJob::initialize(QQuickAnimatorController *controller) +void QQuickOpacityAnimatorJob::postSync() { - QQuickAnimatorJob::initialize(controller); + if (!m_target) { + invalidate(); + return; + } + QQuickItemPrivate *d = QQuickItemPrivate::get(m_target); #if QT_CONFIG(quick_shadereffect) if (d->extra.isAllocated() @@ -434,6 +555,7 @@ void QQuickOpacityAnimatorJob::initialize(QQuickAnimatorController *controller) #endif m_opacityNode = d->opacityNode(); + if (!m_opacityNode) { m_opacityNode = new QSGOpacityNode(); @@ -464,11 +586,12 @@ void QQuickOpacityAnimatorJob::initialize(QQuickAnimatorController *controller) d->extra.value().opacityNode = m_opacityNode; } + Q_ASSERT(m_opacityNode); } -void QQuickOpacityAnimatorJob::nodeWasDestroyed() +void QQuickOpacityAnimatorJob::invalidate() { - m_opacityNode = 0; + m_opacityNode = nullptr; } void QQuickOpacityAnimatorJob::writeBack() @@ -479,77 +602,21 @@ void QQuickOpacityAnimatorJob::writeBack() void QQuickOpacityAnimatorJob::updateCurrentTime(int time) { - if (!m_controller || !m_opacityNode) - return; - - m_value = m_from + (m_to - m_from) * progress(time); - m_opacityNode->setOpacity(m_value); -} - -void QQuickScaleAnimatorJob::writeBack() -{ - if (m_target) - m_target->setScale(value()); -} +#if QT_CONFIG(opengl) + Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread()); +#endif -void QQuickScaleAnimatorJob::updateCurrentTime(int time) -{ - if (!m_controller) + if (!m_opacityNode) return; m_value = m_from + (m_to - m_from) * progress(time); - m_helper->scale = m_value; - m_helper->wasChanged = true; -} - -QQuickRotationAnimatorJob::QQuickRotationAnimatorJob() - : m_direction(QQuickRotationAnimator::Numerical) -{ -} - -extern QVariant _q_interpolateShortestRotation(qreal &f, qreal &t, qreal progress); -extern QVariant _q_interpolateClockwiseRotation(qreal &f, qreal &t, qreal progress); -extern QVariant _q_interpolateCounterclockwiseRotation(qreal &f, qreal &t, qreal progress); - -void QQuickRotationAnimatorJob::updateCurrentTime(int time) -{ - if (!m_controller) - return; - - float t = progress(time); - - switch (m_direction) { - case QQuickRotationAnimator::Clockwise: - m_value = _q_interpolateClockwiseRotation(m_from, m_to, t).toFloat(); - // The logic in _q_interpolateClockwise comes out a bit wrong - // for the case of X->0 where 0<X<360. It ends on 360 which it - // shouldn't. - if (t == 1) - m_value = m_to; - break; - case QQuickRotationAnimator::Counterclockwise: - m_value = _q_interpolateCounterclockwiseRotation(m_from, m_to, t).toFloat(); - break; - case QQuickRotationAnimator::Shortest: - m_value = _q_interpolateShortestRotation(m_from, m_to, t).toFloat(); - break; - case QQuickRotationAnimator::Numerical: - m_value = m_from + (m_to - m_from) * t; - break; - } - m_helper->rotation = m_value; - m_helper->wasChanged = true; + m_opacityNode->setOpacity(m_value); } -void QQuickRotationAnimatorJob::writeBack() -{ - if (m_target) - m_target->setRotation(value()); -} #if QT_CONFIG(quick_shadereffect) && QT_CONFIG(opengl) QQuickUniformAnimatorJob::QQuickUniformAnimatorJob() - : m_node(0) + : m_node(nullptr) , m_uniformIndex(-1) , m_uniformType(-1) { @@ -558,19 +625,24 @@ QQuickUniformAnimatorJob::QQuickUniformAnimatorJob() void QQuickUniformAnimatorJob::setTarget(QQuickItem *target) { - if (qobject_cast<QQuickOpenGLShaderEffect *>(target) != 0) + if (qobject_cast<QQuickOpenGLShaderEffect *>(target) != nullptr) m_target = target; } -void QQuickUniformAnimatorJob::nodeWasDestroyed() +void QQuickUniformAnimatorJob::invalidate() { - m_node = 0; + m_node = nullptr; m_uniformIndex = -1; m_uniformType = -1; } -void QQuickUniformAnimatorJob::afterNodeSync() +void QQuickUniformAnimatorJob::postSync() { + if (!m_target) { + invalidate(); + return; + } + m_node = static_cast<QQuickOpenGLShaderEffectNode *>(QQuickItemPrivate::get(m_target)->paintNode); if (m_node && m_uniformIndex == -1 && m_uniformType == -1) { |