From d0b9f2182b1ef26c4da6a95bc9f248fe4ca4d0e3 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Mon, 7 Mar 2022 16:35:20 -0800 Subject: [PATCH] Add pipeline blend modes & demo (#55) --- impeller/aiks/canvas.cc | 2 +- impeller/aiks/paint.cc | 4 +- impeller/entity/contents/content_context.h | 45 +++++++- impeller/entity/entity.h | 11 ++ impeller/entity/entity_unittests.cc | 116 ++++++++++++++++++++- 5 files changed, 170 insertions(+), 8 deletions(-) diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 311655ffdfaa6..775d80a8eee19 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -266,7 +266,7 @@ void Canvas::DrawTextFrame(TextFrame text_frame, auto text_contents = std::make_shared(); text_contents->SetTextFrame(std::move(text_frame)); text_contents->SetGlyphAtlas(std::move(atlas)); - text_contents->SetColor(paint.color); + text_contents->SetColor(paint.color.Premultiply()); Entity entity; entity.SetTransformation(GetCurrentTransformation() * diff --git a/impeller/aiks/paint.cc b/impeller/aiks/paint.cc index 06de4e6399f9a..d79a67b71453a 100644 --- a/impeller/aiks/paint.cc +++ b/impeller/aiks/paint.cc @@ -16,12 +16,12 @@ std::shared_ptr Paint::CreateContentsForEntity() const { switch (style) { case Style::kFill: { auto solid_color = std::make_shared(); - solid_color->SetColor(color); + solid_color->SetColor(color.Premultiply()); return solid_color; } case Style::kStroke: { auto solid_stroke = std::make_shared(); - solid_stroke->SetColor(color); + solid_stroke->SetColor(color.Premultiply()); solid_stroke->SetStrokeSize(stroke_width); return solid_stroke; } diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index 7217bf3344f19..0a3db38541c8e 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -19,6 +19,8 @@ #include "flutter/impeller/entity/solid_stroke.vert.h" #include "flutter/impeller/entity/texture_fill.frag.h" #include "flutter/impeller/entity/texture_fill.vert.h" +#include "impeller/entity/entity.h" +#include "impeller/renderer/formats.h" namespace impeller { @@ -38,17 +40,19 @@ using ClipPipeline = PipelineT; struct ContentContextOptions { SampleCount sample_count = SampleCount::kCount1; + Entity::BlendMode blend_mode = Entity::BlendMode::kSource; struct Hash { constexpr std::size_t operator()(const ContentContextOptions& o) const { - return fml::HashCombine(o.sample_count); + return fml::HashCombine(o.sample_count, o.blend_mode); } }; struct Equal { constexpr bool operator()(const ContentContextOptions& lhs, const ContentContextOptions& rhs) const { - return lhs.sample_count == rhs.sample_count; + return lhs.sample_count == rhs.sample_count && + lhs.blend_mode == rhs.blend_mode; } }; }; @@ -120,6 +124,43 @@ class ContentContext { static void ApplyOptionsToDescriptor(PipelineDescriptor& desc, const ContentContextOptions& options) { 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) { + case Entity::BlendMode::kClear: + color0.dst_alpha_blend_factor = BlendFactor::kZero; + color0.dst_color_blend_factor = BlendFactor::kZero; + color0.src_alpha_blend_factor = BlendFactor::kZero; + color0.src_color_blend_factor = BlendFactor::kZero; + break; + case Entity::BlendMode::kSource: + color0.dst_alpha_blend_factor = BlendFactor::kZero; + color0.dst_color_blend_factor = BlendFactor::kZero; + color0.src_alpha_blend_factor = BlendFactor::kSourceAlpha; + color0.src_color_blend_factor = BlendFactor::kOne; + break; + case Entity::BlendMode::kDestination: + color0.dst_alpha_blend_factor = BlendFactor::kDestinationAlpha; + color0.dst_color_blend_factor = BlendFactor::kOne; + color0.src_alpha_blend_factor = BlendFactor::kZero; + color0.src_color_blend_factor = BlendFactor::kZero; + break; + case Entity::BlendMode::kSourceOver: + color0.dst_alpha_blend_factor = BlendFactor::kOneMinusSourceAlpha; + color0.dst_color_blend_factor = BlendFactor::kOneMinusSourceAlpha; + color0.src_alpha_blend_factor = BlendFactor::kSourceAlpha; + color0.src_color_blend_factor = BlendFactor::kOne; + break; + case Entity::BlendMode::kDestinationOver: + color0.dst_alpha_blend_factor = BlendFactor::kDestinationAlpha; + color0.dst_color_blend_factor = BlendFactor::kOne; + color0.src_alpha_blend_factor = BlendFactor::kOneMinusDestinationAlpha; + color0.src_color_blend_factor = BlendFactor::kOneMinusDestinationAlpha; + break; + } + desc.SetColorAttachmentDescriptor(0u, std::move(color0)); } template diff --git a/impeller/entity/entity.h b/impeller/entity/entity.h index 4cad573c49136..c43b549df6316 100644 --- a/impeller/entity/entity.h +++ b/impeller/entity/entity.h @@ -18,6 +18,17 @@ 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. + enum class BlendMode { + kClear, + kSource, + kDestination, + kSourceOver, + kDestinationOver, + }; + Entity(); ~Entity(); diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index f442fa6907ccc..149ded9942af5 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -10,6 +10,8 @@ #include "impeller/geometry/path_builder.h" #include "impeller/playground/playground.h" #include "impeller/playground/widgets.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/vertex_buffer_builder.h" #include "third_party/imgui/imgui.h" namespace impeller { @@ -42,7 +44,7 @@ TEST_F(EntityTest, ThreeStrokesInOnePath) { Entity entity; entity.SetPath(path); auto contents = std::make_unique(); - contents->SetColor(Color::Red()); + contents->SetColor(Color::Red().Premultiply()); contents->SetStrokeSize(5.0); entity.SetContents(std::move(contents)); ASSERT_TRUE(OpenPlaygroundHere(entity)); @@ -72,7 +74,7 @@ TEST_F(EntityTest, TriangleInsideASquare) { Entity entity; entity.SetPath(path); auto contents = std::make_unique(); - contents->SetColor(Color::Red()); + contents->SetColor(Color::Red().Premultiply()); contents->SetStrokeSize(20.0); entity.SetContents(std::move(contents)); @@ -110,7 +112,7 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) { auto create_contents = [width = width](SolidStrokeContents::Cap cap, SolidStrokeContents::Join join) { auto contents = std::make_unique(); - contents->SetColor(Color::Red()); + contents->SetColor(Color::Red().Premultiply()); contents->SetStrokeSize(width); contents->SetStrokeCap(cap); contents->SetStrokeJoin(join); @@ -491,5 +493,113 @@ TEST_F(EntityTest, SolidStrokeContentsSetMiter) { ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 8); } +TEST_F(EntityTest, BlendingModeOptions) { + std::vector blend_mode_names; + std::vector blend_mode_values; + { + // Force an exhausiveness check with a switch. When adding blend modes, + // update this switch with a new name/value to to make it selectable in the + // test GUI. + + const Entity::BlendMode b{}; + static_assert( + b == Entity::BlendMode::kClear); // Ensure the first item in + // the switch is the first + // item in the enum. + switch (b) { + case Entity::BlendMode::kClear: + blend_mode_names.push_back("Clear"); + blend_mode_values.push_back(Entity::BlendMode::kClear); + case Entity::BlendMode::kSource: + blend_mode_names.push_back("Source"); + blend_mode_values.push_back(Entity::BlendMode::kSource); + case Entity::BlendMode::kDestination: + blend_mode_names.push_back("Destination"); + blend_mode_values.push_back(Entity::BlendMode::kDestination); + case Entity::BlendMode::kSourceOver: + blend_mode_names.push_back("SourceOver"); + blend_mode_values.push_back(Entity::BlendMode::kSourceOver); + case Entity::BlendMode::kDestinationOver: + blend_mode_names.push_back("DestinationOver"); + blend_mode_values.push_back( + Entity::BlendMode::kDestinationOver); + }; + } + + bool first_frame = true; + auto callback = [&](ContentContext& context, RenderPass& pass) { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({350, 200}); + ImGui::SetNextWindowPos({200, 450}); + } + + auto draw_rect = [&context, &pass]( + Rect rect, Color color, + Entity::BlendMode blend_mode) -> bool { + using VS = SolidFillPipeline::VertexShader; + VertexBufferBuilder vtx_builder; + { + auto r = rect.GetLTRB(); + vtx_builder.AddVertices({ + {Point(r[0], r[1])}, + {Point(r[2], r[1])}, + {Point(r[2], r[3])}, + {Point(r[0], r[1])}, + {Point(r[2], r[3])}, + {Point(r[0], r[3])}, + }); + } + + Command cmd; + cmd.label = "Blended Rectangle"; + auto options = OptionsFromPass(pass); + options.blend_mode = blend_mode; + cmd.pipeline = context.GetSolidFillPipeline(options); + cmd.BindVertices( + vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer())); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()); + frame_info.color = color.Premultiply(); + VS::BindFrameInfo(cmd, + pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + + cmd.primitive_type = PrimitiveType::kTriangle; + + return pass.AddCommand(std::move(cmd)); + }; + + ImGui::Begin("Controls"); + static Color color1(1, 0, 0, 0.5), color2(0, 1, 0, 0.5); + ImGui::ColorEdit4("Color 1", reinterpret_cast(&color1)); + ImGui::ColorEdit4("Color 2", reinterpret_cast(&color2)); + static int current_blend_index = 3; + ImGui::ListBox("Blending mode", ¤t_blend_index, + blend_mode_names.data(), blend_mode_names.size()); + ImGui::End(); + + Entity::BlendMode selected_mode = + blend_mode_values[current_blend_index]; + + Point a, b, c, d; + std::tie(a, b) = IMPELLER_PLAYGROUND_LINE( + Point(400, 100), Point(200, 300), 20, Color::White(), Color::White()); + std::tie(c, d) = IMPELLER_PLAYGROUND_LINE( + Point(470, 190), Point(270, 390), 20, Color::White(), Color::White()); + + bool result = true; + result = result && draw_rect(Rect(0, 0, pass.GetRenderTargetSize().width, + pass.GetRenderTargetSize().height), + Color(), Entity::BlendMode::kClear); + result = result && draw_rect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), color1, + Entity::BlendMode::kSourceOver); + result = result && draw_rect(Rect::MakeLTRB(c.x, c.y, d.x, d.y), color2, + selected_mode); + return result; + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + } // namespace testing } // namespace impeller