33// found in the LICENSE file.
44
55#include " impeller/aiks/experimental_canvas.h"
6+
67#include < limits>
8+ #include < optional>
9+
710#include " fml/logging.h"
811#include " fml/trace_event.h"
912#include " impeller/aiks/canvas.h"
@@ -61,7 +64,6 @@ static void ApplyFramebufferBlend(Entity& entity) {
6164static std::shared_ptr<Texture> FlipBackdrop (
6265 std::vector<LazyRenderingConfig>& render_passes,
6366 Point global_pass_position,
64- size_t current_clip_depth,
6567 EntityPassClipStack& clip_coverage_stack,
6668 ContentContext& renderer) {
6769 auto rendering_config = std::move (render_passes.back ());
@@ -137,21 +139,16 @@ static std::shared_ptr<Texture> FlipBackdrop(
137139
138140 // Restore any clips that were recorded before the backdrop filter was
139141 // applied.
140- clip_coverage_stack.ActivateClipReplay ();
141-
142- // If there are any pending clips to replay, render any that may affect
143- // the entity we're about to render.
144- while (const EntityPassClipStack::ReplayResult* next_replay_clip =
145- clip_coverage_stack.GetNextReplayResult (current_clip_depth)) {
146- auto & replay_entity = next_replay_clip->entity ;
142+ auto & replay_entities = clip_coverage_stack.GetReplayEntities ();
143+ for (const auto & replay : replay_entities) {
147144 SetClipScissor (
148- next_replay_clip-> clip_coverage ,
145+ replay. clip_coverage ,
149146 *render_passes.back ().inline_pass_context ->GetRenderPass (0 ).pass ,
150147 global_pass_position);
151- if (!replay_entity .Render (
148+ if (!replay. entity .Render (
152149 renderer,
153150 *render_passes.back ().inline_pass_context ->GetRenderPass (0 ).pass )) {
154- VALIDATION_LOG << " Failed to render entity for clip replay ." ;
151+ VALIDATION_LOG << " Failed to render entity for clip restore ." ;
155152 }
156153 }
157154
@@ -296,41 +293,43 @@ void ExperimentalCanvas::SetupRenderPass() {
296293 }
297294}
298295
296+ void ExperimentalCanvas::SkipUntilMatchingRestore (size_t total_content_depth) {
297+ auto entry = CanvasStackEntry{};
298+ entry.skipping = true ;
299+ entry.clip_depth = current_depth_ + total_content_depth;
300+ transform_stack_.push_back (entry);
301+ }
302+
299303void ExperimentalCanvas::Save (uint32_t total_content_depth) {
304+ if (IsSkipping ()) {
305+ return SkipUntilMatchingRestore (total_content_depth);
306+ }
307+
300308 auto entry = CanvasStackEntry{};
301309 entry.transform = transform_stack_.back ().transform ;
302310 entry.cull_rect = transform_stack_.back ().cull_rect ;
303311 entry.clip_depth = current_depth_ + total_content_depth;
304312 entry.distributed_opacity = transform_stack_.back ().distributed_opacity ;
305- FML_CHECK (entry.clip_depth <= transform_stack_.back ().clip_depth )
313+ FML_DCHECK (entry.clip_depth <= transform_stack_.back ().clip_depth )
306314 << entry.clip_depth << " <=? " << transform_stack_.back ().clip_depth
307315 << " after allocating " << total_content_depth;
308316 entry.clip_height = transform_stack_.back ().clip_height ;
309317 entry.rendering_mode = Entity::RenderingMode::kDirect ;
310- transform_stack_.emplace_back (entry);
318+ transform_stack_.push_back (entry);
311319}
312320
313- void ExperimentalCanvas::SaveLayer (
314- const Paint& paint,
315- std::optional<Rect> bounds,
316- const std::shared_ptr<ImageFilter>& backdrop_filter,
317- ContentBoundsPromise bounds_promise,
318- uint32_t total_content_depth,
319- bool can_distribute_opacity) {
320- TRACE_EVENT0 (" flutter" , " Canvas::saveLayer" );
321-
321+ std::optional<Rect> ExperimentalCanvas::ComputeCoverageLimit () const {
322322 if (!clip_coverage_stack_.HasCoverage ()) {
323323 // The current clip is empty. This means the pass texture won't be
324324 // visible, so skip it.
325- Save (total_content_depth);
326- return ;
325+ return std::nullopt ;
327326 }
328327
329328 auto maybe_current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage ();
330329 if (!maybe_current_clip_coverage.has_value ()) {
331- Save (total_content_depth);
332- return ;
330+ return std::nullopt ;
333331 }
332+
334333 auto current_clip_coverage = maybe_current_clip_coverage.value ();
335334
336335 // The maximum coverage of the subpass. Subpasses textures should never
@@ -343,8 +342,28 @@ void ExperimentalCanvas::SaveLayer(
343342 .Intersection (current_clip_coverage);
344343
345344 if (!maybe_coverage_limit.has_value () || maybe_coverage_limit->IsEmpty ()) {
346- Save (total_content_depth);
347- return ;
345+ return std::nullopt ;
346+ }
347+
348+ return maybe_coverage_limit->Intersection (
349+ Rect::MakeSize (render_target_.GetRenderTargetSize ()));
350+ }
351+
352+ void ExperimentalCanvas::SaveLayer (
353+ const Paint& paint,
354+ std::optional<Rect> bounds,
355+ const std::shared_ptr<ImageFilter>& backdrop_filter,
356+ ContentBoundsPromise bounds_promise,
357+ uint32_t total_content_depth,
358+ bool can_distribute_opacity) {
359+ TRACE_EVENT0 (" flutter" , " Canvas::saveLayer" );
360+ if (IsSkipping ()) {
361+ return SkipUntilMatchingRestore (total_content_depth);
362+ }
363+
364+ auto maybe_coverage_limit = ComputeCoverageLimit ();
365+ if (!maybe_coverage_limit.has_value ()) {
366+ return SkipUntilMatchingRestore (total_content_depth);
348367 }
349368 auto coverage_limit = maybe_coverage_limit.value ();
350369
@@ -356,10 +375,9 @@ void ExperimentalCanvas::SaveLayer(
356375 return ;
357376 }
358377
359- std::shared_ptr<FilterContents> filter_contents;
360- if (paint.image_filter ) {
361- filter_contents = paint.image_filter ->GetFilterContents ();
362- }
378+ std::shared_ptr<FilterContents> filter_contents = paint.WithImageFilter (
379+ Rect (), transform_stack_.back ().transform ,
380+ Entity::RenderingMode::kSubpassPrependSnapshotTransform );
363381
364382 std::optional<Rect> maybe_subpass_coverage = ComputeSaveLayerCoverage (
365383 bounds.value_or (Rect::MakeMaximum ()),
@@ -371,13 +389,32 @@ void ExperimentalCanvas::SaveLayer(
371389 /* flood_input_coverage=*/ !!backdrop_filter //
372390 );
373391
374- if (!maybe_subpass_coverage.has_value () ||
375- maybe_subpass_coverage->IsEmpty ()) {
376- Save (total_content_depth);
377- return ;
392+ if (!maybe_subpass_coverage.has_value ()) {
393+ return SkipUntilMatchingRestore (total_content_depth);
378394 }
395+
379396 auto subpass_coverage = maybe_subpass_coverage.value ();
380397
398+ // When an image filter is present, clamp to avoid flicking due to nearest
399+ // sampled image. For other cases, round out to ensure than any geometry is
400+ // not cut off.
401+ //
402+ // See also this bug: https://github.com/flutter/flutter/issues/144213
403+ //
404+ // TODO(jonahwilliams): this could still round out for filters that use decal
405+ // sampling mode.
406+ ISize subpass_size;
407+ bool did_round_out = false ;
408+ if (paint.image_filter ) {
409+ subpass_size = ISize (subpass_coverage.GetSize ());
410+ } else {
411+ did_round_out = true ;
412+ subpass_size = ISize (IRect::RoundOut (subpass_coverage).GetSize ());
413+ }
414+ if (subpass_size.IsEmpty ()) {
415+ return SkipUntilMatchingRestore (total_content_depth);
416+ }
417+
381418 // Backdrop filter state, ignored if there is no BDF.
382419 std::shared_ptr<FilterContents> backdrop_filter_contents;
383420 Point local_position = {0 , 0 };
@@ -393,11 +430,10 @@ void ExperimentalCanvas::SaveLayer(
393430 return filter;
394431 };
395432
396- auto input_texture = FlipBackdrop (render_passes_, //
397- GetGlobalPassPosition (), //
398- std::numeric_limits<uint32_t >::max (), //
399- clip_coverage_stack_, //
400- renderer_ //
433+ auto input_texture = FlipBackdrop (render_passes_, //
434+ GetGlobalPassPosition (), //
435+ clip_coverage_stack_, //
436+ renderer_ //
401437 );
402438 if (!input_texture) {
403439 // Validation failures are logged in FlipBackdrop.
@@ -419,23 +455,24 @@ void ExperimentalCanvas::SaveLayer(
419455 paint_copy.color .alpha *= transform_stack_.back ().distributed_opacity ;
420456 transform_stack_.back ().distributed_opacity = 1.0 ;
421457
422- render_passes_.push_back (LazyRenderingConfig (
423- renderer_, //
424- CreateRenderTarget (renderer_, //
425- ISize (subpass_coverage. GetSize ()), //
426- Color::BlackTransparent () //
427- )));
458+ render_passes_.push_back (
459+ LazyRenderingConfig ( renderer_, //
460+ CreateRenderTarget (renderer_, //
461+ subpass_size, //
462+ Color::BlackTransparent () //
463+ )));
428464 save_layer_state_.push_back (SaveLayerState{paint_copy, subpass_coverage});
429465
430466 CanvasStackEntry entry;
431467 entry.transform = transform_stack_.back ().transform ;
432468 entry.cull_rect = transform_stack_.back ().cull_rect ;
433469 entry.clip_depth = current_depth_ + total_content_depth;
434- FML_CHECK (entry.clip_depth <= transform_stack_.back ().clip_depth )
470+ FML_DCHECK (entry.clip_depth <= transform_stack_.back ().clip_depth )
435471 << entry.clip_depth << " <=? " << transform_stack_.back ().clip_depth
436472 << " after allocating " << total_content_depth;
437473 entry.clip_height = transform_stack_.back ().clip_height ;
438474 entry.rendering_mode = Entity::RenderingMode::kSubpassAppendSnapshotTransform ;
475+ entry.did_round_out = did_round_out;
439476 transform_stack_.emplace_back (entry);
440477
441478 // The current clip aiks clip culling can not handle image filters.
@@ -484,10 +521,15 @@ bool ExperimentalCanvas::Restore() {
484521 // to be overly conservative, but we need to jump the depth to
485522 // the clip depth so that the next rendering op will get a
486523 // larger depth (it will pre-increment the current_depth_ value).
487- FML_CHECK (current_depth_ <= transform_stack_.back ().clip_depth )
524+ FML_DCHECK (current_depth_ <= transform_stack_.back ().clip_depth )
488525 << current_depth_ << " <=? " << transform_stack_.back ().clip_depth ;
489526 current_depth_ = transform_stack_.back ().clip_depth ;
490527
528+ if (IsSkipping ()) {
529+ transform_stack_.pop_back ();
530+ return true ;
531+ }
532+
491533 if (transform_stack_.back ().rendering_mode ==
492534 Entity::RenderingMode::kSubpassAppendSnapshotTransform ||
493535 transform_stack_.back ().rendering_mode ==
@@ -499,12 +541,13 @@ bool ExperimentalCanvas::Restore() {
499541
500542 SaveLayerState save_layer_state = save_layer_state_.back ();
501543 save_layer_state_.pop_back ();
544+ auto global_pass_position = GetGlobalPassPosition ();
502545
503546 std::shared_ptr<Contents> contents =
504547 PaintPassDelegate (save_layer_state.paint )
505548 .CreateContentsForSubpassTarget (
506549 lazy_render_pass.inline_pass_context ->GetTexture (),
507- Matrix::MakeTranslation (Vector3{-GetGlobalPassPosition () }) *
550+ Matrix::MakeTranslation (Vector3{-global_pass_position }) *
508551 transform_stack_.back ().transform );
509552
510553 lazy_render_pass.inline_pass_context ->EndPass ();
@@ -514,16 +557,19 @@ bool ExperimentalCanvas::Restore() {
514557 // sampling, so aligning here is important for avoiding visual nearest
515558 // sampling errors caused by limited floating point precision when
516559 // straddling a half pixel boundary.
517- //
518- // We do this in lieu of expanding/rounding out the subpass coverage in
519- // order to keep the bounds wrapping consistently tight around subpass
520- // elements. Which is necessary to avoid intense flickering in cases
521- // where a subpass texture has a large blur filter with clamp sampling.
522- //
523- // See also this bug: https://github.com/flutter/flutter/issues/144213
524- Point subpass_texture_position =
525- (save_layer_state.coverage .GetOrigin () - GetGlobalPassPosition ())
526- .Round ();
560+ Point subpass_texture_position;
561+ if (transform_stack_.back ().did_round_out ) {
562+ // Subpass coverage was rounded out, origin potentially moved "down" by
563+ // as much as a pixel.
564+ subpass_texture_position =
565+ (save_layer_state.coverage .GetOrigin () - global_pass_position)
566+ .Floor ();
567+ } else {
568+ // Subpass coverage was truncated. Pick the closest phyiscal pixel.
569+ subpass_texture_position =
570+ (save_layer_state.coverage .GetOrigin () - global_pass_position)
571+ .Round ();
572+ }
527573
528574 Entity element_entity;
529575 element_entity.SetClipDepth (++current_depth_);
@@ -545,9 +591,9 @@ bool ExperimentalCanvas::Restore() {
545591 // to the render target texture so far need to execute before it's bound
546592 // for blending (otherwise the blend pass will end up executing before
547593 // all the previous commands in the active pass).
548- auto input_texture = FlipBackdrop (
549- render_passes_, GetGlobalPassPosition (),
550- element_entity. GetClipDepth (), clip_coverage_stack_, renderer_);
594+ auto input_texture =
595+ FlipBackdrop ( render_passes_, GetGlobalPassPosition (),
596+ clip_coverage_stack_, renderer_);
551597 if (!input_texture) {
552598 return false ;
553599 }
@@ -670,6 +716,10 @@ void ExperimentalCanvas::DrawTextFrame(
670716
671717void ExperimentalCanvas::AddRenderEntityToCurrentPass (Entity entity,
672718 bool reuse_depth) {
719+ if (IsSkipping ()) {
720+ return ;
721+ }
722+
673723 entity.SetTransform (
674724 Matrix::MakeTranslation (Vector3 (-GetGlobalPassPosition ())) *
675725 entity.GetTransform ());
@@ -708,7 +758,7 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity,
708758 // We can render at a depth up to and including the depth of the currently
709759 // active clips and we will still be clipped out, but we cannot render at
710760 // a depth that is greater than the current clips or we will not be clipped.
711- FML_CHECK (current_depth_ <= transform_stack_.back ().clip_depth )
761+ FML_DCHECK (current_depth_ <= transform_stack_.back ().clip_depth )
712762 << current_depth_ << " <=? " << transform_stack_.back ().clip_depth ;
713763 entity.SetClipDepth (current_depth_);
714764
@@ -725,9 +775,8 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity,
725775 // to the render target texture so far need to execute before it's bound
726776 // for blending (otherwise the blend pass will end up executing before
727777 // all the previous commands in the active pass).
728- auto input_texture =
729- FlipBackdrop (render_passes_, GetGlobalPassPosition (),
730- entity.GetClipDepth (), clip_coverage_stack_, renderer_);
778+ auto input_texture = FlipBackdrop (render_passes_, GetGlobalPassPosition (),
779+ clip_coverage_stack_, renderer_);
731780 if (!input_texture) {
732781 return ;
733782 }
@@ -763,6 +812,10 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity,
763812}
764813
765814void ExperimentalCanvas::AddClipEntityToCurrentPass (Entity entity) {
815+ if (IsSkipping ()) {
816+ return ;
817+ }
818+
766819 auto transform = entity.GetTransform ();
767820 entity.SetTransform (
768821 Matrix::MakeTranslation (Vector3 (-GetGlobalPassPosition ())) * transform);
@@ -777,7 +830,7 @@ void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) {
777830 // to know if a clip will actually be used in advance of storing it in
778831 // the DisplayList buffer.
779832 // See https://github.com/flutter/flutter/issues/147021
780- FML_CHECK (current_depth_ <= transform_stack_.back ().clip_depth )
833+ FML_DCHECK (current_depth_ <= transform_stack_.back ().clip_depth )
781834 << current_depth_ << " <=? " << transform_stack_.back ().clip_depth ;
782835 entity.SetClipDepth (transform_stack_.back ().clip_depth );
783836
0 commit comments