Skip to content

Commit

Permalink
[Impeller] add clip coverage stack to exp canvas. (flutter#52215)
Browse files Browse the repository at this point in the history
Uses clip scissor and clip stack to update the clip state. This is working now!

flutter/flutter#142054
  • Loading branch information
jonahwilliams authored May 7, 2024
1 parent b64e230 commit b7bfd94
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 3 deletions.
126 changes: 123 additions & 3 deletions impeller/aiks/experimental_canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,29 @@
#include "impeller/entity/contents/framebuffer_blend_contents.h"
#include "impeller/entity/contents/text_contents.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/entity_pass_clip_stack.h"
#include "impeller/geometry/color.h"

namespace impeller {

namespace {

static void SetClipScissor(std::optional<Rect> clip_coverage,
RenderPass& pass,
Point global_pass_position) {
// Set the scissor to the clip coverage area. We do this prior to rendering
// the clip itself and all its contents.
IRect scissor;
if (clip_coverage.has_value()) {
clip_coverage = clip_coverage->Shift(-global_pass_position);
scissor = IRect::RoundOut(clip_coverage.value());
// The scissor rect must not exceed the size of the render target.
scissor = scissor.Intersection(IRect::MakeSize(pass.GetRenderTargetSize()))
.value_or(IRect());
}
pass.SetScissor(scissor);
}

static void ApplyFramebufferBlend(Entity& entity) {
auto src_contents = entity.GetContents();
auto contents = std::make_shared<FramebufferBlendContents>();
Expand All @@ -23,6 +42,8 @@ static void ApplyFramebufferBlend(Entity& entity) {
entity.SetBlendMode(BlendMode::kSource);
}

} // namespace

static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig =
RenderTarget::AttachmentConfig{
.storage_mode = StorageMode::kDeviceTransient,
Expand Down Expand Up @@ -87,21 +108,33 @@ static std::unique_ptr<EntityPassTarget> CreateRenderTarget(

ExperimentalCanvas::ExperimentalCanvas(ContentContext& renderer,
RenderTarget& render_target)
: Canvas(), renderer_(renderer), render_target_(render_target) {
: Canvas(),
renderer_(renderer),
render_target_(render_target),
clip_coverage_stack_(EntityPassClipStack(
Rect::MakeSize(render_target.GetRenderTargetSize()))) {
SetupRenderPass();
}

ExperimentalCanvas::ExperimentalCanvas(ContentContext& renderer,
RenderTarget& render_target,
Rect cull_rect)
: Canvas(cull_rect), renderer_(renderer), render_target_(render_target) {
: Canvas(cull_rect),
renderer_(renderer),
render_target_(render_target),
clip_coverage_stack_(EntityPassClipStack(
Rect::MakeSize(render_target.GetRenderTargetSize()))) {
SetupRenderPass();
}

ExperimentalCanvas::ExperimentalCanvas(ContentContext& renderer,
RenderTarget& render_target,
IRect cull_rect)
: Canvas(cull_rect), renderer_(renderer), render_target_(render_target) {
: Canvas(cull_rect),
renderer_(renderer),
render_target_(render_target),
clip_coverage_stack_(EntityPassClipStack(
Rect::MakeSize(render_target.GetRenderTargetSize()))) {
SetupRenderPass();
}

Expand Down Expand Up @@ -189,6 +222,13 @@ void ExperimentalCanvas::SaveLayer(

auto result = inline_pass_contexts_.back()->GetRenderPass(0u);
render_passes_.push_back(result.pass);

// Start non-collapsed subpasses with a fresh clip coverage stack limited by
// the subpass coverage. This is important because image filters applied to
// save layers may transform the subpass texture after it's rendered,
// causing parent clip coverage to get misaligned with the actual area that
// the subpass will affect in the parent pass.
clip_coverage_stack_.PushSubpass(subpass_coverage, GetClipHeight());
}

bool ExperimentalCanvas::Restore() {
Expand Down Expand Up @@ -247,10 +287,61 @@ bool ExperimentalCanvas::Restore() {
}

element_entity.Render(renderer_, *render_passes_.back());
clip_coverage_stack_.PopSubpass();
transform_stack_.pop_back();

// We don't need to restore clips if a saveLayer was performed, as the clip
// state is per render target, and no more rendering operations will be
// performed as the render target workloaded is completed in the restore.
return true;
}

size_t num_clips = transform_stack_.back().num_clips;
transform_stack_.pop_back();

if (num_clips > 0) {
Entity entity;
entity.SetTransform(
Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) *
GetCurrentTransform());
// This path is empty because ClipRestoreContents just generates a quad that
// takes up the full render target.
auto clip_restore = std::make_shared<ClipRestoreContents>();
clip_restore->SetRestoreHeight(GetClipHeight());
entity.SetContents(std::move(clip_restore));

auto current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage();
if (current_clip_coverage.has_value()) {
// Entity transforms are relative to the current pass position, so we need
// to check clip coverage in the same space.
current_clip_coverage =
current_clip_coverage->Shift(-GetGlobalPassPosition());
}

auto clip_coverage = entity.GetClipCoverage(current_clip_coverage);
if (clip_coverage.coverage.has_value()) {
clip_coverage.coverage =
clip_coverage.coverage->Shift(GetGlobalPassPosition());
}

EntityPassClipStack::ClipStateResult clip_state_result =
clip_coverage_stack_.ApplyClipState(clip_coverage, entity,
GetClipHeightFloor(),
GetGlobalPassPosition());

if (clip_state_result.clip_did_change) {
// We only need to update the pass scissor if the clip state has changed.
SetClipScissor(clip_coverage_stack_.CurrentClipCoverage(),
*render_passes_.back(), GetGlobalPassPosition());
}

if (!clip_state_result.should_render) {
return true;
}

entity.Render(renderer_, *render_passes_.back());
}

return true;
}

Expand Down Expand Up @@ -327,6 +418,35 @@ void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) {
FML_CHECK(current_depth_ <= transform_stack_.back().clip_depth)
<< current_depth_ << " <=? " << transform_stack_.back().clip_depth;
entity.SetClipDepth(transform_stack_.back().clip_depth);

auto current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage();
if (current_clip_coverage.has_value()) {
// Entity transforms are relative to the current pass position, so we need
// to check clip coverage in the same space.
current_clip_coverage =
current_clip_coverage->Shift(-GetGlobalPassPosition());
}

auto clip_coverage = entity.GetClipCoverage(current_clip_coverage);
if (clip_coverage.coverage.has_value()) {
clip_coverage.coverage =
clip_coverage.coverage->Shift(GetGlobalPassPosition());
}

EntityPassClipStack::ClipStateResult clip_state_result =
clip_coverage_stack_.ApplyClipState(
clip_coverage, entity, GetClipHeightFloor(), GetGlobalPassPosition());

if (clip_state_result.clip_did_change) {
// We only need to update the pass scissor if the clip state has changed.
SetClipScissor(clip_coverage_stack_.CurrentClipCoverage(),
*render_passes_.back(), GetGlobalPassPosition());
}

if (!clip_state_result.should_render) {
return;
}

entity.Render(renderer_, *render_passes_.back());
}

Expand Down
10 changes: 10 additions & 0 deletions impeller/aiks/experimental_canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "impeller/aiks/paint.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/entity_pass.h"
#include "impeller/entity/entity_pass_clip_stack.h"

namespace impeller {

Expand Down Expand Up @@ -62,8 +63,17 @@ class ExperimentalCanvas : public Canvas {
};

private:
// clip depth of the previous save or 0.
size_t GetClipHeightFloor() const {
if (transform_stack_.size() > 1) {
return transform_stack_[transform_stack_.size() - 2].clip_height;
}
return 0;
}

ContentContext& renderer_;
RenderTarget& render_target_;
EntityPassClipStack clip_coverage_stack_;
std::vector<std::unique_ptr<InlinePassContext>> inline_pass_contexts_;
std::vector<std::unique_ptr<EntityPassTarget>> entity_pass_targets_;
std::vector<SaveLayerState> save_layer_state_;
Expand Down

0 comments on commit b7bfd94

Please sign in to comment.