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 }
154}
155
156void QQuickAnimatorProxyJob::debugAnimation(QDebug d) const
157{
158 d << "QuickAnimatorProxyJob("<< Qt::hex << (const void *) this << Qt::dec
159 << "state:" << state() << "duration:" << duration()
160 << "proxying: (" << job() << ')';
161}
162
163void QQuickAnimatorProxyJob::windowChanged(QQuickWindow *window)
164{
165 setWindow(window);
166}
167
168void QQuickAnimatorProxyJob::setWindow(QQuickWindow *window)
169{
170 if (!window) {
171 if (m_job && m_controller) {
172 disconnect(m_controller->window(), &QQuickWindow::sceneGraphInitialized,
173 this, &QQuickAnimatorProxyJob::sceneGraphInitialized);
174 m_controller->cancel(m_job);
175 }
176
177 m_controller = nullptr;
178 stop();
179
180 } else if (!m_controller && m_job) {
181 m_controller = QQuickWindowPrivate::get(window)->animationController.get();
182 if (window->isSceneGraphInitialized())
183 readyToAnimate();
184 else
185 connect(window, &QQuickWindow::sceneGraphInitialized, this, &QQuickAnimatorProxyJob::sceneGraphInitialized);
186 }
187}
188
189void QQuickAnimatorProxyJob::sceneGraphInitialized()
190{
191 if (m_controller) {
192 disconnect(m_controller->window(), &QQuickWindow::sceneGraphInitialized, this, &QQuickAnimatorProxyJob::sceneGraphInitialized);
193 readyToAnimate();
194 }
195}
196
197void QQuickAnimatorProxyJob::readyToAnimate()
198{
199 Q_ASSERT(m_controller);
200 if (m_internalState == State_Starting) {
201 m_internalState = State_Running;
202 m_controller->start(m_job);
203 }
204}
205
206static void qquick_syncback_helper(QAbstractAnimationJob *job)
207{
208 if (job->isRenderThreadJob()) {
209 static_cast<QQuickAnimatorJob *>(job)->writeBack();
210
211 } else if (job->isGroup()) {
212 QAnimationGroupJob *g = static_cast<QAnimationGroupJob *>(job);
213 for (QAbstractAnimationJob *a : *g->children())
214 qquick_syncback_helper(a);
215 }
216
217}
218
219void QQuickAnimatorProxyJob::syncBackCurrentValues()
220{
221 if (m_job)
222 qquick_syncback_helper(m_job.data());
223}
224
225QQuickAnimatorJob::QQuickAnimatorJob()
226 : m_target(nullptr)
227 , m_controller(nullptr)
228 , m_from(0)
229 , m_to(0)
230 , m_value(0)
231 , m_duration(0)
232 , m_isTransform(false)
233 , m_isUniform(false)
234{
235 m_isRenderThreadJob = true;
236}
237
238void QQuickAnimatorJob::debugAnimation(QDebug d) const
239{
240 d << "QuickAnimatorJob(" << Qt::hex << (const void *) this << Qt::dec
241 << ") state:" << state() << "duration:" << duration()
242 << "target:" << m_target << "value:" << m_value;
243}
244
245qreal QQuickAnimatorJob::progress(int time) const
246{
247 return m_easing.valueForProgress((m_duration == 0) ? qreal(1) : qreal(time) / qreal(m_duration));
248}
249
250void QQuickAnimatorJob::boundValue()
251{
252 qreal rangeMin = m_from;
253 qreal rangeMax = m_to;
254 if (m_from > m_to) {
255 rangeMax = m_from;
256 rangeMin = m_to;
257 }
258 m_value = qBound(rangeMin, m_value, rangeMax);
259}
260
261qreal QQuickAnimatorJob::value() const
262{
263 qreal value = m_to;
264 if (m_controller) {
265 m_controller->lock();
266 value = m_value;
267 m_controller->unlock();
268 }
269 return value;
270}
271
272void QQuickAnimatorJob::setTarget(QQuickItem *target)
273{
274 m_target = target;
275}
276
277void QQuickAnimatorJob::initialize(QQuickAnimatorController *controller)
278{
279 m_controller = controller;
280}
281
283 : m_helper(nullptr)
284{
285 m_isTransform = true;
286}
287
289{
290 if (m_helper)
291 qquick_transform_animatorjob_helper_store()->release(m_helper);
292}
293
295{
296 // In the extremely unlikely event that the target of an animator has been
297 // changed into a new item that sits in the exact same pointer address, we
298 // want to force syncing it again.
299 if (m_helper && m_target)
300 m_helper->wasSynced = false;
301 QQuickAnimatorJob::setTarget(item);
302}
303
305{
306 // If the target has changed or become null, release and reset the helper
307 if (m_helper && (m_helper->item != m_target || !m_target)) {
308 qquick_transform_animatorjob_helper_store()->release(m_helper);
309 m_helper = nullptr;
310 }
311
312 if (!m_target) {
314 return;
315 }
316
317 if (!m_helper) {
318 m_helper = qquick_transform_animatorjob_helper_store()->acquire(m_target);
319
320 // This is a bit superfluous, but it ends up being simpler than the
321 // alternative. When an item happens to land on the same address as a
322 // previous item, that helper might not have been fully cleaned up by
323 // the time it gets taken back into use. As an alternative to storing
324 // connections to each and every item's QObject::destroyed() and
325 // having to clean those up afterwards, we simply sync all helpers on
326 // the first run. The sync is only done once for the run of an
327 // animation and it is a fairly light function (compared to storing
328 // potentially thousands of connections and managing their lifetime.
329 m_helper->wasSynced = false;
330 }
331
333}
334
336{
337 if (m_helper)
338 m_helper->node = nullptr;
339}
340
342{
343 const quint32 mask = QQuickItemPrivate::Position
344 | QQuickItemPrivate::BasicTransform
345 | QQuickItemPrivate::TransformOrigin
346 | QQuickItemPrivate::Size;
347
348 QQuickItemPrivate *d = QQuickItemPrivate::get(item);
349#if QT_CONFIG(quick_shadereffect)
350 if (d->extra.isAllocated()
351 && d->extra->layer
352 && d->extra->layer->enabled()) {
353 d = QQuickItemPrivate::get(d->extra->layer->m_effectSource);
354 }
355#endif
356
357 quint32 dirty = mask & d->dirtyAttributes;
358
359 if (!wasSynced) {
360 dirty = 0xffffffffu;
361 wasSynced = true;
362 }
363
364 // We update the node before checking on dirty, as the node might have changed without the animator running
365 node = d->itemNode();
366
367 if (dirty == 0)
368 return;
369
370 if (dirty & QQuickItemPrivate::Position) {
371 dx = item->x();
372 dy = item->y();
373 }
374
375 if (dirty & QQuickItemPrivate::BasicTransform) {
376 scale = item->scale();
377 rotation = item->rotation();
378 }
379
380 if (dirty & (QQuickItemPrivate::TransformOrigin | QQuickItemPrivate::Size)) {
381 QPointF o = item->transformOriginPoint();
382 ox = o.x();
383 oy = o.y();
384 }
385}
386
388{
389 if (!wasChanged || !node)
390 return;
391
392 QMatrix4x4 m;
393 m.translate(dx, dy);
394 m.translate(ox, oy);
395 m.scale(scale);
396 m.rotate(rotation, 0, 0, 1);
397 m.translate(-ox, -oy);
398 node->setMatrix(m);
399
400 wasChanged = false;
401}
402
408
409void QQuickXAnimatorJob::writeBack()
410{
411 if (m_target)
412 m_target->setX(value());
413}
414
415void QQuickXAnimatorJob::updateCurrentTime(int time)
416{
417 if (!m_helper)
418 return;
419
420 m_value = m_from + (m_to - m_from) * progress(time);
421 m_helper->dx = m_value;
422 m_helper->wasChanged = true;
423}
424
425void QQuickYAnimatorJob::writeBack()
426{
427 if (m_target)
428 m_target->setY(value());
429}
430
431void QQuickYAnimatorJob::updateCurrentTime(int time)
432{
433 if (!m_helper)
434 return;
435
436 m_value = m_from + (m_to - m_from) * progress(time);
437 m_helper->dy = m_value;
438 m_helper->wasChanged = true;
439}
440
441void QQuickScaleAnimatorJob::writeBack()
442{
443 if (m_target)
444 m_target->setScale(value());
445}
446
447void QQuickScaleAnimatorJob::updateCurrentTime(int time)
448{
449 if (!m_helper)
450 return;
451
452 m_value = m_from + (m_to - m_from) * progress(time);
453 m_helper->scale = m_value;
454 m_helper->wasChanged = true;
455}
456
457
458QQuickRotationAnimatorJob::QQuickRotationAnimatorJob()
459 : m_direction(QQuickRotationAnimator::Numerical)
460{
461}
462
463extern QVariant _q_interpolateShortestRotation(qreal &f, qreal &t, qreal progress);
464extern QVariant _q_interpolateClockwiseRotation(qreal &f, qreal &t, qreal progress);
465extern QVariant _q_interpolateCounterclockwiseRotation(qreal &f, qreal &t, qreal progress);
466
467void QQuickRotationAnimatorJob::updateCurrentTime(int time)
468{
469 if (!m_helper)
470 return;
471
472 float t = progress(time);
473
474 switch (m_direction) {
475 case QQuickRotationAnimator::Clockwise:
476 m_value = _q_interpolateClockwiseRotation(m_from, m_to, t).toFloat();
477 // The logic in _q_interpolateClockwise comes out a bit wrong
478 // for the case of X->0 where 0<X<360. It ends on 360 which it
479 // shouldn't.
480 if (t == 1)
481 m_value = m_to;
482 break;
483 case QQuickRotationAnimator::Counterclockwise:
484 m_value = _q_interpolateCounterclockwiseRotation(m_from, m_to, t).toFloat();
485 break;
486 case QQuickRotationAnimator::Shortest:
487 m_value = _q_interpolateShortestRotation(m_from, m_to, t).toFloat();
488 break;
489 case QQuickRotationAnimator::Numerical:
490 m_value = m_from + (m_to - m_from) * t;
491 break;
492 }
493 m_helper->rotation = m_value;
494 m_helper->wasChanged = true;
495}
496
497void QQuickRotationAnimatorJob::writeBack()
498{
499 if (m_target)
500 m_target->setRotation(value());
501}
502
503
504QQuickOpacityAnimatorJob::QQuickOpacityAnimatorJob()
505 : m_opacityNode(nullptr)
506{
507}
508
509void QQuickOpacityAnimatorJob::postSync()
510{
511 if (!m_target) {
512 invalidate();
513 return;
514 }
515
516 QQuickItemPrivate *d = QQuickItemPrivate::get(m_target);
517#if QT_CONFIG(quick_shadereffect)
518 if (d->extra.isAllocated()
519 && d->extra->layer
520 && d->extra->layer->enabled()) {
521 d = QQuickItemPrivate::get(d->extra->layer->m_effectSource);
522 }
523#endif
524
525 m_opacityNode = d->opacityNode();
526
527 if (!m_opacityNode) {
528 m_opacityNode = new QSGOpacityNode();
529
530 /* The item node subtree is like this
531 *
532 * itemNode
533 * (opacityNode) optional
534 * (clipNode) optional
535 * (rootNode) optional
536 * children / paintNode
537 *
538 * If the opacity node doesn't exist, we need to insert it into
539 * the hierarchy between itemNode and clipNode or rootNode. If
540 * neither clip or root exists, we need to reparent all children
541 * from itemNode to opacityNode.
542 */
543 QSGNode *iNode = d->itemNode();
544 QSGNode *child = d->childContainerNode();
545 if (child != iNode) {
546 if (child->parent())
547 child->parent()->removeChildNode(child);
548 m_opacityNode->appendChildNode(child);
549 iNode->appendChildNode(m_opacityNode);
550 } else {
551 iNode->reparentChildNodesTo(m_opacityNode);
552 iNode->appendChildNode(m_opacityNode);
553 }
554
555 d->extra.value().opacityNode = m_opacityNode;
556 updateCurrentTime(0);
557 }
558 Q_ASSERT(m_opacityNode);
559}
560
561void QQuickOpacityAnimatorJob::invalidate()
562{
563 m_opacityNode = nullptr;
564}
565
566void QQuickOpacityAnimatorJob::writeBack()
567{
568 if (m_target)
569 m_target->setOpacity(value());
570}
571
572void QQuickOpacityAnimatorJob::updateCurrentTime(int time)
573{
574 if (!m_opacityNode)
575 return;
576
577 m_value = m_from + (m_to - m_from) * progress(time);
578 m_opacityNode->setOpacity(m_value);
579}
580
581#if QT_CONFIG(quick_shadereffect)
582QQuickUniformAnimatorJob::QQuickUniformAnimatorJob()
583{
584 m_isUniform = true;
585}
586
587void QQuickUniformAnimatorJob::setTarget(QQuickItem *target)
588{
589 // Check target is of expected type
590 if (qobject_cast<QQuickShaderEffect *>(target) != nullptr)
591 m_target = target;
592}
593
594void QQuickUniformAnimatorJob::updateCurrentTime(int time)
595{
596 if (!m_effect || m_target != m_effect)
597 return;
598
599 m_value = m_from + (m_to - m_from) * progress(time);
600 m_effect->updateUniformValue(m_uniform, m_value);
601}
602
603void QQuickUniformAnimatorJob::writeBack()
604{
605 if (m_target)
606 m_target->setProperty(m_uniform, value());
607}
608
609void QQuickUniformAnimatorJob::postSync()
610{
611 if (!m_target) {
612 invalidate();
613 return;
614 }
615
616 m_effect = qobject_cast<QQuickShaderEffect *>(m_target);
617}
618
619void QQuickUniformAnimatorJob::invalidate()
620{
621
622}
623#endif
624
625QT_END_NAMESPACE
626
627#include "moc_qquickanimatorjob_p.cpp"
void setTarget(QQuickItem *item) override
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)