diff --git a/common/config.gni b/common/config.gni index 4f8f690811c38..6c2354e7c9128 100644 --- a/common/config.gni +++ b/common/config.gni @@ -37,7 +37,7 @@ declare_args() { slimpeller = false # Opt into new DL dispatcher that skips AIKS layer - experimental_canvas = false + experimental_canvas = true } # feature_defines_list --------------------------------------------------------- diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index dbc3a1952dd73..0b0c3554c451d 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -37,6 +37,11 @@ struct CanvasStackEntry { size_t num_clips = 0u; Scalar distributed_opacity = 1.0f; Entity::RenderingMode rendering_mode = Entity::RenderingMode::kDirect; + // Whether all entities in the current save should be skipped. + bool skipping = false; + // Whether subpass coverage was rounded out to pixel coverage, or if false + // truncated. + bool did_round_out = false; }; enum class PointStyle { diff --git a/impeller/aiks/experimental_canvas.cc b/impeller/aiks/experimental_canvas.cc index 0ea88adc32511..b262e8dbb3a77 100644 --- a/impeller/aiks/experimental_canvas.cc +++ b/impeller/aiks/experimental_canvas.cc @@ -3,7 +3,10 @@ // found in the LICENSE file. #include "impeller/aiks/experimental_canvas.h" + #include +#include + #include "fml/logging.h" #include "fml/trace_event.h" #include "impeller/aiks/canvas.h" @@ -61,7 +64,6 @@ static void ApplyFramebufferBlend(Entity& entity) { static std::shared_ptr FlipBackdrop( std::vector& 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()); @@ -137,21 +139,16 @@ static std::shared_ptr FlipBackdrop( // Restore any clips that were recorded before the backdrop filter was // 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; + auto& replay_entities = clip_coverage_stack.GetReplayEntities(); + for (const auto& replay : replay_entities) { SetClipScissor( - next_replay_clip->clip_coverage, + replay.clip_coverage, *render_passes.back().inline_pass_context->GetRenderPass(0).pass, global_pass_position); - if (!replay_entity.Render( + if (!replay.entity.Render( renderer, *render_passes.back().inline_pass_context->GetRenderPass(0).pass)) { - VALIDATION_LOG << "Failed to render entity for clip replay."; + VALIDATION_LOG << "Failed to render entity for clip restore."; } } @@ -296,41 +293,43 @@ 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; entry.clip_depth = current_depth_ + total_content_depth; entry.distributed_opacity = transform_stack_.back().distributed_opacity; - FML_CHECK(entry.clip_depth <= transform_stack_.back().clip_depth) + FML_DCHECK(entry.clip_depth <= transform_stack_.back().clip_depth) << entry.clip_depth << " <=? " << transform_stack_.back().clip_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 bounds, - const std::shared_ptr& backdrop_filter, - ContentBoundsPromise bounds_promise, - uint32_t total_content_depth, - bool can_distribute_opacity) { - TRACE_EVENT0("flutter", "Canvas::saveLayer"); - +std::optional 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 +342,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 bounds, + const std::shared_ptr& 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 +375,9 @@ void ExperimentalCanvas::SaveLayer( return; } - std::shared_ptr filter_contents; - if (paint.image_filter) { - filter_contents = paint.image_filter->GetFilterContents(); - } + std::shared_ptr filter_contents = paint.WithImageFilter( + Rect(), transform_stack_.back().transform, + Entity::RenderingMode::kSubpassPrependSnapshotTransform); std::optional maybe_subpass_coverage = ComputeSaveLayerCoverage( bounds.value_or(Rect::MakeMaximum()), @@ -371,13 +389,32 @@ 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. + // + // See also this bug: https://github.com/flutter/flutter/issues/144213 + // + // TODO(jonahwilliams): this could still round out for filters that use decal + // sampling mode. + ISize subpass_size; + bool did_round_out = false; + if (paint.image_filter) { + subpass_size = ISize(subpass_coverage.GetSize()); + } else { + did_round_out = true; + subpass_size = ISize(IRect::RoundOut(subpass_coverage).GetSize()); + } + if (subpass_size.IsEmpty()) { + return SkipUntilMatchingRestore(total_content_depth); + } + // Backdrop filter state, ignored if there is no BDF. std::shared_ptr backdrop_filter_contents; Point local_position = {0, 0}; @@ -393,11 +430,10 @@ void ExperimentalCanvas::SaveLayer( return filter; }; - auto input_texture = FlipBackdrop(render_passes_, // - GetGlobalPassPosition(), // - std::numeric_limits::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,23 +455,24 @@ 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; entry.transform = transform_stack_.back().transform; entry.cull_rect = transform_stack_.back().cull_rect; entry.clip_depth = current_depth_ + total_content_depth; - FML_CHECK(entry.clip_depth <= transform_stack_.back().clip_depth) + FML_DCHECK(entry.clip_depth <= transform_stack_.back().clip_depth) << entry.clip_depth << " <=? " << transform_stack_.back().clip_depth << " after allocating " << total_content_depth; entry.clip_height = transform_stack_.back().clip_height; entry.rendering_mode = Entity::RenderingMode::kSubpassAppendSnapshotTransform; + entry.did_round_out = did_round_out; transform_stack_.emplace_back(entry); // The current clip aiks clip culling can not handle image filters. @@ -484,10 +521,15 @@ bool ExperimentalCanvas::Restore() { // to be overly conservative, but we need to jump the depth to // the clip depth so that the next rendering op will get a // larger depth (it will pre-increment the current_depth_ value). - FML_CHECK(current_depth_ <= transform_stack_.back().clip_depth) + FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth) << 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 +541,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 = 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(); @@ -514,16 +557,19 @@ bool ExperimentalCanvas::Restore() { // sampling, so aligning here is important for avoiding visual nearest // sampling errors caused by limited floating point precision when // straddling a half pixel boundary. - // - // 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. - // - // See also this bug: https://github.com/flutter/flutter/issues/144213 - Point subpass_texture_position = - (save_layer_state.coverage.GetOrigin() - GetGlobalPassPosition()) - .Round(); + Point subpass_texture_position; + if (transform_stack_.back().did_round_out) { + // Subpass coverage was rounded out, origin potentially moved "down" by + // as much as a pixel. + subpass_texture_position = + (save_layer_state.coverage.GetOrigin() - global_pass_position) + .Floor(); + } else { + // Subpass coverage was truncated. Pick the closest phyiscal pixel. + subpass_texture_position = + (save_layer_state.coverage.GetOrigin() - global_pass_position) + .Round(); + } Entity element_entity; element_entity.SetClipDepth(++current_depth_); @@ -545,9 +591,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 +716,10 @@ void ExperimentalCanvas::DrawTextFrame( void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity, bool reuse_depth) { + if (IsSkipping()) { + return; + } + entity.SetTransform( Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * entity.GetTransform()); @@ -708,7 +758,7 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity, // We can render at a depth up to and including the depth of the currently // active clips and we will still be clipped out, but we cannot render at // a depth that is greater than the current clips or we will not be clipped. - FML_CHECK(current_depth_ <= transform_stack_.back().clip_depth) + FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth) << current_depth_ << " <=? " << transform_stack_.back().clip_depth; entity.SetClipDepth(current_depth_); @@ -725,9 +775,8 @@ 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; } @@ -763,6 +812,10 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity, } void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) { + if (IsSkipping()) { + return; + } + auto transform = entity.GetTransform(); entity.SetTransform( Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * transform); @@ -777,7 +830,7 @@ void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) { // to know if a clip will actually be used in advance of storing it in // the DisplayList buffer. // See https://github.com/flutter/flutter/issues/147021 - FML_CHECK(current_depth_ <= transform_stack_.back().clip_depth) + FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth) << current_depth_ << " <=? " << transform_stack_.back().clip_depth; entity.SetClipDepth(transform_stack_.back().clip_depth); diff --git a/impeller/aiks/experimental_canvas.h b/impeller/aiks/experimental_canvas.h index debb4baf088fc..282c71b536410 100644 --- a/impeller/aiks/experimental_canvas.h +++ b/impeller/aiks/experimental_canvas.h @@ -87,6 +87,10 @@ class ExperimentalCanvas : public Canvas { }; private: + /// @brief Compute the current coverage limit in screen space, or + /// std::nullopt. + std::optional ComputeCoverageLimit() const; + // clip depth of the previous save or 0. size_t GetClipHeightFloor() const { if (transform_stack_.size() > 1) { @@ -95,6 +99,13 @@ class ExperimentalCanvas : public Canvas { return 0; } + /// @brief Whether all entites should be skipped until a corresponding + /// restore. + bool IsSkipping() { return transform_stack_.back().skipping; } + + /// @brief Skip all rendering/clipping entities until next restore. + void SkipUntilMatchingRestore(size_t total_content_depth); + ContentContext& renderer_; RenderTarget& render_target_; const bool requires_readback_; @@ -108,7 +119,7 @@ class ExperimentalCanvas : public Canvas { void AddClipEntityToCurrentPass(Entity entity) override; bool BlitToOnscreen(); - Point GetGlobalPassPosition() { + Point GetGlobalPassPosition() const { if (save_layer_state_.empty()) { return Point(0, 0); } diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 18be21f0a4999..2ab56520b3f8c 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -54,8 +54,9 @@ namespace impeller { // The watchdog object allocated here will automatically double-check // the depth usage at any exit point to the function, or any other // point at which it falls out of scope. -#define AUTO_DEPTH_WATCHER(d) \ - DepthWatcher _watcher(__FILE__, __LINE__, GetCanvas(), d) +#define AUTO_DEPTH_WATCHER(d) \ + DepthWatcher _watcher(__FILE__, __LINE__, GetCanvas(), \ + paint_.mask_blur_descriptor.has_value(), d) // While the AUTO_DEPTH_WATCHER macro will check the depth usage at // any exit point from the dispatch function, sometimes the dispatch @@ -75,11 +76,12 @@ struct DepthWatcher { DepthWatcher(const std::string& file, int line, const impeller::Canvas& canvas, + bool has_mask_blur, int allowed) : file_(file), line_(line), canvas_(canvas), - allowed_(allowed), + allowed_(has_mask_blur ? allowed + 1 : allowed), old_depth_(canvas.GetOpDepth()), old_max_(canvas.GetMaxOpDepth()) {} diff --git a/testing/dart/painting_test.dart b/testing/dart/painting_test.dart index 5c90cc6512107..fc39f9fdb7dc6 100644 --- a/testing/dart/painting_test.dart +++ b/testing/dart/painting_test.dart @@ -89,6 +89,7 @@ void main() { final SceneBuilder sceneBuilder = SceneBuilder(); final Picture redClippedPicture = makePicture((Canvas canvas) { + canvas.drawPaint(Paint()..color = const Color(0xFFFFFFFF)); canvas.clipRect(const Rect.fromLTRB(10, 10, 200, 200)); canvas.clipRect(const Rect.fromLTRB(11, 10, 300, 200)); canvas.drawPaint(Paint()..color = const Color(0xFFFF0000));