[Impeller] Reland 3: Implement draw order optimization. (flutter/engine#54673)

For each clip scope, draw opaque items in reverse order and
translucent/backdrop-independent items in their original order
afterwards. Clips are treated as translucent by the parent scope.

Respects clips, subpass collapse, and the clear color optimization.
This commit is contained in:
Brandon DeRosier
2024-08-22 18:22:09 -07:00
committed by GitHub
parent 3d9e37ac23
commit adbc360970
17 changed files with 675 additions and 92 deletions

View File

@@ -168,6 +168,7 @@
../../../flutter/impeller/entity/contents/host_buffer_unittests.cc
../../../flutter/impeller/entity/contents/test
../../../flutter/impeller/entity/contents/tiled_texture_contents_unittests.cc
../../../flutter/impeller/entity/draw_order_resolver_unittests.cc
../../../flutter/impeller/entity/entity_pass_target_unittests.cc
../../../flutter/impeller/entity/entity_pass_unittests.cc
../../../flutter/impeller/entity/entity_unittests.cc

View File

@@ -42831,6 +42831,8 @@ ORIGIN: ../../../flutter/impeller/entity/contents/tiled_texture_contents.cc + ..
ORIGIN: ../../../flutter/impeller/entity/contents/tiled_texture_contents.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/contents/vertices_contents.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/contents/vertices_contents.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/draw_order_resolver.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/draw_order_resolver.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/entity.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/entity.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/entity_pass.cc + ../../../flutter/LICENSE
@@ -45714,6 +45716,8 @@ FILE: ../../../flutter/impeller/entity/contents/tiled_texture_contents.cc
FILE: ../../../flutter/impeller/entity/contents/tiled_texture_contents.h
FILE: ../../../flutter/impeller/entity/contents/vertices_contents.cc
FILE: ../../../flutter/impeller/entity/contents/vertices_contents.h
FILE: ../../../flutter/impeller/entity/draw_order_resolver.cc
FILE: ../../../flutter/impeller/entity/draw_order_resolver.h
FILE: ../../../flutter/impeller/entity/entity.cc
FILE: ../../../flutter/impeller/entity/entity.h
FILE: ../../../flutter/impeller/entity/entity_pass.cc

View File

@@ -890,5 +890,39 @@ TEST_P(AiksTest, DispatcherDoesNotCullPerspectiveTransformedChildDisplayLists) {
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
// Results in a 100x100 green square. If any red is drawn, there is a bug.
TEST_P(AiksTest, BackdropRestoreUsesCorrectCoverageForFirstRestoredClip) {
DisplayListBuilder builder;
DlPaint paint;
// Add a difference clip that cuts out the bottom right corner
builder.ClipRect(SkRect::MakeLTRB(50, 50, 100, 100),
DlCanvas::ClipOp::kDifference);
// Draw a red rectangle that's going to be completely covered by green later.
paint.setColor(DlColor::kRed());
builder.DrawRect(SkRect::MakeLTRB(0, 0, 100, 100), paint);
// Add a clip restricting the backdrop filter to the top right corner.
auto count = builder.GetSaveCount();
builder.Save();
{
builder.ClipRect(SkRect::MakeLTRB(0, 0, 100, 100));
{
// Create a save layer with a backdrop blur filter.
auto backdrop_filter =
DlBlurImageFilter::Make(10.0, 10.0, DlTileMode::kDecal);
builder.SaveLayer(nullptr, nullptr, backdrop_filter.get());
}
}
builder.RestoreToCount(count);
// Finally, overwrite all the previous stuff with green.
paint.setColor(DlColor::kGreen());
builder.DrawRect(SkRect::MakeLTRB(0, 0, 100, 100), paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
} // namespace testing
} // namespace impeller

View File

@@ -166,6 +166,8 @@ impeller_component("entity") {
"contents/tiled_texture_contents.h",
"contents/vertices_contents.cc",
"contents/vertices_contents.h",
"draw_order_resolver.cc",
"draw_order_resolver.h",
"entity.cc",
"entity.h",
"entity_pass.cc",
@@ -250,6 +252,7 @@ impeller_component("entity_unittests") {
"contents/filters/matrix_filter_contents_unittests.cc",
"contents/host_buffer_unittests.cc",
"contents/tiled_texture_contents_unittests.cc",
"draw_order_resolver_unittests.cc",
"entity_pass_target_unittests.cc",
"entity_pass_unittests.cc",
"entity_playground.cc",

View File

@@ -213,6 +213,11 @@ class ColorSourceContents : public Contents {
pass.SetVertexBuffer(std::move(geometry_result.vertex_buffer));
options.primitive_type = geometry_result.type;
// Enable depth writing for all opaque entities in order to allow
// reordering. Opaque entities are coerced to source blending by
// `EntityPass::AddEntity`.
options.depth_write_enabled = options.blend_mode == BlendMode::kSource;
// Take the pre-populated vertex shader uniform struct and set managed
// values.
frame_info.mvp = geometry_result.transform;

View File

@@ -575,16 +575,16 @@ Entity ApplyBlurStyle(FilterContents::BlurStyle blur_style,
const ContentContext& renderer, const Entity& entity,
RenderPass& pass) mutable {
bool result = true;
blur_entity.SetClipDepth(entity.GetClipDepth());
blur_entity.SetTransform(entity.GetTransform() *
blurred_transform);
result = result && blur_entity.Render(renderer, pass);
snapshot_entity.SetTransform(
entity.GetTransform() *
Matrix::MakeScale(1.f / source_space_scalar) *
snapshot_transform);
snapshot_entity.SetClipDepth(entity.GetClipDepth());
result = result && snapshot_entity.Render(renderer, pass);
blur_entity.SetClipDepth(entity.GetClipDepth());
blur_entity.SetTransform(entity.GetTransform() *
blurred_transform);
result = result && blur_entity.Render(renderer, pass);
return result;
}),
fml::MakeCopyable([blur_entity = blur_entity.Clone(),

View File

@@ -153,6 +153,9 @@ bool TextureContents::Render(const ContentContext& renderer,
}
pipeline_options.primitive_type = PrimitiveType::kTriangleStrip;
pipeline_options.depth_write_enabled =
stencil_enabled_ && pipeline_options.blend_mode == BlendMode::kSource;
pass.SetPipeline(strict_source_rect_enabled_
? renderer.GetTextureStrictSrcPipeline(pipeline_options)
: renderer.GetTexturePipeline(pipeline_options));

View File

@@ -0,0 +1,123 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "impeller/entity/draw_order_resolver.h"
#include "flutter/fml/logging.h"
#include "impeller/base/validation.h"
namespace impeller {
DrawOrderResolver::DrawOrderResolver() : draw_order_layers_({{}}){};
void DrawOrderResolver::AddElement(size_t element_index, bool is_opaque) {
DrawOrderLayer& layer = draw_order_layers_.back();
if (is_opaque) {
layer.opaque_elements.push_back(element_index);
} else {
layer.dependent_elements.push_back(element_index);
}
}
void DrawOrderResolver::PushClip(size_t element_index) {
draw_order_layers_.back().dependent_elements.push_back(element_index);
draw_order_layers_.push_back({});
};
void DrawOrderResolver::PopClip() {
if (draw_order_layers_.size() == 1u) {
// This is likely recoverable, so don't assert.
VALIDATION_LOG << "Attemped to pop the first draw order clip layer. This "
"may be a bug in `EntityPass`.";
return;
}
DrawOrderLayer& layer = draw_order_layers_.back();
DrawOrderLayer& parent_layer =
draw_order_layers_[draw_order_layers_.size() - 2];
layer.WriteCombinedDraws(parent_layer.dependent_elements, 0, 0);
draw_order_layers_.pop_back();
}
void DrawOrderResolver::Flush() {
FML_DCHECK(draw_order_layers_.size() >= 1u);
size_t layer_count = draw_order_layers_.size();
// Pop all clip layers.
while (draw_order_layers_.size() > 1u) {
PopClip();
}
// Move the root layer items into the sorted list.
DrawOrderLayer& layer = draw_order_layers_.back();
if (!first_root_flush_.has_value()) {
// Record the first flush.
first_root_flush_ = std::move(layer);
layer = {};
} else {
// Write subsequent flushes into the sorted root list.
layer.WriteCombinedDraws(sorted_elements_, 0, 0);
layer.opaque_elements.clear();
layer.dependent_elements.clear();
}
// Refill with empty layers.
draw_order_layers_.resize(layer_count);
}
DrawOrderResolver::ElementRefs DrawOrderResolver::GetSortedDraws(
size_t opaque_skip_count,
size_t translucent_skip_count) const {
FML_DCHECK(draw_order_layers_.size() == 1u)
<< "Attempted to get sorted draws before all clips were popped.";
ElementRefs sorted_elements;
sorted_elements.reserve(
(first_root_flush_.has_value()
? first_root_flush_->opaque_elements.size() +
first_root_flush_->dependent_elements.size()
: 0u) +
sorted_elements_.size() +
draw_order_layers_.back().opaque_elements.size() +
draw_order_layers_.back().dependent_elements.size());
// Write all flushed items.
if (first_root_flush_.has_value()) {
first_root_flush_->WriteCombinedDraws(sorted_elements, opaque_skip_count,
translucent_skip_count);
}
sorted_elements.insert(sorted_elements.end(), sorted_elements_.begin(),
sorted_elements_.end());
// Write any remaining non-flushed items.
draw_order_layers_.back().WriteCombinedDraws(
sorted_elements, first_root_flush_.has_value() ? 0 : opaque_skip_count,
first_root_flush_.has_value() ? 0 : translucent_skip_count);
return sorted_elements;
}
void DrawOrderResolver::DrawOrderLayer::WriteCombinedDraws(
ElementRefs& destination,
size_t opaque_skip_count,
size_t translucent_skip_count) const {
FML_DCHECK(opaque_skip_count <= opaque_elements.size());
FML_DCHECK(translucent_skip_count <= dependent_elements.size());
destination.reserve(destination.size() + //
opaque_elements.size() - opaque_skip_count + //
dependent_elements.size() - translucent_skip_count);
// Draw backdrop-independent elements in reverse order first.
destination.insert(destination.end(), opaque_elements.rbegin(),
opaque_elements.rend() - opaque_skip_count);
// Then, draw backdrop-dependent elements in their original order.
destination.insert(destination.end(),
dependent_elements.begin() + translucent_skip_count,
dependent_elements.end());
}
} // namespace impeller

View File

@@ -0,0 +1,94 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_IMPELLER_ENTITY_DRAW_ORDER_RESOLVER_H_
#define FLUTTER_IMPELLER_ENTITY_DRAW_ORDER_RESOLVER_H_
#include <optional>
#include <vector>
namespace impeller {
/// Helper that records draw indices in painter's order and sorts the draws into
/// an optimized order based on translucency and clips.
class DrawOrderResolver {
public:
using ElementRefs = std::vector<size_t>;
DrawOrderResolver();
void AddElement(size_t element_index, bool is_opaque);
void PushClip(size_t element_index);
void PopClip();
void Flush();
//-------------------------------------------------------------------------
/// @brief Returns the sorted draws for the current draw order layer.
/// This should only be called after all recording has finished.
///
/// @param[in] opaque_skip_count The number of opaque elements to skip
/// when appending the combined elements.
/// This is used for the "clear color"
/// optimization.
/// @param[in] translucent_skip_count The number of translucent elements to
/// skip when appending the combined
/// elements. This is used for the
/// "clear color" optimization.
///
ElementRefs GetSortedDraws(size_t opaque_skip_count,
size_t translucent_skip_count) const;
private:
/// A data structure for collecting sorted draws for a given "draw order
/// layer". Currently these layers just correspond to the local clip stack.
struct DrawOrderLayer {
/// The list of backdrop-independent elements (always just opaque). These
/// are order independent, and so we render these elements in reverse
/// painter's order so that they cull one another.
ElementRefs opaque_elements;
/// The list of backdrop-dependent elements with respect to this draw
/// order layer. These elements are drawn after all of the independent
/// elements.
ElementRefs dependent_elements;
//-----------------------------------------------------------------------
/// @brief Appends the combined opaque and transparent elements into
/// a final destination buffer.
///
/// @param[in] destination The buffer to append the combined
/// elements to.
/// @param[in] opaque_skip_count The number of opaque elements to
/// skip when appending the combined
/// elements. This is used for the
/// "clear color" optimization.
/// @param[in] translucent_skip_count The number of translucent elements
/// to skip when appending the combined
/// elements. This is used for the
/// "clear color" optimization.
///
void WriteCombinedDraws(ElementRefs& destination,
size_t opaque_skip_count,
size_t translucent_skip_count) const;
};
std::vector<DrawOrderLayer> draw_order_layers_;
// The first time the root layer is flushed, the layer contents are stored
// here. This is done to enable element skipping for the clear color
// optimization.
std::optional<DrawOrderLayer> first_root_flush_;
// All subsequent root flushes are stored here.
ElementRefs sorted_elements_;
DrawOrderResolver(const DrawOrderResolver&) = delete;
DrawOrderResolver& operator=(const DrawOrderResolver&) = delete;
};
} // namespace impeller
#endif // FLUTTER_IMPELLER_ENTITY_DRAW_ORDER_RESOLVER_H_

View File

@@ -0,0 +1,156 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/testing/testing.h"
#include "impeller/entity/draw_order_resolver.h"
#include "third_party/googletest/googletest/include/gtest/gtest.h"
namespace impeller {
namespace testing {
TEST(DrawOrderResolverTest, GetSortedDrawsReturnsCorrectOrderWithNoClips) {
DrawOrderResolver resolver;
// Opaque items.
resolver.AddElement(0, true);
resolver.AddElement(1, true);
// Translucent items.
resolver.AddElement(2, false);
resolver.AddElement(3, false);
auto sorted_elements = resolver.GetSortedDraws(0, 0);
EXPECT_EQ(sorted_elements.size(), 4u);
// First, the opaque items are drawn in reverse order.
EXPECT_EQ(sorted_elements[0], 1u);
EXPECT_EQ(sorted_elements[1], 0u);
// Then the translucent items are drawn.
EXPECT_EQ(sorted_elements[2], 2u);
EXPECT_EQ(sorted_elements[3], 3u);
}
TEST(DrawOrderResolverTest, GetSortedDrawsReturnsCorrectOrderWithClips) {
DrawOrderResolver resolver;
// Items before clip.
resolver.AddElement(0, false);
resolver.AddElement(1, true);
resolver.AddElement(2, false);
resolver.AddElement(3, true);
// Clip.
resolver.PushClip(4);
{
// Clipped items.
resolver.AddElement(5, false);
resolver.AddElement(6, false);
// Clipped translucent items.
resolver.AddElement(7, true);
resolver.AddElement(8, true);
}
resolver.PopClip();
// Items after clip.
resolver.AddElement(9, true);
resolver.AddElement(10, false);
resolver.AddElement(11, true);
resolver.AddElement(12, false);
auto sorted_elements = resolver.GetSortedDraws(0, 0);
EXPECT_EQ(sorted_elements.size(), 13u);
// First, all the non-clipped opaque items are drawn in reverse order.
EXPECT_EQ(sorted_elements[0], 11u);
EXPECT_EQ(sorted_elements[1], 9u);
EXPECT_EQ(sorted_elements[2], 3u);
EXPECT_EQ(sorted_elements[3], 1u);
// Then, non-clipped translucent items that came before the clip are drawn in
// their original order.
EXPECT_EQ(sorted_elements[4], 0u);
EXPECT_EQ(sorted_elements[5], 2u);
// Then, the clip and its sorted child items are drawn.
EXPECT_EQ(sorted_elements[6], 4u);
{
// Opaque clipped items are drawn in reverse order.
EXPECT_EQ(sorted_elements[7], 8u);
EXPECT_EQ(sorted_elements[8], 7u);
// Translucent clipped items are drawn.
EXPECT_EQ(sorted_elements[9], 5u);
EXPECT_EQ(sorted_elements[10], 6u);
}
// Finally, the non-clipped translucent items which came after the clip are
// drawn in their original order.
EXPECT_EQ(sorted_elements[11], 10u);
EXPECT_EQ(sorted_elements[12], 12u);
}
TEST(DrawOrderResolverTest, GetSortedDrawsRespectsSkipCounts) {
DrawOrderResolver resolver;
// These items will be skipped.
resolver.AddElement(0, false);
resolver.AddElement(1, true);
resolver.AddElement(2, false);
// These ones will be included in the final draw list.
resolver.AddElement(3, false);
resolver.AddElement(4, true);
resolver.AddElement(5, true);
// Form the draw list, skipping elements 0, 1, and 2.
// This emulates what happens when entitypass applies the clear color
// optimization.
auto sorted_elements = resolver.GetSortedDraws(1, 2);
EXPECT_EQ(sorted_elements.size(), 3u);
// First, opaque items are drawn in reverse order.
EXPECT_EQ(sorted_elements[0], 5u);
EXPECT_EQ(sorted_elements[1], 4u);
// Then, translucent items are drawn.
EXPECT_EQ(sorted_elements[2], 3u);
}
TEST(DrawOrderResolverTest, GetSortedDrawsReturnsCorrectOrderWithFlush) {
DrawOrderResolver resolver;
resolver.AddElement(0, false);
resolver.AddElement(1, true);
resolver.AddElement(2, false);
resolver.AddElement(3, true);
resolver.Flush();
resolver.AddElement(4, false);
resolver.AddElement(5, true);
resolver.AddElement(6, false);
resolver.AddElement(7, true);
resolver.Flush();
resolver.AddElement(8, false);
resolver.AddElement(9, true);
resolver.AddElement(10, false);
resolver.AddElement(11, true);
auto sorted_elements = resolver.GetSortedDraws(1, 1);
EXPECT_EQ(sorted_elements.size(), 10u);
// Skipped draws apply to the first flush.
EXPECT_EQ(sorted_elements[0], 3u);
EXPECT_EQ(sorted_elements[1], 2u);
EXPECT_EQ(sorted_elements[2], 7u);
EXPECT_EQ(sorted_elements[3], 5u);
EXPECT_EQ(sorted_elements[4], 4u);
EXPECT_EQ(sorted_elements[5], 6u);
EXPECT_EQ(sorted_elements[6], 11u);
EXPECT_EQ(sorted_elements[7], 9u);
EXPECT_EQ(sorted_elements[8], 8u);
EXPECT_EQ(sorted_elements[9], 10u);
}
} // namespace testing
} // namespace impeller

View File

@@ -43,6 +43,8 @@ Entity::Entity(Entity&&) = default;
Entity::Entity(const Entity&) = default;
Entity& Entity::operator=(Entity&&) = default;
const Matrix& Entity::GetTransform() const {
return transform_;
}

View File

@@ -73,6 +73,8 @@ class Entity {
Entity(Entity&&);
Entity& operator=(Entity&&);
/// @brief Get the global transform matrix for this Entity.
const Matrix& GetTransform() const;

View File

@@ -20,6 +20,7 @@
#include "impeller/entity/contents/filters/inputs/filter_input.h"
#include "impeller/entity/contents/framebuffer_blend_contents.h"
#include "impeller/entity/contents/texture_contents.h"
#include "impeller/entity/draw_order_resolver.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/entity_pass_clip_stack.h"
#include "impeller/entity/inline_pass_context.h"
@@ -44,6 +45,10 @@ std::tuple<std::optional<Color>, BlendMode> ElementAsBackgroundColor(
}
} // namespace
bool EntityPass::IsSubpass(const Element& element) {
return std::holds_alternative<std::unique_ptr<EntityPass>>(element);
}
EntityPass::EntityPass() = default;
EntityPass::~EntityPass() = default;
@@ -106,11 +111,14 @@ void EntityPass::AddEntity(Entity entity) {
if (entity.GetBlendMode() > Entity::kLastPipelineBlendMode) {
advanced_blend_reads_from_pass_texture_ = true;
}
draw_order_resolver_.AddElement(elements_.size(),
entity.GetBlendMode() == BlendMode::kSource);
elements_.emplace_back(std::move(entity));
}
void EntityPass::PushClip(Entity entity) {
elements_.emplace_back(std::move(entity));
draw_order_resolver_.PushClip(elements_.size() - 1);
active_clips_.emplace_back(elements_.size() - 1);
}
@@ -129,6 +137,7 @@ void EntityPass::PopClips(size_t num_clips, uint64_t depth) {
FML_DCHECK(element);
element->SetClipDepth(depth);
active_clips_.pop_back();
draw_order_resolver_.PopClip();
}
}
@@ -271,8 +280,14 @@ EntityPass* EntityPass::AddSubpass(std::unique_ptr<EntityPass> pass) {
FML_DCHECK(pass->superpass_ == nullptr);
pass->superpass_ = this;
if (pass->backdrop_filter_proc_) {
bool has_backdrop_filter = pass->backdrop_filter_proc_ != nullptr;
if (has_backdrop_filter) {
backdrop_filter_reads_from_pass_texture_ = true;
// Since backdrop filters trigger the RenderPass to end and lose all depth
// information for opaque draws, this is a hard barrier for the draw order
// optimization. Flush all sorted draws accumulated up to this point.
draw_order_resolver_.Flush();
}
if (pass->blend_mode_ > Entity::kLastPipelineBlendMode) {
advanced_blend_reads_from_pass_texture_ = true;
@@ -280,6 +295,12 @@ EntityPass* EntityPass::AddSubpass(std::unique_ptr<EntityPass> pass) {
auto subpass_pointer = pass.get();
elements_.emplace_back(std::move(pass));
draw_order_resolver_.AddElement(elements_.size() - 1, false);
if (has_backdrop_filter) {
draw_order_resolver_.Flush();
}
return subpass_pointer;
}
@@ -744,6 +765,55 @@ bool EntityPass::RenderElement(Entity& element_entity,
ContentContext& renderer,
EntityPassClipStack& clip_coverage_stack,
Point global_pass_position) const {
// Setup advanced blends.
if (element_entity.GetBlendMode() > Entity::kLastPipelineBlendMode) {
if (renderer.GetDeviceCapabilities().SupportsFramebufferFetch()) {
auto src_contents = element_entity.GetContents();
auto contents = std::make_shared<FramebufferBlendContents>();
contents->SetChildContents(src_contents);
contents->SetBlendMode(element_entity.GetBlendMode());
element_entity.SetContents(std::move(contents));
element_entity.SetBlendMode(BlendMode::kSource);
} else {
// End the active pass and flush the buffer before rendering
// "advanced" blends. Advanced blends work by binding the current
// render target texture as an input ("destination"), blending with a
// second texture input ("source"), writing the result to an
// intermediate texture, and finally copying the data from the
// intermediate texture back to the render target texture. And so all
// of the commands that have written 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).
if (!pass_context.EndPass()) {
VALIDATION_LOG
<< "Failed to end the current render pass in order to read "
"from "
"the backdrop texture and apply an advanced blend.";
return false;
}
// Amend an advanced blend filter to the contents, attaching the pass
// texture.
auto texture = pass_context.GetTexture();
if (!texture) {
VALIDATION_LOG << "Failed to fetch the color texture in order to "
"apply an advanced blend.";
return false;
}
FilterInput::Vector inputs = {
FilterInput::Make(texture, element_entity.GetTransform().Invert()),
FilterInput::Make(element_entity.GetContents())};
auto contents =
ColorFilterContents::MakeBlend(element_entity.GetBlendMode(), inputs);
contents->SetCoverageHint(element_entity.GetCoverage());
element_entity.SetContents(std::move(contents));
element_entity.SetBlendMode(BlendMode::kSource);
}
}
auto result = pass_context.GetRenderPass(pass_depth);
if (!result.pass) {
// Failure to produce a render pass should be explained by specific errors
@@ -776,15 +846,19 @@ bool EntityPass::RenderElement(Entity& element_entity,
}
if (result.just_created) {
// Restore any clips that were recorded before the backdrop filter was
// applied.
auto& replay_entities = clip_coverage_stack.GetReplayEntities();
for (const auto& replay : replay_entities) {
SetClipScissor(clip_coverage_stack.CurrentClipCoverage(), *result.pass,
global_pass_position);
if (!replay.entity.Render(renderer, *result.pass)) {
VALIDATION_LOG << "Failed to render entity for clip restore.";
}
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(element_entity)) {
auto& replay_entity = next_replay_clip->entity;
SetClipScissor(next_replay_clip->clip_coverage, *result.pass,
global_pass_position);
if (!replay_entity.Render(renderer, *result.pass)) {
VALIDATION_LOG << "Failed to render entity for clip replay.";
return false;
}
}
@@ -891,21 +965,87 @@ bool EntityPass::OnRender(
renderer, clip_coverage_stack, global_pass_position);
}
bool is_collapsing_clear_colors = !collapsed_parent_pass &&
// Backdrop filters act as a entity before
// everything and disrupt the optimization.
!backdrop_filter_proc_;
for (const auto& element : elements_) {
// Skip elements that are incorporated into the clear color.
if (is_collapsing_clear_colors) {
auto [entity_color, _] =
ElementAsBackgroundColor(element, clear_color_size);
if (entity_color.has_value()) {
continue;
}
is_collapsing_clear_colors = false;
}
bool should_collapse_clear_colors =
!collapsed_parent_pass &&
// Backdrop filters act as a entity before
// everything and disrupt the optimization.
!backdrop_filter_proc_;
// Count the number of elements eaten by the clear color optimization. Break
// it down in terms of opaque and translucent elements so that we can skip
// over these entities when applying the clear color optimization.
size_t opaque_clear_entity_count = 0;
size_t translucent_clear_entity_count = 0;
if (should_collapse_clear_colors) {
for (const auto& element : elements_) {
if (const Entity* entity = std::get_if<Entity>(&element)) {
std::optional<Color> entity_color =
entity->AsBackgroundColor(clear_color_size);
if (entity_color.has_value()) {
if (entity->GetBlendMode() == BlendMode::kSource) {
opaque_clear_entity_count++;
} else {
translucent_clear_entity_count++;
}
// We've found an entity that replaces the whole background color of
// this layer, so continue counting.
continue;
}
}
// We came across an element that doesn't replace the background color of
// this layer, so stop counting.
break;
}
}
using ElementCallback = std::function<bool(const Element&)>;
using ElementIterator = std::function<bool(const ElementCallback&)>;
ElementIterator element_iterator;
if (renderer.GetDeviceCapabilities().SupportsFramebufferFetch()) {
element_iterator =
[this, &opaque_clear_entity_count,
&translucent_clear_entity_count](const ElementCallback& callback) {
const auto& sorted_elements = draw_order_resolver_.GetSortedDraws(
opaque_clear_entity_count, translucent_clear_entity_count);
for (const auto& element_ref : sorted_elements) {
const Element& element = elements_[element_ref];
if (!callback(element)) {
return false;
}
}
return true;
};
} else {
// If framebuffer fetch isn't supported, just disable the draw order
// optimization. We could technically make it work by flushing each time
// we encounter an advanced blend at recording time down the road.
element_iterator = [this, &opaque_clear_entity_count,
&translucent_clear_entity_count](
const ElementCallback& callback) {
size_t skips = opaque_clear_entity_count + translucent_clear_entity_count;
for (const auto& element : elements_) {
if (skips > 0) {
skips--;
continue;
}
if (!callback(element)) {
return false;
}
}
return true;
};
}
const auto& render_element = [&, this](Entity& entity) {
return RenderElement(entity, clip_height_floor, pass_context, pass_depth,
renderer, clip_coverage_stack, global_pass_position);
};
std::optional<Entity> deferred_entity;
bool result = element_iterator([&](const Element& element) {
EntityResult result =
GetEntityForElement(element, // element
renderer, // renderer
@@ -924,70 +1064,36 @@ bool EntityPass::OnRender(
// in `GetEntityForElement()`.
return false;
case EntityResult::kSkip:
continue;
return true;
};
//--------------------------------------------------------------------------
/// Setup advanced blends.
///
if (result.entity.GetBlendMode() > Entity::kLastPipelineBlendMode) {
if (renderer.GetDeviceCapabilities().SupportsFramebufferFetch()) {
auto src_contents = result.entity.GetContents();
auto contents = std::make_shared<FramebufferBlendContents>();
contents->SetChildContents(src_contents);
contents->SetBlendMode(result.entity.GetBlendMode());
result.entity.SetContents(std::move(contents));
result.entity.SetBlendMode(BlendMode::kSource);
} else {
// End the active pass and flush the buffer before rendering "advanced"
// blends. Advanced blends work by binding the current render target
// texture as an input ("destination"), blending with a second texture
// input ("source"), writing the result to an intermediate texture, and
// finally copying the data from the intermediate texture back to the
// render target texture. And so all of the commands that have written
// 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).
if (!pass_context.EndPass()) {
VALIDATION_LOG
<< "Failed to end the current render pass in order to read from "
"the backdrop texture and apply an advanced blend.";
return false;
}
// Amend an advanced blend filter to the contents, attaching the pass
// texture.
auto texture = pass_context.GetTexture();
if (!texture) {
VALIDATION_LOG << "Failed to fetch the color texture in order to "
"apply an advanced blend.";
return false;
}
FilterInput::Vector inputs = {
FilterInput::Make(texture, result.entity.GetTransform().Invert()),
FilterInput::Make(result.entity.GetContents())};
auto contents = ColorFilterContents::MakeBlend(
result.entity.GetBlendMode(), inputs);
contents->SetCoverageHint(result.entity.GetCoverage());
result.entity.SetContents(std::move(contents));
result.entity.SetBlendMode(BlendMode::kSource);
if (deferred_entity.has_value() &&
result.entity.GetBlendMode() != BlendMode::kSource) {
if (!render_element(*deferred_entity)) {
return false;
}
deferred_entity.reset();
}
//--------------------------------------------------------------------------
/// Render the Element.
///
if (!RenderElement(result.entity, clip_height_floor, pass_context,
pass_depth, renderer, clip_coverage_stack,
global_pass_position)) {
// Specific validation logs are handled in `render_element()`.
return false;
if (IsSubpass(element)) {
if (deferred_entity.has_value()) {
if (!render_element(*deferred_entity)) {
return false;
}
}
deferred_entity = std::move(result.entity);
return true;
}
return render_element(result.entity);
});
if (!result) {
return false;
}
if (deferred_entity.has_value() && !render_element(*deferred_entity)) {
return false;
}
return true;
}

View File

@@ -13,6 +13,7 @@
#include "impeller/entity/contents/contents.h"
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/entity/draw_order_resolver.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/entity_pass_clip_stack.h"
#include "impeller/entity/entity_pass_delegate.h"
@@ -53,6 +54,8 @@ class EntityPass {
/// `GetEntityForElement()`.
using Element = std::variant<Entity, std::unique_ptr<EntityPass>>;
static bool IsSubpass(const Element& element);
using BackdropFilterProc = std::function<std::shared_ptr<FilterContents>(
FilterInput::Ref,
const Matrix& effect_transform,
@@ -214,8 +217,7 @@ class EntityPass {
kSkip,
};
/// @brief The resulting entity that should be rendered. If `std::nullopt`,
/// there is nothing to render.
/// @brief The resulting entity that should be rendered.
Entity entity;
/// @brief This is set to `false` if there was an unexpected rendering
/// error while resolving the Entity.
@@ -316,10 +318,12 @@ class EntityPass {
/// evaluated and recorded to an `EntityPassTarget` by the `OnRender` method.
std::vector<Element> elements_;
DrawOrderResolver draw_order_resolver_;
/// The stack of currently active clips (during Aiks recording time). Each
/// entry is an index into the `elements_` list. The depth value of a clip is
/// the max of all the entities it affects, so assignment of the depth value
/// is deferred until clip restore or end of the EntityPass.
/// entry is an index into the `elements_` list. The depth value of a clip
/// is the max of all the entities it affects, so assignment of the depth
/// value is deferred until clip restore or end of the EntityPass.
std::vector<size_t> active_clips_;
EntityPass* superpass_ = nullptr;
@@ -338,8 +342,8 @@ class EntityPass {
/// 1. An entity with an "advanced blend" is added to the pass.
/// 2. A subpass with a backdrop filter is added to the pass.
/// These are tracked as separate values because we may ignore
/// `blend_reads_from_pass_texture_` if the device supports framebuffer based
/// advanced blends.
/// `blend_reads_from_pass_texture_` if the device supports framebuffer
/// based advanced blends.
bool advanced_blend_reads_from_pass_texture_ = false;
bool backdrop_filter_reads_from_pass_texture_ = false;

View File

@@ -3,6 +3,8 @@
// found in the LICENSE file.
#include "impeller/entity/entity_pass_clip_stack.h"
#include "flutter/fml/logging.h"
#include "impeller/entity/contents/clip_contents.h"
#include "impeller/entity/entity.h"
@@ -37,10 +39,12 @@ void EntityPassClipStack::PushSubpass(std::optional<Rect> subpass_coverage,
.clip_height = clip_height},
},
});
next_replay_index_ = 0;
}
void EntityPassClipStack::PopSubpass() {
subpass_state_.pop_back();
next_replay_index_ = subpass_state_.back().rendered_clip_entities.size();
}
const std::vector<ClipCoverageLayer>
@@ -138,12 +142,22 @@ void EntityPassClipStack::RecordEntity(const Entity& entity,
case Contents::ClipCoverage::Type::kNoChange:
return;
case Contents::ClipCoverage::Type::kAppend:
FML_DCHECK(next_replay_index_ ==
subpass_state.rendered_clip_entities.size())
<< "Not all clips have been replayed before appending new clip.";
subpass_state.rendered_clip_entities.push_back(
{.entity = entity.Clone(), .clip_coverage = clip_coverage});
next_replay_index_++;
break;
case Contents::ClipCoverage::Type::kRestore:
FML_DCHECK(next_replay_index_ <=
subpass_state.rendered_clip_entities.size());
if (!subpass_state.rendered_clip_entities.empty()) {
subpass_state.rendered_clip_entities.pop_back();
if (next_replay_index_ > subpass_state.rendered_clip_entities.size()) {
next_replay_index_ = subpass_state.rendered_clip_entities.size();
}
}
break;
}
@@ -159,4 +173,27 @@ EntityPassClipStack::GetReplayEntities() const {
return subpass_state_.back().rendered_clip_entities;
}
void EntityPassClipStack::ActivateClipReplay() {
next_replay_index_ = 0;
}
const EntityPassClipStack::ReplayResult*
EntityPassClipStack::GetNextReplayResult(const Entity& entity) {
if (next_replay_index_ >=
subpass_state_.back().rendered_clip_entities.size()) {
// No clips need to be replayed.
return nullptr;
}
ReplayResult* next_replay =
&subpass_state_.back().rendered_clip_entities[next_replay_index_];
if (next_replay->entity.GetClipDepth() < entity.GetClipDepth()) {
// The next replay clip doesn't affect the current entity, so don't replay
// it yet.
return nullptr;
}
next_replay_index_++;
return next_replay;
}
} // namespace impeller

View File

@@ -62,9 +62,14 @@ class EntityPassClipStack {
Contents::ClipCoverage::Type type,
std::optional<Rect> clip_coverage);
// Visible for testing.
const std::vector<ReplayResult>& GetReplayEntities() const;
void ActivateClipReplay();
/// @brief Returns the next Entity that should be replayed. If there are no
/// enities to replay, then nullptr is returned.
const ReplayResult* GetNextReplayResult(const Entity& entity);
// Visible for testing.
const std::vector<ClipCoverageLayer> GetClipCoverageLayers() const;
@@ -77,6 +82,7 @@ class EntityPassClipStack {
SubpassState& GetCurrentSubpassState();
std::vector<SubpassState> subpass_state_;
size_t next_replay_index_ = 0;
};
} // namespace impeller

View File

@@ -1,5 +1,8 @@
digest.json
impeller_GoldenTests_ConicalGradient.png
impeller_Play_AiksTest_BackdropRestoreUsesCorrectCoverageForFirstRestoredClip_Metal.png
impeller_Play_AiksTest_BackdropRestoreUsesCorrectCoverageForFirstRestoredClip_OpenGLES.png
impeller_Play_AiksTest_BackdropRestoreUsesCorrectCoverageForFirstRestoredClip_Vulkan.png
impeller_Play_AiksTest_BlendModeClear_Metal.png
impeller_Play_AiksTest_BlendModeClear_OpenGLES.png
impeller_Play_AiksTest_BlendModeClear_Vulkan.png