Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 6013a70

Browse files
author
Jonah Williams
authored
[Impeller] add a generic porter duff blend foreground shader. (#41098)
The pipeline blend component of flutter/flutter#124025 . This should also land alongside flutter/flutter#124640 which was necessary before we had completely optimized the image filters for texture inputs. Fixes flutter/flutter#121650 Fixes flutter/flutter#124025
1 parent ab93d80 commit 6013a70

12 files changed

Lines changed: 500 additions & 24 deletions

File tree

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,6 +1278,7 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_
12781278
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_saturation.frag + ../../../flutter/LICENSE
12791279
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_screen.frag + ../../../flutter/LICENSE
12801280
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_softlight.frag + ../../../flutter/LICENSE
1281+
ORIGIN: ../../../flutter/impeller/entity/shaders/blending/porter_duff_blend.frag + ../../../flutter/LICENSE
12811282
ORIGIN: ../../../flutter/impeller/entity/shaders/border_mask_blur.frag + ../../../flutter/LICENSE
12821283
ORIGIN: ../../../flutter/impeller/entity/shaders/border_mask_blur.vert + ../../../flutter/LICENSE
12831284
ORIGIN: ../../../flutter/impeller/entity/shaders/color_matrix_color_filter.frag + ../../../flutter/LICENSE
@@ -3867,6 +3868,7 @@ FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_ov
38673868
FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_saturation.frag
38683869
FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_screen.frag
38693870
FILE: ../../../flutter/impeller/entity/shaders/blending/ios/framebuffer_blend_softlight.frag
3871+
FILE: ../../../flutter/impeller/entity/shaders/blending/porter_duff_blend.frag
38703872
FILE: ../../../flutter/impeller/entity/shaders/border_mask_blur.frag
38713873
FILE: ../../../flutter/impeller/entity/shaders/border_mask_blur.vert
38723874
FILE: ../../../flutter/impeller/entity/shaders/color_matrix_color_filter.frag

impeller/aiks/aiks_unittests.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,6 +2035,26 @@ TEST_P(AiksTest, TranslucentSaveLayerWithBlendImageFilterAndDrawsCorrectly) {
20352035
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
20362036
}
20372037

2038+
TEST_P(AiksTest, TranslucentSaveLayerWithColorImageFilterAndDrawsCorrectly) {
2039+
Canvas canvas;
2040+
2041+
canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2042+
2043+
canvas.SaveLayer({
2044+
.color = Color::Black().WithAlpha(0.5),
2045+
.color_filter =
2046+
[](FilterInput::Ref input) {
2047+
return ColorFilterContents::MakeBlend(
2048+
BlendMode::kDestinationOver, {std::move(input)}, Color::Red());
2049+
},
2050+
});
2051+
2052+
canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2053+
canvas.Restore();
2054+
2055+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2056+
}
2057+
20382058
TEST_P(AiksTest, TranslucentSaveLayerImageDrawsCorrectly) {
20392059
Canvas canvas;
20402060

impeller/aiks/paint.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ std::shared_ptr<Contents> Paint::WithFilters(
5959
std::shared_ptr<Contents> input,
6060
std::optional<bool> is_solid_color) const {
6161
bool is_solid_color_val = is_solid_color.value_or(!color_source);
62-
input = WithColorFilter(input);
62+
input = WithColorFilter(input, /*absorb_opacity=*/true);
6363
input = WithInvertFilter(input);
6464
input = WithMaskBlur(input, is_solid_color_val, Matrix());
6565
input = WithImageFilter(input, Matrix(), /*is_subpass=*/false);

impeller/entity/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ impeller_shaders("entity_shaders") {
7070
"shaders/vertices.frag",
7171
"shaders/yuv_to_rgb_filter.frag",
7272
"shaders/yuv_to_rgb_filter.vert",
73+
"shaders/blending/porter_duff_blend.frag",
7374
]
7475
}
7576

impeller/entity/contents/content_context.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
287287
CreateDefaultPipeline<GeometryColorPipeline>(*context_);
288288
yuv_to_rgb_filter_pipelines_[{}] =
289289
CreateDefaultPipeline<YUVToRGBFilterPipeline>(*context_);
290+
porter_duff_blend_pipelines_[{}] =
291+
CreateDefaultPipeline<PorterDuffBlendPipeline>(*context_);
290292

291293
if (solid_fill_pipelines_[{}]->GetDescriptor().has_value()) {
292294
auto clip_pipeline_descriptor =

impeller/entity/contents/content_context.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "impeller/entity/linear_to_srgb_filter.vert.h"
3636
#include "impeller/entity/morphology_filter.frag.h"
3737
#include "impeller/entity/morphology_filter.vert.h"
38+
#include "impeller/entity/porter_duff_blend.frag.h"
3839
#include "impeller/entity/radial_gradient_fill.frag.h"
3940
#include "impeller/entity/rrect_blur.frag.h"
4041
#include "impeller/entity/rrect_blur.vert.h"
@@ -125,7 +126,6 @@ using RadialGradientSSBOFillPipeline =
125126
using SweepGradientSSBOFillPipeline =
126127
RenderPipelineT<GradientFillVertexShader,
127128
SweepGradientSsboFillFragmentShader>;
128-
using BlendPipeline = RenderPipelineT<BlendVertexShader, BlendFragmentShader>;
129129
using RRectBlurPipeline =
130130
RenderPipelineT<RrectBlurVertexShader, RrectBlurFragmentShader>;
131131
using BlendPipeline = RenderPipelineT<BlendVertexShader, BlendFragmentShader>;
@@ -165,6 +165,8 @@ using GlyphAtlasPipeline =
165165
RenderPipelineT<GlyphAtlasVertexShader, GlyphAtlasFragmentShader>;
166166
using GlyphAtlasSdfPipeline =
167167
RenderPipelineT<GlyphAtlasSdfVertexShader, GlyphAtlasSdfFragmentShader>;
168+
using PorterDuffBlendPipeline =
169+
RenderPipelineT<BlendVertexShader, PorterDuffBlendFragmentShader>;
168170
// Instead of requiring new shaders for clips, the solid fill stages are used
169171
// to redirect writing to the stencil instead of color attachments.
170172
using ClipPipeline =
@@ -469,6 +471,11 @@ class ContentContext {
469471
return GetPipeline(yuv_to_rgb_filter_pipelines_, opts);
470472
}
471473

474+
std::shared_ptr<Pipeline<PipelineDescriptor>> GetPorterDuffBlendPipeline(
475+
ContentContextOptions opts) const {
476+
return GetPipeline(porter_duff_blend_pipelines_, opts);
477+
}
478+
472479
// Advanced blends.
473480

474481
std::shared_ptr<Pipeline<PipelineDescriptor>> GetBlendColorPipeline(
@@ -705,6 +712,7 @@ class ContentContext {
705712
mutable Variants<GlyphAtlasSdfPipeline> glyph_atlas_sdf_pipelines_;
706713
mutable Variants<GeometryColorPipeline> geometry_color_pipelines_;
707714
mutable Variants<YUVToRGBFilterPipeline> yuv_to_rgb_filter_pipelines_;
715+
mutable Variants<PorterDuffBlendPipeline> porter_duff_blend_pipelines_;
708716
// Advanced blends.
709717
mutable Variants<BlendColorPipeline> blend_color_pipelines_;
710718
mutable Variants<BlendColorBurnPipeline> blend_colorburn_pipelines_;

impeller/entity/contents/filters/blend_filter_contents.cc

Lines changed: 148 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ static std::optional<Entity> AdvancedBlend(
179179
entity.GetBlendMode(), entity.GetStencilDepth());
180180
}
181181

182-
std::optional<Entity> BlendFilterContents::CreateForegroundBlend(
182+
std::optional<Entity> BlendFilterContents::CreateForegroundAdvancedBlend(
183183
const std::shared_ptr<FilterInput>& input,
184184
const ContentContext& renderer,
185185
const Entity& entity,
@@ -318,21 +318,145 @@ std::optional<Entity> BlendFilterContents::CreateForegroundBlend(
318318

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

321-
// If there is pending opacity but it was not absorbed by this entity, we have
322-
// to convert this back to a snapshot so it can be passed on. This generally
323-
// implies that there is another filter about to run, so we'd perform this
324-
// operation anyway.
325-
auto potential_opacity = alpha.value_or(1.0) * dst_snapshot->opacity;
326-
if (!absorb_opacity && potential_opacity < 1.0) {
327-
auto result_snapshot = contents->RenderToSnapshot(renderer, entity);
328-
if (!result_snapshot.has_value()) {
329-
return std::nullopt;
330-
}
331-
result_snapshot->opacity = potential_opacity;
332-
return Entity::FromSnapshot(result_snapshot.value(), entity.GetBlendMode(),
321+
Entity sub_entity;
322+
sub_entity.SetContents(std::move(contents));
323+
sub_entity.SetStencilDepth(entity.GetStencilDepth());
324+
sub_entity.SetTransformation(entity.GetTransformation());
325+
326+
return sub_entity;
327+
}
328+
329+
constexpr std::array<std::array<Scalar, 5>, 15> kPorterDuffCoefficients = {{
330+
{0, 0, 0, 0, 0}, // Clear
331+
{1, 0, 0, 0, 0}, // Source
332+
{0, 0, 1, 0, 0}, // Destination
333+
{1, 0, 1, -1, 0}, // SourceOver
334+
{1, -1, 1, 0, 0}, // DestinationOver
335+
{0, 1, 0, 0, 0}, // SourceIn
336+
{0, 0, 0, 1, 0}, // DestinationIn
337+
{1, -1, 0, 0, 0}, // SourceOut
338+
{0, 0, 1, -1, 0}, // DestinationOut
339+
{0, 1, 1, -1, 0}, // SourceATop
340+
{1, -1, 0, 1, 0}, // DestinationATop
341+
{1, -1, 1, -1, 0}, // Xor
342+
{1, 0, 1, 0, 0}, // Plus
343+
{0, 0, 0, 0, 1}, // Modulate
344+
{0, 0, 1, 0, -1}, // Screen
345+
}};
346+
347+
std::optional<Entity> BlendFilterContents::CreateForegroundPorterDuffBlend(
348+
const std::shared_ptr<FilterInput>& input,
349+
const ContentContext& renderer,
350+
const Entity& entity,
351+
const Rect& coverage,
352+
Color foreground_color,
353+
BlendMode blend_mode,
354+
std::optional<Scalar> alpha,
355+
bool absorb_opacity) const {
356+
auto dst_snapshot = input->GetSnapshot(renderer, entity);
357+
if (!dst_snapshot.has_value()) {
358+
return std::nullopt;
359+
}
360+
361+
if (blend_mode == BlendMode::kClear) {
362+
return std::nullopt;
363+
}
364+
365+
if (blend_mode == BlendMode::kDestination) {
366+
return Entity::FromSnapshot(dst_snapshot, entity.GetBlendMode(),
333367
entity.GetStencilDepth());
334368
}
335369

370+
if (blend_mode == BlendMode::kSource) {
371+
auto contents = std::make_shared<SolidColorContents>();
372+
contents->SetGeometry(Geometry::MakeRect(coverage));
373+
contents->SetColor(foreground_color);
374+
375+
Entity foreground_entity;
376+
foreground_entity.SetBlendMode(entity.GetBlendMode());
377+
foreground_entity.SetStencilDepth(entity.GetStencilDepth());
378+
foreground_entity.SetContents(std::move(contents));
379+
return foreground_entity;
380+
}
381+
382+
RenderProc render_proc = [foreground_color, coverage, dst_snapshot,
383+
blend_mode, absorb_opacity, alpha](
384+
const ContentContext& renderer,
385+
const Entity& entity, RenderPass& pass) -> bool {
386+
using VS = PorterDuffBlendPipeline::VertexShader;
387+
using FS = PorterDuffBlendPipeline::FragmentShader;
388+
389+
auto& host_buffer = pass.GetTransientsBuffer();
390+
391+
auto maybe_dst_uvs = dst_snapshot->GetCoverageUVs(coverage);
392+
if (!maybe_dst_uvs.has_value()) {
393+
return false;
394+
}
395+
auto dst_uvs = maybe_dst_uvs.value();
396+
397+
auto size = coverage.size;
398+
auto origin = coverage.origin;
399+
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
400+
vtx_builder.AddVertices({
401+
{origin, dst_uvs[0]},
402+
{Point(origin.x + size.width, origin.y), dst_uvs[1]},
403+
{Point(origin.x + size.width, origin.y + size.height), dst_uvs[3]},
404+
{origin, dst_uvs[0]},
405+
{Point(origin.x + size.width, origin.y + size.height), dst_uvs[3]},
406+
{Point(origin.x, origin.y + size.height), dst_uvs[2]},
407+
});
408+
auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer);
409+
410+
Command cmd;
411+
cmd.label = "Foreground PorterDuff Blend Filter";
412+
cmd.BindVertices(vtx_buffer);
413+
cmd.stencil_reference = entity.GetStencilDepth();
414+
auto options = OptionsFromPass(pass);
415+
cmd.pipeline = renderer.GetPorterDuffBlendPipeline(options);
416+
417+
FS::FragInfo frag_info;
418+
VS::FrameInfo frame_info;
419+
420+
auto dst_sampler_descriptor = dst_snapshot->sampler_descriptor;
421+
if (renderer.GetDeviceCapabilities().SupportsDecalTileMode()) {
422+
dst_sampler_descriptor.width_address_mode = SamplerAddressMode::kDecal;
423+
dst_sampler_descriptor.height_address_mode = SamplerAddressMode::kDecal;
424+
}
425+
auto dst_sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler(
426+
dst_sampler_descriptor);
427+
FS::BindTextureSamplerDst(cmd, dst_snapshot->texture, dst_sampler);
428+
frame_info.texture_sampler_y_coord_scale =
429+
dst_snapshot->texture->GetYCoordScale();
430+
431+
frag_info.color = foreground_color.Premultiply();
432+
frag_info.input_alpha =
433+
absorb_opacity ? dst_snapshot->opacity * alpha.value_or(1.0) : 1.0;
434+
435+
auto blend_coefficients =
436+
kPorterDuffCoefficients[static_cast<int>(blend_mode)];
437+
frag_info.src_coeff = blend_coefficients[0];
438+
frag_info.src_coeff_dst_alpha = blend_coefficients[1];
439+
frag_info.dst_coeff = blend_coefficients[2];
440+
frag_info.dst_coeff_src_alpha = blend_coefficients[3];
441+
frag_info.dst_coeff_src_color = blend_coefficients[4];
442+
443+
FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info));
444+
445+
frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize());
446+
447+
auto uniform_view = host_buffer.EmplaceUniform(frame_info);
448+
VS::BindFrameInfo(cmd, uniform_view);
449+
450+
return pass.AddCommand(cmd);
451+
};
452+
453+
CoverageProc coverage_proc =
454+
[coverage](const Entity& entity) -> std::optional<Rect> {
455+
return coverage;
456+
};
457+
458+
auto contents = AnonymousContents::Make(render_proc, coverage_proc);
459+
336460
Entity sub_entity;
337461
sub_entity.SetContents(std::move(contents));
338462
sub_entity.SetStencilDepth(entity.GetStencilDepth());
@@ -531,17 +655,23 @@ std::optional<Entity> BlendFilterContents::RenderFilter(
531655
}
532656

533657
if (blend_mode_ <= Entity::kLastPipelineBlendMode) {
658+
if (inputs.size() == 1 && foreground_color_.has_value() &&
659+
GetAbsorbOpacity()) {
660+
return CreateForegroundPorterDuffBlend(
661+
inputs[0], renderer, entity, coverage, foreground_color_.value(),
662+
blend_mode_, GetAlpha(), GetAbsorbOpacity());
663+
}
534664
return PipelineBlend(inputs, renderer, entity, coverage, blend_mode_,
535665
foreground_color_, GetAbsorbOpacity(), GetAlpha());
536666
}
537667

538668
if (blend_mode_ <= Entity::kLastAdvancedBlendMode) {
539-
if (inputs.size() == 1 && foreground_color_.has_value()) {
540-
return CreateForegroundBlend(inputs[0], renderer, entity, coverage,
541-
foreground_color_.value(), blend_mode_,
542-
GetAlpha(), GetAbsorbOpacity());
669+
if (inputs.size() == 1 && foreground_color_.has_value() &&
670+
GetAbsorbOpacity()) {
671+
return CreateForegroundAdvancedBlend(
672+
inputs[0], renderer, entity, coverage, foreground_color_.value(),
673+
blend_mode_, GetAlpha(), GetAbsorbOpacity());
543674
}
544-
545675
return advanced_blend_proc_(inputs, renderer, entity, coverage,
546676
foreground_color_, GetAbsorbOpacity(),
547677
GetAlpha());

impeller/entity/contents/filters/blend_filter_contents.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,21 @@ class BlendFilterContents : public ColorFilterContents {
4242
/// only a single input and a foreground color.
4343
///
4444
/// These contents cannot absorb opacity.
45-
std::optional<Entity> CreateForegroundBlend(
45+
std::optional<Entity> CreateForegroundAdvancedBlend(
46+
const std::shared_ptr<FilterInput>& input,
47+
const ContentContext& renderer,
48+
const Entity& entity,
49+
const Rect& coverage,
50+
Color foreground_color,
51+
BlendMode blend_mode,
52+
std::optional<Scalar> alpha,
53+
bool absorb_opacity) const;
54+
55+
/// @brief Optimized porter-duff blend that avoids a second subpass when there
56+
/// is only a single input and a foreground color.
57+
///
58+
/// These contents cannot absorb opacity.
59+
std::optional<Entity> CreateForegroundPorterDuffBlend(
4660
const std::shared_ptr<FilterInput>& input,
4761
const ContentContext& renderer,
4862
const Entity& entity,

impeller/entity/contents/tiled_texture_contents.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ SamplerDescriptor TiledTextureContents::CreateDescriptor(
8888

8989
bool TiledTextureContents::UsesEmulatedTileMode(
9090
const Capabilities& capabilities) const {
91-
return TileModeToAddressMode(x_tile_mode_, capabilities).has_value() &&
92-
TileModeToAddressMode(y_tile_mode_, capabilities).has_value();
91+
return !TileModeToAddressMode(x_tile_mode_, capabilities).has_value() ||
92+
!TileModeToAddressMode(y_tile_mode_, capabilities).has_value();
9393
}
9494

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

123123
Command cmd;
124-
cmd.label = "TiledTextureFill";
124+
cmd.label = uses_emulated_tile_mode ? "TiledTextureFill" : "TextureFill";
125125
cmd.stencil_reference = entity.GetStencilDepth();
126126

127127
auto options = OptionsFromPassAndEntity(pass, entity);

0 commit comments

Comments
 (0)