mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Impeller] Add backdrop filter support; refactor EntityPass (flutter/engine#33887)
This commit is contained in:
parent
34246f2872
commit
2ededef19a
@ -568,6 +568,8 @@ FILE: ../../../flutter/impeller/entity/entity_pass_delegate.h
|
||||
FILE: ../../../flutter/impeller/entity/entity_playground.cc
|
||||
FILE: ../../../flutter/impeller/entity/entity_playground.h
|
||||
FILE: ../../../flutter/impeller/entity/entity_unittests.cc
|
||||
FILE: ../../../flutter/impeller/entity/inline_pass_context.cc
|
||||
FILE: ../../../flutter/impeller/entity/inline_pass_context.h
|
||||
FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.glsl
|
||||
FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.vert
|
||||
FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend_color.frag
|
||||
|
||||
@ -265,14 +265,17 @@ size_t Canvas::GetStencilDepth() const {
|
||||
return xformation_stack_.back().stencil_depth;
|
||||
}
|
||||
|
||||
void Canvas::SaveLayer(Paint paint, std::optional<Rect> bounds) {
|
||||
void Canvas::SaveLayer(Paint paint,
|
||||
std::optional<Rect> bounds,
|
||||
std::optional<Paint::ImageFilterProc> backdrop_filter) {
|
||||
Save(true, paint.blend_mode);
|
||||
|
||||
auto& new_layer_pass = GetCurrentPass();
|
||||
new_layer_pass.SetDelegate(
|
||||
std::make_unique<PaintPassDelegate>(paint, bounds));
|
||||
new_layer_pass.SetBackdropFilter(backdrop_filter);
|
||||
|
||||
if (bounds.has_value()) {
|
||||
if (bounds.has_value() && !backdrop_filter.has_value()) {
|
||||
// Render target switches due to a save layer can be elided. In such cases
|
||||
// where passes are collapsed into their parent, the clipping effect to
|
||||
// the size of the render target that would have been allocated will be
|
||||
|
||||
@ -35,7 +35,10 @@ class Canvas {
|
||||
|
||||
void Save();
|
||||
|
||||
void SaveLayer(Paint paint, std::optional<Rect> bounds = std::nullopt);
|
||||
void SaveLayer(
|
||||
Paint paint,
|
||||
std::optional<Rect> bounds = std::nullopt,
|
||||
std::optional<Paint::ImageFilterProc> backdrop_filter = std::nullopt);
|
||||
|
||||
bool Restore();
|
||||
|
||||
|
||||
@ -324,9 +324,12 @@ void DisplayListDispatcher::setMaskFilter(const flutter::DlMaskFilter* filter) {
|
||||
}
|
||||
}
|
||||
|
||||
// |flutter::Dispatcher|
|
||||
void DisplayListDispatcher::setImageFilter(
|
||||
static std::optional<Paint::ImageFilterProc> ToImageFilterProc(
|
||||
const flutter::DlImageFilter* filter) {
|
||||
if (filter == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
switch (filter->type()) {
|
||||
case flutter::DlImageFilterType::kBlur: {
|
||||
auto blur = filter->asBlur();
|
||||
@ -338,7 +341,7 @@ void DisplayListDispatcher::setImageFilter(
|
||||
UNIMPLEMENTED;
|
||||
}
|
||||
|
||||
paint_.image_filter = [sigma_x, sigma_y](FilterInput::Ref input) {
|
||||
return [sigma_x, sigma_y](FilterInput::Ref input) {
|
||||
return FilterContents::MakeGaussianBlur(input, sigma_x, sigma_y);
|
||||
};
|
||||
|
||||
@ -350,11 +353,16 @@ void DisplayListDispatcher::setImageFilter(
|
||||
case flutter::DlImageFilterType::kComposeFilter:
|
||||
case flutter::DlImageFilterType::kColorFilter:
|
||||
case flutter::DlImageFilterType::kUnknown:
|
||||
UNIMPLEMENTED;
|
||||
break;
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// |flutter::Dispatcher|
|
||||
void DisplayListDispatcher::setImageFilter(
|
||||
const flutter::DlImageFilter* filter) {
|
||||
paint_.image_filter = ToImageFilterProc(filter);
|
||||
}
|
||||
|
||||
// |flutter::Dispatcher|
|
||||
void DisplayListDispatcher::save() {
|
||||
canvas_.Save();
|
||||
@ -371,11 +379,8 @@ static std::optional<Rect> ToRect(const SkRect* rect) {
|
||||
void DisplayListDispatcher::saveLayer(const SkRect* bounds,
|
||||
const flutter::SaveLayerOptions options,
|
||||
const flutter::DlImageFilter* backdrop) {
|
||||
if (backdrop) {
|
||||
UNIMPLEMENTED;
|
||||
}
|
||||
canvas_.SaveLayer(options.renders_with_attributes() ? paint_ : Paint{},
|
||||
ToRect(bounds));
|
||||
auto paint = options.renders_with_attributes() ? paint_ : Paint{};
|
||||
canvas_.SaveLayer(paint, ToRect(bounds), ToImageFilterProc(backdrop));
|
||||
}
|
||||
|
||||
// |flutter::Dispatcher|
|
||||
|
||||
@ -3,8 +3,10 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "display_list/display_list_blend_mode.h"
|
||||
#include "display_list/display_list_color.h"
|
||||
#include "display_list/display_list_color_filter.h"
|
||||
#include "display_list/display_list_image_filter.h"
|
||||
#include "display_list/display_list_paint.h"
|
||||
#include "display_list/display_list_tile_mode.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "third_party/imgui/imgui.h"
|
||||
@ -285,5 +287,64 @@ TEST_P(DisplayListTest, CanDrawWithImageBlurFilter) {
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
}
|
||||
|
||||
TEST_P(DisplayListTest, CanDrawBackdropFilter) {
|
||||
auto texture = CreateTextureForFixture("embarcadero.jpg");
|
||||
|
||||
bool first_frame = true;
|
||||
auto callback = [&]() {
|
||||
if (first_frame) {
|
||||
first_frame = false;
|
||||
ImGui::SetNextWindowSize({400, 100});
|
||||
ImGui::SetNextWindowPos({300, 650});
|
||||
}
|
||||
|
||||
static float sigma[] = {10, 10};
|
||||
static bool use_bounds = true;
|
||||
static bool draw_circle = true;
|
||||
|
||||
ImGui::Begin("Controls");
|
||||
ImGui::SliderFloat2("Sigma", sigma, 0, 100);
|
||||
ImGui::Checkbox("Use SaveLayer bounds", &use_bounds);
|
||||
ImGui::Checkbox("Draw child element", &draw_circle);
|
||||
ImGui::End();
|
||||
|
||||
flutter::DisplayListBuilder builder;
|
||||
|
||||
Vector2 scale = GetContentScale();
|
||||
builder.scale(scale.x, scale.y);
|
||||
|
||||
auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1],
|
||||
flutter::DlTileMode::kClamp);
|
||||
|
||||
std::optional<SkRect> bounds;
|
||||
if (use_bounds) {
|
||||
auto [p1, p2] = IMPELLER_PLAYGROUND_LINE(
|
||||
Point(250, 150), Point(800, 600), 20, Color::White(), Color::White());
|
||||
bounds = SkRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y);
|
||||
}
|
||||
|
||||
builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200),
|
||||
SkSamplingOptions{}, true);
|
||||
builder.saveLayer(bounds.has_value() ? &bounds.value() : nullptr, nullptr,
|
||||
&filter);
|
||||
|
||||
if (draw_circle) {
|
||||
auto circle_center =
|
||||
IMPELLER_PLAYGROUND_POINT(Point(500, 400), 20, Color::Red());
|
||||
|
||||
builder.setStyle(flutter::DlDrawStyle::kStroke);
|
||||
builder.setStrokeCap(flutter::DlStrokeCap::kButt);
|
||||
builder.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
|
||||
builder.setStrokeWidth(10);
|
||||
builder.setColor(flutter::DlColor::kRed().withAlpha(100));
|
||||
builder.drawCircle({circle_center.x, circle_center.y}, 100);
|
||||
}
|
||||
|
||||
return builder.Build();
|
||||
};
|
||||
|
||||
ASSERT_TRUE(OpenPlaygroundHere(callback));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
||||
|
||||
@ -87,6 +87,8 @@ impeller_component("entity") {
|
||||
"entity_pass.h",
|
||||
"entity_pass_delegate.cc",
|
||||
"entity_pass_delegate.h",
|
||||
"inline_pass_context.cc",
|
||||
"inline_pass_context.h",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
|
||||
@ -4,15 +4,18 @@
|
||||
|
||||
#include "impeller/entity/entity_pass.h"
|
||||
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
#include "flutter/fml/logging.h"
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "flutter/fml/trace_event.h"
|
||||
#include "impeller/base/validation.h"
|
||||
#include "impeller/entity/contents/content_context.h"
|
||||
#include "impeller/entity/contents/filters/filter_contents.h"
|
||||
#include "impeller/entity/contents/filters/inputs/filter_input.h"
|
||||
#include "impeller/entity/contents/texture_contents.h"
|
||||
#include "impeller/entity/inline_pass_context.h"
|
||||
#include "impeller/geometry/path_builder.h"
|
||||
#include "impeller/renderer/allocator.h"
|
||||
#include "impeller/renderer/command.h"
|
||||
@ -36,7 +39,7 @@ void EntityPass::SetDelegate(std::unique_ptr<EntityPassDelegate> delegate) {
|
||||
|
||||
void EntityPass::AddEntity(Entity entity) {
|
||||
if (entity.GetBlendMode() > Entity::BlendMode::kLastPipelineBlendMode) {
|
||||
contains_advanced_blends_ = true;
|
||||
reads_from_pass_texture_ = true;
|
||||
}
|
||||
|
||||
elements_.emplace_back(std::move(entity));
|
||||
@ -122,8 +125,9 @@ EntityPass* EntityPass::AddSubpass(std::unique_ptr<EntityPass> pass) {
|
||||
FML_DCHECK(pass->superpass_ == nullptr);
|
||||
pass->superpass_ = this;
|
||||
|
||||
if (pass->blend_mode_ > Entity::BlendMode::kLastPipelineBlendMode) {
|
||||
contains_advanced_blends_ = true;
|
||||
if (pass->blend_mode_ > Entity::BlendMode::kLastPipelineBlendMode ||
|
||||
pass->backdrop_filter_proc_.has_value()) {
|
||||
reads_from_pass_texture_ = true;
|
||||
}
|
||||
|
||||
auto subpass_pointer = pass.get();
|
||||
@ -133,13 +137,13 @@ EntityPass* EntityPass::AddSubpass(std::unique_ptr<EntityPass> pass) {
|
||||
|
||||
bool EntityPass::Render(ContentContext& renderer,
|
||||
RenderTarget render_target) const {
|
||||
if (contains_advanced_blends_) {
|
||||
if (reads_from_pass_texture_) {
|
||||
auto offscreen_target = RenderTarget::CreateOffscreen(
|
||||
*renderer.GetContext(), render_target.GetRenderTargetSize(),
|
||||
"EntityPass", //
|
||||
StorageMode::kDevicePrivate, LoadAction::kClear, StoreAction::kStore,
|
||||
StorageMode::kDevicePrivate, LoadAction::kClear, StoreAction::kStore);
|
||||
if (!RenderInternal(renderer, offscreen_target, Point(), 0)) {
|
||||
if (!OnRender(renderer, offscreen_target, Point(), Point(), 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -174,231 +178,252 @@ bool EntityPass::Render(ContentContext& renderer,
|
||||
return true;
|
||||
}
|
||||
|
||||
return RenderInternal(renderer, render_target, Point(), 0);
|
||||
return OnRender(renderer, render_target, Point(), Point(), 0);
|
||||
}
|
||||
|
||||
bool EntityPass::RenderInternal(ContentContext& renderer,
|
||||
RenderTarget render_target,
|
||||
Point position,
|
||||
uint32_t pass_depth,
|
||||
size_t stencil_depth_floor) const {
|
||||
TRACE_EVENT0("impeller", "EntityPass::Render");
|
||||
EntityPass::EntityResult EntityPass::GetEntityForElement(
|
||||
const EntityPass::Element& element,
|
||||
ContentContext& renderer,
|
||||
InlinePassContext& pass_context,
|
||||
Point position,
|
||||
uint32_t pass_depth,
|
||||
size_t stencil_depth_floor) const {
|
||||
Entity element_entity;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// Setup entity element.
|
||||
///
|
||||
|
||||
if (const auto& entity = std::get_if<Entity>(&element)) {
|
||||
element_entity = *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.
|
||||
element_entity.SetTransformation(
|
||||
Matrix::MakeTranslation(Vector3(-position)) *
|
||||
element_entity.GetTransformation());
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// Setup subpass element.
|
||||
///
|
||||
|
||||
else if (const auto& subpass_ptr =
|
||||
std::get_if<std::unique_ptr<EntityPass>>(&element)) {
|
||||
auto subpass = subpass_ptr->get();
|
||||
|
||||
if (subpass->delegate_->CanElide()) {
|
||||
return EntityPass::EntityResult::Skip();
|
||||
}
|
||||
|
||||
if (subpass->delegate_->CanCollapseIntoParentPass() &&
|
||||
!subpass->backdrop_filter_proc_.has_value()) {
|
||||
// Directly render into the parent target and move on.
|
||||
if (!subpass->OnRender(renderer, pass_context.GetRenderTarget(), position,
|
||||
position, stencil_depth_floor)) {
|
||||
return EntityPass::EntityResult::Failure();
|
||||
}
|
||||
return EntityPass::EntityResult::Skip();
|
||||
}
|
||||
|
||||
std::shared_ptr<Contents> backdrop_contents = nullptr;
|
||||
if (subpass->backdrop_filter_proc_.has_value()) {
|
||||
auto texture = pass_context.GetTexture();
|
||||
// Render the backdrop texture before any of the pass elements.
|
||||
const auto& proc = subpass->backdrop_filter_proc_.value();
|
||||
backdrop_contents = proc(FilterInput::Make(std::move(texture)));
|
||||
|
||||
// The subpass will need to read from the current pass texture when
|
||||
// rendering the backdrop, so if there's an active pass, end it prior to
|
||||
// rendering the subpass.
|
||||
pass_context.EndPass();
|
||||
}
|
||||
|
||||
auto subpass_coverage = GetSubpassCoverage(*subpass);
|
||||
|
||||
if (backdrop_contents) {
|
||||
auto backdrop_coverage = backdrop_contents->GetCoverage(Entity{});
|
||||
if (backdrop_coverage.has_value()) {
|
||||
backdrop_coverage->origin += position;
|
||||
|
||||
if (subpass_coverage.has_value()) {
|
||||
subpass_coverage = subpass_coverage->Union(backdrop_coverage.value());
|
||||
} else {
|
||||
subpass_coverage = backdrop_coverage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!subpass_coverage.has_value()) {
|
||||
return EntityPass::EntityResult::Skip();
|
||||
}
|
||||
|
||||
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.
|
||||
return EntityPass::EntityResult::Skip();
|
||||
}
|
||||
|
||||
RenderTarget subpass_target;
|
||||
if (subpass->reads_from_pass_texture_) {
|
||||
subpass_target = RenderTarget::CreateOffscreen(
|
||||
*renderer.GetContext(), ISize::Ceil(subpass_coverage->size),
|
||||
"EntityPass", StorageMode::kDevicePrivate, LoadAction::kClear,
|
||||
StoreAction::kStore, StorageMode::kDevicePrivate, LoadAction::kClear,
|
||||
StoreAction::kStore);
|
||||
} else {
|
||||
subpass_target = RenderTarget::CreateOffscreen(
|
||||
*renderer.GetContext(), ISize::Ceil(subpass_coverage->size),
|
||||
"EntityPass", StorageMode::kDevicePrivate, LoadAction::kClear,
|
||||
StoreAction::kStore, StorageMode::kDeviceTransient,
|
||||
LoadAction::kClear, StoreAction::kDontCare);
|
||||
}
|
||||
|
||||
auto subpass_texture = subpass_target.GetRenderTargetTexture();
|
||||
|
||||
if (!subpass_texture) {
|
||||
return EntityPass::EntityResult::Failure();
|
||||
}
|
||||
|
||||
auto offscreen_texture_contents =
|
||||
subpass->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 EntityPass::EntityResult::Failure();
|
||||
}
|
||||
|
||||
// Stencil textures aren't shared between EntityPasses (as much of the
|
||||
// time they are transient).
|
||||
if (!subpass->OnRender(renderer, subpass_target, subpass_coverage->origin,
|
||||
position, ++pass_depth, subpass->stencil_depth_,
|
||||
backdrop_contents)) {
|
||||
return EntityPass::EntityResult::Failure();
|
||||
}
|
||||
|
||||
element_entity.SetContents(std::move(offscreen_texture_contents));
|
||||
element_entity.SetStencilDepth(subpass->stencil_depth_);
|
||||
element_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.
|
||||
element_entity.SetTransformation(
|
||||
Matrix::MakeTranslation(Vector3(subpass_coverage->origin - position)));
|
||||
} else {
|
||||
FML_UNREACHABLE();
|
||||
}
|
||||
|
||||
return EntityPass::EntityResult::Success(element_entity);
|
||||
}
|
||||
|
||||
bool EntityPass::OnRender(ContentContext& renderer,
|
||||
RenderTarget render_target,
|
||||
Point position,
|
||||
Point parent_position,
|
||||
uint32_t pass_depth,
|
||||
size_t stencil_depth_floor,
|
||||
std::shared_ptr<Contents> backdrop_contents) const {
|
||||
TRACE_EVENT0("impeller", "EntityPass::OnRender");
|
||||
|
||||
auto context = renderer.GetContext();
|
||||
InlinePassContext pass_context(context, render_target);
|
||||
if (!pass_context.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<CommandBuffer> command_buffer;
|
||||
std::shared_ptr<RenderPass> pass;
|
||||
uint32_t pass_count = 0;
|
||||
auto render_element = [&stencil_depth_floor, &pass_context, &pass_depth,
|
||||
&renderer](Entity element_entity) {
|
||||
element_entity.SetStencilDepth(element_entity.GetStencilDepth() -
|
||||
stencil_depth_floor);
|
||||
|
||||
auto end_pass = [&command_buffer, &pass, &context]() {
|
||||
if (!pass->EncodeCommands(context->GetTransientsAllocator())) {
|
||||
auto pass = pass_context.GetRenderPass(pass_depth);
|
||||
if (!element_entity.Render(renderer, *pass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!command_buffer->SubmitCommands()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if (backdrop_filter_proc_.has_value()) {
|
||||
if (!backdrop_contents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Entity backdrop_entity;
|
||||
backdrop_entity.SetContents(std::move(backdrop_contents));
|
||||
backdrop_entity.SetTransformation(
|
||||
Matrix::MakeTranslation(Vector3(parent_position - position)));
|
||||
|
||||
render_element(backdrop_entity);
|
||||
}
|
||||
|
||||
for (const auto& element : elements_) {
|
||||
Entity element_entity;
|
||||
EntityResult result =
|
||||
GetEntityForElement(element, renderer, pass_context, position,
|
||||
pass_depth, stencil_depth_floor);
|
||||
|
||||
// =========================================================================
|
||||
// Setup entity element for rendering ======================================
|
||||
// =========================================================================
|
||||
if (const auto& entity = std::get_if<Entity>(&element)) {
|
||||
element_entity = *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.
|
||||
element_entity.SetTransformation(
|
||||
Matrix::MakeTranslation(Vector3(-position)) *
|
||||
element_entity.GetTransformation());
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Setup subpass element for rendering =====================================
|
||||
// =========================================================================
|
||||
else if (const auto& subpass_ptr =
|
||||
std::get_if<std::unique_ptr<EntityPass>>(&element)) {
|
||||
auto subpass = subpass_ptr->get();
|
||||
|
||||
if (subpass->delegate_->CanElide()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (subpass->delegate_->CanCollapseIntoParentPass()) {
|
||||
// Directly render into the parent target and move on.
|
||||
if (!subpass->RenderInternal(renderer, render_target, position,
|
||||
pass_depth, stencil_depth_floor)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto subpass_coverage = GetSubpassCoverage(*subpass);
|
||||
if (!subpass_coverage.has_value()) {
|
||||
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;
|
||||
}
|
||||
|
||||
RenderTarget subpass_target;
|
||||
if (subpass->contains_advanced_blends_) {
|
||||
subpass_target = RenderTarget::CreateOffscreen(
|
||||
*context, ISize::Ceil(subpass_coverage->size), "EntityPass",
|
||||
StorageMode::kDevicePrivate, LoadAction::kClear,
|
||||
StoreAction::kStore, StorageMode::kDevicePrivate,
|
||||
LoadAction::kClear, StoreAction::kStore);
|
||||
} else {
|
||||
subpass_target = RenderTarget::CreateOffscreen(
|
||||
*context, ISize::Ceil(subpass_coverage->size), "EntityPass",
|
||||
StorageMode::kDevicePrivate, LoadAction::kClear,
|
||||
StoreAction::kStore, StorageMode::kDeviceTransient,
|
||||
LoadAction::kClear, StoreAction::kDontCare);
|
||||
}
|
||||
|
||||
auto subpass_texture = subpass_target.GetRenderTargetTexture();
|
||||
|
||||
if (!subpass_texture) {
|
||||
switch (result.status) {
|
||||
case EntityResult::kSuccess:
|
||||
break;
|
||||
case EntityResult::kFailure:
|
||||
return false;
|
||||
}
|
||||
case EntityResult::kSkip:
|
||||
continue;
|
||||
};
|
||||
|
||||
auto offscreen_texture_contents =
|
||||
subpass->delegate_->CreateContentsForSubpassTarget(subpass_texture);
|
||||
//--------------------------------------------------------------------------
|
||||
/// Setup advanced blends.
|
||||
///
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Stencil textures aren't shared between EntityPasses (as much of the
|
||||
// time they are transient).
|
||||
if (!subpass->RenderInternal(renderer, subpass_target,
|
||||
subpass_coverage->origin, ++pass_depth,
|
||||
subpass->stencil_depth_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
element_entity.SetContents(std::move(offscreen_texture_contents));
|
||||
element_entity.SetStencilDepth(subpass->stencil_depth_);
|
||||
element_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.
|
||||
element_entity.SetTransformation(Matrix::MakeTranslation(
|
||||
Vector3(subpass_coverage->origin - position)));
|
||||
} else {
|
||||
FML_UNREACHABLE();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Configure the RenderPass ================================================
|
||||
// =========================================================================
|
||||
|
||||
if (pass && element_entity.GetBlendMode() >
|
||||
Entity::BlendMode::kLastPipelineBlendMode) {
|
||||
if (result.entity.GetBlendMode() >
|
||||
Entity::BlendMode::kLastPipelineBlendMode) {
|
||||
// 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 (!end_pass()) {
|
||||
// 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()) {
|
||||
return false;
|
||||
}
|
||||
// Resetting these handles triggers a new pass to get created below
|
||||
pass = nullptr;
|
||||
command_buffer = nullptr;
|
||||
|
||||
// Amend an advanced blend to the contents.
|
||||
if (render_target.GetColorAttachments().empty()) {
|
||||
// Amend an advanced blend filter to the contents, attaching the pass
|
||||
// texture.
|
||||
auto texture = pass_context.GetTexture();
|
||||
if (!texture) {
|
||||
return false;
|
||||
}
|
||||
auto color0 = render_target.GetColorAttachments().find(0)->second;
|
||||
|
||||
FilterInput::Vector inputs = {
|
||||
FilterInput::Make(element_entity.GetContents()),
|
||||
FilterInput::Make(
|
||||
color0.resolve_texture ? color0.resolve_texture : color0.texture,
|
||||
element_entity.GetTransformation().Invert())};
|
||||
element_entity.SetContents(
|
||||
FilterContents::MakeBlend(element_entity.GetBlendMode(), inputs));
|
||||
element_entity.SetBlendMode(Entity::BlendMode::kSourceOver);
|
||||
FilterInput::Make(result.entity.GetContents()),
|
||||
FilterInput::Make(texture,
|
||||
result.entity.GetTransformation().Invert())};
|
||||
result.entity.SetContents(
|
||||
FilterContents::MakeBlend(result.entity.GetBlendMode(), inputs));
|
||||
result.entity.SetBlendMode(Entity::BlendMode::kSourceOver);
|
||||
}
|
||||
|
||||
// Create a new render pass to render the element if one isn't active.
|
||||
if (!pass) {
|
||||
command_buffer = context->CreateRenderCommandBuffer();
|
||||
if (!command_buffer) {
|
||||
return false;
|
||||
}
|
||||
//--------------------------------------------------------------------------
|
||||
/// Render the Element.
|
||||
///
|
||||
|
||||
command_buffer->SetLabel(
|
||||
"EntityPass Command Buffer: Depth=" + std::to_string(pass_depth) +
|
||||
" Count=" + std::to_string(pass_count));
|
||||
|
||||
// Never clear the texture for subsequent passes.
|
||||
if (pass_count > 0) {
|
||||
if (!render_target.GetColorAttachments().empty()) {
|
||||
auto color0 = render_target.GetColorAttachments().find(0)->second;
|
||||
color0.load_action = LoadAction::kLoad;
|
||||
render_target.SetColorAttachment(color0, 0);
|
||||
}
|
||||
|
||||
if (auto stencil = render_target.GetStencilAttachment();
|
||||
stencil.has_value()) {
|
||||
stencil->load_action = LoadAction::kLoad;
|
||||
render_target.SetStencilAttachment(stencil.value());
|
||||
}
|
||||
}
|
||||
|
||||
pass = command_buffer->CreateRenderPass(render_target);
|
||||
if (!pass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pass->SetLabel(
|
||||
"EntityPass Render Pass: Depth=" + std::to_string(pass_depth) +
|
||||
" Count=" + std::to_string(pass_count));
|
||||
|
||||
++pass_count;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Render the element ======================================================
|
||||
// =========================================================================
|
||||
|
||||
element_entity.SetStencilDepth(element_entity.GetStencilDepth() -
|
||||
stencil_depth_floor);
|
||||
|
||||
if (!element_entity.Render(renderer, *pass)) {
|
||||
if (!render_element(result.entity)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (pass) {
|
||||
return end_pass();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -455,4 +480,11 @@ void EntityPass::SetBlendMode(Entity::BlendMode blend_mode) {
|
||||
blend_mode_ = blend_mode;
|
||||
}
|
||||
|
||||
void EntityPass::SetBackdropFilter(std::optional<BackdropFilterProc> proc) {
|
||||
backdrop_filter_proc_ = proc;
|
||||
if (superpass_) {
|
||||
superpass_->reads_from_pass_texture_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -4,14 +4,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "impeller/entity/contents/contents.h"
|
||||
#include "impeller/entity/contents/filters/filter_contents.h"
|
||||
#include "impeller/entity/entity.h"
|
||||
#include "impeller/entity/entity_pass_delegate.h"
|
||||
#include "impeller/entity/inline_pass_context.h"
|
||||
#include "impeller/renderer/render_target.h"
|
||||
#include "impeller/typographer/lazy_glyph_atlas.h"
|
||||
|
||||
@ -22,6 +25,8 @@ class ContentContext;
|
||||
class EntityPass {
|
||||
public:
|
||||
using Element = std::variant<Entity, std::unique_ptr<EntityPass>>;
|
||||
using BackdropFilterProc =
|
||||
std::function<std::shared_ptr<FilterContents>(FilterInput::Ref)>;
|
||||
|
||||
EntityPass();
|
||||
|
||||
@ -53,16 +58,50 @@ class EntityPass {
|
||||
|
||||
void SetBlendMode(Entity::BlendMode blend_mode);
|
||||
|
||||
void SetBackdropFilter(std::optional<BackdropFilterProc> proc);
|
||||
|
||||
std::optional<Rect> GetSubpassCoverage(const EntityPass& subpass) const;
|
||||
|
||||
std::optional<Rect> GetElementsCoverage() const;
|
||||
|
||||
private:
|
||||
bool RenderInternal(ContentContext& renderer,
|
||||
RenderTarget render_target,
|
||||
Point position,
|
||||
uint32_t pass_depth,
|
||||
size_t stencil_depth_floor = 0) const;
|
||||
struct EntityResult {
|
||||
enum Status {
|
||||
/// The entity was successfully resolved and can be rendered.
|
||||
kSuccess,
|
||||
/// An unexpected rendering error occurred while resolving the Entity.
|
||||
kFailure,
|
||||
/// The entity should be skipped because rendering it will contribute
|
||||
/// nothing to the frame.
|
||||
kSkip,
|
||||
};
|
||||
|
||||
/// @brief The resulting entity that should be rendered. If `std::nullopt`,
|
||||
/// there is nothing to render.
|
||||
Entity entity;
|
||||
/// @brief This is set to `false` if there was an unexpected rendering
|
||||
/// error while resolving the Entity.
|
||||
Status status = kFailure;
|
||||
|
||||
static EntityResult Success(Entity e) { return {e, kSuccess}; }
|
||||
static EntityResult Failure() { return {{}, kFailure}; }
|
||||
static EntityResult Skip() { return {{}, kSkip}; }
|
||||
};
|
||||
|
||||
EntityResult GetEntityForElement(const EntityPass::Element& element,
|
||||
ContentContext& renderer,
|
||||
InlinePassContext& pass_context,
|
||||
Point position,
|
||||
uint32_t pass_depth,
|
||||
size_t stencil_depth_floor) const;
|
||||
|
||||
bool OnRender(ContentContext& renderer,
|
||||
RenderTarget render_target,
|
||||
Point position,
|
||||
Point parent_position,
|
||||
uint32_t pass_depth,
|
||||
size_t stencil_depth_floor = 0,
|
||||
std::shared_ptr<Contents> backdrop_contents = nullptr) const;
|
||||
|
||||
std::vector<Element> elements_;
|
||||
|
||||
@ -70,7 +109,16 @@ class EntityPass {
|
||||
Matrix xformation_;
|
||||
size_t stencil_depth_ = 0u;
|
||||
Entity::BlendMode blend_mode_ = Entity::BlendMode::kSourceOver;
|
||||
bool contains_advanced_blends_ = false;
|
||||
|
||||
/// This flag is set to `true` whenever an entity is added to the pass that
|
||||
/// requires reading the pass texture during rendering. This can happen in the
|
||||
/// following scenarios:
|
||||
/// 1. An entity with an "advanced blend" is added to the pass.
|
||||
/// 2. A subpass with a backdrop filter is added to the pass.
|
||||
bool reads_from_pass_texture_ = false;
|
||||
|
||||
std::optional<BackdropFilterProc> backdrop_filter_proc_ = std::nullopt;
|
||||
|
||||
std::unique_ptr<EntityPassDelegate> delegate_ =
|
||||
EntityPassDelegate::MakeDefault();
|
||||
std::shared_ptr<LazyGlyphAtlas> lazy_glyph_atlas_ =
|
||||
|
||||
101
engine/src/flutter/impeller/entity/inline_pass_context.cc
Normal file
101
engine/src/flutter/impeller/entity/inline_pass_context.cc
Normal file
@ -0,0 +1,101 @@
|
||||
// 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/inline_pass_context.h"
|
||||
|
||||
#include "impeller/renderer/command_buffer.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
InlinePassContext::InlinePassContext(std::shared_ptr<Context> context,
|
||||
RenderTarget render_target)
|
||||
: context_(context), render_target_(render_target) {}
|
||||
|
||||
InlinePassContext::~InlinePassContext() {
|
||||
EndPass();
|
||||
}
|
||||
|
||||
bool InlinePassContext::IsValid() const {
|
||||
return !render_target_.GetColorAttachments().empty();
|
||||
}
|
||||
|
||||
bool InlinePassContext::IsActive() const {
|
||||
return pass_ != nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Texture> InlinePassContext::GetTexture() {
|
||||
if (!IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto color0 = render_target_.GetColorAttachments().find(0)->second;
|
||||
return color0.resolve_texture ? color0.resolve_texture : color0.texture;
|
||||
}
|
||||
|
||||
bool InlinePassContext::EndPass() {
|
||||
if (!IsActive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!pass_->EncodeCommands(context_->GetTransientsAllocator())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!command_buffer_->SubmitCommands()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pass_ = nullptr;
|
||||
command_buffer_ = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const RenderTarget& InlinePassContext::GetRenderTarget() const {
|
||||
return render_target_;
|
||||
}
|
||||
|
||||
std::shared_ptr<RenderPass> InlinePassContext::GetRenderPass(
|
||||
uint32_t pass_depth) {
|
||||
// Create a new render pass if one isn't active.
|
||||
if (!IsActive()) {
|
||||
command_buffer_ = context_->CreateRenderCommandBuffer();
|
||||
if (!command_buffer_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
command_buffer_->SetLabel(
|
||||
"EntityPass Command Buffer: Depth=" + std::to_string(pass_depth) +
|
||||
" Count=" + std::to_string(pass_count_));
|
||||
|
||||
// Never clear the texture for subsequent passes.
|
||||
if (pass_count_ > 0) {
|
||||
if (!render_target_.GetColorAttachments().empty()) {
|
||||
auto color0 = render_target_.GetColorAttachments().find(0)->second;
|
||||
color0.load_action = LoadAction::kLoad;
|
||||
render_target_.SetColorAttachment(color0, 0);
|
||||
}
|
||||
|
||||
if (auto stencil = render_target_.GetStencilAttachment();
|
||||
stencil.has_value()) {
|
||||
stencil->load_action = LoadAction::kLoad;
|
||||
render_target_.SetStencilAttachment(stencil.value());
|
||||
}
|
||||
}
|
||||
|
||||
pass_ = command_buffer_->CreateRenderPass(render_target_);
|
||||
if (!pass_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
pass_->SetLabel(
|
||||
"EntityPass Render Pass: Depth=" + std::to_string(pass_depth) +
|
||||
" Count=" + std::to_string(pass_count_));
|
||||
|
||||
++pass_count_;
|
||||
}
|
||||
|
||||
return pass_;
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
37
engine/src/flutter/impeller/entity/inline_pass_context.h
Normal file
37
engine/src/flutter/impeller/entity/inline_pass_context.h
Normal file
@ -0,0 +1,37 @@
|
||||
// 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 "impeller/renderer/context.h"
|
||||
#include "impeller/renderer/render_pass.h"
|
||||
#include "impeller/renderer/render_target.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
class InlinePassContext {
|
||||
public:
|
||||
InlinePassContext(std::shared_ptr<Context> context,
|
||||
RenderTarget render_target);
|
||||
~InlinePassContext();
|
||||
|
||||
bool IsValid() const;
|
||||
bool IsActive() const;
|
||||
std::shared_ptr<Texture> GetTexture();
|
||||
bool EndPass();
|
||||
const RenderTarget& GetRenderTarget() const;
|
||||
|
||||
std::shared_ptr<RenderPass> GetRenderPass(uint32_t pass_depth);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Context> context_;
|
||||
RenderTarget render_target_;
|
||||
std::shared_ptr<CommandBuffer> command_buffer_;
|
||||
std::shared_ptr<RenderPass> pass_;
|
||||
uint32_t pass_count_ = 0;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(InlinePassContext);
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
Loading…
x
Reference in New Issue
Block a user