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 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | struct 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 | }; |
48 | Q_GLOBAL_STATIC(QQuickTransformAnimatorHelperStore, qquick_transform_animatorjob_helper_store); |
49 | |
50 | QQuickAnimatorProxyJob::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 | |
87 | void QQuickAnimatorProxyJob::updateLoopCount(int loopCount) |
88 | { |
89 | m_job->setLoopCount(loopCount); |
90 | } |
91 | |
92 | QQuickAnimatorProxyJob::~QQuickAnimatorProxyJob() |
93 | { |
94 | if (m_job && m_controller) |
95 | m_controller->cancel(job: m_job); |
96 | m_job.reset(); |
97 | } |
98 | |
99 | QObject *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 | |
107 | void 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 | |
137 | void 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 | |
155 | void 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 | |
162 | void QQuickAnimatorProxyJob::windowChanged(QQuickWindow *window) |
163 | { |
164 | setWindow(window); |
165 | } |
166 | |
167 | void 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 | |
188 | void 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 | |
196 | void 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 | |
205 | static 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 | |
218 | void QQuickAnimatorProxyJob::syncBackCurrentValues() |
219 | { |
220 | if (m_job) |
221 | qquick_syncback_helper(job: m_job.data()); |
222 | } |
223 | |
224 | QQuickAnimatorJob::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 | |
237 | void 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 | |
244 | qreal QQuickAnimatorJob::progress(int time) const |
245 | { |
246 | return m_easing.valueForProgress(progress: (m_duration == 0) ? qreal(1) : qreal(time) / qreal(m_duration)); |
247 | } |
248 | |
249 | void 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 | |
260 | qreal 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 | |
271 | void QQuickAnimatorJob::setTarget(QQuickItem *target) |
272 | { |
273 | m_target = target; |
274 | } |
275 | |
276 | void QQuickAnimatorJob::initialize(QQuickAnimatorController *controller) |
277 | { |
278 | m_controller = controller; |
279 | } |
280 | |
281 | QQuickTransformAnimatorJob::QQuickTransformAnimatorJob() |
282 | : m_helper(nullptr) |
283 | { |
284 | m_isTransform = true; |
285 | } |
286 | |
287 | QQuickTransformAnimatorJob::~QQuickTransformAnimatorJob() |
288 | { |
289 | if (m_helper) |
290 | qquick_transform_animatorjob_helper_store()->release(helper: m_helper); |
291 | } |
292 | |
293 | void 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 | |
303 | void 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 | |
334 | void QQuickTransformAnimatorJob::invalidate() |
335 | { |
336 | if (m_helper) |
337 | m_helper->node = nullptr; |
338 | } |
339 | |
340 | void 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 | |
386 | void 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 | |
402 | void QQuickTransformAnimatorJob::commit() |
403 | { |
404 | if (m_helper) |
405 | m_helper->commit(); |
406 | } |
407 | |
408 | void QQuickXAnimatorJob::writeBack() |
409 | { |
410 | if (m_target) |
411 | m_target->setX(value()); |
412 | } |
413 | |
414 | void 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 | |
424 | void QQuickYAnimatorJob::writeBack() |
425 | { |
426 | if (m_target) |
427 | m_target->setY(value()); |
428 | } |
429 | |
430 | void 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 | |
440 | void QQuickScaleAnimatorJob::writeBack() |
441 | { |
442 | if (m_target) |
443 | m_target->setScale(value()); |
444 | } |
445 | |
446 | void 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 | |
457 | QQuickRotationAnimatorJob::QQuickRotationAnimatorJob() |
458 | : m_direction(QQuickRotationAnimator::Numerical) |
459 | { |
460 | } |
461 | |
462 | extern QVariant _q_interpolateShortestRotation(qreal &f, qreal &t, qreal progress); |
463 | extern QVariant _q_interpolateClockwiseRotation(qreal &f, qreal &t, qreal progress); |
464 | extern QVariant _q_interpolateCounterclockwiseRotation(qreal &f, qreal &t, qreal progress); |
465 | |
466 | void 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 | |
496 | void QQuickRotationAnimatorJob::writeBack() |
497 | { |
498 | if (m_target) |
499 | m_target->setRotation(value()); |
500 | } |
501 | |
502 | |
503 | QQuickOpacityAnimatorJob::QQuickOpacityAnimatorJob() |
504 | : m_opacityNode(nullptr) |
505 | { |
506 | } |
507 | |
508 | void 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 | |
560 | void QQuickOpacityAnimatorJob::invalidate() |
561 | { |
562 | m_opacityNode = nullptr; |
563 | } |
564 | |
565 | void QQuickOpacityAnimatorJob::writeBack() |
566 | { |
567 | if (m_target) |
568 | m_target->setOpacity(value()); |
569 | } |
570 | |
571 | void 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) |
581 | QQuickUniformAnimatorJob::QQuickUniformAnimatorJob() |
582 | { |
583 | m_isUniform = true; |
584 | } |
585 | |
586 | void 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 | |
593 | void 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 | |
602 | void QQuickUniformAnimatorJob::writeBack() |
603 | { |
604 | if (m_target) |
605 | m_target->setProperty(name: m_uniform, value: value()); |
606 | } |
607 | |
608 | void QQuickUniformAnimatorJob::postSync() |
609 | { |
610 | if (!m_target) { |
611 | invalidate(); |
612 | return; |
613 | } |
614 | |
615 | m_effect = qobject_cast<QQuickShaderEffect *>(object: m_target); |
616 | } |
617 | |
618 | void QQuickUniformAnimatorJob::invalidate() |
619 | { |
620 | m_effect = nullptr; |
621 | } |
622 | #endif |
623 | |
624 | QT_END_NAMESPACE |
625 | |
626 | #include "moc_qquickanimatorjob_p.cpp" |
627 | |