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
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,7 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_saturation.frag + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_screen.frag + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_softlight.frag + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/porter_duff_blend.frag + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/border_mask_blur.frag + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/border_mask_blur.vert + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/shaders/color_matrix_color_filter.frag + ../../../flutter/LICENSE
Expand Down Expand Up @@ -3867,6 +3868,7 @@ FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_ov
FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_saturation.frag
FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_screen.frag
FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_softlight.frag
FILE: ../../../flutter/impeller/entity/shaders/blending/porter_duff_blend.frag
FILE: ../../../flutter/impeller/entity/shaders/border_mask_blur.frag
FILE: ../../../flutter/impeller/entity/shaders/border_mask_blur.vert
FILE: ../../../flutter/impeller/entity/shaders/color_matrix_color_filter.frag
Expand Down
20 changes: 20 additions & 0 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2035,6 +2035,26 @@ TEST_P(AiksTest, TranslucentSaveLayerWithBlendImageFilterAndDrawsCorrectly) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, TranslucentSaveLayerWithColorImageFilterAndDrawsCorrectly) {
Canvas canvas;

canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});

canvas.SaveLayer({
.color = Color::Black().WithAlpha(0.5),
.color_filter =
[](FilterInput::Ref input) {
return ColorFilterContents::MakeBlend(
BlendMode::kDestinationOver, {std::move(input)}, Color::Red());
},
});

canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
canvas.Restore();

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

TEST_P(AiksTest, TranslucentSaveLayerImageDrawsCorrectly) {
Canvas canvas;

Expand Down
2 changes: 1 addition & 1 deletion impeller/aiks/paint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ std::shared_ptr<Contents> Paint::WithFilters(
std::shared_ptr<Contents> input,
std::optional<bool> is_solid_color) const {
bool is_solid_color_val = is_solid_color.value_or(!color_source);
input = WithColorFilter(input);
input = WithColorFilter(input, /*absorb_opacity=*/true);
input = WithInvertFilter(input);
input = WithMaskBlur(input, is_solid_color_val, Matrix());
input = WithImageFilter(input, Matrix(), /*is_subpass=*/false);
Expand Down
1 change: 1 addition & 0 deletions impeller/entity/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ impeller_shaders("entity_shaders") {
"shaders/vertices.frag",
"shaders/yuv_to_rgb_filter.frag",
"shaders/yuv_to_rgb_filter.vert",
"shaders/blending/porter_duff_blend.frag",
]
}

Expand Down
2 changes: 2 additions & 0 deletions impeller/entity/contents/content_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
CreateDefaultPipeline<GeometryColorPipeline>(*context_);
yuv_to_rgb_filter_pipelines_[{}] =
CreateDefaultPipeline<YUVToRGBFilterPipeline>(*context_);
porter_duff_blend_pipelines_[{}] =
CreateDefaultPipeline<PorterDuffBlendPipeline>(*context_);

if (solid_fill_pipelines_[{}]->GetDescriptor().has_value()) {
auto clip_pipeline_descriptor =
Expand Down
10 changes: 9 additions & 1 deletion impeller/entity/contents/content_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "impeller/entity/linear_to_srgb_filter.vert.h"
#include "impeller/entity/morphology_filter.frag.h"
#include "impeller/entity/morphology_filter.vert.h"
#include "impeller/entity/porter_duff_blend.frag.h"
#include "impeller/entity/radial_gradient_fill.frag.h"
#include "impeller/entity/rrect_blur.frag.h"
#include "impeller/entity/rrect_blur.vert.h"
Expand Down Expand Up @@ -125,7 +126,6 @@ using RadialGradientSSBOFillPipeline =
using SweepGradientSSBOFillPipeline =
RenderPipelineT<GradientFillVertexShader,
SweepGradientSsboFillFragmentShader>;
using BlendPipeline = RenderPipelineT<BlendVertexShader, BlendFragmentShader>;
using RRectBlurPipeline =
RenderPipelineT<RrectBlurVertexShader, RrectBlurFragmentShader>;
using BlendPipeline = RenderPipelineT<BlendVertexShader, BlendFragmentShader>;
Expand Down Expand Up @@ -165,6 +165,8 @@ using GlyphAtlasPipeline =
RenderPipelineT<GlyphAtlasVertexShader, GlyphAtlasFragmentShader>;
using GlyphAtlasSdfPipeline =
RenderPipelineT<GlyphAtlasSdfVertexShader, GlyphAtlasSdfFragmentShader>;
using PorterDuffBlendPipeline =
RenderPipelineT<BlendVertexShader, PorterDuffBlendFragmentShader>;
// Instead of requiring new shaders for clips, the solid fill stages are used
// to redirect writing to the stencil instead of color attachments.
using ClipPipeline =
Expand Down Expand Up @@ -469,6 +471,11 @@ class ContentContext {
return GetPipeline(yuv_to_rgb_filter_pipelines_, opts);
}

std::shared_ptr<Pipeline<PipelineDescriptor>> GetPorterDuffBlendPipeline(
ContentContextOptions opts) const {
return GetPipeline(porter_duff_blend_pipelines_, opts);
}

// Advanced blends.

std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendColorPipeline(
Expand Down Expand Up @@ -705,6 +712,7 @@ class ContentContext {
mutable Variants<GlyphAtlasSdfPipeline> glyph_atlas_sdf_pipelines_;
mutable Variants<GeometryColorPipeline> geometry_color_pipelines_;
mutable Variants<YUVToRGBFilterPipeline> yuv_to_rgb_filter_pipelines_;
mutable Variants<PorterDuffBlendPipeline> porter_duff_blend_pipelines_;
// Advanced blends.
mutable Variants<BlendColorPipeline> blend_color_pipelines_;
mutable Variants<BlendColorBurnPipeline> blend_colorburn_pipelines_;
Expand Down
166 changes: 148 additions & 18 deletions impeller/entity/contents/filters/blend_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ static std::optional<Entity> AdvancedBlend(
entity.GetBlendMode(), entity.GetStencilDepth());
}

std::optional<Entity> BlendFilterContents::CreateForegroundBlend(
std::optional<Entity> BlendFilterContents::CreateForegroundAdvancedBlend(
const std::shared_ptr<FilterInput>& input,
const ContentContext& renderer,
const Entity& entity,
Expand Down Expand Up @@ -318,21 +318,145 @@ std::optional<Entity> BlendFilterContents::CreateForegroundBlend(

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

// If there is pending opacity but it was not absorbed by this entity, we have
// to convert this back to a snapshot so it can be passed on. This generally
// implies that there is another filter about to run, so we'd perform this
// operation anyway.
auto potential_opacity = alpha.value_or(1.0) * dst_snapshot->opacity;
if (!absorb_opacity && potential_opacity < 1.0) {
auto result_snapshot = contents->RenderToSnapshot(renderer, entity);
if (!result_snapshot.has_value()) {
return std::nullopt;
}
result_snapshot->opacity = potential_opacity;
return Entity::FromSnapshot(result_snapshot.value(), entity.GetBlendMode(),
Entity sub_entity;
sub_entity.SetContents(std::move(contents));
sub_entity.SetStencilDepth(entity.GetStencilDepth());
sub_entity.SetTransformation(entity.GetTransformation());

return sub_entity;
}

constexpr std::array<std::array<Scalar, 5>, 15> kPorterDuffCoefficients = {{
{0, 0, 0, 0, 0}, // Clear
{1, 0, 0, 0, 0}, // Source
{0, 0, 1, 0, 0}, // Destination
{1, 0, 1, -1, 0}, // SourceOver
{1, -1, 1, 0, 0}, // DestinationOver
{0, 1, 0, 0, 0}, // SourceIn
{0, 0, 0, 1, 0}, // DestinationIn
{1, -1, 0, 0, 0}, // SourceOut
{0, 0, 1, -1, 0}, // DestinationOut
{0, 1, 1, -1, 0}, // SourceATop
{1, -1, 0, 1, 0}, // DestinationATop
{1, -1, 1, -1, 0}, // Xor
{1, 0, 1, 0, 0}, // Plus
{0, 0, 0, 0, 1}, // Modulate
{0, 0, 1, 0, -1}, // Screen
}};

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,
bool absorb_opacity) const {
auto dst_snapshot = input->GetSnapshot(renderer, entity);
if (!dst_snapshot.has_value()) {
return std::nullopt;
}

if (blend_mode == BlendMode::kClear) {
return std::nullopt;
}

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

if (blend_mode == BlendMode::kSource) {
auto contents = std::make_shared<SolidColorContents>();
contents->SetGeometry(Geometry::MakeRect(coverage));
contents->SetColor(foreground_color);

Entity foreground_entity;
foreground_entity.SetBlendMode(entity.GetBlendMode());
foreground_entity.SetStencilDepth(entity.GetStencilDepth());
foreground_entity.SetContents(std::move(contents));
return foreground_entity;
}

RenderProc render_proc = [foreground_color, coverage, 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 = pass.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.size;
auto origin = coverage.origin;
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.AddVertices({
{origin, dst_uvs[0]},
{Point(origin.x + size.width, origin.y), dst_uvs[1]},
{Point(origin.x + size.width, origin.y + size.height), dst_uvs[3]},
{origin, dst_uvs[0]},
{Point(origin.x + size.width, origin.y + size.height), dst_uvs[3]},
{Point(origin.x, origin.y + size.height), dst_uvs[2]},
});
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);

Command cmd;
cmd.label = "Foreground PorterDuff Blend Filter";
cmd.BindVertices(vtx_buffer);
cmd.stencil_reference = entity.GetStencilDepth();
auto options = OptionsFromPass(pass);
cmd.pipeline = renderer.GetPorterDuffBlendPipeline(options);

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

auto dst_sampler_descriptor = dst_snapshot->sampler_descriptor;
if (renderer.GetDeviceCapabilities().SupportsDecalTileMode()) {
dst_sampler_descriptor.width_address_mode = SamplerAddressMode::kDecal;
dst_sampler_descriptor.height_address_mode = SamplerAddressMode::kDecal;
}
auto dst_sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler(
dst_sampler_descriptor);
FS::BindTextureSamplerDst(cmd, dst_snapshot->texture, dst_sampler);
frame_info.texture_sampler_y_coord_scale =
dst_snapshot->texture->GetYCoordScale();

frag_info.color = foreground_color.Premultiply();
frag_info.input_alpha =
absorb_opacity ? dst_snapshot->opacity * alpha.value_or(1.0) : 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(cmd, host_buffer.EmplaceUniform(frag_info));

frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize());

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

return pass.AddCommand(cmd);
};

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

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

Entity sub_entity;
sub_entity.SetContents(std::move(contents));
sub_entity.SetStencilDepth(entity.GetStencilDepth());
Expand Down Expand Up @@ -531,17 +655,23 @@ std::optional<Entity> BlendFilterContents::RenderFilter(
}

if (blend_mode_ <= Entity::kLastPipelineBlendMode) {
if (inputs.size() == 1 && foreground_color_.has_value() &&
GetAbsorbOpacity()) {
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());
}

if (blend_mode_ <= Entity::kLastAdvancedBlendMode) {
if (inputs.size() == 1 && foreground_color_.has_value()) {
return CreateForegroundBlend(inputs[0], renderer, entity, coverage,
foreground_color_.value(), blend_mode_,
GetAlpha(), GetAbsorbOpacity());
if (inputs.size() == 1 && foreground_color_.has_value() &&
GetAbsorbOpacity()) {
return CreateForegroundAdvancedBlend(
inputs[0], renderer, entity, coverage, foreground_color_.value(),
blend_mode_, GetAlpha(), GetAbsorbOpacity());
}

return advanced_blend_proc_(inputs, renderer, entity, coverage,
foreground_color_, GetAbsorbOpacity(),
GetAlpha());
Expand Down
16 changes: 15 additions & 1 deletion impeller/entity/contents/filters/blend_filter_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,21 @@ class BlendFilterContents : public ColorFilterContents {
/// only a single input and a foreground color.
///
/// These contents cannot absorb opacity.
std::optional<Entity> CreateForegroundBlend(
std::optional<Entity> CreateForegroundAdvancedBlend(
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,
bool 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,
Expand Down
6 changes: 3 additions & 3 deletions impeller/entity/contents/tiled_texture_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ SamplerDescriptor TiledTextureContents::CreateDescriptor(

bool TiledTextureContents::UsesEmulatedTileMode(
const Capabilities& capabilities) const {
return TileModeToAddressMode(x_tile_mode_, capabilities).has_value() &&
TileModeToAddressMode(y_tile_mode_, capabilities).has_value();
return !TileModeToAddressMode(x_tile_mode_, capabilities).has_value() ||
!TileModeToAddressMode(y_tile_mode_, capabilities).has_value();
}

bool TiledTextureContents::Render(const ContentContext& renderer,
Expand Down Expand Up @@ -121,7 +121,7 @@ bool TiledTextureContents::Render(const ContentContext& renderer,
frame_info.texture_sampler_y_coord_scale = texture_->GetYCoordScale();

Command cmd;
cmd.label = "TiledTextureFill";
cmd.label = uses_emulated_tile_mode ? "TiledTextureFill" : "TextureFill";
cmd.stencil_reference = entity.GetStencilDepth();

auto options = OptionsFromPassAndEntity(pass, entity);
Expand Down
Loading