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