Revision control

Copy as Markdown

// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include <QtLottie/private/qbatchrenderer_p.h>
#include <QImage>
#include <QPainter>
#include <QHash>
#include <QMap>
#include <QMutexLocker>
#include <QLoggingCategory>
#include <QThread>
#include <QJsonDocument>
#include <QJsonArray>
#include <QtLottie/private/qlottieconstants_p.h>
#include <QtLottie/private/qlottiebase_p.h>
#include <QtLottie/private/qlottieflatlayers_p.h>
#include <QtLottie/private/qlottieroot_p.h>
#include <QtLottie/private/qlottielayer_p.h>
#include <QtLottie/private/qlottieanimation_p.h>
#include <QtLottie/private/qlottierasterrenderer_p.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcLottieQtLottieRenderThread, "qt.lottieqt.lottie.render.thread");
QBatchRenderer *QBatchRenderer::m_rendererInstance = nullptr;
QBatchRenderer::QBatchRenderer()
: QThread()
{
const QByteArray cacheStr = qgetenv("QLOTTIE_RENDER_CACHE_SIZE");
int cacheSize = cacheStr.toInt();
if (cacheSize > 0) {
qCDebug(lcLottieQtLottieRenderThread) << "Setting frame cache size to" << cacheSize;
m_cacheSize = cacheSize;
}
}
QBatchRenderer::~QBatchRenderer()
{
QMutexLocker mlocker(&m_mutex);
for (Entry *entry : std::as_const(m_animData)) {
qDeleteAll(entry->frameCache);
delete entry->lottieTreeBlueprint;
delete entry;
}
}
QBatchRenderer *QBatchRenderer::instance()
{
if (!m_rendererInstance)
m_rendererInstance = new QBatchRenderer;
return m_rendererInstance;
}
void QBatchRenderer::deleteInstance()
{
delete m_rendererInstance;
m_rendererInstance = nullptr;
}
void QBatchRenderer::registerAnimator(QLottieAnimation *animator)
{
QMutexLocker mlocker(&m_mutex);
qCDebug(lcLottieQtLottieRenderThread) << "Register Animator:"
<< static_cast<void*>(animator);
Entry *&entry = m_animData[animator];
if (entry) {
qDeleteAll(entry->frameCache);
delete entry->lottieTreeBlueprint;
delete entry;
entry = nullptr;
}
Q_ASSERT(entry == nullptr);
entry = new Entry;
entry->animator = animator;
entry->startFrame = animator->startFrame();
entry->endFrame = animator->endFrame();
entry->currentFrame = animator->startFrame();
entry->animDir = animator->direction();
QLottieRoot *root = new QLottieRoot;
int ret = root->parseSource(animator->jsonSource(), m_animData.keys().last()->source());
if (ret == -1)
quit();
entry->lottieTreeBlueprint = root;
m_waitCondition.wakeAll();
}
void QBatchRenderer::deregisterAnimator(QLottieAnimation *animator)
{
QMutexLocker mlocker(&m_mutex);
qCDebug(lcLottieQtLottieRenderThread) << "Deregister Animator:"
<< static_cast<void*>(animator);
Entry *entry = m_animData.take(animator);
if (entry) {
qDeleteAll(entry->frameCache);
delete entry->lottieTreeBlueprint;
delete entry;
}
}
bool QBatchRenderer::gotoFrame(QLottieAnimation *animator, int frame)
{
QMutexLocker mlocker(&m_mutex);
Entry *entry = m_animData.value(animator, nullptr);
if (entry) {
qCDebug(lcLottieQtLottieRenderThread) << "Animator:"
<< static_cast<void*>(animator)
<< "Goto frame:" << frame;
entry->currentFrame = frame;
entry->animDir = animator->direction();
pruneFrameCache(entry);
m_waitCondition.wakeAll();
return true;
}
return false;
}
void QBatchRenderer::pruneFrameCache(Entry* e)
{
QHash<int, QLottieRoot *>::iterator removeCandidate = e->frameCache.end();
if (e->frameCache.size() == m_cacheSize &&
!e->frameCache.contains(e->currentFrame))
removeCandidate = e->frameCache.begin();
QHash<int, QLottieRoot *>::iterator it = e->frameCache.begin();
while (it != e->frameCache.end()) {
int frame = it.key();
if ((frame - e->currentFrame) * e->animDir >= 0) { // same frame or same direction
if (removeCandidate != e->frameCache.end() &&
(removeCandidate.key() - frame) * e->animDir < 0)
removeCandidate = it;
++it;
} else {
qCDebug(lcLottieQtLottieRenderThread) << "Animator:" << static_cast<void*>(e->animator)
<< "Remove frame from cache" << frame;
delete it.value();
it = e->frameCache.erase(it);
removeCandidate = e->frameCache.end();
}
}
if (removeCandidate != e->frameCache.end()) {
qCDebug(lcLottieQtLottieRenderThread) << "Animator:"
<< static_cast<void*>(e->animator)
<< "Remove frame from cache"
<< removeCandidate.key()
<< "(Reason - cache is full)";
e->frameCache.erase(removeCandidate);
}
m_lastRenderedFrame = -1;
}
QLottieRoot *QBatchRenderer::getFrame(QLottieAnimation *animator, int frameNumber)
{
QMutexLocker mlocker(&m_mutex);
Entry *entry = m_animData.value(animator, nullptr);
if (entry)
return entry->frameCache.value(frameNumber, nullptr);
else
return nullptr;
}
void QBatchRenderer::prerender(Entry *animEntry)
{
while (animEntry->frameCache.size() < m_cacheSize) {
if (m_lastRenderedFrame == animEntry->currentFrame)
animEntry->currentFrame += animEntry->animDir;
QLottieRoot *&lottieTree = animEntry->frameCache[animEntry->currentFrame];
if (lottieTree == nullptr) {
lottieTree = new QLottieRoot(*animEntry->lottieTreeBlueprint);
for (QLottieBase *elem : lottieTree->children()) {
if (elem->active(animEntry->currentFrame))
elem->updateProperties( animEntry->currentFrame);
}
}
qCDebug(lcLottieQtLottieRenderThread) << "Animator:"
<< static_cast<void*>(animEntry->animator)
<< "Frame drawn to cache. FN:"
<< animEntry->currentFrame;
emit frameReady(animEntry->animator, animEntry->currentFrame);
animEntry->currentFrame += animEntry->animDir;
if (animEntry->currentFrame > animEntry->endFrame) {
animEntry->currentFrame = animEntry->startFrame;
} else if (animEntry->currentFrame < animEntry->startFrame) {
animEntry->currentFrame = animEntry->endFrame;
}
}
}
void QBatchRenderer::frameRendered(QLottieAnimation *animator, int frameNumber)
{
QMutexLocker mlocker(&m_mutex);
Entry *entry = m_animData.value(animator, nullptr);
if (entry) {
qCDebug(lcLottieQtLottieRenderThread) << "Animator:" << static_cast<void*>(animator)
<< "Remove frame from cache" << frameNumber;
QLottieRoot *root = entry->frameCache.take(frameNumber);
if (root != nullptr) {
delete root;
m_waitCondition.wakeAll();
}
m_lastRenderedFrame = frameNumber;
}
}
void QBatchRenderer::run()
{
qCDebug(lcLottieQtLottieRenderThread) << "rendering thread" << QThread::currentThread();
while (!isInterruptionRequested()) {
QMutexLocker mlocker(&m_mutex);
for (Entry *e : std::as_const(m_animData))
prerender(e);
m_waitCondition.wait(&m_mutex);
}
}
QT_END_NAMESPACE