mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Impeller] Refactor clip stack into separate testable class. (flutter/engine#51656)
The clip coverage tracking has had some bugs, but its been difficult to test as it was mixed into the regular entity pass workflow. This change pulls this logic and the clip recorder logic into a new class that is responsible for managing the coverage stacks. Adds an unbalanced restore unit test as well.
This commit is contained in:
parent
eee04ffd80
commit
ca4544834a
@ -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
|
||||
|
||||
@ -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> image = picture.ToImage(renderer, {300, 300});
|
||||
|
||||
EXPECT_EQ(
|
||||
picture.pass->GetEntityPassClipRecorder().GetReplayEntities().size(), 0u);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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<Rect> 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<ClipRestoreContents*>(element_entity.GetContents().get());
|
||||
restore_contents->SetRestoreCoverage(restore_coverage);
|
||||
|
||||
} break;
|
||||
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;
|
||||
}
|
||||
|
||||
#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});
|
||||
}
|
||||
}
|
||||
#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<Contents> backdrop_filter_contents,
|
||||
const std::optional<InlinePassContext::RenderPassResult>&
|
||||
@ -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<Entity>& EntityPassClipRecorder::GetReplayEntities() const {
|
||||
return rendered_clip_entities_;
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -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<Rect> coverage;
|
||||
size_t clip_depth;
|
||||
};
|
||||
|
||||
using ClipCoverageStack = std::vector<ClipCoverageLayer>;
|
||||
|
||||
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<Contents> backdrop_filter_contents = nullptr,
|
||||
const std::optional<InlinePassContext::RenderPassResult>&
|
||||
@ -354,8 +348,6 @@ class EntityPass {
|
||||
bool enable_offscreen_debug_checkerboard_ = false;
|
||||
std::optional<Rect> bounds_limit_;
|
||||
ContentBoundsPromise bounds_promise_ = ContentBoundsPromise::kUnknown;
|
||||
std::unique_ptr<EntityPassClipRecorder> clip_replay_ =
|
||||
std::make_unique<EntityPassClipRecorder>();
|
||||
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<Entity>& GetReplayEntities() const;
|
||||
|
||||
private:
|
||||
std::vector<Entity> rendered_clip_entities_;
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
#endif // FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_H_
|
||||
|
||||
164
engine/src/flutter/impeller/entity/entity_pass_clip_stack.cc
Normal file
164
engine/src/flutter/impeller/entity/entity_pass_clip_stack.cc
Normal file
@ -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<Rect> 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<Rect> 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<ClipCoverageLayer>
|
||||
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<Rect> 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<ClipRestoreContents*>(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<Entity>& EntityPassClipStack::GetReplayEntities() const {
|
||||
return subpass_state_.back().rendered_clip_entities;
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
65
engine/src/flutter/impeller/entity/entity_pass_clip_stack.h
Normal file
65
engine/src/flutter/impeller/entity/entity_pass_clip_stack.h
Normal file
@ -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<Rect> 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<Rect> CurrentClipCoverage() const;
|
||||
|
||||
void PushSubpass(std::optional<Rect> 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<Entity>& GetReplayEntities() const;
|
||||
|
||||
// Visible for testing.
|
||||
const std::vector<ClipCoverageLayer> GetClipCoverageLayers() const;
|
||||
|
||||
private:
|
||||
struct SubpassState {
|
||||
std::vector<Entity> rendered_clip_entities;
|
||||
std::vector<ClipCoverageLayer> clip_coverage;
|
||||
};
|
||||
|
||||
SubpassState& GetCurrentSubpassState();
|
||||
|
||||
std::vector<SubpassState> subpass_state_;
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
#endif // FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_CLIP_STACK_H_
|
||||
@ -7,7 +7,6 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "impeller/core/texture.h"
|
||||
#include "impeller/entity/contents/contents.h"
|
||||
#include "impeller/entity/contents/filters/filter_contents.h"
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user