mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-08-31 14:38:15 +00:00
Parse and render lottie in the background.
This commit is contained in:
@@ -8,222 +8,439 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lottie/lottie_frame_renderer.h"
|
||||
|
||||
#include "lottie/lottie_animation.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QHash>
|
||||
#include <QMutexLocker>
|
||||
#include <QLoggingCategory>
|
||||
#include <QThread>
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include <QtBodymovin/private/bmconstants_p.h>
|
||||
#include <QtBodymovin/private/bmbase_p.h>
|
||||
#include <QtBodymovin/private/bmlayer_p.h>
|
||||
|
||||
#include "rasterrenderer/lottierasterrenderer.h"
|
||||
#include "logs.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(lcLottieQtBodymovinRenderThread, "qt.lottieqt.bodymovin.render.thread");
|
||||
#include <range/v3/algorithm/find.hpp>
|
||||
#include <range/v3/algorithm/count_if.hpp>
|
||||
#include <QJsonDocument>
|
||||
|
||||
namespace Images {
|
||||
QImage prepareColored(QColor add, QImage image);
|
||||
} // namespace Images
|
||||
|
||||
namespace Lottie {
|
||||
//
|
||||
//FrameRenderer *FrameRenderer::_rendererInstance = nullptr;
|
||||
//
|
||||
//FrameRenderer::~FrameRenderer()
|
||||
//{
|
||||
// QMutexLocker mlocker(&_mutex);
|
||||
// qDeleteAll(_animData);
|
||||
// qDeleteAll(_frameCache);
|
||||
//}
|
||||
//
|
||||
//FrameRenderer *FrameRenderer::instance()
|
||||
//{
|
||||
// if (!_rendererInstance)
|
||||
// _rendererInstance = new FrameRenderer;
|
||||
//
|
||||
// return _rendererInstance;
|
||||
//}
|
||||
//
|
||||
//void FrameRenderer::deleteInstance()
|
||||
//{
|
||||
// delete _rendererInstance;
|
||||
// _rendererInstance = nullptr;
|
||||
//}
|
||||
//
|
||||
//void FrameRenderer::registerAnimator(Animation *animator)
|
||||
//{
|
||||
// QMutexLocker mlocker(&_mutex);
|
||||
//
|
||||
// qCDebug(lcLottieQtBodymovinRenderThread) << "Register Animator:"
|
||||
// << static_cast<void*>(animator);
|
||||
//
|
||||
// Entry *entry = new Entry;
|
||||
// entry->animator = animator;
|
||||
// entry->startFrame = animator->startFrame();
|
||||
// entry->endFrame = animator->endFrame();
|
||||
// entry->currentFrame = animator->startFrame();
|
||||
// entry->animDir = animator->direction();
|
||||
// entry->bmTreeBlueprint = new BMBase;
|
||||
// parse(entry->bmTreeBlueprint, animator->jsonSource());
|
||||
// _animData.insert(animator, entry);
|
||||
// _waitCondition.wakeAll();
|
||||
//}
|
||||
//
|
||||
//void FrameRenderer::deregisterAnimator(Animation *animator)
|
||||
//{
|
||||
// QMutexLocker mlocker(&_mutex);
|
||||
//
|
||||
// qCDebug(lcLottieQtBodymovinRenderThread) << "Deregister Animator:"
|
||||
// << static_cast<void*>(animator);
|
||||
//
|
||||
// Entry *entry = _animData.value(animator, nullptr);
|
||||
// if (entry) {
|
||||
// qDeleteAll(entry->frameCache);
|
||||
// delete entry->bmTreeBlueprint;
|
||||
// delete entry;
|
||||
// _animData.remove(animator);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//bool FrameRenderer::gotoFrame(Animation *animator, int frame)
|
||||
//{
|
||||
// QMutexLocker mlocker(&_mutex);
|
||||
// Entry *entry = _animData.value(animator, nullptr);
|
||||
// if (entry) {
|
||||
// qCDebug(lcLottieQtBodymovinRenderThread) << "Animator:"
|
||||
// << static_cast<void*>(animator)
|
||||
// << "Goto frame:" << frame;
|
||||
// entry->currentFrame = frame;
|
||||
// entry->animDir = animator->direction();
|
||||
// pruneFrameCache(entry);
|
||||
// _waitCondition.wakeAll();
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
//}
|
||||
//
|
||||
//FrameRenderer::FrameRenderer() : QThread() {
|
||||
// const QByteArray cacheStr = qgetenv("QLOTTIE_RENDER_CACHE_SIZE");
|
||||
// int cacheSize = cacheStr.toInt();
|
||||
// if (cacheSize > 0) {
|
||||
// qCDebug(lcLottieQtBodymovinRenderThread) << "Setting frame cache size to" << cacheSize;
|
||||
// _cacheSize = cacheSize;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//void FrameRenderer::pruneFrameCache(Entry* e)
|
||||
//{
|
||||
// QHash<int, BMBase*>::iterator it = e->frameCache.begin();
|
||||
//
|
||||
// while (it != e->frameCache.end()) {
|
||||
// if (it.key() == e->currentFrame) {
|
||||
// ++it;
|
||||
// } else {
|
||||
// delete it.value();
|
||||
// it = e->frameCache.erase(it);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//BMBase *FrameRenderer::getFrame(Animation *animator, int frameNumber)
|
||||
//{
|
||||
// QMutexLocker mlocker(&_mutex);
|
||||
//
|
||||
// Entry *entry = _animData.value(animator, nullptr);
|
||||
// if (entry)
|
||||
// return entry->frameCache.value(frameNumber, nullptr);
|
||||
// else
|
||||
// return nullptr;
|
||||
//}
|
||||
//
|
||||
//void FrameRenderer::prerender(Entry *animEntry)
|
||||
//{
|
||||
// while (animEntry->frameCache.count() < _cacheSize) {
|
||||
// if (!animEntry->frameCache.contains(animEntry->currentFrame)) {
|
||||
// BMBase *bmTree = new BMBase(*animEntry->bmTreeBlueprint);
|
||||
//
|
||||
// for (BMBase *elem : bmTree->children()) {
|
||||
// if (elem->active(animEntry->currentFrame))
|
||||
// elem->updateProperties( animEntry->currentFrame);
|
||||
// }
|
||||
//
|
||||
// animEntry->frameCache.insert( animEntry->currentFrame, bmTree);
|
||||
// }
|
||||
//
|
||||
// qCDebug(lcLottieQtBodymovinRenderThread) << "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 FrameRenderer::frameRendered(Animation *animator, int frameNumber)
|
||||
//{
|
||||
// QMutexLocker mlocker(&_mutex);
|
||||
// Entry *entry = _animData.value(animator, nullptr);
|
||||
// if (entry) {
|
||||
// qCDebug(lcLottieQtBodymovinRenderThread) << "Animator:" << static_cast<void*>(animator)
|
||||
// << "Remove frame from cache" << frameNumber;
|
||||
//
|
||||
// BMBase *root = entry->frameCache.value(frameNumber, nullptr);
|
||||
// delete root;
|
||||
// entry->frameCache.remove(frameNumber);
|
||||
// _waitCondition.wakeAll();
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//void FrameRenderer::run()
|
||||
//{
|
||||
// qCDebug(lcLottieQtBodymovinRenderThread) << "rendering thread" << QThread::currentThread();
|
||||
//
|
||||
// while (!isInterruptionRequested()) {
|
||||
// QMutexLocker mlocker(&_mutex);
|
||||
//
|
||||
// for (Entry *e : qAsConst(_animData))
|
||||
// prerender(e);
|
||||
//
|
||||
// _waitCondition.wait(&_mutex);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//int FrameRenderer::parse(BMBase* rootElement, const QByteArray &jsonSource)
|
||||
//{
|
||||
// QJsonDocument doc = QJsonDocument::fromJson(jsonSource);
|
||||
// QJsonObject rootObj = doc.object();
|
||||
//
|
||||
// if (rootObj.empty())
|
||||
// return -1;
|
||||
//
|
||||
// QJsonArray jsonLayers = rootObj.value(QLatin1String("layers")).toArray();
|
||||
// QJsonArray::const_iterator jsonLayerIt = jsonLayers.constEnd();
|
||||
// while (jsonLayerIt != jsonLayers.constBegin()) {
|
||||
// jsonLayerIt--;
|
||||
// QJsonObject jsonLayer = (*jsonLayerIt).toObject();
|
||||
// BMLayer *layer = BMLayer::construct(jsonLayer);
|
||||
// if (layer) {
|
||||
// layer->setParent(rootElement);
|
||||
// // Mask layers must be rendered before the layers they affect to
|
||||
// // although they appear before in layer hierarchy. For this reason
|
||||
// // move a mask after the affected layers, so it will be rendered first
|
||||
// if (layer->isMaskLayer())
|
||||
// rootElement->prependChild(layer);
|
||||
// else
|
||||
// rootElement->appendChild(layer);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return 0;
|
||||
//}
|
||||
namespace {
|
||||
|
||||
constexpr auto kDisplaySkipped = crl::time(-1);
|
||||
|
||||
std::weak_ptr<FrameRenderer> GlobalInstance;
|
||||
|
||||
constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied;
|
||||
|
||||
bool GoodStorageForFrame(const QImage &storage, QSize size) {
|
||||
return !storage.isNull()
|
||||
&& (storage.format() == kImageFormat)
|
||||
&& (storage.size() == size)
|
||||
&& storage.isDetached();
|
||||
}
|
||||
|
||||
QImage CreateFrameStorage(QSize size) {
|
||||
return QImage(size, kImageFormat);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class FrameRendererObject final {
|
||||
public:
|
||||
explicit FrameRendererObject(
|
||||
crl::weak_on_queue<FrameRendererObject> weak);
|
||||
|
||||
void append(std::unique_ptr<SharedState> entry);
|
||||
void frameShown(not_null<SharedState*> entry);
|
||||
void updateFrameRequest(
|
||||
not_null<SharedState*> entry,
|
||||
const FrameRequest &request);
|
||||
void remove(not_null<SharedState*> entry);
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
std::unique_ptr<SharedState> state;
|
||||
FrameRequest request;
|
||||
};
|
||||
|
||||
static not_null<SharedState*> StateFromEntry(const Entry &entry) {
|
||||
return entry.state.get();
|
||||
}
|
||||
|
||||
void queueGenerateFrames();
|
||||
void generateFrames();
|
||||
|
||||
crl::weak_on_queue<FrameRendererObject> _weak;
|
||||
std::vector<Entry> _entries;
|
||||
bool _queued = false;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool GoodForRequest(
|
||||
const QImage &image,
|
||||
const FrameRequest &request) {
|
||||
if (request.resize.isEmpty()) {
|
||||
return true;
|
||||
} else if (request.colored.has_value()) {
|
||||
return false;
|
||||
}
|
||||
return (request.resize == image.size());
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PrepareByRequest(
|
||||
const QImage &original,
|
||||
const FrameRequest &request,
|
||||
QImage storage) {
|
||||
Expects(!request.resize.isEmpty());
|
||||
|
||||
if (!GoodStorageForFrame(storage, request.resize)) {
|
||||
storage = CreateFrameStorage(request.resize);
|
||||
}
|
||||
{
|
||||
QPainter p(&storage);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
p.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
p.drawImage(QRect(QPoint(), request.resize), original);
|
||||
}
|
||||
if (request.colored.has_value()) {
|
||||
storage = Images::prepareColored(*request.colored, std::move(storage));
|
||||
}
|
||||
return storage;
|
||||
}
|
||||
|
||||
QImage PrepareFrameByRequest(
|
||||
not_null<Frame*> frame,
|
||||
bool useExistingPrepared = false) {
|
||||
Expects(!frame->original.isNull());
|
||||
|
||||
if (GoodForRequest(frame->original, frame->request)) {
|
||||
return frame->original;
|
||||
} else if (frame->prepared.isNull() || !useExistingPrepared) {
|
||||
frame->prepared = PrepareByRequest(
|
||||
frame->original,
|
||||
frame->request,
|
||||
std::move(frame->prepared));
|
||||
}
|
||||
return frame->prepared;
|
||||
}
|
||||
|
||||
FrameRendererObject::FrameRendererObject(
|
||||
crl::weak_on_queue<FrameRendererObject> weak)
|
||||
: _weak(std::move(weak)) {
|
||||
}
|
||||
|
||||
void FrameRendererObject::append(std::unique_ptr<SharedState> state) {
|
||||
_entries.push_back({ std::move(state) });
|
||||
queueGenerateFrames();
|
||||
}
|
||||
|
||||
void FrameRendererObject::frameShown(not_null<SharedState*> entry) {
|
||||
queueGenerateFrames();
|
||||
}
|
||||
|
||||
void FrameRendererObject::updateFrameRequest(
|
||||
not_null<SharedState*> entry,
|
||||
const FrameRequest &request) {
|
||||
const auto i = ranges::find(_entries, entry, &StateFromEntry);
|
||||
Assert(i != end(_entries));
|
||||
i->request = request;
|
||||
}
|
||||
|
||||
void FrameRendererObject::remove(not_null<SharedState*> entry) {
|
||||
const auto i = ranges::find(_entries, entry, &StateFromEntry);
|
||||
Assert(i != end(_entries));
|
||||
_entries.erase(i);
|
||||
}
|
||||
|
||||
void FrameRendererObject::generateFrames() {
|
||||
const auto renderOne = [&](const Entry & entry) {
|
||||
return entry.state->renderNextFrame(entry.request);
|
||||
};
|
||||
if (ranges::count_if(_entries, renderOne) > 0) {
|
||||
queueGenerateFrames();
|
||||
}
|
||||
}
|
||||
|
||||
void FrameRendererObject::queueGenerateFrames() {
|
||||
if (_queued) {
|
||||
return;
|
||||
}
|
||||
_queued = true;
|
||||
_weak.with([](FrameRendererObject &that) {
|
||||
that._queued = false;
|
||||
that.generateFrames();
|
||||
});
|
||||
}
|
||||
|
||||
SharedState::SharedState(const QJsonObject &definition)
|
||||
: _scene(definition) {
|
||||
if (_scene.endFrame() > _scene.startFrame()) {
|
||||
auto cover = QImage();
|
||||
renderFrame(cover, FrameRequest::NonStrict(), 0);
|
||||
init(std::move(cover));
|
||||
}
|
||||
}
|
||||
|
||||
void SharedState::renderFrame(
|
||||
QImage &image,
|
||||
const FrameRequest &request,
|
||||
int index) {
|
||||
const auto realSize = QSize(_scene.width(), _scene.height());
|
||||
if (realSize.isEmpty() || _scene.endFrame() <= _scene.startFrame()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto size = request.resize.isEmpty() ? realSize : request.resize;
|
||||
if (!GoodStorageForFrame(image, size)) {
|
||||
image = CreateFrameStorage(size);
|
||||
}
|
||||
image.fill(Qt::transparent);
|
||||
|
||||
QPainter p(&image);
|
||||
p.setRenderHints(QPainter::Antialiasing);
|
||||
p.setRenderHints(QPainter::SmoothPixmapTransform);
|
||||
p.setRenderHint(QPainter::TextAntialiasing);
|
||||
p.setRenderHints(QPainter::HighQualityAntialiasing);
|
||||
if (realSize != size) {
|
||||
p.scale(
|
||||
size.width() / float64(realSize.width()),
|
||||
size.height() / float64(realSize.height()));
|
||||
}
|
||||
|
||||
const auto frame = std::clamp(
|
||||
_scene.startFrame() + index,
|
||||
_scene.startFrame(),
|
||||
_scene.endFrame() - 1);
|
||||
_scene.updateProperties(frame);
|
||||
|
||||
LottieRasterRenderer renderer(&p);
|
||||
_scene.render(renderer, frame);
|
||||
}
|
||||
|
||||
void SharedState::init(QImage cover) {
|
||||
Expects(!initialized());
|
||||
|
||||
_frames[0].original = std::move(cover);
|
||||
_frames[0].position = 0;
|
||||
|
||||
// Usually main thread sets displayed time before _counter increment.
|
||||
// But in this case we update _counter, so we set a fake displayed time.
|
||||
_frames[0].displayed = kDisplaySkipped;
|
||||
|
||||
_counter.store(0, std::memory_order_release);
|
||||
}
|
||||
|
||||
void SharedState::start(not_null<Animation*> owner, crl::time now) {
|
||||
_owner = owner;
|
||||
_started = now;
|
||||
}
|
||||
|
||||
bool IsRendered(not_null<const Frame*> frame) {
|
||||
return (frame->position != kTimeUnknown)
|
||||
&& (frame->displayed == kTimeUnknown);
|
||||
}
|
||||
|
||||
void SharedState::renderNextFrame(
|
||||
not_null<Frame*> frame,
|
||||
const FrameRequest &request) {
|
||||
const auto framesCount = (_scene.endFrame() - _scene.startFrame());
|
||||
Assert(framesCount > 0);
|
||||
|
||||
renderFrame(frame->original, request, (++_frameIndex) % framesCount);
|
||||
PrepareFrameByRequest(frame);
|
||||
frame->position = crl::time(1000) * _frameIndex / _scene.frameRate();
|
||||
frame->displayed = kTimeUnknown;
|
||||
}
|
||||
|
||||
bool SharedState::renderNextFrame(const FrameRequest &request) {
|
||||
const auto prerender = [&](int index) {
|
||||
const auto frame = getFrame(index);
|
||||
const auto next = getFrame((index + 1) % kFramesCount);
|
||||
if (!IsRendered(frame)) {
|
||||
renderNextFrame(frame, request);
|
||||
return true;
|
||||
} else if (!IsRendered(next)) {
|
||||
renderNextFrame(next, request);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto present = [&](int counter, int index) {
|
||||
const auto frame = getFrame(index);
|
||||
if (!IsRendered(frame)) {
|
||||
renderNextFrame(frame, request);
|
||||
}
|
||||
frame->display = _started + frame->position;
|
||||
|
||||
// Release this frame to the main thread for rendering.
|
||||
_counter.store(
|
||||
(counter + 1) % (2 * kFramesCount),
|
||||
std::memory_order_release);
|
||||
crl::on_main(_owner, [=] {
|
||||
_owner->checkNextFrame();
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
switch (counter()) {
|
||||
case 0: return present(0, 1);
|
||||
case 1: return prerender(2);
|
||||
case 2: return present(2, 2);
|
||||
case 3: return prerender(3);
|
||||
case 4: return present(4, 3);
|
||||
case 5: return prerender(0);
|
||||
case 6: return present(6, 0);
|
||||
case 7: return prerender(1);
|
||||
}
|
||||
Unexpected("Counter value in VideoTrack::Shared::prepareState.");
|
||||
|
||||
}
|
||||
|
||||
int SharedState::counter() const {
|
||||
return _counter.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool SharedState::initialized() const {
|
||||
return (counter() != kCounterUninitialized);
|
||||
}
|
||||
|
||||
not_null<Frame*> SharedState::getFrame(int index) {
|
||||
Expects(index >= 0 && index < kFramesCount);
|
||||
|
||||
return &_frames[index];
|
||||
}
|
||||
|
||||
not_null<const Frame*> SharedState::getFrame(int index) const {
|
||||
Expects(index >= 0 && index < kFramesCount);
|
||||
|
||||
return &_frames[index];
|
||||
}
|
||||
|
||||
Information SharedState::information() const {
|
||||
auto result = Information();
|
||||
result.frameRate = _scene.frameRate();
|
||||
result.size = QSize(_scene.width(), _scene.height());
|
||||
result.framesCount = _scene.endFrame() - _scene.startFrame();
|
||||
return result;
|
||||
}
|
||||
|
||||
not_null<Frame*> SharedState::frameForPaint() {
|
||||
const auto result = getFrame(counter() / 2);
|
||||
Assert(!result->original.isNull());
|
||||
Assert(result->position != kTimeUnknown);
|
||||
Assert(result->displayed != kTimeUnknown);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
crl::time SharedState::nextFrameDisplayTime() const {
|
||||
const auto frameDisplayTime = [&](int counter) {
|
||||
const auto next = (counter + 1) % (2 * kFramesCount);
|
||||
const auto index = next / 2;
|
||||
const auto frame = getFrame(index);
|
||||
Assert(IsRendered(frame));
|
||||
Assert(frame->display != kTimeUnknown);
|
||||
|
||||
return frame->display;
|
||||
};
|
||||
|
||||
switch (counter()) {
|
||||
case 0: return kTimeUnknown;
|
||||
case 1: return frameDisplayTime(1);
|
||||
case 2: return kTimeUnknown;
|
||||
case 3: return frameDisplayTime(3);
|
||||
case 4: return kTimeUnknown;
|
||||
case 5: return frameDisplayTime(5);
|
||||
case 6: return kTimeUnknown;
|
||||
case 7: return frameDisplayTime(7);
|
||||
}
|
||||
Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime.");
|
||||
}
|
||||
|
||||
crl::time SharedState::markFrameDisplayed(crl::time now) {
|
||||
const auto mark = [&](int counter) {
|
||||
const auto next = (counter + 1) % (2 * kFramesCount);
|
||||
const auto index = next / 2;
|
||||
const auto frame = getFrame(index);
|
||||
Assert(frame->position != kTimeUnknown);
|
||||
Assert(frame->displayed == kTimeUnknown);
|
||||
|
||||
frame->displayed = now;
|
||||
return frame->position;
|
||||
};
|
||||
|
||||
switch (counter()) {
|
||||
case 0: Unexpected("Value 0 in SharedState::markFrameDisplayed.");
|
||||
case 1: return mark(1);
|
||||
case 2: Unexpected("Value 2 in SharedState::markFrameDisplayed.");
|
||||
case 3: return mark(3);
|
||||
case 4: Unexpected("Value 4 in SharedState::markFrameDisplayed.");
|
||||
case 5: return mark(5);
|
||||
case 6: Unexpected("Value 6 in SharedState::markFrameDisplayed.");
|
||||
case 7: return mark(7);
|
||||
}
|
||||
Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed.");
|
||||
}
|
||||
|
||||
crl::time SharedState::markFrameShown() {
|
||||
const auto jump = [&](int counter) {
|
||||
const auto next = (counter + 1) % (2 * kFramesCount);
|
||||
const auto index = next / 2;
|
||||
const auto frame = getFrame(index);
|
||||
Assert(frame->position != kTimeUnknown);
|
||||
if (frame->displayed == kTimeUnknown) {
|
||||
return kTimeUnknown;
|
||||
}
|
||||
_counter.store(
|
||||
next,
|
||||
std::memory_order_release);
|
||||
return frame->position;
|
||||
};
|
||||
|
||||
switch (counter()) {
|
||||
case 0: return kTimeUnknown;
|
||||
case 1: return jump(1);
|
||||
case 2: return kTimeUnknown;
|
||||
case 3: return jump(3);
|
||||
case 4: return kTimeUnknown;
|
||||
case 5: return jump(5);
|
||||
case 6: return kTimeUnknown;
|
||||
case 7: return jump(7);
|
||||
}
|
||||
Unexpected("Counter value in Lottie::SharedState::markFrameShown.");
|
||||
}
|
||||
|
||||
std::shared_ptr<FrameRenderer> FrameRenderer::Instance() {
|
||||
if (auto result = GlobalInstance.lock()) {
|
||||
return result;
|
||||
}
|
||||
auto result = std::make_shared<FrameRenderer>();
|
||||
GlobalInstance = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void FrameRenderer::append(std::unique_ptr<SharedState> entry) {
|
||||
_wrapped.with([entry = std::move(entry)](
|
||||
FrameRendererObject &unwrapped) mutable {
|
||||
unwrapped.append(std::move(entry));
|
||||
});
|
||||
}
|
||||
|
||||
void FrameRenderer::frameShown(not_null<SharedState*> entry) {
|
||||
_wrapped.with([=](FrameRendererObject &unwrapped) {
|
||||
unwrapped.frameShown(entry);
|
||||
});
|
||||
}
|
||||
|
||||
void FrameRenderer::updateFrameRequest(
|
||||
not_null<SharedState*> entry,
|
||||
const FrameRequest &request) {
|
||||
_wrapped.with([=](FrameRendererObject &unwrapped) {
|
||||
unwrapped.updateFrameRequest(entry, request);
|
||||
});
|
||||
}
|
||||
|
||||
void FrameRenderer::remove(not_null<SharedState*> entry) {
|
||||
_wrapped.with([=](FrameRendererObject &unwrapped) {
|
||||
unwrapped.remove(entry);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Lottie
|
||||
|
Reference in New Issue
Block a user