mirror of https://github.com/procxx/kepka.git
				
				
				
			Trivial in-memory frame caching.
This commit is contained in:
		
							parent
							
								
									56e137b20f
								
							
						
					
					
						commit
						a026aec786
					
				|  | @ -1099,7 +1099,7 @@ std::unique_ptr<Lottie::Animation> LottieFromDocument( | ||||||
| 			Storage::Cache::Key{ key->high, key->low + int(sizeTag) }, | 			Storage::Cache::Key{ key->high, key->low + int(sizeTag) }, | ||||||
| 			data, | 			data, | ||||||
| 			filepath, | 			filepath, | ||||||
| 			box); | 			Lottie::FrameRequest{ box }); | ||||||
| 	} | 	} | ||||||
| 	return Lottie::FromContent(data, filepath); | 	return Lottie::FromContent(data, filepath); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1368,9 +1368,10 @@ void StickersListWidget::setupLottie(Set &set, int section, int index) { | ||||||
| 	auto &sticker = set.stickers[index]; | 	auto &sticker = set.stickers[index]; | ||||||
| 	const auto document = sticker.document; | 	const auto document = sticker.document; | ||||||
| 
 | 
 | ||||||
| 	sticker.animated = Lottie::FromContent( | 	sticker.animated = Stickers::LottieFromDocument( | ||||||
| 		document->data(), | 		document, | ||||||
| 		document->filepath()); | 		Stickers::LottieSize::StickersColumn, // #TODO stickers
 | ||||||
|  | 		boundingBoxSize() * cIntRetinaFactor()); | ||||||
| 	const auto animation = sticker.animated.get(); | 	const auto animation = sticker.animated.get(); | ||||||
| 
 | 
 | ||||||
| 	animation->updates( | 	animation->updates( | ||||||
|  |  | ||||||
|  | @ -97,7 +97,10 @@ QSize HistorySticker::countCurrentSize(int newWidth) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void HistorySticker::setupLottie() { | void HistorySticker::setupLottie() { | ||||||
| 	_lottie = Lottie::FromContent(_data->data(), _data->filepath()); | 	_lottie = Stickers::LottieFromDocument( | ||||||
|  | 		_data, | ||||||
|  | 		Stickers::LottieSize::MessageHistory, | ||||||
|  | 		QSize(st::maxStickerSize, st::maxStickerSize) * cIntRetinaFactor()); | ||||||
| 	_parent->data()->history()->owner().registerHeavyViewPart(_parent); | 	_parent->data()->history()->owner().registerHeavyViewPart(_parent); | ||||||
| 
 | 
 | ||||||
| 	_lottie->updates( | 	_lottie->updates( | ||||||
|  |  | ||||||
|  | @ -76,18 +76,6 @@ std::optional<Error> ContentError(const QByteArray &content) { | ||||||
| 	return std::nullopt; | 	return std::nullopt; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::unique_ptr<rlottie::Animation> CreateImplementation( |  | ||||||
| 		const QByteArray &content) { |  | ||||||
| 	const auto string = UnpackGzip(content); |  | ||||||
| 	Assert(string.size() <= kMaxFileSize); |  | ||||||
| 
 |  | ||||||
| 	auto result = rlottie::Animation::loadFromData(string, std::string()); |  | ||||||
| 	if (!result) { |  | ||||||
| 		qWarning() << "Lottie Error: Parse failed."; |  | ||||||
| 	} |  | ||||||
| 	return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| details::InitData CheckSharedState(std::unique_ptr<SharedState> state) { | details::InitData CheckSharedState(std::unique_ptr<SharedState> state) { | ||||||
| 	Expects(state != nullptr); | 	Expects(state != nullptr); | ||||||
| 
 | 
 | ||||||
|  | @ -104,7 +92,7 @@ details::InitData Init(const QByteArray &content) { | ||||||
| 	if (const auto error = ContentError(content)) { | 	if (const auto error = ContentError(content)) { | ||||||
| 		return *error; | 		return *error; | ||||||
| 	} | 	} | ||||||
| 	auto animation = CreateImplementation(content); | 	auto animation = details::CreateFromContent(content); | ||||||
| 	return animation | 	return animation | ||||||
| 		? CheckSharedState(std::make_unique<SharedState>( | 		? CheckSharedState(std::make_unique<SharedState>( | ||||||
| 			std::move(animation))) | 			std::move(animation))) | ||||||
|  | @ -116,22 +104,43 @@ details::InitData Init( | ||||||
| 		not_null<Storage::Cache::Database*> cache, | 		not_null<Storage::Cache::Database*> cache, | ||||||
| 		Storage::Cache::Key key, | 		Storage::Cache::Key key, | ||||||
| 		const QByteArray &cached, | 		const QByteArray &cached, | ||||||
| 		QSize box) { | 		const FrameRequest &request) { | ||||||
| 	if (const auto error = ContentError(content)) { | 	if (const auto error = ContentError(content)) { | ||||||
| 		return *error; | 		return *error; | ||||||
| 	} | 	} | ||||||
| 	auto state = CacheState(cached, box); | 	auto state = CacheState(cached, request); | ||||||
| 	const auto prepare = !state.framesCount() | 	const auto prepare = !state.framesCount() | ||||||
| 		|| (state.framesReady() < state.framesCount()); | 		|| (state.framesReady() < state.framesCount()); | ||||||
| 	auto animation = prepare ? CreateImplementation(content) : nullptr; | 	auto animation = prepare ? details::CreateFromContent(content) : nullptr; | ||||||
| 	return (!prepare || animation) | 	return (!prepare || animation) | ||||||
| 		? CheckSharedState(std::make_unique<SharedState>( | 		? CheckSharedState(std::make_unique<SharedState>( | ||||||
| 			std::move(animation))) | 			content, | ||||||
|  | 			std::move(animation), | ||||||
|  | 			std::move(state), | ||||||
|  | 			cache, | ||||||
|  | 			key, | ||||||
|  | 			request)) | ||||||
| 		: Error::ParseFailed; | 		: Error::ParseFailed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace
 | } // namespace
 | ||||||
| 
 | 
 | ||||||
|  | namespace details { | ||||||
|  | 
 | ||||||
|  | std::unique_ptr<rlottie::Animation> CreateFromContent( | ||||||
|  | 		const QByteArray &content) { | ||||||
|  | 	const auto string = UnpackGzip(content); | ||||||
|  | 	Assert(string.size() <= kMaxFileSize); | ||||||
|  | 
 | ||||||
|  | 	auto result = rlottie::Animation::loadFromData(string, std::string()); | ||||||
|  | 	if (!result) { | ||||||
|  | 		qWarning() << "Lottie Error: Parse failed."; | ||||||
|  | 	} | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace details
 | ||||||
|  | 
 | ||||||
| std::unique_ptr<Animation> FromContent( | std::unique_ptr<Animation> FromContent( | ||||||
| 		const QByteArray &data, | 		const QByteArray &data, | ||||||
| 		const QString &filepath) { | 		const QString &filepath) { | ||||||
|  | @ -143,12 +152,12 @@ std::unique_ptr<Animation> FromCached( | ||||||
| 		Storage::Cache::Key key, | 		Storage::Cache::Key key, | ||||||
| 		const QByteArray &data, | 		const QByteArray &data, | ||||||
| 		const QString &filepath, | 		const QString &filepath, | ||||||
| 		QSize box) { | 	const FrameRequest &request) { | ||||||
| 	return std::make_unique<Animation>( | 	return std::make_unique<Animation>( | ||||||
| 		cache, | 		cache, | ||||||
| 		key, | 		key, | ||||||
| 		ReadContent(data, filepath), | 		ReadContent(data, filepath), | ||||||
| 		box); | 		request); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| QImage ReadThumbnail(const QByteArray &content) { | QImage ReadThumbnail(const QByteArray &content) { | ||||||
|  | @ -174,12 +183,12 @@ Animation::Animation( | ||||||
| 	not_null<Storage::Cache::Database*> cache, | 	not_null<Storage::Cache::Database*> cache, | ||||||
| 	Storage::Cache::Key key, | 	Storage::Cache::Key key, | ||||||
| 	const QByteArray &content, | 	const QByteArray &content, | ||||||
| 	QSize box) | 	const FrameRequest &request) | ||||||
| : _timer([=] { checkNextFrameRender(); }) { | : _timer([=] { checkNextFrameRender(); }) { | ||||||
| 	const auto weak = base::make_weak(this); | 	const auto weak = base::make_weak(this); | ||||||
| 	cache->get(key, [=](QByteArray &&cached) mutable { | 	cache->get(key, [=](QByteArray &&cached) mutable { | ||||||
| 		crl::async([=] { | 		crl::async([=] { | ||||||
| 			auto result = Init(content, cache, key, cached, box); | 			auto result = Init(content, cache, key, cached, request); | ||||||
| 			crl::on_main(weak, [=, data = std::move(result)]() mutable { | 			crl::on_main(weak, [=, data = std::move(result)]() mutable { | ||||||
| 				initDone(std::move(data)); | 				initDone(std::move(data)); | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|  | @ -30,6 +30,10 @@ struct Key; | ||||||
| } // namespace Cache
 | } // namespace Cache
 | ||||||
| } // namespace Storage
 | } // namespace Storage
 | ||||||
| 
 | 
 | ||||||
|  | namespace rlottie { | ||||||
|  | class Animation; | ||||||
|  | } // namespace rlottie
 | ||||||
|  | 
 | ||||||
| namespace Lottie { | namespace Lottie { | ||||||
| 
 | 
 | ||||||
| inline constexpr auto kMaxFileSize = 1024 * 1024; | inline constexpr auto kMaxFileSize = 1024 * 1024; | ||||||
|  | @ -46,7 +50,7 @@ std::unique_ptr<Animation> FromCached( | ||||||
| 	Storage::Cache::Key key, | 	Storage::Cache::Key key, | ||||||
| 	const QByteArray &data, | 	const QByteArray &data, | ||||||
| 	const QString &filepath, | 	const QString &filepath, | ||||||
| 	QSize box); | 	const FrameRequest &request); | ||||||
| 
 | 
 | ||||||
| QImage ReadThumbnail(const QByteArray &content); | QImage ReadThumbnail(const QByteArray &content); | ||||||
| 
 | 
 | ||||||
|  | @ -54,6 +58,9 @@ namespace details { | ||||||
| 
 | 
 | ||||||
| using InitData = base::variant<std::unique_ptr<SharedState>, Error>; | using InitData = base::variant<std::unique_ptr<SharedState>, Error>; | ||||||
| 
 | 
 | ||||||
|  | std::unique_ptr<rlottie::Animation> CreateFromContent( | ||||||
|  | 	const QByteArray &content); | ||||||
|  | 
 | ||||||
| } // namespace details
 | } // namespace details
 | ||||||
| 
 | 
 | ||||||
| class Animation final : public base::has_weak_ptr { | class Animation final : public base::has_weak_ptr { | ||||||
|  | @ -63,7 +70,7 @@ public: | ||||||
| 		not_null<Storage::Cache::Database*> cache, | 		not_null<Storage::Cache::Database*> cache, | ||||||
| 		Storage::Cache::Key key, | 		Storage::Cache::Key key, | ||||||
| 		const QByteArray &content, | 		const QByteArray &content, | ||||||
| 		QSize box); | 		const FrameRequest &request); | ||||||
| 	~Animation(); | 	~Animation(); | ||||||
| 
 | 
 | ||||||
| 	//void play(const PlaybackOptions &options);
 | 	//void play(const PlaybackOptions &options);
 | ||||||
|  |  | ||||||
|  | @ -30,7 +30,15 @@ bool UncompressToRaw(AlignedStorage &to, bytes::const_span from) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Decode(QImage &to, const AlignedStorage &from, const QSize &fromSize) { | void CompressFromRaw(QByteArray &to, const AlignedStorage &from) { | ||||||
|  | 	const auto size = to.size(); | ||||||
|  | 	to.resize(size + from.rawSize()); | ||||||
|  | 	memcpy(to.data() + size, from.raw(), from.rawSize()); | ||||||
|  | 	// #TODO stickers
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Decode(QImage &to, AlignedStorage &from, const QSize &fromSize) { | ||||||
|  | 	from.copyRawToAligned(); | ||||||
| 	if (!FFmpeg::GoodStorageForFrame(to, fromSize)) { | 	if (!FFmpeg::GoodStorageForFrame(to, fromSize)) { | ||||||
| 		to = FFmpeg::CreateFrameStorage(fromSize); | 		to = FFmpeg::CreateFrameStorage(fromSize); | ||||||
| 	} | 	} | ||||||
|  | @ -45,6 +53,46 @@ void Decode(QImage &to, const AlignedStorage &from, const QSize &fromSize) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void Encode(AlignedStorage &to, const QImage &from, const QSize &toSize) { | ||||||
|  | 	auto fromBytes = from.bits(); | ||||||
|  | 	auto toBytes = static_cast<char*>(to.aligned()); | ||||||
|  | 	const auto fromPerLine = from.bytesPerLine(); | ||||||
|  | 	const auto toPerLine = to.bytesPerLine(); | ||||||
|  | 	for (auto i = 0; i != to.lines(); ++i) { | ||||||
|  | 		memcpy(toBytes, fromBytes, from.width() * 4); | ||||||
|  | 		fromBytes += fromPerLine; | ||||||
|  | 		toBytes += toPerLine; | ||||||
|  | 	} | ||||||
|  | 	to.copyAlignedToRaw(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Xor(AlignedStorage &to, const AlignedStorage &from) { | ||||||
|  | 	Expects(to.rawSize() == from.rawSize()); | ||||||
|  | 
 | ||||||
|  | 	using Block = std::conditional_t< | ||||||
|  | 		sizeof(void*) == sizeof(uint64), | ||||||
|  | 		uint64, | ||||||
|  | 		uint32>; | ||||||
|  | 	constexpr auto kBlockSize = sizeof(Block); | ||||||
|  | 	const auto amount = from.rawSize(); | ||||||
|  | 	const auto fromBytes = reinterpret_cast<const uchar*>(from.raw()); | ||||||
|  | 	const auto toBytes = reinterpret_cast<uchar*>(to.raw()); | ||||||
|  | 	const auto skip = reinterpret_cast<quintptr>(toBytes) % kBlockSize; | ||||||
|  | 	const auto blocks = (amount - skip) / kBlockSize; | ||||||
|  | 	for (auto i = 0; i != skip; ++i) { | ||||||
|  | 		toBytes[i] ^= fromBytes[i]; | ||||||
|  | 	} | ||||||
|  | 	const auto fromBlocks = reinterpret_cast<const Block*>(fromBytes + skip); | ||||||
|  | 	const auto toBlocks = reinterpret_cast<Block*>(toBytes + skip); | ||||||
|  | 	for (auto i = 0; i != blocks; ++i) { | ||||||
|  | 		toBlocks[i] ^= fromBlocks[i]; | ||||||
|  | 	} | ||||||
|  | 	const auto left = amount - skip - (blocks * kBlockSize); | ||||||
|  | 	for (auto i = amount - left; i != amount; ++i) { | ||||||
|  | 		toBytes[i] ^= fromBytes[i]; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } // namespace
 | } // namespace
 | ||||||
| 
 | 
 | ||||||
| void AlignedStorage::allocate(int packedBytesPerLine, int lines) { | void AlignedStorage::allocate(int packedBytesPerLine, int lines) { | ||||||
|  | @ -104,10 +152,10 @@ void AlignedStorage::copyRawToAligned() { | ||||||
| 	if (fromPerLine == toPerLine) { | 	if (fromPerLine == toPerLine) { | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 	auto from = static_cast<char*>(raw()); | 	auto from = static_cast<const char*>(raw()); | ||||||
| 	auto to = static_cast<char*>(aligned()); | 	auto to = static_cast<char*>(aligned()); | ||||||
| 	for (auto i = 0; i != _lines; ++i) { | 	for (auto i = 0; i != _lines; ++i) { | ||||||
| 		memcpy(from, to, fromPerLine); | 		memcpy(to, from, fromPerLine); | ||||||
| 		from += fromPerLine; | 		from += fromPerLine; | ||||||
| 		to += toPerLine; | 		to += toPerLine; | ||||||
| 	} | 	} | ||||||
|  | @ -119,22 +167,36 @@ void AlignedStorage::copyAlignedToRaw() { | ||||||
| 	if (fromPerLine == toPerLine) { | 	if (fromPerLine == toPerLine) { | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 	auto from = static_cast<char*>(aligned()); | 	auto from = static_cast<const char*>(aligned()); | ||||||
| 	auto to = static_cast<char*>(raw()); | 	auto to = static_cast<char*>(raw()); | ||||||
| 	for (auto i = 0; i != _lines; ++i) { | 	for (auto i = 0; i != _lines; ++i) { | ||||||
| 		memcpy(from, to, toPerLine); | 		memcpy(to, from, toPerLine); | ||||||
| 		from += fromPerLine; | 		from += fromPerLine; | ||||||
| 		to += toPerLine; | 		to += toPerLine; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| CacheState::CacheState(const QByteArray &data, QSize box) | CacheState::CacheState(const QByteArray &data, const FrameRequest &request) | ||||||
| : _data(data) { | : _data(data) { | ||||||
| 	if (!readHeader(box)) { | 	if (!readHeader(request)) { | ||||||
| 		_framesReady = 0; | 		_framesReady = 0; | ||||||
|  | 		_data = QByteArray(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void CacheState::init( | ||||||
|  | 		QSize original, | ||||||
|  | 		int frameRate, | ||||||
|  | 		int framesCount, | ||||||
|  | 		const FrameRequest &request) { | ||||||
|  | 	_size = request.size(original); | ||||||
|  | 	_original = original; | ||||||
|  | 	_frameRate = frameRate; | ||||||
|  | 	_framesCount = framesCount; | ||||||
|  | 	_framesReady = 0; | ||||||
|  | 	prepareBuffers(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| int CacheState::frameRate() const { | int CacheState::frameRate() const { | ||||||
| 	return _frameRate; | 	return _frameRate; | ||||||
| } | } | ||||||
|  | @ -147,14 +209,18 @@ int CacheState::framesCount() const { | ||||||
| 	return _framesCount; | 	return _framesCount; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool CacheState::readHeader(QSize box) { | QSize CacheState::originalSize() const { | ||||||
|  | 	return _original; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool CacheState::readHeader(const FrameRequest &request) { | ||||||
| 	if (_data.isEmpty()) { | 	if (_data.isEmpty()) { | ||||||
| 		return false; | 		return false; | ||||||
| 
 | 
 | ||||||
| 	} | 	} | ||||||
| 	QDataStream stream(&_data, QIODevice::ReadOnly); | 	QDataStream stream(&_data, QIODevice::ReadOnly); | ||||||
| 
 | 
 | ||||||
| 	auto encoder = uchar(0); | 	auto encoder = quint8(0); | ||||||
| 	stream >> encoder; | 	stream >> encoder; | ||||||
| 	if (static_cast<Encoder>(encoder) != Encoder::YUV420A4_LZ4) { | 	if (static_cast<Encoder>(encoder) != Encoder::YUV420A4_LZ4) { | ||||||
| 		return false; | 		return false; | ||||||
|  | @ -180,47 +246,137 @@ bool CacheState::readHeader(QSize box) { | ||||||
| 		|| (framesCount > kMaxFramesCount) | 		|| (framesCount > kMaxFramesCount) | ||||||
| 		|| (framesReady <= 0) | 		|| (framesReady <= 0) | ||||||
| 		|| (framesReady > framesCount) | 		|| (framesReady > framesCount) | ||||||
| 		|| FrameRequest{ box }.size(original) != size) { | 		|| request.size(original) != size) { | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
|  | 	_headerSize = stream.device()->pos(); | ||||||
| 	_size = size; | 	_size = size; | ||||||
| 	_original = original; | 	_original = original; | ||||||
| 	_frameRate = frameRate; | 	_frameRate = frameRate; | ||||||
| 	_framesCount = framesCount; | 	_framesCount = framesCount; | ||||||
| 	_framesReady = framesReady; | 	_framesReady = framesReady; | ||||||
| 	prepareBuffers(); | 	prepareBuffers(); | ||||||
| 	if (!readCompressedDelta(stream.device()->pos())) { | 	return renderFrame(_firstFrame, request, 0); | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 	_uncompressed.copyRawToAligned(); |  | ||||||
| 	std::swap(_uncompressed, _previous); |  | ||||||
| 	Decode(_firstFrame, _previous, _size); |  | ||||||
| 	return true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| QImage CacheState::takeFirstFrame() { | QImage CacheState::takeFirstFrame() { | ||||||
| 	return std::move(_firstFrame); | 	return std::move(_firstFrame); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool CacheState::renderFrame( | ||||||
|  | 		QImage &to, | ||||||
|  | 		const FrameRequest &request, | ||||||
|  | 		int index) { | ||||||
|  | 	Expects(index >= _framesReady | ||||||
|  | 		|| index == _offsetFrameIndex | ||||||
|  | 		|| index == 0); | ||||||
|  | 
 | ||||||
|  | 	if (index >= _framesReady) { | ||||||
|  | 		return false; | ||||||
|  | 	} else if (request.size(_original) != _size) { | ||||||
|  | 		return false; | ||||||
|  | 	} else if (index == 0) { | ||||||
|  | 		_offset = _headerSize; | ||||||
|  | 		_offsetFrameIndex = 0; | ||||||
|  | 	} | ||||||
|  | 	if (!readCompressedDelta()) { | ||||||
|  | 		_framesReady = 0; | ||||||
|  | 		_data = QByteArray(); | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 	if (index == 0) { | ||||||
|  | 		std::swap(_uncompressed, _previous); | ||||||
|  | 	} else { | ||||||
|  | 		Xor(_previous, _uncompressed); | ||||||
|  | 	} | ||||||
|  | 	Decode(to, _previous, _size); | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CacheState::appendFrame( | ||||||
|  | 		const QImage &frame, | ||||||
|  | 		const FrameRequest &request, | ||||||
|  | 		int index) { | ||||||
|  | 	if (request.size(_original) != _size) { | ||||||
|  | 		_framesReady = 0; | ||||||
|  | 		_data = QByteArray(); | ||||||
|  | 	} | ||||||
|  | 	if (index != _framesReady) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	if (index == 0) { | ||||||
|  | 		_size = request.size(_original); | ||||||
|  | 		writeHeader(); | ||||||
|  | 		prepareBuffers(); | ||||||
|  | 	} else { | ||||||
|  | 		incrementFramesReady(); | ||||||
|  | 	} | ||||||
|  | 	Encode(_uncompressed, frame, _size); | ||||||
|  | 	if (index == 0) { | ||||||
|  | 		writeCompressedDelta(); | ||||||
|  | 		std::swap(_uncompressed, _previous); | ||||||
|  | 	} else { | ||||||
|  | 		std::swap(_uncompressed, _previous); | ||||||
|  | 		Xor(_uncompressed, _previous); | ||||||
|  | 		writeCompressedDelta(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CacheState::writeHeader() { | ||||||
|  | 	Expects(_framesReady == 0); | ||||||
|  | 	Expects(_data.isEmpty()); | ||||||
|  | 
 | ||||||
|  | 	QDataStream stream(&_data, QIODevice::WriteOnly); | ||||||
|  | 
 | ||||||
|  | 	stream | ||||||
|  | 		<< static_cast<quint8>(Encoder::YUV420A4_LZ4) | ||||||
|  | 		<< _size | ||||||
|  | 		<< _original | ||||||
|  | 		<< qint32(_frameRate) | ||||||
|  | 		<< qint32(_framesCount) | ||||||
|  | 		<< qint32(++_framesReady); | ||||||
|  | 	_headerSize = stream.device()->pos(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CacheState::incrementFramesReady() { | ||||||
|  | 	Expects(_headerSize > sizeof(qint32) && _data.size() > _headerSize); | ||||||
|  | 
 | ||||||
|  | 	const auto framesReady = qint32(++_framesReady); | ||||||
|  | 	bytes::copy( | ||||||
|  | 		bytes::make_detached_span(_data).subspan( | ||||||
|  | 			_headerSize - sizeof(qint32)), | ||||||
|  | 		bytes::object_as_span(&framesReady)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CacheState::writeCompressedDelta() { | ||||||
|  | 	auto length = qint32(0); | ||||||
|  | 	const auto size = _data.size(); | ||||||
|  | 	_data.resize(size + sizeof(length)); | ||||||
|  | 	CompressFromRaw(_data, _uncompressed); | ||||||
|  | 	length = _data.size() - size - sizeof(length); | ||||||
|  | 	bytes::copy( | ||||||
|  | 		bytes::make_detached_span(_data).subspan(size), | ||||||
|  | 		bytes::object_as_span(&length)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void CacheState::prepareBuffers() { | void CacheState::prepareBuffers() { | ||||||
| 	_uncompressed.allocate(_size.width() * 4, _size.height()); | 	_uncompressed.allocate(_size.width() * 4, _size.height()); | ||||||
|  | 	_previous.allocate(_size.width() * 4, _size.height()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int CacheState::uncompressedDeltaSize() const { | bool CacheState::readCompressedDelta() { | ||||||
| 	return _size.width() * _size.height() * 4; // #TODO stickers
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool CacheState::readCompressedDelta(int offset) { |  | ||||||
| 	auto length = qint32(0); | 	auto length = qint32(0); | ||||||
| 	const auto part = bytes::make_span(_data).subspan(offset); | 	const auto part = bytes::make_span(_data).subspan(_offset); | ||||||
| 	if (part.size() < sizeof(length)) { | 	if (part.size() < sizeof(length)) { | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 	bytes::copy(bytes::object_as_span(&length), part); | 	bytes::copy( | ||||||
|  | 		bytes::object_as_span(&length), | ||||||
|  | 		part.subspan(0, sizeof(length))); | ||||||
| 	const auto bytes = part.subspan(sizeof(length)); | 	const auto bytes = part.subspan(sizeof(length)); | ||||||
| 	const auto uncompressedSize = uncompressedDeltaSize(); |  | ||||||
| 
 | 
 | ||||||
| 	_offset = offset + length; | 	_offset += sizeof(length) + length; | ||||||
|  | 	++_offsetFrameIndex; | ||||||
| 	return (length <= bytes.size()) | 	return (length <= bytes.size()) | ||||||
| 		? UncompressToRaw(_uncompressed, bytes.subspan(0, length)) | 		? UncompressToRaw(_uncompressed, bytes.subspan(0, length)) | ||||||
| 		: false; | 		: false; | ||||||
|  |  | ||||||
|  | @ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| 
 | 
 | ||||||
| namespace Lottie { | namespace Lottie { | ||||||
| 
 | 
 | ||||||
|  | struct FrameRequest; | ||||||
|  | 
 | ||||||
| class AlignedStorage { | class AlignedStorage { | ||||||
| public: | public: | ||||||
| 	void allocate(int packedBytesPerLine, int lines); | 	void allocate(int packedBytesPerLine, int lines); | ||||||
|  | @ -46,22 +48,40 @@ private: | ||||||
| 
 | 
 | ||||||
| class CacheState { | class CacheState { | ||||||
| public: | public: | ||||||
| 	enum class Encoder : uchar { | 	enum class Encoder : quint8 { | ||||||
| 		YUV420A4_LZ4, | 		YUV420A4_LZ4, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	CacheState(const QByteArray &data, QSize box); | 	CacheState(const QByteArray &data, const FrameRequest &request); | ||||||
| 
 | 
 | ||||||
|  | 	void init( | ||||||
|  | 		QSize original, | ||||||
|  | 		int frameRate, | ||||||
|  | 		int framesCount, | ||||||
|  | 		const FrameRequest &request); | ||||||
| 	[[nodiscard]] int frameRate() const; | 	[[nodiscard]] int frameRate() const; | ||||||
| 	[[nodiscard]] int framesReady() const; | 	[[nodiscard]] int framesReady() const; | ||||||
| 	[[nodiscard]] int framesCount() const; | 	[[nodiscard]] int framesCount() const; | ||||||
|  | 	[[nodiscard]] QSize originalSize() const; | ||||||
| 	[[nodiscard]] QImage takeFirstFrame(); | 	[[nodiscard]] QImage takeFirstFrame(); | ||||||
| 
 | 
 | ||||||
|  | 	[[nodiscard]] bool renderFrame( | ||||||
|  | 		QImage &to, | ||||||
|  | 		const FrameRequest &request, | ||||||
|  | 		int index); | ||||||
|  | 	void appendFrame( | ||||||
|  | 		const QImage &frame, | ||||||
|  | 		const FrameRequest &request, | ||||||
|  | 		int index); | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
| 	[[nodiscard]] bool readHeader(QSize box); |  | ||||||
| 	void prepareBuffers(); | 	void prepareBuffers(); | ||||||
| 	[[nodiscard]] bool readCompressedDelta(int offset); | 
 | ||||||
| 	[[nodiscard]] int uncompressedDeltaSize() const; | 	void writeHeader(); | ||||||
|  | 	void incrementFramesReady(); | ||||||
|  | 	[[nodiscard]] bool readHeader(const FrameRequest &request); | ||||||
|  | 	void writeCompressedDelta(); | ||||||
|  | 	[[nodiscard]] bool readCompressedDelta(); | ||||||
| 
 | 
 | ||||||
| 	QByteArray _data; | 	QByteArray _data; | ||||||
| 	QSize _size; | 	QSize _size; | ||||||
|  | @ -72,7 +92,9 @@ private: | ||||||
| 	int _frameRate = 0; | 	int _frameRate = 0; | ||||||
| 	int _framesCount = 0; | 	int _framesCount = 0; | ||||||
| 	int _framesReady = 0; | 	int _framesReady = 0; | ||||||
|  | 	int _headerSize = 0; | ||||||
| 	int _offset = 0; | 	int _offset = 0; | ||||||
|  | 	int _offsetFrameIndex = 0; | ||||||
| 	Encoder _encoder = Encoder::YUV420A4_LZ4; | 	Encoder _encoder = Encoder::YUV420A4_LZ4; | ||||||
| 
 | 
 | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | ||||||
| #include "lottie/lottie_frame_renderer.h" | #include "lottie/lottie_frame_renderer.h" | ||||||
| 
 | 
 | ||||||
| #include "lottie/lottie_animation.h" | #include "lottie/lottie_animation.h" | ||||||
|  | #include "lottie/lottie_cache.h" | ||||||
|  | #include "storage/cache/storage_cache_database.h" | ||||||
| #include "logs.h" | #include "logs.h" | ||||||
| #include "rlottie.h" | #include "rlottie.h" | ||||||
| 
 | 
 | ||||||
|  | @ -72,6 +74,26 @@ private: | ||||||
| 
 | 
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct SharedState::Cache { | ||||||
|  | 	Cache( | ||||||
|  | 		CacheState &&state, | ||||||
|  | 		not_null<Storage::Cache::Database*> storage, | ||||||
|  | 		Storage::Cache::Key key); | ||||||
|  | 
 | ||||||
|  | 	CacheState state; | ||||||
|  | 	not_null<Storage::Cache::Database*> storage; | ||||||
|  | 	Storage::Cache::Key key; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | SharedState::Cache::Cache( | ||||||
|  | 	CacheState &&state, | ||||||
|  | 	not_null<Storage::Cache::Database*> storage, | ||||||
|  | 	Storage::Cache::Key key) | ||||||
|  | : state(std::move(state)) | ||||||
|  | , storage(storage) | ||||||
|  | , key(key) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
| [[nodiscard]] bool GoodForRequest( | [[nodiscard]] bool GoodForRequest( | ||||||
| 		const QImage &image, | 		const QImage &image, | ||||||
| 		const FrameRequest &request) { | 		const FrameRequest &request) { | ||||||
|  | @ -133,6 +155,8 @@ FrameRendererObject::FrameRendererObject( | ||||||
| 
 | 
 | ||||||
| void FrameRendererObject::append(std::unique_ptr<SharedState> state) { | void FrameRendererObject::append(std::unique_ptr<SharedState> state) { | ||||||
| 	_entries.push_back({ std::move(state) }); | 	_entries.push_back({ std::move(state) }); | ||||||
|  | 	auto &entry = _entries.back(); | ||||||
|  | 	entry.request = entry.state->frameForPaint()->request; | ||||||
| 	queueGenerateFrames(); | 	queueGenerateFrames(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -176,22 +200,56 @@ void FrameRendererObject::queueGenerateFrames() { | ||||||
| 
 | 
 | ||||||
| SharedState::SharedState(std::unique_ptr<rlottie::Animation> animation) | SharedState::SharedState(std::unique_ptr<rlottie::Animation> animation) | ||||||
| : _animation(std::move(animation)) { | : _animation(std::move(animation)) { | ||||||
| 	Expects(_animation != nullptr); | 	construct(FrameRequest()); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|  | SharedState::SharedState( | ||||||
|  | 	const QByteArray &content, | ||||||
|  | 	std::unique_ptr<rlottie::Animation> animation, | ||||||
|  | 	CacheState &&state, | ||||||
|  | 	not_null<Storage::Cache::Database*> cache, | ||||||
|  | 	Storage::Cache::Key key, | ||||||
|  | 	const FrameRequest &request) | ||||||
|  | : _content(content) | ||||||
|  | , _animation(std::move(animation)) | ||||||
|  | , _cache(std::make_unique<Cache>(std::move(state), cache, key)) { | ||||||
|  | 	construct(request); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SharedState::construct(const FrameRequest &request) { | ||||||
| 	calculateProperties(); | 	calculateProperties(); | ||||||
| 	if (isValid()) { | 	if (!isValid()) { | ||||||
| 		auto cover = QImage(); | 		return; | ||||||
| 		renderFrame(cover, FrameRequest(), 0); |  | ||||||
| 		init(std::move(cover)); |  | ||||||
| 	} | 	} | ||||||
|  | 	auto cover = _cache ? _cache->state.takeFirstFrame() : QImage(); | ||||||
|  | 	if (!cover.isNull()) { | ||||||
|  | 		init(std::move(cover), request); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	if (_cache) { | ||||||
|  | 		_cache->state.init(_size, _frameRate, _framesCount, request); | ||||||
|  | 	} | ||||||
|  | 	renderFrame(cover, request, 0); | ||||||
|  | 	init(std::move(cover), request); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SharedState::calculateProperties() { | void SharedState::calculateProperties() { | ||||||
|  | 	Expects(_animation != nullptr || _cache != nullptr); | ||||||
|  | 
 | ||||||
| 	auto width = size_t(0); | 	auto width = size_t(0); | ||||||
| 	auto height = size_t(0); | 	auto height = size_t(0); | ||||||
| 	_animation->size(width, height); | 	if (_animation) { | ||||||
| 	const auto rate = _animation->frameRate(); | 		_animation->size(width, height); | ||||||
| 	const auto count = _animation->totalFrame(); | 	} else { | ||||||
|  | 		width = _cache->state.originalSize().width(); | ||||||
|  | 		height = _cache->state.originalSize().height(); | ||||||
|  | 	} | ||||||
|  | 	const auto rate = _animation | ||||||
|  | 		? _animation->frameRate() | ||||||
|  | 		: _cache->state.frameRate(); | ||||||
|  | 	const auto count = _animation | ||||||
|  | 		? _animation->totalFrame() | ||||||
|  | 		: _cache->state.framesCount(); | ||||||
| 
 | 
 | ||||||
| 	_size = QSize( | 	_size = QSize( | ||||||
| 		(width > 0 && width < kMaxSize) ? int(width) : 0, | 		(width > 0 && width < kMaxSize) ? int(width) : 0, | ||||||
|  | @ -216,21 +274,33 @@ void SharedState::renderFrame( | ||||||
| 	if (!GoodStorageForFrame(image, size)) { | 	if (!GoodStorageForFrame(image, size)) { | ||||||
| 		image = CreateFrameStorage(size); | 		image = CreateFrameStorage(size); | ||||||
| 	} | 	} | ||||||
| 	image.fill(Qt::transparent); | 	if (_cache && _cache->state.renderFrame(image, request, index)) { | ||||||
|  | 		return; | ||||||
|  | 	} else if (!_animation) { | ||||||
|  | 		_animation = details::CreateFromContent(_content); | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
|  | 	image.fill(Qt::transparent); | ||||||
| 	auto surface = rlottie::Surface( | 	auto surface = rlottie::Surface( | ||||||
| 		reinterpret_cast<uint32_t*>(image.bits()), | 		reinterpret_cast<uint32_t*>(image.bits()), | ||||||
| 		image.width(), | 		image.width(), | ||||||
| 		image.height(), | 		image.height(), | ||||||
| 		image.bytesPerLine()); | 		image.bytesPerLine()); | ||||||
| 	_animation->renderSync(index, surface); | 	_animation->renderSync(index, surface); | ||||||
|  | 	if (_cache) { | ||||||
|  | 		_cache->state.appendFrame(image, request, index); | ||||||
|  | 		if (_cache->state.framesReady() == _cache->state.framesCount()) { | ||||||
|  | 			_animation = nullptr; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SharedState::init(QImage cover) { | void SharedState::init(QImage cover, const FrameRequest &request) { | ||||||
| 	Expects(!initialized()); | 	Expects(!initialized()); | ||||||
| 
 | 
 | ||||||
| 	_duration = crl::time(1000) * _framesCount / _frameRate; | 	_duration = crl::time(1000) * _framesCount / _frameRate; | ||||||
| 
 | 
 | ||||||
|  | 	_frames[0].request = request; | ||||||
| 	_frames[0].original = std::move(cover); | 	_frames[0].original = std::move(cover); | ||||||
| 	_frames[0].position = 0; | 	_frames[0].position = 0; | ||||||
| 
 | 
 | ||||||
|  | @ -302,8 +372,7 @@ bool SharedState::renderNextFrame(const FrameRequest &request) { | ||||||
| 	case 6: return present(6, 0); | 	case 6: return present(6, 0); | ||||||
| 	case 7: return prerender(1); | 	case 7: return prerender(1); | ||||||
| 	} | 	} | ||||||
| 	Unexpected("Counter value in VideoTrack::Shared::prepareState."); | 	Unexpected("Counter value in Lottie::SharedState::renderNextFrame."); | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int SharedState::counter() const { | int SharedState::counter() const { | ||||||
|  |  | ||||||
|  | @ -21,6 +21,13 @@ namespace rlottie { | ||||||
| class Animation; | class Animation; | ||||||
| } // namespace rlottie
 | } // namespace rlottie
 | ||||||
| 
 | 
 | ||||||
|  | namespace Storage { | ||||||
|  | namespace Cache { | ||||||
|  | class Database; | ||||||
|  | struct Key; | ||||||
|  | } // namespace Cache
 | ||||||
|  | } // namespace Storage
 | ||||||
|  | 
 | ||||||
| namespace Lottie { | namespace Lottie { | ||||||
| 
 | 
 | ||||||
| inline constexpr auto kMaxFrameRate = 120; | inline constexpr auto kMaxFrameRate = 120; | ||||||
|  | @ -28,7 +35,7 @@ inline constexpr auto kMaxSize = 3096; | ||||||
| inline constexpr auto kMaxFramesCount = 600; | inline constexpr auto kMaxFramesCount = 600; | ||||||
| 
 | 
 | ||||||
| class Animation; | class Animation; | ||||||
| class JsonObject; | class CacheState; | ||||||
| 
 | 
 | ||||||
| struct Frame { | struct Frame { | ||||||
| 	QImage original; | 	QImage original; | ||||||
|  | @ -47,6 +54,13 @@ QImage PrepareFrameByRequest( | ||||||
| class SharedState { | class SharedState { | ||||||
| public: | public: | ||||||
| 	explicit SharedState(std::unique_ptr<rlottie::Animation> animation); | 	explicit SharedState(std::unique_ptr<rlottie::Animation> animation); | ||||||
|  | 	SharedState( | ||||||
|  | 		const QByteArray &content, | ||||||
|  | 		std::unique_ptr<rlottie::Animation> animation, | ||||||
|  | 		CacheState &&state, | ||||||
|  | 		not_null<Storage::Cache::Database*> cache, | ||||||
|  | 		Storage::Cache::Key key, | ||||||
|  | 		const FrameRequest &request); | ||||||
| 
 | 
 | ||||||
| 	void start(not_null<Animation*> owner, crl::time now); | 	void start(not_null<Animation*> owner, crl::time now); | ||||||
| 
 | 
 | ||||||
|  | @ -64,9 +78,12 @@ public: | ||||||
| 	~SharedState(); | 	~SharedState(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  | 	struct Cache; | ||||||
|  | 
 | ||||||
|  | 	void construct(const FrameRequest &request); | ||||||
| 	void calculateProperties(); | 	void calculateProperties(); | ||||||
| 	bool isValid() const; | 	bool isValid() const; | ||||||
| 	void init(QImage cover); | 	void init(QImage cover, const FrameRequest &request); | ||||||
| 	void renderNextFrame( | 	void renderNextFrame( | ||||||
| 		not_null<Frame*> frame, | 		not_null<Frame*> frame, | ||||||
| 		const FrameRequest &request); | 		const FrameRequest &request); | ||||||
|  | @ -74,6 +91,7 @@ private: | ||||||
| 	[[nodiscard]] not_null<const Frame*> getFrame(int index) const; | 	[[nodiscard]] not_null<const Frame*> getFrame(int index) const; | ||||||
| 	[[nodiscard]] int counter() const; | 	[[nodiscard]] int counter() const; | ||||||
| 
 | 
 | ||||||
|  | 	QByteArray _content; | ||||||
| 	std::unique_ptr<rlottie::Animation> _animation; | 	std::unique_ptr<rlottie::Animation> _animation; | ||||||
| 
 | 
 | ||||||
| 	static constexpr auto kCounterUninitialized = -1; | 	static constexpr auto kCounterUninitialized = -1; | ||||||
|  | @ -91,6 +109,8 @@ private: | ||||||
| 	QSize _size; | 	QSize _size; | ||||||
| 	std::atomic<int> _accumulatedDelayMs = 0; | 	std::atomic<int> _accumulatedDelayMs = 0; | ||||||
| 
 | 
 | ||||||
|  | 	std::unique_ptr<Cache> _cache; | ||||||
|  | 
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class FrameRendererObject; | class FrameRendererObject; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue