diff --git a/impeller/aiks/aiks_blend_unittests.cc b/impeller/aiks/aiks_blend_unittests.cc index 396fb1ccb803f..875c9fbca59aa 100644 --- a/impeller/aiks/aiks_blend_unittests.cc +++ b/impeller/aiks/aiks_blend_unittests.cc @@ -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 @@ -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(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(texture), {200, 200}, + {.color_filter = ColorFilter::MakeBlend( + BlendMode::kColorDodge, Color::Orange())}); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/entity/contents/filters/blend_filter_contents.cc b/impeller/entity/contents/filters/blend_filter_contents.cc index 968fddbf7082d..28320d43e8892 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.cc +++ b/impeller/entity/contents/filters/blend_filter_contents.cc @@ -259,30 +259,22 @@ std::optional 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 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); @@ -361,6 +353,9 @@ std::optional 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) @@ -376,8 +371,6 @@ std::optional 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); @@ -397,6 +390,110 @@ std::optional BlendFilterContents::CreateForegroundAdvancedBlend( return sub_entity; } +std::optional BlendFilterContents::CreateForegroundPorterDuffBlend( + const std::shared_ptr& input, + const ContentContext& renderer, + const Entity& entity, + const Rect& coverage, + Color foreground_color, + BlendMode blend_mode, + std::optional 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 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& 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(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 { + 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 PipelineBlend( const FilterInput::Vector& inputs, const ContentContext& renderer, @@ -626,6 +723,12 @@ std::optional 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()); } diff --git a/impeller/entity/contents/filters/blend_filter_contents.h b/impeller/entity/contents/filters/blend_filter_contents.h index 5ecdd780c53d2..eb47013b35510 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.h +++ b/impeller/entity/contents/filters/blend_filter_contents.h @@ -78,6 +78,20 @@ class BlendFilterContents : public ColorFilterContents { std::optional 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 CreateForegroundPorterDuffBlend( + const std::shared_ptr& input, + const ContentContext& renderer, + const Entity& entity, + const Rect& coverage, + Color foreground_color, + BlendMode blend_mode, + std::optional alpha, + ColorFilterContents::AbsorbOpacity absorb_opacity) const; + BlendMode blend_mode_ = BlendMode::kSourceOver; std::optional foreground_color_; diff --git a/impeller/renderer/snapshot.h b/impeller/renderer/snapshot.h index 849b7ce04ca62..41c5ff08bcdff 100644 --- a/impeller/renderer/snapshot.h +++ b/impeller/renderer/snapshot.h @@ -9,7 +9,6 @@ #include #include -#include "flutter/fml/macros.h" #include "impeller/core/formats.h" #include "impeller/core/sampler_descriptor.h" #include "impeller/core/texture.h" diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt index 871ad5a194190..688d0d6b9cc09 100644 --- a/testing/impeller_golden_tests_output.txt +++ b/testing/impeller_golden_tests_output.txt @@ -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