diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ee89116474024..b6d82a2305b12 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -39694,6 +39694,8 @@ ORIGIN: ../../../flutter/impeller/entity/entity.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_pass.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_pass.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/entity_pass_clip_stack.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/entity_pass_clip_stack.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_pass_delegate.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_pass_delegate.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_pass_target.cc + ../../../flutter/LICENSE @@ -42567,6 +42569,8 @@ FILE: ../../../flutter/impeller/entity/entity.cc FILE: ../../../flutter/impeller/entity/entity.h FILE: ../../../flutter/impeller/entity/entity_pass.cc FILE: ../../../flutter/impeller/entity/entity_pass.h +FILE: ../../../flutter/impeller/entity/entity_pass_clip_stack.cc +FILE: ../../../flutter/impeller/entity/entity_pass_clip_stack.h FILE: ../../../flutter/impeller/entity/entity_pass_delegate.cc FILE: ../../../flutter/impeller/entity/entity_pass_delegate.h FILE: ../../../flutter/impeller/entity/entity_pass_target.cc diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index b08f726ee090d..93768e8c8866c 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -3376,23 +3376,6 @@ TEST_P(AiksTest, CorrectClipDepthAssignedToEntities) { } } -TEST_P(AiksTest, EntityPassClipRecorderRestoresCancelOutClips) { - Canvas canvas; - canvas.Save(); - canvas.ClipRRect(Rect::MakeLTRB(0, 0, 50, 50), {10, 10}, {}); - canvas.DrawRRect(Rect::MakeLTRB(0, 0, 100, 100), {10, 10}, {}); - canvas.Restore(); - canvas.DrawRRect(Rect::MakeLTRB(0, 0, 50, 50), {10, 10}, {}); - - Picture picture = canvas.EndRecordingAsPicture(); - - AiksContext renderer(GetContext(), nullptr); - std::shared_ptr image = picture.ToImage(renderer, {300, 300}); - - EXPECT_EQ( - picture.pass->GetEntityPassClipRecorder().GetReplayEntities().size(), 0u); -} - } // namespace testing } // namespace impeller diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index a917b91b781cf..9e1a56a21a3c5 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -188,6 +188,8 @@ impeller_component("entity") { "entity.h", "entity_pass.cc", "entity_pass.h", + "entity_pass_clip_stack.cc", + "entity_pass_clip_stack.h", "entity_pass_delegate.cc", "entity_pass_delegate.h", "entity_pass_target.cc", diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index b470189a242a9..c128dd06e9747 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -15,13 +15,13 @@ #include "impeller/base/strings.h" #include "impeller/base/validation.h" #include "impeller/core/formats.h" -#include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/filters/color_filter_contents.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" #include "impeller/entity/contents/framebuffer_blend_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/entity/entity.h" +#include "impeller/entity/entity_pass_clip_stack.h" #include "impeller/entity/inline_pass_context.h" #include "impeller/geometry/color.h" #include "impeller/geometry/rect.h" @@ -409,9 +409,8 @@ bool EntityPass::Render(ContentContext& renderer, return true; }); - ClipCoverageStack clip_coverage_stack = {ClipCoverageLayer{ - .coverage = Rect::MakeSize(root_render_target.GetRenderTargetSize()), - .clip_depth = 0}}; + EntityPassClipStack clip_stack = EntityPassClipStack( + Rect::MakeSize(root_render_target.GetRenderTargetSize())); bool reads_from_onscreen_backdrop = GetTotalPassReads(renderer) > 0; // In this branch path, we need to render everything to an offscreen texture @@ -431,7 +430,7 @@ bool EntityPass::Render(ContentContext& renderer, Point(), // global_pass_position Point(), // local_pass_position 0, // pass_depth - clip_coverage_stack // clip_coverage_stack + clip_stack // clip_coverage_stack )) { // Validation error messages are triggered for all `OnRender()` failure // cases. @@ -537,7 +536,7 @@ bool EntityPass::Render(ContentContext& renderer, Point(), // global_pass_position Point(), // local_pass_position 0, // pass_depth - clip_coverage_stack); // clip_coverage_stack + clip_stack); // clip_coverage_stack } EntityPass::EntityResult EntityPass::GetEntityForElement( @@ -548,7 +547,7 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( ISize root_pass_size, Point global_pass_position, uint32_t pass_depth, - ClipCoverageStack& clip_coverage_stack, + EntityPassClipStack& clip_coverage_stack, size_t clip_depth_floor) const { //-------------------------------------------------------------------------- /// Setup entity element. @@ -625,13 +624,13 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( pass_context.EndPass(); } - if (clip_coverage_stack.empty()) { + if (!clip_coverage_stack.HasCoverage()) { // The current clip is empty. This means the pass texture won't be // visible, so skip it. capture.CreateChild("Subpass Entity (Skipped: Empty clip A)"); return EntityPass::EntityResult::Skip(); } - auto clip_coverage_back = clip_coverage_stack.back().coverage; + auto clip_coverage_back = clip_coverage_stack.CurrentClipCoverage(); if (!clip_coverage_back.has_value()) { capture.CreateChild("Subpass Entity (Skipped: Empty clip B)"); return EntityPass::EntityResult::Skip(); @@ -690,8 +689,7 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( // save layers may transform the subpass texture after it's rendered, // causing parent clip coverage to get misaligned with the actual area that // the subpass will affect in the parent pass. - ClipCoverageStack subpass_clip_coverage_stack = {ClipCoverageLayer{ - .coverage = subpass_coverage, .clip_depth = subpass->clip_depth_}}; + clip_coverage_stack.PushSubpass(subpass_coverage, subpass->clip_depth_); // Stencil textures aren't shared between EntityPasses (as much of the // time they are transient). @@ -704,7 +702,7 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( subpass_coverage->GetOrigin() - global_pass_position, // local_pass_position ++pass_depth, // pass_depth - subpass_clip_coverage_stack, // clip_coverage_stack + clip_coverage_stack, // clip_coverage_stack subpass->clip_depth_, // clip_depth_floor subpass_backdrop_filter_contents // backdrop_filter_contents )) { @@ -713,6 +711,8 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( return EntityPass::EntityResult::Failure(); } + clip_coverage_stack.PopSubpass(); + // The subpass target's texture may have changed during OnRender. auto subpass_texture = subpass_target.GetRenderTarget().GetRenderTargetTexture(); @@ -757,7 +757,7 @@ bool EntityPass::RenderElement(Entity& element_entity, InlinePassContext& pass_context, int32_t pass_depth, ContentContext& renderer, - ClipCoverageStack& clip_coverage_stack, + EntityPassClipStack& clip_coverage_stack, Point global_pass_position) const { auto result = pass_context.GetRenderPass(pass_depth); if (!result.pass) { @@ -770,7 +770,7 @@ bool EntityPass::RenderElement(Entity& element_entity, if (result.just_created) { // Restore any clips that were recorded before the backdrop filter was // applied. - auto& replay_entities = clip_replay_->GetReplayEntities(); + auto& replay_entities = clip_coverage_stack.GetReplayEntities(); for (const auto& entity : replay_entities) { if (!entity.Render(renderer, *result.pass)) { VALIDATION_LOG << "Failed to render entity for clip restore."; @@ -801,7 +801,7 @@ bool EntityPass::RenderElement(Entity& element_entity, } } - auto current_clip_coverage = clip_coverage_stack.back().coverage; + auto current_clip_coverage = clip_coverage_stack.CurrentClipCoverage(); if (current_clip_coverage.has_value()) { // Entity transforms are relative to the current pass position, so we need // to check clip coverage in the same space. @@ -826,81 +826,14 @@ bool EntityPass::RenderElement(Entity& element_entity, element_entity.GetContents()->SetCoverageHint( Rect::Intersection(element_coverage_hint, current_clip_coverage)); - switch (clip_coverage.type) { - case Contents::ClipCoverage::Type::kNoChange: - break; - case Contents::ClipCoverage::Type::kAppend: { - auto op = clip_coverage_stack.back().coverage; - clip_coverage_stack.push_back( - ClipCoverageLayer{.coverage = clip_coverage.coverage, - .clip_depth = element_entity.GetClipDepth() + 1}); - FML_DCHECK(clip_coverage_stack.back().clip_depth == - clip_coverage_stack.front().clip_depth + - clip_coverage_stack.size() - 1); - - if (!op.has_value()) { - // Running this append op won't impact the clip buffer because the - // whole screen is already being clipped, so skip it. - return true; - } - } break; - case Contents::ClipCoverage::Type::kRestore: { - if (clip_coverage_stack.back().clip_depth <= - element_entity.GetClipDepth()) { - // Drop clip restores that will do nothing. - return true; - } - - auto restoration_index = element_entity.GetClipDepth() - - clip_coverage_stack.front().clip_depth; - FML_DCHECK(restoration_index < clip_coverage_stack.size()); - - // We only need to restore the area that covers the coverage of the - // clip rect at target depth + 1. - std::optional restore_coverage = - (restoration_index + 1 < clip_coverage_stack.size()) - ? clip_coverage_stack[restoration_index + 1].coverage - : std::nullopt; - if (restore_coverage.has_value()) { - // Make the coverage rectangle relative to the current pass. - restore_coverage = restore_coverage->Shift(-global_pass_position); - } - clip_coverage_stack.resize(restoration_index + 1); - - if constexpr (ContentContext::kEnableStencilThenCover) { - // Skip all clip restores when stencil-then-cover is enabled. - if (clip_coverage_stack.back().coverage.has_value()) { - clip_replay_->RecordEntity(element_entity, clip_coverage.type); - } - return true; - } - - if (!clip_coverage_stack.back().coverage.has_value()) { - // Running this restore op won't make anything renderable, so skip it. - return true; - } - - auto restore_contents = - static_cast(element_entity.GetContents().get()); - restore_contents->SetRestoreCoverage(restore_coverage); - - } break; - } - -#ifdef IMPELLER_ENABLE_CAPTURE - { - auto element_entity_coverage = element_entity.GetCoverage(); - if (element_entity_coverage.has_value()) { - element_entity_coverage = - element_entity_coverage->Shift(global_pass_position); - element_entity.GetCapture().AddRect("Coverage", *element_entity_coverage, - {.readonly = true}); - } + if (!clip_coverage_stack.AppendClipCoverage(clip_coverage, element_entity, + clip_depth_floor, + global_pass_position)) { + // If the entity's coverage change did not change the clip coverage, we + // don't need to render it. + return true; } -#endif - element_entity.SetClipDepth(element_entity.GetClipDepth() - clip_depth_floor); - clip_replay_->RecordEntity(element_entity, clip_coverage.type); if (!element_entity.Render(renderer, *result.pass)) { VALIDATION_LOG << "Failed to render entity."; return false; @@ -916,7 +849,7 @@ bool EntityPass::OnRender( Point global_pass_position, Point local_pass_position, uint32_t pass_depth, - ClipCoverageStack& clip_coverage_stack, + EntityPassClipStack& clip_coverage_stack, size_t clip_depth_floor, std::shared_ptr backdrop_filter_contents, const std::optional& @@ -1256,30 +1189,4 @@ void EntityPass::SetEnableOffscreenCheckerboard(bool enabled) { enable_offscreen_debug_checkerboard_ = enabled; } -const EntityPassClipRecorder& EntityPass::GetEntityPassClipRecorder() const { - return *clip_replay_; -} - -EntityPassClipRecorder::EntityPassClipRecorder() {} - -void EntityPassClipRecorder::RecordEntity(const Entity& entity, - Contents::ClipCoverage::Type type) { - switch (type) { - case Contents::ClipCoverage::Type::kNoChange: - return; - case Contents::ClipCoverage::Type::kAppend: - rendered_clip_entities_.push_back(entity.Clone()); - break; - case Contents::ClipCoverage::Type::kRestore: - if (!rendered_clip_entities_.empty()) { - rendered_clip_entities_.pop_back(); - } - break; - } -} - -const std::vector& EntityPassClipRecorder::GetReplayEntities() const { - return rendered_clip_entities_; -} - } // namespace impeller diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index 4198bccd0fbba..f7c0754be18fa 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -14,6 +14,7 @@ #include "impeller/entity/contents/contents.h" #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/entity.h" +#include "impeller/entity/entity_pass_clip_stack.h" #include "impeller/entity/entity_pass_delegate.h" #include "impeller/entity/inline_pass_context.h" #include "impeller/renderer/render_target.h" @@ -60,13 +61,6 @@ class EntityPass { const Matrix& effect_transform, Entity::RenderingMode rendering_mode)>; - struct ClipCoverageLayer { - std::optional coverage; - size_t clip_depth; - }; - - using ClipCoverageStack = std::vector; - EntityPass(); ~EntityPass(); @@ -252,7 +246,7 @@ class EntityPass { InlinePassContext& pass_context, int32_t pass_depth, ContentContext& renderer, - ClipCoverageStack& clip_coverage_stack, + EntityPassClipStack& clip_coverage_stack, Point global_pass_position) const; EntityResult GetEntityForElement(const EntityPass::Element& element, @@ -262,7 +256,7 @@ class EntityPass { ISize root_pass_size, Point global_pass_position, uint32_t pass_depth, - ClipCoverageStack& clip_coverage_stack, + EntityPassClipStack& clip_coverage_stack, size_t clip_depth_floor) const; //---------------------------------------------------------------------------- @@ -329,7 +323,7 @@ class EntityPass { Point global_pass_position, Point local_pass_position, uint32_t pass_depth, - ClipCoverageStack& clip_coverage_stack, + EntityPassClipStack& clip_coverage_stack, size_t clip_depth_floor = 0, std::shared_ptr backdrop_filter_contents = nullptr, const std::optional& @@ -354,8 +348,6 @@ class EntityPass { bool enable_offscreen_debug_checkerboard_ = false; std::optional bounds_limit_; ContentBoundsPromise bounds_promise_ = ContentBoundsPromise::kUnknown; - std::unique_ptr clip_replay_ = - std::make_unique(); int32_t required_mip_count_ = 1; /// These values are incremented whenever something is added to the pass that @@ -381,26 +373,6 @@ class EntityPass { EntityPass& operator=(const EntityPass&) = delete; }; -/// @brief A class that tracks all clips that have been recorded in the current -/// entity pass stencil. -/// -/// These clips are replayed when restoring the backdrop so that the -/// stencil buffer is left in an identical state. -class EntityPassClipRecorder { - public: - EntityPassClipRecorder(); - - ~EntityPassClipRecorder() = default; - - /// @brief Record the entity based on the provided coverage [type]. - void RecordEntity(const Entity& entity, Contents::ClipCoverage::Type type); - - const std::vector& GetReplayEntities() const; - - private: - std::vector rendered_clip_entities_; -}; - } // namespace impeller #endif // FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_H_ diff --git a/impeller/entity/entity_pass_clip_stack.cc b/impeller/entity/entity_pass_clip_stack.cc new file mode 100644 index 0000000000000..5d126acdcbc94 --- /dev/null +++ b/impeller/entity/entity_pass_clip_stack.cc @@ -0,0 +1,164 @@ +// 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/entity/entity_pass_clip_stack.h" +#include "impeller/entity/contents/clip_contents.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" + +namespace impeller { + +EntityPassClipStack::EntityPassClipStack(const Rect& initial_coverage_rect) { + subpass_state_.push_back(SubpassState{ + .clip_coverage = + { + {ClipCoverageLayer{ + .coverage = initial_coverage_rect, + .clip_depth = 0, + }}, + }, + }); +} + +std::optional EntityPassClipStack::CurrentClipCoverage() const { + return subpass_state_.back().clip_coverage.back().coverage; +} + +bool EntityPassClipStack::HasCoverage() const { + return !subpass_state_.back().clip_coverage.empty(); +} + +void EntityPassClipStack::PushSubpass(std::optional subpass_coverage, + size_t clip_depth) { + subpass_state_.push_back(SubpassState{ + .clip_coverage = + { + ClipCoverageLayer{.coverage = subpass_coverage, + .clip_depth = clip_depth}, + }, + }); +} + +void EntityPassClipStack::PopSubpass() { + subpass_state_.pop_back(); +} + +const std::vector +EntityPassClipStack::GetClipCoverageLayers() const { + return subpass_state_.back().clip_coverage; +} + +bool EntityPassClipStack::AppendClipCoverage( + Contents::ClipCoverage clip_coverage, + Entity& entity, + size_t clip_depth_floor, + Point global_pass_position) { + auto& subpass_state = GetCurrentSubpassState(); + switch (clip_coverage.type) { + case Contents::ClipCoverage::Type::kNoChange: + break; + case Contents::ClipCoverage::Type::kAppend: { + auto op = CurrentClipCoverage(); + subpass_state.clip_coverage.push_back( + ClipCoverageLayer{.coverage = clip_coverage.coverage, + .clip_depth = entity.GetClipDepth() + 1}); + + FML_DCHECK(subpass_state.clip_coverage.back().clip_depth == + subpass_state.clip_coverage.front().clip_depth + + subpass_state.clip_coverage.size() - 1); + + if (!op.has_value()) { + // Running this append op won't impact the clip buffer because the + // whole screen is already being clipped, so skip it. + return false; + } + } break; + case Contents::ClipCoverage::Type::kRestore: { + if (subpass_state.clip_coverage.back().clip_depth <= + entity.GetClipDepth()) { + // Drop clip restores that will do nothing. + return false; + } + + auto restoration_index = entity.GetClipDepth() - + subpass_state.clip_coverage.front().clip_depth; + FML_DCHECK(restoration_index < subpass_state.clip_coverage.size()); + + // We only need to restore the area that covers the coverage of the + // clip rect at target depth + 1. + std::optional restore_coverage = + (restoration_index + 1 < subpass_state.clip_coverage.size()) + ? subpass_state.clip_coverage[restoration_index + 1].coverage + : std::nullopt; + if (restore_coverage.has_value()) { + // Make the coverage rectangle relative to the current pass. + restore_coverage = restore_coverage->Shift(-global_pass_position); + } + subpass_state.clip_coverage.resize(restoration_index + 1); + + if constexpr (ContentContext::kEnableStencilThenCover) { + // Skip all clip restores when stencil-then-cover is enabled. + if (subpass_state.clip_coverage.back().coverage.has_value()) { + RecordEntity(entity, clip_coverage.type); + } + return false; + } + + if (!subpass_state.clip_coverage.back().coverage.has_value()) { + // Running this restore op won't make anything renderable, so skip it. + return false; + } + + auto restore_contents = + static_cast(entity.GetContents().get()); + restore_contents->SetRestoreCoverage(restore_coverage); + + } break; + } + +#ifdef IMPELLER_ENABLE_CAPTURE + { + auto element_entity_coverage = entity.GetCoverage(); + if (element_entity_coverage.has_value()) { + element_entity_coverage = + element_entity_coverage->Shift(global_pass_position); + entity.GetCapture().AddRect("Coverage", *element_entity_coverage, + {.readonly = true}); + } + } +#endif + + entity.SetClipDepth(entity.GetClipDepth() - clip_depth_floor); + RecordEntity(entity, clip_coverage.type); + + return true; +} + +void EntityPassClipStack::RecordEntity(const Entity& entity, + Contents::ClipCoverage::Type type) { + auto& subpass_state = GetCurrentSubpassState(); + switch (type) { + case Contents::ClipCoverage::Type::kNoChange: + return; + case Contents::ClipCoverage::Type::kAppend: + subpass_state.rendered_clip_entities.push_back(entity.Clone()); + break; + case Contents::ClipCoverage::Type::kRestore: + if (!subpass_state.rendered_clip_entities.empty()) { + subpass_state.rendered_clip_entities.pop_back(); + } + break; + } +} + +EntityPassClipStack::SubpassState& +EntityPassClipStack::GetCurrentSubpassState() { + return subpass_state_.back(); +} + +const std::vector& EntityPassClipStack::GetReplayEntities() const { + return subpass_state_.back().rendered_clip_entities; +} + +} // namespace impeller diff --git a/impeller/entity/entity_pass_clip_stack.h b/impeller/entity/entity_pass_clip_stack.h new file mode 100644 index 0000000000000..bfd7f5ba83b8e --- /dev/null +++ b/impeller/entity/entity_pass_clip_stack.h @@ -0,0 +1,65 @@ +// 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. + +#ifndef FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_CLIP_STACK_H_ +#define FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_CLIP_STACK_H_ + +#include "impeller/entity/contents/contents.h" + +namespace impeller { + +struct ClipCoverageLayer { + std::optional coverage; + size_t clip_depth; +}; + +/// @brief A class that tracks all clips that have been recorded in the current +/// entity pass stencil. +/// +/// These clips are replayed when restoring the backdrop so that the +/// stencil buffer is left in an identical state. +class EntityPassClipStack { + public: + /// Create a new [EntityPassClipStack] with an initialized coverage rect. + explicit EntityPassClipStack(const Rect& initial_coverage_rect); + + ~EntityPassClipStack() = default; + + std::optional CurrentClipCoverage() const; + + void PushSubpass(std::optional subpass_coverage, size_t clip_depth); + + void PopSubpass(); + + bool HasCoverage() const; + + /// Returns true if entity should be rendered. + bool AppendClipCoverage(Contents::ClipCoverage clip_coverage, + Entity& entity, + size_t clip_depth_floor, + Point global_pass_position); + + // Visible for testing. + void RecordEntity(const Entity& entity, Contents::ClipCoverage::Type type); + + // Visible for testing. + const std::vector& GetReplayEntities() const; + + // Visible for testing. + const std::vector GetClipCoverageLayers() const; + + private: + struct SubpassState { + std::vector rendered_clip_entities; + std::vector clip_coverage; + }; + + SubpassState& GetCurrentSubpassState(); + + std::vector subpass_state_; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_CLIP_STACK_H_ diff --git a/impeller/entity/entity_pass_delegate.h b/impeller/entity/entity_pass_delegate.h index 72a38af65eebc..04b96ba00782f 100644 --- a/impeller/entity/entity_pass_delegate.h +++ b/impeller/entity/entity_pass_delegate.h @@ -7,7 +7,6 @@ #include -#include "flutter/fml/macros.h" #include "impeller/core/texture.h" #include "impeller/entity/contents/contents.h" #include "impeller/entity/contents/filters/filter_contents.h" diff --git a/impeller/entity/entity_pass_unittests.cc b/impeller/entity/entity_pass_unittests.cc index d1f05448e938b..ce2a66a82dc7c 100644 --- a/impeller/entity/entity_pass_unittests.cc +++ b/impeller/entity/entity_pass_unittests.cc @@ -4,13 +4,15 @@ #include "flutter/testing/testing.h" #include "gtest/gtest.h" -#include "impeller/entity/entity_pass.h" +#include "impeller/entity/entity.h" +#include "impeller/entity/entity_pass_clip_stack.h" namespace impeller { namespace testing { -TEST(EntityPassClipRecorderTest, CanPushAndPopEntities) { - EntityPassClipRecorder recorder = EntityPassClipRecorder(); +TEST(EntityPassClipStackTest, CanPushAndPopEntities) { + EntityPassClipStack recorder = + EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100)); EXPECT_TRUE(recorder.GetReplayEntities().empty()); @@ -28,8 +30,9 @@ TEST(EntityPassClipRecorderTest, CanPushAndPopEntities) { EXPECT_TRUE(recorder.GetReplayEntities().empty()); } -TEST(EntityPassClipRecorderTest, CanPopEntitiesSafely) { - EntityPassClipRecorder recorder = EntityPassClipRecorder(); +TEST(EntityPassClipStackTest, CanPopEntitiesSafely) { + EntityPassClipStack recorder = + EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100)); EXPECT_TRUE(recorder.GetReplayEntities().empty()); @@ -38,8 +41,9 @@ TEST(EntityPassClipRecorderTest, CanPopEntitiesSafely) { EXPECT_TRUE(recorder.GetReplayEntities().empty()); } -TEST(EntityPassClipRecorderTest, CanAppendNoChange) { - EntityPassClipRecorder recorder = EntityPassClipRecorder(); +TEST(EntityPassClipStackTest, CanAppendNoChange) { + EntityPassClipStack recorder = + EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100)); EXPECT_TRUE(recorder.GetReplayEntities().empty()); @@ -48,5 +52,133 @@ TEST(EntityPassClipRecorderTest, CanAppendNoChange) { EXPECT_TRUE(recorder.GetReplayEntities().empty()); } +TEST(EntityPassClipStackTest, AppendCoverageNoChange) { + EntityPassClipStack recorder = + EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100)); + + EXPECT_EQ(recorder.GetClipCoverageLayers()[0].coverage, + Rect::MakeSize(Size::MakeWH(100, 100))); + EXPECT_EQ(recorder.GetClipCoverageLayers()[0].clip_depth, 0u); + + Entity entity; + recorder.AppendClipCoverage( + Contents::ClipCoverage{ + .type = Contents::ClipCoverage::Type::kNoChange, + .coverage = std::nullopt, + }, + entity, 0, Point(0, 0)); + + EXPECT_EQ(recorder.GetClipCoverageLayers()[0].coverage, + Rect::MakeSize(Size::MakeWH(100, 100))); + EXPECT_EQ(recorder.GetClipCoverageLayers()[0].clip_depth, 0u); +} + +TEST(EntityPassClipStackTest, AppendAndRestoreClipCoverage) { + EntityPassClipStack recorder = + EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100)); + + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); + + // Push a clip. + Entity entity; + entity.SetClipDepth(0); + recorder.AppendClipCoverage( + Contents::ClipCoverage{ + .type = Contents::ClipCoverage::Type::kAppend, + .coverage = Rect::MakeLTRB(50, 50, 55, 55), + }, + entity, 0, Point(0, 0)); + + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 2u); + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage, + Rect::MakeLTRB(50, 50, 55, 55)); + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].clip_depth, 1u); + EXPECT_EQ(recorder.GetReplayEntities().size(), 1u); + + // Restore the clip. + entity.SetClipDepth(0); + recorder.AppendClipCoverage( + Contents::ClipCoverage{ + .type = Contents::ClipCoverage::Type::kRestore, + .coverage = Rect::MakeLTRB(50, 50, 55, 55), + }, + entity, 0, Point(0, 0)); + + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); + EXPECT_EQ(recorder.GetClipCoverageLayers()[0].coverage, + Rect::MakeSize(Size::MakeWH(100, 100))); + EXPECT_EQ(recorder.GetClipCoverageLayers()[0].clip_depth, 0u); + EXPECT_EQ(recorder.GetReplayEntities().size(), 0u); +} + +TEST(EntityPassClipStackTest, UnbalancedRestore) { + EntityPassClipStack recorder = + EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100)); + + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); + + // Restore the clip. + Entity entity; + entity.SetClipDepth(0); + recorder.AppendClipCoverage( + Contents::ClipCoverage{ + .type = Contents::ClipCoverage::Type::kRestore, + .coverage = Rect::MakeLTRB(50, 50, 55, 55), + }, + entity, 0, Point(0, 0)); + + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); + EXPECT_EQ(recorder.GetClipCoverageLayers()[0].coverage, + Rect::MakeSize(Size::MakeWH(100, 100))); + EXPECT_EQ(recorder.GetClipCoverageLayers()[0].clip_depth, 0u); + EXPECT_EQ(recorder.GetReplayEntities().size(), 0u); +} + +TEST(EntityPassClipStackTest, ClipAndRestoreWithSubpasses) { + EntityPassClipStack recorder = + EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100)); + + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); + + // Push a clip. + Entity entity; + entity.SetClipDepth(0u); + recorder.AppendClipCoverage( + Contents::ClipCoverage{ + .type = Contents::ClipCoverage::Type::kAppend, + .coverage = Rect::MakeLTRB(50, 50, 55, 55), + }, + entity, 0, Point(0, 0)); + + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 2u); + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage, + Rect::MakeLTRB(50, 50, 55, 55)); + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].clip_depth, 1u); + EXPECT_EQ(recorder.GetReplayEntities().size(), 1u); + + // Begin a subpass. + recorder.PushSubpass(Rect::MakeLTRB(50, 50, 55, 55), 1); + ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u); + EXPECT_EQ(recorder.GetClipCoverageLayers()[0].coverage, + Rect::MakeLTRB(50, 50, 55, 55)); + + entity.SetClipDepth(1); + recorder.AppendClipCoverage( + Contents::ClipCoverage{ + .type = Contents::ClipCoverage::Type::kAppend, + .coverage = Rect::MakeLTRB(54, 54, 55, 55), + }, + entity, 0, Point(0, 0)); + + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage, + Rect::MakeLTRB(54, 54, 55, 55)); + + // End subpass. + recorder.PopSubpass(); + + EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage, + Rect::MakeLTRB(50, 50, 55, 55)); +} + } // namespace testing } // namespace impeller