This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6k
[Impeller] Refactor clip stack into separate testable class. #51656
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
60b868e
[Impeller] fix unbalanced restores.
4e8357a
Merge branch 'main' into fix_unbalanced_restore
7d56494
update licenses
c9509d8
Merge branch 'main' of github.com:flutter/engine into fix_unbalanced_…
815a370
Merge branch 'fix_unbalanced_restore' of github.com:jonahwilliams/eng…
8dbe8e8
Merge branch 'main' of github.com:flutter/engine into fix_unbalanced_…
0a60ba8
[Impeller] refactor clip stack and record/replay into separate class.
5cfbdb3
minor cleanups.
2c3fc3f
make clip stack share state.
29e56fd
remove unique ptr.
769c95c
fix clip values and add license files.
be351fd
Merge branch 'main' into refactor_clip_stack
b13cc79
Merge branch 'main' into refactor_clip_stack
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
| } | ||
|
|
||
| #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; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe store the 1-line comment explaining why this state is terminal
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| } | ||
| #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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This patch moves from per-pass tracking of replay clips to a single list of tracked replay clips reused across all passes. So when replaying clips we need to be careful that only clips which impact the current pass are replayed, otherwise there may be perf issues if StC is turned on and fidelity bugs if StC is turned off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, taking a closer look, maybe you're already handling this correctly. I see the state is held in a per-pass vector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made this mistake initially but I believe thatI've fully corrected it. The clip stacks and record/replay entities are stored per-subpass, and for each subpass we push/pop a new set of data.