Skip to content

Commit

Permalink
Fix subpass ordering (flutter#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdero authored and dnfield committed Apr 27, 2022
1 parent 3b1dfc4 commit 4d50315
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 126 deletions.
26 changes: 25 additions & 1 deletion impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,31 @@ TEST_P(AiksTest, DrawRectStrokesRenderCorrectly) {

canvas.Translate({100, 100});
canvas.DrawPath(PathBuilder{}.AddRect(Rect::MakeSize({100, 100})).TakePath(),
paint);
{paint});

ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, SaveLayerDrawsBehindSubsequentEntities) {
// Compare with https://fiddle.skia.org/c/9e03de8567ffb49e7e83f53b64bcf636
Canvas canvas;
Paint paint;

paint.color = Color::Black();
Rect rect(25, 25, 25, 25);
canvas.DrawRect(rect, paint);

canvas.Translate({10, 10});
canvas.SaveLayer({});

paint.color = Color::Green();
canvas.DrawRect(rect, paint);

canvas.Restore();

canvas.Translate({10, 10});
paint.color = Color::Red();
canvas.DrawRect(rect, paint);

ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
Expand Down
273 changes: 158 additions & 115 deletions impeller/entity/entity_pass.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// found in the LICENSE file.

#include "impeller/entity/entity_pass.h"
#include <variant>

#include "flutter/fml/logging.h"
#include "flutter/fml/trace_event.h"
#include "impeller/base/validation.h"
#include "impeller/entity/contents/content_context.h"
Expand All @@ -26,22 +28,20 @@ void EntityPass::SetDelegate(std::unique_ptr<EntityPassDelegate> delegate) {
}

void EntityPass::AddEntity(Entity entity) {
entities_.emplace_back(std::move(entity));
elements_.emplace_back(std::move(entity));
}

const std::vector<Entity>& EntityPass::GetEntities() const {
return entities_;
}

void EntityPass::SetEntities(Entities entities) {
entities_ = std::move(entities);
void EntityPass::SetElements(std::vector<Element> elements) {
elements_ = std::move(elements);
}

size_t EntityPass::GetSubpassesDepth() const {
size_t max_subpass_depth = 0u;
for (const auto& subpass : subpasses_) {
max_subpass_depth =
std::max(max_subpass_depth, subpass->GetSubpassesDepth());
for (const auto& element : elements_) {
if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
max_subpass_depth =
std::max(max_subpass_depth, subpass->get()->GetSubpassesDepth());
}
}
return max_subpass_depth + 1u;
}
Expand All @@ -50,10 +50,20 @@ const std::shared_ptr<LazyGlyphAtlas>& EntityPass::GetLazyGlyphAtlas() const {
return lazy_glyph_atlas_;
}

std::optional<Rect> EntityPass::GetEntitiesCoverage() const {
std::optional<Rect> EntityPass::GetElementsCoverage() const {
std::optional<Rect> result;
for (const auto& entity : entities_) {
auto coverage = entity.GetCoverage();
for (const auto& element : elements_) {
std::optional<Rect> coverage;

if (auto entity = std::get_if<Entity>(&element)) {
coverage = entity->GetCoverage();
} else if (auto subpass =
std::get_if<std::unique_ptr<EntityPass>>(&element)) {
coverage = subpass->get()->GetElementsCoverage();
} else {
FML_UNREACHABLE();
}

if (!result.has_value() && coverage.has_value()) {
result = coverage;
continue;
Expand All @@ -68,7 +78,7 @@ std::optional<Rect> EntityPass::GetEntitiesCoverage() const {

std::optional<Rect> EntityPass::GetSubpassCoverage(
const EntityPass& subpass) const {
auto entities_coverage = subpass.GetEntitiesCoverage();
auto entities_coverage = subpass.GetElementsCoverage();
// The entities don't cover anything. There is nothing to do.
if (!entities_coverage.has_value()) {
return std::nullopt;
Expand All @@ -94,132 +104,149 @@ EntityPass* EntityPass::GetSuperpass() const {
return superpass_;
}

const EntityPass::Subpasses& EntityPass::GetSubpasses() const {
return subpasses_;
}

EntityPass* EntityPass::AddSubpass(std::unique_ptr<EntityPass> pass) {
if (!pass) {
return nullptr;
}
FML_DCHECK(pass->superpass_ == nullptr);
pass->superpass_ = this;
return subpasses_.emplace_back(std::move(pass)).get();
auto subpass_pointer = pass.get();
elements_.emplace_back(std::move(pass));
return subpass_pointer;
}

bool EntityPass::Render(ContentContext& renderer,
RenderPass& parent_pass,
Point position) const {
TRACE_EVENT0("impeller", "EntityPass::Render");

for (Entity entity : entities_) {
if (!position.IsZero()) {
// If the pass image is going to be rendered with a non-zero position,
// apply the negative translation to entity copies before rendering them
// so that they'll end up rendering to the correct on-screen position.
entity.SetTransformation(Matrix::MakeTranslation(Vector3(-position)) *
entity.GetTransformation());
}
if (!entity.Render(renderer, parent_pass)) {
return false;
}
}

for (const auto& subpass : subpasses_) {
if (delegate_->CanElide()) {
continue;
}

if (delegate_->CanCollapseIntoParentPass()) {
// Directly render into the parent pass and move on.
if (!subpass->Render(renderer, parent_pass, position)) {
for (const auto& element : elements_) {
// =========================================================================
// Entity rendering ========================================================
// =========================================================================
if (const auto& entity = std::get_if<Entity>(&element)) {
Entity e = *entity;
if (!position.IsZero()) {
// If the pass image is going to be rendered with a non-zero position,
// apply the negative translation to entity copies before rendering them
// so that they'll end up rendering to the correct on-screen position.
e.SetTransformation(Matrix::MakeTranslation(Vector3(-position)) *
e.GetTransformation());
}
if (!e.Render(renderer, parent_pass)) {
return false;
}
continue;
}

const auto subpass_coverage = GetSubpassCoverage(*subpass);
// =========================================================================
// Subpass rendering =======================================================
// =========================================================================
if (const auto& subpass_ptr =
std::get_if<std::unique_ptr<EntityPass>>(&element)) {
auto subpass = subpass_ptr->get();

if (!subpass_coverage.has_value()) {
continue;
}
if (delegate_->CanElide()) {
continue;
}

if (subpass_coverage->size.IsEmpty()) {
// It is not an error to have an empty subpass. But subpasses that can't
// create their intermediates must trip errors.
continue;
}
if (delegate_->CanCollapseIntoParentPass()) {
// Directly render into the parent pass and move on.
if (!subpass->Render(renderer, parent_pass, position)) {
return false;
}
continue;
}

auto context = renderer.GetContext();
const auto subpass_coverage = GetSubpassCoverage(*subpass);

auto subpass_target = RenderTarget::CreateOffscreen(
*context, ISize::Ceil(subpass_coverage->size));
if (!subpass_coverage.has_value()) {
continue;
}

auto subpass_texture = subpass_target.GetRenderTargetTexture();
if (subpass_coverage->size.IsEmpty()) {
// It is not an error to have an empty subpass. But subpasses that can't
// create their intermediates must trip errors.
continue;
}

if (!subpass_texture) {
return false;
}
auto context = renderer.GetContext();

auto offscreen_texture_contents =
delegate_->CreateContentsForSubpassTarget(subpass_texture);

if (!offscreen_texture_contents) {
// This is an error because the subpass delegate said the pass couldn't be
// collapsed into its parent. Yet, when asked how it want's to postprocess
// the offscreen texture, it couldn't give us an answer.
//
// Theoretically, we could collapse the pass now. But that would be
// wasteful as we already have the offscreen texture and we don't want to
// discard it without ever using it. Just make the delegate do the right
// thing.
return false;
}
auto subpass_target = RenderTarget::CreateOffscreen(
*context, ISize::Ceil(subpass_coverage->size));

auto sub_command_buffer = context->CreateRenderCommandBuffer();
auto subpass_texture = subpass_target.GetRenderTargetTexture();

sub_command_buffer->SetLabel("Offscreen Command Buffer");
if (!subpass_texture) {
return false;
}

if (!sub_command_buffer) {
return false;
}
auto offscreen_texture_contents =
delegate_->CreateContentsForSubpassTarget(subpass_texture);

if (!offscreen_texture_contents) {
// This is an error because the subpass delegate said the pass couldn't
// be collapsed into its parent. Yet, when asked how it want's to
// postprocess the offscreen texture, it couldn't give us an answer.
//
// Theoretically, we could collapse the pass now. But that would be
// wasteful as we already have the offscreen texture and we don't want
// to discard it without ever using it. Just make the delegate do the
// right thing.
return false;
}

auto sub_renderpass = sub_command_buffer->CreateRenderPass(subpass_target);
auto sub_command_buffer = context->CreateRenderCommandBuffer();

if (!sub_renderpass) {
return false;
}
sub_command_buffer->SetLabel("Offscreen Command Buffer");

if (!sub_command_buffer) {
return false;
}

sub_renderpass->SetLabel("OffscreenPass");
auto sub_renderpass =
sub_command_buffer->CreateRenderPass(subpass_target);

if (!subpass->Render(renderer, *sub_renderpass, subpass_coverage->origin)) {
return false;
}
if (!sub_renderpass) {
return false;
}

if (!sub_renderpass->EncodeCommands(*context->GetTransientsAllocator())) {
return false;
}
sub_renderpass->SetLabel("OffscreenPass");

if (!sub_command_buffer->SubmitCommands()) {
return false;
}
if (!subpass->Render(renderer, *sub_renderpass,
subpass_coverage->origin)) {
return false;
}

if (!sub_renderpass->EncodeCommands(*context->GetTransientsAllocator())) {
return false;
}

if (!sub_command_buffer->SubmitCommands()) {
return false;
}

Entity entity;
entity.SetPath(PathBuilder{}
.AddRect(Rect::MakeSize(subpass_coverage->size))
.TakePath());
entity.SetContents(std::move(offscreen_texture_contents));
entity.SetStencilDepth(stencil_depth_);
entity.SetBlendMode(subpass->blend_mode_);
// Once we have filters being applied for SaveLayer, some special sauce
// may be needed here (or in PaintPassDelegate) to ensure the filter
// parameters are transformed by the `xformation_` matrix, while
// continuing to apply only the subpass offset to the offscreen texture.
entity.SetTransformation(Matrix::MakeTranslation(
Vector3(subpass_coverage->origin - position)));
if (!entity.Render(renderer, parent_pass)) {
return false;
}

Entity entity;
entity.SetPath(PathBuilder{}
.AddRect(Rect::MakeSize(subpass_coverage->size))
.TakePath());
entity.SetContents(std::move(offscreen_texture_contents));
entity.SetStencilDepth(stencil_depth_);
entity.SetBlendMode(subpass->blend_mode_);
// Once we have filters being applied for SaveLayer, some special sauce
// may be needed here (or in PaintPassDelegate) to ensure the filter
// parameters are transformed by the `xformation_` matrix, while continuing
// to apply only the subpass offset to the offscreen texture.
entity.SetTransformation(
Matrix::MakeTranslation(Vector3(subpass_coverage->origin - position)));
if (!entity.Render(renderer, parent_pass)) {
return false;
continue;
}

FML_UNREACHABLE();
}

return true;
Expand All @@ -230,23 +257,39 @@ void EntityPass::IterateAllEntities(std::function<bool(Entity&)> iterator) {
return;
}

for (auto& entity : entities_) {
if (!iterator(entity)) {
return;
for (auto& element : elements_) {
if (auto entity = std::get_if<Entity>(&element)) {
if (!iterator(*entity)) {
return;
}
continue;
}
}

for (auto& subpass : subpasses_) {
subpass->IterateAllEntities(iterator);
if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
subpass->get()->IterateAllEntities(iterator);
continue;
}
FML_UNREACHABLE();
}
}

std::unique_ptr<EntityPass> EntityPass::Clone() const {
auto pass = std::make_unique<EntityPass>();
pass->SetEntities(entities_);
for (const auto& subpass : subpasses_) {
pass->AddSubpass(subpass->Clone());
std::vector<Element> new_elements;
new_elements.reserve(elements_.size());

for (const auto& element : elements_) {
if (auto entity = std::get_if<Entity>(&element)) {
new_elements.push_back(*entity);
continue;
}
if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
new_elements.push_back(subpass->get()->Clone());
continue;
}
FML_UNREACHABLE();
}

auto pass = std::make_unique<EntityPass>();
pass->SetElements(std::move(new_elements));
return pass;
}

Expand Down
Loading

0 comments on commit 4d50315

Please sign in to comment.