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