From d916e2c7c7eec919a59c8fe53f025e14545cab74 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Thu, 17 Mar 2022 10:51:28 -0700 Subject: [PATCH] Add blend filter support for advanced blends (#81) --- impeller/entity/BUILD.gn | 4 + impeller/entity/contents/content_context.cc | 4 + impeller/entity/contents/content_context.h | 38 +++- impeller/entity/contents/filter_contents.cc | 180 ++++++++++++++++-- impeller/entity/contents/filter_contents.h | 10 +- impeller/entity/entity.h | 16 +- impeller/entity/entity_unittests.cc | 10 +- impeller/entity/shaders/texture_blend.frag | 13 ++ impeller/entity/shaders/texture_blend.vert | 17 ++ .../entity/shaders/texture_blend_screen.frag | 16 ++ .../entity/shaders/texture_blend_screen.vert | 17 ++ 11 files changed, 298 insertions(+), 27 deletions(-) create mode 100644 impeller/entity/shaders/texture_blend.frag create mode 100644 impeller/entity/shaders/texture_blend.vert create mode 100644 impeller/entity/shaders/texture_blend_screen.frag create mode 100644 impeller/entity/shaders/texture_blend_screen.vert diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 4e12078c2c999..9e5f951d407fd 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -14,6 +14,10 @@ impeller_shaders("entity_shaders") { "shaders/solid_fill.vert", "shaders/solid_stroke.frag", "shaders/solid_stroke.vert", + "shaders/texture_blend.frag", + "shaders/texture_blend.vert", + "shaders/texture_blend_screen.frag", + "shaders/texture_blend_screen.vert", "shaders/texture_fill.frag", "shaders/texture_fill.vert", "shaders/glyph_atlas.frag", diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index 0570a940b89a3..ad22a5e474a39 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -18,6 +18,10 @@ ContentContext::ContentContext(std::shared_ptr context) gradient_fill_pipelines_[{}] = std::make_unique(*context_); solid_fill_pipelines_[{}] = std::make_unique(*context_); + texture_blend_pipelines_[{}] = + std::make_unique(*context_); + texture_blend_screen_pipelines_[{}] = + std::make_unique(*context_); texture_pipelines_[{}] = std::make_unique(*context_); solid_stroke_pipelines_[{}] = std::make_unique(*context_); diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index 5003623f85c4c..7caa6aa01e104 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -9,6 +9,8 @@ #include "flutter/fml/hash_combine.h" #include "flutter/fml/macros.h" +#include "fml/logging.h" +#include "impeller/base/validation.h" #include "impeller/entity/entity.h" #include "impeller/entity/glyph_atlas.frag.h" #include "impeller/entity/glyph_atlas.vert.h" @@ -21,6 +23,10 @@ #include "impeller/entity/texture_fill.frag.h" #include "impeller/entity/texture_fill.vert.h" #include "impeller/renderer/formats.h" +#include "texture_blend.frag.h" +#include "texture_blend.vert.h" +#include "texture_blend_screen.frag.h" +#include "texture_blend_screen.vert.h" namespace impeller { @@ -28,6 +34,10 @@ using GradientFillPipeline = PipelineT; using SolidFillPipeline = PipelineT; +using TextureBlendPipeline = + PipelineT; +using TextureBlendScreenPipeline = + PipelineT; using TexturePipeline = PipelineT; using SolidStrokePipeline = @@ -75,6 +85,16 @@ class ContentContext { return GetPipeline(solid_fill_pipelines_, opts); } + std::shared_ptr GetTextureBlendPipeline( + ContentContextOptions opts) const { + return GetPipeline(texture_blend_pipelines_, opts); + } + + std::shared_ptr GetTextureBlendScreenPipeline( + ContentContextOptions opts) const { + return GetPipeline(texture_blend_screen_pipelines_, opts); + } + std::shared_ptr GetTexturePipeline( ContentContextOptions opts) const { return GetPipeline(texture_pipelines_, opts); @@ -115,6 +135,8 @@ class ContentContext { // map. mutable Variants gradient_fill_pipelines_; mutable Variants solid_fill_pipelines_; + mutable Variants texture_blend_pipelines_; + mutable Variants texture_blend_screen_pipelines_; mutable Variants texture_pipelines_; mutable Variants solid_stroke_pipelines_; mutable Variants clip_pipelines_; @@ -123,12 +145,24 @@ class ContentContext { static void ApplyOptionsToDescriptor(PipelineDescriptor& desc, const ContentContextOptions& options) { + auto blend_mode = options.blend_mode; + if (blend_mode > Entity::BlendMode::kLastPipelineBlendMode) { + VALIDATION_LOG << "Cannot use blend mode " + << static_cast(options.blend_mode) + << " as a pipeline blend."; + blend_mode = Entity::BlendMode::kSourceOver; + } + desc.SetSampleCount(options.sample_count); ColorAttachmentDescriptor color0 = *desc.GetColorAttachmentDescriptor(0u); color0.alpha_blend_op = BlendOperation::kAdd; color0.color_blend_op = BlendOperation::kAdd; - switch (options.blend_mode) { + + static_assert(Entity::BlendMode::kLastPipelineBlendMode == + Entity::BlendMode::kModulate); + + switch (blend_mode) { case Entity::BlendMode::kClear: color0.dst_alpha_blend_factor = BlendFactor::kZero; color0.dst_color_blend_factor = BlendFactor::kZero; @@ -214,6 +248,8 @@ class ContentContext { color0.src_alpha_blend_factor = BlendFactor::kZero; color0.src_color_blend_factor = BlendFactor::kZero; break; + default: + FML_UNREACHABLE(); } desc.SetColorAttachmentDescriptor(0u, std::move(color0)); } diff --git a/impeller/entity/contents/filter_contents.cc b/impeller/entity/contents/filter_contents.cc index d043a6b3334d1..d1c22cfeac04e 100644 --- a/impeller/entity/contents/filter_contents.cc +++ b/impeller/entity/contents/filter_contents.cc @@ -28,10 +28,33 @@ namespace impeller { std::shared_ptr FilterContents::MakeBlend( Entity::BlendMode blend_mode, InputTextures input_textures) { - auto blend = std::make_shared(); - blend->SetInputTextures(input_textures); - blend->SetBlendMode(blend_mode); - return blend; + if (blend_mode > Entity::BlendMode::kLastAdvancedBlendMode) { + VALIDATION_LOG << "Invalid blend mode " << static_cast(blend_mode) + << " passed to FilterContents::MakeBlend."; + return nullptr; + } + + if (input_textures.size() < 2 || + blend_mode <= Entity::BlendMode::kLastPipelineBlendMode) { + auto blend = std::make_shared(); + blend->SetInputTextures(input_textures); + blend->SetBlendMode(blend_mode); + return blend; + } + + if (blend_mode <= Entity::BlendMode::kLastAdvancedBlendMode) { + InputVariant blend = input_textures[0]; + for (auto in_i = input_textures.begin() + 1; in_i < input_textures.end(); + in_i++) { + auto new_blend = std::make_shared(); + new_blend->SetInputTextures({blend, *in_i}); + new_blend->SetBlendMode(blend_mode); + blend = new_blend; + } + return std::get>(blend); + } + + FML_UNREACHABLE(); } FilterContents::FilterContents() = default; @@ -150,20 +173,106 @@ ISize FilterContents::GetOutputSize() const { ******* BlendFilterContents ******************************************************************************/ -BlendFilterContents::BlendFilterContents() = default; +BlendFilterContents::BlendFilterContents() { + SetBlendMode(Entity::BlendMode::kSourceOver); +} BlendFilterContents::~BlendFilterContents() = default; +using PipelineProc = + std::shared_ptr (ContentContext::*)(ContentContextOptions) const; + +template +static void AdvancedBlendPass(std::shared_ptr input_d, + std::shared_ptr input_s, + std::shared_ptr sampler, + const ContentContext& renderer, + RenderPass& pass, + Command& cmd) {} + +template +static bool AdvancedBlend( + const std::vector>& input_textures, + const ContentContext& renderer, + RenderPass& pass, + PipelineProc pipeline_proc) { + if (input_textures.size() < 2) { + return false; + } + + auto& host_buffer = pass.GetTransientsBuffer(); + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0), Point(0, 0)}, + {Point(1, 0), Point(1, 0)}, + {Point(1, 1), Point(1, 1)}, + {Point(0, 0), Point(0, 0)}, + {Point(1, 1), Point(1, 1)}, + {Point(0, 1), Point(0, 1)}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + + typename VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); + + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + std::shared_ptr pipeline = + std::invoke(pipeline_proc, renderer, options); + + Command cmd; + cmd.label = "Advanced Blend Filter"; + cmd.BindVertices(vtx_buffer); + cmd.pipeline = std::move(pipeline); + VS::BindFrameInfo(cmd, uniform_view); + + FS::BindTextureSamplerDst(cmd, input_textures[0], sampler); + FS::BindTextureSamplerSrc(cmd, input_textures[1], sampler); + pass.AddCommand(cmd); + + return true; +} + void BlendFilterContents::SetBlendMode(Entity::BlendMode blend_mode) { + if (blend_mode > Entity::BlendMode::kLastAdvancedBlendMode) { + VALIDATION_LOG << "Invalid blend mode " << static_cast(blend_mode) + << " assigned to BlendFilterContents."; + } + blend_mode_ = blend_mode; + + if (blend_mode > Entity::BlendMode::kLastPipelineBlendMode) { + static_assert(Entity::BlendMode::kLastAdvancedBlendMode == + Entity::BlendMode::kScreen); + + switch (blend_mode) { + case Entity::BlendMode::kScreen: + advanced_blend_proc_ = + [](const std::vector>& input_textures, + const ContentContext& renderer, RenderPass& pass) { + PipelineProc p = &ContentContext::GetTextureBlendScreenPipeline; + return AdvancedBlend( + input_textures, renderer, pass, p); + }; + break; + default: + FML_UNREACHABLE(); + } + } } -bool BlendFilterContents::RenderFilter( +static bool BasicBlend( const std::vector>& input_textures, const ContentContext& renderer, - RenderPass& pass) const { - using VS = TexturePipeline::VertexShader; - using FS = TexturePipeline::FragmentShader; + RenderPass& pass, + Entity::BlendMode basic_blend) { + using VS = TextureBlendPipeline::VertexShader; + using FS = TextureBlendPipeline::FragmentShader; auto& host_buffer = pass.GetTransientsBuffer(); @@ -180,24 +289,63 @@ bool BlendFilterContents::RenderFilter( VS::FrameInfo frame_info; frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); - frame_info.alpha = 1; auto uniform_view = host_buffer.EmplaceUniform(frame_info); auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + // Draw the first texture using kSource. + Command cmd; - cmd.label = "Blend Filter"; - auto options = OptionsFromPass(pass); - options.blend_mode = blend_mode_; - cmd.pipeline = renderer.GetTexturePipeline(options); + cmd.label = "Basic Blend Filter"; cmd.BindVertices(vtx_buffer); + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetTextureBlendPipeline(options); + FS::BindTextureSamplerSrc(cmd, input_textures[0], sampler); VS::BindFrameInfo(cmd, uniform_view); - for (const auto& texture : input_textures) { - FS::BindTextureSampler(cmd, texture, sampler); + pass.AddCommand(cmd); + + if (input_textures.size() < 2) { + return true; + } + + // Write subsequent textures using the selected blend mode. + + options.blend_mode = basic_blend; + cmd.pipeline = renderer.GetTextureBlendPipeline(options); + + for (auto texture_i = input_textures.begin() + 1; + texture_i < input_textures.end(); texture_i++) { + FS::BindTextureSamplerSrc(cmd, *texture_i, sampler); pass.AddCommand(cmd); } return true; } +bool BlendFilterContents::RenderFilter( + const std::vector>& input_textures, + const ContentContext& renderer, + RenderPass& pass) const { + if (input_textures.empty()) { + return true; + } + + if (input_textures.size() == 1) { + // Nothing to blend. + return BasicBlend(input_textures, renderer, pass, + Entity::BlendMode::kSource); + } + + if (blend_mode_ <= Entity::BlendMode::kLastPipelineBlendMode) { + return BasicBlend(input_textures, renderer, pass, blend_mode_); + } + + if (blend_mode_ <= Entity::BlendMode::kLastAdvancedBlendMode) { + return advanced_blend_proc_(input_textures, renderer, pass); + } + + FML_UNREACHABLE(); +} + } // namespace impeller diff --git a/impeller/entity/contents/filter_contents.h b/impeller/entity/contents/filter_contents.h index b1839cff4efbd..bcee31d0e55a6 100644 --- a/impeller/entity/contents/filter_contents.h +++ b/impeller/entity/contents/filter_contents.h @@ -14,6 +14,8 @@ namespace impeller { +class Pipeline; + /******************************************************************************* ******* FilterContents ******************************************************************************/ @@ -75,6 +77,11 @@ class FilterContents : public Contents { class BlendFilterContents : public FilterContents { public: + using AdvancedBlendProc = std::function>& input_textures, + const ContentContext& renderer, + RenderPass& pass)>; + BlendFilterContents(); ~BlendFilterContents() override; @@ -86,7 +93,8 @@ class BlendFilterContents : public FilterContents { const ContentContext& renderer, RenderPass& pass) const override; - Entity::BlendMode blend_mode_ = Entity::BlendMode::kSourceOver; + Entity::BlendMode blend_mode_; + AdvancedBlendProc advanced_blend_proc_; FML_DISALLOW_COPY_AND_ASSIGN(BlendFilterContents); }; diff --git a/impeller/entity/entity.h b/impeller/entity/entity.h index 36d965f36e28e..efe072f64dca6 100644 --- a/impeller/entity/entity.h +++ b/impeller/entity/entity.h @@ -4,6 +4,7 @@ #pragma once +#include #include "impeller/entity/contents/contents.h" #include "impeller/geometry/color.h" #include "impeller/geometry/matrix.h" @@ -18,10 +19,11 @@ class RenderPass; class Entity { public: - /// All pipeline blend mode presets assume that both the source (fragment - /// output) and destination (first color attachment) have colors with - /// premultiplied alpha. + /// All blend modes assume that both the source (fragment output) and + /// destination (first color attachment) have colors with premultiplied alpha. enum class BlendMode { + // The following blend modes are able to be used as pipeline blend modes or + // via `BlendFilterContents`. kClear, kSource, kDestination, @@ -36,6 +38,14 @@ class Entity { kXor, kPlus, kModulate, + + // The following blend modes use equations that are not available for + // pipelines on most graphics devices without extensions, and so they are + // only able to be used via `BlendFilterContents`. + kScreen, + + kLastPipelineBlendMode = kModulate, + kLastAdvancedBlendMode = kScreen, }; Entity(); diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 87cf91a69056f..f20f811c26c77 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -657,13 +657,11 @@ TEST_F(EntityTest, Filters) { ASSERT_TRUE(bridge && boston && kalimba); auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - // Draws kalimba and overwrites it with boston. - auto blend0 = FilterContents::MakeBlend( - Entity::BlendMode::kSourceOver, {kalimba, boston}); + auto blend0 = FilterContents::MakeBlend(Entity::BlendMode::kModulate, + {kalimba, boston}); - // Adds bridge*3 to boston. - auto blend1 = FilterContents::MakeBlend( - Entity::BlendMode::kPlus, {bridge, bridge, blend0, bridge}); + auto blend1 = FilterContents::MakeBlend(Entity::BlendMode::kScreen, + {bridge, blend0, bridge, bridge}); Entity entity; entity.SetPath(PathBuilder{}.AddRect({100, 100, 300, 300}).TakePath()); diff --git a/impeller/entity/shaders/texture_blend.frag b/impeller/entity/shaders/texture_blend.frag new file mode 100644 index 0000000000000..debce522d6b43 --- /dev/null +++ b/impeller/entity/shaders/texture_blend.frag @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +uniform sampler2D texture_sampler_src; + +in vec2 v_texture_coords; + +out vec4 frag_color; + +void main() { + frag_color = texture(texture_sampler_src, v_texture_coords); +} diff --git a/impeller/entity/shaders/texture_blend.vert b/impeller/entity/shaders/texture_blend.vert new file mode 100644 index 0000000000000..daa30f5650a3f --- /dev/null +++ b/impeller/entity/shaders/texture_blend.vert @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +uniform FrameInfo { + mat4 mvp; +} frame_info; + +in vec2 vertices; +in vec2 texture_coords; + +out vec2 v_texture_coords; + +void main() { + gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); + v_texture_coords = texture_coords; +} diff --git a/impeller/entity/shaders/texture_blend_screen.frag b/impeller/entity/shaders/texture_blend_screen.frag new file mode 100644 index 0000000000000..4594592efff9c --- /dev/null +++ b/impeller/entity/shaders/texture_blend_screen.frag @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +uniform sampler2D texture_sampler_dst; +uniform sampler2D texture_sampler_src; + +in vec2 v_texture_coords; + +out vec4 frag_color; + +void main() { + vec4 dst = texture(texture_sampler_dst, v_texture_coords); + vec4 src = texture(texture_sampler_src, v_texture_coords); + frag_color = src + dst - src * dst; +} diff --git a/impeller/entity/shaders/texture_blend_screen.vert b/impeller/entity/shaders/texture_blend_screen.vert new file mode 100644 index 0000000000000..daa30f5650a3f --- /dev/null +++ b/impeller/entity/shaders/texture_blend_screen.vert @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +uniform FrameInfo { + mat4 mvp; +} frame_info; + +in vec2 vertices; +in vec2 texture_coords; + +out vec2 v_texture_coords; + +void main() { + gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); + v_texture_coords = texture_coords; +}