| 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"
8 #include "impeller/entity/contents/content_context.h"
9 #include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h"
10 #include "impeller/entity/contents/texture_contents.h"
11 #include "impeller/entity/entity_playground.h"
12 #include "impeller/geometry/color.h"
13 #include "impeller/geometry/geometry_asserts.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() *
68 Vector2(GaussianBlurFilterContents::ScaleSigma(x),
69 GaussianBlurFilterContents::ScaleSigma(x)))
70 .Abs();
71 Vector2 blur_radius = Vector2(
72 GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.x),
73 GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.y));
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
83 class GaussianBlurFilterContentsTest : public EntityPlayground {
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 };
111 INSTANTIATE_PLAYGROUND_SUITE(GaussianBlurFilterContentsTest);
112
113 TEST(GaussianBlurFilterContentsTest, Create) {
114 GaussianBlurFilterContents contents(
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
122 TEST(GaussianBlurFilterContentsTest, CoverageEmpty) {
123 GaussianBlurFilterContents contents(
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
134 TEST(GaussianBlurFilterContentsTest, CoverageSimple) {
135 GaussianBlurFilterContents contents(
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
148 TEST(GaussianBlurFilterContentsTest, CoverageWithSigma) {
149 fml::StatusOr<Scalar> sigma_radius_1 =
150 CalculateSigmaForBlurRadius(1.0, Matrix());
151 ASSERT_TRUE(sigma_radius_1.ok());
152 GaussianBlurFilterContents contents(
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());
173 GaussianBlurFilterContents contents(
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());
196 GaussianBlurFilterContents contents(
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
276 TEST_P(GaussianBlurFilterContentsTest,
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
310 TEST_P(GaussianBlurFilterContentsTest,
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}) *
325 Matrix::MakeRotationZ(Degrees(90.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
344 TEST_P(GaussianBlurFilterContentsTest, CalculateUVsSimple) {
345 std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
346 auto filter_input = FilterInput::Make(texture);
347 Entity entity;
348 Quad uvs = GaussianBlurFilterContents::CalculateUVs(
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
392 TEST_P(GaussianBlurFilterContentsTest,
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;
472 Scalar radius = GaussianBlurFilterContents::CalculateBlurRadius(
473 GaussianBlurFilterContents::ScaleSigma(sigma));
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
480 TEST(GaussianBlurFilterContentsTest, Coefficients) {
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>(
582 std::ceil(GaussianBlurFilterContents::CalculateBlurRadius(sigma)));
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
633 TEST(GaussianBlurFilterContentsTest, ChopHugeBlurs) {
634 Scalar sigma = 30.5f;
635 int32_t blur_radius = static_cast<int32_t>(
636 std::ceil(GaussianBlurFilterContents::CalculateBlurRadius(sigma)));
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
impeller::ContentContext
Definition: content_context.h:122
impeller::Entity
Definition: entity.h:26
impeller::Entity::SetTransform
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:62
impeller::Entity::TileMode::kDecal
@ kDecal
impeller::EntityPlayground
Definition: entity_playground.h:16
impeller::EntityPlayground::GetContentContext
std::shared_ptr< ContentContext > GetContentContext() const
Definition: entity_playground.cc:28
impeller::FilterContents::BlurStyle::kNormal
@ kNormal
Blurred inside and outside.
impeller::FilterInput::Make
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
Definition: filter_input.cc:19
impeller::FilterInput::Vector
std::vector< FilterInput::Ref > Vector
Definition: filter_input.h:33
impeller::GaussianBlurFilterContents
Performs a bidirectional Gaussian blur.
Definition: gaussian_blur_filter_contents.h:90
impeller::GaussianBlurFilterContents::GetSigmaX
Scalar GetSigmaX() const
Definition: gaussian_blur_filter_contents.h:100
impeller::GaussianBlurFilterContents::GetSigmaY
Scalar GetSigmaY() const
Definition: gaussian_blur_filter_contents.h:101
impeller::GaussianBlurFilterContents::CalculateBlurRadius
static Scalar CalculateBlurRadius(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:1019
impeller::GaussianBlurFilterContents::ScaleSigma
static Scalar ScaleSigma(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:1039
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:824
impeller::GaussianBlurFilterContents::CalculateScale
static Scalar CalculateScale(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:786
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:1023
impeller::Playground::GetContext
std::shared_ptr< Context > GetContext() const
Definition: playground.cc:91
impeller::RenderPass
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
impeller::testing::GaussianBlurFilterContentsTest
Definition: gaussian_blur_filter_contents_unittests.cc:83
impeller::testing::GaussianBlurFilterContentsTest::MakeTexture
std::shared_ptr< Texture > MakeTexture(ISize size)
Create a texture that has been cleared to transparent black.
Definition: gaussian_blur_filter_contents_unittests.cc:86
color.h
content_context.h
x
int32_t x
Definition: dl_golden_unittests.cc:458
entity_playground.h
uvs
Quad uvs
Definition: gaussian_blur_filter_contents.cc:298
blur_radius
Vector2 blur_radius
Blur radius in source pixels based on scaled_sigma.
Definition: gaussian_blur_filter_contents.cc:85
scaled_sigma
Vector2 scaled_sigma
Sigma when considering an entity's scale and the effect transform.
Definition: gaussian_blur_filter_contents.cc:83
gaussian_blur_filter_contents.h
IMPELLER_RAND
#define IMPELLER_RAND
Definition: gaussian_blur_filter_contents_unittests.cc:19
geometry_asserts.h
RectNear
inline ::testing::AssertionResult RectNear(impeller::Rect a, impeller::Rect b)
Definition: geometry_asserts.h:89
EXPECT_RECT_NEAR
#define EXPECT_RECT_NEAR(a, b)
Definition: geometry_asserts.h:203
EXPECT_POINT_NEAR
#define EXPECT_POINT_NEAR(a, b)
Definition: geometry_asserts.h:205
impeller::interop::Create
ScopedObject< Object > Create(CtorArgs &&... args)
Definition: object.h:161
impeller::testing::TEST
TEST(AllocationSizeTest, CanCreateTypedAllocations)
Definition: allocation_size_unittests.cc:10
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:59
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller
Definition: allocation.cc:12
impeller::Vector2
Point Vector2
Definition: point.h:429
impeller::kGaussianBlurMaxKernelSize
static constexpr int32_t kGaussianBlurMaxKernelSize
Definition: gaussian_blur_filter_contents.h:17
impeller::Scalar
float Scalar
Definition: scalar.h:19
impeller::Point
TPoint< Scalar > Point
Definition: point.h:425
impeller::BlendMode::kSrcOver
@ kSrcOver
impeller::GenerateBlurInfo
KernelSamples GenerateBlurInfo(BlurParameters parameters)
Definition: gaussian_blur_filter_contents.cc:1049
impeller::ISize
ISize64 ISize
Definition: size.h:162
impeller::Quad
std::array< Point, 4 > Quad
Definition: point.h:430
impeller::LerpHackKernelSamples
GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(KernelSamples parameters)
Definition: gaussian_blur_filter_contents.cc:1096
impeller::BlurParameters
Definition: gaussian_blur_filter_contents.h:22
impeller::BlurParameters::blur_uv_offset
Point blur_uv_offset
Definition: gaussian_blur_filter_contents.h:23
impeller::Degrees
Definition: scalar.h:66
impeller::KernelSample
Definition: gaussian_blur_filter_contents.h:30
impeller::KernelSample::coefficient
float coefficient
Definition: gaussian_blur_filter_contents.h:32
impeller::KernelSamples
Definition: gaussian_blur_filter_contents.h:40
impeller::KernelSamples::samples
KernelSample samples[kMaxKernelSize]
Definition: gaussian_blur_filter_contents.h:43
impeller::KernelSamples::sample_count
int sample_count
Definition: gaussian_blur_filter_contents.h:42
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
impeller::Matrix::MakeRotationZ
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:223
impeller::Matrix::MakeScale
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
impeller::TPoint< Scalar >
impeller::TPoint::x
Type x
Definition: point.h:31
impeller::TRect< Scalar >::MakePointBounds
constexpr static std::optional< TRect > MakePointBounds(const U &value)
Definition: rect.h:165
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
impeller::TRect< Scalar >::MakeSize
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:150
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
impeller::TSize
Definition: size.h:25
texture_contents.h
data
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:69