-
Notifications
You must be signed in to change notification settings - Fork 6k
[Impeller] opt into exp canvas. #54913
Changes from 12 commits
97160d4
dd06f92
5181dd1
9468843
e2f5268
d198704
0292df8
bf7a396
08f6361
c85e2b4
df4e5e5
b894059
f50102e
be9be26
06cbda0
1c0c46c
c168b96
69884f9
eb2bd85
4248520
3938253
90e2eb3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
|
|
||
| #include "impeller/aiks/experimental_canvas.h" | ||
| #include <limits> | ||
| #include <optional> | ||
| #include "fml/logging.h" | ||
| #include "fml/trace_event.h" | ||
| #include "impeller/aiks/canvas.h" | ||
|
|
@@ -61,7 +62,6 @@ static void ApplyFramebufferBlend(Entity& entity) { | |
| static std::shared_ptr<Texture> FlipBackdrop( | ||
| std::vector<LazyRenderingConfig>& render_passes, | ||
| Point global_pass_position, | ||
| size_t current_clip_depth, | ||
| EntityPassClipStack& clip_coverage_stack, | ||
| ContentContext& renderer) { | ||
| auto rendering_config = std::move(render_passes.back()); | ||
|
|
@@ -139,22 +139,6 @@ static std::shared_ptr<Texture> FlipBackdrop( | |
| // applied. | ||
| clip_coverage_stack.ActivateClipReplay(); | ||
|
|
||
| // If there are any pending clips to replay, render any that may affect | ||
| // the entity we're about to render. | ||
| while (const EntityPassClipStack::ReplayResult* next_replay_clip = | ||
| clip_coverage_stack.GetNextReplayResult(current_clip_depth)) { | ||
| auto& replay_entity = next_replay_clip->entity; | ||
| SetClipScissor( | ||
| next_replay_clip->clip_coverage, | ||
| *render_passes.back().inline_pass_context->GetRenderPass(0).pass, | ||
| global_pass_position); | ||
| if (!replay_entity.Render( | ||
| renderer, | ||
| *render_passes.back().inline_pass_context->GetRenderPass(0).pass)) { | ||
| VALIDATION_LOG << "Failed to render entity for clip replay."; | ||
| } | ||
| } | ||
|
|
||
| return input_texture; | ||
| } | ||
|
|
||
|
|
@@ -296,7 +280,18 @@ void ExperimentalCanvas::SetupRenderPass() { | |
| } | ||
| } | ||
|
|
||
| void ExperimentalCanvas::SkipUntilMatchingRestore(size_t total_content_depth) { | ||
| auto entry = CanvasStackEntry{}; | ||
| entry.skipping = true; | ||
| entry.clip_depth = current_depth_ + total_content_depth; | ||
| transform_stack_.push_back(entry); | ||
| } | ||
|
|
||
| void ExperimentalCanvas::Save(uint32_t total_content_depth) { | ||
| if (IsSkipping()) { | ||
| return SkipUntilMatchingRestore(total_content_depth); | ||
| } | ||
|
|
||
| auto entry = CanvasStackEntry{}; | ||
| entry.transform = transform_stack_.back().transform; | ||
| entry.cull_rect = transform_stack_.back().cull_rect; | ||
|
|
@@ -307,30 +302,21 @@ void ExperimentalCanvas::Save(uint32_t total_content_depth) { | |
| << " after allocating " << total_content_depth; | ||
| entry.clip_height = transform_stack_.back().clip_height; | ||
| entry.rendering_mode = Entity::RenderingMode::kDirect; | ||
| transform_stack_.emplace_back(entry); | ||
| transform_stack_.push_back(entry); | ||
| } | ||
|
|
||
| void ExperimentalCanvas::SaveLayer( | ||
| const Paint& paint, | ||
| std::optional<Rect> bounds, | ||
| const std::shared_ptr<ImageFilter>& backdrop_filter, | ||
| ContentBoundsPromise bounds_promise, | ||
| uint32_t total_content_depth, | ||
| bool can_distribute_opacity) { | ||
| TRACE_EVENT0("flutter", "Canvas::saveLayer"); | ||
|
|
||
| std::optional<Rect> ExperimentalCanvas::ComputeCoverageLimit() const { | ||
| if (!clip_coverage_stack_.HasCoverage()) { | ||
| // The current clip is empty. This means the pass texture won't be | ||
| // visible, so skip it. | ||
| Save(total_content_depth); | ||
| return; | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| auto maybe_current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage(); | ||
| if (!maybe_current_clip_coverage.has_value()) { | ||
| Save(total_content_depth); | ||
| return; | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| auto current_clip_coverage = maybe_current_clip_coverage.value(); | ||
|
|
||
| // The maximum coverage of the subpass. Subpasses textures should never | ||
|
|
@@ -343,8 +329,28 @@ void ExperimentalCanvas::SaveLayer( | |
| .Intersection(current_clip_coverage); | ||
|
|
||
| if (!maybe_coverage_limit.has_value() || maybe_coverage_limit->IsEmpty()) { | ||
| Save(total_content_depth); | ||
| return; | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| return maybe_coverage_limit->Intersection( | ||
| Rect::MakeSize(render_target_.GetRenderTargetSize())); | ||
| } | ||
|
|
||
| void ExperimentalCanvas::SaveLayer( | ||
| const Paint& paint, | ||
| std::optional<Rect> bounds, | ||
| const std::shared_ptr<ImageFilter>& backdrop_filter, | ||
| ContentBoundsPromise bounds_promise, | ||
| uint32_t total_content_depth, | ||
| bool can_distribute_opacity) { | ||
| TRACE_EVENT0("flutter", "Canvas::saveLayer"); | ||
| if (IsSkipping()) { | ||
| return SkipUntilMatchingRestore(total_content_depth); | ||
| } | ||
|
|
||
| auto maybe_coverage_limit = ComputeCoverageLimit(); | ||
| if (!maybe_coverage_limit.has_value()) { | ||
| return SkipUntilMatchingRestore(total_content_depth); | ||
| } | ||
| auto coverage_limit = maybe_coverage_limit.value(); | ||
|
|
||
|
|
@@ -356,10 +362,9 @@ void ExperimentalCanvas::SaveLayer( | |
| return; | ||
| } | ||
|
|
||
| std::shared_ptr<FilterContents> filter_contents; | ||
| if (paint.image_filter) { | ||
| filter_contents = paint.image_filter->GetFilterContents(); | ||
| } | ||
| std::shared_ptr<FilterContents> filter_contents = paint.WithImageFilter( | ||
| Rect(), transform_stack_.back().transform, | ||
| Entity::RenderingMode::kSubpassPrependSnapshotTransform); | ||
|
|
||
| std::optional<Rect> maybe_subpass_coverage = ComputeSaveLayerCoverage( | ||
| bounds.value_or(Rect::MakeMaximum()), | ||
|
|
@@ -371,13 +376,25 @@ void ExperimentalCanvas::SaveLayer( | |
| /*flood_input_coverage=*/!!backdrop_filter // | ||
| ); | ||
|
|
||
| if (!maybe_subpass_coverage.has_value() || | ||
| maybe_subpass_coverage->IsEmpty()) { | ||
| Save(total_content_depth); | ||
| return; | ||
| if (!maybe_subpass_coverage.has_value()) { | ||
| return SkipUntilMatchingRestore(total_content_depth); | ||
| } | ||
|
|
||
| auto subpass_coverage = maybe_subpass_coverage.value(); | ||
|
|
||
| // When an image filter is present, clamp to avoid flicking due to nearest | ||
| // sampled image. For other cases, round out to ensure than any geometry is | ||
| // not cut off. | ||
|
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. This fixes flutter/flutter#152366
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. Yes, you will be filtering all of the subpass pixels, not just its theoretical coverage. |
||
| ISize subpass_size; | ||
| if (paint.image_filter) { | ||
| subpass_size = ISize(subpass_coverage.GetSize()); | ||
| } else { | ||
| subpass_size = ISize(IRect::RoundOut(subpass_coverage).GetSize()); | ||
| } | ||
|
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. Don't the variations of how you compute the subpass_size have a ramification for the origin? The subpass_coverage was relative to the global position I think? In which case if you just took its size and made a drawable for that then the global position would be the origin of the coverage. But if you rounded it out, then the global position would be the rounded down value of the origin of the coverage?
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. hmm, good point. Right now we're always rounding in https://github.com/flutter/engine/blob/main/impeller/aiks/experimental_canvas.cc#L524-L526 But I think instead we need to take into account whether we rounded in or out, so that we're not a pixel off. |
||
| if (subpass_size.IsEmpty()) { | ||
| return SkipUntilMatchingRestore(total_content_depth); | ||
| } | ||
|
|
||
| // Backdrop filter state, ignored if there is no BDF. | ||
| std::shared_ptr<FilterContents> backdrop_filter_contents; | ||
| Point local_position = {0, 0}; | ||
|
|
@@ -393,11 +410,10 @@ void ExperimentalCanvas::SaveLayer( | |
| return filter; | ||
| }; | ||
|
|
||
| auto input_texture = FlipBackdrop(render_passes_, // | ||
| GetGlobalPassPosition(), // | ||
| std::numeric_limits<uint32_t>::max(), // | ||
| clip_coverage_stack_, // | ||
| renderer_ // | ||
| auto input_texture = FlipBackdrop(render_passes_, // | ||
| GetGlobalPassPosition(), // | ||
| clip_coverage_stack_, // | ||
| renderer_ // | ||
| ); | ||
| if (!input_texture) { | ||
| // Validation failures are logged in FlipBackdrop. | ||
|
|
@@ -419,12 +435,12 @@ void ExperimentalCanvas::SaveLayer( | |
| paint_copy.color.alpha *= transform_stack_.back().distributed_opacity; | ||
| transform_stack_.back().distributed_opacity = 1.0; | ||
|
|
||
| render_passes_.push_back(LazyRenderingConfig( | ||
| renderer_, // | ||
| CreateRenderTarget(renderer_, // | ||
| ISize(subpass_coverage.GetSize()), // | ||
| Color::BlackTransparent() // | ||
| ))); | ||
| render_passes_.push_back( | ||
| LazyRenderingConfig(renderer_, // | ||
| CreateRenderTarget(renderer_, // | ||
| subpass_size, // | ||
| Color::BlackTransparent() // | ||
| ))); | ||
| save_layer_state_.push_back(SaveLayerState{paint_copy, subpass_coverage}); | ||
|
|
||
| CanvasStackEntry entry; | ||
|
|
@@ -452,6 +468,8 @@ void ExperimentalCanvas::SaveLayer( | |
| clip_coverage_stack_.PushSubpass(subpass_coverage, GetClipHeight()); | ||
|
|
||
| if (backdrop_filter_contents) { | ||
| FlushPendingClips(); | ||
|
|
||
| // Render the backdrop entity. | ||
| Entity backdrop_entity; | ||
| backdrop_entity.SetContents(std::move(backdrop_filter_contents)); | ||
|
|
@@ -488,6 +506,11 @@ bool ExperimentalCanvas::Restore() { | |
| << current_depth_ << " <=? " << transform_stack_.back().clip_depth; | ||
| current_depth_ = transform_stack_.back().clip_depth; | ||
|
|
||
| if (IsSkipping()) { | ||
| transform_stack_.pop_back(); | ||
| return true; | ||
| } | ||
|
|
||
| if (transform_stack_.back().rendering_mode == | ||
| Entity::RenderingMode::kSubpassAppendSnapshotTransform || | ||
| transform_stack_.back().rendering_mode == | ||
|
|
@@ -499,12 +522,13 @@ bool ExperimentalCanvas::Restore() { | |
|
|
||
| SaveLayerState save_layer_state = save_layer_state_.back(); | ||
| save_layer_state_.pop_back(); | ||
| auto global_pass_position = GetGlobalPassPosition(); | ||
|
|
||
| std::shared_ptr<Contents> contents = | ||
| PaintPassDelegate(save_layer_state.paint) | ||
| .CreateContentsForSubpassTarget( | ||
| lazy_render_pass.inline_pass_context->GetTexture(), | ||
| Matrix::MakeTranslation(Vector3{-GetGlobalPassPosition()}) * | ||
| Matrix::MakeTranslation(Vector3{-global_pass_position}) * | ||
| transform_stack_.back().transform); | ||
|
|
||
| lazy_render_pass.inline_pass_context->EndPass(); | ||
|
|
@@ -517,13 +541,13 @@ bool ExperimentalCanvas::Restore() { | |
| // | ||
| // We do this in lieu of expanding/rounding out the subpass coverage in | ||
| // order to keep the bounds wrapping consistently tight around subpass | ||
| // elements. Which is necessary to avoid intense flickering in cases | ||
| // where a subpass texture has a large blur filter with clamp sampling. | ||
| // elements when there are image filters. Which is necessary to avoid | ||
| // intense flickering in cases where a subpass texture has a large blur | ||
| // filter with clamp sampling. | ||
| // | ||
| // See also this bug: https://github.com/flutter/flutter/issues/144213 | ||
| Point subpass_texture_position = | ||
| (save_layer_state.coverage.GetOrigin() - GetGlobalPassPosition()) | ||
| .Round(); | ||
| (save_layer_state.coverage.GetOrigin() - global_pass_position).Round(); | ||
|
|
||
| Entity element_entity; | ||
| element_entity.SetClipDepth(++current_depth_); | ||
|
|
@@ -545,9 +569,9 @@ bool ExperimentalCanvas::Restore() { | |
| // to the render target texture so far need to execute before it's bound | ||
| // for blending (otherwise the blend pass will end up executing before | ||
| // all the previous commands in the active pass). | ||
| auto input_texture = FlipBackdrop( | ||
| render_passes_, GetGlobalPassPosition(), | ||
| element_entity.GetClipDepth(), clip_coverage_stack_, renderer_); | ||
| auto input_texture = | ||
| FlipBackdrop(render_passes_, GetGlobalPassPosition(), | ||
| clip_coverage_stack_, renderer_); | ||
| if (!input_texture) { | ||
| return false; | ||
| } | ||
|
|
@@ -670,6 +694,11 @@ void ExperimentalCanvas::DrawTextFrame( | |
|
|
||
| void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity, | ||
| bool reuse_depth) { | ||
| if (IsSkipping()) { | ||
|
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. Should we test this earlier? Before we make the entity in the first place?
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. Eventually yes, as we won't ever make entities. But that logic is spread throughout the old and new canvas, so moving it earlier isn't easy as the old canvas doesn't use this check yet. |
||
| return; | ||
| } | ||
| FlushPendingClips(); | ||
|
|
||
| entity.SetTransform( | ||
| Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * | ||
| entity.GetTransform()); | ||
|
|
@@ -725,13 +754,14 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity, | |
| // to the render target texture so far need to execute before it's bound | ||
| // for blending (otherwise the blend pass will end up executing before | ||
| // all the previous commands in the active pass). | ||
| auto input_texture = | ||
| FlipBackdrop(render_passes_, GetGlobalPassPosition(), | ||
| entity.GetClipDepth(), clip_coverage_stack_, renderer_); | ||
| auto input_texture = FlipBackdrop(render_passes_, GetGlobalPassPosition(), | ||
| clip_coverage_stack_, renderer_); | ||
| if (!input_texture) { | ||
| return; | ||
| } | ||
|
|
||
| FlushPendingClips(); | ||
|
|
||
| // The coverage hint tells the rendered Contents which portion of the | ||
| // rendered output will actually be used, and so we set this to the | ||
| // current clip coverage (which is the max clip bounds). The contents may | ||
|
|
@@ -763,6 +793,10 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity, | |
| } | ||
|
|
||
| void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) { | ||
| if (IsSkipping()) { | ||
| return; | ||
|
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. Same as above, check this sooner? |
||
| } | ||
|
|
||
| auto transform = entity.GetTransform(); | ||
| entity.SetTransform( | ||
| Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * transform); | ||
|
|
@@ -816,6 +850,26 @@ void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) { | |
| *render_passes_.back().inline_pass_context->GetRenderPass(0).pass); | ||
| } | ||
|
|
||
| void ExperimentalCanvas::FlushPendingClips() { | ||
| // If there are any pending clips to replay, render any that may affect | ||
| // the entity we're about to render. | ||
| while (const EntityPassClipStack::ReplayResult* next_replay_clip = | ||
| clip_coverage_stack_.GetNextReplayResult(current_depth_)) { | ||
| auto& replay_entity = next_replay_clip->entity; | ||
|
|
||
| SetClipScissor( | ||
| next_replay_clip->clip_coverage, | ||
| *render_passes_.back().inline_pass_context->GetRenderPass(0).pass, | ||
| GetGlobalPassPosition()); | ||
| if (!replay_entity.Render(renderer_, | ||
| *render_passes_.back() | ||
| .inline_pass_context->GetRenderPass(0) | ||
| .pass)) { | ||
| VALIDATION_LOG << "Failed to render entity for clip replay."; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| bool ExperimentalCanvas::BlitToOnscreen() { | ||
| auto command_buffer = renderer_.GetContext()->CreateCommandBuffer(); | ||
| command_buffer->SetLabel("EntityPass Root Command Buffer"); | ||
|
|
||
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.
When we encounter a saveLayer that should be skipped, we need a way to ensure that no child entities of that saveLayer get rendered. This is a quickish fix to make this work correctly.
In the future, we could change the dispatcher to use indicies and have a more explicit skip step.
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.
We could also do a normal
save; clip(Empty)