From c752e2d895521cff4d1352b2a515db2ef60ca5ef Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 7 May 2024 20:05:50 +0400 Subject: [PATCH] LottieCpp improvements --- submodules/ChatSendMessageActionUI/BUILD | 2 + .../ChatSendMessageContextScreen.swift | 66 +- .../MetalEngine/Sources/MetalEngine.swift | 11 +- .../Sources/ChatMessageBubbleItemNode.swift | 8 +- .../TelegramUI/Components/LottieCpp/BUILD | 1 + .../PublicHeaders/LottieCpp/LottieAnimation.h | 1 + .../LottieCpp/LottieRenderTree.h | 9 +- .../Lottie/Private/Model/Animation.hpp | 48 +- .../Lottie/Private/Model/Assets/Asset.hpp | 4 +- .../Private/Model/Assets/AssetLibrary.hpp | 8 +- .../Private/Model/Assets/ImageAsset.hpp | 4 +- .../Private/Model/Assets/PrecompAsset.hpp | 8 +- .../Private/Model/Keyframes/KeyframeGroup.hpp | 10 +- .../Private/Model/Layers/ImageLayerModel.hpp | 4 +- .../Private/Model/Layers/LayerModel.cpp | 2 +- .../Private/Model/Layers/LayerModel.hpp | 12 +- .../Model/Layers/LayerModelSerialization.cpp | 2 +- .../Model/Layers/LayerModelSerialization.hpp | 2 +- .../Model/Layers/PreCompLayerModel.hpp | 4 +- .../Private/Model/Layers/ShapeLayerModel.hpp | 8 +- .../Private/Model/Layers/SolidLayerModel.hpp | 4 +- .../Private/Model/Layers/TextLayerModel.hpp | 14 +- .../Private/Model/Objects/DashElement.hpp | 6 +- .../Private/Model/Objects/FitzModifier.hpp | 6 +- .../Lottie/Private/Model/Objects/Marker.hpp | 6 +- .../Lottie/Private/Model/Objects/Mask.hpp | 6 +- .../Private/Model/Objects/Transform.hpp | 12 +- .../Private/Model/ShapeItems/Ellipse.hpp | 4 +- .../Lottie/Private/Model/ShapeItems/Fill.hpp | 4 +- .../Private/Model/ShapeItems/GradientFill.hpp | 6 +- .../Model/ShapeItems/GradientStroke.hpp | 8 +- .../Lottie/Private/Model/ShapeItems/Group.hpp | 8 +- .../Lottie/Private/Model/ShapeItems/Merge.hpp | 4 +- .../Private/Model/ShapeItems/Rectangle.hpp | 4 +- .../Private/Model/ShapeItems/Repeater.hpp | 6 +- .../Model/ShapeItems/RoundedRectangle.hpp | 4 +- .../Lottie/Private/Model/ShapeItems/Shape.hpp | 4 +- .../Private/Model/ShapeItems/ShapeItem.cpp | 2 +- .../Private/Model/ShapeItems/ShapeItem.hpp | 8 +- .../Model/ShapeItems/ShapeTransform.hpp | 4 +- .../Lottie/Private/Model/ShapeItems/Star.hpp | 4 +- .../Private/Model/ShapeItems/Stroke.hpp | 8 +- .../Lottie/Private/Model/ShapeItems/Trim.hpp | 4 +- .../Lottie/Private/Model/Text/Font.hpp | 14 +- .../Lottie/Private/Model/Text/Glyph.hpp | 12 +- .../Private/Model/Text/TextAnimator.hpp | 14 +- .../Private/Model/Text/TextDocument.hpp | 8 +- .../Lottie/Private/Parsing/JsonParsing.cpp | 36 +- .../Lottie/Private/Parsing/JsonParsing.hpp | 32 +- .../Private/Utility/Primitives/BezierPath.hpp | 20 +- .../Lottie/Public/Keyframes/Keyframe.hpp | 8 +- .../Lottie/Public/Primitives/Color.hpp | 14 +- .../Public/Primitives/GradientColorSet.hpp | 6 +- .../Lottie/Public/Primitives/Vectors.hpp | 28 +- .../LottieCpp/Sources/LottieAnimation.mm | 10 +- .../Sources/LottieAnimationContainer.mm | 9 - .../LottieCpp/Sources/LottieRenderTree.h | 147 +++ .../LottieCpp/Sources/LottieRenderTree.mm | 240 +++-- .../TelegramUI/Components/LottieMetal/BUILD | 45 + .../Metal/LottieMetalShaders.metal | 872 ++++++++++++++++++ .../LottieMetalAnimatedStickerNode.swift | 625 ++++++++++++- .../LottieMetal/Sources/PathFrameState.swift | 374 ++++++++ .../Sources/PathRenderBuffer.swift | 77 ++ .../Sources/PathRenderContext.swift | 208 +++++ .../Sources/PathRenderFillState.swift | 277 ++++++ .../Sources/PathRenderStrokeState.swift | 425 +++++++++ .../Sources/RenderTreeSerialization.swift | 485 ++++++++++ 67 files changed, 4000 insertions(+), 336 deletions(-) create mode 100644 submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.h create mode 100644 submodules/TelegramUI/Components/LottieMetal/Metal/LottieMetalShaders.metal create mode 100644 submodules/TelegramUI/Components/LottieMetal/Sources/PathFrameState.swift create mode 100644 submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderBuffer.swift create mode 100644 submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderContext.swift create mode 100644 submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderFillState.swift create mode 100644 submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderStrokeState.swift create mode 100644 submodules/TelegramUI/Components/LottieMetal/Sources/RenderTreeSerialization.swift diff --git a/submodules/ChatSendMessageActionUI/BUILD b/submodules/ChatSendMessageActionUI/BUILD index e3f895d56d..d850ac9f2d 100644 --- a/submodules/ChatSendMessageActionUI/BUILD +++ b/submodules/ChatSendMessageActionUI/BUILD @@ -32,6 +32,8 @@ swift_library( "//submodules/WallpaperBackgroundNode", "//submodules/Components/MultilineTextWithEntitiesComponent", "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramUI/Components/LottieMetal", + "//submodules/TelegramAnimatedStickerNode", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index ad03b16d00..7513d56c5b 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -16,6 +16,8 @@ import ComponentDisplayAdapters import WallpaperBackgroundNode import ReactionSelectionNode import EntityKeyboard +import LottieMetal +import TelegramAnimatedStickerNode func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) -> CGRect { let sourceWindowFrame = fromView.convert(frame, to: nil) @@ -127,7 +129,7 @@ final class ChatSendMessageContextScreenComponent: Component { private let messageEffectDisposable = MetaDisposable() private var selectedMessageEffect: AvailableMessageEffects.MessageEffect? - private var standaloneReactionAnimation: StandaloneReactionAnimation? + private var standaloneReactionAnimation: LottieMetalAnimatedStickerNode? private var presentationAnimationState: PresentationAnimationState = .initial private var appliedAnimationState: PresentationAnimationState = .initial @@ -509,7 +511,7 @@ final class ChatSendMessageContextScreenComponent: Component { ReactionContextNode.randomGenericReactionEffect(context: component.context) ) |> deliverOnMainQueue).startStrict(next: { [weak self] messageEffect, path in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let component = self.component else { return } guard let messageEffect else { @@ -517,17 +519,6 @@ final class ChatSendMessageContextScreenComponent: Component { } let effectId = messageEffect.id - let reactionItem = ReactionItem( - reaction: ReactionItem.Reaction(rawValue: updateReaction.reaction), - appearAnimation: messageEffect.effectSticker, - stillAnimation: messageEffect.effectSticker, - listAnimation: messageEffect.effectSticker, - largeListAnimation: messageEffect.effectSticker, - applicationAnimation: nil, - largeApplicationAnimation: nil, - isCustom: true - ) - if let selectedMessageEffect = self.selectedMessageEffect { if selectedMessageEffect.id == effectId { self.selectedMessageEffect = nil @@ -570,12 +561,7 @@ final class ChatSendMessageContextScreenComponent: Component { }) } - let genericReactionEffect = path - - let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: genericReactionEffect) - standaloneReactionAnimation.frame = self.bounds - self.standaloneReactionAnimation = standaloneReactionAnimation - self.addSubnode(standaloneReactionAnimation) + let _ = path var customEffectResource: MediaResource? if let effectAnimation = messageEffect.effectAnimation { @@ -586,24 +572,34 @@ final class ChatSendMessageContextScreenComponent: Component { customEffectResource = effectFile.resource } } + guard let customEffectResource else { + return + } - standaloneReactionAnimation.animateReactionSelection( - context: component.context, - theme: environment.theme, - animationCache: component.context.animationCache, - reaction: reactionItem, - customEffectResource: customEffectResource, - avatarPeers: [], - playHaptic: true, - isLarge: true, - playCenterReaction: false, - targetView: targetView, - addStandaloneReactionAnimation: { _ in - }, - completion: { [weak standaloneReactionAnimation] in - standaloneReactionAnimation?.removeFromSupernode() + let standaloneReactionAnimation = LottieMetalAnimatedStickerNode() + standaloneReactionAnimation.isUserInteractionEnabled = false + let effectSize = CGSize(width: 380.0, height: 380.0) + var effectFrame = effectSize.centered(around: targetView.convert(targetView.bounds.center, to: self)) + effectFrame.origin.x -= effectFrame.width * 0.3 + self.standaloneReactionAnimation = standaloneReactionAnimation + standaloneReactionAnimation.frame = effectFrame + standaloneReactionAnimation.updateLayout(size: effectFrame.size) + self.addSubnode(standaloneReactionAnimation) + + let source = AnimatedStickerResourceSource(account: component.context.account, resource: customEffectResource, fitzModifier: nil) + standaloneReactionAnimation.setup(source: source, width: Int(effectSize.width), height: Int(effectSize.height), playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + standaloneReactionAnimation.completed = { [weak self, weak standaloneReactionAnimation] _ in + guard let self else { + return } - ) + if let standaloneReactionAnimation { + standaloneReactionAnimation.removeFromSupernode() + if self.standaloneReactionAnimation === standaloneReactionAnimation { + self.standaloneReactionAnimation = nil + } + } + } + standaloneReactionAnimation.visibility = true })) } reactionContextNode.displayTail = true diff --git a/submodules/MetalEngine/Sources/MetalEngine.swift b/submodules/MetalEngine/Sources/MetalEngine.swift index b34ca53a79..7fc7064245 100644 --- a/submodules/MetalEngine/Sources/MetalEngine.swift +++ b/submodules/MetalEngine/Sources/MetalEngine.swift @@ -347,6 +347,7 @@ public final class MetalEngineSubjectContext { fileprivate var computeOperations: [ComputeOperation] = [] fileprivate var renderToLayerOperationsGroupedByState: [ObjectIdentifier: [RenderToLayerOperation]] = [:] fileprivate var freeResourcesOnCompletion: [MetalEngineResource] = [] + fileprivate var customCompletions: [() -> Void] = [] fileprivate init(device: MTLDevice, impl: MetalEngine.Impl) { self.device = device @@ -446,6 +447,10 @@ public final class MetalEngineSubjectContext { return commands(commandBuffer, state) }) } + + public func addCustomCompletion(_ customCompletion: @escaping () -> Void) { + self.customCompletions.append(customCompletion) + } } public final class MetalEngineSubjectInternalData { @@ -1039,13 +1044,17 @@ public final class MetalEngine { } } - if !subjectContext.freeResourcesOnCompletion.isEmpty { + if !subjectContext.freeResourcesOnCompletion.isEmpty || !subjectContext.customCompletions.isEmpty { let freeResourcesOnCompletion = subjectContext.freeResourcesOnCompletion + let customCompletions = subjectContext.customCompletions commandBuffer.addCompletedHandler { _ in DispatchQueue.main.async { for resource in freeResourcesOnCompletion { resource.free() } + for customCompletion in customCompletions { + customCompletion() + } } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index c534befd47..750e216f82 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -5793,7 +5793,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let source = AnimatedStickerResourceSource(account: item.context.account, resource: resource, fitzModifier: nil) - let animationSize = CGSize(width: 180.0, height: 180.0) + let animationSize = CGSize(width: 380.0, height: 380.0) let animationNodeFrame: CGRect var messageEffectView: UIView? @@ -5825,18 +5825,18 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id) let additionalAnimationNode = LottieMetalAnimatedStickerNode() + additionalAnimationNode.updateLayout(size: animationSize) additionalAnimationNode.setup(source: source, width: Int(animationSize.width * 1.6), height: Int(animationSize.height * 1.6), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) var animationFrame: CGRect if isStickerEffect { - let scale: CGFloat = 0.245 let offsetScale: CGFloat = 0.5 - animationFrame = animationNodeFrame.offsetBy(dx: incomingMessage ? animationNodeFrame.width * offsetScale : -animationNodeFrame.width * offsetScale, dy: -25.0).insetBy(dx: -animationNodeFrame.width * scale, dy: -animationNodeFrame.height * scale) + animationFrame = animationNodeFrame.offsetBy(dx: incomingMessage ? animationNodeFrame.width * offsetScale : -animationNodeFrame.width * offsetScale, dy: -25.0) } else { animationFrame = animationNodeFrame.insetBy(dx: -animationNodeFrame.width, dy: -animationNodeFrame.height) .offsetBy(dx: incomingMessage ? animationNodeFrame.width - 10.0 : -animationNodeFrame.width + 10.0, dy: 0.0) animationFrame = animationFrame.offsetBy(dx: CGFloat.random(in: -30.0 ... 30.0), dy: CGFloat.random(in: -30.0 ... 30.0)) } - + animationFrame = animationFrame.offsetBy(dx: 0.0, dy: self.insets.top) additionalAnimationNode.frame = animationFrame if incomingMessage { diff --git a/submodules/TelegramUI/Components/LottieCpp/BUILD b/submodules/TelegramUI/Components/LottieCpp/BUILD index 510c938526..50f747cca6 100644 --- a/submodules/TelegramUI/Components/LottieCpp/BUILD +++ b/submodules/TelegramUI/Components/LottieCpp/BUILD @@ -15,6 +15,7 @@ objc_library( copts = [ "-Werror", "-I{}/Sources".format(package_name()), + "-O2", ], hdrs = glob([ "PublicHeaders/**/*.h", diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimation.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimation.h index 93a988af5b..e7112776ed 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimation.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimation.h @@ -12,6 +12,7 @@ extern "C" { @interface LottieAnimation : NSObject @property (nonatomic, readonly) NSInteger frameCount; +@property (nonatomic, readonly) NSInteger framesPerSecond; @property (nonatomic, readonly) CGSize size; - (instancetype _Nullable)initWithData:(NSData * _Nonnull)data; diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieRenderTree.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieRenderTree.h index d853b6bad5..e4158c3ccb 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieRenderTree.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieRenderTree.h @@ -42,15 +42,16 @@ typedef NS_ENUM(NSUInteger, LottieGradientType) { @property (nonatomic, readonly, direct) CGFloat location; - (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithColor:(LottieColor)color location:(CGFloat)location __attribute__((objc_direct)); @end @interface LottiePath : NSObject -- (CGRect)boundingBox __attribute__((objc_direct)); - (void)enumerateItems:(void (^ _Nonnull)(LottiePathItem * _Nonnull))iterate __attribute__((objc_direct)); - (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithCustomData:(NSData * _Nonnull)customData __attribute__((objc_direct)); @end @@ -64,6 +65,7 @@ typedef NS_ENUM(NSUInteger, LottieGradientType) { @property (nonatomic, readonly, direct) CGFloat opacity; - (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithColor:(LottieColor)color opacity:(CGFloat)opacity __attribute__((objc_direct)); @end @@ -76,6 +78,7 @@ typedef NS_ENUM(NSUInteger, LottieGradientType) { @property (nonatomic, readonly, direct) CGPoint end; - (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithOpacity:(CGFloat)opacity gradientType:(LottieGradientType)gradientType colorStops:(NSArray * _Nonnull)colorStops start:(CGPoint)start end:(CGPoint)end __attribute__((objc_direct)); @end @@ -85,6 +88,7 @@ typedef NS_ENUM(NSUInteger, LottieGradientType) { @property (nonatomic, readonly, direct) LottieFillRule fillRule; - (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading fillRule:(LottieFillRule)fillRule __attribute__((objc_direct)); @end @@ -99,6 +103,7 @@ typedef NS_ENUM(NSUInteger, LottieGradientType) { @property (nonatomic, strong, readonly, direct) NSArray * _Nullable dashPattern; - (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading lineWidth:(CGFloat)lineWidth lineJoin:(CGLineJoin)lineJoin lineCap:(CGLineCap)lineCap miterLimit:(CGFloat)miterLimit dashPhase:(CGFloat)dashPhase dashPattern:(NSArray * _Nullable)dashPattern __attribute__((objc_direct)); @end @@ -109,6 +114,7 @@ typedef NS_ENUM(NSUInteger, LottieGradientType) { @property (nonatomic, strong, readonly, direct) LottieRenderContentFill * _Nullable fill; - (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithPath:(LottiePath * _Nonnull)path stroke:(LottieRenderContentStroke * _Nullable)stroke fill:(LottieRenderContentFill * _Nullable)fill __attribute__((objc_direct)); @end @@ -130,6 +136,7 @@ typedef NS_ENUM(NSUInteger, LottieGradientType) { @property (nonatomic, readonly, direct) LottieRenderNode * _Nullable mask; - (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithPosition:(CGPoint)position bounds:(CGRect)bounds transform:(CATransform3D)transform opacity:(CGFloat)opacity masksToBounds:(bool)masksToBounds isHidden:(bool)isHidden globalRect:(CGRect)globalRect globalTransform:(CATransform3D)globalTransform renderContent:(LottieRenderContent * _Nullable)renderContent hasSimpleContents:(bool)hasSimpleContents isInvertedMatte:(bool)isInvertedMatte subnodes:(NSArray * _Nonnull)subnodes mask:(LottieRenderNode * _Nullable)mask __attribute__((objc_direct)); @end diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp index 55c0c68225..8a3829e646 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp @@ -43,8 +43,8 @@ public: std::shared_ptr assetLibrary_, std::optional> markers_, std::optional> fitzModifiers_, - std::optional meta_, - std::optional comps_ + std::optional meta_, + std::optional comps_ ) : startFrame(startFrame_), endFrame(endFrame_), @@ -75,7 +75,7 @@ public: Animation(const Animation&) = delete; Animation& operator=(Animation&) = delete; - static std::shared_ptr fromJson(json11::Json::object const &json) noexcept(false) { + static std::shared_ptr fromJson(lottiejson11::Json::object const &json) noexcept(false) { auto name = getOptionalString(json, "nm"); auto version = getString(json, "v"); @@ -168,14 +168,14 @@ public: ); } - json11::Json::object toJson() const { - json11::Json::object result; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; if (name.has_value()) { result.insert(std::make_pair("nm", name.value())); } - result.insert(std::make_pair("v", json11::Json(version))); + result.insert(std::make_pair("v", lottiejson11::Json(version))); if (tgs.has_value()) { result.insert(std::make_pair("tgs", tgs.value())); @@ -184,34 +184,34 @@ public: if (type.has_value()) { switch (type.value()) { case CoordinateSpace::Type2d: - result.insert(std::make_pair("ddd", json11::Json(0))); + result.insert(std::make_pair("ddd", lottiejson11::Json(0))); break; case CoordinateSpace::Type3d: - result.insert(std::make_pair("ddd", json11::Json(1))); + result.insert(std::make_pair("ddd", lottiejson11::Json(1))); break; } } - result.insert(std::make_pair("ip", json11::Json(startFrame))); - result.insert(std::make_pair("op", json11::Json(endFrame))); - result.insert(std::make_pair("fr", json11::Json(framerate))); - result.insert(std::make_pair("w", json11::Json(width))); - result.insert(std::make_pair("h", json11::Json(height))); + result.insert(std::make_pair("ip", lottiejson11::Json(startFrame))); + result.insert(std::make_pair("op", lottiejson11::Json(endFrame))); + result.insert(std::make_pair("fr", lottiejson11::Json(framerate))); + result.insert(std::make_pair("w", lottiejson11::Json(width))); + result.insert(std::make_pair("h", lottiejson11::Json(height))); - json11::Json::array layersArray; + lottiejson11::Json::array layersArray; for (const auto &layer : layers) { - json11::Json::object layerJson; + lottiejson11::Json::object layerJson; layer->toJson(layerJson); layersArray.push_back(layerJson); } - result.insert(std::make_pair("layers", json11::Json(layersArray))); + result.insert(std::make_pair("layers", lottiejson11::Json(layersArray))); if (glyphs.has_value()) { - json11::Json::array glyphArray; + lottiejson11::Json::array glyphArray; for (const auto &glyph : glyphs.value()) { glyphArray.push_back(glyph->toJson()); } - result.insert(std::make_pair("chars", json11::Json(glyphArray))); + result.insert(std::make_pair("chars", lottiejson11::Json(glyphArray))); } if (fonts.has_value()) { @@ -223,19 +223,19 @@ public: } if (markers.has_value()) { - json11::Json::array markerArray; + lottiejson11::Json::array markerArray; for (const auto &marker : markers.value()) { markerArray.push_back(marker.toJson()); } - result.insert(std::make_pair("markers", json11::Json(markerArray))); + result.insert(std::make_pair("markers", lottiejson11::Json(markerArray))); } if (fitzModifiers.has_value()) { - json11::Json::array fitzModifierArray; + lottiejson11::Json::array fitzModifierArray; for (const auto &fitzModifier : fitzModifiers.value()) { fitzModifierArray.push_back(fitzModifier.toJson()); } - result.insert(std::make_pair("fitz", json11::Json(fitzModifierArray))); + result.insert(std::make_pair("fitz", lottiejson11::Json(fitzModifierArray))); } if (meta.has_value()) { @@ -305,8 +305,8 @@ public: std::optional> fitzModifiers; - std::optional meta; - std::optional comps; + std::optional meta; + std::optional comps; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.hpp index c071ab7854..dabec9756c 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.hpp @@ -14,7 +14,7 @@ public: id(id_) { } - explicit Asset(json11::Json::object const &json) noexcept(false) { + explicit Asset(lottiejson11::Json::object const &json) noexcept(false) { auto idData = getAny(json, "id"); if (idData.is_string()) { id = idData.string_value(); @@ -30,7 +30,7 @@ public: Asset(const Asset&) = delete; Asset& operator=(Asset&) = delete; - virtual void toJson(json11::Json::object &json) const { + virtual void toJson(lottiejson11::Json::object &json) const { json.insert(std::make_pair("id", id)); if (objectName.has_value()) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.hpp index 7ff3b07451..5e7b893d02 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.hpp @@ -22,7 +22,7 @@ public: precompAssets(precompAssets_) { } - explicit AssetLibrary(json11::Json const &json) noexcept(false) { + explicit AssetLibrary(lottiejson11::Json const &json) noexcept(false) { if (!json.is_array()) { throw LottieParsingException(); } @@ -45,11 +45,11 @@ public: } } - json11::Json::array toJson() const { - json11::Json::array result; + lottiejson11::Json::array toJson() const { + lottiejson11::Json::array result; for (const auto &asset : assetList) { - json11::Json::object assetJson; + lottiejson11::Json::object assetJson; asset->toJson(assetJson); result.push_back(assetJson); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp index a1e63cbea7..f4210ea065 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp @@ -23,7 +23,7 @@ public: virtual ~ImageAsset() = default; - explicit ImageAsset(json11::Json::object const &json) noexcept(false) : + explicit ImageAsset(lottiejson11::Json::object const &json) noexcept(false) : Asset(json) { name = getString(json, "p"); directory = getString(json, "u"); @@ -34,7 +34,7 @@ public: _t = getOptionalString(json, "t"); } - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { Asset::toJson(json); json.insert(std::make_pair("p", name)); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp index a6869c2081..d79f0016b8 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp @@ -21,7 +21,7 @@ public: virtual ~PrecompAsset() = default; - explicit PrecompAsset(json11::Json::object const &json) noexcept(false) : + explicit PrecompAsset(lottiejson11::Json::object const &json) noexcept(false) : Asset(json) { frameRate = getOptionalDouble(json, "fr"); @@ -36,12 +36,12 @@ public: } } - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { Asset::toJson(json); - json11::Json::array layerArray; + lottiejson11::Json::array layerArray; for (const auto &layer : layers) { - json11::Json::object layerJson; + lottiejson11::Json::object layerJson; layer->toJson(layerJson); layerArray.push_back(layerJson); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.hpp index ce591c7655..50549a1121 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.hpp @@ -27,7 +27,7 @@ public: isSingle(false) { } - KeyframeGroup(json11::Json::object const &json) noexcept(false) { + KeyframeGroup(lottiejson11::Json::object const &json) noexcept(false) { isAnimated = getOptionalInt(json, "a"); expression = getOptionalAny(json, "x"); expressionIndex = getOptionalInt(json, "ix"); @@ -101,15 +101,15 @@ public: } } - json11::Json::object toJson() const { - json11::Json::object result; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; assert(!keyframes.empty()); if (keyframes.size() == 1 && isSingle) { result.insert(std::make_pair("k", keyframes[0].value.toJson())); } else { - json11::Json::array containerData; + lottiejson11::Json::array containerData; for (const auto &keyframe : rawKeyframeData) { containerData.push_back(keyframe.toJson()); @@ -138,7 +138,7 @@ public: std::vector> keyframes; std::optional isAnimated; - std::optional expression; + std::optional expression; std::optional expressionIndex; std::vector> rawKeyframeData; bool isSingle = false; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.hpp index 2787e4e557..23349f7390 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.hpp @@ -9,7 +9,7 @@ namespace lottie { /// A layer that holds an image. class ImageLayerModel: public LayerModel { public: - explicit ImageLayerModel(json11::Json::object const &json) noexcept(false) : + explicit ImageLayerModel(lottiejson11::Json::object const &json) noexcept(false) : LayerModel(json) { referenceID = getString(json, "refId"); @@ -18,7 +18,7 @@ public: virtual ~ImageLayerModel() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { LayerModel::toJson(json); json.insert(std::make_pair("refId", referenceID)); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.cpp index 6ceabadcf5..f14f1df9b8 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.cpp @@ -2,7 +2,7 @@ namespace lottie { -LayerType parseLayerType(json11::Json::object const &json, std::string const &key) { +LayerType parseLayerType(lottiejson11::Json::object const &json, std::string const &key) { if (const auto layerTypeValue = getOptionalInt(json, "ty")) { switch (layerTypeValue.value()) { case 0: diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp index a5e08d4447..2670043c44 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp @@ -21,7 +21,7 @@ enum class LayerType { Text }; -LayerType parseLayerType(json11::Json::object const &json, std::string const &key); +LayerType parseLayerType(lottiejson11::Json::object const &json, std::string const &key); int serializeLayerType(LayerType value); enum class MatteType: int { @@ -53,7 +53,7 @@ enum class BlendMode: int { /// A base top container for shapes, images, and other view objects. class LayerModel { public: - explicit LayerModel(json11::Json::object const &json) noexcept(false) { + explicit LayerModel(lottiejson11::Json::object const &json) noexcept(false) { name = getOptionalString(json, "nm"); index = getOptionalInt(json, "ind"); @@ -179,7 +179,7 @@ public: virtual ~LayerModel() = default; - virtual void toJson(json11::Json::object &json) const { + virtual void toJson(lottiejson11::Json::object &json) const { if (name.has_value()) { json.insert(std::make_pair("nm", name.value())); } @@ -219,7 +219,7 @@ public: } if (masks.has_value()) { - json11::Json::array maskArray; + lottiejson11::Json::array maskArray; for (const auto &mask : masks.value()) { maskArray.push_back(mask->toJson()); } @@ -308,9 +308,9 @@ public: std::optional hasMask; std::optional td; - std::optional effectsData; + std::optional effectsData; std::optional layerClass; - std::optional _extraHidden; + std::optional _extraHidden; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.cpp index 6183d51be3..47bb5b1467 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.cpp @@ -8,7 +8,7 @@ namespace lottie { -std::shared_ptr parseLayerModel(json11::Json::object const &json) noexcept(false) { +std::shared_ptr parseLayerModel(lottiejson11::Json::object const &json) noexcept(false) { LayerType layerType = parseLayerType(json, "ty"); switch (layerType) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.hpp index 6f42ac527d..7b1b8cfe71 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.hpp @@ -7,7 +7,7 @@ namespace lottie { -std::shared_ptr parseLayerModel(json11::Json::object const &json) noexcept(false); +std::shared_ptr parseLayerModel(lottiejson11::Json::object const &json) noexcept(false); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp index 7f4956e803..7934474c78 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp @@ -13,7 +13,7 @@ namespace lottie { /// A layer that holds another animation composition. class PreCompLayerModel: public LayerModel { public: - PreCompLayerModel(json11::Json::object const &json) : + PreCompLayerModel(lottiejson11::Json::object const &json) : LayerModel(json) { referenceID = getString(json, "refId"); if (const auto timeRemappingData = getOptionalObject(json, "tm")) { @@ -25,7 +25,7 @@ public: virtual ~PreCompLayerModel() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { LayerModel::toJson(json); json.insert(std::make_pair("refId", referenceID)); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.hpp index 72477ef93b..61f4329bba 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.hpp @@ -12,7 +12,7 @@ namespace lottie { /// A layer that holds vector shape objects. class ShapeLayerModel: public LayerModel { public: - ShapeLayerModel(json11::Json::object const &json) noexcept(false) : + ShapeLayerModel(lottiejson11::Json::object const &json) noexcept(false) : LayerModel(json) { auto shapeItemsData = getObjectArray(json, "shapes"); for (const auto &shapeItemData : shapeItemsData) { @@ -22,12 +22,12 @@ public: virtual ~ShapeLayerModel() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { LayerModel::toJson(json); - json11::Json::array shapeItemArray; + lottiejson11::Json::array shapeItemArray; for (const auto &item : items) { - json11::Json::object itemJson; + lottiejson11::Json::object itemJson; item->toJson(itemJson); shapeItemArray.push_back(itemJson); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp index 3a375d2a51..922d3e6342 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp @@ -9,7 +9,7 @@ namespace lottie { /// A layer that holds a solid color. class SolidLayerModel: public LayerModel { public: - explicit SolidLayerModel(json11::Json::object const &json) noexcept(false) : + explicit SolidLayerModel(lottiejson11::Json::object const &json) noexcept(false) : LayerModel(json) { colorHex = getString(json, "sc"); width = getDouble(json, "sw"); @@ -18,7 +18,7 @@ public: virtual ~SolidLayerModel() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { LayerModel::toJson(json); json.insert(std::make_pair("sc", colorHex)); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.hpp index 6ea5e62c28..37fb374ddf 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.hpp @@ -12,7 +12,7 @@ namespace lottie { /// A layer that holds text. class TextLayerModel: public LayerModel { public: - TextLayerModel(json11::Json::object const &json) : + TextLayerModel(lottiejson11::Json::object const &json) : LayerModel(json), text(KeyframeGroup(TextDocument( "", @@ -46,10 +46,10 @@ public: virtual ~TextLayerModel() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { LayerModel::toJson(json); - json11::Json::object textContainer; + lottiejson11::Json::object textContainer; textContainer.insert(std::make_pair("d", text.toJson())); if (_extraM.has_value()) { textContainer.insert(std::make_pair("m", _extraM.value())); @@ -57,7 +57,7 @@ public: if (_extraP.has_value()) { textContainer.insert(std::make_pair("p", _extraP.value())); } - json11::Json::array animatorArray; + lottiejson11::Json::array animatorArray; for (const auto &animator : animators) { animatorArray.push_back(animator->toJson()); } @@ -73,9 +73,9 @@ public: /// Text animators std::vector> animators; - std::optional _extraM; - std::optional _extraP; - std::optional _extraA; + std::optional _extraM; + std::optional _extraP; + std::optional _extraA; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.hpp index 6cd73101ff..87379ea722 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.hpp @@ -23,7 +23,7 @@ public: value(value_) { } - explicit DashElement(json11::Json::object const &json) noexcept(false) : + explicit DashElement(lottiejson11::Json::object const &json) noexcept(false) : type(DashElementType::Offset), value(KeyframeGroup(Vector1D(0.0))) { auto typeRawValue = getString(json, "n"); @@ -42,8 +42,8 @@ public: name = getOptionalString(json, "nm"); } - json11::Json::object toJson() const { - json11::Json::object result; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; switch (type) { case DashElementType::Offset: diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp index 163cf09c21..06553a99b6 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp @@ -7,7 +7,7 @@ namespace lottie { class FitzModifier { public: - explicit FitzModifier(json11::Json::object const &json) noexcept(false) { + explicit FitzModifier(lottiejson11::Json::object const &json) noexcept(false) { original = getInt(json, "o"); type12 = getOptionalInt(json, "f12"); type3 = getOptionalInt(json, "f3"); @@ -16,8 +16,8 @@ public: type6 = getOptionalInt(json, "f6"); } - json11::Json::object toJson() const { - json11::Json::object result; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; result.insert(std::make_pair("o", (double)original)); if (type12.has_value()) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp index 4219996a4b..4f715e9ac7 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp @@ -18,14 +18,14 @@ public: frameTime(frameTime_) { } - explicit Marker(json11::Json::object const &json) noexcept(false) { + explicit Marker(lottiejson11::Json::object const &json) noexcept(false) { name = getString(json, "cm"); frameTime = getDouble(json, "tm"); dr = getOptionalInt(json, "dr"); } - json11::Json::object toJson() const { - json11::Json::object result; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; result.insert(std::make_pair("cm", name)); result.insert(std::make_pair("tm", frameTime)); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp index d453a1c374..fd3c2cba7b 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp @@ -19,7 +19,7 @@ enum class MaskMode { class Mask { public: - explicit Mask(json11::Json::object const &json) noexcept(false) : + explicit Mask(lottiejson11::Json::object const &json) noexcept(false) : opacity(KeyframeGroup(Vector1D(100.0))), shape(KeyframeGroup(BezierPath())), inverted(false), @@ -61,8 +61,8 @@ public: name = getOptionalString(json, "nm"); } - json11::Json::object toJson() const { - json11::Json::object result; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; if (_mode.has_value()) { switch (_mode.value()) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.hpp index d3bfa4471e..19cfc92da8 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.hpp @@ -43,7 +43,7 @@ public: _rotationZ(rotationZ_) { } - explicit Transform(json11::Json::object const &json) noexcept(false) { + explicit Transform(lottiejson11::Json::object const &json) noexcept(false) { // AnchorPoint if (const auto anchorPointDictionary = getOptionalObject(json, "a")) { _anchorPoint = KeyframeGroup(anchorPointDictionary.value()); @@ -112,8 +112,8 @@ public: _extraSk = getOptionalAny(json, "sk"); } - json11::Json::object toJson() const { - json11::Json::object result; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; if (_anchorPoint.has_value()) { result.insert(std::make_pair("a", _anchorPoint->toJson())); @@ -139,7 +139,7 @@ public: result.insert(std::make_pair("p", _position->toJson())); break; case PositionInternalRepresentation::NestedXY: - json11::Json::object nestedPosition; + lottiejson11::Json::object nestedPosition; assert(_positionX.has_value()); assert(_positionY.has_value()); assert(!_position.has_value()); @@ -258,8 +258,8 @@ private: std::optional _extra_positionS; std::optional _extraTy; - std::optional _extraSa; - std::optional _extraSk; + std::optional _extraSa; + std::optional _extraSk; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.hpp index 2565dbf817..17bd82d716 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.hpp @@ -17,7 +17,7 @@ enum class PathDirection: int { /// An item that define an ellipse shape class Ellipse: public ShapeItem { public: - explicit Ellipse(json11::Json::object const &json) noexcept(false) : + explicit Ellipse(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json), position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), size(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))) { @@ -43,7 +43,7 @@ public: virtual ~Ellipse() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); if (direction.has_value()) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp index 6666c3b238..194a08dcbb 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp @@ -17,7 +17,7 @@ enum class FillRule: int { class Fill: public ShapeItem { public: - explicit Fill(json11::Json::object const &json) noexcept(false) : + explicit Fill(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json), opacity(KeyframeGroup(Vector1D(0.0))), color(KeyframeGroup(Color(0.0, 0.0, 0.0, 0.0))) { @@ -45,7 +45,7 @@ public: virtual ~Fill() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); json.insert(std::make_pair("o", opacity.toJson())); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp index 55c7efc9c2..fbe8bdbe16 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp @@ -17,7 +17,7 @@ enum class GradientType: int { /// An item that define a gradient fill class GradientFill: public ShapeItem { public: - explicit GradientFill(json11::Json::object const &json) noexcept(false) : + explicit GradientFill(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json), opacity(KeyframeGroup(Vector1D(100.0))), startPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), @@ -60,7 +60,7 @@ public: virtual ~GradientFill() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); json.insert(std::make_pair("o", opacity.toJson())); @@ -75,7 +75,7 @@ public: json.insert(std::make_pair("a", highlightAngle->toJson())); } - json11::Json::object colorsContainer; + lottiejson11::Json::object colorsContainer; colorsContainer.insert(std::make_pair("p", numberOfColors)); colorsContainer.insert(std::make_pair("k", colors.toJson())); json.insert(std::make_pair("g", colorsContainer)); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp index 77b45b76aa..ff60fc1b20 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp @@ -13,7 +13,7 @@ namespace lottie { /// An item that define a gradient stroke class GradientStroke: public ShapeItem { public: - explicit GradientStroke(json11::Json::object const &json) noexcept(false) : + explicit GradientStroke(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json), opacity(KeyframeGroup(Vector1D(100.0))), startPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), @@ -109,7 +109,7 @@ public: virtual ~GradientStroke() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); json.insert(std::make_pair("o", opacity.toJson())); @@ -133,13 +133,13 @@ public: json.insert(std::make_pair("ml", miterLimit.value())); } - json11::Json::object colorsContainer; + lottiejson11::Json::object colorsContainer; colorsContainer.insert(std::make_pair("p", numberOfColors)); colorsContainer.insert(std::make_pair("k", colors.toJson())); json.insert(std::make_pair("g", colorsContainer)); if (dashPattern.has_value()) { - json11::Json::array dashElements; + lottiejson11::Json::array dashElements; for (const auto &dashElement : dashPattern.value()) { dashElements.push_back(dashElement.toJson()); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.hpp index 10002a2691..7bc2eb4d10 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.hpp @@ -12,7 +12,7 @@ namespace lottie { /// An item that define an ellipse shape class Group: public ShapeItem { public: - explicit Group(json11::Json::object const &json) noexcept(false) : + explicit Group(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json) { auto itemsData = getObjectArray(json, "it"); for (const auto &itemData : itemsData) { @@ -24,12 +24,12 @@ public: virtual ~Group() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); - json11::Json::array itemArray; + lottiejson11::Json::array itemArray; for (const auto &item : items) { - json11::Json::object itemJson; + lottiejson11::Json::object itemJson; item->toJson(itemJson); itemArray.push_back(itemJson); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.hpp index 0492f66664..4f6fcc4881 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.hpp @@ -18,7 +18,7 @@ enum class MergeMode: int { /// An item that define an ellipse shape class Merge: public ShapeItem { public: - explicit Merge(json11::Json::object const &json) noexcept(false) : + explicit Merge(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json), mode(MergeMode::None) { auto modeRawValue = getInt(json, "mm"); @@ -48,7 +48,7 @@ public: virtual ~Merge() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); json.insert(std::make_pair("mm", (int)mode)); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.hpp index bb4a1d19e9..67926f2e29 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.hpp @@ -11,7 +11,7 @@ namespace lottie { /// An item that define an ellipse shape class Rectangle: public ShapeItem { public: - explicit Rectangle(json11::Json::object const &json) noexcept(false) : + explicit Rectangle(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json), position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), size(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), @@ -70,7 +70,7 @@ public: cornerRadius(cornerRadius_) { } - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); if (direction.has_value()) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.hpp index 9c8b7f86b2..8bb25a1c37 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.hpp @@ -10,7 +10,7 @@ namespace lottie { /// An item that define a repeater class Repeater: public ShapeItem { public: - explicit Repeater(json11::Json::object const &json) noexcept(false) : + explicit Repeater(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json) { if (const auto copiesData = getOptionalObject(json, "c")) { copies = KeyframeGroup(copiesData.value()); @@ -39,7 +39,7 @@ public: virtual ~Repeater() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); if (copies.has_value()) { @@ -49,7 +49,7 @@ public: json.insert(std::make_pair("o", offset->toJson())); } - json11::Json::object transformContainer; + lottiejson11::Json::object transformContainer; if (startOpacity.has_value()) { json.insert(std::make_pair("so", startOpacity->toJson())); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.hpp index f50a21e7c3..548d23a32d 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.hpp @@ -11,7 +11,7 @@ namespace lottie { /// An item that define an ellipse shape class RoundedRectangle: public ShapeItem { public: - explicit RoundedRectangle(json11::Json::object const &json) noexcept(false) : + explicit RoundedRectangle(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json), cornerRadius(KeyframeGroup(Vector1D(0.0))) { if (const auto directionRawValue = getOptionalInt(json, "d")) { @@ -42,7 +42,7 @@ public: virtual ~RoundedRectangle() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); if (direction.has_value()) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp index b3b52ca370..0fd4a2dd76 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp @@ -13,7 +13,7 @@ namespace lottie { /// An item that defines an custom shape class Shape: public ShapeItem { public: - explicit Shape(json11::Json::object const &json) noexcept(false) : + explicit Shape(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json), path(KeyframeGroup(getObject(json, "ks"))) { if (const auto directionRawValue = getOptionalInt(json, "d")) { @@ -35,7 +35,7 @@ public: virtual ~Shape() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); json.insert(std::make_pair("ks", path.toJson())); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.cpp index ca859c56dd..a47b348c29 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.cpp @@ -17,7 +17,7 @@ namespace lottie { -std::shared_ptr parseShapeItem(json11::Json::object const &json) noexcept(false) { +std::shared_ptr parseShapeItem(lottiejson11::Json::object const &json) noexcept(false) { auto typeRawValue = getString(json, "ty"); if (typeRawValue == "el") { return std::make_shared(json); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.hpp index 2b05dba93a..892eba1db2 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.hpp @@ -27,13 +27,13 @@ enum class ShapeType { /// An item belonging to a Shape Layer class ShapeItem { public: - ShapeItem(json11::Json const &jsonAny) noexcept(false) : + ShapeItem(lottiejson11::Json const &jsonAny) noexcept(false) : type(ShapeType::Ellipse) { if (!jsonAny.is_object()) { throw LottieParsingException(); } - json11::Json::object const &json = jsonAny.object_items(); + lottiejson11::Json::object const &json = jsonAny.object_items(); name = getOptionalString(json, "nm"); matchName = getOptionalString(json, "mn"); @@ -106,7 +106,7 @@ public: ShapeItem(const ShapeItem&) = delete; ShapeItem& operator=(ShapeItem&) = delete; - virtual void toJson(json11::Json::object &json) const { + virtual void toJson(lottiejson11::Json::object &json) const { if (name.has_value()) { json.insert(std::make_pair("nm", name.value())); } @@ -202,7 +202,7 @@ public: std::optional layerClass; }; -std::shared_ptr parseShapeItem(json11::Json::object const &json) noexcept(false); +std::shared_ptr parseShapeItem(lottiejson11::Json::object const &json) noexcept(false); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.hpp index 5160cf4a0b..fcfd28a2b8 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.hpp @@ -10,7 +10,7 @@ namespace lottie { /// An item that define a shape transform class ShapeTransform: public ShapeItem { public: - explicit ShapeTransform(json11::Json::object const &json) noexcept(false) : + explicit ShapeTransform(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json) { if (const auto anchorData = getOptionalObject(json, "a")) { anchor = KeyframeGroup(anchorData.value()); @@ -37,7 +37,7 @@ public: virtual ~ShapeTransform() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); if (anchor.has_value()) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.hpp index 7bc5fb16c6..c9f711526a 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.hpp @@ -19,7 +19,7 @@ enum class StarType: int { /// An item that define a star shape class Star: public ShapeItem { public: - explicit Star(json11::Json::object const &json) noexcept(false) : + explicit Star(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json), position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), outerRadius(KeyframeGroup(Vector1D(0.0))), @@ -75,7 +75,7 @@ public: virtual ~Star() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); if (direction.has_value()) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp index 5930dbadd2..0131f7e69f 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp @@ -15,7 +15,7 @@ namespace lottie { /// An item that define an ellipse shape class Stroke: public ShapeItem { public: - explicit Stroke(json11::Json::object const &json) noexcept(false) : + explicit Stroke(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json), opacity(KeyframeGroup(Vector1D(100.0))), color(KeyframeGroup(Color(0.0, 0.0, 0.0, 0.0))), @@ -81,7 +81,7 @@ public: virtual ~Stroke() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); json.insert(std::make_pair("o", opacity.toJson())); @@ -96,7 +96,7 @@ public: } if (dashPattern.has_value()) { - json11::Json::array dashElements; + lottiejson11::Json::array dashElements; for (const auto &dashElement : dashPattern.value()) { dashElements.push_back(dashElement.toJson()); } @@ -134,7 +134,7 @@ public: std::optional> dashPattern; std::optional fillEnabled; - std::optional ml2; + std::optional ml2; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp index 7ae612262b..3e0365548b 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp @@ -16,7 +16,7 @@ enum class TrimType: int { /// An item that defines trim class Trim: public ShapeItem { public: - explicit Trim(json11::Json::object const &json) noexcept(false) : + explicit Trim(lottiejson11::Json::object const &json) noexcept(false) : ShapeItem(json), start(KeyframeGroup(Vector1D(0.0))), end(KeyframeGroup(Vector1D(0.0))), @@ -41,7 +41,7 @@ public: virtual ~Trim() = default; - virtual void toJson(json11::Json::object &json) const override { + virtual void toJson(lottiejson11::Json::object &json) const override { ShapeItem::toJson(json); json.insert(std::make_pair("s", start.toJson())); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp index 8bde83023e..229a85c9dd 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp @@ -22,7 +22,7 @@ public: ascent(ascent_) { } - explicit Font(json11::Json::object const &json) noexcept(false) { + explicit Font(lottiejson11::Json::object const &json) noexcept(false) { name = getString(json, "fName"); familyName = getString(json, "fFamily"); path = getOptionalString(json, "fPath"); @@ -33,8 +33,8 @@ public: origin = getOptionalInt(json, "origin"); } - json11::Json::object toJson() const { - json11::Json::object result; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; result.insert(std::make_pair("fName", name)); result.insert(std::make_pair("fFamily", familyName)); @@ -74,7 +74,7 @@ public: fonts(fonts_) { } - explicit FontList(json11::Json::object const &json) noexcept(false) { + explicit FontList(lottiejson11::Json::object const &json) noexcept(false) { if (const auto fontsData = getOptionalObjectArray(json, "list")) { for (const auto &fontData : fontsData.value()) { fonts.emplace_back(fontData); @@ -82,14 +82,14 @@ public: } } - json11::Json::object toJson() const { - json11::Json::array fontArray; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::array fontArray; for (const auto &font : fonts) { fontArray.push_back(font.toJson()); } - json11::Json::object result; + lottiejson11::Json::object result; result.insert(std::make_pair("list", fontArray)); return result; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp index 25d0af5fa9..17b09c0ce0 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp @@ -27,7 +27,7 @@ public: shapes(shapes_) { } - explicit Glyph(json11::Json::object const &json) noexcept(false) : + explicit Glyph(lottiejson11::Json::object const &json) noexcept(false) : character(""), fontSize(0.0), fontFamily(""), @@ -52,8 +52,8 @@ public: } } - json11::Json::object toJson() const { - json11::Json::object result; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; result.insert(std::make_pair("ch", character)); result.insert(std::make_pair("size", fontSize)); @@ -62,13 +62,13 @@ public: result.insert(std::make_pair("w", width)); if (internalHasData || shapes.has_value()) { - json11::Json::object shapeContainer; + lottiejson11::Json::object shapeContainer; if (shapes.has_value()) { - json11::Json::array shapeArray; + lottiejson11::Json::array shapeArray; for (const auto &shape : shapes.value()) { - json11::Json::object shapeJson; + lottiejson11::Json::object shapeJson; shape->toJson(shapeJson); shapeArray.push_back(shapeJson); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp index e302a9051f..5004e74189 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp @@ -41,18 +41,18 @@ public: tracking(tracking_) { } - explicit TextAnimator(json11::Json const &jsonAny) { + explicit TextAnimator(lottiejson11::Json const &jsonAny) { if (!jsonAny.is_object()) { throw LottieParsingException(); } - json11::Json::object const &json = jsonAny.object_items(); + lottiejson11::Json::object const &json = jsonAny.object_items(); if (const auto nameData = getOptionalString(json, "nm")) { name = nameData.value(); } _extraS = getOptionalAny(json, "s"); - json11::Json::object const &animatorContainer = getObject(json, "a"); + lottiejson11::Json::object const &animatorContainer = getObject(json, "a"); if (const auto fillColorData = getOptionalObject(animatorContainer, "fc")) { fillColor = KeyframeGroup(fillColorData.value()); @@ -89,8 +89,8 @@ public: } } - json11::Json::object toJson() const { - json11::Json::object animatorContainer; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object animatorContainer; if (fillColor.has_value()) { animatorContainer.insert(std::make_pair("fc", fillColor->toJson())); @@ -126,7 +126,7 @@ public: animatorContainer.insert(std::make_pair("o", opacity->toJson())); } - json11::Json::object result; + lottiejson11::Json::object result; result.insert(std::make_pair("a", animatorContainer)); if (name.has_value()) { @@ -175,7 +175,7 @@ public: /// Tracking std::optional> tracking; - std::optional _extraS; + std::optional _extraS; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp index 9eb3b1f154..72df5474b1 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp @@ -48,7 +48,7 @@ public: textFrameSize(textFrameSize_) { } - explicit TextDocument(json11::Json const &jsonAny) noexcept(false) : + explicit TextDocument(lottiejson11::Json const &jsonAny) noexcept(false) : text(""), fontSize(0.0), fontFamily(""), @@ -59,7 +59,7 @@ public: throw LottieParsingException(); } - json11::Json::object const &json = jsonAny.object_items(); + lottiejson11::Json::object const &json = jsonAny.object_items(); text = getString(json, "t"); fontSize = getDouble(json, "s"); @@ -103,8 +103,8 @@ public: } } - json11::Json::object toJson() const { - json11::Json::object result; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; result.insert(std::make_pair("t", text)); result.insert(std::make_pair("s", fontSize)); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.cpp index da1dcb51e6..f72968be59 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.cpp @@ -26,7 +26,7 @@ const char* LottieParsingException::what() const throw() { return "Lottie parsing exception"; } -json11::Json getAny(json11::Json::object const &object, std::string const &key) noexcept(false) { +lottiejson11::Json getAny(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { throw LottieParsingException(); @@ -34,7 +34,7 @@ json11::Json getAny(json11::Json::object const &object, std::string const &key) return value->second; } -std::optional getOptionalAny(json11::Json::object const &object, std::string const &key) noexcept(false) { +std::optional getOptionalAny(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { return std::nullopt; @@ -42,7 +42,7 @@ std::optional getOptionalAny(json11::Json::object const &object, s return value->second; } -json11::Json::object getObject(json11::Json::object const &object, std::string const &key) noexcept(false) { +lottiejson11::Json::object getObject(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { throw LottieParsingException(); @@ -53,7 +53,7 @@ json11::Json::object getObject(json11::Json::object const &object, std::string c return value->second.object_items(); } -std::optional getOptionalObject(json11::Json::object const &object, std::string const &key) noexcept(false) { +std::optional getOptionalObject(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { return std::nullopt; @@ -64,7 +64,7 @@ std::optional getOptionalObject(json11::Json::object const return value->second.object_items(); } -std::vector getObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false) { +std::vector getObjectArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { throw LottieParsingException(); @@ -73,7 +73,7 @@ std::vector getObjectArray(json11::Json::object const &obj throw LottieParsingException(); } - std::vector result; + std::vector result; for (const auto &item : value->second.array_items()) { if (!item.is_object()) { throw LottieParsingException(); @@ -84,7 +84,7 @@ std::vector getObjectArray(json11::Json::object const &obj return result; } -std::optional> getOptionalObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false) { +std::optional> getOptionalObjectArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { return std::nullopt; @@ -93,7 +93,7 @@ std::optional> getOptionalObjectArray(json11:: throw LottieParsingException(); } - std::vector result; + std::vector result; for (const auto &item : value->second.array_items()) { if (!item.is_object()) { throw LottieParsingException(); @@ -104,7 +104,7 @@ std::optional> getOptionalObjectArray(json11:: return result; } -std::vector getAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false) { +std::vector getAnyArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { throw LottieParsingException(); @@ -116,7 +116,7 @@ std::vector getAnyArray(json11::Json::object const &object, std::s return value->second.array_items(); } -std::optional> getOptionalAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false) { +std::optional> getOptionalAnyArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { throw std::nullopt; @@ -128,7 +128,7 @@ std::optional> getOptionalAnyArray(json11::Json::objec return value->second.array_items(); } -std::string getString(json11::Json::object const &object, std::string const &key) noexcept(false) { +std::string getString(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { throw LottieParsingException(); @@ -139,7 +139,7 @@ std::string getString(json11::Json::object const &object, std::string const &key return value->second.string_value(); } -std::optional getOptionalString(json11::Json::object const &object, std::string const &key) noexcept(false) { +std::optional getOptionalString(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { return std::nullopt; @@ -150,7 +150,7 @@ std::optional getOptionalString(json11::Json::object const &object, return value->second.string_value(); } -int32_t getInt(json11::Json::object const &object, std::string const &key) noexcept(false) { +int32_t getInt(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { throw LottieParsingException(); @@ -161,7 +161,7 @@ int32_t getInt(json11::Json::object const &object, std::string const &key) noexc return value->second.int_value(); } -std::optional getOptionalInt(json11::Json::object const &object, std::string const &key) noexcept(false) { +std::optional getOptionalInt(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { return std::nullopt; @@ -172,7 +172,7 @@ std::optional getOptionalInt(json11::Json::object const &object, std::s return value->second.int_value(); } -double getDouble(json11::Json::object const &object, std::string const &key) noexcept(false) { +double getDouble(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { throw LottieParsingException(); @@ -183,7 +183,7 @@ double getDouble(json11::Json::object const &object, std::string const &key) noe return value->second.number_value(); } -std::optional getOptionalDouble(json11::Json::object const &object, std::string const &key) noexcept(false) { +std::optional getOptionalDouble(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { return std::nullopt; @@ -194,7 +194,7 @@ std::optional getOptionalDouble(json11::Json::object const &object, std: return value->second.number_value(); } -bool getBool(json11::Json::object const &object, std::string const &key) noexcept(false) { +bool getBool(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { throw LottieParsingException(); @@ -205,7 +205,7 @@ bool getBool(json11::Json::object const &object, std::string const &key) noexcep return value->second.bool_value(); } -std::optional getOptionalBool(json11::Json::object const &object, std::string const &key) noexcept(false) { +std::optional getOptionalBool(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { auto value = object.find(key); if (value == object.end()) { return std::nullopt; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.hpp index 37b666d38a..fc57ae101e 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.hpp @@ -23,29 +23,29 @@ public: virtual const char* what() const throw(); }; -json11::Json getAny(json11::Json::object const &object, std::string const &key) noexcept(false); -std::optional getOptionalAny(json11::Json::object const &object, std::string const &key) noexcept(false); +lottiejson11::Json getAny(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalAny(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); -json11::Json::object getObject(json11::Json::object const &object, std::string const &key) noexcept(false); -std::optional getOptionalObject(json11::Json::object const &object, std::string const &key) noexcept(false); +lottiejson11::Json::object getObject(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalObject(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); -std::vector getObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false); -std::optional> getOptionalObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false); +std::vector getObjectArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional> getOptionalObjectArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); -std::vector getAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false); -std::optional> getOptionalAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false); +std::vector getAnyArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional> getOptionalAnyArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); -std::string getString(json11::Json::object const &object, std::string const &key) noexcept(false); -std::optional getOptionalString(json11::Json::object const &object, std::string const &key) noexcept(false); +std::string getString(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalString(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); -int32_t getInt(json11::Json::object const &object, std::string const &key) noexcept(false); -std::optional getOptionalInt(json11::Json::object const &object, std::string const &key) noexcept(false); +int32_t getInt(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalInt(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); -double getDouble(json11::Json::object const &object, std::string const &key) noexcept(false); -std::optional getOptionalDouble(json11::Json::object const &object, std::string const &key) noexcept(false); +double getDouble(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalDouble(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); -bool getBool(json11::Json::object const &object, std::string const &key) noexcept(false); -std::optional getOptionalBool(json11::Json::object const &object, std::string const &key) noexcept(false); +bool getBool(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalBool(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.hpp index 881a5917ef..66277737d8 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.hpp @@ -32,9 +32,9 @@ public: closed(false) { } - explicit BezierPathContents(json11::Json const &jsonAny) noexcept(false) : + explicit BezierPathContents(lottiejson11::Json const &jsonAny) noexcept(false) : elements({}) { - json11::Json::object const *json = nullptr; + lottiejson11::Json::object const *json = nullptr; if (jsonAny.is_object()) { json = &jsonAny.object_items(); } else if (jsonAny.is_array()) { @@ -97,12 +97,12 @@ public: BezierPathContents(const BezierPathContents&) = delete; BezierPathContents& operator=(BezierPathContents&) = delete; - json11::Json toJson() const { - json11::Json::object result; + lottiejson11::Json toJson() const { + lottiejson11::Json::object result; - json11::Json::array vertices; - json11::Json::array inPoints; - json11::Json::array outPoints; + lottiejson11::Json::array vertices; + lottiejson11::Json::array inPoints; + lottiejson11::Json::array outPoints; for (const auto &element : elements) { vertices.push_back(element.vertex.point.toJson()); @@ -118,7 +118,7 @@ public: result.insert(std::make_pair("c", closed.value())); } - return json11::Json(result); + return lottiejson11::Json(result); } std::shared_ptr cgPath() const { @@ -445,11 +445,11 @@ public: _contents(std::make_shared()) { } - explicit BezierPath(json11::Json const &jsonAny) noexcept(false) : + explicit BezierPath(lottiejson11::Json const &jsonAny) noexcept(false) : _contents(std::make_shared(jsonAny)) { } - json11::Json toJson() const { + lottiejson11::Json toJson() const { return _contents->toJson(); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp index 51f9ae4f13..1cf21a8667 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp @@ -149,7 +149,7 @@ public: spatialOutTangent(spatialOutTangent_) { } - explicit KeyframeData(json11::Json const &json) noexcept(false) { + explicit KeyframeData(lottiejson11::Json const &json) noexcept(false) { if (!json.is_object()) { throw LottieParsingException(); } @@ -186,8 +186,8 @@ public: } } - json11::Json::object toJson() const { - json11::Json::object result; + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; if (startValue.has_value()) { result.insert(std::make_pair("s", startValue->toJson())); @@ -240,7 +240,7 @@ public: /// The spacial out tangent of the vector. std::optional spatialOutTangent; - std::optional nData; + std::optional nData; bool isHold() const { if (hold.has_value()) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.hpp index 8fdbdea8ae..df436cc932 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.hpp @@ -62,7 +62,7 @@ struct Color { a = a_ / denominatorValue; } - explicit Color(json11::Json const &jsonAny) noexcept(false) : + explicit Color(lottiejson11::Json const &jsonAny) noexcept(false) : r(0.0), g(0.0), b(0.0), a(0.0) { if (!jsonAny.is_array()) { throw LottieParsingException(); @@ -125,13 +125,13 @@ struct Color { a = a1; } - json11::Json toJson() const { - json11::Json::array result; + lottiejson11::Json toJson() const { + lottiejson11::Json::array result; - result.push_back(json11::Json(r)); - result.push_back(json11::Json(g)); - result.push_back(json11::Json(b)); - result.push_back(json11::Json(a)); + result.push_back(lottiejson11::Json(r)); + result.push_back(lottiejson11::Json(g)); + result.push_back(lottiejson11::Json(b)); + result.push_back(lottiejson11::Json(a)); return result; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp index e1db01a15f..d37448999a 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp @@ -11,7 +11,7 @@ struct GradientColorSet { GradientColorSet() { } - explicit GradientColorSet(json11::Json const &jsonAny) noexcept(false) { + explicit GradientColorSet(lottiejson11::Json const &jsonAny) noexcept(false) { if (!jsonAny.is_array()) { throw LottieParsingException(); } @@ -24,8 +24,8 @@ struct GradientColorSet { } } - json11::Json toJson() const { - json11::Json::array result; + lottiejson11::Json toJson() const { + lottiejson11::Json::array result; for (auto value : colors) { result.push_back(value); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.hpp index 9cdff038d1..41eb1c5667 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.hpp @@ -18,7 +18,7 @@ struct Vector1D { value(value_) { } - explicit Vector1D(json11::Json const &json) noexcept(false) { + explicit Vector1D(lottiejson11::Json const &json) noexcept(false) { if (json.is_number()) { value = json.number_value(); } else if (json.is_array()) { @@ -34,8 +34,8 @@ struct Vector1D { } } - json11::Json toJson() const { - return json11::Json(value); + lottiejson11::Json toJson() const { + return lottiejson11::Json(value); } double value; @@ -68,7 +68,7 @@ struct Vector2D { y(y_) { } - explicit Vector2D(json11::Json const &json) noexcept(false) { + explicit Vector2D(lottiejson11::Json const &json) noexcept(false) { x = 0.0; y = 0.0; @@ -121,13 +121,13 @@ struct Vector2D { } } - json11::Json toJson() const { - json11::Json::object result; + lottiejson11::Json toJson() const { + lottiejson11::Json::object result; result.insert(std::make_pair("x", x)); result.insert(std::make_pair("y", y)); - return json11::Json(result); + return lottiejson11::Json(result); } double x; @@ -200,7 +200,7 @@ struct Vector3D { z(z_) { } - explicit Vector3D(json11::Json const &json) noexcept(false) { + explicit Vector3D(lottiejson11::Json const &json) noexcept(false) { if (!json.is_array()) { throw LottieParsingException(); } @@ -236,14 +236,14 @@ struct Vector3D { } } - json11::Json toJson() const { - json11::Json::array result; + lottiejson11::Json toJson() const { + lottiejson11::Json::array result; - result.push_back(json11::Json(x)); - result.push_back(json11::Json(y)); - result.push_back(json11::Json(z)); + result.push_back(lottiejson11::Json(x)); + result.push_back(lottiejson11::Json(y)); + result.push_back(lottiejson11::Json(z)); - return json11::Json(result); + return lottiejson11::Json(result); } double x = 0.0; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimation.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimation.mm index 2d25dd7e0f..db68b68370 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimation.mm +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimation.mm @@ -17,7 +17,7 @@ self = [super init]; if (self != nil) { std::string errorText; - auto json = json11::Json::parse(std::string((uint8_t const *)data.bytes, ((uint8_t const *)data.bytes) + data.length), errorText); + auto json = lottiejson11::Json::parse(std::string((uint8_t const *)data.bytes, ((uint8_t const *)data.bytes) + data.length), errorText); if (!json.is_object()) { return nil; } @@ -35,13 +35,17 @@ return (NSInteger)(_animation->endFrame - _animation->startFrame); } +- (NSInteger)framesPerSecond { + return (NSInteger)(_animation->framerate); +} + - (CGSize)size { return CGSizeMake(_animation->width, _animation->height); } - (NSData * _Nonnull)toJson { - json11::Json::object json = _animation->toJson(); - std::string jsonString = json11::Json(json).dump(); + lottiejson11::Json::object json = _animation->toJson(); + std::string jsonString = lottiejson11::Json(json).dump(); return [[NSData alloc] initWithBytes:jsonString.data() length:jsonString.size()]; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm index 9fe84fc5f3..db650d76ff 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm @@ -321,13 +321,6 @@ static std::shared_ptr convertRenderTree(std::shared_ptrposition().x + -node->bounds().x, node->position().y + -node->bounds().y); CATransform3D localTransform = node->transform(); localTransform = localTransform.translated(localTranslation); - //if (localTransform.isIdentity()) { - // localTransform.m41 += localTranslation.x; - // localTransform.m42 += localTranslation.y; - //} else { - // localTransform.m41 += localTranslation.x; - // localTransform.m42 += localTranslation.y; - //} currentTransform = localTransform * currentTransform; @@ -554,8 +547,6 @@ static std::shared_ptr convertRenderTree(std::shared_ptr + +#ifdef __cplusplus +extern "C" { +#endif + +typedef NS_ENUM(NSUInteger, LottiePathItemType) { + LottiePathItemTypeMoveTo, + LottiePathItemTypeLineTo, + LottiePathItemTypeCurveTo, + LottiePathItemTypeClose +}; + +typedef struct { + LottiePathItemType type; + CGPoint points[4]; +} LottiePathItem; + +typedef struct { + CGFloat r; + CGFloat g; + CGFloat b; + CGFloat a; +} LottieColor; + +typedef NS_ENUM(NSUInteger, LottieFillRule) { + LottieFillRuleEvenOdd, + LottieFillRuleWinding +}; + +typedef NS_ENUM(NSUInteger, LottieGradientType) { + LottieGradientTypeLinear, + LottieGradientTypeRadial +}; + +@interface LottieColorStop : NSObject + +@property (nonatomic, readonly, direct) LottieColor color; +@property (nonatomic, readonly, direct) CGFloat location; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithColor:(LottieColor)color location:(CGFloat)location __attribute__((objc_direct)); + +@end + +@interface LottiePath : NSObject + +- (void)enumerateItems:(void (^ _Nonnull)(LottiePathItem * _Nonnull))iterate __attribute__((objc_direct)); + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithCustomData:(NSData * _Nonnull)customData __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentShading : NSObject + +@end + +@interface LottieRenderContentSolidShading : LottieRenderContentShading + +@property (nonatomic, readonly, direct) LottieColor color; +@property (nonatomic, readonly, direct) CGFloat opacity; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithColor:(LottieColor)color opacity:(CGFloat)opacity __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentGradientShading : LottieRenderContentShading + +@property (nonatomic, readonly, direct) CGFloat opacity; +@property (nonatomic, readonly, direct) LottieGradientType gradientType; +@property (nonatomic, strong, readonly, direct) NSArray * _Nonnull colorStops; +@property (nonatomic, readonly, direct) CGPoint start; +@property (nonatomic, readonly, direct) CGPoint end; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithOpacity:(CGFloat)opacity gradientType:(LottieGradientType)gradientType colorStops:(NSArray * _Nonnull)colorStops start:(CGPoint)start end:(CGPoint)end __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentFill : NSObject + +@property (nonatomic, strong, readonly, direct) LottieRenderContentShading * _Nonnull shading; +@property (nonatomic, readonly, direct) LottieFillRule fillRule; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading fillRule:(LottieFillRule)fillRule __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentStroke : NSObject + +@property (nonatomic, strong, readonly, direct) LottieRenderContentShading * _Nonnull shading; +@property (nonatomic, readonly, direct) CGFloat lineWidth; +@property (nonatomic, readonly, direct) CGLineJoin lineJoin; +@property (nonatomic, readonly, direct) CGLineCap lineCap; +@property (nonatomic, readonly, direct) CGFloat miterLimit; +@property (nonatomic, readonly, direct) CGFloat dashPhase; +@property (nonatomic, strong, readonly, direct) NSArray * _Nullable dashPattern; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading lineWidth:(CGFloat)lineWidth lineJoin:(CGLineJoin)lineJoin lineCap:(CGLineCap)lineCap miterLimit:(CGFloat)miterLimit dashPhase:(CGFloat)dashPhase dashPattern:(NSArray * _Nullable)dashPattern __attribute__((objc_direct)); + +@end + +@interface LottieRenderContent : NSObject + +@property (nonatomic, strong, readonly, direct) LottiePath * _Nonnull path; +@property (nonatomic, strong, readonly, direct) LottieRenderContentStroke * _Nullable stroke; +@property (nonatomic, strong, readonly, direct) LottieRenderContentFill * _Nullable fill; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithPath:(LottiePath * _Nonnull)path stroke:(LottieRenderContentStroke * _Nullable)stroke fill:(LottieRenderContentFill * _Nullable)fill __attribute__((objc_direct)); + +@end + +@interface LottieRenderNode : NSObject + +@property (nonatomic, readonly, direct) CGPoint position; +@property (nonatomic, readonly, direct) CGRect bounds; +@property (nonatomic, readonly, direct) CATransform3D transform; +@property (nonatomic, readonly, direct) CGFloat opacity; +@property (nonatomic, readonly, direct) bool masksToBounds; +@property (nonatomic, readonly, direct) bool isHidden; + +@property (nonatomic, readonly, direct) CGRect globalRect; +@property (nonatomic, readonly, direct) CATransform3D globalTransform; +@property (nonatomic, readonly, direct) LottieRenderContent * _Nullable renderContent; +@property (nonatomic, readonly, direct) bool hasSimpleContents; +@property (nonatomic, readonly, direct) bool isInvertedMatte; +@property (nonatomic, readonly, direct) NSArray * _Nonnull subnodes; +@property (nonatomic, readonly, direct) LottieRenderNode * _Nullable mask; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithPosition:(CGPoint)position bounds:(CGRect)bounds transform:(CATransform3D)transform opacity:(CGFloat)opacity masksToBounds:(bool)masksToBounds isHidden:(bool)isHidden globalRect:(CGRect)globalRect globalTransform:(CATransform3D)globalTransform renderContent:(LottieRenderContent * _Nullable)renderContent hasSimpleContents:(bool)hasSimpleContents isInvertedMatte:(bool)isInvertedMatte subnodes:(NSArray * _Nonnull)subnodes mask:(LottieRenderNode * _Nullable)mask __attribute__((objc_direct)); + +@end + +#ifdef __cplusplus +} +#endif + +#endif /* LottieRenderTree_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm index 19bfcb3f57..01890e9290 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm @@ -1,4 +1,4 @@ -#include +#include "LottieRenderTree.h" #include "LottieRenderTreeInternal.h" #include "Lottie/Public/Primitives/CGPath.hpp" @@ -15,6 +15,7 @@ namespace { @interface LottiePath () { std::vector _paths; + NSData *_customData; } @end @@ -29,83 +30,128 @@ namespace { return self; } -/*- (instancetype _Nonnull)initWithCGPath:(CGPathRef _Nonnull)cgPath { +- (instancetype _Nonnull)initWithCustomData:(NSData * _Nonnull)customData __attribute__((objc_direct)) { self = [super init]; if (self != nil) { - CGMutablePathRef mutableCopy = CGPathCreateMutableCopy(cgPath); - _path = std::make_shared(mutableCopy); - CFRelease(mutableCopy); + _customData = customData; } return self; -}*/ - -- (CGRect)boundingBox { - lottie::CGRect result = bezierPathsBoundingBox(_paths); - return CGRectMake(result.x, result.y, result.width, result.height); } - (void)enumerateItems:(void (^ _Nonnull)(LottiePathItem * _Nonnull))iterate { LottiePathItem item; - for (const auto &path : _paths) { - std::optional previousElement; - for (const auto &element : path.elements()) { - if (previousElement.has_value()) { - if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + if (_customData != nil) { + int dataOffset = 0; + int dataLength = (int)_customData.length; + uint8_t const *dataBytes = (uint8_t const *)_customData.bytes; + while (dataOffset < dataLength) { + uint8_t itemType = dataBytes[dataOffset]; + dataOffset += 1; + + switch (itemType) { + case 0: { + Float32 px; + memcpy(&px, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 py; + memcpy(&py, dataBytes + dataOffset, 4); + dataOffset += 4; + + item.type = LottiePathItemTypeMoveTo; + item.points[0] = CGPointMake(px, py); + iterate(&item); + + break; + } + case 1: { + Float32 px; + memcpy(&px, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 py; + memcpy(&py, dataBytes + dataOffset, 4); + dataOffset += 4; + item.type = LottiePathItemTypeLineTo; + item.points[0] = CGPointMake(px, py); + iterate(&item); + + break; + } + case 2: { + Float32 p1x; + memcpy(&p1x, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 p1y; + memcpy(&p1y, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 p2x; + memcpy(&p2x, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 p2y; + memcpy(&p2y, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 px; + memcpy(&px, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 py; + memcpy(&py, dataBytes + dataOffset, 4); + dataOffset += 4; + + item.type = LottiePathItemTypeCurveTo; + item.points[0] = CGPointMake(p1x, p1y); + item.points[1] = CGPointMake(p2x, p2y); + item.points[2] = CGPointMake(px, py); + iterate(&item); + + break; + } + case 3: { + item.type = LottiePathItemTypeClose; + iterate(&item); + break; + } + default: { + break; + } + } + } + } else { + for (const auto &path : _paths) { + std::optional previousElement; + for (const auto &element : path.elements()) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + item.type = LottiePathItemTypeLineTo; + item.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(&item); + } else { + item.type = LottiePathItemTypeCurveTo; + item.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + item.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); + item.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); + iterate(&item); + } + } else { + item.type = LottiePathItemTypeMoveTo; item.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); iterate(&item); - } else { - item.type = LottiePathItemTypeCurveTo; - item.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); - item.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); - item.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); - iterate(&item); } - } else { - item.type = LottiePathItemTypeMoveTo; - item.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); - iterate(&item); + previousElement = element; } - previousElement = element; - } - if (path.closed().value_or(true)) { - item.type = LottiePathItemTypeClose; - iterate(&item); - } - } - - /*_path->enumerate([iterate](lottie::CGPathItem const &element) { - LottiePathItem item; - - switch (element.type) { - case lottie::CGPathItem::Type::MoveTo: { - item.type = LottiePathItemTypeMoveTo; - item.points[0] = CGPointMake(element.points[0].x, element.points[0].y); - iterate(&item); - break; - } - case lottie::CGPathItem::Type::LineTo: { - item.type = LottiePathItemTypeLineTo; - item.points[0] = CGPointMake(element.points[0].x, element.points[0].y); - iterate(&item); - break; - } - case lottie::CGPathItem::Type::CurveTo: { - item.type = LottiePathItemTypeCurveTo; - item.points[0] = CGPointMake(element.points[0].x, element.points[0].y); - item.points[1] = CGPointMake(element.points[1].x, element.points[1].y); - item.points[2] = CGPointMake(element.points[2].x, element.points[2].y); - iterate(&item); - break; - } - case lottie::CGPathItem::Type::Close: { + if (path.closed().value_or(true)) { item.type = LottiePathItemTypeClose; iterate(&item); - break; } } - });*/ + } } @end @@ -155,6 +201,15 @@ static LottieColor lottieColorFromColor(lottie::Color color) { return self; } +- (instancetype _Nonnull)initWithColor:(LottieColor)color opacity:(CGFloat)opacity { + self = [super init]; + if (self != nil) { + _color = color; + _opacity = opacity; + } + return self; +} + @end @implementation LottieRenderContentGradientShading @@ -187,6 +242,18 @@ static LottieColor lottieColorFromColor(lottie::Color color) { return self; } +- (instancetype _Nonnull)initWithOpacity:(CGFloat)opacity gradientType:(LottieGradientType)gradientType colorStops:(NSArray * _Nonnull)colorStops start:(CGPoint)start end:(CGPoint)end __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _opacity = opacity; + _gradientType = gradientType; + _colorStops = colorStops; + _start = start; + _end = end; + } + return self; +} + @end @implementation LottieRenderContentFill @@ -222,6 +289,15 @@ static LottieColor lottieColorFromColor(lottie::Color color) { return self; } +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading fillRule:(LottieFillRule)fillRule __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _shading = shading; + _fillRule = fillRule; + } + return self; +} + @end @implementation LottieRenderContentStroke @@ -298,6 +374,20 @@ static LottieColor lottieColorFromColor(lottie::Color color) { return self; } +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading lineWidth:(CGFloat)lineWidth lineJoin:(CGLineJoin)lineJoin lineCap:(CGLineCap)lineCap miterLimit:(CGFloat)miterLimit dashPhase:(CGFloat)dashPhase dashPattern:(NSArray * _Nullable)dashPattern __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _shading = shading; + _lineWidth = lineWidth; + _lineJoin = lineJoin; + _lineCap = lineCap; + _miterLimit = miterLimit; + _dashPhase = dashPhase; + _dashPattern = dashPattern; + } + return self; +} + @end @implementation LottieRenderContent @@ -316,10 +406,40 @@ static LottieColor lottieColorFromColor(lottie::Color color) { return self; } +- (instancetype _Nonnull)initWithPath:(LottiePath * _Nonnull)path stroke:(LottieRenderContentStroke * _Nullable)stroke fill:(LottieRenderContentFill * _Nullable)fill __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _path = path; + _stroke = stroke; + _fill = fill; + } + return self; +} + @end @implementation LottieRenderNode +- (instancetype _Nonnull)initWithPosition:(CGPoint)position bounds:(CGRect)bounds transform:(CATransform3D)transform opacity:(CGFloat)opacity masksToBounds:(bool)masksToBounds isHidden:(bool)isHidden globalRect:(CGRect)globalRect globalTransform:(CATransform3D)globalTransform renderContent:(LottieRenderContent * _Nullable)renderContent hasSimpleContents:(bool)hasSimpleContents isInvertedMatte:(bool)isInvertedMatte subnodes:(NSArray * _Nonnull)subnodes mask:(LottieRenderNode * _Nullable)mask __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _position = position; + _bounds = bounds; + _transform = transform; + _opacity = opacity; + _masksToBounds = masksToBounds; + _isHidden = isHidden; + _globalRect = globalRect; + _globalTransform= globalTransform; + _renderContent = renderContent; + _hasSimpleContents = hasSimpleContents; + _isInvertedMatte = isInvertedMatte; + _subnodes = subnodes; + _mask = mask; + } + return self; +} + @end @implementation LottieRenderNode (Internal) diff --git a/submodules/TelegramUI/Components/LottieMetal/BUILD b/submodules/TelegramUI/Components/LottieMetal/BUILD index 9c92883589..dd63b1b0eb 100644 --- a/submodules/TelegramUI/Components/LottieMetal/BUILD +++ b/submodules/TelegramUI/Components/LottieMetal/BUILD @@ -1,5 +1,46 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") +load( + "@build_bazel_rules_apple//apple:resources.bzl", + "apple_resource_bundle", + "apple_resource_group", +) +load("//build-system/bazel-utils:plist_fragment.bzl", + "plist_fragment", +) + +filegroup( + name = "LottieMetalSources", + srcs = glob([ + "Metal/**/*.metal", + ]), + visibility = ["//visibility:public"], +) + +plist_fragment( + name = "LottieMetalSourcesBundleInfoPlist", + extension = "plist", + template = + """ + CFBundleIdentifier + org.telegram.LottieMetalSources + CFBundleDevelopmentRegion + en + CFBundleName + LottieMetal + """ +) + +apple_resource_bundle( + name = "LottieMetalSourcesBundle", + infoplists = [ + ":LottieMetalSourcesBundleInfoPlist", + ], + resources = [ + ":LottieMetalSources", + ], +) + swift_library( name = "LottieMetal", module_name = "LottieMetal", @@ -9,6 +50,9 @@ swift_library( copts = [ "-warnings-as-errors", ], + data = [ + ":LottieMetalSourcesBundle", + ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", @@ -17,6 +61,7 @@ swift_library( "//submodules/GZip", "//submodules/MetalEngine", "//submodules/TelegramUI/Components/LottieCpp", + "//submodules/Components/HierarchyTrackingLayer", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/LottieMetal/Metal/LottieMetalShaders.metal b/submodules/TelegramUI/Components/LottieMetal/Metal/LottieMetalShaders.metal new file mode 100644 index 0000000000..62257c7dae --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Metal/LottieMetalShaders.metal @@ -0,0 +1,872 @@ +#include +using namespace metal; + +typedef struct +{ + packed_float2 position; + packed_float2 texCoord; +} QuadVertex; + +typedef struct +{ + packed_float2 position; +} Vertex; + +typedef struct +{ + float4 position [[position]]; + float2 texCoord; + float2 transformedPosition; +} QuadOut; + +typedef struct +{ + float4 position [[position]]; + float direction; +} FillVertexOut; + +float calculateNormalDirection(float2 a, float2 b, float2 c) { + float2 ab = b - a; + float2 ac = c - a; + + return ab.x * ac.y - ab.y * ac.x; +} + +vertex QuadOut quad_vertex_shader( + device QuadVertex const *vertices [[buffer(0)]], + uint vertexId [[vertex_id]], + device matrix const &transform [[buffer(1)]] +) { + QuadVertex in = vertices[vertexId]; + QuadOut out; + float4 position = transform * float4(float2(in.position), 0.0, 1.0); + out.position = position; + out.texCoord = in.texCoord; + out.transformedPosition = (transform * float4(float2(in.position), 0.0, 1.0)).xy; + + return out; +} + +vertex FillVertexOut fill_vertex_shader( + device Vertex const *vertices [[buffer(0)]], + uint vertexId [[vertex_id]], + device matrix const &transform [[buffer(1)]], + device packed_float2 const &baseVertex [[buffer(2)]] +) { + FillVertexOut out; + uint triangleIndex = vertexId / 3; + uint vertexInTriangleIndex = vertexId % 3; + + //[0, 1], [1, 2], [2, 3]... + //0, 1, 2 + + float2 sourcePosition; + float2 v1 = float2(vertices[triangleIndex].position); + float2 v2 = float2(vertices[triangleIndex + 1].position); + + sourcePosition = select( + select( + v2, + v1, + vertexInTriangleIndex == 1 + ), + baseVertex, + vertexInTriangleIndex == 0 + ); + + float normalDirection = calculateNormalDirection(baseVertex, v1, v2); + + float4 position = transform * float4(sourcePosition, 0.0, 1.0); + out.position = position; + + out.direction = sign(normalDirection); + + return out; +} + +struct ShapeOut { + half4 color [[color(1)]]; +}; + +fragment ShapeOut fragment_shader( + FillVertexOut in [[stage_in]], + ShapeOut current, + device const int32_t &mode [[buffer(1)]] +) { + ShapeOut out = current; + + if (mode == 0) { + half result = select(out.color.r, half(127.0 / 255.0), out.color.r == 0.0); + result += half(in.direction) * 3.0 / 255.0; + out.color.r = result; + } else { + out.color.r = out.color.r == 0.0 ? 1.0 : 0.0; + } + return out; +} + +fragment ShapeOut clear_mask_fragment( + QuadOut in [[stage_in]] +) { + ShapeOut out; + out.color = half4(0.0); + return out; +} + +struct ColorOut { + half4 color [[color(0)]]; +}; + +fragment ColorOut merge_color_fill_fragment_shader( + ShapeOut colorIn, + device const float4 &color [[buffer(0)]], + device const int32_t &mode [[buffer(1)]] +) { + ColorOut out; + + half4 sampledColor = half4(color); + sampledColor.r = sampledColor.r * sampledColor.a; + sampledColor.g = sampledColor.g * sampledColor.a; + sampledColor.b = sampledColor.b * sampledColor.a; + + if (mode == 0) { + half diff = abs(colorIn.color.r - 127.0 / 255.0); + float diffSelect = select(0.0, 1.0, diff > (2.0 / 255.0)); + float outColorFactor = select( + 0.0, + diffSelect, + colorIn.color.r > 1.0 / 255.0 + ); + out.color = sampledColor * outColorFactor; + } else { + float outColorFactor = select( + 0.0, + 1.0, + colorIn.color.r > 1.0 / 255.0 + ); + + out.color = sampledColor * outColorFactor; + } + + if (out.color.a == 0.0) { + //discard_fragment(); + } + + return out; +} + +typedef struct +{ + packed_float4 color; + float location; +} GradientColorStop; + +float linearGradientStep(float edge0, float edge1, float x) { + float t = clamp((x - edge0) / (edge1 - edge0), float(0), float(1)); + return t; +} + +fragment ColorOut merge_linear_gradient_fill_fragment_shader( + QuadOut quadIn [[stage_in]], + ShapeOut colorIn, + device const GradientColorStop *colorStops [[buffer(0)]], + device const int32_t &mode [[buffer(1)]], + device const uint &numColorStops [[buffer(2)]], + device const packed_float2 &localStartPosition [[buffer(3)]], + device const packed_float2 &localEndPosition [[buffer(4)]] +) { + ColorOut out; + + float4 sourceColor; + + if (numColorStops <= 1) { + sourceColor = colorStops[0].color; + } else { + float2 localPixelPosition = quadIn.transformedPosition.xy; + + float2 gradientVector = normalize(localEndPosition - localStartPosition); + float2 pointVector = localPixelPosition - localStartPosition; + float pixelDistance = dot(pointVector, gradientVector) / dot(gradientVector, gradientVector); + float gradientLength = length(localEndPosition - localStartPosition); + float pixelValue = clamp(pixelDistance / gradientLength, 0.0, 1.0); + + sourceColor = mix(colorStops[0].color, colorStops[1].color, linearGradientStep( + colorStops[0].location, + colorStops[1].location, + pixelValue + )); + for (int i = 1; i < (int)numColorStops - 1; i++) { + sourceColor = mix(sourceColor, colorStops[i + 1].color, linearGradientStep( + colorStops[i].location, + colorStops[i + 1].location, + pixelValue + )); + } + } + + half4 sampledColor = half4(sourceColor); + + sampledColor.r = sampledColor.r * sampledColor.a; + sampledColor.g = sampledColor.g * sampledColor.a; + sampledColor.b = sampledColor.b * sampledColor.a; + + if (mode == 0) { + half diff = abs(colorIn.color.r - 127.0 / 255.0); + float diffSelect = select(0.0, 1.0, diff > (2.0 / 255.0)); + float outColorFactor = select( + 0.0, + diffSelect, + colorIn.color.r > 1.0 / 255.0 + ); + out.color = sampledColor * outColorFactor; + } else { + float outColorFactor = select( + 0.0, + 1.0, + colorIn.color.r > 1.0 / 255.0 + ); + + out.color = sampledColor * outColorFactor; + } + + if (out.color.a == 0.0) { + //discard_fragment(); + } + + return out; +} + +fragment ColorOut merge_radial_gradient_fill_fragment_shader( + QuadOut quadIn [[stage_in]], + ShapeOut colorIn, + device const GradientColorStop *colorStops [[buffer(0)]], + device const int32_t &mode [[buffer(1)]], + device const uint &numColorStops [[buffer(2)]], + device const packed_float2 &localStartPosition [[buffer(3)]], + device const packed_float2 &localEndPosition [[buffer(4)]] +) { + ColorOut out; + + float4 sourceColor; + + if (numColorStops <= 1) { + sourceColor = colorStops[0].color; + } else { + float pixelDistance = distance(quadIn.transformedPosition.xy, localStartPosition); + float gradientLength = length(localEndPosition - localStartPosition); + float pixelValue = clamp(pixelDistance / gradientLength, 0.0, 1.0); + + sourceColor = colorStops[0].color; + for (int i = 0; i < (int)numColorStops - 1; i++) { + float currentStopLocation = colorStops[i].location; + float nextStopLocation = colorStops[i + 1].location; + float4 nextStopColor = colorStops[i + 1].color; + sourceColor = mix(sourceColor, nextStopColor, linearGradientStep( + currentStopLocation, + nextStopLocation, + pixelValue + )); + } + } + + half4 sampledColor = half4(sourceColor); + + sampledColor.r = sampledColor.r * sampledColor.a; + sampledColor.g = sampledColor.g * sampledColor.a; + sampledColor.b = sampledColor.b * sampledColor.a; + + if (mode == 0) { + half diff = abs(colorIn.color.r - 127.0 / 255.0); + float diffSelect = select(0.0, 1.0, diff > (2.0 / 255.0)); + float outColorFactor = select( + 0.0, + diffSelect, + colorIn.color.r > 1.0 / 255.0 + ); + out.color = sampledColor * outColorFactor; + } else { + float outColorFactor = select( + 0.0, + 1.0, + colorIn.color.r > 1.0 / 255.0 + ); + + out.color = sampledColor * outColorFactor; + } + + if (out.color.a == 0.0) { + //discard_fragment(); + } + + return out; +} + +typedef struct { + packed_float2 position; +} StrokePositionIn; + +typedef struct { + packed_float2 point; +} StrokePointIn; + +typedef struct { + float id; +} StrokeRoundJoinVertexIn; + +typedef struct { + packed_float4 position; +} StrokeMiterJoinVertexIn; + +typedef struct { + packed_float3 position; +} StrokeBevelJoinVertexIn; + +typedef struct { + packed_float2 position; +} StrokeCapVertexIn; + +typedef struct +{ + float4 position [[position]]; +} StrokeVertexOut; + +fragment ColorOut stroke_fragment_shader( + StrokeVertexOut in [[stage_in]], + ShapeOut colorIn, + device const float4 &color [[buffer(0)]] +) { + ColorOut out; + + half4 result = half4(color); + result.r *= result.a; + result.g *= result.a; + result.b *= result.a; + + out.color = result; + + return out; +} + +typedef struct { + int32_t bufferOffset; // 4 + packed_float2 start; // 4 * 2 + packed_float2 end; // 4 * 2 + packed_float2 cp1; // 4 * 2 + packed_float2 cp2; // 4 * 2 + float offset; // 4 +} BezierInputItem; + +kernel void evaluateBezier( + device BezierInputItem const *inputItems [[buffer(0)]], + device float *vertexData [[buffer(1)]], + device uint const &itemCount [[buffer(2)]], + uint2 index [[ thread_position_in_grid ]] +) { + if (index.x >= itemCount) { + return; + } + BezierInputItem item = inputItems[index.x]; + + float2 p0 = item.start; + float2 p1 = item.cp1; + float2 p2 = item.cp2; + float2 p3 = item.end; + + float t = (((float)index.y) + 1.0) / (8.0); + float oneMinusT = 1.0 - t; + + float2 value = oneMinusT * oneMinusT * oneMinusT * p0 + 3.0 * t * oneMinusT * oneMinusT * p1 + 3.0 * t * t * oneMinusT * p2 + t * t * t * p3; + + vertexData[item.bufferOffset + 2 * index.y] = value.x; + vertexData[item.bufferOffset + 2 * index.y + 1] = value.y; +} + +fragment half4 quad_offscreen_fragment( + QuadOut in [[stage_in]], + texture2d texture[[texture(0)]], + device float const &opacity [[buffer(1)]] +) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + half4 color = texture.sample(s, float2(in.texCoord.x, 1.0 - in.texCoord.y)); + + color *= half(opacity); + + return color; +} + +fragment half4 quad_offscreen_fragment_with_mask( + QuadOut in [[stage_in]], + texture2d texture[[texture(0)]], + texture2d maskTexture[[texture(1)]], + device float const &opacity [[buffer(1)]], + device uint const &maskMode [[buffer(2)]] +) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + half4 color = texture.sample(s, float2(in.texCoord.x, 1.0 - in.texCoord.y)); + half4 maskColor = maskTexture.sample(s, float2(in.texCoord.x, 1.0 - in.texCoord.y)); + + if (maskMode == 0) { + color *= maskColor.a; + } else { + color *= 1.0 - maskColor.a; + } + + color *= half(opacity); + + return color; +} + +bool myIsNan(float val) { + return (val < 0.0 || 0.0 < val || val == 0.0) ? false : true; +} + +bool isLinePointInvalid(float4 p) { + return p.w == 0.0 || myIsNan(p.x); +} + +// Adapted from https://github.com/rreusser/regl-gpu-lines + +vertex StrokeVertexOut strokeTerminalVertex( + uint instanceId [[instance_id]], + uint index [[vertex_id]], + device StrokePointIn const *points [[buffer(0)]], + device matrix const &transform [[buffer(1)]], + device packed_float2 const &_vertCnt2 [[buffer(2)]], + device packed_float2 const &_capJoinRes2 [[buffer(3)]], + device uint const &isJoinRound [[buffer(4)]], + device uint const &isCapRound [[buffer(5)]], + device float const &miterLimit [[buffer(6)]], + device float const &width [[buffer(7)]] +) { + const float2 ROUND_CAP_SCALE = float2(1.0, 1.0); + const float2 SQUARE_CAP_SCALE = float2(2.0, 2.0 / sqrt(3.0)); + + float2 _capScale = isCapRound ? ROUND_CAP_SCALE : SQUARE_CAP_SCALE; + + const float pi = 3.141592653589793; + + float2 xyB = points[instanceId * 3 + 0].point; + float2 xyC = points[instanceId * 3 + 1].point; + float2 xyD = points[instanceId * 3 + 2].point; + + StrokeVertexOut out; + + float4 pB = float4(xyB, 0.0, 1.0); + float4 pC = float4(xyC, 0.0, 1.0); + float4 pD = float4(xyD, 0.0, 1.0); + + // A sensible default for early returns + out.position = pB; + + bool aInvalid = false; + bool bInvalid = isLinePointInvalid(pB); + bool cInvalid = isLinePointInvalid(pC); + bool dInvalid = isLinePointInvalid(pD); + + // Vertex count for each part (first half of join, second (mirrored) half). Note that not all of + // these vertices may be used, for example if we have enough for a round cap but only draw a miter + // join. + float2 v = _vertCnt2 + 3.0; + + // Total vertex count + float N = dot(v, float2(1)); + + // If we're past the first half-join and half of the segment, then we swap all vertices and start + // over from the opposite end. + bool mirror = index >= v.x; + + // When rendering dedicated endpoints, this allows us to insert an end cap *alone* (without the attached + // segment and join) + if (dInvalid && mirror) { + return out; + } + + // Convert to screen-pixel coordinates + // Save w so we can perspective re-multiply at the end to get varyings depth-correct + float pw = mirror ? pC.w : pB.w; + pB = float4(float3(pB.xy, pB.z) / pB.w, 1); + pC = float4(float3(pC.xy, pC.z) / pC.w, 1); + pD = float4(float3(pD.xy, pD.z) / pD.w, 1); + + // If it's a cap, mirror A back onto C to accomplish a round + float4 pA = pC; + + // Reject if invalid or if outside viewing planes + if (bInvalid || cInvalid || max(abs(pB.z), abs(pC.z)) > 1.0) { + return out; + } + + // Swap everything computed so far if computing mirrored half + if (mirror) { + float4 vTmp = pC; pC = pB; pB = vTmp; + vTmp = pD; pD = pA; pA = vTmp; + bool bTmp = dInvalid; dInvalid = aInvalid; aInvalid = bTmp; + } + + bool isCap = !mirror; + + // Either flip A onto C (and D onto B) to produce a 180 degree-turn cap, or extrapolate to produce a + // degenerate (no turn) join, depending on whether we're inserting caps or just leaving ends hanging. + if (aInvalid) { pA = 2.0 * pB - pC; } + if (dInvalid) { pD = 2.0 * pC - pB; } + bool roundOrCap = isJoinRound || isCap; + + // Tangent and normal vectors + float2 tBC = pC.xy - pB.xy; + float lBC = length(tBC); + tBC /= lBC; + float2 nBC = float2(-tBC.y, tBC.x); + + float2 tAB = pB.xy - pA.xy; + float lAB = length(tAB); + if (lAB > 0.0) tAB /= lAB; + float2 nAB = float2(-tAB.y, tAB.x); + + float2 tCD = pD.xy - pC.xy; + float lCD = length(tCD); + if (lCD > 0.0) tCD /= lCD; + float2 nCD = float2(-tCD.y, tCD.x); + + // Clamp for safety, since we take the arccos + float cosB = clamp(dot(tAB, tBC), -1.0, 1.0); + + // This section is somewhat fragile. When lines are collinear, signs flip randomly and break orientation + // of the middle segment. The fix appears straightforward, but this took a few hours to get right. + const float tol = 1e-4; + float mirrorSign = mirror ? -1.0 : 1.0; + float dirB = -dot(tBC, nAB); + float dirC = dot(tBC, nCD); + bool bCollinear = abs(dirB) < tol; + bool cCollinear = abs(dirC) < tol; + bool bIsHairpin = bCollinear && cosB < 0.0; + // bool cIsHairpin = cCollinear && dot(tBC, tCD) < 0.0; + dirB = bCollinear ? -mirrorSign : sign(dirB); + dirC = cCollinear ? -mirrorSign : sign(dirC); + + float2 miter = bIsHairpin ? -tBC : 0.5 * (nAB + nBC) * dirB; + + // Compute our primary "join index", that is, the index starting at the very first point of the join. + // The second half of the triangle strip instance is just the first, reversed, and with vertices swapped! + float i = mirror ? N - index : index; + + // Decide the resolution of whichever feature we're drawing. n is twice the number of points used since + // that's the only form in which we use this number. + float res = (isCap ? _capJoinRes2.x : _capJoinRes2.y); + + // Shift the index to send unused vertices to an index below zero, which will then just get clamped to + // zero and result in repeated points, i.e. degenerate triangles. + i -= max(0.0, (mirror ? _vertCnt2.y : _vertCnt2.x) - res); + + // Use the direction to offset the index by one. This has the effect of flipping the winding number so + // that it's always consistent no matter which direction the join turns. + i += (dirB < 0.0 ? -1.0 : 0.0); + + // Vertices of the second (mirrored) half of the join are offset by one to get it to connect correctly + // in the middle, where the mirrored and unmirrored halves meet. + i -= mirror ? 1.0 : 0.0; + + // Clamp to zero and repeat unused excess vertices. + i = max(0.0, i); + + // Start with a default basis pointing along the segment with normal vector outward + float2 xBasis = tBC; + float2 yBasis = nBC * dirB; + + // Default point is 0 along the segment, 1 (width unit) normal to it + float2 xy = float2(0); + + if (i == res + 1.0) { + // pick off this one specific index to be the interior miter point + // If not div-by-zero, then sinB / (1 + cosB) + float m = cosB > -0.9999 ? (tAB.x * tBC.y - tAB.y * tBC.x) / (1.0 + cosB) : 0.0; + xy = float2(min(abs(m), min(lBC, lAB) / width), -1); + } else { + // Draw half of a join + float m2 = dot(miter, miter); + float lm = sqrt(m2); + yBasis = miter / lm; + xBasis = dirB * float2(yBasis.y, -yBasis.x); + bool isBevel = 1.0 > miterLimit * m2; + + if (((int)i) % 2 == 0) { + // Outer joint points + if (roundOrCap || i != 0.0) { + // Round joins + float theta = -0.5 * (acos(cosB) * (clamp(i, 0.0, res) / res) - pi) * (isCap ? 2.0 : 1.0); + xy = float2(cos(theta), sin(theta)); + + if (isCap) { + // A special multiplier factor for turning 3-point rounds into square caps (but leave the + // y == 0.0 point unaffected) + if (xy.y > 0.001) xy *= _capScale; + } + } else { + // Miter joins + yBasis = bIsHairpin ? float2(0) : miter; + xy.y = isBevel ? 1.0 : 1.0 / m2; + } + } else { + // Offset the center vertex position to get bevel SDF correct + if (isBevel && !roundOrCap) { + xy.y = -1.0 + sqrt((1.0 + cosB) * 0.5); + } + } + } + + // Point offset from main vertex position + float2 dP = float2x2(xBasis, yBasis) * xy; + + out.position = pB; + out.position.xy += width * dP; + out.position *= pw; + out.position = transform * out.position; + + return out; +} + +vertex StrokeVertexOut strokeInnerVertex( + uint instanceId [[instance_id]], + uint index [[vertex_id]], + device StrokePointIn const *points [[buffer(0)]], + device matrix const &transform [[buffer(1)]], + device packed_float2 const &_vertCnt2 [[buffer(2)]], + device packed_float2 const &_capJoinRes2 [[buffer(3)]], + device uint const &isJoinRound [[buffer(4)]], + device uint const &isCapRound [[buffer(5)]], + device float const &miterLimit [[buffer(6)]], + device float const &width [[buffer(7)]] +) { + const float2 ROUND_CAP_SCALE = float2(1.0, 1.0); + const float2 SQUARE_CAP_SCALE = float2(2.0, 2.0 / sqrt(3.0)); + + float2 _capScale = isCapRound ? ROUND_CAP_SCALE : SQUARE_CAP_SCALE; + + const float pi = 3.141592653589793; + + float2 xyA = points[instanceId + 0].point; + float2 xyB = points[instanceId + 1].point; + float2 xyC = points[instanceId + 2].point; + float2 xyD = points[instanceId + 3].point; + + StrokeVertexOut out; + + float4 pA = float4(xyA, 0.0, 1.0); + float4 pB = float4(xyB, 0.0, 1.0); + float4 pC = float4(xyC, 0.0, 1.0); + float4 pD = float4(xyD, 0.0, 1.0); + + // A sensible default for early returns + out.position = pB; + + bool aInvalid = isLinePointInvalid(pA); + bool bInvalid = isLinePointInvalid(pB); + bool cInvalid = isLinePointInvalid(pC); + bool dInvalid = isLinePointInvalid(pD); + + // Vertex count for each part (first half of join, second (mirrored) half). Note that not all of + // these vertices may be used, for example if we have enough for a round cap but only draw a miter + // join. + float2 v = _vertCnt2 + 3.0; + + // Total vertex count + float N = dot(v, float2(1)); + + // If we're past the first half-join and half of the segment, then we swap all vertices and start + // over from the opposite end. + bool mirror = index >= v.x; + + // When rendering dedicated endoints, this allows us to insert an end cap *alone* (without the attached + // segment and join) + + + // Convert to screen-pixel coordinates + // Save w so we can perspective re-multiply at the end to get varyings depth-correct + float pw = mirror ? pC.w : pB.w; + pA = float4(float3(pA.xy, pA.z) / pA.w, 1); + pB = float4(float3(pB.xy, pB.z) / pB.w, 1); + pC = float4(float3(pC.xy, pC.z) / pC.w, 1); + pD = float4(float3(pD.xy, pD.z) / pD.w, 1); + + // If it's a cap, mirror A back onto C to accomplish a round + + + // Reject if invalid or if outside viewing planes + if (bInvalid || cInvalid || max(abs(pB.z), abs(pC.z)) > 1.0) { + return out; + } + + // Swap everything computed so far if computing mirrored half + if (mirror) { + float4 vTmp = pC; pC = pB; pB = vTmp; + vTmp = pD; pD = pA; pA = vTmp; + bool bTmp = dInvalid; dInvalid = aInvalid; aInvalid = bTmp; + } + + const bool isCap = false; + + // Either flip A onto C (and D onto B) to produce a 180 degree-turn cap, or extrapolate to produce a + // degenerate (no turn) join, depending on whether we're inserting caps or just leaving ends hanging. + if (aInvalid) { pA = 2.0 * pB - pC; } + if (dInvalid) { pD = 2.0 * pC - pB; } + bool roundOrCap = isJoinRound || isCap; + + // Tangent and normal vectors + float2 tBC = pC.xy - pB.xy; + float lBC = length(tBC); + tBC /= lBC; + float2 nBC = float2(-tBC.y, tBC.x); + + float2 tAB = pB.xy - pA.xy; + float lAB = length(tAB); + if (lAB > 0.0) tAB /= lAB; + float2 nAB = float2(-tAB.y, tAB.x); + + float2 tCD = pD.xy - pC.xy; + float lCD = length(tCD); + if (lCD > 0.0) tCD /= lCD; + float2 nCD = float2(-tCD.y, tCD.x); + + // Clamp for safety, since we take the arccos + float cosB = clamp(dot(tAB, tBC), -1.0, 1.0); + + // This section is somewhat fragile. When lines are collinear, signs flip randomly and break orientation + // of the middle segment. The fix appears straightforward, but this took a few hours to get right. + const float tol = 1e-4; + float mirrorSign = mirror ? -1.0 : 1.0; + float dirB = -dot(tBC, nAB); + float dirC = dot(tBC, nCD); + bool bCollinear = abs(dirB) < tol; + bool cCollinear = abs(dirC) < tol; + bool bIsHairpin = bCollinear && cosB < 0.0; + // bool cIsHairpin = cCollinear && dot(tBC, tCD) < 0.0; + dirB = bCollinear ? -mirrorSign : sign(dirB); + dirC = cCollinear ? -mirrorSign : sign(dirC); + + float2 miter = bIsHairpin ? -tBC : 0.5 * (nAB + nBC) * dirB; + + // Compute our primary "join index", that is, the index starting at the very first point of the join. + // The second half of the triangle strip instance is just the first, reversed, and with vertices swapped! + float i = mirror ? N - index : index; + + // Decide the resolution of whichever feature we're drawing. n is twice the number of points used since + // that's the only form in which we use this number. + float res = (isCap ? _capJoinRes2.x : _capJoinRes2.y); + + // Shift the index to send unused vertices to an index below zero, which will then just get clamped to + // zero and result in repeated points, i.e. degenerate triangles. + i -= max(0.0, (mirror ? _vertCnt2.y : _vertCnt2.x) - res); + + // Use the direction to offset the index by one. This has the effect of flipping the winding number so + // that it's always consistent no matter which direction the join turns. + i += (dirB < 0.0 ? -1.0 : 0.0); + + // Vertices of the second (mirrored) half of the join are offset by one to get it to connect correctly + // in the middle, where the mirrored and unmirrored halves meet. + i -= mirror ? 1.0 : 0.0; + + // Clamp to zero and repeat unused excess vertices. + i = max(0.0, i); + + // Start with a default basis pointing along the segment with normal vector outward + float2 xBasis = tBC; + float2 yBasis = nBC * dirB; + + // Default point is 0 along the segment, 1 (width unit) normal to it + float2 xy = float2(0); + + if (i == res + 1.0) { + // pick off this one specific index to be the interior miter point + // If not div-by-zero, then sinB / (1 + cosB) + float m = cosB > -0.9999 ? (tAB.x * tBC.y - tAB.y * tBC.x) / (1.0 + cosB) : 0.0; + xy = float2(min(abs(m), min(lBC, lAB) / width), -1); + } else { + // Draw half of a join + float m2 = dot(miter, miter); + float lm = sqrt(m2); + yBasis = miter / lm; + xBasis = dirB * float2(yBasis.y, -yBasis.x); + bool isBevel = 1.0 > miterLimit * m2; + + if (((int)i) % 2 == 0) { + // Outer joint points + if (roundOrCap || i != 0.0) { + // Round joins + float theta = -0.5 * (acos(cosB) * (clamp(i, 0.0, res) / res) - pi) * (isCap ? 2.0 : 1.0); + xy = float2(cos(theta), sin(theta)); + + if (isCap) { + // A special multiplier factor for turning 3-point rounds into square caps (but leave the + // y == 0.0 point unaffected) + if (xy.y > 0.001) xy *= _capScale; + } + } else { + // Miter joins + yBasis = bIsHairpin ? float2(0) : miter; + xy.y = isBevel ? 1.0 : 1.0 / m2; + } + } else { + // Offset the center vertex position to get bevel SDF correct + if (isBevel && !roundOrCap) { + xy.y = -1.0 + sqrt((1.0 + cosB) * 0.5); + } + } + } + + // Point offset from main vertex position + float2 dP = float2x2(xBasis, yBasis) * xy; + + // The varying generation code handles clamping, if needed + + out.position = pB; + out.position.xy += width * dP; + out.position *= pw; + out.position = transform * out.position; + + return out; +} + +constant static float2 quadVertices[6] = { + float2(0.0, 0.0), + float2(1.0, 0.0), + float2(0.0, 1.0), + float2(1.0, 0.0), + float2(0.0, 1.0), + float2(1.0, 1.0) +}; + +struct MetalEngineRectangle { + float2 origin; + float2 size; +}; + +struct MetalEngineQuadVertexOut { + float4 position [[position]]; + float2 uv; +}; + +vertex MetalEngineQuadVertexOut blitVertex( + const device MetalEngineRectangle &rect [[ buffer(0) ]], + unsigned int vid [[ vertex_id ]] +) { + float2 quadVertex = quadVertices[vid]; + + MetalEngineQuadVertexOut out; + + out.position = float4(rect.origin.x + quadVertex.x * rect.size.x, rect.origin.y + quadVertex.y * rect.size.y, 0.0, 1.0); + out.position.x = -1.0 + out.position.x * 2.0; + out.position.y = -1.0 + out.position.y * 2.0; + + out.uv = float2(quadVertex.x, 1.0 - quadVertex.y); + + return out; +} + +fragment half4 blitFragment( + MetalEngineQuadVertexOut in [[stage_in]], + texture2d texture [[ texture(0) ]] +) { + constexpr sampler sampler(coord::normalized, address::repeat, filter::linear); + half4 color = texture.sample(sampler, in.uv); + + return half4(color.r, color.g, color.b, color.a); +} diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift index bc27655510..f9698f2615 100644 --- a/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift @@ -7,17 +7,566 @@ import AnimatedStickerNode import MetalEngine import LottieCpp import GZip +import MetalKit +import HierarchyTrackingLayer + +private final class BundleMarker: NSObject { +} + +private var metalLibraryValue: MTLLibrary? +func metalLibrary(device: MTLDevice) -> MTLLibrary? { + if let metalLibraryValue { + return metalLibraryValue + } + + let mainBundle = Bundle(for: BundleMarker.self) + guard let path = mainBundle.path(forResource: "LottieMetalSourcesBundle", ofType: "bundle") else { + return nil + } + guard let bundle = Bundle(path: path) else { + return nil + } + guard let library = try? device.makeDefaultLibrary(bundle: bundle) else { + return nil + } + + metalLibraryValue = library + return library +} + +private func generateTexture(device: MTLDevice, sideSize: Int, msaaSampleCount: Int) -> MTLTexture { + let textureDescriptor = MTLTextureDescriptor() + textureDescriptor.sampleCount = msaaSampleCount + if msaaSampleCount == 1 { + textureDescriptor.textureType = .type2D + } else { + textureDescriptor.textureType = .type2DMultisample + } + textureDescriptor.width = sideSize + textureDescriptor.height = sideSize + textureDescriptor.pixelFormat = .bgra8Unorm + //textureDescriptor.storageMode = .memoryless + textureDescriptor.storageMode = .private + textureDescriptor.usage = [.renderTarget, .shaderRead] + + return device.makeTexture(descriptor: textureDescriptor)! +} + +final class LottieContentLayer: MetalEngineSubjectLayer, MetalEngineSubject { + private var animationContainer: LottieAnimationContainer? + var frameIndex: Int = 0 + + var internalData: MetalEngineSubjectInternalData? + + private var renderBufferHeap: MTLHeap? + private var offscreenHeap: MTLHeap? + + private var multisampleTextureQueue: [MTLTexture] = [] + + private let currentBezierIndicesBuffer = PathRenderBuffer() + private let currentBuffer = PathRenderBuffer() + + final class PrepareState: ComputeState { + let pathRenderContext: PathRenderContext + + init?(device: MTLDevice) { + guard let pathRenderContext = PathRenderContext(device: device, msaaSampleCount: 1) else { + return nil + } + self.pathRenderContext = pathRenderContext + } + } + + final class RenderState: RenderToLayerState { + let pipelineState: MTLRenderPipelineState + + required init?(device: MTLDevice) { + guard let library = metalLibrary(device: device) else { + return nil + } + guard let vertexFunction = library.makeFunction(name: "blitVertex"), let fragmentFunction = library.makeFunction(name: "blitFragment") else { + return nil + } + + let pipelineDescriptor = MTLRenderPipelineDescriptor() + pipelineDescriptor.vertexFunction = vertexFunction + pipelineDescriptor.fragmentFunction = fragmentFunction + pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + guard let pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineDescriptor) else { + return nil + } + self.pipelineState = pipelineState + } + } + + init(animationContainer: LottieAnimationContainer) { + self.animationContainer = animationContainer + + #if DEBUG && false + let startTime = CFAbsoluteTimeGetCurrent() + let buffer = WriteBuffer() + for i in 0 ..< animationContainer.animation.frameCount { + animationContainer.update(i) + serializeNode(buffer: buffer, node: animationContainer.getCurrentRenderTree(for: CGSize(width: 512.0, height: 512.0))) + } + buffer.trim() + let deltaTime = (CFAbsoluteTimeGetCurrent() - startTime) + let zippedData = TGGZipData(buffer.data, 1.0) + print("Serialized in \(deltaTime * 1000.0) size: \(zippedData.count / (1 * 1024 * 1024)) MB") + #endif + + super.init() + + self.isOpaque = false + } + + override init(layer: Any) { + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func fillPath(frameState: PathFrameState, path: LottiePath, shading: PathShading, rule: LottieFillRule, transform: CATransform3D) { + let fillState = PathRenderFillState(buffer: self.currentBuffer, bezierDataBuffer: self.currentBezierIndicesBuffer, fillRule: rule, shading: shading, transform: transform) + + path.enumerateItems { pathItem in + switch pathItem.pointee.type { + case .moveTo: + let point = pathItem.pointee.points.0 + fillState.begin(point: SIMD2(Float(point.x), Float(point.y))) + case .lineTo: + let point = pathItem.pointee.points.0 + fillState.addLine(to: SIMD2(Float(point.x), Float(point.y))) + case .curveTo: + let cp1 = pathItem.pointee.points.0 + let cp2 = pathItem.pointee.points.1 + let point = pathItem.pointee.points.2 + + fillState.addCurve( + to: SIMD2(Float(point.x), Float(point.y)), + cp1: SIMD2(Float(cp1.x), Float(cp1.y)), + cp2: SIMD2(Float(cp2.x), Float(cp2.y)) + ) + case .close: + fillState.close() + @unknown default: + break + } + } + + fillState.close() + + frameState.add(fill: fillState) + } + + private func strokePath(frameState: PathFrameState, path: LottiePath, width: CGFloat, join: CGLineJoin, cap: CGLineCap, miterLimit: CGFloat, color: LottieColor, transform: CATransform3D) { + let strokeState = PathRenderStrokeState(buffer: self.currentBuffer, bezierDataBuffer: self.currentBezierIndicesBuffer, lineWidth: Float(width), lineJoin: join, lineCap: cap, miterLimit: Float(miterLimit), color: color, transform: transform) + + path.enumerateItems { pathItem in + switch pathItem.pointee.type { + case .moveTo: + let point = pathItem.pointee.points.0 + strokeState.begin(point: SIMD2(Float(point.x), Float(point.y))) + case .lineTo: + let point = pathItem.pointee.points.0 + strokeState.addLine(to: SIMD2(Float(point.x), Float(point.y))) + case .curveTo: + let cp1 = pathItem.pointee.points.0 + let cp2 = pathItem.pointee.points.1 + let point = pathItem.pointee.points.2 + + strokeState.addCurve( + to: SIMD2(Float(point.x), Float(point.y)), + cp1: SIMD2(Float(cp1.x), Float(cp1.y)), + cp2: SIMD2(Float(cp2.x), Float(cp2.y)) + ) + case .close: + strokeState.close() + @unknown default: + break + } + } + + strokeState.complete() + + frameState.add(stroke: strokeState) + } + + func update(context: MetalEngineSubjectContext) { + if self.bounds.isEmpty { + return + } + + let size = CGSize(width: 800.0, height: 800.0) + let msaaSampleCount = 1 + + let renderSpec = RenderLayerSpec(size: RenderSize(width: Int(size.width), height: Int(size.height))) + + guard let animationContainer = self.animationContainer else { + return + } + animationContainer.update(self.frameIndex) + + func defaultTransformForSize(_ size: CGSize) -> CATransform3D { + var transform = CATransform3DIdentity + transform = CATransform3DScale(transform, 2.0 / size.width, 2.0 / size.height, 1.0) + transform = CATransform3DTranslate(transform, -size.width * 0.5, -size.height * 0.5, 0.0) + transform = CATransform3DTranslate(transform, 0.0, size.height, 0.0) + transform = CATransform3DScale(transform, 1.0, -1.0, 1.0) + + return transform + } + + let canvasSize = size + var transform = defaultTransformForSize(canvasSize) + + concat(CATransform3DMakeScale(canvasSize.width / animationContainer.animation.size.width, canvasSize.height / animationContainer.animation.size.height, 1.0)) + + var transformStack: [CATransform3D] = [] + + func saveState() { + transformStack.append(transform) + } + + func restoreState() { + transform = transformStack.removeLast() + } + + func concat(_ other: CATransform3D) { + transform = CATransform3DConcat(other, transform) + } + + func renderNodeContent(frameState: PathFrameState, item: LottieRenderContent, alpha: Double) { + if let fill = item.fill { + if let solidShading = fill.shading as? LottieRenderContentSolidShading { + self.fillPath( + frameState: frameState, + path: item.path, + shading: .color(LottieColor(r: solidShading.color.r, g: solidShading.color.g, b: solidShading.color.b, a: solidShading.color.a * solidShading.opacity * alpha)), + rule: fill.fillRule, + transform: transform + ) + } else if let gradientShading = fill.shading as? LottieRenderContentGradientShading { + let gradientType: PathShading.Gradient.GradientType + switch gradientShading.gradientType { + case .linear: + gradientType = .linear + case .radial: + gradientType = .radial + @unknown default: + gradientType = .linear + } + var colorStops: [PathShading.Gradient.ColorStop] = [] + for colorStop in gradientShading.colorStops { + colorStops.append(PathShading.Gradient.ColorStop( + color: LottieColor(r: colorStop.color.r, g: colorStop.color.g, b: colorStop.color.b, a: colorStop.color.a * gradientShading.opacity * alpha), + location: Float(colorStop.location) + )) + } + let gradientShading = PathShading.Gradient( + gradientType: gradientType, + colorStops: colorStops, + start: SIMD2(Float(gradientShading.start.x), Float(gradientShading.start.y)), + end: SIMD2(Float(gradientShading.end.x), Float(gradientShading.end.y)) + ) + self.fillPath( + frameState: frameState, + path: item.path, + shading: .gradient(gradientShading), + rule: fill.fillRule, + transform: transform + ) + } + } else if let stroke = item.stroke { + if let solidShading = stroke.shading as? LottieRenderContentSolidShading { + let color = solidShading.color + strokePath( + frameState: frameState, + path: item.path, + width: stroke.lineWidth, + join: stroke.lineJoin, + cap: stroke.lineCap, + miterLimit: stroke.miterLimit, + color: LottieColor(r: color.r, g: color.g, b: color.b, a: color.a * solidShading.opacity * alpha), + transform: transform + ) + } + } + } + + func renderNode(frameState: PathFrameState, node: LottieRenderNode, globalSize: CGSize, parentAlpha: CGFloat) { + let normalizedOpacity = node.opacity + let layerAlpha = normalizedOpacity * parentAlpha + + if node.isHidden || normalizedOpacity == 0.0 { + return + } + + saveState() + + var needsTempContext = false + if node.mask != nil { + needsTempContext = true + } else { + needsTempContext = (layerAlpha != 1.0 && !node.hasSimpleContents) || node.masksToBounds + } + + var maskSurface: PathFrameState.MaskSurface? + + if needsTempContext { + if node.mask != nil || node.masksToBounds { + var maskMode: PathFrameState.MaskSurface.Mode = .regular + + frameState.pushOffscreen(width: Int(node.globalRect.width), height: Int(node.globalRect.height)) + saveState() + + transform = defaultTransformForSize(node.globalRect.size) + concat(CATransform3DMakeTranslation(-node.globalRect.minX, -node.globalRect.minY, 0.0)) + concat(node.globalTransform) + + if node.masksToBounds { + let fillState = PathRenderFillState(buffer: self.currentBuffer, bezierDataBuffer: self.currentBezierIndicesBuffer, fillRule: .evenOdd, shading: .color(.init(r: 1.0, g: 1.0, b: 1.0, a: 1.0)), transform: transform) + + fillState.begin(point: SIMD2(Float(node.bounds.minX), Float(node.bounds.minY))) + fillState.addLine(to: SIMD2(Float(node.bounds.minX), Float(node.bounds.maxY))) + fillState.addLine(to: SIMD2(Float(node.bounds.maxX), Float(node.bounds.maxY))) + fillState.addLine(to: SIMD2(Float(node.bounds.maxX), Float(node.bounds.minY))) + fillState.close() + + frameState.add(fill: fillState) + } + if let maskNode = node.mask { + if maskNode.isInvertedMatte { + maskMode = .inverse + } + renderNode(frameState: frameState, node: maskNode, globalSize: globalSize, parentAlpha: 1.0) + } + + restoreState() + + maskSurface = frameState.popOffscreenMask(mode: maskMode) + } + + frameState.pushOffscreen(width: Int(node.globalRect.width), height: Int(node.globalRect.height)) + saveState() + + transform = defaultTransformForSize(node.globalRect.size) + concat(CATransform3DMakeTranslation(-node.globalRect.minX, -node.globalRect.minY, 0.0)) + concat(node.globalTransform) + } else { + concat(CATransform3DMakeTranslation(node.position.x, node.position.y, 0.0)) + concat(CATransform3DMakeTranslation(-node.bounds.origin.x, -node.bounds.origin.y, 0.0)) + concat(node.transform) + } + + var renderAlpha: CGFloat = 1.0 + if needsTempContext { + renderAlpha = 1.0 + } else { + renderAlpha = layerAlpha + } + + if let renderContent = node.renderContent { + renderNodeContent(frameState: frameState, item: renderContent, alpha: renderAlpha) + } + + for subnode in node.subnodes { + renderNode(frameState: frameState, node: subnode, globalSize: globalSize, parentAlpha: renderAlpha) + } + + if needsTempContext { + restoreState() + + concat(CATransform3DMakeTranslation(node.position.x, node.position.y, 0.0)) + concat(CATransform3DMakeTranslation(-node.bounds.origin.x, -node.bounds.origin.y, 0.0)) + concat(node.transform) + concat(CATransform3DInvert(node.globalTransform)) + + frameState.popOffscreen(rect: node.globalRect, transform: transform, opacity: Float(layerAlpha), mask: maskSurface) + } + + restoreState() + } + + self.currentBuffer.reset() + self.currentBezierIndicesBuffer.reset() + let frameState = PathFrameState(width: Int(size.width), height: Int(size.height), msaaSampleCount: 1, buffer: self.currentBuffer, bezierDataBuffer: self.currentBezierIndicesBuffer) + + let node = animationContainer.getCurrentRenderTree(for: CGSize(width: 512.0, height: 512.0)) + renderNode(frameState: frameState, node: node, globalSize: canvasSize, parentAlpha: 1.0) + + final class ComputeOutput { + let pathRenderContext: PathRenderContext + let renderBufferHeap: MTLHeap + let multisampleTexture: MTLTexture + let takenMultisampleTextures: [MTLTexture] + + init(pathRenderContext: PathRenderContext, renderBufferHeap: MTLHeap, multisampleTexture: MTLTexture, takenMultisampleTextures: [MTLTexture]) { + self.pathRenderContext = pathRenderContext + self.renderBufferHeap = renderBufferHeap + self.multisampleTexture = multisampleTexture + self.takenMultisampleTextures = takenMultisampleTextures + } + } + + var customCompletion: (() -> Void)? + + let computeOutput = context.compute(state: PrepareState.self, commands: { commandBuffer, state -> ComputeOutput? in + let renderBufferHeap: MTLHeap + if let current = self.renderBufferHeap { + renderBufferHeap = current + } else { + let heapDescriptor = MTLHeapDescriptor() + heapDescriptor.size = 32 * 1024 * 1024 + heapDescriptor.storageMode = .shared + heapDescriptor.cpuCacheMode = .writeCombined + if #available(iOS 13.0, *) { + heapDescriptor.hazardTrackingMode = .tracked + } + guard let value = MetalEngine.shared.device.makeHeap(descriptor: heapDescriptor) else { + print() + return nil + } + self.renderBufferHeap = value + renderBufferHeap = value + } + + guard let computeEncoder = commandBuffer.makeComputeCommandEncoder() else { + return nil + } + + frameState.prepare(heap: renderBufferHeap) + frameState.encodeCompute(context: state.pathRenderContext, computeEncoder: computeEncoder) + + computeEncoder.endEncoding() + + let multisampleTexture: MTLTexture + if !self.multisampleTextureQueue.isEmpty { + multisampleTexture = self.multisampleTextureQueue.removeFirst() + } else { + multisampleTexture = generateTexture(device: MetalEngine.shared.device, sideSize: Int(size.width), msaaSampleCount: 1) + } + + let tempTexture: MTLTexture + if !self.multisampleTextureQueue.isEmpty { + tempTexture = self.multisampleTextureQueue.removeFirst() + } else { + tempTexture = generateTexture(device: MetalEngine.shared.device, sideSize: Int(size.width), msaaSampleCount: 1) + } + + let renderPassDescriptor = MTLRenderPassDescriptor() + renderPassDescriptor.colorAttachments[0].texture = multisampleTexture + if msaaSampleCount == 1 { + renderPassDescriptor.colorAttachments[0].storeAction = .store + } else { + //renderPassDescriptor.colorAttachments[0].resolveTexture = self.currentDrawable?.texture + renderPassDescriptor.colorAttachments[0].storeAction = .multisampleResolve + preconditionFailure() + } + renderPassDescriptor.colorAttachments[0].loadAction = .clear + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) + + renderPassDescriptor.colorAttachments[1].texture = tempTexture + renderPassDescriptor.colorAttachments[1].loadAction = .clear + renderPassDescriptor.colorAttachments[1].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) + renderPassDescriptor.colorAttachments[1].storeAction = .dontCare + + if msaaSampleCount == 4 { + renderPassDescriptor.setSamplePositions([ + MTLSamplePosition(x: 0.25, y: 0.25), + MTLSamplePosition(x: 0.75, y: 0.25), + MTLSamplePosition(x: 0.75, y: 0.75), + MTLSamplePosition(x: 0.25, y: 0.75) + ]) + } + + var offscreenHeapMemorySize = frameState.calculateOffscreenHeapMemorySize(device: MetalEngine.shared.device) + offscreenHeapMemorySize = max(offscreenHeapMemorySize, 1 * 1024 * 1024) + + let offscreenHeap: MTLHeap + if let current = self.offscreenHeap, current.size >= offscreenHeapMemorySize * 3 { + offscreenHeap = current + } else { + print("Creating offscreen heap \(offscreenHeapMemorySize * 3 / (1024 * 1024)) MB (3 * \(offscreenHeapMemorySize / (1024 * 1024)) MB)") + let heapDescriptor = MTLHeapDescriptor() + heapDescriptor.size = offscreenHeapMemorySize * 3 + heapDescriptor.storageMode = .private + heapDescriptor.cpuCacheMode = .defaultCache + if #available(iOS 13.0, *) { + heapDescriptor.hazardTrackingMode = .tracked + } + offscreenHeap = MetalEngine.shared.device.makeHeap(descriptor: heapDescriptor)! + self.offscreenHeap = offscreenHeap + } + + frameState.encodeOffscreen(context: state.pathRenderContext, heap: offscreenHeap, commandBuffer: commandBuffer, canvasSize: canvasSize) + + guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { + self.multisampleTextureQueue.append(multisampleTexture) + self.multisampleTextureQueue.append(tempTexture) + return nil + } + + frameState.encodeRender(context: state.pathRenderContext, encoder: renderEncoder, canvasSize: canvasSize) + + renderEncoder.endEncoding() + + return ComputeOutput( + pathRenderContext: state.pathRenderContext, + renderBufferHeap: renderBufferHeap, + multisampleTexture: multisampleTexture, + takenMultisampleTextures: [multisampleTexture, tempTexture] + ) + }) + + context.renderToLayer(spec: renderSpec, state: RenderState.self, layer: self, inputs: computeOutput, commands: { [weak self] encoder, placement, computeOutput in + guard let computeOutput else { + return + } + + let effectiveRect = placement.effectiveRect + + var rect = SIMD4(Float(effectiveRect.minX), Float(effectiveRect.minY), Float(effectiveRect.width), Float(effectiveRect.height)) + encoder.setVertexBytes(&rect, length: 4 * 4, index: 0) + + encoder.setFragmentTexture(computeOutput.multisampleTexture, index: 0) + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6) + + let takenMultisampleTextures = computeOutput.takenMultisampleTextures + customCompletion = { + guard let self else { + return + } + for texture in takenMultisampleTextures { + self.multisampleTextureQueue.append(texture) + } + } + }) + + context.addCustomCompletion({ + customCompletion?() + }) + } +} public final class LottieMetalAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode { private final class LoadFrameTask { var isCancelled: Bool = false } + private let hierarchyTrackingLayer: HierarchyTrackingLayer + public var automaticallyLoadFirstFrame: Bool = false public var automaticallyLoadLastFrame: Bool = false public var playToCompletionOnStop: Bool = false + private var layoutSize: CGSize? private var lottieInstance: LottieAnimationContainer? + private var renderLayer: LottieContentLayer? + + private var displayLinkSubscription: SharedDisplayLinkDriver.Link? private var didStart: Bool = false public var started: () -> Void = {} @@ -73,12 +622,26 @@ public final class LottieMetalAnimatedStickerNode: ASDisplayNode, AnimatedSticke private var playbackMode: AnimatedStickerPlaybackMode = .loop override public init() { + self.hierarchyTrackingLayer = HierarchyTrackingLayer() + super.init() - self.backgroundColor = .blue + self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in + guard let self else { + return + } + self.updatePlayback() + } + self.hierarchyTrackingLayer.didExitHierarchy = { [weak self] in + guard let self else { + return + } + self.updatePlayback() + } } deinit { + self.sourceDisposable?.dispose() } public func cloneCurrentFrame(from otherNode: AnimatedStickerNode?) { @@ -124,6 +687,55 @@ public final class LottieMetalAnimatedStickerNode: ASDisplayNode, AnimatedSticke self.isPlaying = isPlaying self.isPlayingChanged(self.isPlaying) } + + if isPlaying, let lottieInstance = self.lottieInstance { + if self.displayLinkSubscription == nil { + let fps: Int + if lottieInstance.animation.framesPerSecond == 30 { + fps = 30 + } else { + fps = 60 + } + self.displayLinkSubscription = SharedDisplayLinkDriver.shared.add(framesPerSecond: .fps(fps), { [weak self] deltaTime in + guard let self, let lottieInstance = self.lottieInstance, let renderLayer = self.renderLayer else { + return + } + if renderLayer.frameIndex == lottieInstance.animation.frameCount - 1 { + switch self.playbackMode { + case .loop: + self.completed(false) + case let .count(count): + if count <= 1 { + if !self.didComplete { + self.didComplete = true + self.completed(true) + } + return + } else { + self.playbackMode = .count(count - 1) + self.completed(false) + } + case .once: + if !self.didComplete { + self.didComplete = true + self.completed(true) + } + return + case .still: + break + } + } + + self.frameIndex = (self.frameIndex + 1) % lottieInstance.animation.frameCount + renderLayer.frameIndex = self.frameIndex + renderLayer.setNeedsUpdate() + }) + + self.renderLayer?.setNeedsUpdate() + } + } else { + self.displayLinkSubscription = nil + } } private func advanceFrameIfPossible() { @@ -173,6 +785,13 @@ public final class LottieMetalAnimatedStickerNode: ASDisplayNode, AnimatedSticke private func setupPlayback(lottieInstance: LottieAnimationContainer) { self.lottieInstance = lottieInstance + let renderLayer = LottieContentLayer(animationContainer: lottieInstance) + self.renderLayer = renderLayer + if let layoutSize = self.layoutSize { + renderLayer.frame = CGRect(origin: CGPoint(), size: layoutSize) + } + self.layer.addSublayer(renderLayer) + self.updatePlayback() } @@ -205,6 +824,10 @@ public final class LottieMetalAnimatedStickerNode: ASDisplayNode, AnimatedSticke } public func updateLayout(size: CGSize) { + self.layoutSize = size + if let renderLayer = self.renderLayer { + renderLayer.frame = CGRect(origin: CGPoint(), size: size) + } } public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) { diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathFrameState.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathFrameState.swift new file mode 100644 index 0000000000..9013fff1da --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathFrameState.swift @@ -0,0 +1,374 @@ +import Foundation +import MetalKit +import LottieCpp + +private func alignUp(size: Int, align: Int) -> Int { + precondition(((align - 1) & align) == 0, "Align must be a power of two") + + let alignmentMask = align - 1 + return (size + alignmentMask) & ~alignmentMask +} + +final class PathFrameState { + struct RenderItem { + enum Content { + case fill(PathRenderFillState) + case stroke(PathRenderStrokeState) + case offscreen(surface: Surface, rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface?) + + func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, buffer: MTLBuffer, canvasSize: CGSize) { + switch self { + case let .fill(fill): + fill.encode(context: context, encoder: encoder, buffer: buffer) + case let .stroke(stroke): + stroke.encode(context: context, encoder: encoder, buffer: buffer) + case let .offscreen(surface, rect, transform, opacity, mask): + surface.encode(context: context, encoder: encoder, canvasSize: canvasSize, rect: rect, transform: transform, opacity: opacity, mask: mask) + } + } + } + + let content: Content + + init(content: Content) { + self.content = content + } + } + + final class MaskSurface { + enum Mode { + case regular + case inverse + } + + let surface: Surface + let mode: Mode + + init(surface: Surface, mode: Mode) { + self.surface = surface + self.mode = mode + } + } + + final class Surface { + let width: Int + let height: Int + private let msaaSampleCount: Int + + private var texture: MTLTexture? + + private(set) var items: [RenderItem] = [] + + init(width: Int, height: Int, msaaSampleCount: Int) { + self.width = width + self.height = height + self.msaaSampleCount = msaaSampleCount + } + + func add(fill: PathRenderFillState) { + self.items.append(RenderItem(content: .fill(fill))) + } + + func add(stroke: PathRenderStrokeState) { + self.items.append(RenderItem(content: .stroke(stroke))) + } + + func add(surface: Surface, rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface?) { + self.items.append(RenderItem(content: .offscreen(surface: surface, rect: rect, transform: transform, opacity: opacity, mask: mask))) + } + + func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, canvasSize: CGSize, rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface?) { + guard let texture = self.texture else { + print("Trying to encode offscreen blit pass, but no texture is present") + return + } + if mask != nil { + encoder.setRenderPipelineState(context.drawOffscreenWithMaskPipelineState) + } else { + encoder.setRenderPipelineState(context.drawOffscreenPipelineState) + } + + let identityTransform = CATransform3DIdentity + var identityTransformMatrix = SIMD16( + Float(identityTransform.m11), Float(identityTransform.m12), Float(identityTransform.m13), Float(identityTransform.m14), + Float(identityTransform.m21), Float(identityTransform.m22), Float(identityTransform.m23), Float(identityTransform.m24), + Float(identityTransform.m31), Float(identityTransform.m32), Float(identityTransform.m33), Float(identityTransform.m34), + Float(identityTransform.m41), Float(identityTransform.m42), Float(identityTransform.m43), Float(identityTransform.m44) + ) + + let boundingBox = rect.applying(CATransform3DGetAffineTransform(transform)) + + var quadVertices: [SIMD4] = [ + SIMD4(Float(boundingBox.minX), Float(boundingBox.minY), 0.0, 0.0), + SIMD4(Float(boundingBox.maxX), Float(boundingBox.minY), 1.0, 0.0), + SIMD4(Float(boundingBox.minX), Float(boundingBox.maxY), 0.0, 1.0), + + SIMD4(Float(boundingBox.maxX), Float(boundingBox.minY), 1.0, 0.0), + SIMD4(Float(boundingBox.minX), Float(boundingBox.maxY), 0.0, 1.0), + SIMD4(Float(boundingBox.maxX), Float(boundingBox.maxY), 1.0, 1.0) + ] + + encoder.setVertexBytes(&quadVertices, length: MemoryLayout>.size * quadVertices.count, index: 0) + encoder.setVertexBytes(&identityTransformMatrix, length: 4 * 4 * 4, index: 1) + encoder.setFragmentTexture(texture, index: 0) + if let mask { + guard let maskTexture = mask.surface.texture else { + print("Trying to encode offscreen blit pass, but no mask texture is present") + return + } + encoder.setFragmentTexture(maskTexture, index: 1) + } + var opacity = opacity + encoder.setFragmentBytes(&opacity, length: 4, index: 1) + + if let mask { + var maskMode: UInt32 = mask.mode == .regular ? 0 : 1; + encoder.setFragmentBytes(&maskMode, length: 4, index: 2) + } + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: quadVertices.count) + } + + func offscreenTextureDescriptor() -> MTLTextureDescriptor { + let textureDescriptor = MTLTextureDescriptor() + textureDescriptor.textureType = .type2D + textureDescriptor.width = self.width + textureDescriptor.height = self.height + textureDescriptor.pixelFormat = .bgra8Unorm + textureDescriptor.storageMode = .private + textureDescriptor.usage = [.renderTarget, .shaderRead] + return textureDescriptor + } + + func offscreenTempTextureDescriptor() -> MTLTextureDescriptor { + let tempTextureDescriptor = MTLTextureDescriptor() + tempTextureDescriptor.sampleCount = self.msaaSampleCount + if self.msaaSampleCount == 1 { + tempTextureDescriptor.textureType = .type2D + } else { + tempTextureDescriptor.textureType = .type2DMultisample + } + tempTextureDescriptor.width = self.width + tempTextureDescriptor.height = self.height + tempTextureDescriptor.pixelFormat = .bgra8Unorm + tempTextureDescriptor.storageMode = .private + tempTextureDescriptor.usage = [.renderTarget, .shaderRead] + return tempTextureDescriptor + } + + func calculateOffscreenHeapMemorySize(device: MTLDevice) -> Int { + var result = 0 + + var sizeAndAlign = device.heapTextureSizeAndAlign(descriptor: self.offscreenTextureDescriptor()) + result += sizeAndAlign.size + + sizeAndAlign = device.heapTextureSizeAndAlign(descriptor: self.offscreenTempTextureDescriptor()) + result += sizeAndAlign.size * 2 + + for item in self.items { + if case let .offscreen(surface, _, _, _, mask) = item.content { + result += surface.calculateOffscreenHeapMemorySize(device: device) + if let mask { + result += mask.surface.calculateOffscreenHeapMemorySize(device: device) + } + } + } + return result + } + + func encodeOffscreen(context: PathRenderContext, heap: MTLHeap, commandBuffer: MTLCommandBuffer, materializedBuffer: MTLBuffer, canvasSize: CGSize) { + guard let resultTexture = heap.makeTexture(descriptor: self.offscreenTextureDescriptor()) else { + return + } + + for item in self.items { + if case let .offscreen(surface, _, _, _, mask) = item.content { + if let mask { + mask.surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize) + } + surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize) + } + } + + self.texture = resultTexture + + guard let offscreenTexture = heap.makeTexture(descriptor: self.offscreenTempTextureDescriptor()) else { + return + } + guard let tempTexture = heap.makeTexture(descriptor: self.offscreenTempTextureDescriptor()) else { + return + } + + let offscreenRenderPassDescriptor = MTLRenderPassDescriptor() + if msaaSampleCount == 1 { + offscreenRenderPassDescriptor.colorAttachments[0].texture = resultTexture + offscreenRenderPassDescriptor.colorAttachments[0].storeAction = .store + } else { + offscreenRenderPassDescriptor.colorAttachments[0].texture = offscreenTexture + offscreenRenderPassDescriptor.colorAttachments[0].storeAction = .multisampleResolve + offscreenRenderPassDescriptor.colorAttachments[0].resolveTexture = resultTexture + } + offscreenRenderPassDescriptor.colorAttachments[0].loadAction = .clear + offscreenRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) + + offscreenRenderPassDescriptor.colorAttachments[1].texture = tempTexture + offscreenRenderPassDescriptor.colorAttachments[1].loadAction = .clear + offscreenRenderPassDescriptor.colorAttachments[1].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) + offscreenRenderPassDescriptor.colorAttachments[1].storeAction = .dontCare + + if self.msaaSampleCount == 4 { + offscreenRenderPassDescriptor.setSamplePositions([ + MTLSamplePosition(x: 0.25, y: 0.25), + MTLSamplePosition(x: 0.75, y: 0.25), + MTLSamplePosition(x: 0.75, y: 0.75), + MTLSamplePosition(x: 0.25, y: 0.75) + ]) + } + + guard let offscreenRenderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: offscreenRenderPassDescriptor) else { + return + } + + for item in self.items { + item.content.encode(context: context, encoder: offscreenRenderEncoder, buffer: materializedBuffer, canvasSize: canvasSize) + } + + offscreenRenderEncoder.endEncoding() + } + } + + let msaaSampleCount: Int + let buffer: PathRenderBuffer + let bezierDataBuffer: PathRenderBuffer + + private var surfaceStack: [Surface] = [] + + private var materializedBuffer: MTLBuffer? + private var materializedBezierIndexBuffer: MTLBuffer? + + init(width: Int, height: Int, msaaSampleCount: Int, buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer) { + self.msaaSampleCount = msaaSampleCount + self.buffer = buffer + self.bezierDataBuffer = bezierDataBuffer + self.surfaceStack.append(Surface(width: width, height: height, msaaSampleCount: msaaSampleCount)) + } + + func pushOffscreen(width: Int, height: Int) { + self.surfaceStack.append(Surface(width: width, height: height, msaaSampleCount: self.msaaSampleCount)) + } + + func popOffscreen(rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface? = nil) { + self.surfaceStack[self.surfaceStack.count - 2].add(surface: self.surfaceStack[self.surfaceStack.count - 1], rect: rect, transform: transform, opacity: opacity, mask: mask) + self.surfaceStack.removeLast() + } + + func popOffscreenMask(mode: MaskSurface.Mode) -> MaskSurface { + return MaskSurface( + surface: self.surfaceStack.removeLast(), + mode: mode + ) + } + + func add(fill: PathRenderFillState) { + self.surfaceStack.last!.add(fill: fill) + } + + func add(stroke: PathRenderStrokeState) { + self.surfaceStack.last!.add(stroke: stroke) + } + + func prepare(heap: MTLHeap) { + if self.buffer.length == 0 { + return + } + + var bufferOptions: MTLResourceOptions = [.storageModeShared, .cpuCacheModeWriteCombined] + if #available(iOS 13.0, *) { + bufferOptions.insert(.hazardTrackingModeTracked) + } + + guard let materializedBuffer = heap.makeBuffer(length: self.buffer.length, options: bufferOptions) else { + print("Could not create materialized buffer") + return + } + materializedBuffer.label = "materializedBuffer" + self.materializedBuffer = materializedBuffer + + memcpy(materializedBuffer.contents(), self.buffer.memory, self.buffer.length) + + if self.bezierDataBuffer.length != 0 { + guard let materializedBezierIndexBuffer = heap.makeBuffer(length: self.bezierDataBuffer.length, options: bufferOptions) else { + print("Could not create materialized bezier index buffer") + return + } + self.materializedBezierIndexBuffer = materializedBezierIndexBuffer + materializedBezierIndexBuffer.label = "materializedBezierIndexBuffer" + + memcpy(materializedBezierIndexBuffer.contents(), self.bezierDataBuffer.memory, self.bezierDataBuffer.length) + } + } + + func calculateOffscreenHeapMemorySize(device: MTLDevice) -> Int { + var result = 0 + for item in self.surfaceStack[0].items { + if case let .offscreen(surface, _, _, _, mask) = item.content { + result += surface.calculateOffscreenHeapMemorySize(device: device) + if let mask { + result += mask.surface.calculateOffscreenHeapMemorySize(device: device) + } + } + } + return result + } + + func encodeOffscreen(context: PathRenderContext, heap: MTLHeap, commandBuffer: MTLCommandBuffer, canvasSize: CGSize) { + guard let materializedBuffer = self.materializedBuffer else { + return + } + + assert(self.surfaceStack.count == 1) + + for item in self.surfaceStack[0].items { + if case let .offscreen(surface, _, _, _, mask) = item.content { + if let mask { + mask.surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize) + } + surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize) + } + } + } + + func encodeRender(context: PathRenderContext, encoder: MTLRenderCommandEncoder, canvasSize: CGSize) { + guard let materializedBuffer = self.materializedBuffer else { + return + } + + assert(self.surfaceStack.count == 1) + + for item in self.surfaceStack[0].items { + item.content.encode(context: context, encoder: encoder, buffer: materializedBuffer, canvasSize: canvasSize) + } + } + + func encodeCompute(context: PathRenderContext, computeEncoder: MTLComputeCommandEncoder) { + guard let materializedBuffer = self.materializedBuffer, let materializedBezierIndexBuffer = self.materializedBezierIndexBuffer else { + return + } + + let itemSize = 4 + 4 * 4 * 2 + 4 + let itemCount = self.bezierDataBuffer.length / itemSize + + computeEncoder.setComputePipelineState(context.prepareBezierPipelineState) + + let threadGroupWidth = 16 + let threadGroupHeight = 8 + + computeEncoder.useResource(materializedBuffer, usage: .write) + + computeEncoder.setBuffer(materializedBezierIndexBuffer, offset: 0, index: 0) + computeEncoder.setBuffer(materializedBuffer, offset: 0, index: 1) + var itemCountSize: UInt32 = UInt32(itemCount) + computeEncoder.setBytes(&itemCountSize, length: 4, index: 2) + let dispatchSize = alignUp(size: itemCount, align: threadGroupWidth) + computeEncoder.dispatchThreadgroups(MTLSize(width: dispatchSize, height: 1, depth: 1), threadsPerThreadgroup: MTLSize(width: 1, height: threadGroupHeight, depth: 1)) + } +} diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderBuffer.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderBuffer.swift new file mode 100644 index 0000000000..3190d0aa62 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderBuffer.swift @@ -0,0 +1,77 @@ +import Foundation +import MetalKit +import LottieCpp + +final class PathRenderBuffer { + private(set) var memory: UnsafeMutableRawPointer + private(set) var capacity: Int = 8 * 1024 * 1024 + private(set) var length: Int = 0 + + init() { + self.memory = malloc(self.capacity)! + } + + func reset() { + self.length = 0 + } + + func append(bytes: UnsafeRawPointer, length: Int) { + assert(length % 4 == 0) + + if self.length + length > self.capacity { + self.capacity = self.capacity * 2 + preconditionFailure() + } + memcpy(self.memory.advanced(by: self.length), bytes, length) + self.length += length + } + + func appendZero(count: Int) { + if self.length + length > self.capacity { + self.capacity = self.capacity * 2 + preconditionFailure() + } + self.length += count + } + + func append(float: Float) { + var value: Float = float + self.append(bytes: &value, length: 4) + } + + func append(float2: SIMD2) { + var value: SIMD2 = float2 + self.append(bytes: &value, length: 4 * 2) + } + + func append(float3: SIMD3) { + var value = float3.x + self.append(bytes: &value, length: 4) + value = float3.y + self.append(bytes: &value, length: 4) + value = float3.z + self.append(bytes: &value, length: 4) + } + + func append(int: Int32) { + var value = int + self.append(bytes: &value, length: 4) + } + + func appendBezierData( + bufferOffset: Int, + start: SIMD2, + end: SIMD2, + cp1: SIMD2, + cp2: SIMD2, + offset: Float + ) { + self.append(int: Int32(bufferOffset)) + self.append(float2: start) + self.append(float2: end) + self.append(float2: cp1) + self.append(float2: cp2) + self.append(float: offset) + } +} + diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderContext.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderContext.swift new file mode 100644 index 0000000000..524dda3006 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderContext.swift @@ -0,0 +1,208 @@ +import Foundation +import MetalKit +import LottieCpp + +final class PathRenderContext { + let device: MTLDevice + let msaaSampleCount: Int + + let prepareBezierPipelineState: MTLComputePipelineState + let shapePipelineState: MTLRenderPipelineState + let clearPipelineState: MTLRenderPipelineState + let mergeColorFillPipelineState: MTLRenderPipelineState + let mergeLinearGradientFillPipelineState: MTLRenderPipelineState + let mergeRadialGradientFillPipelineState: MTLRenderPipelineState + let strokeTerminalPipelineState: MTLRenderPipelineState + let strokeInnerPipelineState: MTLRenderPipelineState + let drawOffscreenPipelineState: MTLRenderPipelineState + let drawOffscreenWithMaskPipelineState: MTLRenderPipelineState + + let maximumThreadGroupWidth: Int + + init?(device: MTLDevice, msaaSampleCount: Int) { + self.device = device + self.msaaSampleCount = msaaSampleCount + + self.maximumThreadGroupWidth = device.maxThreadsPerThreadgroup.width + + guard let library = metalLibrary(device: device) else { + return nil + } + + guard let quadVertexFunction = library.makeFunction(name: "quad_vertex_shader") else { + print("Unable to find vertex function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let shapeVertexFunction = library.makeFunction(name: "fill_vertex_shader") else { + print("Unable to find vertex function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let shapeFragmentFunction = library.makeFunction(name: "fragment_shader") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let clearFragmentFunction = library.makeFunction(name: "clear_mask_fragment") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let mergeColorFillFragmentFunction = library.makeFunction(name: "merge_color_fill_fragment_shader") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let mergeLinearGradientFillFragmentFunction = library.makeFunction(name: "merge_linear_gradient_fill_fragment_shader") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let mergeRadialGradientFillFragmentFunction = library.makeFunction(name: "merge_radial_gradient_fill_fragment_shader") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let strokeFragmentFunction = library.makeFunction(name: "stroke_fragment_shader") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let strokeTerminalVertexFunction = library.makeFunction(name: "strokeTerminalVertex") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let strokeInnerVertexFunction = library.makeFunction(name: "strokeInnerVertex") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let prepareBezierPipelineFunction = library.makeFunction(name: "evaluateBezier") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let quadOffscreenFragmentFunction = library.makeFunction(name: "quad_offscreen_fragment") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let quadOffscreenWithMaskFragmentFunction = library.makeFunction(name: "quad_offscreen_fragment_with_mask") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + + self.prepareBezierPipelineState = try! device.makeComputePipelineState(function: prepareBezierPipelineFunction) + + let shapePipelineDescriptor = MTLRenderPipelineDescriptor() + shapePipelineDescriptor.vertexFunction = shapeVertexFunction + shapePipelineDescriptor.fragmentFunction = shapeFragmentFunction + + shapePipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + shapePipelineDescriptor.colorAttachments[0].writeMask = [] + shapePipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm + shapePipelineDescriptor.colorAttachments[1].writeMask = [.all] + shapePipelineDescriptor.rasterSampleCount = msaaSampleCount + + guard let shapePipelineState = try? device.makeRenderPipelineState(descriptor: shapePipelineDescriptor) else { + preconditionFailure() + } + self.shapePipelineState = shapePipelineState + + let clearPipelineDescriptor = MTLRenderPipelineDescriptor() + clearPipelineDescriptor.vertexFunction = quadVertexFunction + clearPipelineDescriptor.fragmentFunction = clearFragmentFunction + + clearPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + clearPipelineDescriptor.colorAttachments[0].writeMask = [] + clearPipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm + clearPipelineDescriptor.colorAttachments[1].writeMask = .all + clearPipelineDescriptor.rasterSampleCount = msaaSampleCount + + guard let clearPipelineState = try? device.makeRenderPipelineState(descriptor: clearPipelineDescriptor) else { + preconditionFailure() + } + self.clearPipelineState = clearPipelineState + + let mergePipelineDescriptor = MTLRenderPipelineDescriptor() + mergePipelineDescriptor.vertexFunction = quadVertexFunction + mergePipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + mergePipelineDescriptor.colorAttachments[0].writeMask = [.all] + mergePipelineDescriptor.colorAttachments[0].isBlendingEnabled = true + mergePipelineDescriptor.colorAttachments[0].rgbBlendOperation = .add + mergePipelineDescriptor.colorAttachments[0].alphaBlendOperation = .add + mergePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = .one + mergePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .one + mergePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha + mergePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .one + mergePipelineDescriptor.rasterSampleCount = msaaSampleCount + + mergePipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm + mergePipelineDescriptor.colorAttachments[1].writeMask = [] + + mergePipelineDescriptor.fragmentFunction = mergeColorFillFragmentFunction + guard let mergeColorFillPipelineState = try? device.makeRenderPipelineState(descriptor: mergePipelineDescriptor) else { + preconditionFailure() + } + self.mergeColorFillPipelineState = mergeColorFillPipelineState + + mergePipelineDescriptor.fragmentFunction = mergeLinearGradientFillFragmentFunction + guard let mergeLinearGradientFillPipelineState = try? device.makeRenderPipelineState(descriptor: mergePipelineDescriptor) else { + preconditionFailure() + } + self.mergeLinearGradientFillPipelineState = mergeLinearGradientFillPipelineState + + mergePipelineDescriptor.fragmentFunction = mergeRadialGradientFillFragmentFunction + guard let mergeRadialGradientFillPipelineState = try? device.makeRenderPipelineState(descriptor: mergePipelineDescriptor) else { + preconditionFailure() + } + self.mergeRadialGradientFillPipelineState = mergeRadialGradientFillPipelineState + + let strokePipelineDescriptor = MTLRenderPipelineDescriptor() + strokePipelineDescriptor.fragmentFunction = strokeFragmentFunction + strokePipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + strokePipelineDescriptor.colorAttachments[0].writeMask = [.all] + strokePipelineDescriptor.colorAttachments[0].isBlendingEnabled = true + strokePipelineDescriptor.colorAttachments[0].rgbBlendOperation = mergePipelineDescriptor.colorAttachments[0].rgbBlendOperation + strokePipelineDescriptor.colorAttachments[0].alphaBlendOperation = mergePipelineDescriptor.colorAttachments[0].alphaBlendOperation + strokePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor + strokePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor + strokePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor + strokePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor + strokePipelineDescriptor.rasterSampleCount = msaaSampleCount + + strokePipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm + strokePipelineDescriptor.colorAttachments[1].writeMask = [] + strokePipelineDescriptor.vertexFunction = strokeTerminalVertexFunction + guard let strokeTerminalPipelineState = try? device.makeRenderPipelineState(descriptor: strokePipelineDescriptor) else { + preconditionFailure() + } + self.strokeTerminalPipelineState = strokeTerminalPipelineState + + strokePipelineDescriptor.vertexFunction = strokeInnerVertexFunction + guard let strokeInnerPipelineState = try? device.makeRenderPipelineState(descriptor: strokePipelineDescriptor) else { + preconditionFailure() + } + self.strokeInnerPipelineState = strokeInnerPipelineState + + let drawOffscreenPipelineDescriptor = MTLRenderPipelineDescriptor() + drawOffscreenPipelineDescriptor.vertexFunction = quadVertexFunction + + drawOffscreenPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + drawOffscreenPipelineDescriptor.colorAttachments[0].writeMask = [.all] + drawOffscreenPipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm + drawOffscreenPipelineDescriptor.colorAttachments[1].writeMask = [] + drawOffscreenPipelineDescriptor.rasterSampleCount = msaaSampleCount + + drawOffscreenPipelineDescriptor.colorAttachments[0].isBlendingEnabled = true + drawOffscreenPipelineDescriptor.colorAttachments[0].rgbBlendOperation = mergePipelineDescriptor.colorAttachments[0].rgbBlendOperation + drawOffscreenPipelineDescriptor.colorAttachments[0].alphaBlendOperation = mergePipelineDescriptor.colorAttachments[0].alphaBlendOperation + drawOffscreenPipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor + drawOffscreenPipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor + drawOffscreenPipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor + drawOffscreenPipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor + + drawOffscreenPipelineDescriptor.fragmentFunction = quadOffscreenFragmentFunction + guard let drawOffscreenPipelineState = try? device.makeRenderPipelineState(descriptor: drawOffscreenPipelineDescriptor) else { + preconditionFailure() + } + self.drawOffscreenPipelineState = drawOffscreenPipelineState + + drawOffscreenPipelineDescriptor.fragmentFunction = quadOffscreenWithMaskFragmentFunction + guard let drawOffscreenWithMaskPipelineState = try? device.makeRenderPipelineState(descriptor: drawOffscreenPipelineDescriptor) else { + preconditionFailure() + } + self.drawOffscreenWithMaskPipelineState = drawOffscreenWithMaskPipelineState + } +} + diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderFillState.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderFillState.swift new file mode 100644 index 0000000000..f7f22cdd56 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderFillState.swift @@ -0,0 +1,277 @@ +import Foundation +import MetalKit +import simd +import LottieCpp + +enum PathShading { + final class Gradient { + enum GradientType { + case linear + case radial + } + + struct ColorStop { + var color: LottieColor + var location: Float + + init(color: LottieColor, location: Float) { + self.color = color + self.location = location + } + } + + let gradientType: GradientType + let colorStops: [ColorStop] + let start: SIMD2 + let end: SIMD2 + + init(gradientType: GradientType, colorStops: [ColorStop], start: SIMD2, end: SIMD2) { + self.gradientType = gradientType + self.colorStops = colorStops + self.start = start + self.end = end + } + } + + case color(LottieColor) + case gradient(Gradient) +} + +final class PathRenderSubpathFillState { + private let buffer: PathRenderBuffer + private let bezierDataBuffer: PathRenderBuffer + let bufferOffset: Int + private(set) var vertexCount: Int = 0 + + private var firstPosition: SIMD2 + private var lastPosition: SIMD2 + + private(set) var minPosition: SIMD2 + private(set) var maxPosition: SIMD2 + + private var isClosed: Bool = false + + init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, point: SIMD2) { + self.buffer = buffer + self.bezierDataBuffer = bezierDataBuffer + self.bufferOffset = buffer.length + + self.firstPosition = point + self.lastPosition = point + self.minPosition = point + self.maxPosition = point + + self.add(point: point) + } + + func add(point: SIMD2) { + self.buffer.append(float2: point) + + self.minPosition.x = min(self.minPosition.x, point.x) + self.minPosition.y = min(self.minPosition.y, point.y) + self.maxPosition.x = max(self.maxPosition.x, point.x) + self.maxPosition.y = max(self.maxPosition.y, point.y) + + self.lastPosition = point + + self.vertexCount += 1 + } + + func addCurve(to point: SIMD2, cp1: SIMD2, cp2: SIMD2) { + let stepCount = 8 + self.bezierDataBuffer.appendBezierData( + bufferOffset: self.buffer.length / 4, + start: self.lastPosition, + end: point, + cp1: cp1, + cp2: cp2, + offset: 0.0 + ) + self.buffer.appendZero(count: 4 * 2 * stepCount) + self.vertexCount += stepCount + + let (curveMin, curveMax) = bezierBounds(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point) + + self.minPosition.x = min(self.minPosition.x, curveMin.x) + self.minPosition.y = min(self.minPosition.y, curveMin.y) + self.maxPosition.x = max(self.maxPosition.x, curveMax.x) + self.maxPosition.y = max(self.maxPosition.y, curveMax.y) + + self.lastPosition = point + } + + func close() { + if self.isClosed { + assert(false) + } else { + self.isClosed = true + + if self.lastPosition != self.firstPosition { + self.add(point: self.firstPosition) + } + } + } +} + +final class PathRenderFillState { + private let buffer: PathRenderBuffer + private let bezierDataBuffer: PathRenderBuffer + private let fillRule: LottieFillRule + private let shading: PathShading + private let transform: CATransform3D + + private var currentSubpath: PathRenderSubpathFillState? + private(set) var subpaths: [PathRenderSubpathFillState] = [] + + init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, fillRule: LottieFillRule, shading: PathShading, transform: CATransform3D) { + self.buffer = buffer + self.bezierDataBuffer = bezierDataBuffer + self.fillRule = fillRule + self.shading = shading + self.transform = transform + } + + func begin(point: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.close() + self.subpaths.append(currentSubpath) + self.currentSubpath = nil + } + + self.currentSubpath = PathRenderSubpathFillState(buffer: self.buffer, bezierDataBuffer: self.bezierDataBuffer, point: point) + } + + func addLine(to point: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.add(point: point) + } + } + + func addCurve(to point: SIMD2, cp1: SIMD2, cp2: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.addCurve(to: point, cp1: cp1, cp2: cp2) + } + } + + func close() { + if let currentSubpath = self.currentSubpath { + currentSubpath.close() + self.subpaths.append(currentSubpath) + self.currentSubpath = nil + } + } + + func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, buffer: MTLBuffer) { + if self.subpaths.isEmpty { + return + } + var minPosition: SIMD2 = self.subpaths[0].minPosition + var maxPosition: SIMD2 = self.subpaths[0].maxPosition + for subpath in self.subpaths { + minPosition.x = min(minPosition.x, subpath.minPosition.x) + minPosition.y = min(minPosition.y, subpath.minPosition.y) + maxPosition.x = max(maxPosition.x, subpath.maxPosition.x) + maxPosition.y = max(maxPosition.y, subpath.maxPosition.y) + } + + let localBoundingBox = CGRect(x: CGFloat(minPosition.x), y: CGFloat(minPosition.y), width: CGFloat(maxPosition.x - minPosition.x), height: CGFloat(maxPosition.y - minPosition.y)) + if localBoundingBox.isEmpty { + return + } + + var transformMatrix = simd_float4x4( + SIMD4(Float(transform.m11), Float(transform.m12), Float(transform.m13), Float(transform.m14)), + SIMD4(Float(transform.m21), Float(transform.m22), Float(transform.m23), Float(transform.m24)), + SIMD4(Float(transform.m31), Float(transform.m32), Float(transform.m33), Float(transform.m34)), + SIMD4(Float(transform.m41), Float(transform.m42), Float(transform.m43), Float(transform.m44)) + ) + + let identityTransform = CATransform3DIdentity + var identityTransformMatrix = SIMD16( + Float(identityTransform.m11), Float(identityTransform.m12), Float(identityTransform.m13), Float(identityTransform.m14), + Float(identityTransform.m21), Float(identityTransform.m22), Float(identityTransform.m23), Float(identityTransform.m24), + Float(identityTransform.m31), Float(identityTransform.m32), Float(identityTransform.m33), Float(identityTransform.m34), + Float(identityTransform.m41), Float(identityTransform.m42), Float(identityTransform.m43), Float(identityTransform.m44) + ) + + let transform = CATransform3DGetAffineTransform(self.transform) + let boundingBox = localBoundingBox.applying(transform) + let baseVertex = boundingBox.origin.applying(transform.inverted()) + + encoder.setRenderPipelineState(context.clearPipelineState) + + var quadVertices: [SIMD4] = [ + SIMD4(Float(boundingBox.minX), Float(boundingBox.minY), 0.0, 0.0), + SIMD4(Float(boundingBox.maxX), Float(boundingBox.minY), 1.0, 0.0), + SIMD4(Float(boundingBox.minX), Float(boundingBox.maxY), 0.0, 1.0), + + SIMD4(Float(boundingBox.maxX), Float(boundingBox.minY), 1.0, 0.0), + SIMD4(Float(boundingBox.minX), Float(boundingBox.maxY), 0.0, 1.0), + SIMD4(Float(boundingBox.maxX), Float(boundingBox.maxY), 1.0, 1.0) + ] + + encoder.setVertexBytes(&quadVertices, length: MemoryLayout>.size * quadVertices.count, index: 0) + encoder.setVertexBytes(&identityTransformMatrix, length: 4 * 4 * 4, index: 1) + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: quadVertices.count) + + encoder.setRenderPipelineState(context.shapePipelineState) + encoder.setVertexBytes(&transformMatrix, length: 4 * 4 * 4, index: 1) + var baseVertexData = SIMD2(Float(baseVertex.x), Float(baseVertex.y)) + encoder.setVertexBytes(&baseVertexData, length: 4 * 2, index: 2) + + var modeBytes: Int32 = self.fillRule == .winding ? 0 : 1 + encoder.setFragmentBytes(&modeBytes, length: 4, index: 1) + + for subpath in self.subpaths { + encoder.setVertexBuffer(buffer, offset: subpath.bufferOffset, index: 0) + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: (subpath.vertexCount - 1) * 3) + } + + encoder.setVertexBytes(&quadVertices, length: MemoryLayout>.size * quadVertices.count, index: 0) + encoder.setVertexBytes(&identityTransformMatrix, length: 4 * 4 * 4, index: 1) + + switch self.shading { + case let .color(color): + encoder.setRenderPipelineState(context.mergeColorFillPipelineState) + + var colorVector = SIMD4(Float(color.r), Float(color.g), Float(color.b), Float(color.a)) + encoder.setFragmentBytes(&colorVector, length: MemoryLayout>.size, index: 0) + case let .gradient(gradient): + switch gradient.gradientType { + case .linear: + encoder.setRenderPipelineState(context.mergeLinearGradientFillPipelineState) + case .radial: + encoder.setRenderPipelineState(context.mergeRadialGradientFillPipelineState) + } + + var modeBytes: Int32 = self.fillRule == .winding ? 0 : 1 + encoder.setFragmentBytes(&modeBytes, length: 4, index: 1) + + let colorStopSize = 4 * 4 + 4 + var colorStopsData = Data(count: colorStopSize * gradient.colorStops.count) + colorStopsData.withUnsafeMutableBytes { buffer in + let bytes = buffer.baseAddress!.assumingMemoryBound(to: Float.self) + for i in 0 ..< gradient.colorStops.count { + let colorStop = gradient.colorStops[i] + bytes[i * 5 + 0] = Float(colorStop.color.r) + bytes[i * 5 + 1] = Float(colorStop.color.g) + bytes[i * 5 + 2] = Float(colorStop.color.b) + bytes[i * 5 + 3] = Float(colorStop.color.a) + bytes[i * 5 + 4] = colorStop.location + } + encoder.setFragmentBytes(buffer.baseAddress!, length: buffer.count, index: 0) + } + + var numColorStops: UInt32 = UInt32(gradient.colorStops.count) + encoder.setFragmentBytes(&numColorStops, length: 4, index: 2) + + var startPosition = transformMatrix * SIMD4(gradient.start.x, gradient.start.y, 0.0, 1.0) + encoder.setFragmentBytes(&startPosition, length: 4 * 2, index: 3) + var endPosition = transformMatrix * SIMD4(gradient.end.x, gradient.end.y, 0.0, 1.0) + encoder.setFragmentBytes(&endPosition, length: 4 * 2, index: 4) + } + + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: quadVertices.count) + } +} + diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderStrokeState.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderStrokeState.swift new file mode 100644 index 0000000000..ce9accfe1f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderStrokeState.swift @@ -0,0 +1,425 @@ +import Foundation +import MetalKit +import LottieCpp + +func evaluateBezier(p0: SIMD2, p1: SIMD2, p2: SIMD2, p3: SIMD2, t: Float) -> SIMD2 { + let t2 = t * t + let t3 = t * t * t + + let A = (3 * t2 - 3 * t3) + let B = (3 * t3 - 6 * t2 + 3 * t) + let C = (3 * t2 - t3 - 3 * t + 1) + + let value = t3 * p3 + A * p2 + B * p1 + C * p0 + return value +} + +func evaluateBezier(p0: Float, p1: Float, p2: Float, p3: Float, t: Float) -> Float { + let oneMinusT = 1.0 - t + + let value = oneMinusT * oneMinusT * oneMinusT * p0 + 3.0 * t * oneMinusT * oneMinusT * p1 + 3.0 * t * t * oneMinusT * p2 + t * t * t * p3 + return value +} + +func solveQuadratic(p0: Float, p1: Float, p2: Float, p3: Float) -> (Float, Float) { + let i = p1 - p0 + let j = p2 - p1 + let k = p3 - p2 + + let a = (3 * i) - (6 * j) + (3 * k) + let b = (6 * j) - (6 * i) + let c = (3 * i) + + let sqrtPart = (b * b) - (4 * a * c) + let hasSolution = sqrtPart >= 0 + if !hasSolution { + return (.nan, .nan) + } + + let t1 = (-b + sqrt(sqrtPart)) / (2 * a) + let t2 = (-b - sqrt(sqrtPart)) / (2 * a) + + var s1: Float = .nan + var s2: Float = .nan + + if t1 >= 0.0 && t1 <= 1.0 { + s1 = evaluateBezier(p0: p0, p1: p1, p2: p2, p3: p3, t: t1) + } + + if t2 >= 0.0 && t2 <= 1.0 { + s2 = evaluateBezier(p0: p0, p1: p1, p2: p2, p3: p3, t: t2) + } + + return (s1, s2) +} + +func bezierBounds(p0: SIMD2, p1: SIMD2, p2: SIMD2, p3: SIMD2) -> (minPosition: SIMD2, maxPosition: SIMD2) { + let (solX1, solX2) = solveQuadratic(p0: p0.x, p1: p1.x, p2: p2.x, p3: p3.x) + let (solY1, solY2) = solveQuadratic(p0: p0.y, p1: p1.y, p2: p2.y, p3: p3.y) + + var minX = min(p0.x, p3.x) + var maxX = max(p0.x, p3.x) + + if !solX1.isNaN { + minX = min(minX, solX1) + maxX = max(maxX, solX1) + } + + if !solX2.isNaN { + minX = min(minX, solX2) + maxX = max(maxX, solX2) + } + + var minY = min(p0.y, p3.y) + var maxY = max(p0.y, p3.y) + + if !solY1.isNaN { + minY = min(minY, solY1) + maxY = max(maxY, solY1) + } + + if !solY2.isNaN { + minY = min(minY, solY2) + maxY = max(maxY, solY2) + } + + return (SIMD2(minX, minY), SIMD2(maxX, maxY)) +} + +final class PathRenderSubpathStrokeState { + struct TerminalState { + var bufferOffset: Int + var segmentCount: Int + } + + enum UnresolvedPosition { + case position(SIMD2) + case curve(p0: SIMD2, p1: SIMD2, p2: SIMD2, p3: SIMD2, t: Float) + + func resolve() -> SIMD2 { + switch self { + case let .position(value): + return value + case let .curve(p0, p1, p2, p3, t): + return evaluateBezier(p0: p0, p1: p1, p2: p2, p3: p3, t: t) + } + } + } + + private let buffer: PathRenderBuffer + private let bezierDataBuffer: PathRenderBuffer + let bufferOffset: Int + private(set) var vertexCount: Int = 0 + + private(set) var terminalState: TerminalState? + + private(set) var curveJoinVertexRanges: [Range] = [] + + private var firstPosition: SIMD2 + private var secondPosition: UnresolvedPosition + private var thirdPosition: UnresolvedPosition + + private var lastPosition: SIMD2 + private var lastMinus1Position: UnresolvedPosition + private var lastMinus2Position: UnresolvedPosition + + private(set) var isClosed: Bool = false + private(set) var isCompleted: Bool = false + + init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, point: SIMD2) { + self.buffer = buffer + self.bezierDataBuffer = bezierDataBuffer + self.bufferOffset = buffer.length + + self.firstPosition = point + self.secondPosition = .position(point) + self.thirdPosition = .position(point) + self.lastPosition = point + self.lastMinus1Position = .position(point) + self.lastMinus2Position = .position(point) + + self.add(point: point) + } + + func add(point: SIMD2) { + self.buffer.append(float2: point) + + self.lastMinus2Position = self.lastMinus1Position + self.lastMinus1Position = .position(self.lastPosition) + self.lastPosition = point + + self.vertexCount += 1 + if self.vertexCount == 2 { + self.secondPosition = .position(point) + } else if self.vertexCount == 3 { + self.thirdPosition = .position(point) + } + } + + func addCurve(to point: SIMD2, cp1: SIMD2, cp2: SIMD2) { + let stepCount = 8 + self.bezierDataBuffer.appendBezierData( + bufferOffset: self.buffer.length / 4, + start: self.lastPosition, + end: point, + cp1: cp1, + cp2: cp2, + offset: 0.0 + ) + self.buffer.appendZero(count: 4 * 2 * stepCount) + + if self.vertexCount == 1 { + self.secondPosition = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(1) / Float(stepCount)) + self.thirdPosition = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(2) / Float(stepCount)) + } + + self.vertexCount += stepCount + + self.lastMinus2Position = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(stepCount - 2) / Float(stepCount)) + self.lastMinus1Position = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(stepCount - 1) / Float(stepCount)) + self.lastPosition = point + } + + func close() { + if self.isClosed { + assert(false) + } else { + self.isClosed = true + + if self.lastPosition != self.firstPosition { + self.add(point: self.firstPosition) + } + } + } + + func complete() { + if self.isCompleted { + assert(false) + } else { + if self.isClosed { + if self.vertexCount >= 3 { + self.buffer.append(float2: self.secondPosition.resolve()) + self.buffer.append(float2: self.thirdPosition.resolve()) + self.vertexCount += 2 + } + } else { + if self.vertexCount == 2 { + let terminalBufferOffset = self.buffer.length + + let resolvedSecond = self.secondPosition.resolve() + self.buffer.append(float2: self.firstPosition) + self.buffer.append(float2: self.firstPosition * 0.5 + resolvedSecond * 0.5) + self.buffer.append(float2: resolvedSecond) + + self.buffer.append(float2: resolvedSecond) + self.buffer.append(float2: self.firstPosition * 0.5 + resolvedSecond * 0.5) + self.buffer.append(float2: self.firstPosition) + + self.terminalState = TerminalState(bufferOffset: terminalBufferOffset, segmentCount: 2) + } else if self.vertexCount >= 3 { + let terminalBufferOffset = self.buffer.length + + self.buffer.append(float2: self.firstPosition) + self.buffer.append(float2: self.secondPosition.resolve()) + self.buffer.append(float2: self.thirdPosition.resolve()) + + self.buffer.append(float2: self.lastPosition) + self.buffer.append(float2: self.lastMinus1Position.resolve()) + self.buffer.append(float2: self.lastMinus2Position.resolve()) + + self.terminalState = TerminalState(bufferOffset: terminalBufferOffset, segmentCount: 2) + } + } + } + } +} + +final class PathRenderStrokeState { + private let buffer: PathRenderBuffer + private let bezierDataBuffer: PathRenderBuffer + private let lineWidth: Float + private let lineJoin: CGLineJoin + private let lineCap: CGLineCap + private let miterLimit: Float + private let color: LottieColor + private let transform: CATransform3D + + private var currentSubpath: PathRenderSubpathStrokeState? + private(set) var subpaths: [PathRenderSubpathStrokeState] = [] + + init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, lineWidth: Float, lineJoin: CGLineJoin, lineCap: CGLineCap, miterLimit: Float, color: LottieColor, transform: CATransform3D) { + self.buffer = buffer + self.bezierDataBuffer = bezierDataBuffer + self.lineWidth = lineWidth + self.lineJoin = lineJoin + self.lineCap = lineCap + self.miterLimit = miterLimit + self.color = color + self.transform = transform + } + + func begin(point: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.complete() + self.subpaths.append(currentSubpath) + self.currentSubpath = nil + } + + self.currentSubpath = PathRenderSubpathStrokeState(buffer: self.buffer, bezierDataBuffer: self.bezierDataBuffer, point: point) + } + + func addLine(to point: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.add(point: point) + } + } + + func addCurve(to point: SIMD2, cp1: SIMD2, cp2: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.addCurve(to: point, cp1: cp1, cp2: cp2) + } + } + + func close() { + if let currentSubpath = self.currentSubpath { + currentSubpath.close() + currentSubpath.complete() + self.subpaths.append(currentSubpath) + self.currentSubpath = nil + } + } + + func complete() { + if let currentSubpath = self.currentSubpath { + currentSubpath.complete() + self.subpaths.append(currentSubpath) + self.currentSubpath = nil + } + } + + func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, buffer: MTLBuffer) { + if self.subpaths.isEmpty { + return + } + + encoder.setVertexBuffer(buffer, offset: 0, index: 0) + + var colorVector = SIMD4(Float(color.r), Float(color.g), Float(color.b), Float(color.a)) + encoder.setFragmentBytes(&colorVector, length: MemoryLayout>.size, index: 0) + + var transformMatrix = SIMD16( + Float(transform.m11), Float(transform.m12), Float(transform.m13), Float(transform.m14), + Float(transform.m21), Float(transform.m22), Float(transform.m23), Float(transform.m24), + Float(transform.m31), Float(transform.m32), Float(transform.m33), Float(transform.m34), + Float(transform.m41), Float(transform.m42), Float(transform.m43), Float(transform.m44) + ) + encoder.setVertexBytes(&transformMatrix, length: 4 * 4 * 4, index: 1) + + let capRes2: Float + switch self.lineCap { + case .butt: + capRes2 = 2.0 + case .square: + capRes2 = 6.0 + case .round: + capRes2 = 24.0 + @unknown default: + capRes2 = 2.0 + } + let joinRes2: Float = self.lineJoin == .round ? 16.0 : 2.0 + + func computeCount(isEndpoints: Bool, insertCaps: Bool) -> SIMD2 { + if insertCaps { + if isEndpoints { + return SIMD2(capRes2, max(capRes2, joinRes2)) + } else { + return SIMD2(max(capRes2, joinRes2), max(capRes2, joinRes2)) + } + } else { + if isEndpoints { + return SIMD2(capRes2, joinRes2) + } else { + return SIMD2(joinRes2, joinRes2) + } + } + } + + var hasTerminalStates = false + + for subpath in self.subpaths { + let segmentCount = subpath.vertexCount - 1 + if segmentCount <= 0 { + continue + } + + if subpath.vertexCount >= 4 { + encoder.setRenderPipelineState(context.strokeInnerPipelineState) + + encoder.setVertexBufferOffset(subpath.bufferOffset, index: 0) + + var vertCnt2 = computeCount(isEndpoints: false, insertCaps: false) + encoder.setVertexBytes(&vertCnt2, length: 4 * 2, index: 2) + + var capJoinRes2 = SIMD2(capRes2, joinRes2) + encoder.setVertexBytes(&capJoinRes2, length: 4 * 2, index: 3) + + var isRoundJoinValue: UInt32 = self.lineJoin == .round ? 1 : 0 + encoder.setVertexBytes(&isRoundJoinValue, length: 4, index: 4) + + var isRoundCapValue: UInt32 = self.lineCap == .round ? 1 : 0 + encoder.setVertexBytes(&isRoundCapValue, length: 4, index: 5) + + var miterLimitValue: Float = self.lineJoin == .miter ? self.miterLimit : 1.0 + encoder.setVertexBytes(&miterLimitValue, length: 4, index: 6) + + var lineWidthValue: Float = self.lineWidth * 0.5 + encoder.setVertexBytes(&lineWidthValue, length: 4, index: 7) + + let vertexCount = 6 + Int(vertCnt2.x) + Int(vertCnt2.y) + 2 + encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertexCount, instanceCount: subpath.vertexCount - 4 + 1, baseInstance: 0) + } + + if subpath.terminalState != nil { + hasTerminalStates = true + } + } + + if hasTerminalStates { + encoder.setRenderPipelineState(context.strokeTerminalPipelineState) + + for subpath in self.subpaths { + let segmentCount = subpath.vertexCount - 1 + if segmentCount <= 0 { + continue + } + + if !subpath.isClosed { + if let terminalState = subpath.terminalState { + encoder.setVertexBufferOffset(terminalState.bufferOffset, index: 0) + + var vertCnt2 = computeCount(isEndpoints: true, insertCaps: false) + encoder.setVertexBytes(&vertCnt2, length: 4 * 2, index: 2) + + var capJoinRes2 = SIMD2(capRes2, joinRes2) + encoder.setVertexBytes(&capJoinRes2, length: 4 * 2, index: 3) + + var isRoundJoinValue: UInt32 = self.lineJoin == .round ? 1 : 0 + encoder.setVertexBytes(&isRoundJoinValue, length: 4, index: 4) + + var isRoundCapValue: UInt32 = self.lineCap == .round ? 1 : 0 + encoder.setVertexBytes(&isRoundCapValue, length: 4, index: 5) + + var miterLimitValue: Float = self.lineJoin == .miter ? self.miterLimit : 1.0 + encoder.setVertexBytes(&miterLimitValue, length: 4, index: 6) + + var lineWidthValue: Float = self.lineWidth * 0.5 + encoder.setVertexBytes(&lineWidthValue, length: 4, index: 7) + + let vertexCount = 6 + Int(vertCnt2.x) + Int(vertCnt2.y) + 2 + encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertexCount, instanceCount: terminalState.segmentCount, baseInstance: 0) + } + } + } + } + } +} diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/RenderTreeSerialization.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/RenderTreeSerialization.swift new file mode 100644 index 0000000000..ee54cc3baa --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/RenderTreeSerialization.swift @@ -0,0 +1,485 @@ +import Foundation +import LottieCpp + +final class WriteBuffer { + private(set) var data: Data + private var capacity: Int + var length: Int + + init() { + self.capacity = 1024 + self.data = Data(count: self.capacity) + self.length = 0 + } + + func trim() { + self.data.count = self.length + self.capacity = self.data.count + } + + func write(bytes: UnsafeRawBufferPointer) { + if self.data.count < self.length + bytes.count { + self.data.count = self.data.count * 2 + } + self.data.withUnsafeMutableBytes { buffer -> Void in + memcpy(buffer.baseAddress!.advanced(by: self.length), bytes.baseAddress!, bytes.count) + } + self.length += bytes.count + } + + func write(uInt32 value: UInt32) { + var value = value + withUnsafeBytes(of: &value, { bytes in + self.write(bytes: bytes) + }) + } + + func write(uInt16 value: UInt16) { + var value = value + withUnsafeBytes(of: &value, { bytes in + self.write(bytes: bytes) + }) + } + + func write(uInt8 value: UInt8) { + var value = value + withUnsafeBytes(of: &value, { bytes in + self.write(bytes: bytes) + }) + } + + func write(float value: Float) { + var value = value + withUnsafeBytes(of: &value, { bytes in + self.write(bytes: bytes) + }) + } + + func write(point: CGPoint) { + self.write(float: Float(point.x)) + self.write(float: Float(point.y)) + } + + func write(size: CGSize) { + self.write(float: Float(size.width)) + self.write(float: Float(size.height)) + } + + func write(rect: CGRect) { + self.write(point: rect.origin) + self.write(size: rect.size) + } + + func write(transform: CATransform3D) { + self.write(float: Float(transform.m11)) + self.write(float: Float(transform.m12)) + self.write(float: Float(transform.m13)) + self.write(float: Float(transform.m14)) + self.write(float: Float(transform.m21)) + self.write(float: Float(transform.m22)) + self.write(float: Float(transform.m23)) + self.write(float: Float(transform.m24)) + self.write(float: Float(transform.m31)) + self.write(float: Float(transform.m32)) + self.write(float: Float(transform.m33)) + self.write(float: Float(transform.m34)) + self.write(float: Float(transform.m41)) + self.write(float: Float(transform.m42)) + self.write(float: Float(transform.m43)) + self.write(float: Float(transform.m44)) + } +} + +final class ReadBuffer { + private let data: Data + private var offset: Int + + init(data: Data) { + self.data = data + self.offset = 0 + } + + func read(bytes: UnsafeMutableRawBufferPointer) { + if self.offset + bytes.count <= self.data.count { + self.data.withUnsafeBytes { buffer -> Void in + memcpy(bytes.baseAddress!, buffer.baseAddress!.advanced(by: self.offset), bytes.count) + } + self.offset += bytes.count + } else { + preconditionFailure() + } + } + + func readUInt32() -> UInt32 { + var value: UInt32 = 0 + withUnsafeMutableBytes(of: &value, { bytes in + self.read(bytes: bytes) + }) + return value + } + + func readUInt16() -> UInt16 { + var value: UInt16 = 0 + withUnsafeMutableBytes(of: &value, { bytes in + self.read(bytes: bytes) + }) + return value + } + + func readUInt8() -> UInt8 { + var value: UInt8 = 0 + withUnsafeMutableBytes(of: &value, { bytes in + self.read(bytes: bytes) + }) + return value + } + + func readFloat() -> Float { + var value: Float = 0 + withUnsafeMutableBytes(of: &value, { bytes in + self.read(bytes: bytes) + }) + return value + } + + func readPoint() -> CGPoint { + return CGPoint(x: CGFloat(self.readFloat()), y: CGFloat(self.readFloat())) + } + + func readSize() -> CGSize { + return CGSize(width: CGFloat(self.readFloat()), height: CGFloat(self.readFloat())) + } + + func readRect() -> CGRect { + return CGRect(origin: self.readPoint(), size: self.readSize()) + } + + func readTransform() -> CATransform3D { + return CATransform3D( + m11: CGFloat(self.readFloat()), + m12: CGFloat(self.readFloat()), + m13: CGFloat(self.readFloat()), + m14: CGFloat(self.readFloat()), + m21: CGFloat(self.readFloat()), + m22: CGFloat(self.readFloat()), + m23: CGFloat(self.readFloat()), + m24: CGFloat(self.readFloat()), + m31: CGFloat(self.readFloat()), + m32: CGFloat(self.readFloat()), + m33: CGFloat(self.readFloat()), + m34: CGFloat(self.readFloat()), + m41: CGFloat(self.readFloat()), + m42: CGFloat(self.readFloat()), + m43: CGFloat(self.readFloat()), + m44: CGFloat(self.readFloat()) + ) + } +} + +private extension LottieColor { + init(argb: UInt32) { + self.init(r: CGFloat((argb >> 16) & 0xff) / 255.0, g: CGFloat((argb >> 8) & 0xff) / 255.0, b: CGFloat(argb & 0xff) / 255.0, a: CGFloat((argb >> 24) & 0xff) / 255.0) + } + + var argb: UInt32 { + return (UInt32(self.a * 255.0) << 24) | (UInt32(max(0.0, self.r) * 255.0) << 16) | (UInt32(max(0.0, self.g) * 255.0) << 8) | (UInt32(max(0.0, self.b) * 255.0)) + } +} + +private struct NodeFlags: OptionSet { + var rawValue: UInt8 + + init(rawValue: UInt8) { + self.rawValue = rawValue + } + + static let masksToBounds = NodeFlags(rawValue: 1 << 0) + static let isHidden = NodeFlags(rawValue: 1 << 1) + static let hasSimpleContents = NodeFlags(rawValue: 1 << 2) + static let isInvertedMatte = NodeFlags(rawValue: 1 << 3) + + static let hasRenderContent = NodeFlags(rawValue: 1 << 4) + static let hasSubnodes = NodeFlags(rawValue: 1 << 5) + static let hasMask = NodeFlags(rawValue: 1 << 6) +} + +private struct LottieContentFlags: OptionSet { + var rawValue: UInt8 + + init(rawValue: UInt8) { + self.rawValue = rawValue + } + + static let hasStroke = LottieContentFlags(rawValue: 1 << 0) + static let hasFill = LottieContentFlags(rawValue: 1 << 1) +} + +func serializePath(buffer: WriteBuffer, path: LottiePath) { + let lengthOffset = buffer.length + buffer.write(uInt32: 0) + + path.enumerateItems { pathItem in + switch pathItem.pointee.type { + case .moveTo: + let point = pathItem.pointee.points.0 + buffer.write(uInt8: 0) + buffer.write(point: point) + case .lineTo: + let point = pathItem.pointee.points.0 + buffer.write(uInt8: 1) + buffer.write(point: point) + case .curveTo: + let cp1 = pathItem.pointee.points.0 + let cp2 = pathItem.pointee.points.1 + let point = pathItem.pointee.points.2 + + buffer.write(uInt8: 2) + buffer.write(point: cp1) + buffer.write(point: cp2) + buffer.write(point: point) + case .close: + buffer.write(uInt8: 3) + @unknown default: + break + } + } + + let dataLength = buffer.length - lengthOffset - 4 + + let previousLength = buffer.length + buffer.length = lengthOffset + buffer.write(uInt32: UInt32(dataLength)) + buffer.length = previousLength +} + +func deserializePath(buffer: ReadBuffer) -> LottiePath { + let itemDataLength = Int(buffer.readUInt32()) + var itemData = Data(count: itemDataLength) + itemData.withUnsafeMutableBytes { bytes in + buffer.read(bytes: bytes) + } + + return LottiePath(customData: itemData) +} + +func serializeContentShading(buffer: WriteBuffer, shading: LottieRenderContentShading) { + if let shading = shading as? LottieRenderContentSolidShading { + buffer.write(uInt8: 0) + buffer.write(uInt32: shading.color.argb) + buffer.write(uInt8: UInt8(clamping: Int(shading.opacity * 255.0))) + } else if let shading = shading as? LottieRenderContentGradientShading { + buffer.write(uInt8: 1) + buffer.write(uInt8: UInt8(clamping: Int(shading.opacity * 255.0))) + buffer.write(uInt8: UInt8(shading.gradientType.rawValue)) + let colorStopCount = min(shading.colorStops.count, 255) + buffer.write(uInt8: UInt8(colorStopCount)) + for i in 0 ..< colorStopCount { + buffer.write(uInt32: shading.colorStops[i].color.argb) + buffer.write(float: Float(shading.colorStops[i].location)) + } + buffer.write(point: shading.start) + buffer.write(point: shading.end) + } else { + buffer.write(uInt8: 0) + buffer.write(uInt8: UInt8(clamping: Int(1.0 * 255.0))) + } +} + +func deserializeContentShading(buffer: ReadBuffer) -> LottieRenderContentShading { + switch buffer.readUInt8() { + case 0: + return LottieRenderContentSolidShading( + color: LottieColor(argb: buffer.readUInt32()), + opacity: CGFloat(buffer.readUInt8()) / 255.0 + ) + case 1: + let opacity = CGFloat(buffer.readUInt8()) / 255.0 + let gradientType = LottieGradientType(rawValue: UInt(buffer.readUInt8()))! + + var colorStops: [LottieColorStop] = [] + let colorStopCount = Int(buffer.readUInt8()) + for _ in 0 ..< colorStopCount { + colorStops.append(LottieColorStop( + color: LottieColor(argb: buffer.readUInt32()), + location: CGFloat(buffer.readFloat()) + )) + } + + let start = buffer.readPoint() + let end = buffer.readPoint() + + return LottieRenderContentGradientShading( + opacity: opacity, + gradientType: gradientType, + colorStops: colorStops, + start: start, + end: end + ) + default: + preconditionFailure() + } +} + +func serializeStroke(buffer: WriteBuffer, stroke: LottieRenderContentStroke) { + serializeContentShading(buffer: buffer, shading: stroke.shading) + buffer.write(float: Float(stroke.lineWidth)) + buffer.write(uInt8: UInt8(stroke.lineJoin.rawValue)) + buffer.write(uInt8: UInt8(stroke.lineCap.rawValue)) + buffer.write(float: Float(stroke.miterLimit)) +} + +func deserializeStroke(buffer: ReadBuffer) -> LottieRenderContentStroke { + return LottieRenderContentStroke( + shading: deserializeContentShading(buffer: buffer), + lineWidth: CGFloat(buffer.readFloat()), + lineJoin: CGLineJoin(rawValue: Int32(buffer.readUInt8()))!, + lineCap: CGLineCap(rawValue: Int32(buffer.readUInt8()))!, + miterLimit: CGFloat(buffer.readFloat()), + dashPhase: 0.0, + dashPattern: nil + ) +} + +func serializeFill(buffer: WriteBuffer, fill: LottieRenderContentFill) { + serializeContentShading(buffer: buffer, shading: fill.shading) + buffer.write(uInt8: UInt8(fill.fillRule.rawValue)) +} + +func deserializeFill(buffer: ReadBuffer) -> LottieRenderContentFill { + return LottieRenderContentFill( + shading: deserializeContentShading(buffer: buffer), + fillRule: LottieFillRule(rawValue: UInt(buffer.readUInt8()))! + ) +} + +func serializeRenderContent(buffer: WriteBuffer, renderContent: LottieRenderContent) { + var flags: LottieContentFlags = [] + if renderContent.stroke != nil { + flags.insert(.hasStroke) + } + if renderContent.fill != nil { + flags.insert(.hasFill) + } + buffer.write(uInt8: flags.rawValue) + + serializePath(buffer: buffer, path: renderContent.path) + if let stroke = renderContent.stroke { + serializeStroke(buffer: buffer, stroke: stroke) + } + if let fill = renderContent.fill { + serializeFill(buffer: buffer, fill: fill) + } +} + +func deserializeRenderContent(buffer: ReadBuffer) -> LottieRenderContent { + let flags = LottieContentFlags(rawValue: buffer.readUInt8()) + + let path = deserializePath(buffer: buffer) + + var stroke: LottieRenderContentStroke? + if flags.contains(.hasStroke) { + stroke = deserializeStroke(buffer: buffer) + } + + var fill: LottieRenderContentFill? + if flags.contains(.hasFill) { + fill = deserializeFill(buffer: buffer) + } + + return LottieRenderContent( + path: path, + stroke: stroke, + fill: fill + ) +} + +func serializeNode(buffer: WriteBuffer, node: LottieRenderNode) { + var flags: NodeFlags = [] + if node.masksToBounds { + flags.insert(.masksToBounds) + } + if node.isHidden { + flags.insert(.isHidden) + } + if node.hasSimpleContents { + flags.insert(.hasSimpleContents) + } + if node.isInvertedMatte { + flags.insert(.isInvertedMatte) + } + if node.renderContent != nil { + flags.insert(.hasRenderContent) + } + if !node.subnodes.isEmpty { + flags.insert(.hasSubnodes) + } + if node.mask != nil { + flags.insert(.hasMask) + } + + buffer.write(uInt8: flags.rawValue) + + buffer.write(point: node.position) + buffer.write(rect: node.bounds) + buffer.write(transform: node.transform) + buffer.write(uInt8: UInt8(clamping: Int(node.opacity * 255.0))) + buffer.write(rect: node.globalRect) + buffer.write(transform: node.globalTransform) + + if let renderContent = node.renderContent { + serializeRenderContent(buffer: buffer, renderContent: renderContent) + } + if !node.subnodes.isEmpty { + let count = min(node.subnodes.count, 4095) + buffer.write(uInt16: UInt16(count)) + for i in 0 ..< count { + serializeNode(buffer: buffer, node: node.subnodes[i]) + } + } + if let mask = node.mask { + serializeNode(buffer: buffer, node: mask) + } +} + +func deserializeNode(buffer: ReadBuffer) -> LottieRenderNode { + let flags = NodeFlags(rawValue: buffer.readUInt8()) + + let position = buffer.readPoint() + let bounds = buffer.readRect() + let transform = buffer.readTransform() + let opacity = CGFloat(buffer.readUInt8()) / 255.0 + let globalRect = buffer.readRect() + let globalTransform = buffer.readTransform() + + var renderContent: LottieRenderContent? + if flags.contains(.hasRenderContent) { + renderContent = deserializeRenderContent(buffer: buffer) + } + var subnodes: [LottieRenderNode] = [] + if flags.contains(.hasSubnodes) { + let count = Int(buffer.readUInt16()) + for _ in 0 ..< count { + subnodes.append(deserializeNode(buffer: buffer)) + } + } + var mask: LottieRenderNode? + if flags.contains(.hasMask) { + mask = deserializeNode(buffer: buffer) + } + + return LottieRenderNode( + position: position, + bounds: bounds, + transform: transform, + opacity: opacity, + masksToBounds: flags.contains(.masksToBounds), + isHidden: flags.contains(.isHidden), + globalRect: globalRect, + globalTransform: globalTransform, + renderContent: renderContent, + hasSimpleContents: flags.contains(.hasSimpleContents), + isInvertedMatte: flags.contains(.isInvertedMatte), + subnodes: subnodes, + mask: mask + ) +}