diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 3b3ec37ae63..14de4f81e9b 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -582,6 +582,8 @@ FILE: ../../../flutter/impeller/entity/contents/filters/inputs/filter_input.h FILE: ../../../flutter/impeller/entity/contents/filters/inputs/filter_input_unittests.cc FILE: ../../../flutter/impeller/entity/contents/filters/inputs/texture_filter_input.cc FILE: ../../../flutter/impeller/entity/contents/filters/inputs/texture_filter_input.h +FILE: ../../../flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.cc +FILE: ../../../flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.h FILE: ../../../flutter/impeller/entity/contents/linear_gradient_contents.cc FILE: ../../../flutter/impeller/entity/contents/linear_gradient_contents.h FILE: ../../../flutter/impeller/entity/contents/radial_gradient_contents.cc @@ -644,6 +646,8 @@ FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.frag FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.vert FILE: ../../../flutter/impeller/entity/shaders/gradient_fill.vert FILE: ../../../flutter/impeller/entity/shaders/linear_gradient_fill.frag +FILE: ../../../flutter/impeller/entity/shaders/linear_to_srgb_filter.frag +FILE: ../../../flutter/impeller/entity/shaders/linear_to_srgb_filter.vert FILE: ../../../flutter/impeller/entity/shaders/radial_gradient_fill.frag FILE: ../../../flutter/impeller/entity/shaders/rrect_blur.frag FILE: ../../../flutter/impeller/entity/shaders/rrect_blur.vert diff --git a/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc b/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc index 9f08a4bfc49..f483856b6a4 100644 --- a/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc +++ b/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc @@ -441,9 +441,10 @@ void DisplayListDispatcher::setColorFilter( UNIMPLEMENTED; break; case flutter::DlColorFilterType::kLinearToSrgbGamma: - FML_LOG(ERROR) << "requested DlColorFilterType::kLinearToSrgbGamma"; - UNIMPLEMENTED; - break; + paint_.color_filter = [](FilterInput::Ref input) { + return FilterContents::MakeLinearToSrgbFilter({input}); + }; + return; case flutter::DlColorFilterType::kUnknown: FML_LOG(ERROR) << "requested DlColorFilterType::kUnknown"; UNIMPLEMENTED; diff --git a/engine/src/flutter/impeller/entity/BUILD.gn b/engine/src/flutter/impeller/entity/BUILD.gn index 8ac8d402be1..270f3c8a64b 100644 --- a/engine/src/flutter/impeller/entity/BUILD.gn +++ b/engine/src/flutter/impeller/entity/BUILD.gn @@ -37,6 +37,8 @@ impeller_shaders("entity_shaders") { "shaders/glyph_atlas.frag", "shaders/glyph_atlas.vert", "shaders/gradient_fill.vert", + "shaders/linear_to_srgb_filter.frag", + "shaders/linear_to_srgb_filter.vert", "shaders/linear_gradient_fill.frag", "shaders/radial_gradient_fill.frag", "shaders/rrect_blur.vert", @@ -85,6 +87,8 @@ impeller_component("entity") { "contents/filters/inputs/filter_input.h", "contents/filters/inputs/texture_filter_input.cc", "contents/filters/inputs/texture_filter_input.h", + "contents/filters/linear_to_srgb_filter_contents.cc", + "contents/filters/linear_to_srgb_filter_contents.h", "contents/linear_gradient_contents.cc", "contents/linear_gradient_contents.h", "contents/radial_gradient_contents.cc", diff --git a/engine/src/flutter/impeller/entity/contents/content_context.cc b/engine/src/flutter/impeller/entity/contents/content_context.cc index 10d9bee9cc0..030fb6b1398 100644 --- a/engine/src/flutter/impeller/entity/contents/content_context.cc +++ b/engine/src/flutter/impeller/entity/contents/content_context.cc @@ -199,6 +199,8 @@ ContentContext::ContentContext(std::shared_ptr context) CreateDefaultPipeline(*context_); color_matrix_color_filter_pipelines_[{}] = CreateDefaultPipeline(*context_); + linear_to_srgb_filter_pipelines_[{}] = + CreateDefaultPipeline(*context_); solid_stroke_pipelines_[{}] = CreateDefaultPipeline(*context_); glyph_atlas_pipelines_[{}] = diff --git a/engine/src/flutter/impeller/entity/contents/content_context.h b/engine/src/flutter/impeller/entity/contents/content_context.h index 194278a437b..f984a1da068 100644 --- a/engine/src/flutter/impeller/entity/contents/content_context.h +++ b/engine/src/flutter/impeller/entity/contents/content_context.h @@ -42,6 +42,8 @@ #include "impeller/entity/glyph_atlas.vert.h" #include "impeller/entity/gradient_fill.vert.h" #include "impeller/entity/linear_gradient_fill.frag.h" +#include "impeller/entity/linear_to_srgb_filter.frag.h" +#include "impeller/entity/linear_to_srgb_filter.vert.h" #include "impeller/entity/radial_gradient_fill.frag.h" #include "impeller/entity/rrect_blur.frag.h" #include "impeller/entity/rrect_blur.vert.h" @@ -113,6 +115,8 @@ using BorderMaskBlurPipeline = using ColorMatrixColorFilterPipeline = PipelineT; +using LinearToSrgbFilterPipeline = + PipelineT; using SolidStrokePipeline = PipelineT; using GlyphAtlasPipeline = @@ -211,6 +215,11 @@ class ContentContext { return GetPipeline(color_matrix_color_filter_pipelines_, opts); } + std::shared_ptr GetLinearToSrgbFilterPipeline( + ContentContextOptions opts) const { + return GetPipeline(linear_to_srgb_filter_pipelines_, opts); + } + std::shared_ptr GetSolidStrokePipeline( ContentContextOptions opts) const { return GetPipeline(solid_stroke_pipelines_, opts); @@ -345,6 +354,7 @@ class ContentContext { mutable Variants border_mask_blur_pipelines_; mutable Variants color_matrix_color_filter_pipelines_; + mutable Variants linear_to_srgb_filter_pipelines_; mutable Variants solid_stroke_pipelines_; mutable Variants clip_pipelines_; mutable Variants glyph_atlas_pipelines_; diff --git a/engine/src/flutter/impeller/entity/contents/filters/filter_contents.cc b/engine/src/flutter/impeller/entity/contents/filters/filter_contents.cc index 115eac6b948..48a79fd8784 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/filter_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/filters/filter_contents.cc @@ -19,6 +19,7 @@ #include "impeller/entity/contents/filters/color_matrix_filter_contents.h" #include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" +#include "impeller/entity/contents/filters/linear_to_srgb_filter_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/entity/entity.h" #include "impeller/geometry/path_builder.h" @@ -122,6 +123,13 @@ std::shared_ptr FilterContents::MakeColorMatrix( return filter; } +std::shared_ptr FilterContents::MakeLinearToSrgbFilter( + FilterInput::Ref input) { + auto filter = std::make_shared(); + filter->SetInputs({input}); + return filter; +} + FilterContents::FilterContents() = default; FilterContents::~FilterContents() = default; diff --git a/engine/src/flutter/impeller/entity/contents/filters/filter_contents.h b/engine/src/flutter/impeller/entity/contents/filters/filter_contents.h index b3064b44750..677ebc1dbae 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/filter_contents.h +++ b/engine/src/flutter/impeller/entity/contents/filters/filter_contents.h @@ -66,6 +66,9 @@ class FilterContents : public Contents { FilterInput::Ref input, const ColorMatrix& matrix); + static std::shared_ptr MakeLinearToSrgbFilter( + FilterInput::Ref input); + FilterContents(); ~FilterContents() override; diff --git a/engine/src/flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.cc b/engine/src/flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.cc new file mode 100644 index 00000000000..ff9b65087b7 --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.cc @@ -0,0 +1,83 @@ +// 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. + +#include "impeller/entity/contents/filters/linear_to_srgb_filter_contents.h" + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/vector.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" + +namespace impeller { + +LinearToSrgbFilterContents::LinearToSrgbFilterContents() = default; + +LinearToSrgbFilterContents::~LinearToSrgbFilterContents() = default; + +std::optional LinearToSrgbFilterContents::RenderFilter( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Rect& coverage) const { + if (inputs.empty()) { + return std::nullopt; + } + + using VS = LinearToSrgbFilterPipeline::VertexShader; + using FS = LinearToSrgbFilterPipeline::FragmentShader; + + auto input_snapshot = inputs[0]->GetSnapshot(renderer, entity); + if (!input_snapshot.has_value()) { + return std::nullopt; + } + + ContentContext::SubpassCallback callback = [&](const ContentContext& renderer, + RenderPass& pass) { + Command cmd; + cmd.label = "Linear to sRGB Filter"; + + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetLinearToSrgbFilterPipeline(options); + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0)}, + {Point(1, 0)}, + {Point(1, 1)}, + {Point(0, 0)}, + {Point(1, 1)}, + {Point(0, 1)}, + }); + + auto& host_buffer = pass.GetTransientsBuffer(); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + cmd.BindVertices(vtx_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); + + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + FS::BindInputTexture(cmd, input_snapshot->texture, sampler); + + VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info)); + + return pass.AddCommand(std::move(cmd)); + }; + + auto out_texture = + renderer.MakeSubpass(input_snapshot->texture->GetSize(), callback); + if (!out_texture) { + return std::nullopt; + } + out_texture->SetLabel("LinearToSrgb Texture"); + + return Snapshot{.texture = out_texture, + .transform = input_snapshot->transform, + .sampler_descriptor = input_snapshot->sampler_descriptor}; +} + +} // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.h b/engine/src/flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.h new file mode 100644 index 00000000000..ec1424c2573 --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.h @@ -0,0 +1,29 @@ +// 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. + +#pragma once + +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" + +namespace impeller { + +class LinearToSrgbFilterContents final : public FilterContents { + public: + LinearToSrgbFilterContents(); + + ~LinearToSrgbFilterContents() override; + + private: + // |FilterContents| + std::optional RenderFilter( + const FilterInput::Vector& input_textures, + const ContentContext& renderer, + const Entity& entity, + const Rect& coverage) const override; + + FML_DISALLOW_COPY_AND_ASSIGN(LinearToSrgbFilterContents); +}; + +} // namespace impeller diff --git a/engine/src/flutter/impeller/entity/entity_unittests.cc b/engine/src/flutter/impeller/entity/entity_unittests.cc index 4fa1f959ee3..260b9e48050 100644 --- a/engine/src/flutter/impeller/entity/entity_unittests.cc +++ b/engine/src/flutter/impeller/entity/entity_unittests.cc @@ -8,6 +8,7 @@ #include "flutter/testing/testing.h" #include "impeller/entity/contents/atlas_contents.h" #include "impeller/entity/contents/clip_contents.h" +#include "impeller/entity/contents/contents.h" #include "impeller/entity/contents/filters/blend_filter_contents.h" #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" @@ -1542,5 +1543,56 @@ TEST_P(EntityTest, ColorMatrixFilterEditable) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } +TEST_P(EntityTest, LinearToSrgbFilterCoverageIsCorrect) { + // Set up a simple color background. + auto fill = std::make_shared(); + fill->SetPath( + PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()); + fill->SetColor(Color::MintCream()); + + auto filter = FilterContents::MakeLinearToSrgbFilter(FilterInput::Make(fill)); + + Entity e; + e.SetTransformation(Matrix()); + + // Confirm that the actual filter coverage matches the expected coverage. + auto actual = filter->GetCoverage(e); + auto expected = Rect::MakeXYWH(0, 0, 300, 400); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); +} + +TEST_P(EntityTest, LinearToSrgbFilter) { + auto image = CreateTextureForFixture("kalimba.jpg"); + ASSERT_TRUE(image); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + auto filtered = + FilterContents::MakeLinearToSrgbFilter(FilterInput::Make(image)); + + // Define the entity that will serve as the control image as a Gaussian blur + // filter with no filter at all. + Entity entity_left; + entity_left.SetTransformation(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({100, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image), + Sigma{0}, Sigma{0}); + entity_left.SetContents(unfiltered); + + // Define the entity that will be filtered from linear to sRGB. + Entity entity_right; + entity_right.SetTransformation(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity_right.SetContents(filtered); + return entity_left.Render(context, pass) && + entity_right.Render(context, pass); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + } // namespace testing } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/shaders/linear_to_srgb_filter.frag b/engine/src/flutter/impeller/entity/shaders/linear_to_srgb_filter.frag new file mode 100644 index 00000000000..fea911c14f1 --- /dev/null +++ b/engine/src/flutter/impeller/entity/shaders/linear_to_srgb_filter.frag @@ -0,0 +1,28 @@ +// 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. + +#include + +// A color filter that applies the sRGB gamma curve to the color. +// +// This filter is used so that the colors are suitable for display in monitors. + +uniform sampler2D input_texture; + +in vec2 v_position; +out vec4 frag_color; + +void main() { + vec4 input_color = texture(input_texture, v_position); + + for (int i = 0; i < 4; i++) { + if (input_color[i] <= 0.0031308) { + input_color[i] = input_color[i] * 12.92; + } else { + input_color[i] = 1.055 * pow(input_color[i], (1.0 / 2.4)) - 0.055; + } + } + + frag_color = input_color; +} diff --git a/engine/src/flutter/impeller/entity/shaders/linear_to_srgb_filter.vert b/engine/src/flutter/impeller/entity/shaders/linear_to_srgb_filter.vert new file mode 100644 index 00000000000..b741b2744ec --- /dev/null +++ b/engine/src/flutter/impeller/entity/shaders/linear_to_srgb_filter.vert @@ -0,0 +1,15 @@ +// 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 position; +out vec2 v_position; + +void main() { + v_position = position; + gl_Position = frame_info.mvp * vec4(position, 0.0, 1.0); +}