diff --git a/impeller/entity/contents/filters/blend_filter_contents.cc b/impeller/entity/contents/filters/blend_filter_contents.cc index 1548d80c82da3..c5e7c8c63cf8d 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.cc +++ b/impeller/entity/contents/filters/blend_filter_contents.cc @@ -9,6 +9,7 @@ #include #include "impeller/core/formats.h" +#include "impeller/entity/contents/anonymous_contents.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/contents.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" @@ -178,6 +179,168 @@ static std::optional AdvancedBlend( entity.GetBlendMode(), entity.GetStencilDepth()); } +std::optional BlendFilterContents::CreateForegroundBlend( + const std::shared_ptr& input, + const ContentContext& renderer, + const Entity& entity, + const Rect& coverage, + Color foreground_color, + BlendMode blend_mode, + std::optional alpha, + bool absorb_opacity) const { + auto dst_snapshot = input->GetSnapshot(renderer, entity); + if (!dst_snapshot.has_value()) { + 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 { + using VS = BlendScreenPipeline::VertexShader; + using FS = BlendScreenPipeline::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 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 + size.width, origin.y + size.height), dst_uvs[3], + dst_uvs[3]}, + {origin, dst_uvs[0], dst_uvs[0]}, + {Point(origin.x + size.width, origin.y + size.height), dst_uvs[3], + dst_uvs[3]}, + {Point(origin.x, origin.y + size.height), dst_uvs[2], dst_uvs[2]}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + + Command cmd; + cmd.label = "Foreground Advanced Blend Filter"; + cmd.BindVertices(vtx_buffer); + cmd.stencil_reference = entity.GetStencilDepth(); + auto options = OptionsFromPass(pass); + + switch (blend_mode) { + case BlendMode::kScreen: + cmd.pipeline = renderer.GetBlendScreenPipeline(options); + break; + case BlendMode::kOverlay: + cmd.pipeline = renderer.GetBlendOverlayPipeline(options); + break; + case BlendMode::kDarken: + cmd.pipeline = renderer.GetBlendDarkenPipeline(options); + break; + case BlendMode::kLighten: + cmd.pipeline = renderer.GetBlendLightenPipeline(options); + break; + case BlendMode::kColorDodge: + cmd.pipeline = renderer.GetBlendColorDodgePipeline(options); + break; + case BlendMode::kColorBurn: + cmd.pipeline = renderer.GetBlendColorBurnPipeline(options); + break; + case BlendMode::kHardLight: + cmd.pipeline = renderer.GetBlendHardLightPipeline(options); + break; + case BlendMode::kSoftLight: + cmd.pipeline = renderer.GetBlendSoftLightPipeline(options); + break; + case BlendMode::kDifference: + cmd.pipeline = renderer.GetBlendDifferencePipeline(options); + break; + case BlendMode::kExclusion: + cmd.pipeline = renderer.GetBlendExclusionPipeline(options); + break; + case BlendMode::kMultiply: + cmd.pipeline = renderer.GetBlendMultiplyPipeline(options); + break; + case BlendMode::kHue: + cmd.pipeline = renderer.GetBlendHuePipeline(options); + break; + case BlendMode::kSaturation: + cmd.pipeline = renderer.GetBlendSaturationPipeline(options); + break; + case BlendMode::kColor: + cmd.pipeline = renderer.GetBlendColorPipeline(options); + break; + case BlendMode::kLuminosity: + cmd.pipeline = renderer.GetBlendLuminosityPipeline(options); + break; + default: + return false; + } + + FS::BlendInfo blend_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.dst_y_coord_scale = dst_snapshot->texture->GetYCoordScale(); + blend_info.dst_input_alpha = + absorb_opacity ? dst_snapshot->opacity * alpha.value_or(1.0) : 1.0; + + blend_info.color_factor = 1; + blend_info.color = foreground_color; + // This texture will not be sampled from due to the color factor. But + // this is present so that validation doesn't trip on a missing + // binding. + FS::BindTextureSamplerSrc(cmd, dst_snapshot->texture, dst_sampler); + + auto blend_uniform = host_buffer.EmplaceUniform(blend_info); + FS::BindBlendInfo(cmd, blend_uniform); + + 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 { + return coverage; + }; + + 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.GetStencilDepth()); + } + + Entity sub_entity; + sub_entity.SetContents(std::move(contents)); + sub_entity.SetStencilDepth(entity.GetStencilDepth()); + sub_entity.SetTransformation(entity.GetTransformation()); + + return sub_entity; +} + static std::optional PipelineBlend( const FilterInput::Vector& inputs, const ContentContext& renderer, @@ -373,6 +536,12 @@ std::optional BlendFilterContents::RenderFilter( } 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()); + } + return advanced_blend_proc_(inputs, renderer, entity, coverage, 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 1b7f51a839719..30bb6d401eb1f 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.h +++ b/impeller/entity/contents/filters/blend_filter_contents.h @@ -38,6 +38,20 @@ class BlendFilterContents : public ColorFilterContents { const Matrix& effect_transform, const Rect& coverage) const override; + /// @brief Optimized advanced blend that avoids a second subpass when there is + /// only a single input and a foreground color. + /// + /// These contents cannot absorb opacity. + std::optional CreateForegroundBlend( + const std::shared_ptr& input, + const ContentContext& renderer, + const Entity& entity, + const Rect& coverage, + Color foreground_color, + BlendMode blend_mode, + std::optional alpha, + bool absorb_opacity) const; + BlendMode blend_mode_ = BlendMode::kSourceOver; AdvancedBlendProc advanced_blend_proc_; std::optional foreground_color_;