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