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