Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // Copyright (C) 2016 Gunnar Sletta <gunnar@sletta.org>
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qquickanimatorcontroller_p.h"
++ #include "qquickanimatorjob_p.h"
++ #include "qquickanimator_p.h"
++ #include "qquickanimator_p_p.h"
++ #include <private/qquickitem_p.h>
++ #if QT_CONFIG(quick_shadereffect)
++ #include <private/qquickshadereffect_p.h>
++ #endif
++ #include <private/qanimationgroupjob_p.h>
++
++ #include <qcoreapplication.h>
++ #include <qdebug.h>
++
++ 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,
++ QQuickAbstractAnimation *animation)
++ : m_controller(nullptr)
++ , m_internalState(State_Stopped)
++ {
++ m_job.reset(job);
++
++ m_isRenderThreadProxy = true;
++
++ setLoopCount(job->loopCount());
++
++ // Instead of setting duration to job->duration() we need to set it to -1 so that
++ // it runs as long as the job is running on the render thread. If we gave it
++ // an explicit duration, it would be stopped, potentially stopping the RT animation
++ // prematurely.
++ // This means that the animation driver will tick on the GUI thread as long
++ // as the animation is running on the render thread, but this overhead will
++ // be negligiblie compared to animating and re-rendering the scene on the render thread.
++ m_duration = -1;
++
++ QObject *ctx = findAnimationContext(animation);
++ if (!ctx) {
++ qWarning("QtQuick: unable to find animation context for RT animation...");
++ return;
++ }
++
++ QQuickWindow *window = qobject_cast<QQuickWindow *>(ctx);
++ if (window) {
++ setWindow(window);
++ } else {
++ QQuickItem *item = qobject_cast<QQuickItem *>(ctx);
++ if (item->window())
++ setWindow(item->window());
++ connect(item, &QQuickItem::windowChanged, this, &QQuickAnimatorProxyJob::windowChanged);
++ }
++ }
++
++ void QQuickAnimatorProxyJob::updateLoopCount(int loopCount)
++ {
++ m_job->setLoopCount(loopCount);
++ }
++
++ QQuickAnimatorProxyJob::~QQuickAnimatorProxyJob()
++ {
++ if (m_job && m_controller)
++ m_controller->cancel(m_job);
++ m_job.reset();
++ }
++
++ QObject *QQuickAnimatorProxyJob::findAnimationContext(QQuickAbstractAnimation *a)
++ {
++ QObject *p = a->parent();
++ while (p != nullptr && qobject_cast<QQuickWindow *>(p) == nullptr && qobject_cast<QQuickItem *>(p) == nullptr)
++ p = p->parent();
++ return p;
++ }
++
++ void QQuickAnimatorProxyJob::updateCurrentTime(int)
++ {
++ if (m_internalState != State_Running)
++ return;
++
++ // Copy current loop number from the job
++ // we could make currentLoop() virtual but it would be less efficient
++ m_currentLoop = m_job->currentLoop();
++
++ // A proxy which is being ticked should be associated with a window, (see
++ // setWindow() below). If we get here when there is no more controller we
++ // have a problem.
++ Q_ASSERT(m_controller);
++
++ // 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_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_internalState = State_Running;
++ m_controller->start(m_job);
++ }
++
++ } else if (newState == Stopped) {
++ m_internalState = State_Stopped;
++ if (m_controller) {
++ syncBackCurrentValues();
++ m_controller->cancel(m_job);
++ }
++ } else if (newState == Paused) {
++ m_internalState = State_Paused;
++ if (m_controller) {
++ syncBackCurrentValues();
++ m_controller->pause(m_job);
++ }
++ }
++ }
++
++ void QQuickAnimatorProxyJob::debugAnimation(QDebug d) const
++ {
++ d << "QuickAnimatorProxyJob("<< Qt::hex << (const void *) this << Qt::dec
++ << "state:" << state() << "duration:" << duration()
++ << "proxying: (" << job() << ')';
++ }
++
++ void QQuickAnimatorProxyJob::windowChanged(QQuickWindow *window)
++ {
++ setWindow(window);
++ }
++
++ void QQuickAnimatorProxyJob::setWindow(QQuickWindow *window)
++ {
++ if (!window) {
++ if (m_job && m_controller) {
++ disconnect(m_controller->window(), &QQuickWindow::sceneGraphInitialized,
++ this, &QQuickAnimatorProxyJob::sceneGraphInitialized);
++ m_controller->cancel(m_job);
++ }
++
++ m_controller = nullptr;
++ stop();
++
++ } else if (!m_controller && m_job) {
++ m_controller = QQuickWindowPrivate::get(window)->animationController.get();
++ if (window->isSceneGraphInitialized())
++ readyToAnimate();
++ else
++ connect(window, &QQuickWindow::sceneGraphInitialized, this, &QQuickAnimatorProxyJob::sceneGraphInitialized);
++ }
++ }
++
++ void QQuickAnimatorProxyJob::sceneGraphInitialized()
++ {
++ if (m_controller) {
++ disconnect(m_controller->window(), &QQuickWindow::sceneGraphInitialized, this, &QQuickAnimatorProxyJob::sceneGraphInitialized);
++ readyToAnimate();
++ }
++ }
++
++ void QQuickAnimatorProxyJob::readyToAnimate()
++ {
++ 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()) {
++ static_cast<QQuickAnimatorJob *>(job)->writeBack();
++
++ } else if (job->isGroup()) {
++ QAnimationGroupJob *g = static_cast<QAnimationGroupJob *>(job);
++ for (QAbstractAnimationJob *a : *g->children())
++ qquick_syncback_helper(a);
++ }
++
++ }
++
++ void QQuickAnimatorProxyJob::syncBackCurrentValues()
++ {
++ if (m_job)
++ qquick_syncback_helper(m_job.data());
++ }
++
++ QQuickAnimatorJob::QQuickAnimatorJob()
++ : m_target(nullptr)
++ , m_controller(nullptr)
++ , m_from(0)
++ , m_to(0)
++ , m_value(0)
++ , m_duration(0)
++ , m_isTransform(false)
++ , m_isUniform(false)
++ {
++ m_isRenderThreadJob = true;
++ }
++
++ void QQuickAnimatorJob::debugAnimation(QDebug d) const
++ {
++ d << "QuickAnimatorJob(" << Qt::hex << (const void *) this << Qt::dec
++ << ") state:" << state() << "duration:" << duration()
++ << "target:" << m_target << "value:" << m_value;
++ }
++
++ qreal QQuickAnimatorJob::progress(int time) const
++ {
++ return m_easing.valueForProgress((m_duration == 0) ? qreal(1) : qreal(time) / qreal(m_duration));
++ }
++
++ void QQuickAnimatorJob::boundValue()
++ {
++ qreal rangeMin = m_from;
++ qreal rangeMax = m_to;
++ if (m_from > m_to) {
++ rangeMax = m_from;
++ rangeMin = m_to;
++ }
++ m_value = qBound(rangeMin, m_value, rangeMax);
++ }
++
++ qreal QQuickAnimatorJob::value() const
++ {
++ qreal value = m_to;
++ if (m_controller) {
++ m_controller->lock();
++ value = m_value;
++ m_controller->unlock();
++ }
++ return value;
++ }
++
++ void QQuickAnimatorJob::setTarget(QQuickItem *target)
++ {
++ m_target = target;
++ }
++
++ void QQuickAnimatorJob::initialize(QQuickAnimatorController *controller)
++ {
++ m_controller = controller;
++ }
++
++ QQuickTransformAnimatorJob::QQuickTransformAnimatorJob()
++ : m_helper(nullptr)
++ {
++ m_isTransform = true;
++ }
++
++ QQuickTransformAnimatorJob::~QQuickTransformAnimatorJob()
++ {
++ if (m_helper)
++ qquick_transform_animatorjob_helper_store()->release(m_helper);
++ }
++
++ void QQuickTransformAnimatorJob::setTarget(QQuickItem *item)
++ {
++ // 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);
++ }
++
++ 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) {
++ invalidate();
++ 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::invalidate()
++ {
++ if (m_helper)
++ m_helper->node = nullptr;
++ }
++
++ void QQuickTransformAnimatorJob::Helper::sync()
++ {
++ const quint32 mask = QQuickItemPrivate::Position
++ | QQuickItemPrivate::BasicTransform
++ | QQuickItemPrivate::TransformOrigin
++ | QQuickItemPrivate::Size;
++
++ QQuickItemPrivate *d = QQuickItemPrivate::get(item);
++ #if QT_CONFIG(quick_shadereffect)
++ if (d->extra.isAllocated()
++ && d->extra->layer
++ && d->extra->layer->enabled()) {
++ d = QQuickItemPrivate::get(d->extra->layer->m_effectSource);
++ }
++ #endif
++
++ quint32 dirty = mask & d->dirtyAttributes;
++
++ if (!wasSynced) {
++ dirty = 0xffffffffu;
++ wasSynced = true;
++ }
++
++ // We update the node before checking on dirty, as the node might have changed without the animator running
++ node = d->itemNode();
++
++ if (dirty == 0)
++ return;
++
++ if (dirty & QQuickItemPrivate::Position) {
++ dx = item->x();
++ dy = item->y();
++ }
++
++ if (dirty & QQuickItemPrivate::BasicTransform) {
++ scale = item->scale();
++ rotation = item->rotation();
++ }
++
++ if (dirty & (QQuickItemPrivate::TransformOrigin | QQuickItemPrivate::Size)) {
++ QPointF o = item->transformOriginPoint();
++ ox = o.x();
++ oy = o.y();
++ }
++ }
++
++ void QQuickTransformAnimatorJob::Helper::commit()
++ {
++ if (!wasChanged || !node)
++ return;
++
++ QMatrix4x4 m;
++ m.translate(dx, dy);
++ m.translate(ox, oy);
++ m.scale(scale);
++ m.rotate(rotation, 0, 0, 1);
++ m.translate(-ox, -oy);
++ node->setMatrix(m);
++
++ wasChanged = false;
++ }
++
++ void QQuickTransformAnimatorJob::commit()
++ {
++ if (m_helper)
++ m_helper->commit();
++ }
++
++ void QQuickXAnimatorJob::writeBack()
++ {
++ if (m_target)
++ m_target->setX(value());
++ }
++
++ void QQuickXAnimatorJob::updateCurrentTime(int time)
++ {
++ if (!m_helper)
++ return;
++
++ m_value = m_from + (m_to - m_from) * progress(time);
++ m_helper->dx = m_value;
++ m_helper->wasChanged = true;
++ }
++
++ void QQuickYAnimatorJob::writeBack()
++ {
++ if (m_target)
++ m_target->setY(value());
++ }
++
++ void QQuickYAnimatorJob::updateCurrentTime(int time)
++ {
++ if (!m_helper)
++ return;
++
++ m_value = m_from + (m_to - m_from) * progress(time);
++ m_helper->dy = m_value;
++ m_helper->wasChanged = true;
++ }
++
++ void QQuickScaleAnimatorJob::writeBack()
++ {
++ if (m_target)
++ m_target->setScale(value());
++ }
++
++ void QQuickScaleAnimatorJob::updateCurrentTime(int time)
++ {
++ 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 (!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(nullptr)
++ {
++ }
++
++ void QQuickOpacityAnimatorJob::postSync()
++ {
++ if (!m_target) {
++ invalidate();
++ return;
++ }
++
++ QQuickItemPrivate *d = QQuickItemPrivate::get(m_target);
++ #if QT_CONFIG(quick_shadereffect)
++ if (d->extra.isAllocated()
++ && d->extra->layer
++ && d->extra->layer->enabled()) {
++ d = QQuickItemPrivate::get(d->extra->layer->m_effectSource);
++ }
++ #endif
++
++ m_opacityNode = d->opacityNode();
++
++ if (!m_opacityNode) {
++ m_opacityNode = new QSGOpacityNode();
++
++ /* The item node subtree is like this
++ *
++ * itemNode
++ * (opacityNode) optional
++ * (clipNode) optional
++ * (rootNode) optional
++ * children / paintNode
++ *
++ * If the opacity node doesn't exist, we need to insert it into
++ * the hierarchy between itemNode and clipNode or rootNode. If
++ * neither clip or root exists, we need to reparent all children
++ * from itemNode to opacityNode.
++ */
++ QSGNode *iNode = d->itemNode();
++ QSGNode *child = d->childContainerNode();
++ if (child != iNode) {
++ if (child->parent())
++ child->parent()->removeChildNode(child);
++ m_opacityNode->appendChildNode(child);
++ iNode->appendChildNode(m_opacityNode);
++ } else {
++ iNode->reparentChildNodesTo(m_opacityNode);
++ iNode->appendChildNode(m_opacityNode);
++ }
++
++ d->extra.value().opacityNode = m_opacityNode;
++ updateCurrentTime(0);
++ }
++ Q_ASSERT(m_opacityNode);
++ }
++
++ void QQuickOpacityAnimatorJob::invalidate()
++ {
++ m_opacityNode = nullptr;
++ }
++
++ void QQuickOpacityAnimatorJob::writeBack()
++ {
++ if (m_target)
++ m_target->setOpacity(value());
++ }
++
++ void QQuickOpacityAnimatorJob::updateCurrentTime(int time)
++ {
++ if (!m_opacityNode)
++ return;
++
++ m_value = m_from + (m_to - m_from) * progress(time);
++ m_opacityNode->setOpacity(m_value);
++ }
++
++ #if QT_CONFIG(quick_shadereffect)
++ QQuickUniformAnimatorJob::QQuickUniformAnimatorJob()
++ {
++ m_isUniform = true;
++ }
++
++ void QQuickUniformAnimatorJob::setTarget(QQuickItem *target)
++ {
++ // Check target is of expected type
++ if (qobject_cast<QQuickShaderEffect *>(target) != nullptr)
++ m_target = target;
++ }
++
++ void QQuickUniformAnimatorJob::updateCurrentTime(int time)
++ {
++ if (!m_effect || m_target != m_effect)
++ return;
++
++ m_value = m_from + (m_to - m_from) * progress(time);
++ m_effect->updateUniformValue(m_uniform, m_value);
++ }
++
++ void QQuickUniformAnimatorJob::writeBack()
++ {
++ if (m_target)
++ m_target->setProperty(m_uniform, value());
++ }
++
++ void QQuickUniformAnimatorJob::postSync()
++ {
++ if (!m_target) {
++ invalidate();
++ return;
++ }
++
++ m_effect = qobject_cast<QQuickShaderEffect *>(m_target);
++ }
++
++ void QQuickUniformAnimatorJob::invalidate()
++ {
++
++ }
++ #endif
++
++ QT_END_NAMESPACE
++
++ #include "moc_qquickanimatorjob_p.cpp"