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(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(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(job);
94
95 m_isRenderThreadProxy = true;
96 m_animation = qobject_cast<QQuickAbstractAnimation *>(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("QtQuick: unable to find animation context for RT animation...");
112 return;
113 }
114
115 QQuickWindow *window = qobject_cast<QQuickWindow *>(ctx);
116 if (window) {
117 setWindow(window);
118 } else {
119 QQuickItem *item = qobject_cast<QQuickItem *>(ctx);
120 if (item->window())
121 setWindow(item->window());
122 connect(item, &QQuickItem::windowChanged, this, &QQuickAnimatorProxyJob::windowChanged);
123 }
124}
125
126QQuickAnimatorProxyJob::~QQuickAnimatorProxyJob()
127{
128 if (m_job && m_controller)
129 m_controller->cancel(m_job);
130 m_job.reset();
131}
132
133QObject *QQuickAnimatorProxyJob::findAnimationContext(QQuickAbstractAnimation *a)
134{
135 QObject *p = a->parent();
136 while (p != nullptr && qobject_cast<QQuickWindow *>(p) == nullptr && qobject_cast<QQuickItem *>(p) == nullptr)
137 p = p->parent();
138 return p;
139}
140
141void QQuickAnimatorProxyJob::updateCurrentTime(int)
142{
143 if (m_internalState != State_Running)
144 return;
145
146 // A proxy which is being ticked should be associated with a window, (see
147 // setWindow() below). If we get here when there is no more controller we
148 // have a problem.
149 Q_ASSERT(m_controller);
150
151 // We do a simple check here to see if the animator has run and stopped on
152 // the render thread. isPendingStart() will perform a check against jobs
153 // that have been scheduled for start, but that will not yet have entered
154 // the actual running state.
155 // Secondly, we make an unprotected read of the job's state to figure out
156 // if it is running, but this is ok, since we're only reading the state
157 // and if the render thread should happen to be writing it concurrently,
158 // we might get the wrong value for this update, but then we'll simply
159 // pick it up on the next iterationm when the job is stopped and render
160 // thread is no longer using it.
161 if (!m_controller->isPendingStart(m_job)
162 && !m_job->isRunning()) {
163 stop();
164 }
165}
166
167void QQuickAnimatorProxyJob::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State)
168{
169 if (m_state == Running) {
170 m_internalState = State_Starting;
171 if (m_controller) {
172 m_internalState = State_Running;
173 m_controller->start(m_job);
174 }
175
176 } else if (newState == Stopped) {
177 m_internalState = State_Stopped;
178 if (m_controller) {
179 syncBackCurrentValues();
180 m_controller->cancel(m_job);
181 }
182 }
183}
184
185void QQuickAnimatorProxyJob::debugAnimation(QDebug d) const
186{
187 d << "QuickAnimatorProxyJob("<< hex << (const void *) this << dec
188 << "state:" << state() << "duration:" << duration()
189 << "proxying: (" << job() << ')';
190}
191
192void QQuickAnimatorProxyJob::windowChanged(QQuickWindow *window)
193{
194 setWindow(window);
195}
196
197void QQuickAnimatorProxyJob::setWindow(QQuickWindow *window)
198{
199 if (!window) {
200 if (m_job && m_controller) {
201 disconnect(m_controller->window(), &QQuickWindow::sceneGraphInitialized,
202 this, &QQuickAnimatorProxyJob::sceneGraphInitialized);
203 m_controller->cancel(m_job);
204 }
205
206 m_controller = nullptr;
207 stop();
208
209 } else if (!m_controller && m_job) {
210 m_controller = QQuickWindowPrivate::get(window)->animationController;
211 if (window->isSceneGraphInitialized())
212 readyToAnimate();
213 else
214 connect(window, &QQuickWindow::sceneGraphInitialized, this, &QQuickAnimatorProxyJob::sceneGraphInitialized);
215 }
216}
217
218void QQuickAnimatorProxyJob::sceneGraphInitialized()
219{
220 if (m_controller) {
221 disconnect(m_controller->window(), &QQuickWindow::sceneGraphInitialized, this, &QQuickAnimatorProxyJob::sceneGraphInitialized);
222 readyToAnimate();
223 }
224}
225
226void QQuickAnimatorProxyJob::readyToAnimate()
227{
228 Q_ASSERT(m_controller);
229 if (m_internalState == State_Starting) {
230 m_internalState = State_Running;
231 m_controller->start(m_job);
232 }
233}
234
235static void qquick_syncback_helper(QAbstractAnimationJob *job)
236{
237 if (job->isRenderThreadJob()) {
238 static_cast<QQuickAnimatorJob *>(job)->writeBack();
239
240 } else if (job->isGroup()) {
241 QAnimationGroupJob *g = static_cast<QAnimationGroupJob *>(job);
242 for (QAbstractAnimationJob *a = g->firstChild(); a; a = a->nextSibling())
243 qquick_syncback_helper(a);
244 }
245
246}
247
248void QQuickAnimatorProxyJob::syncBackCurrentValues()
249{
250 if (m_job)
251 qquick_syncback_helper(m_job.data());
252}
253
254QQuickAnimatorJob::QQuickAnimatorJob()
255 : m_target(nullptr)
256 , m_controller(nullptr)
257 , m_from(0)
258 , m_to(0)
259 , m_value(0)
260 , m_duration(0)
261 , m_isTransform(false)
262 , m_isUniform(false)
263{
264 m_isRenderThreadJob = true;
265}
266
267void QQuickAnimatorJob::debugAnimation(QDebug d) const
268{
269 d << "QuickAnimatorJob(" << hex << (const void *) this << dec
270 << ") state:" << state() << "duration:" << duration()
271 << "target:" << m_target << "value:" << m_value;
272}
273
274qreal QQuickAnimatorJob::progress(int time) const
275{
276 return m_easing.valueForProgress((m_duration == 0) ? qreal(1) : qreal(time) / qreal(m_duration));
277}
278
279qreal QQuickAnimatorJob::value() const
280{
281 qreal value = m_to;
282 if (m_controller) {
283 m_controller->lock();
284 value = m_value;
285 m_controller->unlock();
286 }
287 return value;
288}
289
290void QQuickAnimatorJob::setTarget(QQuickItem *target)
291{
292 m_target = target;
293}
294
295void QQuickAnimatorJob::initialize(QQuickAnimatorController *controller)
296{
297 m_controller = controller;
298}
299
300QQuickTransformAnimatorJob::QQuickTransformAnimatorJob()
301 : m_helper(nullptr)
302{
303 m_isTransform = true;
304}
305
306QQuickTransformAnimatorJob::~QQuickTransformAnimatorJob()
307{
308 if (m_helper)
309 qquick_transform_animatorjob_helper_store()->release(m_helper);
310}
311
312void QQuickTransformAnimatorJob::setTarget(QQuickItem *item)
313{
314 // In the extremely unlikely event that the target of an animator has been
315 // changed into a new item that sits in the exact same pointer address, we
316 // want to force syncing it again.
317 if (m_helper && m_target)
318 m_helper->wasSynced = false;
319 QQuickAnimatorJob::setTarget(item);
320}
321
322void QQuickTransformAnimatorJob::preSync()
323{
324 // If the target has changed or become null, release and reset the helper
325 if (m_helper && (m_helper->item != m_target || !m_target)) {
326 qquick_transform_animatorjob_helper_store()->release(m_helper);
327 m_helper = nullptr;
328 }
329
330 if (!m_target) {
331 invalidate();
332 return;
333 }
334
335 if (!m_helper) {
336 m_helper = qquick_transform_animatorjob_helper_store()->acquire(m_target);
337
338 // This is a bit superfluous, but it ends up being simpler than the
339 // alternative. When an item happens to land on the same address as a
340 // previous item, that helper might not have been fully cleaned up by
341 // the time it gets taken back into use. As an alternative to storing
342 // connections to each and every item's QObject::destroyed() and
343 // having to clean those up afterwards, we simply sync all helpers on
344 // the first run. The sync is only done once for the run of an
345 // animation and it is a fairly light function (compared to storing
346 // potentially thousands of connections and managing their lifetime.
347 m_helper->wasSynced = false;
348 }
349
350 m_helper->sync();
351}
352
353void QQuickTransformAnimatorJob::invalidate()
354{
355 if (m_helper)
356 m_helper->node = nullptr;
357}
358
359void QQuickTransformAnimatorJob::Helper::sync()
360{
361 const quint32 mask = QQuickItemPrivate::Position
362 | QQuickItemPrivate::BasicTransform
363 | QQuickItemPrivate::TransformOrigin
364 | QQuickItemPrivate::Size;
365
366 QQuickItemPrivate *d = QQuickItemPrivate::get(item);
367#if QT_CONFIG(quick_shadereffect)
368 if (d->extra.isAllocated()
369 && d->extra->layer
370 && d->extra->layer->enabled()) {
371 d = QQuickItemPrivate::get(d->extra->layer->m_effectSource);
372 }
373#endif
374
375 quint32 dirty = mask & d->dirtyAttributes;
376
377 if (!wasSynced) {
378 dirty = 0xffffffffu;
379 wasSynced = true;
380 }
381
382 if (dirty == 0)
383 return;
384
385 node = d->itemNode();
386
387 if (dirty & QQuickItemPrivate::Position) {
388 dx = item->x();
389 dy = item->y();
390 }
391
392 if (dirty & QQuickItemPrivate::BasicTransform) {
393 scale = item->scale();
394 rotation = item->rotation();
395 }
396
397 if (dirty & (QQuickItemPrivate::TransformOrigin | QQuickItemPrivate::Size)) {
398 QPointF o = item->transformOriginPoint();
399 ox = o.x();
400 oy = o.y();
401 }
402}
403
404void QQuickTransformAnimatorJob::Helper::commit()
405{
406 if (!wasChanged || !node)
407 return;
408
409 QMatrix4x4 m;
410 m.translate(dx, dy);
411 m.translate(ox, oy);
412 m.scale(scale);
413 m.rotate(rotation, 0, 0, 1);
414 m.translate(-ox, -oy);
415 node->setMatrix(m);
416
417 wasChanged = false;
418}
419
420void QQuickTransformAnimatorJob::commit()
421{
422 if (m_helper)
423 m_helper->commit();
424}
425
426void QQuickXAnimatorJob::writeBack()
427{
428 if (m_target)
429 m_target->setX(value());
430}
431
432void QQuickXAnimatorJob::updateCurrentTime(int time)
433{
434#if QT_CONFIG(opengl)
435 Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread());
436#endif
437 if (!m_helper)
438 return;
439
440 m_value = m_from + (m_to - m_from) * progress(time);
441 m_helper->dx = m_value;
442 m_helper->wasChanged = true;
443}
444
445void QQuickYAnimatorJob::writeBack()
446{
447 if (m_target)
448 m_target->setY(value());
449}
450
451void QQuickYAnimatorJob::updateCurrentTime(int time)
452{
453#if QT_CONFIG(opengl)
454 Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread());
455#endif
456 if (!m_helper)
457 return;
458
459 m_value = m_from + (m_to - m_from) * progress(time);
460 m_helper->dy = m_value;
461 m_helper->wasChanged = true;
462}
463
464void QQuickScaleAnimatorJob::writeBack()
465{
466 if (m_target)
467 m_target->setScale(value());
468}
469
470void QQuickScaleAnimatorJob::updateCurrentTime(int time)
471{
472#if QT_CONFIG(opengl)
473 Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread());
474#endif
475 if (!m_helper)
476 return;
477
478 m_value = m_from + (m_to - m_from) * progress(time);
479 m_helper->scale = m_value;
480 m_helper->wasChanged = true;
481}
482
483
484QQuickRotationAnimatorJob::QQuickRotationAnimatorJob()
485 : m_direction(QQuickRotationAnimator::Numerical)
486{
487}
488
489extern QVariant _q_interpolateShortestRotation(qreal &f, qreal &t, qreal progress);
490extern QVariant _q_interpolateClockwiseRotation(qreal &f, qreal &t, qreal progress);
491extern QVariant _q_interpolateCounterclockwiseRotation(qreal &f, qreal &t, qreal progress);
492
493void QQuickRotationAnimatorJob::updateCurrentTime(int time)
494{
495#if QT_CONFIG(opengl)
496 Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread());
497#endif
498 if (!m_helper)
499 return;
500
501 float t = progress(time);
502
503 switch (m_direction) {
504 case QQuickRotationAnimator::Clockwise:
505 m_value = _q_interpolateClockwiseRotation(m_from, m_to, t).toFloat();
506 // The logic in _q_interpolateClockwise comes out a bit wrong
507 // for the case of X->0 where 0<X<360. It ends on 360 which it
508 // shouldn't.
509 if (t == 1)
510 m_value = m_to;
511 break;
512 case QQuickRotationAnimator::Counterclockwise:
513 m_value = _q_interpolateCounterclockwiseRotation(m_from, m_to, t).toFloat();
514 break;
515 case QQuickRotationAnimator::Shortest:
516 m_value = _q_interpolateShortestRotation(m_from, m_to, t).toFloat();
517 break;
518 case QQuickRotationAnimator::Numerical:
519 m_value = m_from + (m_to - m_from) * t;
520 break;
521 }
522 m_helper->rotation = m_value;
523 m_helper->wasChanged = true;
524}
525
526void QQuickRotationAnimatorJob::writeBack()
527{
528 if (m_target)
529 m_target->setRotation(value());
530}
531
532
533QQuickOpacityAnimatorJob::QQuickOpacityAnimatorJob()
534 : m_opacityNode(nullptr)
535{
536}
537
538void QQuickOpacityAnimatorJob::postSync()
539{
540 if (!m_target) {
541 invalidate();
542 return;
543 }
544
545 QQuickItemPrivate *d = QQuickItemPrivate::get(m_target);
546#if QT_CONFIG(quick_shadereffect)
547 if (d->extra.isAllocated()
548 && d->extra->layer
549 && d->extra->layer->enabled()) {
550 d = QQuickItemPrivate::get(d->extra->layer->m_effectSource);
551 }
552#endif
553
554 m_opacityNode = d->opacityNode();
555
556 if (!m_opacityNode) {
557 m_opacityNode = new QSGOpacityNode();
558
559 /* The item node subtree is like this
560 *
561 * itemNode
562 * (opacityNode) optional
563 * (clipNode) optional
564 * (rootNode) optional
565 * children / paintNode
566 *
567 * If the opacity node doesn't exist, we need to insert it into
568 * the hierarchy between itemNode and clipNode or rootNode. If
569 * neither clip or root exists, we need to reparent all children
570 * from itemNode to opacityNode.
571 */
572 QSGNode *iNode = d->itemNode();
573 QSGNode *child = d->childContainerNode();
574 if (child != iNode) {
575 if (child->parent())
576 child->parent()->removeChildNode(child);
577 m_opacityNode->appendChildNode(child);
578 iNode->appendChildNode(m_opacityNode);
579 } else {
580 iNode->reparentChildNodesTo(m_opacityNode);
581 iNode->appendChildNode(m_opacityNode);
582 }
583
584 d->extra.value().opacityNode = m_opacityNode;
585 }
586 Q_ASSERT(m_opacityNode);
587}
588
589void QQuickOpacityAnimatorJob::invalidate()
590{
591 m_opacityNode = nullptr;
592}
593
594void QQuickOpacityAnimatorJob::writeBack()
595{
596 if (m_target)
597 m_target->setOpacity(value());
598}
599
600void QQuickOpacityAnimatorJob::updateCurrentTime(int time)
601{
602#if QT_CONFIG(opengl)
603 Q_ASSERT(!m_controller || !m_controller->m_window->openglContext() || m_controller->m_window->openglContext()->thread() == QThread::currentThread());
604#endif
605
606 if (!m_opacityNode)
607 return;
608
609 m_value = m_from + (m_to - m_from) * progress(time);
610 m_opacityNode->setOpacity(m_value);
611}
612
613
614#if QT_CONFIG(quick_shadereffect) && QT_CONFIG(opengl)
615QQuickUniformAnimatorJob::QQuickUniformAnimatorJob()
616 : m_node(nullptr)
617 , m_uniformIndex(-1)
618 , m_uniformType(-1)
619{
620 m_isUniform = true;
621}
622
623void QQuickUniformAnimatorJob::setTarget(QQuickItem *target)
624{
625 QQuickShaderEffect* effect = qobject_cast<QQuickShaderEffect*>(target);
626 if (effect && effect->isOpenGLShaderEffect())
627 m_target = target;
628}
629
630void QQuickUniformAnimatorJob::invalidate()
631{
632 m_node = nullptr;
633 m_uniformIndex = -1;
634 m_uniformType = -1;
635}
636
637void QQuickUniformAnimatorJob::postSync()
638{
639 if (!m_target) {
640 invalidate();
641 return;
642 }
643
644 m_node = static_cast<QQuickOpenGLShaderEffectNode *>(QQuickItemPrivate::get(m_target)->paintNode);
645
646 if (m_node && m_uniformIndex == -1 && m_uniformType == -1) {
647 QQuickOpenGLShaderEffectMaterial *material =
648 static_cast<QQuickOpenGLShaderEffectMaterial *>(m_node->material());
649 bool found = false;
650 for (int t=0; !found && t<QQuickOpenGLShaderEffectMaterialKey::ShaderTypeCount; ++t) {
651 const QVector<QQuickOpenGLShaderEffectMaterial::UniformData> &uniforms = material->uniforms[t];
652 for (int i=0; i<uniforms.size(); ++i) {
653 if (uniforms.at(i).name == m_uniform) {
654 m_uniformIndex = i;
655 m_uniformType = t;
656 found = true;
657 break;
658 }
659 }
660 }
661 }
662
663}
664
665void QQuickUniformAnimatorJob::updateCurrentTime(int time)
666{
667 if (!m_controller)
668 return;
669
670 if (!m_node || m_uniformIndex == -1 || m_uniformType == -1)
671 return;
672
673 m_value = m_from + (m_to - m_from) * progress(time);
674
675 QQuickOpenGLShaderEffectMaterial *material =
676 static_cast<QQuickOpenGLShaderEffectMaterial *>(m_node->material());
677 material->uniforms[m_uniformType][m_uniformIndex].value = m_value;
678 // As we're not touching the nodes, we need to explicitly mark it dirty.
679 // Otherwise, the renderer will abort repainting if this was the only
680 // change in the graph currently rendering.
681 m_node->markDirty(QSGNode::DirtyMaterial);
682}
683
684void QQuickUniformAnimatorJob::writeBack()
685{
686 if (m_target)
687 m_target->setProperty(m_uniform, value());
688}
689#endif
690
691QT_END_NAMESPACE
692
693#include "moc_qquickanimatorjob_p.cpp"
694