Skip to content

Commit

Permalink
[Impeller] opt into exp canvas. (flutter#54913)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jonahwilliams authored Sep 6, 2024
1 parent e562f3f commit 397987a
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 73 deletions.
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
5 changes: 5 additions & 0 deletions impeller/aiks/canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
189 changes: 121 additions & 68 deletions impeller/aiks/experimental_canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
// found in the LICENSE file.

#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 +64,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 @@ -137,21 +139,16 @@ static std::shared_ptr<Texture> 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.";
}
}

Expand Down Expand Up @@ -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<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 +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<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 +375,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 +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<FilterContents> backdrop_filter_contents;
Point local_position = {0, 0};
Expand All @@ -393,11 +430,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,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.
Expand Down Expand Up @@ -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 ==
Expand All @@ -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> 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 @@ -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_);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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_);

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

Expand Down
Loading

0 comments on commit 397987a

Please sign in to comment.