1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtLottie/private/qbatchrenderer_p.h>
5
6#include <QImage>
7#include <QPainter>
8#include <QHash>
9#include <QMap>
10#include <QMutexLocker>
11#include <QLoggingCategory>
12#include <QThread>
13
14#include <QJsonDocument>
15#include <QJsonArray>
16
17#include <QtLottie/private/qlottieconstants_p.h>
18#include <QtLottie/private/qlottiebase_p.h>
19#include <QtLottie/private/qlottieflatlayers_p.h>
20#include <QtLottie/private/qlottieroot_p.h>
21#include <QtLottie/private/qlottielayer_p.h>
22
23#include <QtLottie/private/qlottieanimation_p.h>
24#include <QtLottie/private/qlottierasterrenderer_p.h>
25
26QT_BEGIN_NAMESPACE
27
28Q_LOGGING_CATEGORY(lcLottieQtLottieRenderThread, "qt.lottieqt.lottie.render.thread");
29
30QBatchRenderer *QBatchRenderer::m_rendererInstance = nullptr;
31
32QBatchRenderer::QBatchRenderer()
33 : QThread()
34{
35 const QByteArray cacheStr = qgetenv(varName: "QLOTTIE_RENDER_CACHE_SIZE");
36 int cacheSize = cacheStr.toInt();
37 if (cacheSize > 0) {
38 qCDebug(lcLottieQtLottieRenderThread) << "Setting frame cache size to" << cacheSize;
39 m_cacheSize = cacheSize;
40 }
41}
42
43QBatchRenderer::~QBatchRenderer()
44{
45 QMutexLocker mlocker(&m_mutex);
46
47 for (Entry *entry : std::as_const(t&: m_animData)) {
48 qDeleteAll(c: entry->frameCache);
49 delete entry->lottieTreeBlueprint;
50 delete entry;
51 }
52}
53
54QBatchRenderer *QBatchRenderer::instance()
55{
56 if (!m_rendererInstance)
57 m_rendererInstance = new QBatchRenderer;
58
59 return m_rendererInstance;
60}
61
62void QBatchRenderer::deleteInstance()
63{
64 delete m_rendererInstance;
65 m_rendererInstance = nullptr;
66}
67
68void QBatchRenderer::registerAnimator(QLottieAnimation *animator)
69{
70 QMutexLocker mlocker(&m_mutex);
71
72 qCDebug(lcLottieQtLottieRenderThread) << "Register Animator:"
73 << static_cast<void*>(animator);
74
75 Entry *&entry = m_animData[animator];
76 if (entry) {
77 qDeleteAll(c: entry->frameCache);
78 delete entry->lottieTreeBlueprint;
79 delete entry;
80 entry = nullptr;
81 }
82 Q_ASSERT(entry == nullptr);
83 entry = new Entry;
84 entry->animator = animator;
85 entry->startFrame = animator->startFrame();
86 entry->endFrame = animator->endFrame();
87 entry->currentFrame = animator->startFrame();
88 entry->animDir = animator->direction();
89 QLottieRoot *root = new QLottieRoot;
90 root->parseSource(jsonSource: animator->jsonSource(), fileSource: m_animData.keys().last()->source());
91 entry->lottieTreeBlueprint = root;
92 m_waitCondition.wakeAll();
93}
94
95void QBatchRenderer::deregisterAnimator(QLottieAnimation *animator)
96{
97 QMutexLocker mlocker(&m_mutex);
98
99 qCDebug(lcLottieQtLottieRenderThread) << "Deregister Animator:"
100 << static_cast<void*>(animator);
101
102 Entry *entry = m_animData.take(key: animator);
103 if (entry) {
104 qDeleteAll(c: entry->frameCache);
105 delete entry->lottieTreeBlueprint;
106 delete entry;
107 }
108}
109
110bool QBatchRenderer::gotoFrame(QLottieAnimation *animator, int frame)
111{
112 QMutexLocker mlocker(&m_mutex);
113
114 Entry *entry = m_animData.value(key: animator, defaultValue: nullptr);
115 if (entry) {
116 qCDebug(lcLottieQtLottieRenderThread) << "Animator:"
117 << static_cast<void*>(animator)
118 << "Goto frame:" << frame;
119 entry->currentFrame = frame;
120 entry->animDir = animator->direction();
121 pruneFrameCache(e: entry);
122 m_waitCondition.wakeAll();
123 return true;
124 }
125 return false;
126}
127
128void QBatchRenderer::pruneFrameCache(Entry* e)
129{
130 QHash<int, QLottieBase*>::iterator removeCandidate = e->frameCache.end();
131 if (e->frameCache.size() == m_cacheSize &&
132 !e->frameCache.contains(key: e->currentFrame))
133 removeCandidate = e->frameCache.begin();
134
135 QHash<int, QLottieBase*>::iterator it = e->frameCache.begin();
136 while (it != e->frameCache.end()) {
137 int frame = it.key();
138 if ((frame - e->currentFrame) * e->animDir >= 0) { // same frame or same direction
139 if (removeCandidate != e->frameCache.end() &&
140 (removeCandidate.key() - frame) * e->animDir < 0)
141 removeCandidate = it;
142 ++it;
143 } else {
144 qCDebug(lcLottieQtLottieRenderThread) << "Animator:" << static_cast<void*>(e->animator)
145 << "Remove frame from cache" << frame;
146 delete it.value();
147 it = e->frameCache.erase(it);
148 removeCandidate = e->frameCache.end();
149 }
150 }
151 if (removeCandidate != e->frameCache.end()) {
152 qCDebug(lcLottieQtLottieRenderThread) << "Animator:"
153 << static_cast<void*>(e->animator)
154 << "Remove frame from cache"
155 << removeCandidate.key()
156 << "(Reason - cache is full)";
157 e->frameCache.erase(it: removeCandidate);
158 }
159 m_lastRenderedFrame = -1;
160}
161
162QLottieBase *QBatchRenderer::getFrame(QLottieAnimation *animator, int frameNumber)
163{
164 QMutexLocker mlocker(&m_mutex);
165
166 Entry *entry = m_animData.value(key: animator, defaultValue: nullptr);
167 if (entry)
168 return entry->frameCache.value(key: frameNumber, defaultValue: nullptr);
169 else
170 return nullptr;
171}
172
173void QBatchRenderer::prerender(Entry *animEntry)
174{
175 while (animEntry->frameCache.size() < m_cacheSize) {
176 if (m_lastRenderedFrame == animEntry->currentFrame)
177 animEntry->currentFrame += animEntry->animDir;
178
179 QLottieBase *&lottieTree = animEntry->frameCache[animEntry->currentFrame];
180 if (lottieTree == nullptr) {
181 lottieTree = new QLottieBase(*animEntry->lottieTreeBlueprint);
182
183 for (QLottieBase *elem : lottieTree->children()) {
184 if (elem->active(frame: animEntry->currentFrame))
185 elem->updateProperties( frame: animEntry->currentFrame);
186 }
187 }
188
189 qCDebug(lcLottieQtLottieRenderThread) << "Animator:"
190 << static_cast<void*>(animEntry->animator)
191 << "Frame drawn to cache. FN:"
192 << animEntry->currentFrame;
193 emit frameReady(animator: animEntry->animator, frameNumber: animEntry->currentFrame);
194
195 animEntry->currentFrame += animEntry->animDir;
196
197 if (animEntry->currentFrame > animEntry->endFrame) {
198 animEntry->currentFrame = animEntry->startFrame;
199 } else if (animEntry->currentFrame < animEntry->startFrame) {
200 animEntry->currentFrame = animEntry->endFrame;
201 }
202 }
203}
204
205void QBatchRenderer::frameRendered(QLottieAnimation *animator, int frameNumber)
206{
207 QMutexLocker mlocker(&m_mutex);
208
209 Entry *entry = m_animData.value(key: animator, defaultValue: nullptr);
210 if (entry) {
211 qCDebug(lcLottieQtLottieRenderThread) << "Animator:" << static_cast<void*>(animator)
212 << "Remove frame from cache" << frameNumber;
213
214 QLottieBase *root = entry->frameCache.take(key: frameNumber);
215 if (root != nullptr) {
216 delete root;
217 m_waitCondition.wakeAll();
218 }
219 m_lastRenderedFrame = frameNumber;
220 }
221}
222
223void QBatchRenderer::run()
224{
225 qCDebug(lcLottieQtLottieRenderThread) << "rendering thread" << QThread::currentThread();
226
227 while (!isInterruptionRequested()) {
228 QMutexLocker mlocker(&m_mutex);
229
230 for (Entry *e : std::as_const(t&: m_animData))
231 prerender(animEntry: e);
232
233 m_waitCondition.wait(lockedMutex: &m_mutex);
234 }
235}
236
237QT_END_NAMESPACE
238

source code of qtlottie/src/lottie/renderer/qbatchrenderer.cpp