3
3
// found in the LICENSE file.
4
4
5
5
#include " impeller/aiks/experimental_canvas.h"
6
+
6
7
#include < limits>
8
+ #include < optional>
9
+
7
10
#include " fml/logging.h"
8
11
#include " fml/trace_event.h"
9
12
#include " impeller/aiks/canvas.h"
@@ -61,7 +64,6 @@ static void ApplyFramebufferBlend(Entity& entity) {
61
64
static std::shared_ptr<Texture> FlipBackdrop (
62
65
std::vector<LazyRenderingConfig>& render_passes,
63
66
Point global_pass_position,
64
- size_t current_clip_depth,
65
67
EntityPassClipStack& clip_coverage_stack,
66
68
ContentContext& renderer) {
67
69
auto rendering_config = std::move (render_passes.back ());
@@ -137,21 +139,16 @@ static std::shared_ptr<Texture> FlipBackdrop(
137
139
138
140
// Restore any clips that were recorded before the backdrop filter was
139
141
// 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) {
147
144
SetClipScissor (
148
- next_replay_clip-> clip_coverage ,
145
+ replay. clip_coverage ,
149
146
*render_passes.back ().inline_pass_context ->GetRenderPass (0 ).pass ,
150
147
global_pass_position);
151
- if (!replay_entity .Render (
148
+ if (!replay. entity .Render (
152
149
renderer,
153
150
*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 ." ;
155
152
}
156
153
}
157
154
@@ -296,41 +293,43 @@ void ExperimentalCanvas::SetupRenderPass() {
296
293
}
297
294
}
298
295
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
+
299
303
void ExperimentalCanvas::Save (uint32_t total_content_depth) {
304
+ if (IsSkipping ()) {
305
+ return SkipUntilMatchingRestore (total_content_depth);
306
+ }
307
+
300
308
auto entry = CanvasStackEntry{};
301
309
entry.transform = transform_stack_.back ().transform ;
302
310
entry.cull_rect = transform_stack_.back ().cull_rect ;
303
311
entry.clip_depth = current_depth_ + total_content_depth;
304
312
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 )
306
314
<< entry.clip_depth << " <=? " << transform_stack_.back ().clip_depth
307
315
<< " after allocating " << total_content_depth;
308
316
entry.clip_height = transform_stack_.back ().clip_height ;
309
317
entry.rendering_mode = Entity::RenderingMode::kDirect ;
310
- transform_stack_.emplace_back (entry);
318
+ transform_stack_.push_back (entry);
311
319
}
312
320
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 {
322
322
if (!clip_coverage_stack_.HasCoverage ()) {
323
323
// The current clip is empty. This means the pass texture won't be
324
324
// visible, so skip it.
325
- Save (total_content_depth);
326
- return ;
325
+ return std::nullopt;
327
326
}
328
327
329
328
auto maybe_current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage ();
330
329
if (!maybe_current_clip_coverage.has_value ()) {
331
- Save (total_content_depth);
332
- return ;
330
+ return std::nullopt;
333
331
}
332
+
334
333
auto current_clip_coverage = maybe_current_clip_coverage.value ();
335
334
336
335
// The maximum coverage of the subpass. Subpasses textures should never
@@ -343,8 +342,28 @@ void ExperimentalCanvas::SaveLayer(
343
342
.Intersection (current_clip_coverage);
344
343
345
344
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);
348
367
}
349
368
auto coverage_limit = maybe_coverage_limit.value ();
350
369
@@ -356,10 +375,9 @@ void ExperimentalCanvas::SaveLayer(
356
375
return ;
357
376
}
358
377
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 );
363
381
364
382
std::optional<Rect> maybe_subpass_coverage = ComputeSaveLayerCoverage (
365
383
bounds.value_or (Rect::MakeMaximum ()),
@@ -371,13 +389,32 @@ void ExperimentalCanvas::SaveLayer(
371
389
/* flood_input_coverage=*/ !!backdrop_filter //
372
390
);
373
391
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);
378
394
}
395
+
379
396
auto subpass_coverage = maybe_subpass_coverage.value ();
380
397
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
+
381
418
// Backdrop filter state, ignored if there is no BDF.
382
419
std::shared_ptr<FilterContents> backdrop_filter_contents;
383
420
Point local_position = {0 , 0 };
@@ -393,11 +430,10 @@ void ExperimentalCanvas::SaveLayer(
393
430
return filter;
394
431
};
395
432
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_ //
401
437
);
402
438
if (!input_texture) {
403
439
// Validation failures are logged in FlipBackdrop.
@@ -419,23 +455,24 @@ void ExperimentalCanvas::SaveLayer(
419
455
paint_copy.color .alpha *= transform_stack_.back ().distributed_opacity ;
420
456
transform_stack_.back ().distributed_opacity = 1.0 ;
421
457
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
+ )));
428
464
save_layer_state_.push_back (SaveLayerState{paint_copy, subpass_coverage});
429
465
430
466
CanvasStackEntry entry;
431
467
entry.transform = transform_stack_.back ().transform ;
432
468
entry.cull_rect = transform_stack_.back ().cull_rect ;
433
469
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 )
435
471
<< entry.clip_depth << " <=? " << transform_stack_.back ().clip_depth
436
472
<< " after allocating " << total_content_depth;
437
473
entry.clip_height = transform_stack_.back ().clip_height ;
438
474
entry.rendering_mode = Entity::RenderingMode::kSubpassAppendSnapshotTransform ;
475
+ entry.did_round_out = did_round_out;
439
476
transform_stack_.emplace_back (entry);
440
477
441
478
// The current clip aiks clip culling can not handle image filters.
@@ -484,10 +521,15 @@ bool ExperimentalCanvas::Restore() {
484
521
// to be overly conservative, but we need to jump the depth to
485
522
// the clip depth so that the next rendering op will get a
486
523
// 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 )
488
525
<< current_depth_ << " <=? " << transform_stack_.back ().clip_depth ;
489
526
current_depth_ = transform_stack_.back ().clip_depth ;
490
527
528
+ if (IsSkipping ()) {
529
+ transform_stack_.pop_back ();
530
+ return true ;
531
+ }
532
+
491
533
if (transform_stack_.back ().rendering_mode ==
492
534
Entity::RenderingMode::kSubpassAppendSnapshotTransform ||
493
535
transform_stack_.back ().rendering_mode ==
@@ -499,12 +541,13 @@ bool ExperimentalCanvas::Restore() {
499
541
500
542
SaveLayerState save_layer_state = save_layer_state_.back ();
501
543
save_layer_state_.pop_back ();
544
+ auto global_pass_position = GetGlobalPassPosition ();
502
545
503
546
std::shared_ptr<Contents> contents =
504
547
PaintPassDelegate (save_layer_state.paint )
505
548
.CreateContentsForSubpassTarget (
506
549
lazy_render_pass.inline_pass_context ->GetTexture (),
507
- Matrix::MakeTranslation (Vector3{-GetGlobalPassPosition () }) *
550
+ Matrix::MakeTranslation (Vector3{-global_pass_position }) *
508
551
transform_stack_.back ().transform );
509
552
510
553
lazy_render_pass.inline_pass_context ->EndPass ();
@@ -514,16 +557,19 @@ bool ExperimentalCanvas::Restore() {
514
557
// sampling, so aligning here is important for avoiding visual nearest
515
558
// sampling errors caused by limited floating point precision when
516
559
// 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
+ }
527
573
528
574
Entity element_entity;
529
575
element_entity.SetClipDepth (++current_depth_);
@@ -545,9 +591,9 @@ bool ExperimentalCanvas::Restore() {
545
591
// to the render target texture so far need to execute before it's bound
546
592
// for blending (otherwise the blend pass will end up executing before
547
593
// 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_);
551
597
if (!input_texture) {
552
598
return false ;
553
599
}
@@ -670,6 +716,10 @@ void ExperimentalCanvas::DrawTextFrame(
670
716
671
717
void ExperimentalCanvas::AddRenderEntityToCurrentPass (Entity entity,
672
718
bool reuse_depth) {
719
+ if (IsSkipping ()) {
720
+ return ;
721
+ }
722
+
673
723
entity.SetTransform (
674
724
Matrix::MakeTranslation (Vector3 (-GetGlobalPassPosition ())) *
675
725
entity.GetTransform ());
@@ -708,7 +758,7 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity,
708
758
// We can render at a depth up to and including the depth of the currently
709
759
// active clips and we will still be clipped out, but we cannot render at
710
760
// 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 )
712
762
<< current_depth_ << " <=? " << transform_stack_.back ().clip_depth ;
713
763
entity.SetClipDepth (current_depth_);
714
764
@@ -725,9 +775,8 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity,
725
775
// to the render target texture so far need to execute before it's bound
726
776
// for blending (otherwise the blend pass will end up executing before
727
777
// 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_);
731
780
if (!input_texture) {
732
781
return ;
733
782
}
@@ -763,6 +812,10 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity,
763
812
}
764
813
765
814
void ExperimentalCanvas::AddClipEntityToCurrentPass (Entity entity) {
815
+ if (IsSkipping ()) {
816
+ return ;
817
+ }
818
+
766
819
auto transform = entity.GetTransform ();
767
820
entity.SetTransform (
768
821
Matrix::MakeTranslation (Vector3 (-GetGlobalPassPosition ())) * transform);
@@ -777,7 +830,7 @@ void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) {
777
830
// to know if a clip will actually be used in advance of storing it in
778
831
// the DisplayList buffer.
779
832
// 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 )
781
834
<< current_depth_ << " <=? " << transform_stack_.back ().clip_depth ;
782
835
entity.SetClipDepth (transform_stack_.back ().clip_depth );
783
836
0 commit comments