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