Flutter Impeller
gaussian_blur_filter_contents_unittests.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "flutter/testing/testing.h"
6 #include "fml/status_or.h"
7 #include "gmock/gmock.h"
12 #include "impeller/renderer/testing/mocks.h"
13 
14 namespace impeller {
15 namespace testing {
16 
17 namespace {
18 
19 // Use newtonian method to give the closest answer to target where
20 // f(x) is less than the target. We do this because the value is `ceil`'d to
21 // grab fractional pixels.
22 float LowerBoundNewtonianMethod(const std::function<float(float)>& func,
23  float target,
24  float guess,
25  float tolerance) {
26  const float delta = 1e-6;
27  float x = guess;
28  float fx;
29 
30  do {
31  fx = func(x) - target;
32  float derivative = (func(x + delta) - func(x)) / delta;
33  x = x - fx / derivative;
34 
35  } while (std::abs(fx) > tolerance ||
36  fx < 0.0); // fx < 0.0 makes this lower bound.
37 
38  return x;
39 }
40 
41 Scalar CalculateSigmaForBlurRadius(Scalar radius) {
42  auto f = [](Scalar x) -> Scalar {
45  };
46  // The newtonian method is used here since inverting the function is
47  // non-trivial because of conditional logic and would be fragile to changes.
48  return LowerBoundNewtonianMethod(f, radius, 2.f, 0.001f);
49 }
50 
51 } // namespace
52 
54  public:
55  std::shared_ptr<Texture> MakeTexture(const TextureDescriptor& desc) {
56  return GetContentContext()
57  ->GetContext()
58  ->GetResourceAllocator()
59  ->CreateTexture(desc);
60  }
61 };
62 INSTANTIATE_PLAYGROUND_SUITE(GaussianBlurFilterContentsTest);
63 
65  GaussianBlurFilterContents contents(/*sigma_x=*/0.0, /*sigma_y=*/0.0,
67  EXPECT_EQ(contents.GetSigmaX(), 0.0);
68  EXPECT_EQ(contents.GetSigmaY(), 0.0);
69 }
70 
72  GaussianBlurFilterContents contents(/*sigma_x=*/0.0, /*sigma_y=*/0.0,
74  FilterInput::Vector inputs = {};
75  Entity entity;
76  std::optional<Rect> coverage =
77  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
78  ASSERT_FALSE(coverage.has_value());
79 }
80 
82  GaussianBlurFilterContents contents(/*sigma_x=*/0.0, /*sigma_y=*/0.0,
84  FilterInput::Vector inputs = {
85  FilterInput::Make(Rect::MakeLTRB(10, 10, 110, 110))};
86  Entity entity;
87  std::optional<Rect> coverage =
88  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
89  ASSERT_EQ(coverage, Rect::MakeLTRB(10, 10, 110, 110));
90 }
91 
92 TEST(GaussianBlurFilterContentsTest, CoverageWithSigma) {
93  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
94  GaussianBlurFilterContents contents(/*sigma_x=*/sigma_radius_1,
95  /*sigma_y=*/sigma_radius_1,
97  FilterInput::Vector inputs = {
98  FilterInput::Make(Rect::MakeLTRB(100, 100, 200, 200))};
99  Entity entity;
100  std::optional<Rect> coverage =
101  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
102  EXPECT_TRUE(coverage.has_value());
103  if (coverage.has_value()) {
104  EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201));
105  }
106 }
107 
108 TEST_P(GaussianBlurFilterContentsTest, CoverageWithTexture) {
109  TextureDescriptor desc = {
112  .size = ISize(100, 100),
113  };
114  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
115  GaussianBlurFilterContents contents(/*sigma_X=*/sigma_radius_1,
116  /*sigma_y=*/sigma_radius_1,
118  std::shared_ptr<Texture> texture =
119  GetContentContext()->GetContext()->GetResourceAllocator()->CreateTexture(
120  desc);
121  FilterInput::Vector inputs = {FilterInput::Make(texture)};
122  Entity entity;
123  entity.SetTransform(Matrix::MakeTranslation({100, 100, 0}));
124  std::optional<Rect> coverage =
125  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
126  EXPECT_TRUE(coverage.has_value());
127  if (coverage.has_value()) {
128  EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201));
129  }
130 }
131 
132 TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) {
133  TextureDescriptor desc = {
136  .size = ISize(100, 100),
137  };
138  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
139  GaussianBlurFilterContents contents(/*sigma_x=*/sigma_radius_1,
140  /*sigma_y=*/sigma_radius_1,
142  std::shared_ptr<Texture> texture =
143  GetContentContext()->GetContext()->GetResourceAllocator()->CreateTexture(
144  desc);
145  FilterInput::Vector inputs = {FilterInput::Make(texture)};
146  Entity entity;
147  entity.SetTransform(Matrix::MakeTranslation({100, 100, 0}));
148  std::optional<Rect> coverage = contents.GetFilterCoverage(
149  inputs, entity, /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}));
150  EXPECT_TRUE(coverage.has_value());
151  if (coverage.has_value()) {
152  EXPECT_RECT_NEAR(coverage.value(),
153  Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2));
154  }
155 }
156 
157 TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) {
158  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
159  auto contents = std::make_unique<GaussianBlurFilterContents>(
160  sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
161  std::optional<Rect> coverage = contents->GetFilterSourceCoverage(
162  /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}),
163  /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200));
164  ASSERT_EQ(coverage, Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2));
165 }
166 
167 TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) {
168  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1.0f), 1);
169  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(2.0f), 1);
170  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(3.0f), 1);
171  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(4.0f), 1);
172  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(16.0f), 0.25);
173  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1024.0f), 4.f / 1024.f);
174 }
175 
176 TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) {
177  TextureDescriptor desc = {
180  .size = ISize(100, 100),
181  };
182  std::shared_ptr<Texture> texture = MakeTexture(desc);
183  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
184  auto contents = std::make_unique<GaussianBlurFilterContents>(
185  sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
186  contents->SetInputs({FilterInput::Make(texture)});
187  std::shared_ptr<ContentContext> renderer = GetContentContext();
188 
189  Entity entity;
190  std::optional<Entity> result =
191  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
192  EXPECT_TRUE(result.has_value());
193  if (result.has_value()) {
194  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver);
195  std::optional<Rect> result_coverage = result.value().GetCoverage();
196  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
197  EXPECT_TRUE(result_coverage.has_value());
198  EXPECT_TRUE(contents_coverage.has_value());
199  if (result_coverage.has_value() && contents_coverage.has_value()) {
200  EXPECT_TRUE(RectNear(contents_coverage.value(),
201  Rect::MakeLTRB(-1, -1, 101, 101)));
202  EXPECT_TRUE(
203  RectNear(result_coverage.value(), Rect::MakeLTRB(-1, -1, 101, 101)));
204  }
205  }
206 }
207 
209  RenderCoverageMatchesGetCoverageTranslate) {
210  TextureDescriptor desc = {
213  .size = ISize(100, 100),
214  };
215  std::shared_ptr<Texture> texture = MakeTexture(desc);
216  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
217  auto contents = std::make_unique<GaussianBlurFilterContents>(
218  sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
219  contents->SetInputs({FilterInput::Make(texture)});
220  std::shared_ptr<ContentContext> renderer = GetContentContext();
221 
222  Entity entity;
223  entity.SetTransform(Matrix::MakeTranslation({100, 200, 0}));
224  std::optional<Entity> result =
225  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
226 
227  EXPECT_TRUE(result.has_value());
228  if (result.has_value()) {
229  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver);
230  std::optional<Rect> result_coverage = result.value().GetCoverage();
231  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
232  EXPECT_TRUE(result_coverage.has_value());
233  EXPECT_TRUE(contents_coverage.has_value());
234  if (result_coverage.has_value() && contents_coverage.has_value()) {
235  EXPECT_TRUE(RectNear(contents_coverage.value(),
236  Rect::MakeLTRB(99, 199, 201, 301)));
237  EXPECT_TRUE(
238  RectNear(result_coverage.value(), Rect::MakeLTRB(99, 199, 201, 301)));
239  }
240  }
241 }
242 
244  RenderCoverageMatchesGetCoverageRotated) {
245  TextureDescriptor desc = {
248  .size = ISize(400, 300),
249  };
250  std::shared_ptr<Texture> texture = MakeTexture(desc);
251  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
252  auto contents = std::make_unique<GaussianBlurFilterContents>(
253  sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
254  contents->SetInputs({FilterInput::Make(texture)});
255  std::shared_ptr<ContentContext> renderer = GetContentContext();
256 
257  Entity entity;
258  // Rotate around the top left corner, then push it over to (100, 100).
259  entity.SetTransform(Matrix::MakeTranslation({400, 100, 0}) *
261  std::optional<Entity> result =
262  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
263  EXPECT_TRUE(result.has_value());
264  if (result.has_value()) {
265  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver);
266  std::optional<Rect> result_coverage = result.value().GetCoverage();
267  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
268  EXPECT_TRUE(result_coverage.has_value());
269  EXPECT_TRUE(contents_coverage.has_value());
270  if (result_coverage.has_value() && contents_coverage.has_value()) {
271  EXPECT_TRUE(RectNear(contents_coverage.value(),
272  Rect::MakeLTRB(99, 99, 401, 501)));
273  EXPECT_TRUE(
274  RectNear(result_coverage.value(), Rect::MakeLTRB(99, 99, 401, 501)));
275  }
276  }
277 }
278 
280  TextureDescriptor desc = {
283  .size = ISize(100, 100),
284  };
285  std::shared_ptr<Texture> texture = MakeTexture(desc);
286  auto filter_input = FilterInput::Make(texture);
287  Entity entity;
289  filter_input, entity, Rect::MakeSize(ISize(100, 100)), ISize(100, 100));
290  std::optional<Rect> uvs_bounds = Rect::MakePointBounds(uvs);
291  EXPECT_TRUE(uvs_bounds.has_value());
292  if (uvs_bounds.has_value()) {
293  EXPECT_TRUE(RectNear(uvs_bounds.value(), Rect::MakeXYWH(0, 0, 1, 1)));
294  }
295 }
296 
297 TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithDestinationRect) {
298  TextureDescriptor desc = {
301  .size = ISize(100, 100),
302  };
303 
304  std::shared_ptr<Texture> texture = MakeTexture(desc);
305  auto texture_contents = std::make_shared<TextureContents>();
306  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
307  texture_contents->SetTexture(texture);
308  texture_contents->SetDestinationRect(Rect::MakeXYWH(
309  50, 40, texture->GetSize().width, texture->GetSize().height));
310 
311  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
312  auto contents = std::make_unique<GaussianBlurFilterContents>(
313  sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
314  contents->SetInputs({FilterInput::Make(texture_contents)});
315  std::shared_ptr<ContentContext> renderer = GetContentContext();
316 
317  Entity entity;
318  std::optional<Entity> result =
319  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
320  EXPECT_TRUE(result.has_value());
321  if (result.has_value()) {
322  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver);
323  std::optional<Rect> result_coverage = result.value().GetCoverage();
324  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
325  EXPECT_TRUE(result_coverage.has_value());
326  EXPECT_TRUE(contents_coverage.has_value());
327  if (result_coverage.has_value() && contents_coverage.has_value()) {
328  EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
329  EXPECT_TRUE(RectNear(result_coverage.value(),
330  Rect::MakeLTRB(49.f, 39.f, 151.f, 141.f)));
331  }
332  }
333 }
334 
336  TextureContentsWithDestinationRectScaled) {
337  TextureDescriptor desc = {
340  .size = ISize(100, 100),
341  };
342 
343  std::shared_ptr<Texture> texture = MakeTexture(desc);
344  auto texture_contents = std::make_shared<TextureContents>();
345  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
346  texture_contents->SetTexture(texture);
347  texture_contents->SetDestinationRect(Rect::MakeXYWH(
348  50, 40, texture->GetSize().width, texture->GetSize().height));
349 
350  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
351  auto contents = std::make_unique<GaussianBlurFilterContents>(
352  sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
353  contents->SetInputs({FilterInput::Make(texture_contents)});
354  std::shared_ptr<ContentContext> renderer = GetContentContext();
355 
356  Entity entity;
357  entity.SetTransform(Matrix::MakeScale({2.0, 2.0, 1.0}));
358  std::optional<Entity> result =
359  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
360  EXPECT_TRUE(result.has_value());
361  if (result.has_value()) {
362  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver);
363  std::optional<Rect> result_coverage = result.value().GetCoverage();
364  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
365  EXPECT_TRUE(result_coverage.has_value());
366  EXPECT_TRUE(contents_coverage.has_value());
367  if (result_coverage.has_value() && contents_coverage.has_value()) {
368  EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
369  EXPECT_TRUE(RectNear(contents_coverage.value(),
370  Rect::MakeLTRB(98.f, 78.f, 302.f, 282.f)));
371  }
372  }
373 }
374 
375 TEST(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius) {
376  Scalar sigma = 1.0;
379  Scalar derived_sigma = CalculateSigmaForBlurRadius(radius);
380 
381  EXPECT_NEAR(sigma, derived_sigma, 0.01f);
382 }
383 
385  BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
386  .blur_sigma = 1,
387  .blur_radius = 5,
388  .step_size = 1};
389  KernelPipeline::FragmentShader::KernelSamples samples =
390  GenerateBlurInfo(parameters);
391  EXPECT_EQ(samples.sample_count, 11);
392 
393  // Coefficients should add up to 1.
394  Scalar tally = 0;
395  for (int i = 0; i < samples.sample_count; ++i) {
396  tally += samples.samples[i].coefficient;
397  }
398  EXPECT_FLOAT_EQ(tally, 1.0f);
399 
400  // Verify the shape of the curve.
401  for (int i = 0; i < 5; ++i) {
402  EXPECT_FLOAT_EQ(samples.samples[i].coefficient,
403  samples.samples[10 - i].coefficient);
404  EXPECT_TRUE(samples.samples[i + 1].coefficient >
405  samples.samples[i].coefficient);
406  }
407 }
408 
409 } // namespace testing
410 } // namespace impeller
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::testing::GaussianBlurFilterContentsTest::MakeTexture
std::shared_ptr< Texture > MakeTexture(const TextureDescriptor &desc)
Definition: gaussian_blur_filter_contents_unittests.cc:55
texture_contents.h
geometry_asserts.h
impeller::BlurParameters::blur_uv_offset
Point blur_uv_offset
Definition: gaussian_blur_filter_contents.h:15
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:34
impeller::FilterInput::Make
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
Definition: filter_input.cc:19
impeller::Entity::TileMode::kDecal
@ kDecal
gaussian_blur_filter_contents.h
impeller::GaussianBlurFilterContents::CalculateBlurRadius
static Scalar CalculateBlurRadius(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:405
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
impeller::GenerateBlurInfo
KernelPipeline::FragmentShader::KernelSamples GenerateBlurInfo(BlurParameters parameters)
Definition: gaussian_blur_filter_contents.cc:435
EXPECT_RECT_NEAR
#define EXPECT_RECT_NEAR(a, b)
Definition: geometry_asserts.h:170
impeller::TRect< Scalar >::MakePointBounds
constexpr static std::optional< TRect > MakePointBounds(const U &value)
Definition: rect.h:49
impeller::GaussianBlurFilterContents::GetSigmaX
Scalar GetSigmaX() const
Definition: gaussian_blur_filter_contents.h:34
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller::GaussianBlurFilterContents::GetFilterCoverage
std::optional< Rect > GetFilterCoverage(const FilterInput::Vector &inputs, const Entity &entity, const Matrix &effect_transform) const override
Internal utility method for |GetLocalCoverage| that computes the output coverage of this filter acros...
Definition: gaussian_blur_filter_contents.cc:225
impeller::Entity
Definition: entity.h:21
impeller::Point
TPoint< Scalar > Point
Definition: point.h:308
impeller::Quad
std::array< Point, 4 > Quad
Definition: point.h:313
impeller::StorageMode::kDevicePrivate
@ kDevicePrivate
impeller::testing::TEST
TEST(CanvasRecorder, Save)
Definition: canvas_recorder_unittests.cc:61
impeller::BlurParameters
Definition: gaussian_blur_filter_contents.h:14
impeller::ISize
TSize< int64_t > ISize
Definition: size.h:138
impeller::testing::GaussianBlurFilterContentsTest
Definition: gaussian_blur_filter_contents_unittests.cc:53
impeller::GaussianBlurFilterContents::GetSigmaY
Scalar GetSigmaY() const
Definition: gaussian_blur_filter_contents.h:35
impeller::EntityPlayground
Definition: entity_playground.h:18
impeller::Entity::SetTransform
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:53
impeller::Matrix::MakeRotationZ
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:209
RectNear
inline ::testing::AssertionResult RectNear(impeller::Rect a, impeller::Rect b)
Definition: geometry_asserts.h:56
impeller::TRect< Scalar >::MakeSize
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:44
impeller::GaussianBlurFilterContents::CalculateScale
static Scalar CalculateScale(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:207
impeller::Degrees
Definition: scalar.h:46
impeller::TextureDescriptor::storage_mode
StorageMode storage_mode
Definition: texture_descriptor.h:38
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:27
impeller::GaussianBlurFilterContents::CalculateUVs
static Quad CalculateUVs(const std::shared_ptr< FilterInput > &filter_input, const Entity &entity, const Rect &source_rect, const ISize &texture_size)
Definition: gaussian_blur_filter_contents.cc:409
impeller::TextureDescriptor
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...
Definition: texture_descriptor.h:37
impeller::FilterInput::Vector
std::vector< FilterInput::Ref > Vector
Definition: filter_input.h:33
impeller::testing::TEST_P
TEST_P(AiksTest, CanRenderLinearGradientClamp)
Definition: aiks_gradient_unittests.cc:48
impeller::PixelFormat::kB8G8R8A8UNormInt
@ kB8G8R8A8UNormInt
impeller::GaussianBlurFilterContents
Definition: gaussian_blur_filter_contents.h:28
impeller
Definition: aiks_context.cc:10
impeller::Matrix::MakeScale
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
impeller::EntityPlayground::GetContentContext
std::shared_ptr< ContentContext > GetContentContext() const
Definition: entity_playground.cc:39
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::BlendMode::kSourceOver
@ kSourceOver
entity_playground.h
impeller::GaussianBlurFilterContents::ScaleSigma
static Scalar ScaleSigma(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:425