234 lines
6 KiB
C++
234 lines
6 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "lottie/details/lottie_frame_provider_cached_multi.h"
|
|
|
|
#include "base/assertion.h"
|
|
|
|
#include <range/v3/numeric/accumulate.hpp>
|
|
|
|
namespace Lottie {
|
|
|
|
FrameProviderCachedMulti::FrameProviderCachedMulti(
|
|
const QByteArray &content,
|
|
FnMut<void(int index, QByteArray &&cached)> put,
|
|
std::vector<QByteArray> caches,
|
|
const FrameRequest &request,
|
|
Quality quality,
|
|
const ColorReplacements *replacements)
|
|
: _content(content)
|
|
, _replacements(replacements)
|
|
, _put(std::move(put))
|
|
, _direct(quality) {
|
|
Expects(!caches.empty());
|
|
|
|
_caches.reserve(caches.size());
|
|
const auto emplace = [&](const QByteArray &cached) {
|
|
const auto index = int(_caches.size());
|
|
_caches.emplace_back(cached, request, [=](QByteArray &&v) {
|
|
// We capture reference to _put, so the provider is not movable.
|
|
_put(index, std::move(v));
|
|
});
|
|
};
|
|
const auto load = [&] {
|
|
if (_direct.loaded() || _direct.load(content, replacements)) {
|
|
return true;
|
|
}
|
|
_caches.clear();
|
|
return false;
|
|
};
|
|
const auto fill = [&] {
|
|
if (!load()) {
|
|
return false;
|
|
}
|
|
while (_caches.size() < caches.size()) {
|
|
emplace({});
|
|
}
|
|
return true;
|
|
};
|
|
for (const auto &cached : caches) {
|
|
emplace(cached);
|
|
auto &cache = _caches.back();
|
|
const auto &first = _caches.front();
|
|
Assert(cache.sizeRounding() == first.sizeRounding());
|
|
|
|
if (!cache.framesCount()) {
|
|
if (!fill()) {
|
|
return;
|
|
}
|
|
break;
|
|
} else if (cache.framesReady() < cache.framesCount() && !load()) {
|
|
return;
|
|
} else if (cache.frameRate() != first.frameRate()
|
|
|| cache.originalSize() != first.originalSize()) {
|
|
_caches.pop_back();
|
|
if (!fill()) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!_direct.loaded()) {
|
|
_direct.setInformation({
|
|
.size = _caches.front().originalSize(),
|
|
.frameRate = _caches.front().frameRate(),
|
|
.framesCount = ranges::accumulate(
|
|
_caches,
|
|
0,
|
|
std::plus<>(),
|
|
&Cache::framesCount),
|
|
});
|
|
}
|
|
if (!validateFramesPerCache() && _framesPerCache > 0) {
|
|
fill();
|
|
}
|
|
}
|
|
|
|
bool FrameProviderCachedMulti::validateFramesPerCache() {
|
|
const auto &info = information();
|
|
const auto count = int(_caches.size());
|
|
_framesPerCache = (info.framesCount + count - 1) / count;
|
|
if (!_framesPerCache
|
|
|| (info.framesCount <= (count - 1) * _framesPerCache)) {
|
|
_framesPerCache = 0;
|
|
return false;
|
|
}
|
|
for (auto i = 0; i != count; ++i) {
|
|
const auto cacheFramesCount = _caches[i].framesCount();
|
|
if (!cacheFramesCount) {
|
|
break;
|
|
}
|
|
const auto shouldBe = (i + 1 == count
|
|
? (info.framesCount - (count - 1) * _framesPerCache)
|
|
: _framesPerCache);
|
|
if (cacheFramesCount != shouldBe) {
|
|
_caches.erase(begin(_caches) + i, end(_caches));
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QImage FrameProviderCachedMulti::construct(
|
|
std::unique_ptr<FrameProviderToken> &token,
|
|
const FrameRequest &request) {
|
|
if (!_framesPerCache) {
|
|
if (token) {
|
|
token->result = FrameRenderResult::Failed;
|
|
}
|
|
return QImage();
|
|
}
|
|
auto cover = QImage();
|
|
using Token = FrameProviderCachedMultiToken;
|
|
const auto my = static_cast<Token*>(token.get());
|
|
if (!my || my->exclusive) {
|
|
const auto &info = information();
|
|
const auto count = int(_caches.size());
|
|
for (auto i = 0; i != count; ++i) {
|
|
auto cacheCover = _caches[i].takeFirstFrame();
|
|
if (cacheCover.isNull()) {
|
|
_caches[i].init(
|
|
info.size,
|
|
info.frameRate,
|
|
(i + 1 == count
|
|
? (info.framesCount - (count - 1) * _framesPerCache)
|
|
: _framesPerCache),
|
|
request);
|
|
} else if (!i) {
|
|
cover = std::move(cacheCover);
|
|
}
|
|
}
|
|
if (!cover.isNull()) {
|
|
if (my) {
|
|
_caches[0].keepUpContext(my->context);
|
|
}
|
|
return cover;
|
|
}
|
|
}
|
|
render(token, cover, request, 0);
|
|
return cover;
|
|
}
|
|
|
|
const Information &FrameProviderCachedMulti::information() {
|
|
return _direct.information();
|
|
}
|
|
|
|
bool FrameProviderCachedMulti::valid() {
|
|
return _direct.valid() && (_framesPerCache > 0);
|
|
}
|
|
|
|
int FrameProviderCachedMulti::sizeRounding() {
|
|
return _caches.front().sizeRounding();
|
|
}
|
|
|
|
std::unique_ptr<FrameProviderToken> FrameProviderCachedMulti::createToken() {
|
|
auto result = std::make_unique<FrameProviderCachedMultiToken>();
|
|
if (!_caches.empty()) {
|
|
_caches.front().prepareBuffers(result->context);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool FrameProviderCachedMulti::render(
|
|
const std::unique_ptr<FrameProviderToken> &token,
|
|
QImage &to,
|
|
const FrameRequest &request,
|
|
int index) {
|
|
if (!valid()) {
|
|
if (token) {
|
|
token->result = FrameRenderResult::Failed;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const auto original = information().size;
|
|
const auto size = request.box.isEmpty()
|
|
? original
|
|
: request.size(original, sizeRounding());
|
|
if (!GoodStorageForFrame(to, size)) {
|
|
to = CreateFrameStorage(size);
|
|
}
|
|
const auto cacheIndex = index / _framesPerCache;
|
|
const auto indexInCache = index % _framesPerCache;
|
|
Assert(cacheIndex < _caches.size());
|
|
auto &cache = _caches[cacheIndex];
|
|
using Token = FrameProviderCachedMultiToken;
|
|
const auto my = static_cast<Token*>(token.get());
|
|
if (my && !my->exclusive) {
|
|
// Many threads may get here simultaneously.
|
|
my->result = cache.renderFrame(
|
|
my->context,
|
|
to,
|
|
request,
|
|
indexInCache);
|
|
return (my->result == FrameRenderResult::Ok);
|
|
}
|
|
const auto result = cache.renderFrame(to, request, indexInCache);
|
|
if (result == FrameRenderResult::Ok) {
|
|
if (my) {
|
|
cache.keepUpContext(my->context);
|
|
}
|
|
return true;
|
|
} else if (result == FrameRenderResult::Failed
|
|
// We don't support changing size on the fly for shared providers.
|
|
|| (result == FrameRenderResult::BadCacheSize && my)
|
|
|| (!_direct.loaded() && !_direct.load(_content, _replacements))) {
|
|
_direct.setInformation({});
|
|
return false;
|
|
}
|
|
_direct.renderToPrepared(to, index);
|
|
cache.appendFrame(to, request, indexInCache);
|
|
if (cache.framesReady() == cache.framesCount()
|
|
&& cacheIndex + 1 == _caches.size()) {
|
|
_direct.unload();
|
|
}
|
|
if (my) {
|
|
cache.keepUpContext(my->context);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace Lottie
|