Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions impeller/aiks/aiks_blend_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

#include "flutter/testing/testing.h"
#include "impeller/aiks/canvas.h"
#include "impeller/aiks/color_filter.h"
#include "impeller/geometry/color.h"
#include "impeller/geometry/scalar.h"

////////////////////////////////////////////////////////////////////////////////
// This is for tests of Canvas that are interested the results of rendering
Expand Down Expand Up @@ -553,5 +556,31 @@ TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) {
ASSERT_TRUE(OpenPlaygroundHere(callback));
}

TEST_P(AiksTest, ForegroundPipelineBlendAppliesTransformCorrectly) {
auto texture = CreateTextureForFixture("airplane.jpg",
/*enable_mipmapping=*/true);

Canvas canvas;
canvas.Rotate(Degrees(30));
canvas.DrawImage(std::make_shared<Image>(texture), {200, 200},
{.color_filter = ColorFilter::MakeBlend(BlendMode::kSourceIn,
Color::Orange())});

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

TEST_P(AiksTest, ForegroundAdvancedBlendAppliesTransformCorrectly) {
auto texture = CreateTextureForFixture("airplane.jpg",
/*enable_mipmapping=*/true);

Canvas canvas;
canvas.Rotate(Degrees(30));
canvas.DrawImage(std::make_shared<Image>(texture), {200, 200},
{.color_filter = ColorFilter::MakeBlend(
BlendMode::kColorDodge, Color::Orange())});

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

} // namespace testing
} // namespace impeller
141 changes: 122 additions & 19 deletions impeller/entity/contents/filters/blend_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -259,30 +259,22 @@ std::optional<Entity> BlendFilterContents::CreateForegroundAdvancedBlend(
return std::nullopt;
}

RenderProc render_proc = [foreground_color, coverage, dst_snapshot,
blend_mode, alpha, absorb_opacity](
const ContentContext& renderer,
const Entity& entity, RenderPass& pass) -> bool {
RenderProc render_proc = [foreground_color, dst_snapshot, blend_mode, alpha,
absorb_opacity](const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) -> bool {
using VS = BlendScreenPipeline::VertexShader;
using FS = BlendScreenPipeline::FragmentShader;

auto& host_buffer = renderer.GetTransientsBuffer();

auto maybe_dst_uvs = dst_snapshot->GetCoverageUVs(coverage);
if (!maybe_dst_uvs.has_value()) {
return false;
}
auto dst_uvs = maybe_dst_uvs.value();

auto size = coverage.GetSize();
auto origin = coverage.GetOrigin();
auto size = dst_snapshot->texture->GetSize();
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.AddVertices({
{origin, dst_uvs[0], dst_uvs[0]},
{Point(origin.x + size.width, origin.y), dst_uvs[1], dst_uvs[1]},
{Point(origin.x, origin.y + size.height), dst_uvs[2], dst_uvs[2]},
{Point(origin.x + size.width, origin.y + size.height), dst_uvs[3],
dst_uvs[3]},
{{0, 0}, {0, 0}, {0, 0}},
{Point(size.width, 0), {1, 0}, {1, 0}},
{Point(0, size.height), {0, 1}, {0, 1}},
{Point(size.width, size.height), {1, 1}, {1, 1}},
});
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);

Expand Down Expand Up @@ -361,6 +353,9 @@ std::optional<Entity> BlendFilterContents::CreateForegroundAdvancedBlend(
dst_sampler_descriptor);
FS::BindTextureSamplerDst(pass, dst_snapshot->texture, dst_sampler);
frame_info.dst_y_coord_scale = dst_snapshot->texture->GetYCoordScale();

frame_info.mvp = pass.GetOrthographicTransform() * dst_snapshot->transform;

blend_info.dst_input_alpha =
absorb_opacity == ColorFilterContents::AbsorbOpacity::kYes
? dst_snapshot->opacity * alpha.value_or(1.0)
Expand All @@ -376,8 +371,6 @@ std::optional<Entity> BlendFilterContents::CreateForegroundAdvancedBlend(
auto blend_uniform = host_buffer.EmplaceUniform(blend_info);
FS::BindBlendInfo(pass, blend_uniform);

frame_info.mvp = entity.GetShaderTransform(pass);

auto uniform_view = host_buffer.EmplaceUniform(frame_info);
VS::BindFrameInfo(pass, uniform_view);

Expand All @@ -397,6 +390,110 @@ std::optional<Entity> BlendFilterContents::CreateForegroundAdvancedBlend(
return sub_entity;
}

std::optional<Entity> BlendFilterContents::CreateForegroundPorterDuffBlend(
const std::shared_ptr<FilterInput>& input,
const ContentContext& renderer,
const Entity& entity,
const Rect& coverage,
Color foreground_color,
BlendMode blend_mode,
std::optional<Scalar> alpha,
ColorFilterContents::AbsorbOpacity absorb_opacity) const {
if (blend_mode == BlendMode::kClear) {
return std::nullopt;
}

auto dst_snapshot =
input->GetSnapshot("ForegroundPorterDuffBlend", renderer, entity);
if (!dst_snapshot.has_value()) {
return std::nullopt;
}

if (blend_mode == BlendMode::kDestination) {
return Entity::FromSnapshot(dst_snapshot.value(), entity.GetBlendMode(),
entity.GetClipDepth());
}

RenderProc render_proc = [foreground_color, dst_snapshot, blend_mode,
absorb_opacity, alpha](
const ContentContext& renderer,
const Entity& entity, RenderPass& pass) -> bool {
using VS = PorterDuffBlendPipeline::VertexShader;
using FS = PorterDuffBlendPipeline::FragmentShader;

auto& host_buffer = renderer.GetTransientsBuffer();
auto size = dst_snapshot->texture->GetSize();
auto color = foreground_color.Premultiply();
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.AddVertices({
{{0, 0}, {0, 0}, color},
{Point(size.width, 0), {1, 0}, color},
{Point(0, size.height), {0, 1}, color},
{Point(size.width, size.height), {1, 1}, color},
});
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);

#ifdef IMPELLER_DEBUG
pass.SetCommandLabel(SPrintF("Foreground PorterDuff Blend Filter (%s)",
BlendModeToString(blend_mode)));
#endif // IMPELLER_DEBUG
pass.SetVertexBuffer(std::move(vtx_buffer));
pass.SetStencilReference(entity.GetClipDepth());
auto options = OptionsFromPass(pass);
options.primitive_type = PrimitiveType::kTriangleStrip;
pass.SetPipeline(renderer.GetPorterDuffBlendPipeline(options));

FS::FragInfo frag_info;
VS::FrameInfo frame_info;

frame_info.mvp = pass.GetOrthographicTransform() * dst_snapshot->transform;

auto dst_sampler_descriptor = dst_snapshot->sampler_descriptor;
if (renderer.GetDeviceCapabilities().SupportsDecalSamplerAddressMode()) {
dst_sampler_descriptor.width_address_mode = SamplerAddressMode::kDecal;
dst_sampler_descriptor.height_address_mode = SamplerAddressMode::kDecal;
}
const std::unique_ptr<const Sampler>& dst_sampler =
renderer.GetContext()->GetSamplerLibrary()->GetSampler(
dst_sampler_descriptor);
FS::BindTextureSamplerDst(pass, dst_snapshot->texture, dst_sampler);
frame_info.texture_sampler_y_coord_scale =
dst_snapshot->texture->GetYCoordScale();

frag_info.input_alpha =
absorb_opacity == ColorFilterContents::AbsorbOpacity::kYes
? dst_snapshot->opacity * alpha.value_or(1.0)
: 1.0;
frag_info.output_alpha = 1.0;

auto blend_coefficients =
kPorterDuffCoefficients[static_cast<int>(blend_mode)];
frag_info.src_coeff = blend_coefficients[0];
frag_info.src_coeff_dst_alpha = blend_coefficients[1];
frag_info.dst_coeff = blend_coefficients[2];
frag_info.dst_coeff_src_alpha = blend_coefficients[3];
frag_info.dst_coeff_src_color = blend_coefficients[4];

FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info));
VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info));

return pass.Draw().ok();
};

CoverageProc coverage_proc =
[coverage](const Entity& entity) -> std::optional<Rect> {
return coverage.TransformBounds(entity.GetTransform());
};

auto contents = AnonymousContents::Make(render_proc, coverage_proc);

Entity sub_entity;
sub_entity.SetContents(std::move(contents));
sub_entity.SetClipDepth(entity.GetClipDepth());

return sub_entity;
}

static std::optional<Entity> PipelineBlend(
const FilterInput::Vector& inputs,
const ContentContext& renderer,
Expand Down Expand Up @@ -626,6 +723,12 @@ std::optional<Entity> BlendFilterContents::RenderFilter(
}

if (blend_mode <= Entity::kLastPipelineBlendMode) {
if (inputs.size() == 1 && foreground_color_.has_value() &&
GetAbsorbOpacity() == ColorFilterContents::AbsorbOpacity::kYes) {
return CreateForegroundPorterDuffBlend(
inputs[0], renderer, entity, coverage, foreground_color_.value(),
blend_mode_, GetAlpha(), GetAbsorbOpacity());
}
return PipelineBlend(inputs, renderer, entity, coverage, blend_mode,
foreground_color_, GetAbsorbOpacity(), GetAlpha());
}
Expand Down
14 changes: 14 additions & 0 deletions impeller/entity/contents/filters/blend_filter_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ class BlendFilterContents : public ColorFilterContents {
std::optional<Scalar> alpha,
ColorFilterContents::AbsorbOpacity absorb_opacity) const;

/// @brief Optimized porter-duff blend that avoids a second subpass when there
/// is only a single input and a foreground color.
///
/// These contents cannot absorb opacity.
std::optional<Entity> CreateForegroundPorterDuffBlend(
const std::shared_ptr<FilterInput>& input,
const ContentContext& renderer,
const Entity& entity,
const Rect& coverage,
Color foreground_color,
BlendMode blend_mode,
std::optional<Scalar> alpha,
ColorFilterContents::AbsorbOpacity absorb_opacity) const;

BlendMode blend_mode_ = BlendMode::kSourceOver;
std::optional<Color> foreground_color_;

Expand Down
1 change: 0 additions & 1 deletion impeller/renderer/snapshot.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include <memory>
#include <vector>

#include "flutter/fml/macros.h"
#include "impeller/core/formats.h"
#include "impeller/core/sampler_descriptor.h"
#include "impeller/core/texture.h"
Expand Down
6 changes: 6 additions & 0 deletions testing/impeller_golden_tests_output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -569,9 +569,15 @@ impeller_Play_AiksTest_FilledRoundRectPathsRenderCorrectly_Vulkan.png
impeller_Play_AiksTest_FilledRoundRectsRenderCorrectly_Metal.png
impeller_Play_AiksTest_FilledRoundRectsRenderCorrectly_OpenGLES.png
impeller_Play_AiksTest_FilledRoundRectsRenderCorrectly_Vulkan.png
impeller_Play_AiksTest_ForegroundAdvancedBlendAppliesTransformCorrectly_Metal.png
impeller_Play_AiksTest_ForegroundAdvancedBlendAppliesTransformCorrectly_OpenGLES.png
impeller_Play_AiksTest_ForegroundAdvancedBlendAppliesTransformCorrectly_Vulkan.png
impeller_Play_AiksTest_ForegroundBlendSubpassCollapseOptimization_Metal.png
impeller_Play_AiksTest_ForegroundBlendSubpassCollapseOptimization_OpenGLES.png
impeller_Play_AiksTest_ForegroundBlendSubpassCollapseOptimization_Vulkan.png
impeller_Play_AiksTest_ForegroundPipelineBlendAppliesTransformCorrectly_Metal.png
impeller_Play_AiksTest_ForegroundPipelineBlendAppliesTransformCorrectly_OpenGLES.png
impeller_Play_AiksTest_ForegroundPipelineBlendAppliesTransformCorrectly_Vulkan.png
impeller_Play_AiksTest_GaussianBlurAnimatedBackdrop_Metal.png
impeller_Play_AiksTest_GaussianBlurAnimatedBackdrop_OpenGLES.png
impeller_Play_AiksTest_GaussianBlurAnimatedBackdrop_Vulkan.png
Expand Down