From 58a1caf9dfafcdf076fc95cf14208ce3799128cb Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Thu, 9 May 2024 00:22:07 +0400 Subject: [PATCH] Lottie: optimization --- .../Sources/CompareToReferenceRendering.swift | 3 +- .../Sources/ViewController.swift | 2 +- .../CompLayers/CompositionLayer.hpp | 14 +-- .../CompLayers/PreCompositionLayer.hpp | 87 ++++++++++++++++--- .../CompLayers/ShapeCompositionLayer.cpp | 86 +++++++++++------- .../CompLayers/ShapeCompositionLayer.hpp | 4 + .../CompLayers/TextCompositionLayer.hpp | 43 --------- .../MainThreadAnimationLayer.hpp | 15 ++++ 8 files changed, 159 insertions(+), 95 deletions(-) diff --git a/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift b/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift index 33f52ac8a4..3889a5e3d3 100644 --- a/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift +++ b/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift @@ -256,9 +256,10 @@ func processAnimationFolderAsync(basePath: String, path: String, stopOnFailure: return await processAnimationFolderItems(items: items, countPerBucket: 1, stopOnFailure: stopOnFailure, process: process) } +@available(iOS 13.0, *) func processAnimationFolderParallel(basePath: String, path: String, stopOnFailure: Bool, process: @escaping (String, String, Bool) async -> Bool) async -> Bool { let items = buildAnimationFolderItems(basePath: basePath, path: path) - return await processAnimationFolderItems(items: items, countPerBucket: 16, stopOnFailure: stopOnFailure, process: process) + return await processAnimationFolderItemsParallel(items: items, stopOnFailure: stopOnFailure, process: process) } func cacheReferenceFolderPath(baseCachePath: String, width: Int, name: String) -> String { diff --git a/Tests/LottieMetalTest/Sources/ViewController.swift b/Tests/LottieMetalTest/Sources/ViewController.swift index 4b2b4665c7..5c937dfd33 100644 --- a/Tests/LottieMetalTest/Sources/ViewController.swift +++ b/Tests/LottieMetalTest/Sources/ViewController.swift @@ -79,7 +79,7 @@ private final class ReferenceCompareTest { var continueFromName: String? = "5138957708585599529.json" - let _ = await processAnimationFolderParallel(basePath: bundlePath, path: "", stopOnFailure: true, process: { path, name, alwaysDraw in + let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: true, process: { path, name, alwaysDraw in if let continueFromNameValue = continueFromName { if continueFromNameValue == name { continueFromName = nil diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp index afe0d7eed6..1d27bbd0ba 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp @@ -89,7 +89,13 @@ public: void displayWithFrame(double frame, bool forceUpdates) { _transformNode->updateTree(frame, forceUpdates); + bool layerVisible = isInRangeOrEqual(frame, _inFrame, _outFrame); + + _contentsLayer->setTransform(_transformNode->globalTransform()); + _contentsLayer->setOpacity(_transformNode->opacity()); + _contentsLayer->setIsHidden(!layerVisible); + /// Only update contents if current time is within the layers time bounds. if (layerVisible) { displayContentsWithFrame(frame, forceUpdates); @@ -97,9 +103,6 @@ public: _maskLayer->updateWithFrame(frame, forceUpdates); } } - _contentsLayer->setTransform(_transformNode->globalTransform()); - _contentsLayer->setOpacity(_transformNode->opacity()); - _contentsLayer->setIsHidden(!layerVisible); if (const auto delegate = _layerDelegate.lock()) { delegate->frameUpdated(frame); @@ -168,6 +171,9 @@ public: return nullptr; } + virtual void updateRenderTree() { + } + public: std::shared_ptr const transformNode() const { return _transformNode; @@ -193,8 +199,6 @@ private: std::string _keypathName; - //std::shared_ptr _renderTree; - public: virtual bool isImageCompositionLayer() const { return false; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp index 384d96d645..58f27deedf 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp @@ -101,6 +101,34 @@ public: } virtual std::shared_ptr renderTreeNode() override { + if (!_renderTreeNode) { + _renderTreeNode = std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + std::vector>(), + nullptr, + false + ); + + _contentsTreeNode = std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + std::vector>(), + nullptr, + false + ); + } + if (_contentsLayer->isHidden()) { return nullptr; } @@ -121,7 +149,17 @@ public: } std::vector> subnodes; - subnodes.push_back(std::make_shared( + + _contentsTreeNode->_bounds = _contentsLayer->bounds(); + _contentsTreeNode->_position = _contentsLayer->position(); + _contentsTreeNode->_transform = _contentsLayer->transform(); + _contentsTreeNode->_alpha = _contentsLayer->opacity(); + _contentsTreeNode->_masksToBounds = _contentsLayer->masksToBounds(); + _contentsTreeNode->_isHidden = _contentsLayer->isHidden(); + _contentsTreeNode->_subnodes = renderTreeValue; + + subnodes.push_back(_contentsTreeNode); + /*subnodes.push_back(std::make_shared( _contentsLayer->bounds(), _contentsLayer->position(), _contentsLayer->transform(), @@ -132,7 +170,7 @@ public: renderTreeValue, nullptr, false - )); + ));*/ assert(opacity() == 1.0); assert(!isHidden()); @@ -140,18 +178,17 @@ public: assert(transform().isIdentity()); assert(position() == Vector2D::Zero()); - return std::make_shared( - bounds(), - position(), - transform(), - opacity(), - masksToBounds(), - isHidden(), - nullptr, - subnodes, - maskNode, - invertMask - ); + _renderTreeNode->_bounds = bounds(); + _renderTreeNode->_position = position(); + _renderTreeNode->_transform = transform(); + _renderTreeNode->_alpha = opacity(); + _renderTreeNode->_masksToBounds = masksToBounds(); + _renderTreeNode->_isHidden = isHidden(); + _renderTreeNode->_subnodes = subnodes; + _renderTreeNode->_mask = maskNode; + _renderTreeNode->_invertMask = invertMask; + + return _renderTreeNode; } std::shared_ptr renderTree() { @@ -188,11 +225,33 @@ public: ); } + virtual void updateRenderTree() override { + if (_matteLayer) { + _matteLayer->updateRenderTree(); + } + + for (const auto &animationLayer : _animationLayers) { + bool found = false; + for (const auto &sublayer : contentsLayer()->sublayers()) { + if (animationLayer == sublayer) { + found = true; + break; + } + } + if (found) { + animationLayer->updateRenderTree(); + } + } + } + private: double _frameRate = 0.0; std::shared_ptr> _remappingNode; std::vector> _animationLayers; + + std::shared_ptr _renderTreeNode; + std::shared_ptr _contentsTreeNode; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp index 38043c601e..a7b68a0506 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp @@ -1284,16 +1284,46 @@ CompositionLayer(solidLayer, Vector2D::Zero()) { void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates) { _frameTime = frame; _frameTimeInitialized = true; - _contentTree->itemTree->renderChildren(_frameTime, std::nullopt); } std::shared_ptr ShapeCompositionLayer::renderTreeNode() { - if (_contentsLayer->isHidden()) { - return nullptr; + if (!_frameTimeInitialized) { + _frameTime = 0.0; + _frameTimeInitialized = true; + _contentTree->itemTree->renderChildren(_frameTime, std::nullopt); } - assert(_frameTimeInitialized); + if (!_renderTreeNode) { + _renderTreeNode = std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + std::vector>(), + nullptr, + false + ); + + std::vector> renderTreeValue; + renderTreeValue.push_back(_contentTree->itemTree->renderTree()); + + _contentsTreeNode = std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + renderTreeValue, + nullptr, + false + ); + } std::shared_ptr maskNode; bool invertMask = false; @@ -1304,45 +1334,39 @@ std::shared_ptr ShapeCompositionLayer::renderTreeNode() { } } - std::vector> renderTreeValue; - renderTreeValue.push_back(_contentTree->itemTree->renderTree()); + _contentsTreeNode->_bounds = _contentsLayer->bounds(); + _contentsTreeNode->_position = _contentsLayer->position(); + _contentsTreeNode->_transform = _contentsLayer->transform(); + _contentsTreeNode->_alpha = _contentsLayer->opacity(); + _contentsTreeNode->_masksToBounds = _contentsLayer->masksToBounds(); + _contentsTreeNode->_isHidden = _contentsLayer->isHidden(); std::vector> subnodes; - subnodes.push_back(std::make_shared( - _contentsLayer->bounds(), - _contentsLayer->position(), - _contentsLayer->transform(), - _contentsLayer->opacity(), - _contentsLayer->masksToBounds(), - _contentsLayer->isHidden(), - nullptr, - renderTreeValue, - nullptr, - false - )); + subnodes.push_back(_contentsTreeNode); assert(position() == Vector2D::Zero()); assert(transform().isIdentity()); assert(opacity() == 1.0); assert(!masksToBounds()); assert(!isHidden()); - assert(_contentsLayer->bounds() == CGRect(0.0, 0.0, 0.0, 0.0)); assert(_contentsLayer->position() == Vector2D::Zero()); assert(!_contentsLayer->masksToBounds()); - return std::make_shared( - bounds(), - position(), - transform(), - opacity(), - masksToBounds(), - isHidden(), - nullptr, - subnodes, - maskNode, - invertMask - ); + _renderTreeNode->_bounds = bounds(); + _renderTreeNode->_position = position(); + _renderTreeNode->_transform = transform(); + _renderTreeNode->_alpha = opacity(); + _renderTreeNode->_masksToBounds = masksToBounds(); + _renderTreeNode->_isHidden = isHidden(); + _renderTreeNode->_subnodes = subnodes; + _renderTreeNode->_mask = maskNode; + _renderTreeNode->_invertMask = invertMask; + + return _renderTreeNode; +} + +void ShapeCompositionLayer::updateRenderTree() { } } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp index ea88c5d0ac..8fb3ead48c 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp @@ -18,12 +18,16 @@ public: virtual void displayContentsWithFrame(double frame, bool forceUpdates) override; virtual std::shared_ptr renderTreeNode() override; + virtual void updateRenderTree() override; private: std::shared_ptr _contentTree; AnimationFrameTime _frameTime = 0.0; bool _frameTimeInitialized = false; + + std::shared_ptr _renderTreeNode; + std::shared_ptr _contentsTreeNode; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp index 57e0545345..d2b9076b2d 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp @@ -23,12 +23,6 @@ public: _textProvider = textProvider; _fontProvider = fontProvider; - //_contentsLayer->addSublayer(_textLayer); - - assert(false); - //self.textLayer.masksToBounds = false - //self.textLayer.isGeometryFlipped = true - if (_rootNode) { _childKeypaths.push_back(rootNode); } @@ -67,42 +61,6 @@ public: if (_rootNode) { _rootNode->rebuildOutputs(frame); } - - assert(false); - /*// Get Text Attributes - let text = textDocument.value(frame: frame) as! TextDocument - let strokeColor = rootNode?.textOutputNode.strokeColor ?? text.strokeColorData?.cgColorValue - let strokeWidth = rootNode?.textOutputNode.strokeWidth ?? CGFloat(text.strokeWidth ?? 0) - let tracking = (CGFloat(text.fontSize) * (rootNode?.textOutputNode.tracking ?? CGFloat(text.tracking))) / 1000.0 - let matrix = rootNode?.textOutputNode.xform ?? CATransform3DIdentity - let textString = textProvider.textFor(keypathName: keypathName, sourceText: text.text) - let ctFont = fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize)) - - // Set all of the text layer options - textLayer.text = textString - textLayer.font = ctFont - textLayer.alignment = text.justification.textAlignment - textLayer.lineHeight = CGFloat(text.lineHeight) - textLayer.tracking = tracking - - if let fillColor = rootNode?.textOutputNode.fillColor { - textLayer.fillColor = fillColor - } else if let fillColor = text.fillColorData?.cgColorValue { - textLayer.fillColor = fillColor - } else { - textLayer.fillColor = nil - } - - textLayer.preferredSize = text.textFrameSize?.sizeValue - textLayer.strokeOnTop = text.strokeOverFill ?? false - textLayer.strokeWidth = strokeWidth - textLayer.strokeColor = strokeColor - textLayer.sizeToFit() - - textLayer.opacity = Float(rootNode?.textOutputNode.opacity ?? 1) - textLayer.transform = CATransform3DIdentity - textLayer.position = text.textFramePosition?.pointValue ?? CGPoint.zero - textLayer.transform = matrix*/ } public: @@ -114,7 +72,6 @@ private: std::shared_ptr _rootNode; std::shared_ptr> _textDocument; - //std::shared_ptr _textLayer; std::shared_ptr _textProvider; std::shared_ptr _fontProvider; }; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp index 29260f7507..cbd9811be6 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp @@ -250,6 +250,21 @@ public: ); } + virtual void updateRenderTree() { + for (const auto &animationLayer : _animationLayers) { + bool found = false; + for (const auto &sublayer : sublayers()) { + if (animationLayer == sublayer) { + found = true; + break; + } + } + if (found) { + animationLayer->updateRenderTree(); + } + } + } + private: // MARK: Internal