From 4a7b5a8e01290ea02d3c57ff43b1fbcd124118f1 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 27 Jun 2019 18:57:32 +0200
Subject: [PATCH] Fix caching for large sticker area.

---
 .../SourceFiles/chat_helpers/stickers.cpp     | 14 ++++++-
 .../history/media/history_media_sticker.cpp   | 13 +++++--
 .../SourceFiles/lottie/lottie_animation.cpp   | 18 +++++----
 .../SourceFiles/lottie/lottie_animation.h     |  7 +++-
 Telegram/SourceFiles/lottie/lottie_cache.cpp  | 39 ++++++++++++-------
 Telegram/SourceFiles/lottie/lottie_cache.h    |  3 ++
 .../lottie/lottie_frame_renderer.cpp          |  6 ++-
 .../lottie/lottie_frame_renderer.h            |  4 +-
 Telegram/SourceFiles/storage/file_download.h  |  2 +
 Telegram/SourceFiles/window/layer_widget.cpp  |  5 ++-
 Telegram/ThirdParty/crl                       |  2 +-
 11 files changed, 80 insertions(+), 33 deletions(-)

diff --git a/Telegram/SourceFiles/chat_helpers/stickers.cpp b/Telegram/SourceFiles/chat_helpers/stickers.cpp
index c7af4a590..2635cdc88 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<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
diff --git a/Telegram/SourceFiles/history/media/history_media_sticker.cpp b/Telegram/SourceFiles/history/media/history_media_sticker.cpp
index 83630e8e2..357d03a37 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 fb06bc6cf..aa4b97ecb 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<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));
 		});
 	});
diff --git a/Telegram/SourceFiles/lottie/lottie_animation.h b/Telegram/SourceFiles/lottie/lottie_animation.h
index 294dc46f5..3c4271d42 100644
--- a/Telegram/SourceFiles/lottie/lottie_animation.h
+++ b/Telegram/SourceFiles/lottie/lottie_animation.h
@@ -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.
diff --git a/Telegram/SourceFiles/lottie/lottie_cache.cpp b/Telegram/SourceFiles/lottie/lottie_cache.cpp
index 765ee11bf..0167d01f9 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) != 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 {
diff --git a/Telegram/SourceFiles/lottie/lottie_cache.h b/Telegram/SourceFiles/lottie/lottie_cache.h
index c352d3184..983a99fa5 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 e96098085..dc4af1879 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<rlottie::Animation> animation)
+SharedState::SharedState(
+	std::unique_ptr<rlottie::Animation> 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 c05cacd93..5f881fb30 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<rlottie::Animation> animation);
+	SharedState(
+		std::unique_ptr<rlottie::Animation> animation,
+		const FrameRequest &request);
 	SharedState(
 		const QByteArray &content,
 		std::unique_ptr<rlottie::Animation> animation,
diff --git a/Telegram/SourceFiles/storage/file_download.h b/Telegram/SourceFiles/storage/file_download.h
index 76a6afc39..03a198c1d 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 934e6bb9d..32db4fd8e 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 d259aebc1..1f0d4470b 160000
--- a/Telegram/ThirdParty/crl
+++ b/Telegram/ThirdParty/crl
@@ -1 +1 @@
-Subproject commit d259aebc11df52cb6ff8c738580dc4d8f245d681
+Subproject commit 1f0d4470b1234e31c75a4186abd59759d8142414