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  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
117  EXPECT_EQ(contents.GetSigmaX(), 0.0);
118  EXPECT_EQ(contents.GetSigmaY(), 0.0);
119 }
120 
123  /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal,
124  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
125  FilterInput::Vector inputs = {};
126  Entity entity;
127  std::optional<Rect> coverage =
128  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
129  ASSERT_FALSE(coverage.has_value());
130 }
131 
134  /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal,
135  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
136  FilterInput::Vector inputs = {
137  FilterInput::Make(Rect::MakeLTRB(10, 10, 110, 110))};
138  Entity entity;
139  std::optional<Rect> coverage =
140  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
141 
142  ASSERT_EQ(coverage, Rect::MakeLTRB(10, 10, 110, 110));
143 }
144 
146  fml::StatusOr<Scalar> sigma_radius_1 =
147  CalculateSigmaForBlurRadius(1.0, Matrix());
148  ASSERT_TRUE(sigma_radius_1.ok());
150  /*sigma_x=*/sigma_radius_1.value(),
151  /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
152  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
153  FilterInput::Vector inputs = {
154  FilterInput::Make(Rect::MakeLTRB(100, 100, 200, 200))};
155  Entity entity;
156  std::optional<Rect> coverage =
157  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
158 
159  EXPECT_TRUE(coverage.has_value());
160  if (coverage.has_value()) {
161  EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201));
162  }
163 }
164 
165 TEST_P(GaussianBlurFilterContentsTest, CoverageWithTexture) {
166  fml::StatusOr<Scalar> sigma_radius_1 =
167  CalculateSigmaForBlurRadius(1.0, Matrix());
168  ASSERT_TRUE(sigma_radius_1.ok());
170  /*sigma_X=*/sigma_radius_1.value(),
171  /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
172  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
173  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
174  FilterInput::Vector inputs = {FilterInput::Make(texture)};
175  Entity entity;
176  entity.SetTransform(Matrix::MakeTranslation({100, 100, 0}));
177  std::optional<Rect> coverage =
178  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
179 
180  EXPECT_TRUE(coverage.has_value());
181  if (coverage.has_value()) {
182  EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201));
183  }
184 }
185 
186 TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) {
187  Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0});
188  fml::StatusOr<Scalar> sigma_radius_1 =
189  CalculateSigmaForBlurRadius(1.0, effect_transform);
190  ASSERT_TRUE(sigma_radius_1.ok());
192  /*sigma_x=*/sigma_radius_1.value(),
193  /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
194  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
195  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
196  FilterInput::Vector inputs = {FilterInput::Make(texture)};
197  Entity entity;
198  entity.SetTransform(Matrix::MakeTranslation({100, 100, 0}));
199  std::optional<Rect> coverage =
200  contents.GetFilterCoverage(inputs, entity, effect_transform);
201  EXPECT_TRUE(coverage.has_value());
202  if (coverage.has_value()) {
203  EXPECT_RECT_NEAR(coverage.value(),
204  Rect::MakeLTRB(100 - 1, 100 - 1, 200 + 1, 200 + 1));
205  }
206 }
207 
208 TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) {
209  fml::StatusOr<Scalar> sigma_radius_1 =
210  CalculateSigmaForBlurRadius(1.0, Matrix());
211  ASSERT_TRUE(sigma_radius_1.ok());
212  auto contents = std::make_unique<GaussianBlurFilterContents>(
213  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
214  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
215  std::optional<Rect> coverage = contents->GetFilterSourceCoverage(
216  /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}),
217  /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200));
218  EXPECT_TRUE(coverage.has_value());
219  if (coverage.has_value()) {
220  EXPECT_RECT_NEAR(coverage.value(),
221  Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2));
222  }
223 }
224 
225 TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) {
226  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1.0f), 1);
227  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(2.0f), 1);
228  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(3.0f), 1);
229  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(4.0f), 1);
230  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(16.0f), 0.25);
231  // Hang on to 1/8 as long as possible.
232  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(95.0f), 0.125);
233  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(96.0f), 0.0625);
234  // Downsample clamped to 1/16th.
235  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1024.0f), 0.0625);
236 }
237 
238 TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) {
239  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
240  fml::StatusOr<Scalar> sigma_radius_1 =
241  CalculateSigmaForBlurRadius(1.0, Matrix());
242  ASSERT_TRUE(sigma_radius_1.ok());
243  auto contents = std::make_unique<GaussianBlurFilterContents>(
244  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
245  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
246  contents->SetInputs({FilterInput::Make(texture)});
247  std::shared_ptr<ContentContext> renderer = GetContentContext();
248 
249  Entity entity;
250  std::optional<Entity> result =
251  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
252  EXPECT_TRUE(result.has_value());
253  if (result.has_value()) {
254  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
255  std::optional<Rect> result_coverage = result.value().GetCoverage();
256  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
257  EXPECT_TRUE(result_coverage.has_value());
258  EXPECT_TRUE(contents_coverage.has_value());
259  if (result_coverage.has_value() && contents_coverage.has_value()) {
260  EXPECT_TRUE(RectNear(contents_coverage.value(),
261  Rect::MakeLTRB(-1, -1, 101, 101)));
262  EXPECT_TRUE(
263  RectNear(result_coverage.value(), Rect::MakeLTRB(-1, -1, 101, 101)));
264  }
265  }
266 }
267 
269  RenderCoverageMatchesGetCoverageTranslate) {
270  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
271  fml::StatusOr<Scalar> sigma_radius_1 =
272  CalculateSigmaForBlurRadius(1.0, Matrix());
273  ASSERT_TRUE(sigma_radius_1.ok());
274  auto contents = std::make_unique<GaussianBlurFilterContents>(
275  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
276  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
277  contents->SetInputs({FilterInput::Make(texture)});
278  std::shared_ptr<ContentContext> renderer = GetContentContext();
279 
280  Entity entity;
281  entity.SetTransform(Matrix::MakeTranslation({100, 200, 0}));
282  std::optional<Entity> result =
283  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
284 
285  EXPECT_TRUE(result.has_value());
286  if (result.has_value()) {
287  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
288  std::optional<Rect> result_coverage = result.value().GetCoverage();
289  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
290  EXPECT_TRUE(result_coverage.has_value());
291  EXPECT_TRUE(contents_coverage.has_value());
292  if (result_coverage.has_value() && contents_coverage.has_value()) {
293  EXPECT_TRUE(RectNear(contents_coverage.value(),
294  Rect::MakeLTRB(99, 199, 201, 301)));
295  EXPECT_TRUE(
296  RectNear(result_coverage.value(), Rect::MakeLTRB(99, 199, 201, 301)));
297  }
298  }
299 }
300 
302  RenderCoverageMatchesGetCoverageRotated) {
303  std::shared_ptr<Texture> texture = MakeTexture(ISize(400, 300));
304  fml::StatusOr<Scalar> sigma_radius_1 =
305  CalculateSigmaForBlurRadius(1.0, Matrix());
306  auto contents = std::make_unique<GaussianBlurFilterContents>(
307  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
308  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
309  contents->SetInputs({FilterInput::Make(texture)});
310  std::shared_ptr<ContentContext> renderer = GetContentContext();
311 
312  Entity entity;
313  // Rotate around the top left corner, then push it over to (100, 100).
314  entity.SetTransform(Matrix::MakeTranslation({400, 100, 0}) *
316  std::optional<Entity> result =
317  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
318  EXPECT_TRUE(result.has_value());
319  if (result.has_value()) {
320  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
321  std::optional<Rect> result_coverage = result.value().GetCoverage();
322  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
323  EXPECT_TRUE(result_coverage.has_value());
324  EXPECT_TRUE(contents_coverage.has_value());
325  if (result_coverage.has_value() && contents_coverage.has_value()) {
326  EXPECT_TRUE(RectNear(contents_coverage.value(),
327  Rect::MakeLTRB(99, 99, 401, 501)));
328  EXPECT_TRUE(
329  RectNear(result_coverage.value(), Rect::MakeLTRB(99, 99, 401, 501)));
330  }
331  }
332 }
333 
335  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
336  auto filter_input = FilterInput::Make(texture);
337  Entity entity;
339  filter_input, entity, Rect::MakeSize(ISize(100, 100)), ISize(100, 100));
340  std::optional<Rect> uvs_bounds = Rect::MakePointBounds(uvs);
341  EXPECT_TRUE(uvs_bounds.has_value());
342  if (uvs_bounds.has_value()) {
343  EXPECT_TRUE(RectNear(uvs_bounds.value(), Rect::MakeXYWH(0, 0, 1, 1)));
344  }
345 }
346 
347 TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithDestinationRect) {
348  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
349  auto texture_contents = std::make_shared<TextureContents>();
350  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
351  texture_contents->SetTexture(texture);
352  texture_contents->SetDestinationRect(Rect::MakeXYWH(
353  50, 40, texture->GetSize().width, texture->GetSize().height));
354 
355  fml::StatusOr<Scalar> sigma_radius_1 =
356  CalculateSigmaForBlurRadius(1.0, Matrix());
357  auto contents = std::make_unique<GaussianBlurFilterContents>(
358  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
359  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
360  contents->SetInputs({FilterInput::Make(texture_contents)});
361  std::shared_ptr<ContentContext> renderer = GetContentContext();
362 
363  Entity entity;
364  std::optional<Entity> result =
365  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
366  EXPECT_TRUE(result.has_value());
367  if (result.has_value()) {
368  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
369  std::optional<Rect> result_coverage = result.value().GetCoverage();
370  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
371  EXPECT_TRUE(result_coverage.has_value());
372  EXPECT_TRUE(contents_coverage.has_value());
373  if (result_coverage.has_value() && contents_coverage.has_value()) {
374  EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
375  EXPECT_TRUE(RectNear(result_coverage.value(),
376  Rect::MakeLTRB(49.f, 39.f, 151.f, 141.f)));
377  }
378  }
379 }
380 
382  TextureContentsWithDestinationRectScaled) {
383  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
384  auto texture_contents = std::make_shared<TextureContents>();
385  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
386  texture_contents->SetTexture(texture);
387  texture_contents->SetDestinationRect(Rect::MakeXYWH(
388  50, 40, texture->GetSize().width, texture->GetSize().height));
389 
390  fml::StatusOr<Scalar> sigma_radius_1 =
391  CalculateSigmaForBlurRadius(1.0, Matrix());
392  auto contents = std::make_unique<GaussianBlurFilterContents>(
393  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
395  /*mask_geometry=*/nullptr);
396  contents->SetInputs({FilterInput::Make(texture_contents)});
397  std::shared_ptr<ContentContext> renderer = GetContentContext();
398 
399  Entity entity;
400  entity.SetTransform(Matrix::MakeScale({2.0, 2.0, 1.0}));
401  std::optional<Entity> result =
402  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
403  EXPECT_TRUE(result.has_value());
404  if (result.has_value()) {
405  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
406  std::optional<Rect> result_coverage = result.value().GetCoverage();
407  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
408  EXPECT_TRUE(result_coverage.has_value());
409  EXPECT_TRUE(contents_coverage.has_value());
410  if (result_coverage.has_value() && contents_coverage.has_value()) {
411  EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
412  // Scaling a blurred entity doesn't seem to scale the blur radius linearly
413  // when comparing results with rrect_blur. That's why this is not
414  // Rect::MakeXYWH(98.f, 78.f, 204.0f, 204.f).
415  EXPECT_TRUE(RectNear(contents_coverage.value(),
416  Rect::MakeXYWH(94.f, 74.f, 212.0f, 212.f)));
417  }
418  }
419 }
420 
421 TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithEffectTransform) {
422  Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0});
423  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
424  auto texture_contents = std::make_shared<TextureContents>();
425  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
426  texture_contents->SetTexture(texture);
427  texture_contents->SetDestinationRect(Rect::MakeXYWH(
428  50, 40, texture->GetSize().width, texture->GetSize().height));
429 
430  fml::StatusOr<Scalar> sigma_radius_1 =
431  CalculateSigmaForBlurRadius(1.0, effect_transform);
432  ASSERT_TRUE(sigma_radius_1.ok());
433  auto contents = std::make_unique<GaussianBlurFilterContents>(
434  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
435  FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
436  contents->SetInputs({FilterInput::Make(texture_contents)});
437  contents->SetEffectTransform(effect_transform);
438  std::shared_ptr<ContentContext> renderer = GetContentContext();
439 
440  Entity entity;
441  std::optional<Entity> result =
442  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
443  EXPECT_TRUE(result.has_value());
444  if (result.has_value()) {
445  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
446  std::optional<Rect> result_coverage = result.value().GetCoverage();
447  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
448  EXPECT_TRUE(result_coverage.has_value());
449  EXPECT_TRUE(contents_coverage.has_value());
450  if (result_coverage.has_value() && contents_coverage.has_value()) {
451  EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
452  EXPECT_TRUE(RectNear(contents_coverage.value(),
453  Rect::MakeXYWH(49.f, 39.f, 102.f, 102.f)));
454  }
455  }
456 }
457 
458 TEST(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius) {
459  Scalar sigma = 1.0;
462  fml::StatusOr<Scalar> derived_sigma =
463  CalculateSigmaForBlurRadius(radius, Matrix());
464  ASSERT_TRUE(derived_sigma.ok());
465  EXPECT_NEAR(sigma, derived_sigma.value(), 0.01f);
466 }
467 
469  BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
470  .blur_sigma = 1,
471  .blur_radius = 5,
472  .step_size = 1};
473  KernelSamples samples = GenerateBlurInfo(parameters);
474  EXPECT_EQ(samples.sample_count, 11);
475 
476  // Coefficients should add up to 1.
477  Scalar tally = 0;
478  for (int i = 0; i < samples.sample_count; ++i) {
479  tally += samples.samples[i].coefficient;
480  }
481  EXPECT_FLOAT_EQ(tally, 1.0f);
482 
483  // Verify the shape of the curve.
484  for (int i = 0; i < 4; ++i) {
485  EXPECT_FLOAT_EQ(samples.samples[i].coefficient,
486  samples.samples[10 - i].coefficient);
487  EXPECT_TRUE(samples.samples[i + 1].coefficient >
488  samples.samples[i].coefficient);
489  }
490 }
491 
492 TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) {
493  KernelSamples kernel_samples = {
494  .sample_count = 5,
495  .samples =
496  {
497  {
498  .uv_offset = Vector2(-2, 0),
499  .coefficient = 0.1f,
500  },
501  {
502  .uv_offset = Vector2(-1, 0),
503  .coefficient = 0.2f,
504  },
505  {
506  .uv_offset = Vector2(0, 0),
507  .coefficient = 0.4f,
508  },
509  {
510  .uv_offset = Vector2(1, 0),
511  .coefficient = 0.2f,
512  },
513  {
514  .uv_offset = Vector2(2, 0),
515  .coefficient = 0.1f,
516  },
517  },
518  };
519 
520  GaussianBlurPipeline::FragmentShader::KernelSamples blur_info =
521  LerpHackKernelSamples(kernel_samples);
522  EXPECT_EQ(blur_info.sample_count, 3);
523 
524  KernelSample* samples = kernel_samples.samples;
525 
526  //////////////////////////////////////////////////////////////////////////////
527  // Check output kernel.
528 
529  EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[0]),
530  Point(-1.3333333, 0));
531  EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[0]), 0.3);
532 
533  EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[1]), Point(0, 0));
534  EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[1]), 0.4);
535 
536  EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[2]), Point(1.333333, 0));
537  EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[2]), 0.3);
538 
539  //////////////////////////////////////////////////////////////////////////////
540  // Check output of fast kernel versus original kernel.
541 
542  Scalar data[5] = {0.25, 0.5, 0.5, 1.0, 0.2};
543  Scalar original_output =
544  samples[0].coefficient * data[0] + samples[1].coefficient * data[1] +
545  samples[2].coefficient * data[2] + samples[3].coefficient * data[3] +
546  samples[4].coefficient * data[4];
547 
548  auto lerp = [](const Point& point, Scalar left, Scalar right) {
549  Scalar int_part;
550  Scalar fract = fabsf(modf(point.x, &int_part));
551  if (point.x < 0) {
552  return left * fract + right * (1.0 - fract);
553  } else {
554  return left * (1.0 - fract) + right * fract;
555  }
556  };
557  Scalar fast_output =
558  /*1st*/ lerp(GetUVOffset(blur_info.sample_data[0]), data[0], data[1]) *
559  GetCoefficient(blur_info.sample_data[0]) +
560  /*2nd*/ data[2] * GetCoefficient(blur_info.sample_data[1]) +
561  /*3rd*/ lerp(GetUVOffset(blur_info.sample_data[2]), data[3], data[4]) *
562  GetCoefficient(blur_info.sample_data[2]);
563 
564  EXPECT_NEAR(original_output, fast_output, 0.01);
565 }
566 
567 TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) {
568  Scalar sigma = 10.0f;
569  int32_t blur_radius = static_cast<int32_t>(
571  BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
572  .blur_sigma = sigma,
573  .blur_radius = blur_radius,
574  .step_size = 1};
575  KernelSamples kernel_samples = GenerateBlurInfo(parameters);
576  EXPECT_EQ(kernel_samples.sample_count, 33);
577  GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples =
578  LerpHackKernelSamples(kernel_samples);
579  EXPECT_EQ(fast_kernel_samples.sample_count, 17);
580  float data[33];
581  srand(0);
582  for (int i = 0; i < 33; i++) {
583  data[i] = 255.0 * static_cast<double>(IMPELLER_RAND()) / RAND_MAX;
584  }
585 
586  auto sampler = [data](Point point) -> Scalar {
587  FML_CHECK(point.y == 0.0f);
588  FML_CHECK(point.x >= -16);
589  FML_CHECK(point.x <= 16);
590  Scalar fint_part;
591  Scalar fract = fabsf(modf(point.x, &fint_part));
592  if (fract == 0) {
593  int32_t int_part = static_cast<int32_t>(fint_part) + 16;
594  return data[int_part];
595  } else {
596  int32_t left = static_cast<int32_t>(floor(point.x)) + 16;
597  int32_t right = static_cast<int32_t>(ceil(point.x)) + 16;
598  if (point.x < 0) {
599  return fract * data[left] + (1.0 - fract) * data[right];
600  } else {
601  return (1.0 - fract) * data[left] + fract * data[right];
602  }
603  }
604  };
605 
606  Scalar output = 0.0;
607  for (int i = 0; i < kernel_samples.sample_count; ++i) {
608  auto sample = kernel_samples.samples[i];
609  output += sample.coefficient * sampler(sample.uv_offset);
610  }
611 
612  Scalar fast_output = 0.0;
613  for (int i = 0; i < fast_kernel_samples.sample_count; i++) {
614  fast_output += GetCoefficient(fast_kernel_samples.sample_data[i]) *
615  sampler(GetUVOffset(fast_kernel_samples.sample_data[i]));
616  }
617 
618  EXPECT_NEAR(output, fast_output, 0.1);
619 }
620 
622  Scalar sigma = 30.5f;
623  int32_t blur_radius = static_cast<int32_t>(
625  BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
626  .blur_sigma = sigma,
627  .blur_radius = blur_radius,
628  .step_size = 1};
629  KernelSamples kernel_samples = GenerateBlurInfo(parameters);
630  GaussianBlurPipeline::FragmentShader::KernelSamples frag_kernel_samples =
631  LerpHackKernelSamples(kernel_samples);
632  EXPECT_TRUE(frag_kernel_samples.sample_count <= kGaussianBlurMaxKernelSize);
633 }
634 
635 } // namespace testing
636 } // namespace impeller
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:60
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
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:331
static constexpr int32_t kGaussianBlurMaxKernelSize
float Scalar
Definition: scalar.h:19
TPoint< Scalar > Point
Definition: point.h:327
KernelSamples GenerateBlurInfo(BlurParameters parameters)
ISize64 ISize
Definition: size.h:162
std::array< Point, 4 > Quad
Definition: point.h:332
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:68