diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index fd7804c0fa31e..227835a713578 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -3867,6 +3867,54 @@ TEST_P(AiksTest, GaussianBlurMipMapImageFilter) { #endif } +TEST_P(AiksTest, SaveLayersCloseToRootPassSizeAreScaledUp) { + Canvas canvas; + // Create a subpass with no bounds hint and an entity coverage of (95, 95). + canvas.SaveLayer({ + .color = Color::Black().WithAlpha(0.5), + }); + canvas.DrawRect(Rect::MakeLTRB(0, 0, 10, 10), {.color = Color::Red()}); + canvas.DrawRect(Rect::MakeLTRB(0, 0, 95, 95), {.color = Color::Blue()}); + canvas.Restore(); + + Picture picture = canvas.EndRecordingAsPicture(); + std::shared_ptr cache = + std::make_shared(GetContext()->GetResourceAllocator()); + AiksContext aiks_context(GetContext(), nullptr, cache); + picture.ToImage(aiks_context, {100, 100}); + + for (auto it = cache->GetTextureDataBegin(); it != cache->GetTextureDataEnd(); + ++it) { + EXPECT_EQ(it->texture->GetTextureDescriptor().size, ISize(100, 100)); + } +} + +TEST_P(AiksTest, SaveLayersCloseToRootPassSizeAreNotScaledUpPastBoundsHint) { + Canvas canvas; + canvas.SaveLayer( + { + .color = Color::Black().WithAlpha(0.5), + }, + Rect::MakeSize(ISize(95, 95))); + canvas.DrawRect(Rect::MakeLTRB(0, 0, 100, 100), {.color = Color::Red()}); + canvas.DrawRect(Rect::MakeLTRB(50, 50, 150, 150), {.color = Color::Blue()}); + canvas.Restore(); + + Picture picture = canvas.EndRecordingAsPicture(); + std::shared_ptr cache = + std::make_shared(GetContext()->GetResourceAllocator()); + AiksContext aiks_context(GetContext(), nullptr, cache); + picture.ToImage(aiks_context, {100, 100}); + + // We expect a single 100x100 texture and the rest should be 95x95. + EXPECT_EQ(cache->GetTextureDataBegin()->texture->GetTextureDescriptor().size, + ISize(100, 100)); + for (auto it = ++cache->GetTextureDataBegin(); + it != cache->GetTextureDataEnd(); ++it) { + EXPECT_EQ(it->texture->GetTextureDescriptor().size, ISize(95, 95)); + } +} + TEST_P(AiksTest, ImageColorSourceEffectTransform) { // Compare with https://fiddle.skia.org/c/6cdc5aefb291fda3833b806ca347a885 diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index 701910f5e893a..52e44c9c3efc9 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -23,7 +23,9 @@ #include "impeller/entity/entity.h" #include "impeller/entity/inline_pass_context.h" #include "impeller/geometry/color.h" +#include "impeller/geometry/matrix.h" #include "impeller/geometry/rect.h" +#include "impeller/geometry/size.h" #include "impeller/renderer/command_buffer.h" #ifdef IMPELLER_DEBUG @@ -67,6 +69,13 @@ std::optional EntityPass::GetBoundsLimit() const { return bounds_limit_; } +std::optional EntityPass::GetTransformedBoundsLimit() const { + if (bounds_limit_.has_value()) { + return bounds_limit_->TransformBounds(transform_); + } + return std::nullopt; +} + void EntityPass::AddEntity(Entity entity) { if (entity.GetBlendMode() == BlendMode::kSourceOver && entity.GetContents()->IsOpaque()) { @@ -190,12 +199,11 @@ std::optional EntityPass::GetSubpassCoverage( return std::nullopt; } - if (!subpass.bounds_limit_.has_value()) { + auto user_bounds_coverage = subpass.GetTransformedBoundsLimit(); + if (!user_bounds_coverage.has_value()) { return entities_coverage; } - auto user_bounds_coverage = - subpass.bounds_limit_->TransformBounds(subpass.transform_); - return entities_coverage->Intersection(user_bounds_coverage); + return entities_coverage->Intersection(user_bounds_coverage.value()); } EntityPass* EntityPass::GetSuperpass() const { @@ -485,6 +493,43 @@ bool EntityPass::Render(ContentContext& renderer, clip_coverage_stack); // clip_coverage_stack } +// When a subpass size is close to, but still smaller than the root pass +// size and smaller than the bounds hint, we may scale it up to the root +// pass size. This will improve performance by improving the efficiency of +// the render target cache, as only textures with exactly the same sizes + +// descriptors can be recycled. +static ISize MaybeRoundUpTextureSize(ISize subpass_size, + ISize root_pass_size, + std::optional bounds_limit) { + // If the subpass is already bigger than the root pass size, + // return the existing subpass size. + if (subpass_size.width > root_pass_size.width || + subpass_size.height > root_pass_size.height) { + return subpass_size; + } + + // If there is a bounds limit and it is tigher than the root pass size, + // return the existing subpass size. This case could be removed if we + // conditionally inserted clips/scissor instead. + if (bounds_limit.has_value()) { + auto bounds_size = bounds_limit->GetSize(); + if (bounds_size.width < root_pass_size.width || + bounds_size.height < root_pass_size.height) { + return subpass_size; + } + } + + // If the subpass size is within 10% of the root pass size, round up + // to the root pass size. + if (root_pass_size.width - subpass_size.width <= + (0.1 * root_pass_size.width) && + root_pass_size.height - subpass_size.height <= + (0.1 * root_pass_size.height)) { + return root_pass_size; + } + return subpass_size; +} + EntityPass::EntityResult EntityPass::GetEntityForElement( const EntityPass::Element& element, ContentContext& renderer, @@ -618,6 +663,8 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( return EntityPass::EntityResult::Skip(); } + subpass_size = MaybeRoundUpTextureSize( + subpass_size, root_pass_size, subpass->GetTransformedBoundsLimit()); auto subpass_target = CreateRenderTarget( renderer, // renderer subpass_size, // size diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index 7de68d0d9ad11..f1d4b51715b6e 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -157,6 +157,9 @@ class EntityPass { required_mip_count_ = mip_count; } + /// @brief Return the local bounds transformed intro screen coordinate space. + std::optional GetTransformedBoundsLimit() const; + //---------------------------------------------------------------------------- /// @brief Computes the coverage of a given subpass. This is used to /// determine the texture size of a given subpass before it's rendered