diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 6277a94c27890..ed5f5a0df817a 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -164,6 +164,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 diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2759069858445..bbd92f1c6e000 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -42128,6 +42128,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 @@ -45009,6 +45011,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 diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index d91d56cdfe5fe..c75ca724dc17d 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -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", @@ -248,6 +250,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", diff --git a/impeller/entity/contents/color_source_contents.h b/impeller/entity/contents/color_source_contents.h index 5f72b13f05e82..008674a99e7f6 100644 --- a/impeller/entity/contents/color_source_contents.h +++ b/impeller/entity/contents/color_source_contents.h @@ -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; diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index 3fa906e89296c..cf39b60e337bc 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -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(), diff --git a/impeller/entity/contents/texture_contents.cc b/impeller/entity/contents/texture_contents.cc index a2af33e96c486..7cc6befb517c8 100644 --- a/impeller/entity/contents/texture_contents.cc +++ b/impeller/entity/contents/texture_contents.cc @@ -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)); diff --git a/impeller/entity/draw_order_resolver.cc b/impeller/entity/draw_order_resolver.cc new file mode 100644 index 0000000000000..5afdf438d0d2f --- /dev/null +++ b/impeller/entity/draw_order_resolver.cc @@ -0,0 +1,124 @@ +// 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 is 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 diff --git a/impeller/entity/draw_order_resolver.h b/impeller/entity/draw_order_resolver.h new file mode 100644 index 0000000000000..5c8d7a91d4755 --- /dev/null +++ b/impeller/entity/draw_order_resolver.h @@ -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 +#include + +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; + + 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 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 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_ diff --git a/impeller/entity/draw_order_resolver_unittests.cc b/impeller/entity/draw_order_resolver_unittests.cc new file mode 100644 index 0000000000000..c19e781aaec63 --- /dev/null +++ b/impeller/entity/draw_order_resolver_unittests.cc @@ -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 diff --git a/impeller/entity/entity.cc b/impeller/entity/entity.cc index cb336892d3a61..2e5229d707b62 100644 --- a/impeller/entity/entity.cc +++ b/impeller/entity/entity.cc @@ -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_; } diff --git a/impeller/entity/entity.h b/impeller/entity/entity.h index 95f8b29b2400f..daaac1420a420 100644 --- a/impeller/entity/entity.h +++ b/impeller/entity/entity.h @@ -73,6 +73,8 @@ class Entity { Entity(Entity&&); + Entity& operator=(Entity&&); + /// @brief Get the global transform matrix for this Entity. const Matrix& GetTransform() const; diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index bd1e070836cb0..cd29877cbac14 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -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, BlendMode> ElementAsBackgroundColor( } } // namespace +bool EntityPass::IsSubpass(const Element& element) { + return std::holds_alternative>(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 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 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(); + 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(clip_coverage_stack.CurrentClipCoverage(), *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; + 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(&element)) { + std::optional 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; + } } - is_collapsing_clear_colors = false; + // We came across an element that doesn't replace the background color of + // this layer, so stop counting. + break; } + } + using ElementCallback = std::function; + using ElementIterator = std::function; + + 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 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(); - 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; - } + if (deferred_entity.has_value() && + result.entity.GetBlendMode() != BlendMode::kSource) { + if (!render_element(*deferred_entity)) { + return false; + } + deferred_entity.reset(); + } - // 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."; + if (IsSubpass(element)) { + if (deferred_entity.has_value()) { + if (!render_element(*deferred_entity)) { 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); } + deferred_entity = std::move(result.entity); + return true; } - //-------------------------------------------------------------------------- - /// 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; - } + return render_element(result.entity); + }); + if (!result) { + return false; } + if (deferred_entity.has_value() && !render_element(*deferred_entity)) { + return false; + } return true; } diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index 87731602d6925..38e5d28d44231 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -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>; + static bool IsSubpass(const Element& element); + using BackdropFilterProc = std::function( 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 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 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; diff --git a/impeller/entity/entity_pass_clip_stack.cc b/impeller/entity/entity_pass_clip_stack.cc index eab0565b3d382..1cef7a108587a 100644 --- a/impeller/entity/entity_pass_clip_stack.cc +++ b/impeller/entity/entity_pass_clip_stack.cc @@ -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 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 @@ -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 diff --git a/impeller/entity/entity_pass_clip_stack.h b/impeller/entity/entity_pass_clip_stack.h index de1f1c9eee070..df230fddd4404 100644 --- a/impeller/entity/entity_pass_clip_stack.h +++ b/impeller/entity/entity_pass_clip_stack.h @@ -62,9 +62,14 @@ class EntityPassClipStack { Contents::ClipCoverage::Type type, std::optional clip_coverage); - // Visible for testing. const std::vector& 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 GetClipCoverageLayers() const; @@ -77,6 +82,7 @@ class EntityPassClipStack { SubpassState& GetCurrentSubpassState(); std::vector subpass_state_; + size_t next_replay_index_ = 0; }; } // namespace impeller