diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 283aef4df5727..25167d5f6fb9a 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -314,7 +314,7 @@ TEST_P(AiksTest, OpaqueEntitiesGetCoercedToSource) { }); ASSERT_TRUE(entity.size() >= 1); - ASSERT_TRUE(contents->IsOpaque()); + ASSERT_TRUE(contents->IsOpaque({})); ASSERT_EQ(entity[0].GetBlendMode(), BlendMode::kSource); } diff --git a/impeller/aiks/experimental_canvas.cc b/impeller/aiks/experimental_canvas.cc index b9cd19a12afcd..040cb1125d71b 100644 --- a/impeller/aiks/experimental_canvas.cc +++ b/impeller/aiks/experimental_canvas.cc @@ -662,7 +662,7 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity, entity.GetTransform()); entity.SetInheritedOpacity(transform_stack_.back().distributed_opacity); if (entity.GetBlendMode() == BlendMode::kSourceOver && - entity.GetContents()->IsOpaque()) { + entity.GetContents()->IsOpaque(entity.GetTransform())) { entity.SetBlendMode(BlendMode::kSource); } diff --git a/impeller/entity/contents/color_source_contents.cc b/impeller/entity/contents/color_source_contents.cc index 18bc16608a037..9048af516d9dc 100644 --- a/impeller/entity/contents/color_source_contents.cc +++ b/impeller/entity/contents/color_source_contents.cc @@ -54,4 +54,9 @@ void ColorSourceContents::SetInheritedOpacity(Scalar opacity) { inherited_opacity_ = opacity; } +bool ColorSourceContents::AppliesAlphaForStrokeCoverage( + const Matrix& transform) const { + return GetGeometry() && GetGeometry()->ComputeAlphaCoverage(transform) < 1.0; +} + } // namespace impeller diff --git a/impeller/entity/contents/color_source_contents.h b/impeller/entity/contents/color_source_contents.h index 008674a99e7f6..3b548f146f0e4 100644 --- a/impeller/entity/contents/color_source_contents.h +++ b/impeller/entity/contents/color_source_contents.h @@ -126,6 +126,10 @@ class ColorSourceContents : public Contents { return geom.GetPositionBuffer(renderer, entity, pass); } + /// @brief Whether the entity should be treated as non-opaque due to stroke + /// geometry requiring alpha for coverage. + bool AppliesAlphaForStrokeCoverage(const Matrix& transform) const; + template bool DrawGeometry(const ContentContext& renderer, const Entity& entity, diff --git a/impeller/entity/contents/conical_gradient_contents.cc b/impeller/entity/contents/conical_gradient_contents.cc index 691995bf07860..82a9bbff5ea4b 100644 --- a/impeller/entity/contents/conical_gradient_contents.cc +++ b/impeller/entity/contents/conical_gradient_contents.cc @@ -80,7 +80,8 @@ bool ConicalGradientContents::RenderSSBO(const ContentContext& renderer, frag_info.tile_mode = static_cast(tile_mode_); frag_info.decal_border_color = decal_border_color_; frag_info.alpha = - GetOpacityFactor() * GetGeometry()->ComputeAlphaCoverage(entity); + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); if (focus_) { frag_info.focus = focus_.value(); frag_info.focus_radius = focus_radius_; @@ -137,7 +138,8 @@ bool ConicalGradientContents::RenderTexture(const ContentContext& renderer, frag_info.texture_sampler_y_coord_scale = gradient_texture->GetYCoordScale(); frag_info.alpha = - GetOpacityFactor() * GetGeometry()->ComputeAlphaCoverage(entity); + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); frag_info.half_texel = Vector2(0.5 / gradient_texture->GetSize().width, 0.5 / gradient_texture->GetSize().height); diff --git a/impeller/entity/contents/contents.cc b/impeller/entity/contents/contents.cc index 421713761175e..4ab73d78d6e3b 100644 --- a/impeller/entity/contents/contents.cc +++ b/impeller/entity/contents/contents.cc @@ -49,7 +49,7 @@ Contents::Contents() = default; Contents::~Contents() = default; -bool Contents::IsOpaque() const { +bool Contents::IsOpaque(const Matrix& transform) const { return false; } diff --git a/impeller/entity/contents/contents.h b/impeller/entity/contents/contents.h index c26d1d56a49c8..cc0cdeb65c8fd 100644 --- a/impeller/entity/contents/contents.h +++ b/impeller/entity/contents/contents.h @@ -91,7 +91,9 @@ class Contents { /// properties (e.g. the blend mode), clips/visibility culling, or /// inherited opacity. /// - virtual bool IsOpaque() const; + /// @param transform The current transform matrix of the entity that will + /// render this contents. + virtual bool IsOpaque(const Matrix& transform) const; //---------------------------------------------------------------------------- /// @brief Given the current pass space bounding rectangle of the clip diff --git a/impeller/entity/contents/linear_gradient_contents.cc b/impeller/entity/contents/linear_gradient_contents.cc index b117a13f8fcab..28ea871b61830 100644 --- a/impeller/entity/contents/linear_gradient_contents.cc +++ b/impeller/entity/contents/linear_gradient_contents.cc @@ -44,7 +44,7 @@ void LinearGradientContents::SetTileMode(Entity::TileMode tile_mode) { tile_mode_ = tile_mode; } -bool LinearGradientContents::IsOpaque() const { +bool LinearGradientContents::IsOpaque(const Matrix& transform) const { if (GetOpacityFactor() < 1 || tile_mode_ == Entity::TileMode::kDecal) { return false; } @@ -53,7 +53,7 @@ bool LinearGradientContents::IsOpaque() const { return false; } } - return true; + return !AppliesAlphaForStrokeCoverage(transform); } bool LinearGradientContents::CanApplyFastGradient() const { @@ -176,7 +176,8 @@ bool LinearGradientContents::FastLinearGradient(const ContentContext& renderer, FS::FragInfo frag_info; frag_info.alpha = - GetOpacityFactor() * GetGeometry()->ComputeAlphaCoverage(entity); + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); @@ -231,7 +232,8 @@ bool LinearGradientContents::RenderTexture(const ContentContext& renderer, frag_info.texture_sampler_y_coord_scale = gradient_texture->GetYCoordScale(); frag_info.alpha = - GetOpacityFactor() * GetGeometry()->ComputeAlphaCoverage(entity); + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); ; frag_info.half_texel = Vector2(0.5 / gradient_texture->GetSize().width, @@ -284,7 +286,8 @@ bool LinearGradientContents::RenderSSBO(const ContentContext& renderer, frag_info.tile_mode = static_cast(tile_mode_); frag_info.decal_border_color = decal_border_color_; frag_info.alpha = - GetOpacityFactor() * GetGeometry()->ComputeAlphaCoverage(entity); + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); frag_info.start_to_end = end_point_ - start_point_; frag_info.inverse_dot_start_to_end = CalculateInverseDotStartToEnd(start_point_, end_point_); diff --git a/impeller/entity/contents/linear_gradient_contents.h b/impeller/entity/contents/linear_gradient_contents.h index e39d9aae8ef19..ae2e12f2bfdaa 100644 --- a/impeller/entity/contents/linear_gradient_contents.h +++ b/impeller/entity/contents/linear_gradient_contents.h @@ -21,7 +21,7 @@ class LinearGradientContents final : public ColorSourceContents { ~LinearGradientContents() override; // |Contents| - bool IsOpaque() const override; + bool IsOpaque(const Matrix& transform) const override; // |Contents| bool Render(const ContentContext& renderer, diff --git a/impeller/entity/contents/radial_gradient_contents.cc b/impeller/entity/contents/radial_gradient_contents.cc index 64998af951876..31fc52eee3cd3 100644 --- a/impeller/entity/contents/radial_gradient_contents.cc +++ b/impeller/entity/contents/radial_gradient_contents.cc @@ -43,7 +43,7 @@ const std::vector& RadialGradientContents::GetStops() const { return stops_; } -bool RadialGradientContents::IsOpaque() const { +bool RadialGradientContents::IsOpaque(const Matrix& transform) const { if (GetOpacityFactor() < 1 || tile_mode_ == Entity::TileMode::kDecal) { return false; } @@ -52,7 +52,7 @@ bool RadialGradientContents::IsOpaque() const { return false; } } - return true; + return !AppliesAlphaForStrokeCoverage(transform); } bool RadialGradientContents::Render(const ContentContext& renderer, @@ -86,7 +86,8 @@ bool RadialGradientContents::RenderSSBO(const ContentContext& renderer, frag_info.tile_mode = static_cast(tile_mode_); frag_info.decal_border_color = decal_border_color_; frag_info.alpha = - GetOpacityFactor() * GetGeometry()->ComputeAlphaCoverage(entity); + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); auto& host_buffer = renderer.GetTransientsBuffer(); auto colors = CreateGradientColors(colors_, stops_); @@ -136,7 +137,8 @@ bool RadialGradientContents::RenderTexture(const ContentContext& renderer, frag_info.texture_sampler_y_coord_scale = gradient_texture->GetYCoordScale(); frag_info.alpha = - GetOpacityFactor() * GetGeometry()->ComputeAlphaCoverage(entity); + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); frag_info.half_texel = Vector2(0.5 / gradient_texture->GetSize().width, 0.5 / gradient_texture->GetSize().height); diff --git a/impeller/entity/contents/radial_gradient_contents.h b/impeller/entity/contents/radial_gradient_contents.h index b25f3e2c85ec9..21872d801fe07 100644 --- a/impeller/entity/contents/radial_gradient_contents.h +++ b/impeller/entity/contents/radial_gradient_contents.h @@ -21,7 +21,7 @@ class RadialGradientContents final : public ColorSourceContents { ~RadialGradientContents() override; // |Contents| - bool IsOpaque() const override; + bool IsOpaque(const Matrix& transform) const override; // |Contents| bool Render(const ContentContext& renderer, diff --git a/impeller/entity/contents/solid_color_contents.cc b/impeller/entity/contents/solid_color_contents.cc index cb41781729f8f..efddb125f6701 100644 --- a/impeller/entity/contents/solid_color_contents.cc +++ b/impeller/entity/contents/solid_color_contents.cc @@ -28,8 +28,8 @@ bool SolidColorContents::IsSolidColor() const { return true; } -bool SolidColorContents::IsOpaque() const { - return GetColor().IsOpaque(); +bool SolidColorContents::IsOpaque(const Matrix& transform) const { + return GetColor().IsOpaque() && !AppliesAlphaForStrokeCoverage(transform); } std::optional SolidColorContents::GetCoverage( @@ -54,8 +54,8 @@ bool SolidColorContents::Render(const ContentContext& renderer, VS::FrameInfo frame_info; FS::FragInfo frag_info; - frag_info.color = - GetColor().Premultiply() * GetGeometry()->ComputeAlphaCoverage(entity); + frag_info.color = GetColor().Premultiply() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); PipelineBuilderCallback pipeline_callback = [&renderer](ContentContextOptions options) { diff --git a/impeller/entity/contents/solid_color_contents.h b/impeller/entity/contents/solid_color_contents.h index db5abdda94097..af6d5048ec22e 100644 --- a/impeller/entity/contents/solid_color_contents.h +++ b/impeller/entity/contents/solid_color_contents.h @@ -31,7 +31,7 @@ class SolidColorContents final : public ColorSourceContents { bool IsSolidColor() const override; // |Contents| - bool IsOpaque() const override; + bool IsOpaque(const Matrix& transform) const override; // |Contents| std::optional GetCoverage(const Entity& entity) const override; diff --git a/impeller/entity/contents/sweep_gradient_contents.cc b/impeller/entity/contents/sweep_gradient_contents.cc index 9cc65847bd30f..a26d5ac43335a 100644 --- a/impeller/entity/contents/sweep_gradient_contents.cc +++ b/impeller/entity/contents/sweep_gradient_contents.cc @@ -49,7 +49,7 @@ const std::vector& SweepGradientContents::GetStops() const { return stops_; } -bool SweepGradientContents::IsOpaque() const { +bool SweepGradientContents::IsOpaque(const Matrix& transform) const { if (GetOpacityFactor() < 1 || tile_mode_ == Entity::TileMode::kDecal) { return false; } @@ -58,7 +58,7 @@ bool SweepGradientContents::IsOpaque() const { return false; } } - return true; + return !AppliesAlphaForStrokeCoverage(transform); } bool SweepGradientContents::Render(const ContentContext& renderer, @@ -95,7 +95,8 @@ bool SweepGradientContents::RenderSSBO(const ContentContext& renderer, frag_info.tile_mode = static_cast(tile_mode_); frag_info.decal_border_color = decal_border_color_; frag_info.alpha = - GetOpacityFactor() * GetGeometry()->ComputeAlphaCoverage(entity); + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); auto& host_buffer = renderer.GetTransientsBuffer(); auto colors = CreateGradientColors(colors_, stops_); @@ -147,7 +148,8 @@ bool SweepGradientContents::RenderTexture(const ContentContext& renderer, frag_info.tile_mode = static_cast(tile_mode_); frag_info.decal_border_color = decal_border_color_; frag_info.alpha = - GetOpacityFactor() * GetGeometry()->ComputeAlphaCoverage(entity); + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); frag_info.half_texel = Vector2(0.5 / gradient_texture->GetSize().width, 0.5 / gradient_texture->GetSize().height); diff --git a/impeller/entity/contents/sweep_gradient_contents.h b/impeller/entity/contents/sweep_gradient_contents.h index 720158c243942..81e643d660516 100644 --- a/impeller/entity/contents/sweep_gradient_contents.h +++ b/impeller/entity/contents/sweep_gradient_contents.h @@ -22,7 +22,7 @@ class SweepGradientContents final : public ColorSourceContents { ~SweepGradientContents() override; // |Contents| - bool IsOpaque() const override; + bool IsOpaque(const Matrix& transform) const override; // |Contents| bool Render(const ContentContext& renderer, diff --git a/impeller/entity/contents/tiled_texture_contents.cc b/impeller/entity/contents/tiled_texture_contents.cc index f1c6330578d3c..557d1257494d8 100644 --- a/impeller/entity/contents/tiled_texture_contents.cc +++ b/impeller/entity/contents/tiled_texture_contents.cc @@ -96,7 +96,7 @@ bool TiledTextureContents::UsesEmulatedTileMode( } // |Contents| -bool TiledTextureContents::IsOpaque() const { +bool TiledTextureContents::IsOpaque(const Matrix& transform) const { if (GetOpacityFactor() < 1 || x_tile_mode_ == Entity::TileMode::kDecal || y_tile_mode_ == Entity::TileMode::kDecal) { return false; @@ -104,7 +104,7 @@ bool TiledTextureContents::IsOpaque() const { if (color_filter_) { return false; } - return texture_->IsOpaque(); + return texture_->IsOpaque() && !AppliesAlphaForStrokeCoverage(transform); } bool TiledTextureContents::Render(const ContentContext& renderer, @@ -160,14 +160,16 @@ bool TiledTextureContents::Render(const ContentContext& renderer, frag_info.x_tile_mode = static_cast(x_tile_mode_); frag_info.y_tile_mode = static_cast(y_tile_mode_); frag_info.alpha = - GetOpacityFactor() * GetGeometry()->ComputeAlphaCoverage(entity); + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); FSExternal::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); } else { FS::FragInfo frag_info; frag_info.x_tile_mode = static_cast(x_tile_mode_); frag_info.y_tile_mode = static_cast(y_tile_mode_); frag_info.alpha = - GetOpacityFactor() * GetGeometry()->ComputeAlphaCoverage(entity); + GetOpacityFactor() * + GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); } diff --git a/impeller/entity/contents/tiled_texture_contents.h b/impeller/entity/contents/tiled_texture_contents.h index f26ebd36a45f2..b561a5e65832d 100644 --- a/impeller/entity/contents/tiled_texture_contents.h +++ b/impeller/entity/contents/tiled_texture_contents.h @@ -28,7 +28,7 @@ class TiledTextureContents final : public ColorSourceContents { std::function(FilterInput::Ref)>; // |Contents| - bool IsOpaque() const override; + bool IsOpaque(const Matrix& transform) const override; // |Contents| bool Render(const ContentContext& renderer, diff --git a/impeller/entity/entity.cc b/impeller/entity/entity.cc index 2e5229d707b62..f6a969f64e80a 100644 --- a/impeller/entity/entity.cc +++ b/impeller/entity/entity.cc @@ -126,7 +126,8 @@ bool Entity::CanInheritOpacity() const { if (!contents_) { return false; } - if (!((blend_mode_ == BlendMode::kSource && contents_->IsOpaque()) || + if (!((blend_mode_ == BlendMode::kSource && + contents_->IsOpaque(GetTransform())) || blend_mode_ == BlendMode::kSourceOver)) { return false; } @@ -137,7 +138,8 @@ bool Entity::SetInheritedOpacity(Scalar alpha) { if (!CanInheritOpacity()) { return false; } - if (blend_mode_ == BlendMode::kSource && contents_->IsOpaque()) { + if (blend_mode_ == BlendMode::kSource && + contents_->IsOpaque(GetTransform())) { blend_mode_ = BlendMode::kSourceOver; } contents_->SetInheritedOpacity(alpha); diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index c2eb3b89812bd..0f71d72ffc6cc 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -104,7 +104,7 @@ bool EntityPass::GetBoundsLimitIsSnug() const { void EntityPass::AddEntity(Entity entity) { if (entity.GetBlendMode() == BlendMode::kSourceOver && - entity.GetContents()->IsOpaque()) { + entity.GetContents()->IsOpaque(entity.GetTransform())) { entity.SetBlendMode(BlendMode::kSource); } diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 92408fd7da6e2..563c19af04c04 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -2275,62 +2275,118 @@ TEST_P(EntityTest, CoverageForStrokePathWithNegativeValuesInTransform) { } TEST_P(EntityTest, SolidColorContentsIsOpaque) { + Matrix matrix; SolidColorContents contents; + contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); + contents.SetColor(Color::CornflowerBlue()); - ASSERT_TRUE(contents.IsOpaque()); + EXPECT_TRUE(contents.IsOpaque(matrix)); contents.SetColor(Color::CornflowerBlue().WithAlpha(0.5)); - ASSERT_FALSE(contents.IsOpaque()); + EXPECT_FALSE(contents.IsOpaque(matrix)); + + // Create stroked path that required alpha coverage. + contents.SetGeometry(Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), + /*stroke_width=*/0.05)); + contents.SetColor(Color::CornflowerBlue()); + + EXPECT_FALSE(contents.IsOpaque(matrix)); } TEST_P(EntityTest, ConicalGradientContentsIsOpaque) { + Matrix matrix; ConicalGradientContents contents; + contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); + contents.SetColors({Color::CornflowerBlue()}); - ASSERT_FALSE(contents.IsOpaque()); + EXPECT_FALSE(contents.IsOpaque(matrix)); contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); - ASSERT_FALSE(contents.IsOpaque()); + EXPECT_FALSE(contents.IsOpaque(matrix)); + + // Create stroked path that required alpha coverage. + contents.SetGeometry(Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), + /*stroke_width=*/0.05)); + contents.SetColors({Color::CornflowerBlue()}); + + EXPECT_FALSE(contents.IsOpaque(matrix)); } TEST_P(EntityTest, LinearGradientContentsIsOpaque) { + Matrix matrix; LinearGradientContents contents; + contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); + contents.SetColors({Color::CornflowerBlue()}); - ASSERT_TRUE(contents.IsOpaque()); + EXPECT_TRUE(contents.IsOpaque(matrix)); contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); - ASSERT_FALSE(contents.IsOpaque()); + EXPECT_FALSE(contents.IsOpaque(matrix)); contents.SetColors({Color::CornflowerBlue()}); contents.SetTileMode(Entity::TileMode::kDecal); - ASSERT_FALSE(contents.IsOpaque()); + EXPECT_FALSE(contents.IsOpaque(matrix)); + + // Create stroked path that required alpha coverage. + contents.SetGeometry(Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), + /*stroke_width=*/0.05)); + contents.SetColors({Color::CornflowerBlue()}); + + EXPECT_FALSE(contents.IsOpaque(matrix)); } TEST_P(EntityTest, RadialGradientContentsIsOpaque) { + Matrix matrix; RadialGradientContents contents; + contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); + contents.SetColors({Color::CornflowerBlue()}); - ASSERT_TRUE(contents.IsOpaque()); + EXPECT_TRUE(contents.IsOpaque(matrix)); contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); - ASSERT_FALSE(contents.IsOpaque()); + EXPECT_FALSE(contents.IsOpaque(matrix)); contents.SetColors({Color::CornflowerBlue()}); contents.SetTileMode(Entity::TileMode::kDecal); - ASSERT_FALSE(contents.IsOpaque()); + EXPECT_FALSE(contents.IsOpaque(matrix)); + + // Create stroked path that required alpha coverage. + contents.SetGeometry(Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), + /*stroke_width=*/0.05)); + contents.SetColors({Color::CornflowerBlue()}); + + EXPECT_FALSE(contents.IsOpaque(matrix)); } TEST_P(EntityTest, SweepGradientContentsIsOpaque) { + Matrix matrix; RadialGradientContents contents; + contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); + contents.SetColors({Color::CornflowerBlue()}); - ASSERT_TRUE(contents.IsOpaque()); + EXPECT_TRUE(contents.IsOpaque(matrix)); contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); - ASSERT_FALSE(contents.IsOpaque()); + EXPECT_FALSE(contents.IsOpaque(matrix)); contents.SetColors({Color::CornflowerBlue()}); contents.SetTileMode(Entity::TileMode::kDecal); - ASSERT_FALSE(contents.IsOpaque()); + EXPECT_FALSE(contents.IsOpaque(matrix)); + + // Create stroked path that required alpha coverage. + contents.SetGeometry(Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), + /*stroke_width=*/0.05)); + contents.SetColors({Color::CornflowerBlue()}); + + EXPECT_FALSE(contents.IsOpaque(matrix)); } TEST_P(EntityTest, TiledTextureContentsIsOpaque) { + Matrix matrix; auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg"); TiledTextureContents contents; contents.SetTexture(bay_bridge); // This is a placeholder test. Images currently never decompress as opaque // (whether in Flutter or the playground), and so this should currently always // return false in practice. - ASSERT_FALSE(contents.IsOpaque()); + EXPECT_FALSE(contents.IsOpaque(matrix)); } TEST_P(EntityTest, PointFieldGeometryCoverage) { diff --git a/impeller/entity/geometry/circle_geometry.cc b/impeller/entity/geometry/circle_geometry.cc index 6152e1270b20b..b4075757780a3 100644 --- a/impeller/entity/geometry/circle_geometry.cc +++ b/impeller/entity/geometry/circle_geometry.cc @@ -7,6 +7,8 @@ #include "flutter/impeller/entity/geometry/circle_geometry.h" #include "flutter/impeller/entity/geometry/line_geometry.h" +#include "impeller/core/formats.h" +#include "impeller/entity/geometry/geometry.h" namespace impeller { @@ -25,16 +27,26 @@ CircleGeometry::CircleGeometry(const Point& center, FML_DCHECK(stroke_width >= 0); } +// |Geometry| +Scalar CircleGeometry::ComputeAlphaCoverage(const Matrix& transform) const { + if (stroke_width_ < 0) { + return 1; + } + return Geometry::ComputeStrokeAlphaCoverage(transform, stroke_width_); +} + GeometryResult CircleGeometry::GetPositionBuffer(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { auto& transform = entity.GetTransform(); - Scalar half_width = stroke_width_ < 0 ? 0.0 - : LineGeometry::ComputePixelHalfWidth( - transform, stroke_width_); + Scalar half_width = stroke_width_ < 0 + ? 0.0 + : LineGeometry::ComputePixelHalfWidth( + transform, stroke_width_, + pass.GetSampleCount() == SampleCount::kCount4); - std::shared_ptr tessellator = renderer.GetTessellator(); + const std::shared_ptr& tessellator = renderer.GetTessellator(); // We call the StrokedCircle method which will simplify to a // FilledCircleGenerator if the inner_radius is <= 0. diff --git a/impeller/entity/geometry/circle_geometry.h b/impeller/entity/geometry/circle_geometry.h index 8493f11eafea4..7d33fdd390a92 100644 --- a/impeller/entity/geometry/circle_geometry.h +++ b/impeller/entity/geometry/circle_geometry.h @@ -27,6 +27,9 @@ class CircleGeometry final : public Geometry { // |Geometry| bool IsAxisAlignedRect() const override; + // |Geometry| + Scalar ComputeAlphaCoverage(const Matrix& transform) const override; + private: // |Geometry| GeometryResult GetPositionBuffer(const ContentContext& renderer, diff --git a/impeller/entity/geometry/geometry.cc b/impeller/entity/geometry/geometry.cc index d595317d3656a..c92e02dad9aec 100644 --- a/impeller/entity/geometry/geometry.cc +++ b/impeller/entity/geometry/geometry.cc @@ -129,4 +129,17 @@ bool Geometry::CanApplyMaskFilter() const { return true; } +// static +Scalar Geometry::ComputeStrokeAlphaCoverage(const Matrix& transform, + Scalar stroke_width) { + Scalar scaled_stroke_width = transform.GetMaxBasisLengthXY() * stroke_width; + // If the stroke width is 0 or greater than kMinStrokeSizeMSAA, don't apply + // any additional alpha. This is intended to match Skia behavior. + if (scaled_stroke_width == 0.0 || scaled_stroke_width >= kMinStrokeSizeMSAA) { + return 1.0; + } + // This scalling is eyeballed from Skia. + return std::clamp(scaled_stroke_width * 2.0f, 0.f, 1.f); +} + } // namespace impeller diff --git a/impeller/entity/geometry/geometry.h b/impeller/entity/geometry/geometry.h index 12cbd8f08de37..09d4384882a1b 100644 --- a/impeller/entity/geometry/geometry.h +++ b/impeller/entity/geometry/geometry.h @@ -16,6 +16,13 @@ namespace impeller { class Tessellator; +/// @brief The minimum stroke size can be less than one physical pixel because +/// of MSAA, but no less that half a physical pixel otherwise we might +/// not hit one of the sample positions. +static constexpr Scalar kMinStrokeSizeMSAA = 0.5f; + +static constexpr Scalar kMinStrokeSize = 1.0f; + struct GeometryResult { enum class Mode { /// The geometry has no overlapping triangles. @@ -91,6 +98,11 @@ class Geometry { virtual std::optional GetCoverage(const Matrix& transform) const = 0; + /// @brief Compute an alpha value to simulate lower coverage of fractional + /// pixel strokes. + static Scalar ComputeStrokeAlphaCoverage(const Matrix& entity, + Scalar stroke_width); + /// @brief Determines if this geometry, transformed by the given /// `transform`, will completely cover all surface area of the given /// `rect`. @@ -107,7 +119,7 @@ class Geometry { virtual bool CanApplyMaskFilter() const; - virtual Scalar ComputeAlphaCoverage(const Entity& entitys) const { + virtual Scalar ComputeAlphaCoverage(const Matrix& transform) const { return 1.0; } diff --git a/impeller/entity/geometry/geometry_unittests.cc b/impeller/entity/geometry/geometry_unittests.cc index b433828347a54..4b29450bf595d 100644 --- a/impeller/entity/geometry/geometry_unittests.cc +++ b/impeller/entity/geometry/geometry_unittests.cc @@ -137,19 +137,19 @@ TEST(EntityGeometryTest, GeometryResultHasReasonableDefaults) { } TEST(EntityGeometryTest, AlphaCoverageStrokePaths) { - Entity entity; - entity.SetTransform(Matrix::MakeScale(Vector2{3.0, 3.0})); - EXPECT_EQ(Geometry::MakeStrokePath({}, 0.5)->ComputeAlphaCoverage(entity), 1); - EXPECT_EQ(Geometry::MakeStrokePath({}, 0.1)->ComputeAlphaCoverage(entity), 1); - EXPECT_EQ(Geometry::MakeStrokePath({}, 0.05)->ComputeAlphaCoverage(entity), - 1); - EXPECT_NEAR(Geometry::MakeStrokePath({}, 0.01)->ComputeAlphaCoverage(entity), - 0.6, 0.1); + auto matrix = Matrix::MakeScale(Vector2{3.0, 3.0}); + EXPECT_EQ(Geometry::MakeStrokePath({}, 0.5)->ComputeAlphaCoverage(matrix), 1); + EXPECT_NEAR(Geometry::MakeStrokePath({}, 0.1)->ComputeAlphaCoverage(matrix), + 0.6, 0.05); + EXPECT_NEAR(Geometry::MakeStrokePath({}, 0.05)->ComputeAlphaCoverage(matrix), + 0.3, 0.05); + EXPECT_NEAR(Geometry::MakeStrokePath({}, 0.01)->ComputeAlphaCoverage(matrix), + 0.1, 0.1); EXPECT_NEAR( - Geometry::MakeStrokePath({}, 0.0000005)->ComputeAlphaCoverage(entity), + Geometry::MakeStrokePath({}, 0.0000005)->ComputeAlphaCoverage(matrix), 1e-05, 0.001); - EXPECT_EQ(Geometry::MakeStrokePath({}, 0)->ComputeAlphaCoverage(entity), 1); - EXPECT_EQ(Geometry::MakeStrokePath({}, 40)->ComputeAlphaCoverage(entity), 1); + EXPECT_EQ(Geometry::MakeStrokePath({}, 0)->ComputeAlphaCoverage(matrix), 1); + EXPECT_EQ(Geometry::MakeStrokePath({}, 40)->ComputeAlphaCoverage(matrix), 1); } } // namespace testing diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index c04c32626f7a3..72c20cd83e436 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "impeller/entity/geometry/line_geometry.h" +#include "impeller/entity/geometry/geometry.h" namespace impeller { @@ -12,19 +13,21 @@ LineGeometry::LineGeometry(Point p0, Point p1, Scalar width, Cap cap) } Scalar LineGeometry::ComputePixelHalfWidth(const Matrix& transform, - Scalar width) { - auto determinant = transform.GetDeterminant(); - if (determinant == 0) { - return 0.0f; + Scalar width, + bool msaa) { + Scalar max_basis = transform.GetMaxBasisLengthXY(); + if (max_basis == 0) { + return {}; } - Scalar min_size = 1.0f / sqrt(std::abs(determinant)); + Scalar min_size = (msaa ? kMinStrokeSize : kMinStrokeSizeMSAA) / max_basis; return std::max(width, min_size) * 0.5f; } Vector2 LineGeometry::ComputeAlongVector(const Matrix& transform, - bool allow_zero_length) const { - Scalar stroke_half_width = ComputePixelHalfWidth(transform, width_); + bool allow_zero_length, + bool msaa) const { + Scalar stroke_half_width = ComputePixelHalfWidth(transform, width_, msaa); if (stroke_half_width < kEhCloseEnough) { return {}; } @@ -44,8 +47,9 @@ Vector2 LineGeometry::ComputeAlongVector(const Matrix& transform, bool LineGeometry::ComputeCorners(Point corners[4], const Matrix& transform, - bool extend_endpoints) const { - auto along = ComputeAlongVector(transform, extend_endpoints); + bool extend_endpoints, + bool msaa) const { + auto along = ComputeAlongVector(transform, extend_endpoints, msaa); if (along.IsZero()) { return false; } @@ -64,13 +68,18 @@ bool LineGeometry::ComputeCorners(Point corners[4], return true; } +Scalar LineGeometry::ComputeAlphaCoverage(const Matrix& entity) const { + return Geometry::ComputeStrokeAlphaCoverage(entity, width_); +} + GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { using VT = SolidFillVertexShader::PerVertexData; auto& transform = entity.GetTransform(); - auto radius = ComputePixelHalfWidth(transform, width_); + auto radius = ComputePixelHalfWidth( + transform, width_, pass.GetSampleCount() == SampleCount::kCount4); if (cap_ == Cap::kRound) { std::shared_ptr tessellator = renderer.GetTessellator(); @@ -79,7 +88,8 @@ GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, } Point corners[4]; - if (!ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { + if (!ComputeCorners(corners, transform, cap_ == Cap::kSquare, + pass.GetSampleCount() == SampleCount::kCount4)) { return kEmptyResult; } @@ -110,7 +120,8 @@ GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, std::optional LineGeometry::GetCoverage(const Matrix& transform) const { Point corners[4]; - if (!ComputeCorners(corners, transform, cap_ != Cap::kButt)) { + // Note: MSAA boolean doesn't matter for coverage computation. + if (!ComputeCorners(corners, transform, cap_ != Cap::kButt, /*msaa=*/false)) { return {}; } diff --git a/impeller/entity/geometry/line_geometry.h b/impeller/entity/geometry/line_geometry.h index 58e00e5d32588..861ae4a0d8624 100644 --- a/impeller/entity/geometry/line_geometry.h +++ b/impeller/entity/geometry/line_geometry.h @@ -16,7 +16,9 @@ class LineGeometry final : public Geometry { ~LineGeometry() = default; - static Scalar ComputePixelHalfWidth(const Matrix& transform, Scalar width); + static Scalar ComputePixelHalfWidth(const Matrix& transform, + Scalar width, + bool msaa); // |Geometry| bool CoversArea(const Matrix& transform, const Rect& rect) const override; @@ -24,6 +26,8 @@ class LineGeometry final : public Geometry { // |Geometry| bool IsAxisAlignedRect() const override; + Scalar ComputeAlphaCoverage(const Matrix& transform) const override; + private: // Computes the 4 corners of a rectangle that defines the line and // possibly extended endpoints which will be rendered under the given @@ -41,10 +45,12 @@ class LineGeometry final : public Geometry { // @return true if the transform and width were not degenerate bool ComputeCorners(Point corners[4], const Matrix& transform, - bool extend_endpoints) const; + bool extend_endpoints, + bool msaa) const; Vector2 ComputeAlongVector(const Matrix& transform, - bool allow_zero_length) const; + bool allow_zero_length, + bool msaa) const; // |Geometry| GeometryResult GetPositionBuffer(const ContentContext& renderer, diff --git a/impeller/entity/geometry/stroke_path_geometry.cc b/impeller/entity/geometry/stroke_path_geometry.cc index 5956adff1c34f..cd188c6044a88 100644 --- a/impeller/entity/geometry/stroke_path_geometry.cc +++ b/impeller/entity/geometry/stroke_path_geometry.cc @@ -17,13 +17,6 @@ using VS = SolidFillVertexShader; namespace { -/// @brief The minimum stroke size can be less than one physical pixel because -/// of MSAA, but no less that half a physical pixel otherwise we might -/// not hit one of the sample positions. -static constexpr Scalar kMinStrokeSizeMSAA = 0.5f; - -static constexpr Scalar kMinStrokeSize = 1.0f; - template using CapProc = std::function= kMinStrokeSizeMSAA) { - return 1.0; - } - // This scalling is eyeballed from Skia. - return std::clamp(scaled_stroke_width * 20.0f, 0.f, 1.f); +Scalar StrokePathGeometry::ComputeAlphaCoverage(const Matrix& transform) const { + return Geometry::ComputeStrokeAlphaCoverage(transform, stroke_width_); } GeometryResult StrokePathGeometry::GetPositionBuffer( @@ -584,15 +569,15 @@ GeometryResult StrokePathGeometry::GetPositionBuffer( if (stroke_width_ < 0.0) { return {}; } - auto determinant = entity.GetTransform().GetDeterminant(); - if (determinant == 0) { + Scalar max_basis = entity.GetTransform().GetMaxBasisLengthXY(); + if (max_basis == 0) { return {}; } Scalar min_size = (pass.GetSampleCount() == SampleCount::kCount4 ? kMinStrokeSizeMSAA : kMinStrokeSize) / - sqrt(std::abs(determinant)); + max_basis; Scalar stroke_width = std::max(stroke_width_, min_size); auto& host_buffer = renderer.GetTransientsBuffer(); @@ -641,12 +626,12 @@ std::optional StrokePathGeometry::GetCoverage( if (stroke_join_ == Join::kMiter) { max_radius = std::max(max_radius, miter_limit_ * 0.5f); } - Scalar determinant = transform.GetDeterminant(); - if (determinant == 0) { - return std::nullopt; + Scalar max_basis = transform.GetMaxBasisLengthXY(); + if (max_basis == 0) { + return {}; } // Use the most conervative coverage setting. - Scalar min_size = kMinStrokeSize / sqrt(std::abs(determinant)); + Scalar min_size = kMinStrokeSize / max_basis; max_radius *= std::max(stroke_width_, min_size); return path_bounds->Expand(max_radius).TransformBounds(transform); } diff --git a/impeller/entity/geometry/stroke_path_geometry.h b/impeller/entity/geometry/stroke_path_geometry.h index 69bf18774bc76..e5575f8147ed6 100644 --- a/impeller/entity/geometry/stroke_path_geometry.h +++ b/impeller/entity/geometry/stroke_path_geometry.h @@ -6,6 +6,7 @@ #define FLUTTER_IMPELLER_ENTITY_GEOMETRY_STROKE_PATH_GEOMETRY_H_ #include "impeller/entity/geometry/geometry.h" +#include "impeller/geometry/matrix.h" namespace impeller { @@ -28,7 +29,7 @@ class StrokePathGeometry final : public Geometry { Join GetStrokeJoin() const; - Scalar ComputeAlphaCoverage(const Entity& entity) const override; + Scalar ComputeAlphaCoverage(const Matrix& transform) const override; private: // |Geometry|