diff --git a/Telegram/SourceFiles/chat_helpers/stickers.cpp b/Telegram/SourceFiles/chat_helpers/stickers.cpp index c7af4a5903..2635cdc881 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers.cpp @@ -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 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 LottieFromDocument( filepath, Lottie::FrameRequest{ box }); } - return Lottie::FromContent(data, filepath); + return Lottie::FromContent(data, filepath, Lottie::FrameRequest{ box }); } } // namespace Stickers diff --git a/Telegram/SourceFiles/history/media/history_media_sticker.cpp b/Telegram/SourceFiles/history/media/history_media_sticker.cpp index 83630e8e2e..357d03a370 100644 --- a/Telegram/SourceFiles/history/media/history_media_sticker.cpp +++ b/Telegram/SourceFiles/history/media/history_media_sticker.cpp @@ -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; diff --git a/Telegram/SourceFiles/lottie/lottie_animation.cpp b/Telegram/SourceFiles/lottie/lottie_animation.cpp index fb06bc6cfc..aa4b97ecbf 100644 --- a/Telegram/SourceFiles/lottie/lottie_animation.cpp +++ b/Telegram/SourceFiles/lottie/lottie_animation.cpp @@ -87,14 +87,17 @@ details::InitData CheckSharedState(std::unique_ptr 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( - std::move(animation))) + std::move(animation), + request)) : Error::ParseFailed; } @@ -139,8 +142,9 @@ std::unique_ptr CreateFromContent( std::unique_ptr FromContent( const QByteArray &data, - const QString &filepath) { - return std::make_unique(ReadContent(data, filepath)); + const QString &filepath, + const FrameRequest &request) { + return std::make_unique(ReadContent(data, filepath), request); } std::unique_ptr FromCached( @@ -157,7 +161,7 @@ std::unique_ptr FromCached( } QImage ReadThumbnail(const QByteArray &content) { - return Init(std::move(content)).match([]( + return Init(content, FrameRequest()).match([]( const std::unique_ptr &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)); }); }); diff --git a/Telegram/SourceFiles/lottie/lottie_animation.h b/Telegram/SourceFiles/lottie/lottie_animation.h index 294dc46f5c..3c4271d422 100644 --- a/Telegram/SourceFiles/lottie/lottie_animation.h +++ b/Telegram/SourceFiles/lottie/lottie_animation.h @@ -37,7 +37,8 @@ class FrameRenderer; std::unique_ptr FromContent( const QByteArray &data, - const QString &filepath); + const QString &filepath, + const FrameRequest &request); std::unique_ptr FromCached( FnMut)> get, // Main thread. FnMut put, // Unknown thread. @@ -58,7 +59,9 @@ std::unique_ptr 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)> get, // Main thread. FnMut put, // Unknown thread. diff --git a/Telegram/SourceFiles/lottie/lottie_cache.cpp b/Telegram/SourceFiles/lottie/lottie_cache.cpp index 765ee11bfc..0167d01f9f 100644 --- a/Telegram/SourceFiles/lottie/lottie_cache.cpp +++ b/Telegram/SourceFiles/lottie/lottie_cache.cpp @@ -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::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(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 { diff --git a/Telegram/SourceFiles/lottie/lottie_cache.h b/Telegram/SourceFiles/lottie/lottie_cache.h index c352d31845..983a99fa53 100644 --- a/Telegram/SourceFiles/lottie/lottie_cache.h +++ b/Telegram/SourceFiles/lottie/lottie_cache.h @@ -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(); diff --git a/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp b/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp index e960980853..dc4af1879e 100644 --- a/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp +++ b/Telegram/SourceFiles/lottie/lottie_frame_renderer.cpp @@ -177,9 +177,11 @@ void FrameRendererObject::queueGenerateFrames() { }); } -SharedState::SharedState(std::unique_ptr animation) +SharedState::SharedState( + std::unique_ptr animation, + const FrameRequest &request) : _animation(std::move(animation)) { - construct(FrameRequest()); + construct(request); } SharedState::SharedState( diff --git a/Telegram/SourceFiles/lottie/lottie_frame_renderer.h b/Telegram/SourceFiles/lottie/lottie_frame_renderer.h index c05cacd93b..5f881fb308 100644 --- a/Telegram/SourceFiles/lottie/lottie_frame_renderer.h +++ b/Telegram/SourceFiles/lottie/lottie_frame_renderer.h @@ -46,7 +46,9 @@ QImage PrepareFrameByRequest( class SharedState { public: - explicit SharedState(std::unique_ptr animation); + SharedState( + std::unique_ptr animation, + const FrameRequest &request); SharedState( const QByteArray &content, std::unique_ptr animation, diff --git a/Telegram/SourceFiles/storage/file_download.h b/Telegram/SourceFiles/storage/file_download.h index 76a6afc39b..03a198c1d8 100644 --- a/Telegram/SourceFiles/storage/file_download.h +++ b/Telegram/SourceFiles/storage/file_download.h @@ -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; diff --git a/Telegram/SourceFiles/window/layer_widget.cpp b/Telegram/SourceFiles/window/layer_widget.cpp index 934e6bb9d3..32db4fd8ed 100644 --- a/Telegram/SourceFiles/window/layer_widget.cpp +++ b/Telegram/SourceFiles/window/layer_widget.cpp @@ -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) { diff --git a/Telegram/ThirdParty/crl b/Telegram/ThirdParty/crl index d259aebc11..1f0d4470b1 160000 --- a/Telegram/ThirdParty/crl +++ b/Telegram/ThirdParty/crl @@ -1 +1 @@ -Subproject commit d259aebc11df52cb6ff8c738580dc4d8f245d681 +Subproject commit 1f0d4470b1234e31c75a4186abd59759d8142414