| /**************************************************************************** |
| ** |
| ** 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. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquickanimatorcontroller_p.h" |
| #include "qquickanimatorjob_p.h" |
| #include "qquickanimator_p.h" |
| #include "qquickanimator_p_p.h" |
| #include <private/qquickwindow_p.h> |
| #include <private/qquickitem_p.h> |
| #if QT_CONFIG(quick_shadereffect) && QT_CONFIG(opengl) |
| # include <private/qquickopenglshadereffectnode_p.h> |
| # include <private/qquickopenglshadereffect_p.h> |
| # 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, QObject *item) |
| : m_controller(nullptr) |
| , m_internalState(State_Stopped) |
| { |
| m_job.reset(job); |
| |
| m_isRenderThreadProxy = true; |
| m_animation = qobject_cast<QQuickAbstractAnimation *>(item); |
| |
| 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(m_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); |
| } |
| } |
| |
| 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; |
| |
| // 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); |
| } |
| } |
| } |
| |
| 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->firstChild(); a; a = a->nextSibling()) |
| 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)); |
| } |
| |
| 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; |
| } |
| |
| if (dirty == 0) |
| return; |
| |
| node = d->itemNode(); |
| |
| 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 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->dx = m_value; |
| m_helper->wasChanged = true; |
| } |
| |
| void QQuickYAnimatorJob::writeBack() |
| { |
| if (m_target) |
| m_target->setY(value()); |
| } |
| |
| void QQuickYAnimatorJob::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->dy = m_value; |
| 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(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 QT_CONFIG(opengl) |
| Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread()); |
| #endif |
| |
| if (!m_opacityNode) |
| return; |
| |
| m_value = m_from + (m_to - m_from) * progress(time); |
| m_opacityNode->setOpacity(m_value); |
| } |
| |
| |
| #if QT_CONFIG(quick_shadereffect) && QT_CONFIG(opengl) |
| QQuickUniformAnimatorJob::QQuickUniformAnimatorJob() |
| : m_node(nullptr) |
| , m_uniformIndex(-1) |
| , m_uniformType(-1) |
| { |
| m_isUniform = true; |
| } |
| |
| void QQuickUniformAnimatorJob::setTarget(QQuickItem *target) |
| { |
| QQuickShaderEffect* effect = qobject_cast<QQuickShaderEffect*>(target); |
| if (effect && effect->isOpenGLShaderEffect()) |
| m_target = target; |
| } |
| |
| void QQuickUniformAnimatorJob::invalidate() |
| { |
| m_node = nullptr; |
| m_uniformIndex = -1; |
| m_uniformType = -1; |
| } |
| |
| 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) { |
| QQuickOpenGLShaderEffectMaterial *material = |
| static_cast<QQuickOpenGLShaderEffectMaterial *>(m_node->material()); |
| bool found = false; |
| for (int t=0; !found && t<QQuickOpenGLShaderEffectMaterialKey::ShaderTypeCount; ++t) { |
| const QVector<QQuickOpenGLShaderEffectMaterial::UniformData> &uniforms = material->uniforms[t]; |
| for (int i=0; i<uniforms.size(); ++i) { |
| if (uniforms.at(i).name == m_uniform) { |
| m_uniformIndex = i; |
| m_uniformType = t; |
| found = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| } |
| |
| void QQuickUniformAnimatorJob::updateCurrentTime(int time) |
| { |
| if (!m_controller) |
| return; |
| |
| if (!m_node || m_uniformIndex == -1 || m_uniformType == -1) |
| return; |
| |
| m_value = m_from + (m_to - m_from) * progress(time); |
| |
| QQuickOpenGLShaderEffectMaterial *material = |
| static_cast<QQuickOpenGLShaderEffectMaterial *>(m_node->material()); |
| material->uniforms[m_uniformType][m_uniformIndex].value = m_value; |
| // As we're not touching the nodes, we need to explicitly mark it dirty. |
| // Otherwise, the renderer will abort repainting if this was the only |
| // change in the graph currently rendering. |
| m_node->markDirty(QSGNode::DirtyMaterial); |
| } |
| |
| void QQuickUniformAnimatorJob::writeBack() |
| { |
| if (m_target) |
| m_target->setProperty(m_uniform, value()); |
| } |
| #endif |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qquickanimatorjob_p.cpp" |