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"
14 #include "impeller/renderer/testing/mocks.h"
15 
16 #if FML_OS_MACOSX
17 #define IMPELLER_RAND arc4random
18 #else
19 #define IMPELLER_RAND rand
20 #endif
21 
22 namespace impeller {
23 namespace testing {
24 
25 namespace {
26 
27 // Use newtonian method to give the closest answer to target where
28 // f(x) is less than the target. We do this because the value is `ceil`'d to
29 // grab fractional pixels.
30 fml::StatusOr<float> LowerBoundNewtonianMethod(
31  const std::function<float(float)>& func,
32  float target,
33  float guess,
34  float tolerance) {
35  const double delta = 1e-6;
36  double x = guess;
37  double fx;
38  static const int kMaxIterations = 1000;
39  int count = 0;
40 
41  do {
42  fx = func(x) - target;
43  double derivative = (func(x + delta) - func(x)) / delta;
44  x = x - fx / derivative;
45  if (++count > kMaxIterations) {
46  return fml::Status(fml::StatusCode::kDeadlineExceeded,
47  "Did not converge on answer.");
48  }
49  } while (std::abs(fx) > tolerance ||
50  fx < 0.0); // fx < 0.0 makes this lower bound.
51 
52  return x;
53 }
54 
55 Scalar GetCoefficient(const Vector4& vec) {
56  return vec.z;
57 }
58 
59 Vector2 GetUVOffset(const Vector4& vec) {
60  return vec.xy();
61 }
62 
63 fml::StatusOr<Scalar> CalculateSigmaForBlurRadius(
64  Scalar radius,
65  const Matrix& effect_transform) {
66  auto f = [effect_transform](Scalar x) -> Scalar {
67  Vector2 scaled_sigma = (effect_transform.Basis() *
70  .Abs();
74  return std::max(blur_radius.x, blur_radius.y);
75  };
76  // The newtonian method is used here since inverting the function is
77  // non-trivial because of conditional logic and would be fragile to changes.
78  return LowerBoundNewtonianMethod(f, radius, 2.f, 0.001f);
79 }
80 
81 } // namespace
82 
84  public:
85  /// Create a texture that has been cleared to transparent black.
86  std::shared_ptr<Texture> MakeTexture(ISize size) {
87  std::shared_ptr<CommandBuffer> command_buffer =
88  GetContentContext()->GetContext()->CreateCommandBuffer();
89  if (!command_buffer) {
90  return nullptr;
91  }
92 
93  auto render_target = GetContentContext()->MakeSubpass(
94  "Clear Subpass", size, command_buffer,
95  [](const ContentContext&, RenderPass&) { return true; });
96 
97  if (!GetContentContext()
98  ->GetContext()
99  ->GetCommandQueue()
100  ->Submit(/*buffers=*/{command_buffer})
101  .ok()) {
102  return nullptr;
103  }
104 
105  if (render_target.ok()) {
106  return render_target.value().GetRenderTargetTexture();
107  }
108  return nullptr;
109  }
110 };
112 
115  /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal,
116  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
117  /*mask_geometry=*/nullptr);
118  EXPECT_EQ(contents.GetSigmaX(), 0.0);
119  EXPECT_EQ(contents.GetSigmaY(), 0.0);
120 }
121 
124  /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal,
125  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
126  /*mask_geometry=*/nullptr);
127  FilterInput::Vector inputs = {};
128  Entity entity;
129  std::optional<Rect> coverage =
130  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
131  ASSERT_FALSE(coverage.has_value());
132 }
133 
136  /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal,
137  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
138  /*mask_geometry=*/nullptr);
139  FilterInput::Vector inputs = {
140  FilterInput::Make(Rect::MakeLTRB(10, 10, 110, 110))};
141  Entity entity;
142  std::optional<Rect> coverage =
143  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
144 
145  ASSERT_EQ(coverage, Rect::MakeLTRB(10, 10, 110, 110));
146 }
147 
149  fml::StatusOr<Scalar> sigma_radius_1 =
150  CalculateSigmaForBlurRadius(1.0, Matrix());
151  ASSERT_TRUE(sigma_radius_1.ok());
153  /*sigma_x=*/sigma_radius_1.value(),
154  /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
155  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
156  /*mask_geometry=*/nullptr);
157  FilterInput::Vector inputs = {
158  FilterInput::Make(Rect::MakeLTRB(100, 100, 200, 200))};
159  Entity entity;
160  std::optional<Rect> coverage =
161  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
162 
163  EXPECT_TRUE(coverage.has_value());
164  if (coverage.has_value()) {
165  EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201));
166  }
167 }
168 
169 TEST_P(GaussianBlurFilterContentsTest, CoverageWithTexture) {
170  fml::StatusOr<Scalar> sigma_radius_1 =
171  CalculateSigmaForBlurRadius(1.0, Matrix());
172  ASSERT_TRUE(sigma_radius_1.ok());
174  /*sigma_X=*/sigma_radius_1.value(),
175  /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
176  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
177  /*mask_geometry=*/nullptr);
178  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
179  FilterInput::Vector inputs = {FilterInput::Make(texture)};
180  Entity entity;
181  entity.SetTransform(Matrix::MakeTranslation({100, 100, 0}));
182  std::optional<Rect> coverage =
183  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
184 
185  EXPECT_TRUE(coverage.has_value());
186  if (coverage.has_value()) {
187  EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201));
188  }
189 }
190 
191 TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) {
192  Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0});
193  fml::StatusOr<Scalar> sigma_radius_1 =
194  CalculateSigmaForBlurRadius(1.0, effect_transform);
195  ASSERT_TRUE(sigma_radius_1.ok());
197  /*sigma_x=*/sigma_radius_1.value(),
198  /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
199  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
200  /*mask_geometry=*/nullptr);
201  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
202  FilterInput::Vector inputs = {FilterInput::Make(texture)};
203  Entity entity;
204  entity.SetTransform(Matrix::MakeTranslation({100, 100, 0}));
205  std::optional<Rect> coverage =
206  contents.GetFilterCoverage(inputs, entity, effect_transform);
207  EXPECT_TRUE(coverage.has_value());
208  if (coverage.has_value()) {
209  EXPECT_RECT_NEAR(coverage.value(),
210  Rect::MakeLTRB(100 - 1, 100 - 1, 200 + 1, 200 + 1));
211  }
212 }
213 
214 TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) {
215  fml::StatusOr<Scalar> sigma_radius_1 =
216  CalculateSigmaForBlurRadius(1.0, Matrix());
217  ASSERT_TRUE(sigma_radius_1.ok());
218  auto contents = std::make_unique<GaussianBlurFilterContents>(
219  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
220  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
221  /*mask_geometry=*/nullptr);
222  std::optional<Rect> coverage = contents->GetFilterSourceCoverage(
223  /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}),
224  /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200));
225  EXPECT_TRUE(coverage.has_value());
226  if (coverage.has_value()) {
227  EXPECT_RECT_NEAR(coverage.value(),
228  Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2));
229  }
230 }
231 
232 TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) {
233  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1.0f), 1);
234  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(2.0f), 1);
235  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(3.0f), 1);
236  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(4.0f), 1);
237  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(16.0f), 0.25);
238  // Hang on to 1/8 as long as possible.
239  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(95.0f), 0.125);
240  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(96.0f), 0.0625);
241  // Downsample clamped to 1/16th.
242  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1024.0f), 0.0625);
243 }
244 
245 TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) {
246  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
247  fml::StatusOr<Scalar> sigma_radius_1 =
248  CalculateSigmaForBlurRadius(1.0, Matrix());
249  ASSERT_TRUE(sigma_radius_1.ok());
250  auto contents = std::make_unique<GaussianBlurFilterContents>(
251  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
252  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
253  /*mask_geometry=*/nullptr);
254  contents->SetInputs({FilterInput::Make(texture)});
255  std::shared_ptr<ContentContext> renderer = GetContentContext();
256 
257  Entity entity;
258  std::optional<Entity> result =
259  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
260  EXPECT_TRUE(result.has_value());
261  if (result.has_value()) {
262  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
263  std::optional<Rect> result_coverage = result.value().GetCoverage();
264  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
265  EXPECT_TRUE(result_coverage.has_value());
266  EXPECT_TRUE(contents_coverage.has_value());
267  if (result_coverage.has_value() && contents_coverage.has_value()) {
268  EXPECT_TRUE(RectNear(contents_coverage.value(),
269  Rect::MakeLTRB(-1, -1, 101, 101)));
270  EXPECT_TRUE(
271  RectNear(result_coverage.value(), Rect::MakeLTRB(-1, -1, 101, 101)));
272  }
273  }
274 }
275 
277  RenderCoverageMatchesGetCoverageTranslate) {
278  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
279  fml::StatusOr<Scalar> sigma_radius_1 =
280  CalculateSigmaForBlurRadius(1.0, Matrix());
281  ASSERT_TRUE(sigma_radius_1.ok());
282  auto contents = std::make_unique<GaussianBlurFilterContents>(
283  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
284  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
285  /*mask_geometry=*/nullptr);
286  contents->SetInputs({FilterInput::Make(texture)});
287  std::shared_ptr<ContentContext> renderer = GetContentContext();
288 
289  Entity entity;
290  entity.SetTransform(Matrix::MakeTranslation({100, 200, 0}));
291  std::optional<Entity> result =
292  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
293 
294  EXPECT_TRUE(result.has_value());
295  if (result.has_value()) {
296  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
297  std::optional<Rect> result_coverage = result.value().GetCoverage();
298  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
299  EXPECT_TRUE(result_coverage.has_value());
300  EXPECT_TRUE(contents_coverage.has_value());
301  if (result_coverage.has_value() && contents_coverage.has_value()) {
302  EXPECT_TRUE(RectNear(contents_coverage.value(),
303  Rect::MakeLTRB(99, 199, 201, 301)));
304  EXPECT_TRUE(
305  RectNear(result_coverage.value(), Rect::MakeLTRB(99, 199, 201, 301)));
306  }
307  }
308 }
309 
311  RenderCoverageMatchesGetCoverageRotated) {
312  std::shared_ptr<Texture> texture = MakeTexture(ISize(400, 300));
313  fml::StatusOr<Scalar> sigma_radius_1 =
314  CalculateSigmaForBlurRadius(1.0, Matrix());
315  auto contents = std::make_unique<GaussianBlurFilterContents>(
316  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
317  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
318  /*mask_geometry=*/nullptr);
319  contents->SetInputs({FilterInput::Make(texture)});
320  std::shared_ptr<ContentContext> renderer = GetContentContext();
321 
322  Entity entity;
323  // Rotate around the top left corner, then push it over to (100, 100).
324  entity.SetTransform(Matrix::MakeTranslation({400, 100, 0}) *
326  std::optional<Entity> result =
327  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
328  EXPECT_TRUE(result.has_value());
329  if (result.has_value()) {
330  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
331  std::optional<Rect> result_coverage = result.value().GetCoverage();
332  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
333  EXPECT_TRUE(result_coverage.has_value());
334  EXPECT_TRUE(contents_coverage.has_value());
335  if (result_coverage.has_value() && contents_coverage.has_value()) {
336  EXPECT_TRUE(RectNear(contents_coverage.value(),
337  Rect::MakeLTRB(99, 99, 401, 501)));
338  EXPECT_TRUE(
339  RectNear(result_coverage.value(), Rect::MakeLTRB(99, 99, 401, 501)));
340  }
341  }
342 }
343 
345  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
346  auto filter_input = FilterInput::Make(texture);
347  Entity entity;
349  filter_input, entity, Rect::MakeSize(ISize(100, 100)), ISize(100, 100));
350  std::optional<Rect> uvs_bounds = Rect::MakePointBounds(uvs);
351  EXPECT_TRUE(uvs_bounds.has_value());
352  if (uvs_bounds.has_value()) {
353  EXPECT_TRUE(RectNear(uvs_bounds.value(), Rect::MakeXYWH(0, 0, 1, 1)));
354  }
355 }
356 
357 TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithDestinationRect) {
358  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
359  auto texture_contents = std::make_shared<TextureContents>();
360  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
361  texture_contents->SetTexture(texture);
362  texture_contents->SetDestinationRect(Rect::MakeXYWH(
363  50, 40, texture->GetSize().width, texture->GetSize().height));
364 
365  fml::StatusOr<Scalar> sigma_radius_1 =
366  CalculateSigmaForBlurRadius(1.0, Matrix());
367  auto contents = std::make_unique<GaussianBlurFilterContents>(
368  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
369  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
370  /*mask_geometry=*/nullptr);
371  contents->SetInputs({FilterInput::Make(texture_contents)});
372  std::shared_ptr<ContentContext> renderer = GetContentContext();
373 
374  Entity entity;
375  std::optional<Entity> result =
376  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
377  EXPECT_TRUE(result.has_value());
378  if (result.has_value()) {
379  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
380  std::optional<Rect> result_coverage = result.value().GetCoverage();
381  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
382  EXPECT_TRUE(result_coverage.has_value());
383  EXPECT_TRUE(contents_coverage.has_value());
384  if (result_coverage.has_value() && contents_coverage.has_value()) {
385  EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
386  EXPECT_TRUE(RectNear(result_coverage.value(),
387  Rect::MakeLTRB(49.f, 39.f, 151.f, 141.f)));
388  }
389  }
390 }
391 
393  TextureContentsWithDestinationRectScaled) {
394  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
395  auto texture_contents = std::make_shared<TextureContents>();
396  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
397  texture_contents->SetTexture(texture);
398  texture_contents->SetDestinationRect(Rect::MakeXYWH(
399  50, 40, texture->GetSize().width, texture->GetSize().height));
400 
401  fml::StatusOr<Scalar> sigma_radius_1 =
402  CalculateSigmaForBlurRadius(1.0, Matrix());
403  auto contents = std::make_unique<GaussianBlurFilterContents>(
404  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
405  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
406  /*mask_geometry=*/nullptr);
407  contents->SetInputs({FilterInput::Make(texture_contents)});
408  std::shared_ptr<ContentContext> renderer = GetContentContext();
409 
410  Entity entity;
411  entity.SetTransform(Matrix::MakeScale({2.0, 2.0, 1.0}));
412  std::optional<Entity> result =
413  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
414  EXPECT_TRUE(result.has_value());
415  if (result.has_value()) {
416  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
417  std::optional<Rect> result_coverage = result.value().GetCoverage();
418  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
419  EXPECT_TRUE(result_coverage.has_value());
420  EXPECT_TRUE(contents_coverage.has_value());
421  if (result_coverage.has_value() && contents_coverage.has_value()) {
422  EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
423  // Scaling a blurred entity doesn't seem to scale the blur radius linearly
424  // when comparing results with rrect_blur. That's why this is not
425  // Rect::MakeXYWH(98.f, 78.f, 204.0f, 204.f).
426  EXPECT_TRUE(RectNear(contents_coverage.value(),
427  Rect::MakeXYWH(94.f, 74.f, 212.0f, 212.f)));
428  }
429  }
430 }
431 
432 TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithEffectTransform) {
433  Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0});
434  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
435  auto texture_contents = std::make_shared<TextureContents>();
436  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
437  texture_contents->SetTexture(texture);
438  texture_contents->SetDestinationRect(Rect::MakeXYWH(
439  50, 40, texture->GetSize().width, texture->GetSize().height));
440 
441  fml::StatusOr<Scalar> sigma_radius_1 =
442  CalculateSigmaForBlurRadius(1.0, effect_transform);
443  ASSERT_TRUE(sigma_radius_1.ok());
444  auto contents = std::make_unique<GaussianBlurFilterContents>(
445  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
446  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
447  /*mask_geometry=*/nullptr);
448  contents->SetInputs({FilterInput::Make(texture_contents)});
449  contents->SetEffectTransform(effect_transform);
450  std::shared_ptr<ContentContext> renderer = GetContentContext();
451 
452  Entity entity;
453  std::optional<Entity> result =
454  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
455  EXPECT_TRUE(result.has_value());
456  if (result.has_value()) {
457  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
458  std::optional<Rect> result_coverage = result.value().GetCoverage();
459  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
460  EXPECT_TRUE(result_coverage.has_value());
461  EXPECT_TRUE(contents_coverage.has_value());
462  if (result_coverage.has_value() && contents_coverage.has_value()) {
463  EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
464  EXPECT_TRUE(RectNear(contents_coverage.value(),
465  Rect::MakeXYWH(49.f, 39.f, 102.f, 102.f)));
466  }
467  }
468 }
469 
470 TEST(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius) {
471  Scalar sigma = 1.0;
474  fml::StatusOr<Scalar> derived_sigma =
475  CalculateSigmaForBlurRadius(radius, Matrix());
476  ASSERT_TRUE(derived_sigma.ok());
477  EXPECT_NEAR(sigma, derived_sigma.value(), 0.01f);
478 }
479 
481  BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
482  .blur_sigma = 1,
483  .blur_radius = 5,
484  .step_size = 1};
485  KernelSamples samples = GenerateBlurInfo(parameters);
486  EXPECT_EQ(samples.sample_count, 11);
487 
488  // Coefficients should add up to 1.
489  Scalar tally = 0;
490  for (int i = 0; i < samples.sample_count; ++i) {
491  tally += samples.samples[i].coefficient;
492  }
493  EXPECT_FLOAT_EQ(tally, 1.0f);
494 
495  // Verify the shape of the curve.
496  for (int i = 0; i < 4; ++i) {
497  EXPECT_FLOAT_EQ(samples.samples[i].coefficient,
498  samples.samples[10 - i].coefficient);
499  EXPECT_TRUE(samples.samples[i + 1].coefficient >
500  samples.samples[i].coefficient);
501  }
502 }
503 
504 TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) {
505  KernelSamples kernel_samples = {
506  .sample_count = 5,
507  .samples =
508  {
509  {
510  .uv_offset = Vector2(-2, 0),
511  .coefficient = 0.1f,
512  },
513  {
514  .uv_offset = Vector2(-1, 0),
515  .coefficient = 0.2f,
516  },
517  {
518  .uv_offset = Vector2(0, 0),
519  .coefficient = 0.4f,
520  },
521  {
522  .uv_offset = Vector2(1, 0),
523  .coefficient = 0.2f,
524  },
525  {
526  .uv_offset = Vector2(2, 0),
527  .coefficient = 0.1f,
528  },
529  },
530  };
531 
532  GaussianBlurPipeline::FragmentShader::KernelSamples blur_info =
533  LerpHackKernelSamples(kernel_samples);
534  EXPECT_EQ(blur_info.sample_count, 3);
535 
536  KernelSample* samples = kernel_samples.samples;
537 
538  //////////////////////////////////////////////////////////////////////////////
539  // Check output kernel.
540 
541  EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[0]),
542  Point(-1.3333333, 0));
543  EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[0]), 0.3);
544 
545  EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[1]), Point(0, 0));
546  EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[1]), 0.4);
547 
548  EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[2]), Point(1.333333, 0));
549  EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[2]), 0.3);
550 
551  //////////////////////////////////////////////////////////////////////////////
552  // Check output of fast kernel versus original kernel.
553 
554  Scalar data[5] = {0.25, 0.5, 0.5, 1.0, 0.2};
555  Scalar original_output =
556  samples[0].coefficient * data[0] + samples[1].coefficient * data[1] +
557  samples[2].coefficient * data[2] + samples[3].coefficient * data[3] +
558  samples[4].coefficient * data[4];
559 
560  auto lerp = [](const Point& point, Scalar left, Scalar right) {
561  Scalar int_part;
562  Scalar fract = fabsf(modf(point.x, &int_part));
563  if (point.x < 0) {
564  return left * fract + right * (1.0 - fract);
565  } else {
566  return left * (1.0 - fract) + right * fract;
567  }
568  };
569  Scalar fast_output =
570  /*1st*/ lerp(GetUVOffset(blur_info.sample_data[0]), data[0], data[1]) *
571  GetCoefficient(blur_info.sample_data[0]) +
572  /*2nd*/ data[2] * GetCoefficient(blur_info.sample_data[1]) +
573  /*3rd*/ lerp(GetUVOffset(blur_info.sample_data[2]), data[3], data[4]) *
574  GetCoefficient(blur_info.sample_data[2]);
575 
576  EXPECT_NEAR(original_output, fast_output, 0.01);
577 }
578 
579 TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) {
580  Scalar sigma = 10.0f;
581  int32_t blur_radius = static_cast<int32_t>(
583  BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
584  .blur_sigma = sigma,
585  .blur_radius = blur_radius,
586  .step_size = 1};
587  KernelSamples kernel_samples = GenerateBlurInfo(parameters);
588  EXPECT_EQ(kernel_samples.sample_count, 33);
589  GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples =
590  LerpHackKernelSamples(kernel_samples);
591  EXPECT_EQ(fast_kernel_samples.sample_count, 17);
592  float data[33];
593  srand(0);
594  for (int i = 0; i < 33; i++) {
595  data[i] = 255.0 * static_cast<double>(IMPELLER_RAND()) / RAND_MAX;
596  }
597 
598  auto sampler = [data](Point point) -> Scalar {
599  FML_CHECK(point.y == 0.0f);
600  FML_CHECK(point.x >= -16);
601  FML_CHECK(point.x <= 16);
602  Scalar fint_part;
603  Scalar fract = fabsf(modf(point.x, &fint_part));
604  if (fract == 0) {
605  int32_t int_part = static_cast<int32_t>(fint_part) + 16;
606  return data[int_part];
607  } else {
608  int32_t left = static_cast<int32_t>(floor(point.x)) + 16;
609  int32_t right = static_cast<int32_t>(ceil(point.x)) + 16;
610  if (point.x < 0) {
611  return fract * data[left] + (1.0 - fract) * data[right];
612  } else {
613  return (1.0 - fract) * data[left] + fract * data[right];
614  }
615  }
616  };
617 
618  Scalar output = 0.0;
619  for (int i = 0; i < kernel_samples.sample_count; ++i) {
620  auto sample = kernel_samples.samples[i];
621  output += sample.coefficient * sampler(sample.uv_offset);
622  }
623 
624  Scalar fast_output = 0.0;
625  for (int i = 0; i < fast_kernel_samples.sample_count; i++) {
626  fast_output += GetCoefficient(fast_kernel_samples.sample_data[i]) *
627  sampler(GetUVOffset(fast_kernel_samples.sample_data[i]));
628  }
629 
630  EXPECT_NEAR(output, fast_output, 0.1);
631 }
632 
634  Scalar sigma = 30.5f;
635  int32_t blur_radius = static_cast<int32_t>(
637  BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
638  .blur_sigma = sigma,
639  .blur_radius = blur_radius,
640  .step_size = 1};
641  KernelSamples kernel_samples = GenerateBlurInfo(parameters);
642  GaussianBlurPipeline::FragmentShader::KernelSamples frag_kernel_samples =
643  LerpHackKernelSamples(kernel_samples);
644  EXPECT_TRUE(frag_kernel_samples.sample_count <= kGaussianBlurMaxKernelSize);
645 }
646 
647 } // namespace testing
648 } // namespace impeller
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:62
std::shared_ptr< ContentContext > GetContentContext() const
@ kNormal
Blurred inside and outside.
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
Definition: filter_input.cc:19
std::vector< FilterInput::Ref > Vector
Definition: filter_input.h:33
Performs a bidirectional Gaussian blur.
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...
static Quad CalculateUVs(const std::shared_ptr< FilterInput > &filter_input, const Entity &entity, const Rect &source_rect, const ISize &texture_size)
std::shared_ptr< Context > GetContext() const
Definition: playground.cc:91
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
std::shared_ptr< Texture > MakeTexture(ISize size)
Create a texture that has been cleared to transparent black.
int32_t x
Vector2 blur_radius
Blur radius in source pixels based on scaled_sigma.
Vector2 scaled_sigma
Sigma when considering an entity's scale and the effect transform.
inline ::testing::AssertionResult RectNear(impeller::Rect a, impeller::Rect b)
#define EXPECT_RECT_NEAR(a, b)
#define EXPECT_POINT_NEAR(a, b)
ScopedObject< Object > Create(CtorArgs &&... args)
Definition: object.h:161
TEST(AllocationSizeTest, CanCreateTypedAllocations)
TEST_P(AiksTest, DrawAtlasNoColor)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
Point Vector2
Definition: point.h:429
static constexpr int32_t kGaussianBlurMaxKernelSize
float Scalar
Definition: scalar.h:19
TPoint< Scalar > Point
Definition: point.h:425
KernelSamples GenerateBlurInfo(BlurParameters parameters)
ISize64 ISize
Definition: size.h:162
std::array< Point, 4 > Quad
Definition: point.h:430
GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(KernelSamples parameters)
KernelSample samples[kMaxKernelSize]
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:223
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
constexpr static std::optional< TRect > MakePointBounds(const U &value)
Definition: rect.h:165
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:150
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:69