Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
2 changes: 1 addition & 1 deletion common/config.gni
Original file line number Diff line number Diff line change
Expand Up @@ -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 ---------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions impeller/aiks/canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ 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;
Copy link
Contributor Author

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.

Copy link
Contributor

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)

};

enum class PointStyle {
Expand Down
180 changes: 117 additions & 63 deletions impeller/aiks/experimental_canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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();

Expand All @@ -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()),
Expand All @@ -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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The 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());
}
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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};
Expand All @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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 ==
Expand All @@ -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();
Expand All @@ -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_);
Expand All @@ -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;
}
Expand Down Expand Up @@ -670,6 +694,11 @@ void ExperimentalCanvas::DrawTextFrame(

void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity,
bool reuse_depth) {
if (IsSkipping()) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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());
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -763,6 +793,10 @@ void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity,
}

void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) {
if (IsSkipping()) {
return;
Copy link
Contributor

Choose a reason for hiding this comment

The 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);
Expand Down Expand Up @@ -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");
Expand Down
17 changes: 16 additions & 1 deletion impeller/aiks/experimental_canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ class ExperimentalCanvas : public Canvas {
};

private:
/// @brief Compute the current coverage limit in screen space, or
/// std::nullopt.
std::optional<Rect> ComputeCoverageLimit() const;

/// @brief After flipping the backdrop texture, render any clips that effect
/// the current entity based on depth.
void FlushPendingClips();

// clip depth of the previous save or 0.
size_t GetClipHeightFloor() const {
if (transform_stack_.size() > 1) {
Expand All @@ -95,6 +103,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_;
Expand All @@ -108,7 +123,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);
}
Expand Down