From 920de21020c8af188401dc02e5744a1f7caa3027 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 14 May 2024 22:34:34 +0400 Subject: [PATCH] Lottie optimization --- .../Sources/SoftwareLottieRenderer.mm | 242 +++++++++++++++++- .../Sources/CompareToReferenceRendering.swift | 5 + .../Sources/ViewController.swift | 4 +- .../ChatSendMessageContextScreen.swift | 13 + .../PublicHeaders/LottieCpp/RenderTreeNode.h | 36 ++- .../CompLayers/ShapeCompositionLayer.cpp | 83 ++++-- 6 files changed, 349 insertions(+), 34 deletions(-) diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm index 97f52a2ddd..9b2842b96b 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm @@ -38,6 +38,54 @@ static void processRenderTree(std::shared_ptr const &node, Vecto double alpha = node->alpha(); + /*if (node->_contentItem) { + RenderTreeNodeContentItem *contentItem = node->_contentItem.get(); + for (const auto &shadingVariant : contentItem->shadings) { + if (shadingVariant->stroke) { + CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shadingVariant->explicitPath.value()); + shapeBounds = shapeBounds.insetBy(-shadingVariant->stroke->lineWidth / 2.0, -shadingVariant->stroke->lineWidth / 2.0); + effectiveLocalBounds = shapeBounds; + + switch (shadingVariant->stroke->shading->type()) { + case RenderTreeNodeContent::ShadingType::Solid: { + RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shadingVariant->stroke->shading.get(); + + alpha *= solidShading->opacity; + + break; + } + case RenderTreeNodeContent::ShadingType::Gradient: { + + break; + } + default: + break; + } + } else if (shadingVariant->fill) { + CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shadingVariant->explicitPath.value()); + effectiveLocalBounds = shapeBounds; + + switch (shadingVariant->fill->shading->type()) { + case RenderTreeNodeContent::ShadingType::Solid: { + RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shadingVariant->fill->shading.get(); + + alpha *= solidShading->opacity; + + break; + } + case RenderTreeNodeContent::ShadingType::Gradient: { + RenderTreeNodeContent::GradientShading *gradientShading = (RenderTreeNodeContent::GradientShading *)shadingVariant->fill->shading.get(); + + alpha *= gradientShading->opacity; + + break; + } + default: + break; + } + } + } + }*/ if (node->content()) { RenderTreeNodeContent *shapeContent = node->content().get(); @@ -47,7 +95,7 @@ static void processRenderTree(std::shared_ptr const &node, Vecto shapeBounds = shapeBounds.insetBy(-shapeContent->stroke->lineWidth / 2.0, -shapeContent->stroke->lineWidth / 2.0); effectiveLocalBounds = shapeBounds; - switch (shapeContent->stroke->shading->type()) { + /*switch (shapeContent->stroke->shading->type()) { case RenderTreeNodeContent::ShadingType::Solid: { RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->stroke->shading.get(); @@ -61,11 +109,11 @@ static void processRenderTree(std::shared_ptr const &node, Vecto } default: break; - } + }*/ } else if (shapeContent->fill) { effectiveLocalBounds = shapeBounds; - switch (shapeContent->fill->shading->type()) { + /*switch (shapeContent->fill->shading->type()) { case RenderTreeNodeContent::ShadingType::Solid: { RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->fill->shading.get(); @@ -82,7 +130,7 @@ static void processRenderTree(std::shared_ptr const &node, Vecto } default: break; - } + }*/ } } @@ -110,7 +158,7 @@ static void processRenderTree(std::shared_ptr const &node, Vecto if (item->renderData.isValid) { drawContentDescendants += item->renderData.drawContentDescendants; - if (item->content()) { + if (item->content() || item->_contentItem) { drawContentDescendants += 1; } @@ -322,7 +370,7 @@ static void drawLottieRenderableItem(std::shared_ptr co dashPattern = item->stroke->dashPattern; } - context->strokePath(path, item->stroke->lineWidth, lineJoin, lineCap, item->stroke->dashPhase, dashPattern, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a)); + context->strokePath(path, item->stroke->lineWidth, lineJoin, lineCap, item->stroke->dashPhase, dashPattern, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity)); } else if (item->stroke->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) { //TODO:gradient stroke } @@ -344,14 +392,14 @@ static void drawLottieRenderableItem(std::shared_ptr co if (item->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Solid) { lottie::RenderTreeNodeContent::SolidShading *solidShading = (lottie::RenderTreeNodeContent::SolidShading *)item->fill->shading.get(); - context->fillPath(path, rule, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a)); + context->fillPath(path, rule, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity)); } else if (item->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) { lottie::RenderTreeNodeContent::GradientShading *gradientShading = (lottie::RenderTreeNodeContent::GradientShading *)item->fill->shading.get(); std::vector colors; std::vector locations; for (const auto &color : gradientShading->colors) { - colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a)); + colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a * gradientShading->opacity)); } locations = gradientShading->locations; @@ -376,6 +424,179 @@ static void drawLottieRenderableItem(std::shared_ptr co } } +static void drawLottieContentItem(std::shared_ptr context, std::shared_ptr item) { + if (item->shadings.empty()) { + return; + } + + for (const auto &shading : item->shadings) { + if (shading->explicitPath->empty()) { + continue; + } + + std::shared_ptr path = lottie::CGPath::makePath(); + + const auto iterate = [&](LottiePathItem const *pathItem) { + switch (pathItem->type) { + case LottiePathItemTypeMoveTo: { + path->moveTo(lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y)); + break; + } + case LottiePathItemTypeLineTo: { + path->addLineTo(lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y)); + break; + } + case LottiePathItemTypeCurveTo: { + path->addCurveTo(lottie::Vector2D(pathItem->points[2].x, pathItem->points[2].y), lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y), lottie::Vector2D(pathItem->points[1].x, pathItem->points[1].y)); + break; + } + case LottiePathItemTypeClose: { + path->closeSubpath(); + break; + } + default: { + break; + } + } + }; + + LottiePathItem pathItem; + for (const auto &path : shading->explicitPath.value()) { + std::optional previousElement; + for (const auto &element : path.elements()) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + pathItem.type = LottiePathItemTypeLineTo; + pathItem.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(&pathItem); + } else { + pathItem.type = LottiePathItemTypeCurveTo; + pathItem.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + pathItem.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); + pathItem.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); + iterate(&pathItem); + } + } else { + pathItem.type = LottiePathItemTypeMoveTo; + pathItem.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(&pathItem); + } + previousElement = element; + } + if (path.closed().value_or(true)) { + pathItem.type = LottiePathItemTypeClose; + iterate(&pathItem); + } + } + + if (shading->stroke) { + if (shading->stroke->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Solid) { + lottie::RenderTreeNodeContent::SolidShading *solidShading = (lottie::RenderTreeNodeContent::SolidShading *)shading->stroke->shading.get(); + + if (solidShading->opacity != 0.0) { + lottieRendering::LineJoin lineJoin = lottieRendering::LineJoin::Bevel; + switch (shading->stroke->lineJoin) { + case lottie::LineJoin::Bevel: { + lineJoin = lottieRendering::LineJoin::Bevel; + break; + } + case lottie::LineJoin::Round: { + lineJoin = lottieRendering::LineJoin::Round; + break; + } + case lottie::LineJoin::Miter: { + lineJoin = lottieRendering::LineJoin::Miter; + break; + } + default: { + break; + } + } + + lottieRendering::LineCap lineCap = lottieRendering::LineCap::Square; + switch (shading->stroke->lineCap) { + case lottie::LineCap::Butt: { + lineCap = lottieRendering::LineCap::Butt; + break; + } + case lottie::LineCap::Round: { + lineCap = lottieRendering::LineCap::Round; + break; + } + case lottie::LineCap::Square: { + lineCap = lottieRendering::LineCap::Square; + break; + } + default: { + break; + } + } + + std::vector dashPattern; + if (!shading->stroke->dashPattern.empty()) { + dashPattern = shading->stroke->dashPattern; + } + + context->strokePath(path, shading->stroke->lineWidth, lineJoin, lineCap, shading->stroke->dashPhase, dashPattern, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity)); + } else if (shading->stroke->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) { + //TODO:gradient stroke + } + } + } else if (shading->fill) { + lottieRendering::FillRule rule = lottieRendering::FillRule::NonZeroWinding; + switch (shading->fill->rule) { + case lottie::FillRule::EvenOdd: { + rule = lottieRendering::FillRule::EvenOdd; + break; + } + case lottie::FillRule::NonZeroWinding: { + rule = lottieRendering::FillRule::NonZeroWinding; + break; + } + default: { + break; + } + } + + if (shading->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Solid) { + lottie::RenderTreeNodeContent::SolidShading *solidShading = (lottie::RenderTreeNodeContent::SolidShading *)shading->fill->shading.get(); + if (solidShading->opacity != 0.0) { + context->fillPath(path, rule, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity)); + } + } else if (shading->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) { + lottie::RenderTreeNodeContent::GradientShading *gradientShading = (lottie::RenderTreeNodeContent::GradientShading *)shading->fill->shading.get(); + + if (gradientShading->opacity != 0.0) { + std::vector colors; + std::vector locations; + for (const auto &color : gradientShading->colors) { + colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a * gradientShading->opacity)); + } + locations = gradientShading->locations; + + lottieRendering::Gradient gradient(colors, locations); + lottie::Vector2D start(gradientShading->start.x, gradientShading->start.y); + lottie::Vector2D end(gradientShading->end.x, gradientShading->end.y); + + switch (gradientShading->gradientType) { + case lottie::GradientType::Linear: { + context->linearGradientFillPath(path, rule, gradient, start, end); + break; + } + case lottie::GradientType::Radial: { + context->radialGradientFillPath(path, rule, gradient, start, 0.0, start, start.distanceTo(end)); + break; + } + default: { + break; + } + } + } + } + } + } +} + static void renderLottieRenderNode(std::shared_ptr node, std::shared_ptr parentContext, lottie::Vector2D const &globalSize, double parentAlpha) { if (!node->renderData.isValid) { return; @@ -442,9 +663,12 @@ static void renderLottieRenderNode(std::shared_ptr node, currentContext->setAlpha(renderAlpha); - if (node->content()) { + if (node->content() && (int64_t)"" < 0) { drawLottieRenderableItem(currentContext, node->content()); } + if (node->_contentItem) {//} && (int64_t)"" < 0) { + drawLottieContentItem(currentContext, node->_contentItem); + } if (node->renderData.isInvertedMatte) { currentContext->fill(lottie::CGRect(node->renderData.layer.bounds().x, node->renderData.layer.bounds().y, node->renderData.layer.bounds().width, node->renderData.layer.bounds().height), lottieRendering::Color(0.0, 0.0, 0.0, 1.0)); diff --git a/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift b/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift index b5ef73185c..7b67ef5470 100644 --- a/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift +++ b/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift @@ -103,6 +103,11 @@ func processDrawAnimation(baseCachePath: String, path: String, name: String, siz return true } } + + /*if #available(iOS 16.0, *) { + try? await Task.sleep(for: .seconds(0.1)) + }*/ + if !frameResult { return false } diff --git a/Tests/LottieMetalTest/Sources/ViewController.swift b/Tests/LottieMetalTest/Sources/ViewController.swift index b3225e661e..d164f4d680 100644 --- a/Tests/LottieMetalTest/Sources/ViewController.swift +++ b/Tests/LottieMetalTest/Sources/ViewController.swift @@ -78,7 +78,7 @@ private final class ReferenceCompareTest { } var continueFromName: String? - //continueFromName = "1258816259754282.json" + //continueFromName = "35707580709863498.json" let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: true, process: { path, name, alwaysDraw in if let continueFromNameValue = continueFromName { @@ -119,7 +119,7 @@ public final class ViewController: UIViewController { self.view.layer.addSublayer(MetalEngine.shared.rootLayer) - if !"".isEmpty { + if "".isEmpty { if #available(iOS 13.0, *) { self.test = ReferenceCompareTest(view: self.view) } diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index b5e61029eb..b91868aa58 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -1072,6 +1072,8 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha private var processedDidAppear: Bool = false private var processedDidDisappear: Bool = false + private var isActiveDisposable: Disposable? + override public var overlayWantsToBeBelowKeyboard: Bool { if let componentView = self.node.hostView.componentView as? ChatSendMessageContextScreenComponent.View { return componentView.wantsToBeBelowKeyboard() @@ -1136,6 +1138,16 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha self.lockOrientation = true self.blocksBackgroundWhenInOverlay = true + + self.isActiveDisposable = (context.sharedContext.applicationBindings.applicationInForeground + |> filter { !$0 } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak self] _ in + guard let self else { + return + } + self.dismiss() + }) } required public init(coder aDecoder: NSCoder) { @@ -1143,6 +1155,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha } deinit { + self.isActiveDisposable?.dispose() } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h index c3a3cd00d7..0581fdbaa6 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h @@ -9,6 +9,8 @@ #include #include +#include + namespace lottie { class RenderableItem { @@ -229,14 +231,7 @@ public: CGRect bounds; }; -class RenderTreeNodeContentItem { -public: - RenderTreeNodeContentItem() { - } - -public: - std::vector> subItems; -}; +class RenderTreeNodeContentItem; class RenderTreeNodeContent { public: @@ -360,6 +355,30 @@ public: std::shared_ptr fill; }; +class RenderTreeNodeContentShadingVariant { +public: + RenderTreeNodeContentShadingVariant() { + } + +public: + std::shared_ptr stroke; + std::shared_ptr fill; + std::optional> explicitPath; + + size_t subItemLimit = 0; + bool isGroup = false; +}; + +class RenderTreeNodeContentItem { +public: + RenderTreeNodeContentItem() { + } + +public: + bool isGroup = false; + std::vector> shadings; +}; + class ProcessedRenderTreeNodeData { public: struct LayerParams { @@ -518,6 +537,7 @@ public: bool _masksToBounds = false; bool _isHidden = false; std::shared_ptr _content; + std::shared_ptr _contentItem; std::vector> _subnodes; std::shared_ptr _mask; bool _invertMask = false; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp index 096c179443..f6bbb25a76 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp @@ -899,7 +899,7 @@ public: std::shared_ptr _renderTree; private: - std::vector collectPaths(AnimationFrameTime frameTime, size_t subItemLimit, CATransform3D parentTransform) { + std::vector collectPaths(size_t subItemLimit, CATransform3D parentTransform) { std::vector mappedPaths; CATransform3D effectiveTransform = parentTransform; @@ -908,7 +908,6 @@ public: size_t maxSubitem = std::min(subItems.size(), subItemLimit); if (path) { - path->update(frameTime); mappedPaths.emplace_back(*(path->currentPath()), effectiveTransform); } @@ -917,17 +916,19 @@ public: CATransform3D subItemTransform = effectiveChildTransform; if (subItem->isGroup && subItem->transform) { - subItem->transform->update(frameTime); + //update? + //subItem->transform->update(frameTime); subItemTransform = subItem->transform->transform() * subItemTransform; } std::optional currentTrim; if (!trims.empty()) { - trims[0]->update(frameTime); + //update? + //trims[0]->update(frameTime); currentTrim = trims[0]->trimParams(); } - auto subItemPaths = subItem->collectPaths(frameTime, INT32_MAX, subItemTransform); + auto subItemPaths = subItem->collectPaths(INT32_MAX, subItemTransform); if (currentTrim) { CompoundBezierPath tempPath; @@ -986,6 +987,9 @@ public: false ); + _renderTree->_contentItem = std::make_shared(); + _renderTree->_contentItem->isGroup = isGroup; + if (!shadings.empty()) { for (int i = 0; i < shadings.size(); i++) { auto &shadingVariant = shadings[i]; @@ -1008,6 +1012,28 @@ public: ); shadingVariant.renderTree = shadingRenderTree; _renderTree->_subnodes.push_back(shadingRenderTree); + + auto itemShadingVariant = std::make_shared(); + if (shadingVariant.fill) { + itemShadingVariant->fill = std::make_shared( + nullptr, + FillRule::NonZeroWinding + ); + } + if (shadingVariant.stroke) { + itemShadingVariant->stroke = std::make_shared( + nullptr, + 0.0, + LineJoin::Bevel, + LineCap::Round, + 0.0, + 0.0, + std::vector() + ); + } + itemShadingVariant->subItemLimit = shadingVariant.subItemLimit; + + _renderTree->_contentItem->shadings.push_back(itemShadingVariant); } } @@ -1036,11 +1062,36 @@ public: } public: - void updateChildren(AnimationFrameTime frameTime, std::optional parentTrim) { + void updateFrame(AnimationFrameTime frameTime) { + if (transform) { + transform->update(frameTime); + } + + if (path) { + path->update(frameTime); + } + for (const auto &trim : trims) { + trim->update(frameTime); + } + + for (const auto &shadingVariant : shadings) { + if (shadingVariant.fill) { + shadingVariant.fill->update(frameTime); + } + if (shadingVariant.stroke) { + shadingVariant.stroke->update(frameTime); + } + } + + for (const auto &subItem : subItems) { + subItem->updateFrame(frameTime); + } + } + + void updateChildren(std::optional parentTrim) { CATransform3D containerTransform = CATransform3D::identity(); double containerOpacity = 1.0; if (transform) { - transform->update(frameTime); containerTransform = transform->transform(); containerOpacity = transform->opacity(); } @@ -1055,7 +1106,7 @@ public: } CompoundBezierPath compoundPath; - auto paths = collectPaths(frameTime, shadingVariant.subItemLimit, CATransform3D::identity()); + auto paths = collectPaths(shadingVariant.subItemLimit, CATransform3D::identity()); for (const auto &path : paths) { compoundPath.appendPath(path.path.copyUsingTransform(path.transform)); } @@ -1079,13 +1130,11 @@ public: std::shared_ptr fill; if (shadingVariant.fill) { - shadingVariant.fill->update(frameTime); fill = shadingVariant.fill->fill(); } std::shared_ptr stroke; if (shadingVariant.stroke) { - shadingVariant.stroke->update(frameTime); stroke = shadingVariant.stroke->stroke(); } @@ -1096,14 +1145,16 @@ public: ); shadingVariant.renderTree->_content = content; + + _renderTree->_contentItem->shadings[i]->stroke = stroke; + _renderTree->_contentItem->shadings[i]->fill = fill; + _renderTree->_contentItem->shadings[i]->explicitPath = resultPaths; } if (isGroup && !subItems.empty()) { for (int i = (int)subItems.size() - 1; i >= 0; i--) { std::optional childTrim = parentTrim; for (const auto &trim : trims) { - trim->update(frameTime); - if (i < (int)trim->trimParams().subItemLimit) { //TODO:allow combination //assert(!parentTrim); @@ -1111,7 +1162,7 @@ public: } } - subItems[i]->updateChildren(frameTime, childTrim); + subItems[i]->updateChildren(childTrim); } } } @@ -1284,14 +1335,16 @@ CompositionLayer(solidLayer, Vector2D::Zero()) { void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates) { _frameTime = frame; _frameTimeInitialized = true; - _contentTree->itemTree->updateChildren(_frameTime, std::nullopt); + _contentTree->itemTree->updateFrame(_frameTime); + _contentTree->itemTree->updateChildren(std::nullopt); } std::shared_ptr ShapeCompositionLayer::renderTreeNode() { if (!_frameTimeInitialized) { _frameTime = 0.0; _frameTimeInitialized = true; - _contentTree->itemTree->updateChildren(_frameTime, std::nullopt); + _contentTree->itemTree->updateFrame(_frameTime); + _contentTree->itemTree->updateChildren(std::nullopt); } if (!_renderTreeNode) {