Fix caching for large sticker area.

This commit is contained in:
John Preston 2019-06-27 18:57:32 +02:00
parent 808583c5ae
commit 4a7b5a8e01
11 changed files with 80 additions and 33 deletions

View File

@ -23,6 +23,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat_helpers.h"
namespace Stickers {
namespace {
constexpr auto kDontCacheLottieAfterArea = 512 * 512;
} // namespace
void ApplyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) {
auto &v = d.vsets.v;
@ -1093,6 +1098,13 @@ std::unique_ptr<Lottie::Animation> LottieFromDocument(
QSize box) {
const auto data = document->data();
const auto filepath = document->filepath();
if (box.width() & box.height() > kDontCacheLottieAfterArea) {
// Don't use frame caching for large stickers.
return Lottie::FromContent(
data,
filepath,
Lottie::FrameRequest{ box });
}
if (const auto baseKey = document->bigFileBaseCacheKey()) {
const auto key = Storage::Cache::Key{
baseKey->high,
@ -1116,7 +1128,7 @@ std::unique_ptr<Lottie::Animation> LottieFromDocument(
filepath,
Lottie::FrameRequest{ box });
}
return Lottie::FromContent(data, filepath);
return Lottie::FromContent(data, filepath, Lottie::FrameRequest{ box });
}
} // namespace Stickers

View File

@ -185,8 +185,7 @@ void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, c
pixmap);
} else if (lottieReady) {
auto request = Lottie::FrameRequest();
request.box = QSize(st::maxStickerSize, st::maxStickerSize)
* cIntRetinaFactor();
request.box = QSize(_pixw, _pixh) * cIntRetinaFactor();
if (selected) {
request.colored = st::msgStickerOverlay->c;
}
@ -194,9 +193,15 @@ void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, c
if (!paused) {
_lottie->markFrameShown();
}
const auto frame = _lottie->frame(request);
const auto size = frame.size() / cIntRetinaFactor();
p.drawImage(
QRect(usex + (usew - _pixw) / 2, (minHeight() - _pixh) / 2, _pixw, _pixh),
_lottie->frame(request));
QRect(
QPoint(
usex + (usew - size.width()) / 2,
(minHeight() - size.height()) / 2),
size),
frame);
}
if (!inWebPage) {
auto fullRight = usex + usew;

View File

@ -87,14 +87,17 @@ details::InitData CheckSharedState(std::unique_ptr<SharedState> state) {
return state;
}
details::InitData Init(const QByteArray &content) {
details::InitData Init(
const QByteArray &content,
const FrameRequest &request) {
if (const auto error = ContentError(content)) {
return *error;
}
auto animation = details::CreateFromContent(content);
return animation
? CheckSharedState(std::make_unique<SharedState>(
std::move(animation)))
std::move(animation),
request))
: Error::ParseFailed;
}
@ -139,8 +142,9 @@ std::unique_ptr<rlottie::Animation> CreateFromContent(
std::unique_ptr<Animation> FromContent(
const QByteArray &data,
const QString &filepath) {
return std::make_unique<Animation>(ReadContent(data, filepath));
const QString &filepath,
const FrameRequest &request) {
return std::make_unique<Animation>(ReadContent(data, filepath), request);
}
std::unique_ptr<Animation> FromCached(
@ -157,7 +161,7 @@ std::unique_ptr<Animation> FromCached(
}
QImage ReadThumbnail(const QByteArray &content) {
return Init(std::move(content)).match([](
return Init(content, FrameRequest()).match([](
const std::unique_ptr<SharedState> &state) {
return state->frameForPaint()->original;
}, [](Error) {
@ -165,11 +169,11 @@ QImage ReadThumbnail(const QByteArray &content) {
});
}
Animation::Animation(const QByteArray &content)
Animation::Animation(const QByteArray &content, const FrameRequest &request)
: _timer([=] { checkNextFrameRender(); }) {
const auto weak = base::make_weak(this);
crl::async([=] {
crl::on_main(weak, [=, data = Init(content)]() mutable {
crl::on_main(weak, [=, data = Init(content, request)]() mutable {
initDone(std::move(data));
});
});

View File

@ -37,7 +37,8 @@ class FrameRenderer;
std::unique_ptr<Animation> FromContent(
const QByteArray &data,
const QString &filepath);
const QString &filepath,
const FrameRequest &request);
std::unique_ptr<Animation> FromCached(
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.
@ -58,7 +59,9 @@ std::unique_ptr<rlottie::Animation> CreateFromContent(
class Animation final : public base::has_weak_ptr {
public:
explicit Animation(const QByteArray &content);
explicit Animation(
const QByteArray &content,
const FrameRequest &request);
Animation(
FnMut<void(FnMut<void(QByteArray &&cached)>)> get, // Main thread.
FnMut<void(QByteArray &&cached)> put, // Unknown thread.

View File

@ -23,6 +23,9 @@ namespace {
constexpr auto kAlignStorage = 16;
// Must not exceed max database allowed entry size.
constexpr auto kMaxCacheSize = 10 * 1024 * 1024;
void Xor(EncodedStorage &to, const EncodedStorage &from) {
Expects(to.size() == from.size());
@ -451,7 +454,7 @@ bool Cache::readHeader(const FrameRequest &request) {
}
QDataStream stream(&_data, QIODevice::ReadOnly);
auto encoder = quint32(0);
auto encoder = qint32(0);
stream >> encoder;
if (static_cast<Encoder>(encoder) != Encoder::YUV420A4_LZ4) {
return false;
@ -542,13 +545,26 @@ void Cache::appendFrame(
prepareBuffers();
}
Assert(frame.size() == _size);
const auto now = crl::profile();
Encode(_uncompressed, frame, _encode.cache, _encode.context);
const auto enc = crl::profile();
CompressAndSwapFrame(
_encode.compressBuffer,
(index != 0) ? &_encode.xorCompressBuffer : nullptr,
_uncompressed,
_previous);
_encode.compressedFrames.push_back(_encode.compressBuffer);
_encode.compress += crl::profile() - enc;
_encode.encode += enc - now;
const auto compressed = _encode.compressBuffer;
const auto nowSize = (_data.isEmpty() ? headerSize() : _data.size())
+ _encode.totalSize;
const auto totalSize = nowSize + compressed.size();
if (nowSize <= kMaxCacheSize && totalSize > kMaxCacheSize) {
// Write to cache while we still can.
finalizeEncoding();
}
_encode.totalSize += compressed.size();
_encode.compressedFrames.push_back(compressed);
_encode.compressedFrames.back().detach();
if (++_framesReady == _framesCount) {
finalizeEncoding();
@ -560,31 +576,26 @@ void Cache::finalizeEncoding() {
return;
}
const auto size = (_data.isEmpty() ? headerSize() : _data.size())
+ ranges::accumulate(
_encode.compressedFrames,
0,
std::plus(),
&QByteArray::size);
+ _encode.totalSize;
if (_data.isEmpty()) {
_data.reserve(size);
writeHeader();
}
auto xored = 0;
const auto offset = _data.size();
_data.resize(size);
auto to = _data.data() + offset;
for (const auto &block : _encode.compressedFrames) {
const auto amount = qint32(block.size());
memcpy(to, block.data(), amount);
if (*reinterpret_cast<const qint32*>(block.data()) < 0) {
++xored;
}
to += amount;
}
if (_data.size() <= kMaxCacheSize) {
_put(QByteArray(_data));
LOG(("SIZE: %1 (%2x%3, %4 frames, %5 encode, %6 compress)").arg(_data.size()).arg(_size.width()).arg(_size.height()).arg(_framesCount).arg(_encode.encode / float64(_encode.compressedFrames.size())).arg(_encode.compress / float64(_encode.compressedFrames.size())));
} else {
LOG(("WARNING: %1 (%2x%3, %4 frames, %5 encode, %6 compress)").arg(_data.size()).arg(_size.width()).arg(_size.height()).arg(_framesCount).arg(_encode.encode / float64(_encode.compressedFrames.size())).arg(_encode.compress / float64(_encode.compressedFrames.size())));
}
_encode = EncodeFields();
LOG(("SIZE: %1 (%2x%3, %4 frames, %5 xored)").arg(_data.size()).arg(_size.width()).arg(_size.height()).arg(_framesCount).arg(xored));
_put(QByteArray(_data));
}
int Cache::headerSize() const {

View File

@ -94,6 +94,9 @@ private:
QByteArray xorCompressBuffer;
QImage cache;
FFmpeg::SwscalePointer context;
int totalSize = 0;
crl::profile_time encode = 0;
crl::profile_time compress = 0;
};
int headerSize() const;
void prepareBuffers();

View File

@ -177,9 +177,11 @@ void FrameRendererObject::queueGenerateFrames() {
});
}
SharedState::SharedState(std::unique_ptr<rlottie::Animation> animation)
SharedState::SharedState(
std::unique_ptr<rlottie::Animation> animation,
const FrameRequest &request)
: _animation(std::move(animation)) {
construct(FrameRequest());
construct(request);
}
SharedState::SharedState(

View File

@ -46,7 +46,9 @@ QImage PrepareFrameByRequest(
class SharedState {
public:
explicit SharedState(std::unique_ptr<rlottie::Animation> animation);
SharedState(
std::unique_ptr<rlottie::Animation> animation,
const FrameRequest &request);
SharedState(
const QByteArray &content,
std::unique_ptr<rlottie::Animation> animation,

View File

@ -20,7 +20,9 @@ namespace Cache {
struct Key;
} // namespace Cache
// This value is used in local cache database settings!
constexpr auto kMaxFileInMemory = 10 * 1024 * 1024; // 10 MB max file could be hold in memory
constexpr auto kMaxVoiceInMemory = 2 * 1024 * 1024; // 2 MB audio is hold in memory and auto loaded
constexpr auto kMaxStickerInMemory = 2 * 1024 * 1024; // 2 MB stickers hold in memory, auto loaded and displayed inline
constexpr auto kMaxWallPaperInMemory = kMaxFileInMemory;

View File

@ -1049,7 +1049,10 @@ QSize MediaPreviewWidget::currentDimensions() const {
void MediaPreviewWidget::setupLottie() {
Expects(_document != nullptr);
_lottie = Lottie::FromContent(_document->data(), _document->filepath());
_lottie = Lottie::FromContent(
_document->data(),
_document->filepath(),
Lottie::FrameRequest{ currentDimensions() * cIntRetinaFactor() });
_lottie->updates(
) | rpl::start_with_next_error([=](Lottie::Update update) {

@ -1 +1 @@
Subproject commit d259aebc11df52cb6ff8c738580dc4d8f245d681
Subproject commit 1f0d4470b1234e31c75a4186abd59759d8142414