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