diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index b88543c1596da..a1dbd354df643 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -363,8 +363,15 @@ void Canvas::SaveLayer( Save(true, paint.blend_mode, backdrop_filter); auto& new_layer_pass = GetCurrentPass(); - new_layer_pass.SetDelegate( - std::make_unique(paint, bounds)); + + // Only apply opacity peephole on default blending. + if (paint.blend_mode == BlendMode::kSourceOver) { + new_layer_pass.SetDelegate( + std::make_unique(paint, bounds)); + } else { + new_layer_pass.SetDelegate( + std::make_unique(paint, bounds)); + } if (bounds.has_value() && !backdrop_filter.has_value()) { // Render target switches due to a save layer can be elided. In such cases diff --git a/impeller/aiks/paint_pass_delegate.cc b/impeller/aiks/paint_pass_delegate.cc index a06128a534ba2..d80d6a3a5333d 100644 --- a/impeller/aiks/paint_pass_delegate.cc +++ b/impeller/aiks/paint_pass_delegate.cc @@ -6,10 +6,14 @@ #include "impeller/entity/contents/contents.h" #include "impeller/entity/contents/texture_contents.h" +#include "impeller/entity/entity_pass.h" #include "impeller/geometry/path_builder.h" namespace impeller { +/// PaintPassDelegate +/// ---------------------------------------------- + PaintPassDelegate::PaintPassDelegate(Paint paint, std::optional coverage) : paint_(std::move(paint)), coverage_(coverage) {} @@ -27,7 +31,7 @@ bool PaintPassDelegate::CanElide() { } // |EntityPassDelgate| -bool PaintPassDelegate::CanCollapseIntoParentPass() { +bool PaintPassDelegate::CanCollapseIntoParentPass(EntityPass* entity_pass) { return false; } @@ -44,4 +48,90 @@ std::shared_ptr PaintPassDelegate::CreateContentsForSubpassTarget( effect_transform); } +/// OpacityPeepholePassDelegate +/// ---------------------------------------------- + +OpacityPeepholePassDelegate::OpacityPeepholePassDelegate( + Paint paint, + std::optional coverage) + : paint_(std::move(paint)), coverage_(coverage) {} + +// |EntityPassDelgate| +OpacityPeepholePassDelegate::~OpacityPeepholePassDelegate() = default; + +// |EntityPassDelgate| +std::optional OpacityPeepholePassDelegate::GetCoverageRect() { + return coverage_; +} + +// |EntityPassDelgate| +bool OpacityPeepholePassDelegate::CanElide() { + return paint_.blend_mode == BlendMode::kDestination; +} + +// |EntityPassDelgate| +bool OpacityPeepholePassDelegate::CanCollapseIntoParentPass( + EntityPass* entity_pass) { + // Note: determing whether any coverage intersects has quadradic complexity in + // the number of rectangles, and depending on whether or not we cache at + // different levels of the entity tree may end up cubic. In the interest of + // proving whether or not this optimization is valuable, we only consider very + // simple peephole optimizations here - where there is a single drawing + // command wrapped in save layer. This would indicate something like an + // Opacity or FadeTransition wrapping a very simple widget, like in the + // CupertinoPicker. + if (entity_pass->GetEntityCount() > 3) { + // Single paint command with a save layer would be: + // 1. clip + // 2. draw command + // 3. restore. + return false; + } + bool all_can_accept = true; + std::vector all_coverages; + auto had_subpass = entity_pass->IterateUntilSubpass( + [&all_coverages, &all_can_accept](Entity& entity) { + auto contents = entity.GetContents(); + if (!contents->CanAcceptOpacity(entity)) { + all_can_accept = false; + return false; + } + auto maybe_coverage = contents->GetCoverage(entity); + if (maybe_coverage.has_value()) { + auto coverage = maybe_coverage.value(); + for (const auto& cv : all_coverages) { + if (cv.IntersectsWithRect(coverage)) { + all_can_accept = false; + return false; + } + } + all_coverages.push_back(coverage); + } + return true; + }); + if (had_subpass || !all_can_accept) { + return false; + } + auto alpha = paint_.color.alpha; + entity_pass->IterateUntilSubpass([&alpha](Entity& entity) { + entity.GetContents()->InheritOpacity(alpha); + return true; + }); + return true; +} + +// |EntityPassDelgate| +std::shared_ptr +OpacityPeepholePassDelegate::CreateContentsForSubpassTarget( + std::shared_ptr target, + const Matrix& effect_transform) { + auto contents = TextureContents::MakeRect(Rect::MakeSize(target->GetSize())); + contents->SetTexture(target); + contents->SetSourceRect(Rect::MakeSize(target->GetSize())); + contents->SetOpacity(paint_.color.alpha); + contents->SetDeferApplyingOpacity(true); + return paint_.WithFiltersForSubpassTarget(std::move(contents), + effect_transform); +} + } // namespace impeller diff --git a/impeller/aiks/paint_pass_delegate.h b/impeller/aiks/paint_pass_delegate.h index 128f7b6abb14a..9217943c8a741 100644 --- a/impeller/aiks/paint_pass_delegate.h +++ b/impeller/aiks/paint_pass_delegate.h @@ -12,6 +12,8 @@ namespace impeller { +class EntityPass; + class PaintPassDelegate final : public EntityPassDelegate { public: PaintPassDelegate(Paint paint, std::optional coverage); @@ -26,7 +28,7 @@ class PaintPassDelegate final : public EntityPassDelegate { bool CanElide() override; // |EntityPassDelgate| - bool CanCollapseIntoParentPass() override; + bool CanCollapseIntoParentPass(EntityPass* entity_pass) override; // |EntityPassDelgate| std::shared_ptr CreateContentsForSubpassTarget( @@ -40,4 +42,37 @@ class PaintPassDelegate final : public EntityPassDelegate { FML_DISALLOW_COPY_AND_ASSIGN(PaintPassDelegate); }; +/// A delegate that attempts to forward opacity from a save layer to +/// child contents. +/// +/// Currently this has a hardcoded limit of 3 entities in a pass, and +/// cannot forward to child subpass delegates. +class OpacityPeepholePassDelegate final : public EntityPassDelegate { + public: + OpacityPeepholePassDelegate(Paint paint, std::optional coverage); + + // |EntityPassDelgate| + ~OpacityPeepholePassDelegate() override; + + // |EntityPassDelegate| + std::optional GetCoverageRect() override; + + // |EntityPassDelgate| + bool CanElide() override; + + // |EntityPassDelgate| + bool CanCollapseIntoParentPass(EntityPass* entity_pass) override; + + // |EntityPassDelgate| + std::shared_ptr CreateContentsForSubpassTarget( + std::shared_ptr target, + const Matrix& effect_transform) override; + + private: + const Paint paint_; + const std::optional coverage_; + + FML_DISALLOW_COPY_AND_ASSIGN(OpacityPeepholePassDelegate); +}; + } // namespace impeller diff --git a/impeller/entity/contents/clip_contents.cc b/impeller/entity/contents/clip_contents.cc index 5f55c726c8297..1689a0d9c804b 100644 --- a/impeller/entity/contents/clip_contents.cc +++ b/impeller/entity/contents/clip_contents.cc @@ -70,6 +70,12 @@ bool ClipContents::ShouldRender( return true; } +bool ClipContents::CanAcceptOpacity(const Entity& entity) const { + return true; +} + +void ClipContents::InheritOpacity(Scalar opacity) {} + bool ClipContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { @@ -166,6 +172,12 @@ bool ClipRestoreContents::ShouldRender( return true; } +bool ClipRestoreContents::CanAcceptOpacity(const Entity& entity) const { + return true; +} + +void ClipRestoreContents::InheritOpacity(Scalar opacity) {} + bool ClipRestoreContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { diff --git a/impeller/entity/contents/clip_contents.h b/impeller/entity/contents/clip_contents.h index 86606d2f59db7..704470c5d7ad9 100644 --- a/impeller/entity/contents/clip_contents.h +++ b/impeller/entity/contents/clip_contents.h @@ -41,6 +41,11 @@ class ClipContents final : public Contents { bool Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const override; + // |Contents| + bool CanAcceptOpacity(const Entity& entity) const override; + + // |Contents| + void InheritOpacity(Scalar opacity) override; private: std::unique_ptr geometry_; @@ -78,6 +83,12 @@ class ClipRestoreContents final : public Contents { const Entity& entity, RenderPass& pass) const override; + // |Contents| + bool CanAcceptOpacity(const Entity& entity) const override; + + // |Contents| + void InheritOpacity(Scalar opacity) override; + private: std::optional restore_coverage_; diff --git a/impeller/entity/contents/color_source_contents.cc b/impeller/entity/contents/color_source_contents.cc index 6f0d88d2e8f36..bfc0f753479f0 100644 --- a/impeller/entity/contents/color_source_contents.cc +++ b/impeller/entity/contents/color_source_contents.cc @@ -42,6 +42,14 @@ std::optional ColorSourceContents::GetCoverage( return geometry_->GetCoverage(entity.GetTransformation()); }; +bool ColorSourceContents::CanAcceptOpacity(const Entity& entity) const { + return true; +} + +void ColorSourceContents::InheritOpacity(Scalar opacity) { + SetAlpha(GetAlpha() * opacity); +} + bool ColorSourceContents::ShouldRender( const Entity& entity, const std::optional& stencil_coverage) const { diff --git a/impeller/entity/contents/color_source_contents.h b/impeller/entity/contents/color_source_contents.h index 5eacfc6041a39..02e0ca039d417 100644 --- a/impeller/entity/contents/color_source_contents.h +++ b/impeller/entity/contents/color_source_contents.h @@ -33,11 +33,17 @@ class ColorSourceContents : public Contents { bool ShouldRender(const Entity& entity, const std::optional& stencil_coverage) const override; - protected: - const std::shared_ptr& GetGeometry() const; + // | Contents| + bool CanAcceptOpacity(const Entity& entity) const override; + + // | Contents| + void InheritOpacity(Scalar opacity) override; Scalar GetAlpha() const; + protected: + const std::shared_ptr& GetGeometry() const; + private: std::shared_ptr geometry_; Matrix inverse_matrix_; diff --git a/impeller/entity/contents/contents.cc b/impeller/entity/contents/contents.cc index 437454d97f9e7..93f431ca09e84 100644 --- a/impeller/entity/contents/contents.cc +++ b/impeller/entity/contents/contents.cc @@ -110,6 +110,14 @@ std::optional Contents::RenderToSnapshot( return snapshot; } +bool Contents::CanAcceptOpacity(const Entity& entity) const { + return false; +} + +void Contents::InheritOpacity(Scalar opacity) { + FML_UNREACHABLE(); +} + bool Contents::ShouldRender(const Entity& entity, const std::optional& stencil_coverage) const { if (!stencil_coverage.has_value()) { diff --git a/impeller/entity/contents/contents.h b/impeller/entity/contents/contents.h index a4ff06b93cd5a..b74b2e2d3c9fb 100644 --- a/impeller/entity/contents/contents.h +++ b/impeller/entity/contents/contents.h @@ -83,6 +83,19 @@ class Contents { void SetColorSourceSize(Size size) { color_source_size_ = size; } + /// @brief Whether or not this contents can accept the opacity peephole + /// optimization. + /// + /// By default all contents return false. Contents are responsible + /// for determining whether or not their own geometries intersect in + /// a way that makes accepting opacity impossible. It is always safe + /// to return false, especially if computing overlap would be + /// computationally expensive. + virtual bool CanAcceptOpacity(const Entity& entity) const; + + /// @brief Inherit the provided opacity. + virtual void InheritOpacity(Scalar opacity); + protected: private: diff --git a/impeller/entity/contents/solid_color_contents.cc b/impeller/entity/contents/solid_color_contents.cc index c515a45162737..da9571b204807 100644 --- a/impeller/entity/contents/solid_color_contents.cc +++ b/impeller/entity/contents/solid_color_contents.cc @@ -28,6 +28,17 @@ void SolidColorContents::SetGeometry(std::shared_ptr geometry) { geometry_ = std::move(geometry); } +// | Contents| +bool SolidColorContents::CanAcceptOpacity(const Entity& entity) const { + return true; +} + +// | Contents| +void SolidColorContents::InheritOpacity(Scalar opacity) { + auto color = color_; + color_ = color.WithAlpha(color.alpha * opacity); +} + std::optional SolidColorContents::GetCoverage( const Entity& entity) const { if (color_.IsTransparent()) { diff --git a/impeller/entity/contents/solid_color_contents.h b/impeller/entity/contents/solid_color_contents.h index 81148b133232a..00839e47ada76 100644 --- a/impeller/entity/contents/solid_color_contents.h +++ b/impeller/entity/contents/solid_color_contents.h @@ -35,6 +35,12 @@ class SolidColorContents final : public Contents { const Color& GetColor() const; + // | Contents| + bool CanAcceptOpacity(const Entity& entity) const override; + + // | Contents| + void InheritOpacity(Scalar opacity) override; + // |Contents| std::optional GetCoverage(const Entity& entity) const override; diff --git a/impeller/entity/contents/text_contents.cc b/impeller/entity/contents/text_contents.cc index 713dbbaf2033b..ed6b4efb8206a 100644 --- a/impeller/entity/contents/text_contents.cc +++ b/impeller/entity/contents/text_contents.cc @@ -50,6 +50,19 @@ void TextContents::SetColor(Color color) { color_ = color; } +Color TextContents::GetColor() const { + return color_; +} + +bool TextContents::CanAcceptOpacity(const Entity& entity) const { + return !frame_.MaybeHasOverlapping(); +} + +void TextContents::InheritOpacity(Scalar opacity) { + auto color = color_; + color_ = color.WithAlpha(color.alpha * opacity); +} + void TextContents::SetInverseMatrix(Matrix matrix) { inverse_matrix_ = matrix; } @@ -122,9 +135,9 @@ static bool CommonRender( // interpolated vertex information is also used in the fragment shader to // sample from the glyph atlas. - const std::array unit_points = {Point{0, 0}, Point{1, 0}, - Point{0, 1}, Point{1, 1}}; - const std::array indices = {0, 1, 2, 1, 2, 3}; + constexpr std::array unit_points = {Point{0, 0}, Point{1, 0}, + Point{0, 1}, Point{1, 1}}; + constexpr std::array indices = {0, 1, 2, 1, 2, 3}; VertexBufferBuilder vertex_builder; diff --git a/impeller/entity/contents/text_contents.h b/impeller/entity/contents/text_contents.h index 0a6b5bf716e4b..3f9cd4c809c48 100644 --- a/impeller/entity/contents/text_contents.h +++ b/impeller/entity/contents/text_contents.h @@ -32,6 +32,12 @@ class TextContents final : public Contents { void SetColor(Color color); + Color GetColor() const; + + bool CanAcceptOpacity(const Entity& entity) const override; + + void InheritOpacity(Scalar opacity) override; + void SetInverseMatrix(Matrix matrix); // |Contents| diff --git a/impeller/entity/contents/texture_contents.cc b/impeller/entity/contents/texture_contents.cc index 7375598dd87ff..135b466c217e5 100644 --- a/impeller/entity/contents/texture_contents.cc +++ b/impeller/entity/contents/texture_contents.cc @@ -57,6 +57,18 @@ void TextureContents::SetStencilEnabled(bool enabled) { stencil_enabled_ = enabled; } +bool TextureContents::CanAcceptOpacity(const Entity& entity) const { + return true; +} + +void TextureContents::InheritOpacity(Scalar opacity) { + opacity_ = opacity_ * opacity; +} + +Scalar TextureContents::GetOpacity() const { + return opacity_; +} + std::optional TextureContents::GetCoverage(const Entity& entity) const { if (opacity_ == 0) { return std::nullopt; diff --git a/impeller/entity/contents/texture_contents.h b/impeller/entity/contents/texture_contents.h index fec23fba6505a..ca2a3ae167a1b 100644 --- a/impeller/entity/contents/texture_contents.h +++ b/impeller/entity/contents/texture_contents.h @@ -46,6 +46,8 @@ class TextureContents final : public Contents { void SetOpacity(Scalar opacity); + Scalar GetOpacity() const; + void SetStencilEnabled(bool enabled); // |Contents| @@ -63,6 +65,12 @@ class TextureContents final : public Contents { const Entity& entity, RenderPass& pass) const override; + // |Contents| + bool CanAcceptOpacity(const Entity& entity) const override; + + // |Contents| + void InheritOpacity(Scalar opacity) override; + void SetDeferApplyingOpacity(bool defer_applying_opacity); private: diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index 487c56d063976..27343bf92bcac 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -292,7 +292,7 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( } if (!subpass->backdrop_filter_proc_.has_value() && - subpass->delegate_->CanCollapseIntoParentPass()) { + subpass->delegate_->CanCollapseIntoParentPass(subpass)) { // Directly render into the parent target and move on. if (!subpass->OnRender(renderer, root_pass_size, pass_context.GetRenderTarget(), position, position, @@ -623,6 +623,28 @@ void EntityPass::IterateAllEntities( } } +bool EntityPass::IterateUntilSubpass( + const std::function& iterator) { + if (!iterator) { + return true; + } + + for (auto& element : elements_) { + if (auto entity = std::get_if(&element)) { + if (!iterator(*entity)) { + return false; + } + continue; + } + return true; + } + return false; +} + +size_t EntityPass::GetEntityCount() const { + return elements_.size(); +} + std::unique_ptr EntityPass::Clone() const { std::vector new_elements; new_elements.reserve(elements_.size()); diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index c958420157d13..6f009202b0857 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -52,6 +52,15 @@ class EntityPass { void IterateAllEntities(const std::function& iterator); + /// @brief Iterate entities in this pass up until the first subpass is found. + /// This is useful for limiting look-ahead optimizations. + /// + /// @return Returns whether a subpass was encountered. + bool IterateUntilSubpass(const std::function& iterator); + + /// @brief Return the number of entities on this pass. + size_t GetEntityCount() const; + void SetTransformation(Matrix xformation); void SetStencilDepth(size_t stencil_depth); diff --git a/impeller/entity/entity_pass_delegate.cc b/impeller/entity/entity_pass_delegate.cc index 529c8698f68bd..eccd880086394 100644 --- a/impeller/entity/entity_pass_delegate.cc +++ b/impeller/entity/entity_pass_delegate.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "impeller/entity/entity_pass_delegate.h" +#include "impeller/entity/entity_pass.h" namespace impeller { @@ -24,7 +25,9 @@ class DefaultEntityPassDelegate final : public EntityPassDelegate { bool CanElide() override { return false; } // |EntityPassDelegate| - bool CanCollapseIntoParentPass() override { return true; } + bool CanCollapseIntoParentPass(EntityPass* entity_pass) override { + return true; + } // |EntityPassDelegate| std::shared_ptr CreateContentsForSubpassTarget( diff --git a/impeller/entity/entity_pass_delegate.h b/impeller/entity/entity_pass_delegate.h index 28f485c1237f7..119d78dda8798 100644 --- a/impeller/entity/entity_pass_delegate.h +++ b/impeller/entity/entity_pass_delegate.h @@ -12,6 +12,8 @@ namespace impeller { +class EntityPass; + class EntityPassDelegate { public: static std::unique_ptr MakeDefault(); @@ -24,7 +26,9 @@ class EntityPassDelegate { virtual bool CanElide() = 0; - virtual bool CanCollapseIntoParentPass() = 0; + /// @brief Whether or not this entity pass can be collapsed into the parent. + /// If true, this method may modify the entities for the current pass. + virtual bool CanCollapseIntoParentPass(EntityPass* entity_pass) = 0; virtual std::shared_ptr CreateContentsForSubpassTarget( std::shared_ptr target, diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 8a96888424807..ee2a70801b072 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -26,6 +26,7 @@ #include "impeller/entity/contents/solid_color_contents.h" #include "impeller/entity/contents/text_contents.h" #include "impeller/entity/contents/texture_contents.h" +#include "impeller/entity/contents/tiled_texture_contents.h" #include "impeller/entity/contents/vertices_contents.h" #include "impeller/entity/entity.h" #include "impeller/entity/entity_pass.h" @@ -74,7 +75,9 @@ class TestPassDelegate final : public EntityPassDelegate { bool CanElide() override { return false; } // |EntityPassDelgate| - bool CanCollapseIntoParentPass() override { return collapse_; } + bool CanCollapseIntoParentPass(EntityPass* entity_pass) override { + return collapse_; + } // |EntityPassDelgate| std::shared_ptr CreateContentsForSubpassTarget( @@ -2345,5 +2348,70 @@ TEST_P(EntityTest, RuntimeEffect) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } +TEST_P(EntityTest, InheritOpacityTest) { + Entity entity; + + // Texture contents can always accept opacity. + auto texture_contents = std::make_shared(); + texture_contents->SetOpacity(0.5); + ASSERT_TRUE(texture_contents->CanAcceptOpacity(entity)); + + texture_contents->InheritOpacity(0.5); + ASSERT_EQ(texture_contents->GetOpacity(), 0.25); + + // Solid color contents can accept opacity if their geometry + // doesn't overlap. + auto solid_color = std::make_shared(); + solid_color->SetGeometry( + Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200))); + solid_color->SetColor(Color::Blue().WithAlpha(0.5)); + + ASSERT_TRUE(solid_color->CanAcceptOpacity(entity)); + + solid_color->InheritOpacity(0.5); + ASSERT_EQ(solid_color->GetColor().alpha, 0.25); + + // Color source contents can accept opacity if their geometry + // doesn't overlap. + auto tiled_texture = std::make_shared(); + tiled_texture->SetGeometry( + Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200))); + tiled_texture->SetAlpha(0.5); + + ASSERT_TRUE(tiled_texture->CanAcceptOpacity(entity)); + + tiled_texture->InheritOpacity(0.5); + ASSERT_EQ(tiled_texture->GetAlpha(), 0.25); + + // Text contents can accept opacity if the text frames do not + // overlap + SkFont font; + font.setSize(30); + auto blob = SkTextBlob::MakeFromString("A", font); + auto frame = TextFrameFromTextBlob(blob); + auto lazy_glyph_atlas = std::make_shared(); + lazy_glyph_atlas->AddTextFrame(frame); + + auto text_contents = std::make_shared(); + text_contents->SetTextFrame(frame); + text_contents->SetColor(Color::Blue().WithAlpha(0.5)); + + ASSERT_TRUE(text_contents->CanAcceptOpacity(entity)); + + text_contents->InheritOpacity(0.5); + ASSERT_EQ(text_contents->GetColor().alpha, 0.25); + + // Clips and restores trivially accept opacity. + ASSERT_TRUE(ClipContents().CanAcceptOpacity(entity)); + ASSERT_TRUE(ClipRestoreContents().CanAcceptOpacity(entity)); + + // Potentially overlapping geometry always returns false. + auto solid_color_2 = std::make_shared(); + Path path = PathBuilder{}.MoveTo({100, 100}).LineTo({100, 200}).TakePath(); + solid_color_2->SetGeometry(Geometry::MakeStrokePath(path, 5.0)); + + ASSERT_FALSE(solid_color_2->CanAcceptOpacity(entity)); +} + } // namespace testing } // namespace impeller diff --git a/impeller/entity/geometry.cc b/impeller/entity/geometry.cc index b610e03dd8eef..2ad20a000afb9 100644 --- a/impeller/entity/geometry.cc +++ b/impeller/entity/geometry.cc @@ -4,6 +4,7 @@ #include "impeller/entity/geometry.h" #include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" #include "impeller/entity/position_color.vert.h" #include "impeller/geometry/matrix.h" #include "impeller/geometry/path_builder.h" diff --git a/impeller/typographer/text_frame.cc b/impeller/typographer/text_frame.cc index ff05c28f41061..d7c241b899283 100644 --- a/impeller/typographer/text_frame.cc +++ b/impeller/typographer/text_frame.cc @@ -46,4 +46,36 @@ bool TextFrame::HasColor() const { return has_color_; } +bool TextFrame::MaybeHasOverlapping() const { + if (runs_.size() > 1) { + return true; + } + auto glyph_positions = runs_[0].GetGlyphPositions(); + if (glyph_positions.size() > 10) { + return true; + } + if (glyph_positions.size() == 1) { + return false; + } + // To avoid quadradic behavior the overlapping is checked against an + // accumulated bounds rect. This gives faster but less precise information + // on text runs. + auto first_position = glyph_positions[0]; + auto overlapping_rect = + Rect(first_position.position + first_position.glyph.bounds.origin, + first_position.glyph.bounds.size); + for (auto i = 1u; i < glyph_positions.size(); i++) { + auto glyph_position = glyph_positions[i]; + auto glyph_rect = + Rect(glyph_position.position + glyph_position.glyph.bounds.origin, + glyph_position.glyph.bounds.size); + auto intersection = glyph_rect.Intersection(overlapping_rect); + if (intersection.has_value()) { + return true; + } + overlapping_rect = overlapping_rect.Union(glyph_rect); + } + return false; +} + } // namespace impeller diff --git a/impeller/typographer/text_frame.h b/impeller/typographer/text_frame.h index f64bef492e842..e43af08b2d6ea 100644 --- a/impeller/typographer/text_frame.h +++ b/impeller/typographer/text_frame.h @@ -52,6 +52,16 @@ class TextFrame { /// const std::vector& GetRuns() const; + //---------------------------------------------------------------------------- + /// @brief Whether any of the glyphs of this run are potentially + /// overlapping + /// + /// It is always safe to return true from this method. Generally, + /// any large blobs of text should return true to avoid + /// computationally complex calculations. This information is used + /// to apply opacity peephole optimizations to text blobs. + bool MaybeHasOverlapping() const; + //---------------------------------------------------------------------------- /// @brief Whether any run in this frame has color. bool HasColor() const; diff --git a/impeller/typographer/typographer_unittests.cc b/impeller/typographer/typographer_unittests.cc index 70718e2be20c7..e8b6258fe3466 100644 --- a/impeller/typographer/typographer_unittests.cc +++ b/impeller/typographer/typographer_unittests.cc @@ -253,5 +253,18 @@ TEST_P(TypographerTest, FontGlyphPairTypeChangesHashAndEquals) { ASSERT_FALSE(FontGlyphPair::Equal{}(pair_1, pair_3)); } +TEST_P(TypographerTest, MaybeHasOverlapping) { + SkFont sk_font; + auto frame = TextFrameFromTextBlob(SkTextBlob::MakeFromString("1", sk_font)); + // Single character has no overlapping + ASSERT_FALSE(frame.MaybeHasOverlapping()); + + auto frame_2 = + TextFrameFromTextBlob(SkTextBlob::MakeFromString("123456789", sk_font)); + // Characters probably have overlap due to low fidelity text metrics, but this + // could be fixed. + ASSERT_TRUE(frame_2.MaybeHasOverlapping()); +} + } // namespace testing } // namespace impeller