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

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