diff --git a/engine/src/flutter/ci/licenses_golden/excluded_files b/engine/src/flutter/ci/licenses_golden/excluded_files index 551126e361..fcfee9f6ad 100644 --- a/engine/src/flutter/ci/licenses_golden/excluded_files +++ b/engine/src/flutter/ci/licenses_golden/excluded_files @@ -121,6 +121,7 @@ ../../../flutter/impeller/README.md ../../../flutter/impeller/aiks/aiks_unittests.cc ../../../flutter/impeller/aiks/canvas_unittests.cc +../../../flutter/impeller/aiks/testing ../../../flutter/impeller/archivist/archivist_unittests.cc ../../../flutter/impeller/base/README.md ../../../flutter/impeller/base/base_unittests.cc diff --git a/engine/src/flutter/impeller/aiks/BUILD.gn b/engine/src/flutter/impeller/aiks/BUILD.gn index b0fe875cfe..1e09105d1d 100644 --- a/engine/src/flutter/impeller/aiks/BUILD.gn +++ b/engine/src/flutter/impeller/aiks/BUILD.gn @@ -50,6 +50,9 @@ impeller_component("aiks_unittests") { sources = [ "aiks_unittests.cc", "canvas_unittests.cc", + "testing/context_mock.h", + "testing/context_spy.cc", + "testing/context_spy.h", ] deps = [ ":aiks", @@ -69,7 +72,12 @@ impeller_component("aiks_unittests_golden") { "IMPELLER_ENABLE_VALIDATION=1", ] - sources = [ "aiks_unittests.cc" ] + sources = [ + "aiks_unittests.cc", + "testing/context_mock.h", + "testing/context_spy.cc", + "testing/context_spy.h", + ] deps = [ ":aiks", ":aiks_playground", diff --git a/engine/src/flutter/impeller/aiks/aiks_unittests.cc b/engine/src/flutter/impeller/aiks/aiks_unittests.cc index 9ce1a517f1..0c13711c27 100644 --- a/engine/src/flutter/impeller/aiks/aiks_unittests.cc +++ b/engine/src/flutter/impeller/aiks/aiks_unittests.cc @@ -16,6 +16,7 @@ #include "impeller/aiks/canvas.h" #include "impeller/aiks/image.h" #include "impeller/aiks/paint_pass_delegate.h" +#include "impeller/aiks/testing/context_spy.h" #include "impeller/entity/contents/color_source_contents.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" #include "impeller/entity/contents/scene_contents.h" @@ -1999,9 +2000,105 @@ TEST_P(AiksTest, DrawPaintAbsorbsClears) { {.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kSource}); Picture picture = canvas.EndRecordingAsPicture(); - - ASSERT_EQ(picture.pass->GetElementCount(), 0u); ASSERT_EQ(picture.pass->GetClearColor(), Color::CornflowerBlue()); + + std::shared_ptr spy = ContextSpy::Make(); + std::shared_ptr real_context = GetContext(); + std::shared_ptr mock_context = spy->MakeContext(real_context); + AiksContext renderer(mock_context); + std::shared_ptr image = picture.ToImage(renderer, {300, 300}); + + ASSERT_EQ(spy->render_passes_.size(), 1llu); + std::shared_ptr render_pass = spy->render_passes_[0]; + ASSERT_EQ(render_pass->GetCommands().size(), 0llu); +} + +TEST_P(AiksTest, DrawRectAbsorbsClears) { + Canvas canvas; + canvas.DrawRect({0, 0, 300, 300}, + {.color = Color::Red(), .blend_mode = BlendMode::kSource}); + canvas.DrawRect({0, 0, 300, 300}, {.color = Color::CornflowerBlue(), + .blend_mode = BlendMode::kSource}); + + std::shared_ptr spy = ContextSpy::Make(); + Picture picture = canvas.EndRecordingAsPicture(); + std::shared_ptr real_context = GetContext(); + std::shared_ptr mock_context = spy->MakeContext(real_context); + AiksContext renderer(mock_context); + std::shared_ptr image = picture.ToImage(renderer, {300, 300}); + + ASSERT_EQ(spy->render_passes_.size(), 1llu); + std::shared_ptr render_pass = spy->render_passes_[0]; + ASSERT_EQ(render_pass->GetCommands().size(), 0llu); +} + +TEST_P(AiksTest, DrawRectAbsorbsClearsNegativeRRect) { + Canvas canvas; + canvas.DrawRRect({0, 0, 300, 300}, 5.0, + {.color = Color::Red(), .blend_mode = BlendMode::kSource}); + canvas.DrawRRect( + {0, 0, 300, 300}, 5.0, + {.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kSource}); + + std::shared_ptr spy = ContextSpy::Make(); + Picture picture = canvas.EndRecordingAsPicture(); + std::shared_ptr real_context = GetContext(); + std::shared_ptr mock_context = spy->MakeContext(real_context); + AiksContext renderer(mock_context); + std::shared_ptr image = picture.ToImage(renderer, {300, 300}); + + ASSERT_EQ(spy->render_passes_.size(), 1llu); + std::shared_ptr render_pass = spy->render_passes_[0]; + ASSERT_EQ(render_pass->GetCommands().size(), 2llu); +} + +TEST_P(AiksTest, DrawRectAbsorbsClearsNegativeRotation) { + Canvas canvas; + canvas.Translate(Vector3(150.0, 150.0, 0.0)); + canvas.Rotate(Degrees(45.0)); + canvas.Translate(Vector3(-150.0, -150.0, 0.0)); + canvas.DrawRect({0, 0, 300, 300}, + {.color = Color::Red(), .blend_mode = BlendMode::kSource}); + + std::shared_ptr spy = ContextSpy::Make(); + Picture picture = canvas.EndRecordingAsPicture(); + std::shared_ptr real_context = GetContext(); + std::shared_ptr mock_context = spy->MakeContext(real_context); + AiksContext renderer(mock_context); + std::shared_ptr image = picture.ToImage(renderer, {300, 300}); + + ASSERT_EQ(spy->render_passes_.size(), 1llu); + std::shared_ptr render_pass = spy->render_passes_[0]; + ASSERT_EQ(render_pass->GetCommands().size(), 1llu); +} + +TEST_P(AiksTest, DrawRectAbsorbsClearsNegative) { + Canvas canvas; + canvas.DrawRect({0, 0, 300, 300}, + {.color = Color::Red(), .blend_mode = BlendMode::kSource}); + canvas.DrawRect({0, 0, 300, 300}, {.color = Color::CornflowerBlue(), + .blend_mode = BlendMode::kSource}); + + std::shared_ptr spy = ContextSpy::Make(); + Picture picture = canvas.EndRecordingAsPicture(); + std::shared_ptr real_context = GetContext(); + std::shared_ptr mock_context = spy->MakeContext(real_context); + AiksContext renderer(mock_context); + std::shared_ptr image = picture.ToImage(renderer, {301, 301}); + + ASSERT_EQ(spy->render_passes_.size(), 1llu); + std::shared_ptr render_pass = spy->render_passes_[0]; + ASSERT_EQ(render_pass->GetCommands().size(), 2llu); +} + +TEST_P(AiksTest, CollapsedDrawPaintInSubpass) { + Canvas canvas; + canvas.DrawPaint( + {.color = Color::Yellow(), .blend_mode = BlendMode::kSource}); + canvas.SaveLayer({.blend_mode = BlendMode::kMultiply}); + canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource}); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) { diff --git a/engine/src/flutter/impeller/aiks/canvas.cc b/engine/src/flutter/impeller/aiks/canvas.cc index 204f8425d4..3e2962ea18 100644 --- a/engine/src/flutter/impeller/aiks/canvas.cc +++ b/engine/src/flutter/impeller/aiks/canvas.cc @@ -172,16 +172,6 @@ void Canvas::DrawPath(const Path& path, const Paint& paint) { } void Canvas::DrawPaint(const Paint& paint) { - if (xformation_stack_.size() == 1 && // If we're recording the root pass, - GetCurrentPass().GetElementCount() == 0 && // and this is the first item, - (paint.blend_mode == BlendMode::kSourceOver || - paint.blend_mode == BlendMode::kSource) && - paint.color.alpha >= 1.0f) { - // Then we can absorb this drawPaint as the clear color of the pass. - GetCurrentPass().SetClearColor(paint.color); - return; - } - Entity entity; entity.SetTransformation(GetCurrentTransformation()); entity.SetStencilDepth(GetStencilDepth()); diff --git a/engine/src/flutter/impeller/aiks/testing/context_mock.h b/engine/src/flutter/impeller/aiks/testing/context_mock.h new file mode 100644 index 0000000000..25283e7498 --- /dev/null +++ b/engine/src/flutter/impeller/aiks/testing/context_mock.h @@ -0,0 +1,86 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include + +#include "gmock/gmock.h" +#include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/context.h" +#include "impeller/renderer/render_target.h" + +namespace impeller { +namespace testing { + +class CommandBufferMock : public CommandBuffer { + public: + CommandBufferMock(std::weak_ptr context) + : CommandBuffer(context) {} + + MOCK_CONST_METHOD0(IsValid, bool()); + + MOCK_CONST_METHOD1(SetLabel, void(const std::string& label)); + + MOCK_METHOD1(SubmitCommandsAsync, + bool(std::shared_ptr render_pass)); + + MOCK_METHOD1(OnCreateRenderPass, + std::shared_ptr(RenderTarget render_target)); + + static std::shared_ptr ForwardOnCreateRenderPass( + CommandBuffer* command_buffer, + RenderTarget render_target) { + return command_buffer->OnCreateRenderPass(render_target); + } + + MOCK_CONST_METHOD0(OnCreateBlitPass, std::shared_ptr()); + static std::shared_ptr ForwardOnCreateBlitPass( + CommandBuffer* command_buffer) { + return command_buffer->OnCreateBlitPass(); + } + + MOCK_METHOD1(OnSubmitCommands, bool(CompletionCallback callback)); + static bool ForwardOnSubmitCommands(CommandBuffer* command_buffer, + CompletionCallback callback) { + return command_buffer->OnSubmitCommands(callback); + } + + MOCK_METHOD0(OnWaitUntilScheduled, void()); + static void ForwardOnWaitUntilScheduled(CommandBuffer* command_buffer) { + return command_buffer->OnWaitUntilScheduled(); + } + + MOCK_CONST_METHOD0(OnCreateComputePass, std::shared_ptr()); + static std::shared_ptr ForwardOnCreateComputePass( + CommandBuffer* command_buffer) { + return command_buffer->OnCreateComputePass(); + } +}; + +class ContextMock : public Context { + public: + MOCK_CONST_METHOD0(DescribeGpuModel, std::string()); + + MOCK_CONST_METHOD0(IsValid, bool()); + + MOCK_CONST_METHOD0(GetCapabilities, + const std::shared_ptr&()); + + MOCK_METHOD1(UpdateOffscreenLayerPixelFormat, bool(PixelFormat format)); + + MOCK_CONST_METHOD0(GetResourceAllocator, std::shared_ptr()); + + MOCK_CONST_METHOD0(GetShaderLibrary, std::shared_ptr()); + + MOCK_CONST_METHOD0(GetSamplerLibrary, std::shared_ptr()); + + MOCK_CONST_METHOD0(GetPipelineLibrary, std::shared_ptr()); + + MOCK_CONST_METHOD0(CreateCommandBuffer, std::shared_ptr()); +}; + +} // namespace testing +} // namespace impeller diff --git a/engine/src/flutter/impeller/aiks/testing/context_spy.cc b/engine/src/flutter/impeller/aiks/testing/context_spy.cc new file mode 100644 index 0000000000..f467773f1d --- /dev/null +++ b/engine/src/flutter/impeller/aiks/testing/context_spy.cc @@ -0,0 +1,111 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/aiks/testing/context_spy.h" + +namespace impeller { +namespace testing { + +std::shared_ptr ContextSpy::Make() { + return std::shared_ptr(new ContextSpy()); +} + +std::shared_ptr ContextSpy::MakeContext( + const std::shared_ptr& real_context) { + std::shared_ptr mock_context = + std::make_shared<::testing::NiceMock>(); + std::shared_ptr shared_this = shared_from_this(); + + ON_CALL(*mock_context, IsValid).WillByDefault([real_context]() { + return real_context->IsValid(); + }); + + ON_CALL(*mock_context, GetCapabilities) + .WillByDefault( + [real_context]() -> const std::shared_ptr& { + return real_context->GetCapabilities(); + }); + + ON_CALL(*mock_context, UpdateOffscreenLayerPixelFormat) + .WillByDefault([real_context](PixelFormat format) { + return real_context->UpdateOffscreenLayerPixelFormat(format); + }); + + ON_CALL(*mock_context, GetResourceAllocator).WillByDefault([real_context]() { + return real_context->GetResourceAllocator(); + }); + + ON_CALL(*mock_context, GetShaderLibrary).WillByDefault([real_context]() { + return real_context->GetShaderLibrary(); + }); + + ON_CALL(*mock_context, GetSamplerLibrary).WillByDefault([real_context]() { + return real_context->GetSamplerLibrary(); + }); + + ON_CALL(*mock_context, GetPipelineLibrary).WillByDefault([real_context]() { + return real_context->GetPipelineLibrary(); + }); + + ON_CALL(*mock_context, CreateCommandBuffer) + .WillByDefault([real_context, shared_this]() { + auto real_buffer = real_context->CreateCommandBuffer(); + auto spy = std::make_shared<::testing::NiceMock>( + real_context); + + ON_CALL(*spy, IsValid).WillByDefault([real_buffer]() { + return real_buffer->IsValid(); + }); + + ON_CALL(*spy, SetLabel) + .WillByDefault([real_buffer](const std::string& label) { + return real_buffer->SetLabel(label); + }); + + ON_CALL(*spy, SubmitCommandsAsync) + .WillByDefault([real_buffer]( + std::shared_ptr render_pass) { + return real_buffer->SubmitCommandsAsync(std::move(render_pass)); + }); + + ON_CALL(*spy, OnCreateRenderPass) + .WillByDefault( + [real_buffer, shared_this](const RenderTarget& render_target) { + std::shared_ptr result = + CommandBufferMock::ForwardOnCreateRenderPass( + real_buffer.get(), render_target); + shared_this->render_passes_.push_back(result); + return result; + }); + + ON_CALL(*spy, OnCreateBlitPass).WillByDefault([real_buffer]() { + return CommandBufferMock::ForwardOnCreateBlitPass(real_buffer.get()); + }); + + ON_CALL(*spy, OnSubmitCommands) + .WillByDefault( + [real_buffer](CommandBuffer::CompletionCallback callback) { + return CommandBufferMock::ForwardOnSubmitCommands( + real_buffer.get(), std::move(callback)); + }); + + ON_CALL(*spy, OnWaitUntilScheduled).WillByDefault([real_buffer]() { + return CommandBufferMock::ForwardOnWaitUntilScheduled( + real_buffer.get()); + }); + + ON_CALL(*spy, OnCreateComputePass).WillByDefault([real_buffer]() { + return CommandBufferMock::ForwardOnCreateComputePass( + real_buffer.get()); + }); + + return spy; + }); + + return mock_context; +} + +} // namespace testing + +} // namespace impeller diff --git a/engine/src/flutter/impeller/aiks/testing/context_spy.h b/engine/src/flutter/impeller/aiks/testing/context_spy.h new file mode 100644 index 0000000000..9d32f2e136 --- /dev/null +++ b/engine/src/flutter/impeller/aiks/testing/context_spy.h @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include "impeller/aiks/testing/context_mock.h" + +namespace impeller { +namespace testing { + +/// Forwards calls to a real Context but can store information about how +/// the Context was used. +class ContextSpy : public std::enable_shared_from_this { + public: + static std::shared_ptr Make(); + + std::shared_ptr MakeContext( + const std::shared_ptr& real_context); + + std::vector> render_passes_; + + private: + ContextSpy() = default; +}; + +} // namespace testing + +} // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/contents.h b/engine/src/flutter/impeller/entity/contents/contents.h index 9d42fb55fc..09d45a5b67 100644 --- a/engine/src/flutter/impeller/entity/contents/contents.h +++ b/engine/src/flutter/impeller/entity/contents/contents.h @@ -117,6 +117,11 @@ class Contents { /// Use of this method is invalid if CanAcceptOpacity returns false. virtual void SetInheritedOpacity(Scalar opacity); + virtual std::optional AsBackgroundColor(const Entity& entity, + ISize target_size) const { + return {}; + } + private: std::optional coverage_hint_; std::optional color_source_size_; diff --git a/engine/src/flutter/impeller/entity/contents/solid_color_contents.cc b/engine/src/flutter/impeller/entity/contents/solid_color_contents.cc index 1c5b032cd4..8814e177b8 100644 --- a/engine/src/flutter/impeller/entity/contents/solid_color_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/solid_color_contents.cc @@ -101,4 +101,19 @@ std::unique_ptr SolidColorContents::Make(const Path& path, return contents; } +std::optional SolidColorContents::AsBackgroundColor( + const Entity& entity, + ISize target_size) const { + if (!(GetColor().IsOpaque() && + (entity.GetBlendMode() == BlendMode::kSource || + entity.GetBlendMode() == BlendMode::kSourceOver))) { + return {}; + } + + Rect target_rect = Rect::MakeSize(target_size); + return GetGeometry()->CoversArea(entity.GetTransformation(), target_rect) + ? GetColor() + : std::optional(); +} + } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/solid_color_contents.h b/engine/src/flutter/impeller/entity/contents/solid_color_contents.h index d3cbb35300..497d08d49f 100644 --- a/engine/src/flutter/impeller/entity/contents/solid_color_contents.h +++ b/engine/src/flutter/impeller/entity/contents/solid_color_contents.h @@ -49,6 +49,9 @@ class SolidColorContents final : public ColorSourceContents { const Entity& entity, RenderPass& pass) const override; + std::optional AsBackgroundColor(const Entity& entity, + ISize target_size) const override; + private: Color color_; diff --git a/engine/src/flutter/impeller/entity/entity_pass.cc b/engine/src/flutter/impeller/entity/entity_pass.cc index 0c02217bfc..fc1cf19e87 100644 --- a/engine/src/flutter/impeller/entity/entity_pass.cc +++ b/engine/src/flutter/impeller/entity/entity_pass.cc @@ -36,6 +36,20 @@ namespace impeller { +namespace { +std::optional AsBackgroundColor(const EntityPass::Element& element, + ISize target_size) { + if (const Entity* entity = std::get_if(&element)) { + std::optional entity_color = + entity->GetContents()->AsBackgroundColor(*entity, target_size); + if (entity_color.has_value()) { + return entity_color.value(); + } + } + return {}; +} +} // namespace + EntityPass::EntityPass() = default; EntityPass::~EntityPass() = default; @@ -235,9 +249,9 @@ bool EntityPass::Render(ContentContext& renderer, // and then blit the results onto the onscreen texture. If using this branch, // there's no need to set up a stencil attachment on the root render target. if (!supports_onscreen_backdrop_reads && reads_from_onscreen_backdrop) { - auto offscreen_target = - CreateRenderTarget(renderer, root_render_target.GetRenderTargetSize(), - true, clear_color_.Premultiply()); + auto offscreen_target = CreateRenderTarget( + renderer, root_render_target.GetRenderTargetSize(), true, + GetClearColor(render_target.GetRenderTargetSize()).Premultiply()); if (!OnRender(renderer, // renderer offscreen_target.GetRenderTarget() @@ -342,7 +356,8 @@ bool EntityPass::Render(ContentContext& renderer, } // Set up the clear color of the root pass. - color0.clear_color = clear_color_.Premultiply(); + color0.clear_color = + GetClearColor(render_target.GetRenderTargetSize()).Premultiply(); root_render_target.SetColorAttachment(color0, 0); EntityPassTarget pass_target( @@ -478,7 +493,7 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( renderer, // renderer subpass_size, // size subpass->GetTotalPassReads(renderer) > 0, // readable - Color::BlackTransparent()); // clear_color + subpass->GetClearColor(subpass_size)); // clear_color if (!subpass_target.IsValid()) { VALIDATION_LOG << "Subpass render target is invalid."; @@ -559,7 +574,7 @@ bool EntityPass::OnRender( return false; } - if (!(clear_color_ == Color::BlackTransparent())) { + if (!(GetClearColor(root_pass_size) == Color::BlackTransparent())) { // Force the pass context to create at least one new pass if the clear color // is present. The `EndPass` first ensures that the clear color will get // applied even if this EntityPass is getting collapsed into the parent @@ -704,7 +719,19 @@ bool EntityPass::OnRender( render_element(backdrop_entity); } + bool is_collapsing_clear_colors = true; for (const auto& element : elements_) { + // Skip elements that are incorporated into the clear color. + if (is_collapsing_clear_colors) { + std::optional entity_color = + AsBackgroundColor(element, root_pass_size); + if (entity_color.has_value()) { + continue; + } else { + is_collapsing_clear_colors = false; + } + } + EntityResult result = GetEntityForElement(element, // element renderer, // renderer @@ -892,12 +919,17 @@ void EntityPass::SetBlendMode(BlendMode blend_mode) { flood_clip_ = Entity::IsBlendModeDestructive(blend_mode); } -void EntityPass::SetClearColor(Color clear_color) { - clear_color_ = clear_color; -} - -Color EntityPass::GetClearColor() const { - return clear_color_; +Color EntityPass::GetClearColor(ISize target_size) const { + Color result = Color::BlackTransparent(); + for (const Element& element : elements_) { + std::optional entity_color = AsBackgroundColor(element, target_size); + if (entity_color.has_value()) { + result = entity_color.value(); + } else { + break; + } + } + return result; } void EntityPass::SetBackdropFilter(std::optional proc) { diff --git a/engine/src/flutter/impeller/entity/entity_pass.h b/engine/src/flutter/impeller/entity/entity_pass.h index 673e162c57..d39f29ede7 100644 --- a/engine/src/flutter/impeller/entity/entity_pass.h +++ b/engine/src/flutter/impeller/entity/entity_pass.h @@ -86,9 +86,7 @@ class EntityPass { void SetBlendMode(BlendMode blend_mode); - void SetClearColor(Color clear_color); - - Color GetClearColor() const; + Color GetClearColor(ISize size = ISize::Infinite()) const; void SetBackdropFilter(std::optional proc); @@ -210,7 +208,6 @@ class EntityPass { size_t stencil_depth_ = 0u; BlendMode blend_mode_ = BlendMode::kSourceOver; bool flood_clip_ = false; - Color clear_color_ = Color::BlackTransparent(); bool enable_offscreen_debug_checkerboard_ = false; /// These values are incremented whenever something is added to the pass that diff --git a/engine/src/flutter/impeller/entity/geometry/cover_geometry.cc b/engine/src/flutter/impeller/entity/geometry/cover_geometry.cc index f2eb6c6814..f927c2d729 100644 --- a/engine/src/flutter/impeller/entity/geometry/cover_geometry.cc +++ b/engine/src/flutter/impeller/entity/geometry/cover_geometry.cc @@ -54,4 +54,9 @@ std::optional CoverGeometry::GetCoverage(const Matrix& transform) const { return Rect::MakeMaximum(); } +bool CoverGeometry::CoversArea(const Matrix& transform, + const Rect& rect) const { + return true; +} + } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/geometry/cover_geometry.h b/engine/src/flutter/impeller/entity/geometry/cover_geometry.h index 8bf20e1cc2..737a46a22a 100644 --- a/engine/src/flutter/impeller/entity/geometry/cover_geometry.h +++ b/engine/src/flutter/impeller/entity/geometry/cover_geometry.h @@ -16,6 +16,9 @@ class CoverGeometry : public Geometry { ~CoverGeometry(); + // |Geometry| + bool CoversArea(const Matrix& transform, const Rect& rect) const override; + private: // |Geometry| GeometryResult GetPositionBuffer(const ContentContext& renderer, diff --git a/engine/src/flutter/impeller/entity/geometry/geometry.h b/engine/src/flutter/impeller/entity/geometry/geometry.h index 95bf6799ad..fa428aca24 100644 --- a/engine/src/flutter/impeller/entity/geometry/geometry.h +++ b/engine/src/flutter/impeller/entity/geometry/geometry.h @@ -87,6 +87,12 @@ class Geometry { virtual GeometryVertexType GetVertexType() const = 0; virtual std::optional GetCoverage(const Matrix& transform) const = 0; + + /// @return `true` if this geometry will completely cover all fragments in + /// `rect` when the `transform` is applied to it. + virtual bool CoversArea(const Matrix& transform, const Rect& rect) const { + return false; + } }; } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/geometry/rect_geometry.cc b/engine/src/flutter/impeller/entity/geometry/rect_geometry.cc index 0c34b8b36f..6328b40768 100644 --- a/engine/src/flutter/impeller/entity/geometry/rect_geometry.cc +++ b/engine/src/flutter/impeller/entity/geometry/rect_geometry.cc @@ -47,4 +47,12 @@ std::optional RectGeometry::GetCoverage(const Matrix& transform) const { return rect_.TransformBounds(transform); } +bool RectGeometry::CoversArea(const Matrix& transform, const Rect& rect) const { + if (!transform.IsTranslationScaleOnly()) { + return false; + } + Rect coverage = rect_.TransformBounds(transform); + return coverage.Contains(rect); +} + } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/geometry/rect_geometry.h b/engine/src/flutter/impeller/entity/geometry/rect_geometry.h index 1833d70d27..4880aa65a0 100644 --- a/engine/src/flutter/impeller/entity/geometry/rect_geometry.h +++ b/engine/src/flutter/impeller/entity/geometry/rect_geometry.h @@ -14,6 +14,9 @@ class RectGeometry : public Geometry { ~RectGeometry(); + // |Geometry| + bool CoversArea(const Matrix& transform, const Rect& rect) const override; + private: // |Geometry| GeometryResult GetPositionBuffer(const ContentContext& renderer, diff --git a/engine/src/flutter/impeller/renderer/command_buffer.h b/engine/src/flutter/impeller/renderer/command_buffer.h index 3a6abacbb2..c7d167362c 100644 --- a/engine/src/flutter/impeller/renderer/command_buffer.h +++ b/engine/src/flutter/impeller/renderer/command_buffer.h @@ -18,6 +18,10 @@ class Context; class RenderPass; class RenderTarget; +namespace testing { +class CommandBufferMock; +} + //------------------------------------------------------------------------------ /// @brief A collection of encoded commands to be submitted to the GPU for /// execution. A command buffer is obtained from a graphics @@ -38,6 +42,8 @@ class RenderTarget; /// different from the encoding order. /// class CommandBuffer { + friend class testing::CommandBufferMock; + public: enum class Status { kPending, diff --git a/engine/src/flutter/impeller/renderer/render_pass.h b/engine/src/flutter/impeller/renderer/render_pass.h index a5fac32292..f76531eed8 100644 --- a/engine/src/flutter/impeller/renderer/render_pass.h +++ b/engine/src/flutter/impeller/renderer/render_pass.h @@ -57,6 +57,13 @@ class RenderPass { /// bool EncodeCommands() const; + //---------------------------------------------------------------------------- + /// @brief Accessor for the current Commands. + /// + /// @details Visible for testing. + /// + const std::vector& GetCommands() const { return commands_; } + protected: const std::weak_ptr context_; const RenderTarget render_target_;