2019-04-27 09:12:53 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
|
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
|
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
|
|
*/
|
|
|
|
#include "lottie/lottie_frame_renderer.h"
|
|
|
|
|
2019-06-28 11:33:47 +00:00
|
|
|
#include "lottie/lottie_player.h"
|
2019-04-27 09:12:53 +00:00
|
|
|
#include "lottie/lottie_animation.h"
|
2019-06-26 17:14:46 +00:00
|
|
|
#include "lottie/lottie_cache.h"
|
2019-06-29 12:24:46 +00:00
|
|
|
#include "base/flat_map.h"
|
|
|
|
#include "logs.h"
|
2019-04-27 09:12:53 +00:00
|
|
|
|
2019-06-29 12:24:46 +00:00
|
|
|
#include <QPainter>
|
|
|
|
#include <rlottie.h>
|
2019-05-13 21:57:59 +00:00
|
|
|
#include <range/v3/algorithm/find.hpp>
|
|
|
|
#include <range/v3/algorithm/count_if.hpp>
|
2019-04-27 09:12:53 +00:00
|
|
|
|
2019-05-13 21:57:59 +00:00
|
|
|
namespace Images {
|
|
|
|
QImage prepareColored(QColor add, QImage image);
|
|
|
|
} // namespace Images
|
2019-04-27 09:12:53 +00:00
|
|
|
|
2019-05-13 21:57:59 +00:00
|
|
|
namespace Lottie {
|
|
|
|
namespace {
|
2019-04-27 09:12:53 +00:00
|
|
|
|
2019-05-13 21:57:59 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-07-05 17:15:25 +00:00
|
|
|
int GetLottieFrameRate(not_null<rlottie::Animation*> animation, Quality quality) {
|
|
|
|
const auto rate = int(qRound(animation->frameRate()));
|
|
|
|
return (quality == Quality::Default && rate == 60) ? (rate / 2) : rate;
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetLottieFramesCount(not_null<rlottie::Animation*> animation, Quality quality) {
|
|
|
|
const auto rate = int(qRound(animation->frameRate()));
|
|
|
|
const auto count = int(animation->totalFrame());
|
|
|
|
return (quality == Quality::Default && rate == 60) ? (count / 2) : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetLottieFrameIndex(not_null<rlottie::Animation*> animation, Quality quality, int index) {
|
|
|
|
const auto rate = int(qRound(animation->frameRate()));
|
|
|
|
return (quality == Quality::Default && rate == 60) ? (index * 2) : index;
|
|
|
|
}
|
|
|
|
|
2019-05-13 21:57:59 +00:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
class FrameRendererObject final {
|
|
|
|
public:
|
|
|
|
explicit FrameRendererObject(
|
|
|
|
crl::weak_on_queue<FrameRendererObject> weak);
|
|
|
|
|
2019-07-05 14:43:21 +00:00
|
|
|
void append(
|
|
|
|
std::unique_ptr<SharedState> entry,
|
|
|
|
const FrameRequest &request);
|
2019-06-28 13:24:33 +00:00
|
|
|
void frameShown();
|
2019-05-13 21:57:59 +00:00
|
|
|
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) {
|
2019-06-26 10:01:04 +00:00
|
|
|
if (request.box.isEmpty()) {
|
2019-05-13 21:57:59 +00:00
|
|
|
return true;
|
|
|
|
} else if (request.colored.has_value()) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-06-26 10:01:04 +00:00
|
|
|
const auto size = image.size();
|
|
|
|
return (request.box.width() == size.width())
|
|
|
|
|| (request.box.height() == size.height());
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] QImage PrepareByRequest(
|
|
|
|
const QImage &original,
|
|
|
|
const FrameRequest &request,
|
|
|
|
QImage storage) {
|
2019-06-26 10:01:04 +00:00
|
|
|
Expects(!request.box.isEmpty());
|
2019-05-13 21:57:59 +00:00
|
|
|
|
2019-06-26 10:01:04 +00:00
|
|
|
const auto size = request.size(original.size());
|
|
|
|
if (!GoodStorageForFrame(storage, size)) {
|
|
|
|
storage = CreateFrameStorage(size);
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
2019-05-14 08:19:08 +00:00
|
|
|
storage.fill(Qt::transparent);
|
|
|
|
|
2019-05-13 21:57:59 +00:00
|
|
|
{
|
|
|
|
QPainter p(&storage);
|
|
|
|
p.setRenderHint(QPainter::Antialiasing);
|
|
|
|
p.setRenderHint(QPainter::SmoothPixmapTransform);
|
|
|
|
p.setRenderHint(QPainter::HighQualityAntialiasing);
|
2019-06-26 10:01:04 +00:00
|
|
|
p.drawImage(QRect(QPoint(), size), original);
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
|
|
|
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)) {
|
|
|
|
}
|
|
|
|
|
2019-07-05 14:43:21 +00:00
|
|
|
void FrameRendererObject::append(
|
|
|
|
std::unique_ptr<SharedState> state,
|
|
|
|
const FrameRequest &request) {
|
|
|
|
_entries.push_back({ std::move(state), request });
|
2019-05-13 21:57:59 +00:00
|
|
|
queueGenerateFrames();
|
|
|
|
}
|
|
|
|
|
2019-06-28 13:24:33 +00:00
|
|
|
void FrameRendererObject::frameShown() {
|
2019-05-13 21:57:59 +00:00
|
|
|
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() {
|
2019-06-29 12:24:46 +00:00
|
|
|
auto players = base::flat_map<Player*, base::weak_ptr<Player>>();
|
|
|
|
const auto renderOne = [&](const Entry &entry) {
|
|
|
|
const auto result = entry.state->renderNextFrame(entry.request);
|
|
|
|
if (const auto player = result.notify.get()) {
|
|
|
|
players.emplace(player, result.notify);
|
|
|
|
}
|
|
|
|
return result.rendered;
|
2019-05-13 21:57:59 +00:00
|
|
|
};
|
2019-06-29 12:24:46 +00:00
|
|
|
const auto rendered = ranges::count_if(_entries, renderOne);
|
|
|
|
if (rendered) {
|
|
|
|
if (!players.empty()) {
|
|
|
|
crl::on_main([players = std::move(players)] {
|
|
|
|
for (const auto &[player, weak] : players) {
|
|
|
|
if (weak) {
|
|
|
|
weak->checkStep();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2019-05-13 21:57:59 +00:00
|
|
|
queueGenerateFrames();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FrameRendererObject::queueGenerateFrames() {
|
|
|
|
if (_queued) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_queued = true;
|
|
|
|
_weak.with([](FrameRendererObject &that) {
|
|
|
|
that._queued = false;
|
|
|
|
that.generateFrames();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-08-01 11:42:24 +00:00
|
|
|
|
|
|
|
Information SharedState::CalculateInformation(
|
|
|
|
Quality quality,
|
|
|
|
rlottie::Animation *animation,
|
|
|
|
Cache *cache) {
|
|
|
|
Expects(animation != nullptr || cache != nullptr);
|
|
|
|
|
|
|
|
auto width = size_t(0);
|
|
|
|
auto height = size_t(0);
|
|
|
|
if (animation) {
|
|
|
|
animation->size(width, height);
|
|
|
|
} else {
|
|
|
|
width = cache->originalSize().width();
|
|
|
|
height = cache->originalSize().height();
|
|
|
|
}
|
|
|
|
const auto rate = animation
|
|
|
|
? GetLottieFrameRate(animation, quality)
|
|
|
|
: cache->frameRate();
|
|
|
|
const auto count = animation
|
|
|
|
? GetLottieFramesCount(animation, quality)
|
|
|
|
: cache->framesCount();
|
|
|
|
|
|
|
|
auto result = Information();
|
|
|
|
result.size = QSize(
|
|
|
|
(width > 0 && width <= kMaxSize) ? int(width) : 0,
|
|
|
|
(height > 0 && height <= kMaxSize) ? int(height) : 0);
|
|
|
|
result.frameRate = (rate > 0 && rate <= kMaxFrameRate) ? int(rate) : 0;
|
|
|
|
result.framesCount = (count > 0 && count <= kMaxFramesCount)
|
|
|
|
? int(count)
|
|
|
|
: 0;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-06-27 16:57:32 +00:00
|
|
|
SharedState::SharedState(
|
|
|
|
std::unique_ptr<rlottie::Animation> animation,
|
2019-07-05 17:15:25 +00:00
|
|
|
const FrameRequest &request,
|
|
|
|
Quality quality)
|
2019-08-01 11:42:24 +00:00
|
|
|
: _info(CalculateInformation(quality, animation.get(), nullptr))
|
|
|
|
, _quality(quality)
|
|
|
|
, _animation(std::move(animation)) {
|
2019-06-27 16:57:32 +00:00
|
|
|
construct(request);
|
2019-06-26 17:14:46 +00:00
|
|
|
}
|
2019-06-25 14:16:38 +00:00
|
|
|
|
2019-06-26 17:14:46 +00:00
|
|
|
SharedState::SharedState(
|
|
|
|
const QByteArray &content,
|
2019-08-07 14:22:51 +00:00
|
|
|
const ColorReplacements *replacements,
|
2019-06-26 17:14:46 +00:00
|
|
|
std::unique_ptr<rlottie::Animation> animation,
|
2019-06-27 14:36:01 +00:00
|
|
|
std::unique_ptr<Cache> cache,
|
2019-07-05 17:15:25 +00:00
|
|
|
const FrameRequest &request,
|
|
|
|
Quality quality)
|
2019-08-01 11:42:24 +00:00
|
|
|
: _info(CalculateInformation(quality, animation.get(), cache.get()))
|
2019-07-05 17:15:25 +00:00
|
|
|
, _quality(quality)
|
2019-08-01 11:42:24 +00:00
|
|
|
, _cache(std::move(cache))
|
|
|
|
, _animation(std::move(animation))
|
2019-08-07 14:22:51 +00:00
|
|
|
, _content(content)
|
|
|
|
, _replacements(replacements) {
|
2019-06-26 17:14:46 +00:00
|
|
|
construct(request);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SharedState::construct(const FrameRequest &request) {
|
|
|
|
if (!isValid()) {
|
|
|
|
return;
|
|
|
|
}
|
2019-06-27 14:36:01 +00:00
|
|
|
auto cover = _cache ? _cache->takeFirstFrame() : QImage();
|
2019-06-26 17:14:46 +00:00
|
|
|
if (!cover.isNull()) {
|
|
|
|
init(std::move(cover), request);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (_cache) {
|
2019-08-01 11:42:24 +00:00
|
|
|
_cache->init(
|
|
|
|
_info.size,
|
|
|
|
_info.frameRate,
|
|
|
|
_info.framesCount,
|
|
|
|
request);
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
2019-06-26 17:14:46 +00:00
|
|
|
renderFrame(cover, request, 0);
|
|
|
|
init(std::move(cover), request);
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
|
|
|
|
2019-06-26 08:29:19 +00:00
|
|
|
bool SharedState::isValid() const {
|
2019-08-01 11:42:24 +00:00
|
|
|
return (_info.framesCount > 0)
|
|
|
|
&& (_info.frameRate > 0)
|
|
|
|
&& !_info.size.isEmpty();
|
2019-06-25 14:16:38 +00:00
|
|
|
}
|
|
|
|
|
2019-05-13 21:57:59 +00:00
|
|
|
void SharedState::renderFrame(
|
|
|
|
QImage &image,
|
|
|
|
const FrameRequest &request,
|
|
|
|
int index) {
|
2019-06-26 08:29:19 +00:00
|
|
|
if (!isValid()) {
|
2019-05-13 21:57:59 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-08-01 11:42:24 +00:00
|
|
|
const auto size = request.box.isEmpty()
|
|
|
|
? _info.size
|
|
|
|
: request.size(_info.size);
|
2019-05-13 21:57:59 +00:00
|
|
|
if (!GoodStorageForFrame(image, size)) {
|
|
|
|
image = CreateFrameStorage(size);
|
|
|
|
}
|
2019-06-27 14:36:01 +00:00
|
|
|
if (_cache && _cache->renderFrame(image, request, index)) {
|
2019-06-26 17:14:46 +00:00
|
|
|
return;
|
|
|
|
} else if (!_animation) {
|
2019-08-07 14:22:51 +00:00
|
|
|
_animation = details::CreateFromContent(_content, _replacements);
|
2019-06-26 17:14:46 +00:00
|
|
|
}
|
2019-05-13 21:57:59 +00:00
|
|
|
|
2019-06-26 17:14:46 +00:00
|
|
|
image.fill(Qt::transparent);
|
2019-06-25 14:16:38 +00:00
|
|
|
auto surface = rlottie::Surface(
|
|
|
|
reinterpret_cast<uint32_t*>(image.bits()),
|
|
|
|
image.width(),
|
|
|
|
image.height(),
|
|
|
|
image.bytesPerLine());
|
2019-07-05 17:15:25 +00:00
|
|
|
_animation->renderSync(
|
|
|
|
GetLottieFrameIndex(_animation.get(), _quality, index),
|
|
|
|
surface);
|
2019-06-26 17:14:46 +00:00
|
|
|
if (_cache) {
|
2019-06-27 14:36:01 +00:00
|
|
|
_cache->appendFrame(image, request, index);
|
|
|
|
if (_cache->framesReady() == _cache->framesCount()) {
|
2019-06-26 17:14:46 +00:00
|
|
|
_animation = nullptr;
|
|
|
|
}
|
|
|
|
}
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
|
|
|
|
2019-06-26 17:14:46 +00:00
|
|
|
void SharedState::init(QImage cover, const FrameRequest &request) {
|
2019-05-13 21:57:59 +00:00
|
|
|
Expects(!initialized());
|
|
|
|
|
2019-06-26 17:14:46 +00:00
|
|
|
_frames[0].request = request;
|
2019-05-13 21:57:59 +00:00
|
|
|
_frames[0].original = std::move(cover);
|
|
|
|
}
|
|
|
|
|
2019-06-30 13:19:57 +00:00
|
|
|
void SharedState::start(
|
|
|
|
not_null<Player*> owner,
|
|
|
|
crl::time started,
|
|
|
|
crl::time delay,
|
|
|
|
int skippedFrames) {
|
2019-05-13 21:57:59 +00:00
|
|
|
_owner = owner;
|
2019-06-30 13:19:57 +00:00
|
|
|
_started = started;
|
|
|
|
_delay = delay;
|
|
|
|
_skippedFrames = skippedFrames;
|
|
|
|
_counter.store(0, std::memory_order_release);
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool IsRendered(not_null<const Frame*> frame) {
|
2019-07-01 11:20:53 +00:00
|
|
|
return (frame->displayed == kTimeUnknown);
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SharedState::renderNextFrame(
|
|
|
|
not_null<Frame*> frame,
|
|
|
|
const FrameRequest &request) {
|
2019-08-01 11:42:24 +00:00
|
|
|
Expects(_info.framesCount > 0);
|
2019-05-13 21:57:59 +00:00
|
|
|
|
2019-08-01 11:42:24 +00:00
|
|
|
renderFrame(
|
|
|
|
frame->original,
|
|
|
|
request,
|
|
|
|
(++_frameIndex) % _info.framesCount);
|
2019-07-01 11:20:53 +00:00
|
|
|
frame->request = request;
|
2019-05-13 21:57:59 +00:00
|
|
|
PrepareFrameByRequest(frame);
|
2019-07-01 11:20:53 +00:00
|
|
|
frame->index = _frameIndex;
|
2019-05-13 21:57:59 +00:00
|
|
|
frame->displayed = kTimeUnknown;
|
|
|
|
}
|
|
|
|
|
2019-06-29 12:24:46 +00:00
|
|
|
auto SharedState::renderNextFrame(const FrameRequest &request)
|
|
|
|
-> RenderResult {
|
|
|
|
const auto prerender = [&](int index) -> RenderResult {
|
2019-05-13 21:57:59 +00:00
|
|
|
const auto frame = getFrame(index);
|
|
|
|
const auto next = getFrame((index + 1) % kFramesCount);
|
|
|
|
if (!IsRendered(frame)) {
|
|
|
|
renderNextFrame(frame, request);
|
2019-06-29 12:24:46 +00:00
|
|
|
return { true };
|
2019-05-13 21:57:59 +00:00
|
|
|
} else if (!IsRendered(next)) {
|
|
|
|
renderNextFrame(next, request);
|
2019-06-29 12:24:46 +00:00
|
|
|
return { true };
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
2019-06-29 12:24:46 +00:00
|
|
|
return { false };
|
2019-05-13 21:57:59 +00:00
|
|
|
};
|
2019-06-29 12:24:46 +00:00
|
|
|
const auto present = [&](int counter, int index) -> RenderResult {
|
2019-05-13 21:57:59 +00:00
|
|
|
const auto frame = getFrame(index);
|
|
|
|
if (!IsRendered(frame)) {
|
|
|
|
renderNextFrame(frame, request);
|
|
|
|
}
|
2019-07-01 11:20:53 +00:00
|
|
|
frame->display = countFrameDisplayTime(frame->index);
|
2019-05-13 21:57:59 +00:00
|
|
|
|
|
|
|
// Release this frame to the main thread for rendering.
|
|
|
|
_counter.store(
|
|
|
|
(counter + 1) % (2 * kFramesCount),
|
|
|
|
std::memory_order_release);
|
2019-06-29 12:24:46 +00:00
|
|
|
return { true, _owner };
|
2019-05-13 21:57:59 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2019-06-26 17:14:46 +00:00
|
|
|
Unexpected("Counter value in Lottie::SharedState::renderNextFrame.");
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
|
|
|
|
2019-07-01 11:20:53 +00:00
|
|
|
crl::time SharedState::countFrameDisplayTime(int index) const {
|
|
|
|
return _started
|
|
|
|
+ _delay
|
2019-08-01 11:42:24 +00:00
|
|
|
+ crl::time(1000) * (_skippedFrames + index) / _info.frameRate;
|
2019-07-01 11:20:53 +00:00
|
|
|
}
|
|
|
|
|
2019-05-13 21:57:59 +00:00
|
|
|
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 {
|
2019-08-01 11:42:24 +00:00
|
|
|
return isValid() ? _info : Information();
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
not_null<Frame*> SharedState::frameForPaint() {
|
|
|
|
const auto result = getFrame(counter() / 2);
|
|
|
|
Assert(!result->original.isNull());
|
|
|
|
Assert(result->displayed != kTimeUnknown);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-08-01 11:42:24 +00:00
|
|
|
int SharedState::framesCount() const {
|
|
|
|
return _info.framesCount;
|
|
|
|
}
|
|
|
|
|
2019-05-13 21:57:59 +00:00
|
|
|
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);
|
2019-05-28 11:39:38 +00:00
|
|
|
if (frame->displayed != kTimeUnknown) {
|
|
|
|
// Frame already displayed, but not yet shown.
|
2019-06-29 12:24:46 +00:00
|
|
|
return kFrameDisplayTimeAlreadyDone;
|
2019-05-28 11:39:38 +00:00
|
|
|
}
|
2019-05-13 21:57:59 +00:00
|
|
|
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.");
|
|
|
|
}
|
|
|
|
|
2019-07-01 11:20:53 +00:00
|
|
|
void SharedState::addTimelineDelay(crl::time delayed, int skippedFrames) {
|
|
|
|
if (!delayed && !skippedFrames) {
|
2019-06-30 13:19:57 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-07-01 11:20:53 +00:00
|
|
|
const auto recountCurrentFrame = [&](int counter) {
|
|
|
|
_delay += delayed;
|
|
|
|
_skippedFrames += skippedFrames;
|
|
|
|
|
|
|
|
const auto next = (counter + 1) % (2 * kFramesCount);
|
|
|
|
const auto index = next / 2;
|
|
|
|
const auto frame = getFrame(index);
|
|
|
|
if (frame->displayed != kTimeUnknown) {
|
|
|
|
// Frame already displayed.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Assert(IsRendered(frame));
|
|
|
|
Assert(frame->display != kTimeUnknown);
|
|
|
|
frame->display = countFrameDisplayTime(frame->index);
|
|
|
|
};
|
|
|
|
|
|
|
|
switch (counter()) {
|
|
|
|
case 0: Unexpected("Value 0 in SharedState::addTimelineDelay.");
|
|
|
|
case 1: return recountCurrentFrame(1);
|
|
|
|
case 2: Unexpected("Value 2 in SharedState::addTimelineDelay.");
|
|
|
|
case 3: return recountCurrentFrame(3);
|
|
|
|
case 4: Unexpected("Value 4 in SharedState::addTimelineDelay.");
|
|
|
|
case 5: return recountCurrentFrame(5);
|
|
|
|
case 6: Unexpected("Value 6 in SharedState::addTimelineDelay.");
|
|
|
|
case 7: return recountCurrentFrame(7);
|
|
|
|
}
|
|
|
|
Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime.");
|
2019-06-30 13:19:57 +00:00
|
|
|
}
|
|
|
|
|
2019-07-01 11:20:53 +00:00
|
|
|
void SharedState::markFrameDisplayed(crl::time now) {
|
2019-05-13 21:57:59 +00:00
|
|
|
const auto mark = [&](int counter) {
|
|
|
|
const auto next = (counter + 1) % (2 * kFramesCount);
|
|
|
|
const auto index = next / 2;
|
|
|
|
const auto frame = getFrame(index);
|
2019-07-01 11:20:53 +00:00
|
|
|
if (frame->displayed == kTimeUnknown) {
|
|
|
|
frame->displayed = now;
|
2019-06-28 13:24:33 +00:00
|
|
|
}
|
2019-05-13 21:57:59 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
switch (counter()) {
|
2019-06-30 13:19:57 +00:00
|
|
|
case 0: Unexpected("Value 0 in SharedState::markFrameDisplayed.");
|
2019-05-13 21:57:59 +00:00
|
|
|
case 1: return mark(1);
|
2019-06-30 13:19:57 +00:00
|
|
|
case 2: Unexpected("Value 2 in SharedState::markFrameDisplayed.");
|
2019-05-13 21:57:59 +00:00
|
|
|
case 3: return mark(3);
|
2019-06-30 13:19:57 +00:00
|
|
|
case 4: Unexpected("Value 4 in SharedState::markFrameDisplayed.");
|
2019-05-13 21:57:59 +00:00
|
|
|
case 5: return mark(5);
|
2019-06-30 13:19:57 +00:00
|
|
|
case 6: Unexpected("Value 6 in SharedState::markFrameDisplayed.");
|
2019-05-13 21:57:59 +00:00
|
|
|
case 7: return mark(7);
|
|
|
|
}
|
|
|
|
Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed.");
|
|
|
|
}
|
|
|
|
|
2019-07-01 11:20:53 +00:00
|
|
|
bool SharedState::markFrameShown() {
|
2019-05-13 21:57:59 +00:00
|
|
|
const auto jump = [&](int counter) {
|
|
|
|
const auto next = (counter + 1) % (2 * kFramesCount);
|
|
|
|
const auto index = next / 2;
|
|
|
|
const auto frame = getFrame(index);
|
|
|
|
if (frame->displayed == kTimeUnknown) {
|
2019-07-01 11:20:53 +00:00
|
|
|
return false;
|
2019-05-13 21:57:59 +00:00
|
|
|
}
|
|
|
|
_counter.store(
|
|
|
|
next,
|
|
|
|
std::memory_order_release);
|
2019-07-01 11:20:53 +00:00
|
|
|
return true;
|
2019-05-13 21:57:59 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
switch (counter()) {
|
2019-07-01 11:20:53 +00:00
|
|
|
case 0: return false;
|
2019-05-13 21:57:59 +00:00
|
|
|
case 1: return jump(1);
|
2019-07-01 11:20:53 +00:00
|
|
|
case 2: return false;
|
2019-05-13 21:57:59 +00:00
|
|
|
case 3: return jump(3);
|
2019-07-01 11:20:53 +00:00
|
|
|
case 4: return false;
|
2019-05-13 21:57:59 +00:00
|
|
|
case 5: return jump(5);
|
2019-07-01 11:20:53 +00:00
|
|
|
case 6: return false;
|
2019-05-13 21:57:59 +00:00
|
|
|
case 7: return jump(7);
|
|
|
|
}
|
|
|
|
Unexpected("Counter value in Lottie::SharedState::markFrameShown.");
|
|
|
|
}
|
|
|
|
|
2019-06-25 14:16:38 +00:00
|
|
|
SharedState::~SharedState() = default;
|
|
|
|
|
2019-06-28 13:24:33 +00:00
|
|
|
std::shared_ptr<FrameRenderer> FrameRenderer::CreateIndependent() {
|
|
|
|
return std::make_shared<FrameRenderer>();
|
|
|
|
}
|
|
|
|
|
2019-05-13 21:57:59 +00:00
|
|
|
std::shared_ptr<FrameRenderer> FrameRenderer::Instance() {
|
|
|
|
if (auto result = GlobalInstance.lock()) {
|
|
|
|
return result;
|
|
|
|
}
|
2019-06-28 13:24:33 +00:00
|
|
|
auto result = CreateIndependent();
|
2019-05-13 21:57:59 +00:00
|
|
|
GlobalInstance = result;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-07-05 14:43:21 +00:00
|
|
|
void FrameRenderer::append(
|
|
|
|
std::unique_ptr<SharedState> entry,
|
|
|
|
const FrameRequest &request) {
|
|
|
|
_wrapped.with([=, entry = std::move(entry)](
|
2019-05-13 21:57:59 +00:00
|
|
|
FrameRendererObject &unwrapped) mutable {
|
2019-07-05 14:43:21 +00:00
|
|
|
unwrapped.append(std::move(entry), request);
|
2019-05-13 21:57:59 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-06-28 13:24:33 +00:00
|
|
|
void FrameRenderer::frameShown() {
|
2019-05-13 21:57:59 +00:00
|
|
|
_wrapped.with([=](FrameRendererObject &unwrapped) {
|
2019-06-28 13:24:33 +00:00
|
|
|
unwrapped.frameShown();
|
2019-05-13 21:57:59 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
}
|
2019-04-27 09:12:53 +00:00
|
|
|
|
|
|
|
} // namespace Lottie
|