1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Copyright (C) 2016 Gunnar Sletta <gunnar@sletta.org>
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the QtQuick module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#include "qquickanimatorcontroller_p.h"
42#include "qquickanimatorjob_p.h"
43#include "qquickanimator_p.h"
44#include "qquickanimator_p_p.h"
45#include <private/qquickwindow_p.h>
46#include <private/qquickitem_p.h>
47#if QT_CONFIG(quick_shadereffect) && QT_CONFIG(opengl)
48# include <private/qquickopenglshadereffectnode_p.h>
49# include <private/qquickopenglshadereffect_p.h>
50# include <private/qquickshadereffect_p.h>
51#endif
52#include <private/qanimationgroupjob_p.h>
53
54#include <qcoreapplication.h>
55#include <qdebug.h>
56
57QT_BEGIN_NAMESPACE
58
59struct QQuickTransformAnimatorHelperStore
60{
61 QHash<QQuickItem *, QQuickTransformAnimatorJob::Helper *> store;
62 QMutex mutex;
63
64 QQuickTransformAnimatorJob::Helper *acquire(QQuickItem *item) {
65 mutex.lock();
66 QQuickTransformAnimatorJob::Helper *helper = store.value(key: item);
67 if (!helper) {
68 helper = new QQuickTransformAnimatorJob::Helper();
69 helper->item = item;
70 store[item] = helper;
71 } else {
72 ++helper->ref;
73 }
74 mutex.unlock();
75 return helper;
76 }
77
78 void release(QQuickTransformAnimatorJob::Helper *helper) {
79 mutex.lock();
80 if (--helper->ref == 0) {
81 store.remove(key: helper->item);
82 delete helper;
83 }
84 mutex.unlock();
85 }
86};
87Q_GLOBAL_STATIC(QQuickTransformAnimatorHelperStore, qquick_transform_animatorjob_helper_store);
88
89QQuickAnimatorProxyJob::QQuickAnimatorProxyJob(QAbstractAnimationJob *job, QObject *item)
90 : m_controller(nullptr)
91 , m_internalState(State_Stopped)
92{
93 m_job.reset(t: job);
94
95 m_isRenderThreadProxy = true;
96 m_animation = qobject_cast<QQuickAbstractAnimation *>(object: item);
97
98 setLoopCount(job->loopCount());
99
100 // Instead of setting duration to job->duration() we need to set it to -1 so that
101 // it runs as long as the job is running on the render thread. If we gave it
102 // an explicit duration, it would be stopped, potentially stopping the RT animation
103 // prematurely.
104 // This means that the animation driver will tick on the GUI thread as long
105 // as the animation is running on the render thread, but this overhead will
106 // be negligiblie compared to animating and re-rendering the scene on the render thread.
107 m_duration = -1;
108
109 QObject *ctx = findAnimationContext(m_animation);
110 if (!ctx) {
111 qWarning(msg: "QtQuick: unable to find animation context for RT animation...");
112 return;
113 }
114
115 QQuickWindow *window = qobject_cast<QQuickWindow *>(object: ctx);
116 if (window) {
117 setWindow(window);
118 } else {
119 QQuickItem *item = qobject_cast<QQuickItem *>(object: ctx);
120 if (item->window())
121 setWindow(item->window());
122 connect(sender: item, signal: &QQuickItem::windowChanged, receiver: this, slot: &QQuickAnimatorProxyJob::windowChanged);
123 }
124}
125
126void QQuickAnimatorProxyJob::updateLoopCount(int loopCount)
127{
128 m_job->setLoopCount(loopCount);
129}
130
131QQuickAnimatorProxyJob::~QQuickAnimatorProxyJob()
132{
133 if (m_job && m_controller)
134 m_controller->cancel(job: m_job);
135 m_job.reset();
136}
137
138QObject *QQuickAnimatorProxyJob::findAnimationContext(QQuickAbstractAnimation *a)
139{
140 QObject *p = a->parent();
141 while (p != nullptr && qobject_cast<QQuickWindow *>(object: p) == nullptr && qobject_cast<QQuickItem *>(object: p) == nullptr)
142 p = p->parent();
143 return p;
144}
145
146void QQuickAnimatorProxyJob::updateCurrentTime(int)
147{
148 if (m_internalState != State_Running)
149 return;
150
151 // Copy current loop number from the job
152 // we could make currentLoop() virtual but it would be less efficient
153 m_currentLoop = m_job->currentLoop();
154
155 // A proxy which is being ticked should be associated with a window, (see
156 // setWindow() below). If we get here when there is no more controller we
157 // have a problem.
158 Q_ASSERT(m_controller);
159
160 // We do a simple check here to see if the animator has run and stopped on
161 // the render thread. isPendingStart() will perform a check against jobs
162 // that have been scheduled for start, but that will not yet have entered
163 // the actual running state.
164 // Secondly, we make an unprotected read of the job's state to figure out
165 // if it is running, but this is ok, since we're only reading the state
166 // and if the render thread should happen to be writing it concurrently,
167 // we might get the wrong value for this update, but then we'll simply
168 // pick it up on the next iterationm when the job is stopped and render
169 // thread is no longer using it.
170 if (!m_controller->isPendingStart(job: m_job)
171 && !m_job->isRunning()) {
172 stop();
173 }
174}
175
176void QQuickAnimatorProxyJob::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State)
177{
178 if (m_state == Running) {
179 m_internalState = State_Starting;
180 if (m_controller) {
181 m_internalState = State_Running;
182 m_controller->start(job: m_job);
183 }
184
185 } else if (newState == Stopped) {
186 m_internalState = State_Stopped;
187 if (m_controller) {
188 syncBackCurrentValues();
189 m_controller->cancel(job: m_job);
190 }
191 }
192}
193
194void QQuickAnimatorProxyJob::debugAnimation(QDebug d) const
195{
196 d << "QuickAnimatorProxyJob("<< Qt::hex << (const void *) this << Qt::dec
197 << "state:" << state() << "duration:" << duration()
198 << "proxying: (" << job() << ')';
199}
200
201void QQuickAnimatorProxyJob::windowChanged(QQuickWindow *window)
202{
203 setWindow(window);
204}
205
206void QQuickAnimatorProxyJob::setWindow(QQuickWindow *window)
207{
208 if (!window) {
209 if (m_job && m_controller) {
210 disconnect(sender: m_controller->window(), signal: &QQuickWindow::sceneGraphInitialized,
211 receiver: this, slot: &QQuickAnimatorProxyJob::sceneGraphInitialized);
212 m_controller->cancel(job: m_job);
213 }
214
215 m_controller = nullptr;
216 stop();
217
218 } else if (!m_controller && m_job) {
219 m_controller = QQuickWindowPrivate::get(c: window)->animationController.get();
220 if (window->isSceneGraphInitialized())
221 readyToAnimate();
222 else
223 connect(sender: window, signal: &QQuickWindow::sceneGraphInitialized, receiver: this, slot: &QQuickAnimatorProxyJob::sceneGraphInitialized);
224 }
225}
226
227void QQuickAnimatorProxyJob::sceneGraphInitialized()
228{
229 if (m_controller) {
230 disconnect(sender: m_controller->window(), signal: &QQuickWindow::sceneGraphInitialized, receiver: this, slot: &QQuickAnimatorProxyJob::sceneGraphInitialized);
231 readyToAnimate();
232 }
233}
234
235void QQuickAnimatorProxyJob::readyToAnimate()
236{
237 Q_ASSERT(m_controller);
238 if (m_internalState == State_Starting) {
239 m_internalState = State_Running;
240 m_controller->start(job: m_job);
241 }
242}
243
244static void qquick_syncback_helper(QAbstractAnimationJob *job)
245{
246 if (job->isRenderThreadJob()) {
247 static_cast<QQuickAnimatorJob *>(job)->writeBack();
248
249 } else if (job->isGroup()) {
250 QAnimationGroupJob *g = static_cast<QAnimationGroupJob *>(job);
251 for (QAbstractAnimationJob *a = g->firstChild(); a; a = a->nextSibling())
252 qquick_syncback_helper(job: a);
253 }
254
255}
256
257void QQuickAnimatorProxyJob::syncBackCurrentValues()
258{
259 if (m_job)
260 qquick_syncback_helper(job: m_job.data());
261}
262
263QQuickAnimatorJob::QQuickAnimatorJob()
264 : m_target(nullptr)
265 , m_controller(nullptr)
266 , m_from(0)
267 , m_to(0)
268 , m_value(0)
269 , m_duration(0)
270 , m_isTransform(false)
271 , m_isUniform(false)
272{
273 m_isRenderThreadJob = true;
274}
275
276void QQuickAnimatorJob::debugAnimation(QDebug d) const
277{
278 d << "QuickAnimatorJob(" << Qt::hex << (const void *) this << Qt::dec
279 << ") state:" << state() << "duration:" << duration()
280 << "target:" << m_target << "value:" << m_value;
281}
282
283qreal QQuickAnimatorJob::progress(int time) const
284{
285 return m_easing.valueForProgress(progress: (m_duration == 0) ? qreal(1) : qreal(time) / qreal(m_duration));
286}
287
288void QQuickAnimatorJob::boundValue()
289{
290 qreal rangeMin = m_from;
291 qreal rangeMax = m_to;
292 if (m_from > m_to) {
293 rangeMax = m_from;
294 rangeMin = m_to;
295 }
296 m_value = qBound(min: rangeMin, val: m_value, max: rangeMax);
297}
298
299qreal QQuickAnimatorJob::value() const
300{
301 qreal value = m_to;
302 if (m_controller) {
303 m_controller->lock();
304 value = m_value;
305 m_controller->unlock();
306 }
307 return value;
308}
309
310void QQuickAnimatorJob::setTarget(QQuickItem *target)
311{
312 m_target = target;
313}
314
315void QQuickAnimatorJob::initialize(QQuickAnimatorController *controller)
316{
317 m_controller = controller;
318}
319
320QQuickTransformAnimatorJob::QQuickTransformAnimatorJob()
321 : m_helper(nullptr)
322{
323 m_isTransform = true;
324}
325
326QQuickTransformAnimatorJob::~QQuickTransformAnimatorJob()
327{
328 if (m_helper)
329 qquick_transform_animatorjob_helper_store()->release(helper: m_helper);
330}
331
332void QQuickTransformAnimatorJob::setTarget(QQuickItem *item)
333{
334 // In the extremely unlikely event that the target of an animator has been
335 // changed into a new item that sits in the exact same pointer address, we
336 // want to force syncing it again.
337 if (m_helper && m_target)
338 m_helper->wasSynced = false;
339 QQuickAnimatorJob::setTarget(item);
340}
341
342void QQuickTransformAnimatorJob::preSync()
343{
344 // If the target has changed or become null, release and reset the helper
345 if (m_helper && (m_helper->item != m_target || !m_target)) {
346 qquick_transform_animatorjob_helper_store()->release(helper: m_helper);
347 m_helper = nullptr;
348 }
349
350 if (!m_target) {
351 invalidate();
352 return;
353 }
354
355 if (!m_helper) {
356 m_helper = qquick_transform_animatorjob_helper_store()->acquire(item: m_target);
357
358 // This is a bit superfluous, but it ends up being simpler than the
359 // alternative. When an item happens to land on the same address as a
360 // previous item, that helper might not have been fully cleaned up by
361 // the time it gets taken back into use. As an alternative to storing
362 // connections to each and every item's QObject::destroyed() and
363 // having to clean those up afterwards, we simply sync all helpers on
364 // the first run. The sync is only done once for the run of an
365 // animation and it is a fairly light function (compared to storing
366 // potentially thousands of connections and managing their lifetime.
367 m_helper->wasSynced = false;
368 }
369
370 m_helper->sync();
371}
372
373void QQuickTransformAnimatorJob::invalidate()
374{
375 if (m_helper)
376 m_helper->node = nullptr;
377}
378
379void QQuickTransformAnimatorJob::Helper::sync()
380{
381 const quint32 mask = QQuickItemPrivate::Position
382 | QQuickItemPrivate::BasicTransform
383 | QQuickItemPrivate::TransformOrigin
384 | QQuickItemPrivate::Size;
385
386 QQuickItemPrivate *d = QQuickItemPrivate::get(item);
387#if QT_CONFIG(quick_shadereffect)
388 if (d->extra.isAllocated()
389 && d->extra->layer
390 && d->extra->layer->enabled()) {
391 d = QQuickItemPrivate::get(item: d->extra->layer->m_effectSource);
392 }
393#endif
394
395 quint32 dirty = mask & d->dirtyAttributes;
396
397 if (!wasSynced) {
398 dirty = 0xffffffffu;
399 wasSynced = true;
400 }
401
402 if (dirty == 0)
403 return;
404
405 node = d->itemNode();
406
407 if (dirty & QQuickItemPrivate::Position) {
408 dx = item->x();
409 dy = item->y();
410 }
411
412 if (dirty & QQuickItemPrivate::BasicTransform) {
413 scale = item->scale();
414 rotation = item->rotation();
415 }
416
417 if (dirty & (QQuickItemPrivate::TransformOrigin | QQuickItemPrivate::Size)) {
418 QPointF o = item->transformOriginPoint();
419 ox = o.x();
420 oy = o.y();
421 }
422}
423
424void QQuickTransformAnimatorJob::Helper::commit()
425{
426 if (!wasChanged || !node)
427 return;
428
429 QMatrix4x4 m;
430 m.translate(x: dx, y: dy);
431 m.translate(x: ox, y: oy);
432 m.scale(factor: scale);
433 m.rotate(angle: rotation, x: 0, y: 0, z: 1);
434 m.translate(x: -ox, y: -oy);
435 node->setMatrix(m);
436
437 wasChanged = false;
438}
439
440void QQuickTransformAnimatorJob::commit()
441{
442 if (m_helper)
443 m_helper->commit();
444}
445
446void QQuickXAnimatorJob::writeBack()
447{
448 if (m_target)
449 m_target->setX(value());
450}
451
452void QQuickXAnimatorJob::updateCurrentTime(int time)
453{
454#if QT_CONFIG(opengl)
455 Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread());
456#endif
457 if (!m_helper)
458 return;
459
460 m_value = m_from + (m_to - m_from) * progress(time);
461 m_helper->dx = m_value;
462 m_helper->wasChanged = true;
463}
464
465void QQuickYAnimatorJob::writeBack()
466{
467 if (m_target)
468 m_target->setY(value());
469}
470
471void QQuickYAnimatorJob::updateCurrentTime(int time)
472{
473#if QT_CONFIG(opengl)
474 Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread());
475#endif
476 if (!m_helper)
477 return;
478
479 m_value = m_from + (m_to - m_from) * progress(time);
480 m_helper->dy = m_value;
481 m_helper->wasChanged = true;
482}
483
484void QQuickScaleAnimatorJob::writeBack()
485{
486 if (m_target)
487 m_target->setScale(value());
488}
489
490void QQuickScaleAnimatorJob::updateCurrentTime(int time)
491{
492#if QT_CONFIG(opengl)
493 Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread());
494#endif
495 if (!m_helper)
496 return;
497
498 m_value = m_from + (m_to - m_from) * progress(time);
499 m_helper->scale = m_value;
500 m_helper->wasChanged = true;
501}
502
503
504QQuickRotationAnimatorJob::QQuickRotationAnimatorJob()
505 : m_direction(QQuickRotationAnimator::Numerical)
506{
507}
508
509extern QVariant _q_interpolateShortestRotation(qreal &f, qreal &t, qreal progress);
510extern QVariant _q_interpolateClockwiseRotation(qreal &f, qreal &t, qreal progress);
511extern QVariant _q_interpolateCounterclockwiseRotation(qreal &f, qreal &t, qreal progress);
512
513void QQuickRotationAnimatorJob::updateCurrentTime(int time)
514{
515#if QT_CONFIG(opengl)
516 Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread());
517#endif
518 if (!m_helper)
519 return;
520
521 float t = progress(time);
522
523 switch (m_direction) {
524 case QQuickRotationAnimator::Clockwise:
525 m_value = _q_interpolateClockwiseRotation(f&: m_from, t&: m_to, progress: t).toFloat();
526 // The logic in _q_interpolateClockwise comes out a bit wrong
527 // for the case of X->0 where 0<X<360. It ends on 360 which it
528 // shouldn't.
529 if (t == 1)
530 m_value = m_to;
531 break;
532 case QQuickRotationAnimator::Counterclockwise:
533 m_value = _q_interpolateCounterclockwiseRotation(f&: m_from, t&: m_to, progress: t).toFloat();
534 break;
535 case QQuickRotationAnimator::Shortest:
536 m_value = _q_interpolateShortestRotation(f&: m_from, t&: m_to, progress: t).toFloat();
537 break;
538 case QQuickRotationAnimator::Numerical:
539 m_value = m_from + (m_to - m_from) * t;
540 break;
541 }
542 m_helper->rotation = m_value;
543 m_helper->wasChanged = true;
544}
545
546void QQuickRotationAnimatorJob::writeBack()
547{
548 if (m_target)
549 m_target->setRotation(value());
550}
551
552
553QQuickOpacityAnimatorJob::QQuickOpacityAnimatorJob()
554 : m_opacityNode(nullptr)
555{
556}
557
558void QQuickOpacityAnimatorJob::postSync()
559{
560 if (!m_target) {
561 invalidate();
562 return;
563 }
564
565 QQuickItemPrivate *d = QQuickItemPrivate::get(item: m_target);
566#if QT_CONFIG(quick_shadereffect)
567 if (d->extra.isAllocated()
568 && d->extra->layer
569 && d->extra->layer->enabled()) {
570 d = QQuickItemPrivate::get(item: d->extra->layer->m_effectSource);
571 }
572#endif
573
574 m_opacityNode = d->opacityNode();
575
576 if (!m_opacityNode) {
577 m_opacityNode = new QSGOpacityNode();
578
579 /* The item node subtree is like this
580 *
581 * itemNode
582 * (opacityNode) optional
583 * (clipNode) optional
584 * (rootNode) optional
585 * children / paintNode
586 *
587 * If the opacity node doesn't exist, we need to insert it into
588 * the hierarchy between itemNode and clipNode or rootNode. If
589 * neither clip or root exists, we need to reparent all children
590 * from itemNode to opacityNode.
591 */
592 QSGNode *iNode = d->itemNode();
593 QSGNode *child = d->childContainerNode();
594 if (child != iNode) {
595 if (child->parent())
596 child->parent()->removeChildNode(node: child);
597 m_opacityNode->appendChildNode(node: child);
598 iNode->appendChildNode(node: m_opacityNode);
599 } else {
600 iNode->reparentChildNodesTo(newParent: m_opacityNode);
601 iNode->appendChildNode(node: m_opacityNode);
602 }
603
604 d->extra.value().opacityNode = m_opacityNode;
605 updateCurrentTime(time: 0);
606 }
607 Q_ASSERT(m_opacityNode);
608}
609
610void QQuickOpacityAnimatorJob::invalidate()
611{
612 m_opacityNode = nullptr;
613}
614
615void QQuickOpacityAnimatorJob::writeBack()
616{
617 if (m_target)
618 m_target->setOpacity(value());
619}
620
621void QQuickOpacityAnimatorJob::updateCurrentTime(int time)
622{
623#if QT_CONFIG(opengl)
624 Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread());
625#endif
626
627 if (!m_opacityNode)
628 return;
629
630 m_value = m_from + (m_to - m_from) * progress(time);
631 m_opacityNode->setOpacity(m_value);
632}
633
634
635#if QT_CONFIG(quick_shadereffect) && QT_CONFIG(opengl)
636QQuickUniformAnimatorJob::QQuickUniformAnimatorJob()
637 : m_node(nullptr)
638 , m_uniformIndex(-1)
639 , m_uniformType(-1)
640{
641 m_isUniform = true;
642}
643
644void QQuickUniformAnimatorJob::setTarget(QQuickItem *target)
645{
646 QQuickShaderEffect* effect = qobject_cast<QQuickShaderEffect*>(object: target);
647 if (effect && effect->isOpenGLShaderEffect())
648 m_target = target;
649}
650
651void QQuickUniformAnimatorJob::invalidate()
652{
653 m_node = nullptr;
654 m_uniformIndex = -1;
655 m_uniformType = -1;
656}
657
658void QQuickUniformAnimatorJob::postSync()
659{
660 if (!m_target) {
661 invalidate();
662 return;
663 }
664
665 m_node = static_cast<QQuickOpenGLShaderEffectNode *>(QQuickItemPrivate::get(item: m_target)->paintNode);
666
667 if (m_node && m_uniformIndex == -1 && m_uniformType == -1) {
668 QQuickOpenGLShaderEffectMaterial *material =
669 static_cast<QQuickOpenGLShaderEffectMaterial *>(m_node->material());
670 bool found = false;
671 for (int t=0; !found && t<QQuickOpenGLShaderEffectMaterialKey::ShaderTypeCount; ++t) {
672 const QVector<QQuickOpenGLShaderEffectMaterial::UniformData> &uniforms = material->uniforms[t];
673 for (int i=0; i<uniforms.size(); ++i) {
674 if (uniforms.at(i).name == m_uniform) {
675 m_uniformIndex = i;
676 m_uniformType = t;
677 found = true;
678 break;
679 }
680 }
681 }
682 }
683
684}
685
686void QQuickUniformAnimatorJob::updateCurrentTime(int time)
687{
688 if (!m_controller)
689 return;
690
691 if (!m_node || m_uniformIndex == -1 || m_uniformType == -1)
692 return;
693
694 m_value = m_from + (m_to - m_from) * progress(time);
695
696 QQuickOpenGLShaderEffectMaterial *material =
697 static_cast<QQuickOpenGLShaderEffectMaterial *>(m_node->material());
698 material->uniforms[m_uniformType][m_uniformIndex].value = m_value;
699 // As we're not touching the nodes, we need to explicitly mark it dirty.
700 // Otherwise, the renderer will abort repainting if this was the only
701 // change in the graph currently rendering.
702 m_node->markDirty(bits: QSGNode::DirtyMaterial);
703}
704
705void QQuickUniformAnimatorJob::writeBack()
706{
707 if (m_target)
708 m_target->setProperty(name: m_uniform, value: value());
709}
710#endif
711
712QT_END_NAMESPACE
713
714#include "moc_qquickanimatorjob_p.cpp"
715

source code of qtdeclarative/src/quick/util/qquickanimatorjob.cpp