Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 397987a

Browse files
author
Jonah Williams
authored
[Impeller] opt into exp canvas. (#54913)
Switch back to new canvas implementation, which allows us to complete the display list/impeller interop arc of work. While we're at it, switch the subpass size rounding logic to round out if there is no image filter. Fixes flutter/flutter#152366 Part of flutter/flutter#142054
1 parent e562f3f commit 397987a

File tree

6 files changed

+145
-73
lines changed

6 files changed

+145
-73
lines changed

common/config.gni

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ declare_args() {
3737
slimpeller = false
3838

3939
# Opt into new DL dispatcher that skips AIKS layer
40-
experimental_canvas = false
40+
experimental_canvas = true
4141
}
4242

4343
# feature_defines_list ---------------------------------------------------------

impeller/aiks/canvas.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ struct CanvasStackEntry {
3737
size_t num_clips = 0u;
3838
Scalar distributed_opacity = 1.0f;
3939
Entity::RenderingMode rendering_mode = Entity::RenderingMode::kDirect;
40+
// Whether all entities in the current save should be skipped.
41+
bool skipping = false;
42+
// Whether subpass coverage was rounded out to pixel coverage, or if false
43+
// truncated.
44+
bool did_round_out = false;
4045
};
4146

4247
enum class PointStyle {

impeller/aiks/experimental_canvas.cc

Lines changed: 121 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
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) {
6164
static 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+
299303
void 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

671717
void 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

765814
void 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

Comments
 (0)