forked from firka/flutter
[Impeller] Collapse DrawRects into clear colors optimization (flutter/engine#43168)
design doc: https://docs.google.com/document/d/1Lqf1BRn4uCcUfv9dZlDyAgdSMeQ3FTnPgPjKF9yQ3MI/edit fixes: https://github.com/flutter/flutter/issues/129292 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
@@ -121,6 +121,7 @@
|
||||
../../../flutter/impeller/README.md
|
||||
../../../flutter/impeller/aiks/aiks_unittests.cc
|
||||
../../../flutter/impeller/aiks/canvas_unittests.cc
|
||||
../../../flutter/impeller/aiks/testing
|
||||
../../../flutter/impeller/archivist/archivist_unittests.cc
|
||||
../../../flutter/impeller/base/README.md
|
||||
../../../flutter/impeller/base/base_unittests.cc
|
||||
|
||||
@@ -50,6 +50,9 @@ impeller_component("aiks_unittests") {
|
||||
sources = [
|
||||
"aiks_unittests.cc",
|
||||
"canvas_unittests.cc",
|
||||
"testing/context_mock.h",
|
||||
"testing/context_spy.cc",
|
||||
"testing/context_spy.h",
|
||||
]
|
||||
deps = [
|
||||
":aiks",
|
||||
@@ -69,7 +72,12 @@ impeller_component("aiks_unittests_golden") {
|
||||
"IMPELLER_ENABLE_VALIDATION=1",
|
||||
]
|
||||
|
||||
sources = [ "aiks_unittests.cc" ]
|
||||
sources = [
|
||||
"aiks_unittests.cc",
|
||||
"testing/context_mock.h",
|
||||
"testing/context_spy.cc",
|
||||
"testing/context_spy.h",
|
||||
]
|
||||
deps = [
|
||||
":aiks",
|
||||
":aiks_playground",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "impeller/aiks/canvas.h"
|
||||
#include "impeller/aiks/image.h"
|
||||
#include "impeller/aiks/paint_pass_delegate.h"
|
||||
#include "impeller/aiks/testing/context_spy.h"
|
||||
#include "impeller/entity/contents/color_source_contents.h"
|
||||
#include "impeller/entity/contents/filters/inputs/filter_input.h"
|
||||
#include "impeller/entity/contents/scene_contents.h"
|
||||
@@ -1999,9 +2000,105 @@ TEST_P(AiksTest, DrawPaintAbsorbsClears) {
|
||||
{.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kSource});
|
||||
|
||||
Picture picture = canvas.EndRecordingAsPicture();
|
||||
|
||||
ASSERT_EQ(picture.pass->GetElementCount(), 0u);
|
||||
ASSERT_EQ(picture.pass->GetClearColor(), Color::CornflowerBlue());
|
||||
|
||||
std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
|
||||
std::shared_ptr<Context> real_context = GetContext();
|
||||
std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
|
||||
AiksContext renderer(mock_context);
|
||||
std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
|
||||
|
||||
ASSERT_EQ(spy->render_passes_.size(), 1llu);
|
||||
std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
|
||||
ASSERT_EQ(render_pass->GetCommands().size(), 0llu);
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, DrawRectAbsorbsClears) {
|
||||
Canvas canvas;
|
||||
canvas.DrawRect({0, 0, 300, 300},
|
||||
{.color = Color::Red(), .blend_mode = BlendMode::kSource});
|
||||
canvas.DrawRect({0, 0, 300, 300}, {.color = Color::CornflowerBlue(),
|
||||
.blend_mode = BlendMode::kSource});
|
||||
|
||||
std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
|
||||
Picture picture = canvas.EndRecordingAsPicture();
|
||||
std::shared_ptr<Context> real_context = GetContext();
|
||||
std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
|
||||
AiksContext renderer(mock_context);
|
||||
std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
|
||||
|
||||
ASSERT_EQ(spy->render_passes_.size(), 1llu);
|
||||
std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
|
||||
ASSERT_EQ(render_pass->GetCommands().size(), 0llu);
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, DrawRectAbsorbsClearsNegativeRRect) {
|
||||
Canvas canvas;
|
||||
canvas.DrawRRect({0, 0, 300, 300}, 5.0,
|
||||
{.color = Color::Red(), .blend_mode = BlendMode::kSource});
|
||||
canvas.DrawRRect(
|
||||
{0, 0, 300, 300}, 5.0,
|
||||
{.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kSource});
|
||||
|
||||
std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
|
||||
Picture picture = canvas.EndRecordingAsPicture();
|
||||
std::shared_ptr<Context> real_context = GetContext();
|
||||
std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
|
||||
AiksContext renderer(mock_context);
|
||||
std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
|
||||
|
||||
ASSERT_EQ(spy->render_passes_.size(), 1llu);
|
||||
std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
|
||||
ASSERT_EQ(render_pass->GetCommands().size(), 2llu);
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, DrawRectAbsorbsClearsNegativeRotation) {
|
||||
Canvas canvas;
|
||||
canvas.Translate(Vector3(150.0, 150.0, 0.0));
|
||||
canvas.Rotate(Degrees(45.0));
|
||||
canvas.Translate(Vector3(-150.0, -150.0, 0.0));
|
||||
canvas.DrawRect({0, 0, 300, 300},
|
||||
{.color = Color::Red(), .blend_mode = BlendMode::kSource});
|
||||
|
||||
std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
|
||||
Picture picture = canvas.EndRecordingAsPicture();
|
||||
std::shared_ptr<Context> real_context = GetContext();
|
||||
std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
|
||||
AiksContext renderer(mock_context);
|
||||
std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
|
||||
|
||||
ASSERT_EQ(spy->render_passes_.size(), 1llu);
|
||||
std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
|
||||
ASSERT_EQ(render_pass->GetCommands().size(), 1llu);
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, DrawRectAbsorbsClearsNegative) {
|
||||
Canvas canvas;
|
||||
canvas.DrawRect({0, 0, 300, 300},
|
||||
{.color = Color::Red(), .blend_mode = BlendMode::kSource});
|
||||
canvas.DrawRect({0, 0, 300, 300}, {.color = Color::CornflowerBlue(),
|
||||
.blend_mode = BlendMode::kSource});
|
||||
|
||||
std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
|
||||
Picture picture = canvas.EndRecordingAsPicture();
|
||||
std::shared_ptr<Context> real_context = GetContext();
|
||||
std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
|
||||
AiksContext renderer(mock_context);
|
||||
std::shared_ptr<Image> image = picture.ToImage(renderer, {301, 301});
|
||||
|
||||
ASSERT_EQ(spy->render_passes_.size(), 1llu);
|
||||
std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
|
||||
ASSERT_EQ(render_pass->GetCommands().size(), 2llu);
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, CollapsedDrawPaintInSubpass) {
|
||||
Canvas canvas;
|
||||
canvas.DrawPaint(
|
||||
{.color = Color::Yellow(), .blend_mode = BlendMode::kSource});
|
||||
canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
|
||||
canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource});
|
||||
|
||||
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) {
|
||||
|
||||
@@ -172,16 +172,6 @@ void Canvas::DrawPath(const Path& path, const Paint& paint) {
|
||||
}
|
||||
|
||||
void Canvas::DrawPaint(const Paint& paint) {
|
||||
if (xformation_stack_.size() == 1 && // If we're recording the root pass,
|
||||
GetCurrentPass().GetElementCount() == 0 && // and this is the first item,
|
||||
(paint.blend_mode == BlendMode::kSourceOver ||
|
||||
paint.blend_mode == BlendMode::kSource) &&
|
||||
paint.color.alpha >= 1.0f) {
|
||||
// Then we can absorb this drawPaint as the clear color of the pass.
|
||||
GetCurrentPass().SetClearColor(paint.color);
|
||||
return;
|
||||
}
|
||||
|
||||
Entity entity;
|
||||
entity.SetTransformation(GetCurrentTransformation());
|
||||
entity.SetStencilDepth(GetStencilDepth());
|
||||
|
||||
86
engine/src/flutter/impeller/aiks/testing/context_mock.h
Normal file
86
engine/src/flutter/impeller/aiks/testing/context_mock.h
Normal file
@@ -0,0 +1,86 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "impeller/renderer/command_buffer.h"
|
||||
#include "impeller/renderer/context.h"
|
||||
#include "impeller/renderer/render_target.h"
|
||||
|
||||
namespace impeller {
|
||||
namespace testing {
|
||||
|
||||
class CommandBufferMock : public CommandBuffer {
|
||||
public:
|
||||
CommandBufferMock(std::weak_ptr<const Context> context)
|
||||
: CommandBuffer(context) {}
|
||||
|
||||
MOCK_CONST_METHOD0(IsValid, bool());
|
||||
|
||||
MOCK_CONST_METHOD1(SetLabel, void(const std::string& label));
|
||||
|
||||
MOCK_METHOD1(SubmitCommandsAsync,
|
||||
bool(std::shared_ptr<RenderPass> render_pass));
|
||||
|
||||
MOCK_METHOD1(OnCreateRenderPass,
|
||||
std::shared_ptr<RenderPass>(RenderTarget render_target));
|
||||
|
||||
static std::shared_ptr<RenderPass> ForwardOnCreateRenderPass(
|
||||
CommandBuffer* command_buffer,
|
||||
RenderTarget render_target) {
|
||||
return command_buffer->OnCreateRenderPass(render_target);
|
||||
}
|
||||
|
||||
MOCK_CONST_METHOD0(OnCreateBlitPass, std::shared_ptr<BlitPass>());
|
||||
static std::shared_ptr<BlitPass> ForwardOnCreateBlitPass(
|
||||
CommandBuffer* command_buffer) {
|
||||
return command_buffer->OnCreateBlitPass();
|
||||
}
|
||||
|
||||
MOCK_METHOD1(OnSubmitCommands, bool(CompletionCallback callback));
|
||||
static bool ForwardOnSubmitCommands(CommandBuffer* command_buffer,
|
||||
CompletionCallback callback) {
|
||||
return command_buffer->OnSubmitCommands(callback);
|
||||
}
|
||||
|
||||
MOCK_METHOD0(OnWaitUntilScheduled, void());
|
||||
static void ForwardOnWaitUntilScheduled(CommandBuffer* command_buffer) {
|
||||
return command_buffer->OnWaitUntilScheduled();
|
||||
}
|
||||
|
||||
MOCK_CONST_METHOD0(OnCreateComputePass, std::shared_ptr<ComputePass>());
|
||||
static std::shared_ptr<ComputePass> ForwardOnCreateComputePass(
|
||||
CommandBuffer* command_buffer) {
|
||||
return command_buffer->OnCreateComputePass();
|
||||
}
|
||||
};
|
||||
|
||||
class ContextMock : public Context {
|
||||
public:
|
||||
MOCK_CONST_METHOD0(DescribeGpuModel, std::string());
|
||||
|
||||
MOCK_CONST_METHOD0(IsValid, bool());
|
||||
|
||||
MOCK_CONST_METHOD0(GetCapabilities,
|
||||
const std::shared_ptr<const Capabilities>&());
|
||||
|
||||
MOCK_METHOD1(UpdateOffscreenLayerPixelFormat, bool(PixelFormat format));
|
||||
|
||||
MOCK_CONST_METHOD0(GetResourceAllocator, std::shared_ptr<Allocator>());
|
||||
|
||||
MOCK_CONST_METHOD0(GetShaderLibrary, std::shared_ptr<ShaderLibrary>());
|
||||
|
||||
MOCK_CONST_METHOD0(GetSamplerLibrary, std::shared_ptr<SamplerLibrary>());
|
||||
|
||||
MOCK_CONST_METHOD0(GetPipelineLibrary, std::shared_ptr<PipelineLibrary>());
|
||||
|
||||
MOCK_CONST_METHOD0(CreateCommandBuffer, std::shared_ptr<CommandBuffer>());
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
||||
111
engine/src/flutter/impeller/aiks/testing/context_spy.cc
Normal file
111
engine/src/flutter/impeller/aiks/testing/context_spy.cc
Normal file
@@ -0,0 +1,111 @@
|
||||
// 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/aiks/testing/context_spy.h"
|
||||
|
||||
namespace impeller {
|
||||
namespace testing {
|
||||
|
||||
std::shared_ptr<ContextSpy> ContextSpy::Make() {
|
||||
return std::shared_ptr<ContextSpy>(new ContextSpy());
|
||||
}
|
||||
|
||||
std::shared_ptr<ContextMock> ContextSpy::MakeContext(
|
||||
const std::shared_ptr<Context>& real_context) {
|
||||
std::shared_ptr<ContextMock> mock_context =
|
||||
std::make_shared<::testing::NiceMock<ContextMock>>();
|
||||
std::shared_ptr<ContextSpy> shared_this = shared_from_this();
|
||||
|
||||
ON_CALL(*mock_context, IsValid).WillByDefault([real_context]() {
|
||||
return real_context->IsValid();
|
||||
});
|
||||
|
||||
ON_CALL(*mock_context, GetCapabilities)
|
||||
.WillByDefault(
|
||||
[real_context]() -> const std::shared_ptr<const Capabilities>& {
|
||||
return real_context->GetCapabilities();
|
||||
});
|
||||
|
||||
ON_CALL(*mock_context, UpdateOffscreenLayerPixelFormat)
|
||||
.WillByDefault([real_context](PixelFormat format) {
|
||||
return real_context->UpdateOffscreenLayerPixelFormat(format);
|
||||
});
|
||||
|
||||
ON_CALL(*mock_context, GetResourceAllocator).WillByDefault([real_context]() {
|
||||
return real_context->GetResourceAllocator();
|
||||
});
|
||||
|
||||
ON_CALL(*mock_context, GetShaderLibrary).WillByDefault([real_context]() {
|
||||
return real_context->GetShaderLibrary();
|
||||
});
|
||||
|
||||
ON_CALL(*mock_context, GetSamplerLibrary).WillByDefault([real_context]() {
|
||||
return real_context->GetSamplerLibrary();
|
||||
});
|
||||
|
||||
ON_CALL(*mock_context, GetPipelineLibrary).WillByDefault([real_context]() {
|
||||
return real_context->GetPipelineLibrary();
|
||||
});
|
||||
|
||||
ON_CALL(*mock_context, CreateCommandBuffer)
|
||||
.WillByDefault([real_context, shared_this]() {
|
||||
auto real_buffer = real_context->CreateCommandBuffer();
|
||||
auto spy = std::make_shared<::testing::NiceMock<CommandBufferMock>>(
|
||||
real_context);
|
||||
|
||||
ON_CALL(*spy, IsValid).WillByDefault([real_buffer]() {
|
||||
return real_buffer->IsValid();
|
||||
});
|
||||
|
||||
ON_CALL(*spy, SetLabel)
|
||||
.WillByDefault([real_buffer](const std::string& label) {
|
||||
return real_buffer->SetLabel(label);
|
||||
});
|
||||
|
||||
ON_CALL(*spy, SubmitCommandsAsync)
|
||||
.WillByDefault([real_buffer](
|
||||
std::shared_ptr<RenderPass> render_pass) {
|
||||
return real_buffer->SubmitCommandsAsync(std::move(render_pass));
|
||||
});
|
||||
|
||||
ON_CALL(*spy, OnCreateRenderPass)
|
||||
.WillByDefault(
|
||||
[real_buffer, shared_this](const RenderTarget& render_target) {
|
||||
std::shared_ptr<RenderPass> result =
|
||||
CommandBufferMock::ForwardOnCreateRenderPass(
|
||||
real_buffer.get(), render_target);
|
||||
shared_this->render_passes_.push_back(result);
|
||||
return result;
|
||||
});
|
||||
|
||||
ON_CALL(*spy, OnCreateBlitPass).WillByDefault([real_buffer]() {
|
||||
return CommandBufferMock::ForwardOnCreateBlitPass(real_buffer.get());
|
||||
});
|
||||
|
||||
ON_CALL(*spy, OnSubmitCommands)
|
||||
.WillByDefault(
|
||||
[real_buffer](CommandBuffer::CompletionCallback callback) {
|
||||
return CommandBufferMock::ForwardOnSubmitCommands(
|
||||
real_buffer.get(), std::move(callback));
|
||||
});
|
||||
|
||||
ON_CALL(*spy, OnWaitUntilScheduled).WillByDefault([real_buffer]() {
|
||||
return CommandBufferMock::ForwardOnWaitUntilScheduled(
|
||||
real_buffer.get());
|
||||
});
|
||||
|
||||
ON_CALL(*spy, OnCreateComputePass).WillByDefault([real_buffer]() {
|
||||
return CommandBufferMock::ForwardOnCreateComputePass(
|
||||
real_buffer.get());
|
||||
});
|
||||
|
||||
return spy;
|
||||
});
|
||||
|
||||
return mock_context;
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
|
||||
} // namespace impeller
|
||||
30
engine/src/flutter/impeller/aiks/testing/context_spy.h
Normal file
30
engine/src/flutter/impeller/aiks/testing/context_spy.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "impeller/aiks/testing/context_mock.h"
|
||||
|
||||
namespace impeller {
|
||||
namespace testing {
|
||||
|
||||
/// Forwards calls to a real Context but can store information about how
|
||||
/// the Context was used.
|
||||
class ContextSpy : public std::enable_shared_from_this<ContextSpy> {
|
||||
public:
|
||||
static std::shared_ptr<ContextSpy> Make();
|
||||
|
||||
std::shared_ptr<ContextMock> MakeContext(
|
||||
const std::shared_ptr<Context>& real_context);
|
||||
|
||||
std::vector<std::shared_ptr<RenderPass>> render_passes_;
|
||||
|
||||
private:
|
||||
ContextSpy() = default;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
|
||||
} // namespace impeller
|
||||
@@ -117,6 +117,11 @@ class Contents {
|
||||
/// Use of this method is invalid if CanAcceptOpacity returns false.
|
||||
virtual void SetInheritedOpacity(Scalar opacity);
|
||||
|
||||
virtual std::optional<Color> AsBackgroundColor(const Entity& entity,
|
||||
ISize target_size) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<Rect> coverage_hint_;
|
||||
std::optional<Size> color_source_size_;
|
||||
|
||||
@@ -101,4 +101,19 @@ std::unique_ptr<SolidColorContents> SolidColorContents::Make(const Path& path,
|
||||
return contents;
|
||||
}
|
||||
|
||||
std::optional<Color> SolidColorContents::AsBackgroundColor(
|
||||
const Entity& entity,
|
||||
ISize target_size) const {
|
||||
if (!(GetColor().IsOpaque() &&
|
||||
(entity.GetBlendMode() == BlendMode::kSource ||
|
||||
entity.GetBlendMode() == BlendMode::kSourceOver))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Rect target_rect = Rect::MakeSize(target_size);
|
||||
return GetGeometry()->CoversArea(entity.GetTransformation(), target_rect)
|
||||
? GetColor()
|
||||
: std::optional<Color>();
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@@ -49,6 +49,9 @@ class SolidColorContents final : public ColorSourceContents {
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const override;
|
||||
|
||||
std::optional<Color> AsBackgroundColor(const Entity& entity,
|
||||
ISize target_size) const override;
|
||||
|
||||
private:
|
||||
Color color_;
|
||||
|
||||
|
||||
@@ -36,6 +36,20 @@
|
||||
|
||||
namespace impeller {
|
||||
|
||||
namespace {
|
||||
std::optional<Color> AsBackgroundColor(const EntityPass::Element& element,
|
||||
ISize target_size) {
|
||||
if (const Entity* entity = std::get_if<Entity>(&element)) {
|
||||
std::optional<Color> entity_color =
|
||||
entity->GetContents()->AsBackgroundColor(*entity, target_size);
|
||||
if (entity_color.has_value()) {
|
||||
return entity_color.value();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
EntityPass::EntityPass() = default;
|
||||
|
||||
EntityPass::~EntityPass() = default;
|
||||
@@ -235,9 +249,9 @@ bool EntityPass::Render(ContentContext& renderer,
|
||||
// and then blit the results onto the onscreen texture. If using this branch,
|
||||
// there's no need to set up a stencil attachment on the root render target.
|
||||
if (!supports_onscreen_backdrop_reads && reads_from_onscreen_backdrop) {
|
||||
auto offscreen_target =
|
||||
CreateRenderTarget(renderer, root_render_target.GetRenderTargetSize(),
|
||||
true, clear_color_.Premultiply());
|
||||
auto offscreen_target = CreateRenderTarget(
|
||||
renderer, root_render_target.GetRenderTargetSize(), true,
|
||||
GetClearColor(render_target.GetRenderTargetSize()).Premultiply());
|
||||
|
||||
if (!OnRender(renderer, // renderer
|
||||
offscreen_target.GetRenderTarget()
|
||||
@@ -342,7 +356,8 @@ bool EntityPass::Render(ContentContext& renderer,
|
||||
}
|
||||
|
||||
// Set up the clear color of the root pass.
|
||||
color0.clear_color = clear_color_.Premultiply();
|
||||
color0.clear_color =
|
||||
GetClearColor(render_target.GetRenderTargetSize()).Premultiply();
|
||||
root_render_target.SetColorAttachment(color0, 0);
|
||||
|
||||
EntityPassTarget pass_target(
|
||||
@@ -478,7 +493,7 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
|
||||
renderer, // renderer
|
||||
subpass_size, // size
|
||||
subpass->GetTotalPassReads(renderer) > 0, // readable
|
||||
Color::BlackTransparent()); // clear_color
|
||||
subpass->GetClearColor(subpass_size)); // clear_color
|
||||
|
||||
if (!subpass_target.IsValid()) {
|
||||
VALIDATION_LOG << "Subpass render target is invalid.";
|
||||
@@ -559,7 +574,7 @@ bool EntityPass::OnRender(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(clear_color_ == Color::BlackTransparent())) {
|
||||
if (!(GetClearColor(root_pass_size) == Color::BlackTransparent())) {
|
||||
// Force the pass context to create at least one new pass if the clear color
|
||||
// is present. The `EndPass` first ensures that the clear color will get
|
||||
// applied even if this EntityPass is getting collapsed into the parent
|
||||
@@ -704,7 +719,19 @@ bool EntityPass::OnRender(
|
||||
render_element(backdrop_entity);
|
||||
}
|
||||
|
||||
bool is_collapsing_clear_colors = true;
|
||||
for (const auto& element : elements_) {
|
||||
// Skip elements that are incorporated into the clear color.
|
||||
if (is_collapsing_clear_colors) {
|
||||
std::optional<Color> entity_color =
|
||||
AsBackgroundColor(element, root_pass_size);
|
||||
if (entity_color.has_value()) {
|
||||
continue;
|
||||
} else {
|
||||
is_collapsing_clear_colors = false;
|
||||
}
|
||||
}
|
||||
|
||||
EntityResult result =
|
||||
GetEntityForElement(element, // element
|
||||
renderer, // renderer
|
||||
@@ -892,12 +919,17 @@ void EntityPass::SetBlendMode(BlendMode blend_mode) {
|
||||
flood_clip_ = Entity::IsBlendModeDestructive(blend_mode);
|
||||
}
|
||||
|
||||
void EntityPass::SetClearColor(Color clear_color) {
|
||||
clear_color_ = clear_color;
|
||||
}
|
||||
|
||||
Color EntityPass::GetClearColor() const {
|
||||
return clear_color_;
|
||||
Color EntityPass::GetClearColor(ISize target_size) const {
|
||||
Color result = Color::BlackTransparent();
|
||||
for (const Element& element : elements_) {
|
||||
std::optional<Color> entity_color = AsBackgroundColor(element, target_size);
|
||||
if (entity_color.has_value()) {
|
||||
result = entity_color.value();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityPass::SetBackdropFilter(std::optional<BackdropFilterProc> proc) {
|
||||
|
||||
@@ -86,9 +86,7 @@ class EntityPass {
|
||||
|
||||
void SetBlendMode(BlendMode blend_mode);
|
||||
|
||||
void SetClearColor(Color clear_color);
|
||||
|
||||
Color GetClearColor() const;
|
||||
Color GetClearColor(ISize size = ISize::Infinite()) const;
|
||||
|
||||
void SetBackdropFilter(std::optional<BackdropFilterProc> proc);
|
||||
|
||||
@@ -210,7 +208,6 @@ class EntityPass {
|
||||
size_t stencil_depth_ = 0u;
|
||||
BlendMode blend_mode_ = BlendMode::kSourceOver;
|
||||
bool flood_clip_ = false;
|
||||
Color clear_color_ = Color::BlackTransparent();
|
||||
bool enable_offscreen_debug_checkerboard_ = false;
|
||||
|
||||
/// These values are incremented whenever something is added to the pass that
|
||||
|
||||
@@ -54,4 +54,9 @@ std::optional<Rect> CoverGeometry::GetCoverage(const Matrix& transform) const {
|
||||
return Rect::MakeMaximum();
|
||||
}
|
||||
|
||||
bool CoverGeometry::CoversArea(const Matrix& transform,
|
||||
const Rect& rect) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@@ -16,6 +16,9 @@ class CoverGeometry : public Geometry {
|
||||
|
||||
~CoverGeometry();
|
||||
|
||||
// |Geometry|
|
||||
bool CoversArea(const Matrix& transform, const Rect& rect) const override;
|
||||
|
||||
private:
|
||||
// |Geometry|
|
||||
GeometryResult GetPositionBuffer(const ContentContext& renderer,
|
||||
|
||||
@@ -87,6 +87,12 @@ class Geometry {
|
||||
virtual GeometryVertexType GetVertexType() const = 0;
|
||||
|
||||
virtual std::optional<Rect> GetCoverage(const Matrix& transform) const = 0;
|
||||
|
||||
/// @return `true` if this geometry will completely cover all fragments in
|
||||
/// `rect` when the `transform` is applied to it.
|
||||
virtual bool CoversArea(const Matrix& transform, const Rect& rect) const {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@@ -47,4 +47,12 @@ std::optional<Rect> RectGeometry::GetCoverage(const Matrix& transform) const {
|
||||
return rect_.TransformBounds(transform);
|
||||
}
|
||||
|
||||
bool RectGeometry::CoversArea(const Matrix& transform, const Rect& rect) const {
|
||||
if (!transform.IsTranslationScaleOnly()) {
|
||||
return false;
|
||||
}
|
||||
Rect coverage = rect_.TransformBounds(transform);
|
||||
return coverage.Contains(rect);
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@@ -14,6 +14,9 @@ class RectGeometry : public Geometry {
|
||||
|
||||
~RectGeometry();
|
||||
|
||||
// |Geometry|
|
||||
bool CoversArea(const Matrix& transform, const Rect& rect) const override;
|
||||
|
||||
private:
|
||||
// |Geometry|
|
||||
GeometryResult GetPositionBuffer(const ContentContext& renderer,
|
||||
|
||||
@@ -18,6 +18,10 @@ class Context;
|
||||
class RenderPass;
|
||||
class RenderTarget;
|
||||
|
||||
namespace testing {
|
||||
class CommandBufferMock;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/// @brief A collection of encoded commands to be submitted to the GPU for
|
||||
/// execution. A command buffer is obtained from a graphics
|
||||
@@ -38,6 +42,8 @@ class RenderTarget;
|
||||
/// different from the encoding order.
|
||||
///
|
||||
class CommandBuffer {
|
||||
friend class testing::CommandBufferMock;
|
||||
|
||||
public:
|
||||
enum class Status {
|
||||
kPending,
|
||||
|
||||
@@ -57,6 +57,13 @@ class RenderPass {
|
||||
///
|
||||
bool EncodeCommands() const;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Accessor for the current Commands.
|
||||
///
|
||||
/// @details Visible for testing.
|
||||
///
|
||||
const std::vector<Command>& GetCommands() const { return commands_; }
|
||||
|
||||
protected:
|
||||
const std::weak_ptr<const Context> context_;
|
||||
const RenderTarget render_target_;
|
||||
|
||||
Reference in New Issue
Block a user