Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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
6#include "fml/status_or.h"
7#include "gmock/gmock.h"
15
16#if FML_OS_MACOSX
17#define IMPELLER_RAND arc4random
18#else
19#define IMPELLER_RAND rand
20#endif
21
22namespace impeller {
23namespace testing {
24
25namespace {
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.
30fml::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) {
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
55Scalar GetCoefficient(const Vector4& vec) {
56 return vec.z;
57}
58
59Vector2 GetUVOffset(const Vector4& vec) {
60 return vec.xy();
61}
62
63fml::StatusOr<Scalar> CalculateSigmaForBlurRadius(
64 Scalar radius,
65 const Matrix& effect_transform) {
66 auto f = [effect_transform](Scalar x) -> Scalar {
67 Vector2 scaled_sigma = (effect_transform.Basis() *
70 .Abs();
74 return std::max(blur_radius.x, blur_radius.y);
75 };
76 // The newtonian method is used here since inverting the function is
77 // non-trivial because of conditional logic and would be fragile to changes.
78 return LowerBoundNewtonianMethod(f, radius, 2.f, 0.001f);
79}
80
81} // namespace
82
84 public:
85 /// Create a texture that has been cleared to transparent black.
86 std::shared_ptr<Texture> MakeTexture(ISize size) {
87 std::shared_ptr<CommandBuffer> command_buffer =
88 GetContentContext()->GetContext()->CreateCommandBuffer();
89 if (!command_buffer) {
90 return nullptr;
91 }
92
93 auto render_target = GetContentContext()->MakeSubpass(
94 "Clear Subpass", size, command_buffer,
95 [](const ContentContext&, RenderPass&) { return true; });
96
98 ->GetContext()
99 ->GetCommandQueue()
100 ->Submit(/*buffers=*/{command_buffer})
101 .ok()) {
102 return nullptr;
103 }
104
105 if (render_target.ok()) {
106 return render_target.value().GetRenderTargetTexture();
107 }
108 return nullptr;
109 }
110};
112
115 /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal,
116 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
117 /*mask_geometry=*/nullptr);
118 EXPECT_EQ(contents.GetSigmaX(), 0.0);
119 EXPECT_EQ(contents.GetSigmaY(), 0.0);
120}
121
124 /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal,
125 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
126 /*mask_geometry=*/nullptr);
127 FilterInput::Vector inputs = {};
128 Entity entity;
129 std::optional<Rect> coverage =
130 contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
131 ASSERT_FALSE(coverage.has_value());
132}
133
136 /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal,
137 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
138 /*mask_geometry=*/nullptr);
139 FilterInput::Vector inputs = {
140 FilterInput::Make(Rect::MakeLTRB(10, 10, 110, 110))};
141 Entity entity;
142 std::optional<Rect> coverage =
143 contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
144
145 ASSERT_EQ(coverage, Rect::MakeLTRB(10, 10, 110, 110));
146}
147
149 fml::StatusOr<Scalar> sigma_radius_1 =
150 CalculateSigmaForBlurRadius(1.0, Matrix());
151 ASSERT_TRUE(sigma_radius_1.ok());
153 /*sigma_x=*/sigma_radius_1.value(),
154 /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
155 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
156 /*mask_geometry=*/nullptr);
157 FilterInput::Vector inputs = {
158 FilterInput::Make(Rect::MakeLTRB(100, 100, 200, 200))};
159 Entity entity;
160 std::optional<Rect> coverage =
161 contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
162
163 EXPECT_TRUE(coverage.has_value());
164 if (coverage.has_value()) {
165 EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201));
166 }
167}
168
170 fml::StatusOr<Scalar> sigma_radius_1 =
171 CalculateSigmaForBlurRadius(1.0, Matrix());
172 ASSERT_TRUE(sigma_radius_1.ok());
174 /*sigma_X=*/sigma_radius_1.value(),
175 /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
176 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
177 /*mask_geometry=*/nullptr);
178 std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
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
191TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) {
192 Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0});
193 fml::StatusOr<Scalar> sigma_radius_1 =
194 CalculateSigmaForBlurRadius(1.0, effect_transform);
195 ASSERT_TRUE(sigma_radius_1.ok());
197 /*sigma_x=*/sigma_radius_1.value(),
198 /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
199 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
200 /*mask_geometry=*/nullptr);
201 std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
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
214TEST(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
232TEST(GaussianBlurFilterContentsTest, FilterSourceCoverageNegativeScale) {
233 fml::StatusOr<Scalar> sigma_radius_1 =
234 CalculateSigmaForBlurRadius(1.0, Matrix());
235 ASSERT_TRUE(sigma_radius_1.ok());
236 auto contents = std::make_unique<GaussianBlurFilterContents>(
237 sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
238 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
239 /*mask_geometry=*/nullptr);
240
241 // Negative scale should still result in an expanded coverage rect.
242 std::optional<Rect> coverage = contents->GetFilterSourceCoverage(
243 /*effect_transform=*/Matrix::MakeScale({-2.0, 2.0, 1.0}),
244 /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200));
245 ASSERT_TRUE(coverage.has_value());
246 if (coverage.has_value()) {
247 EXPECT_RECT_NEAR(coverage.value(),
248 Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2));
249 }
250}
251
252TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) {
257 EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(16.0f), 0.25);
258 // Hang on to 1/8 as long as possible.
259 EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(95.0f), 0.125);
260 EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(96.0f), 0.0625);
261 // Downsample clamped to 1/16th.
262 EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1024.0f), 0.0625);
263}
264
265TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) {
266 std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
267 fml::StatusOr<Scalar> sigma_radius_1 =
268 CalculateSigmaForBlurRadius(1.0, Matrix());
269 ASSERT_TRUE(sigma_radius_1.ok());
270 auto contents = std::make_unique<GaussianBlurFilterContents>(
271 sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
272 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
273 /*mask_geometry=*/nullptr);
274 contents->SetInputs({FilterInput::Make(texture)});
275 std::shared_ptr<ContentContext> renderer = GetContentContext();
276
277 Entity entity;
278 std::optional<Entity> result =
279 contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
280 EXPECT_TRUE(result.has_value());
281 if (result.has_value()) {
282 EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
283 std::optional<Rect> result_coverage = result.value().GetCoverage();
284 std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
285 EXPECT_TRUE(result_coverage.has_value());
286 EXPECT_TRUE(contents_coverage.has_value());
287 if (result_coverage.has_value() && contents_coverage.has_value()) {
288 EXPECT_TRUE(RectNear(contents_coverage.value(),
289 Rect::MakeLTRB(-1, -1, 101, 101)));
290 EXPECT_TRUE(
291 RectNear(result_coverage.value(), Rect::MakeLTRB(-1, -1, 101, 101)));
292 }
293 }
294}
295
297 RenderCoverageMatchesGetCoverageTranslate) {
298 std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
299 fml::StatusOr<Scalar> sigma_radius_1 =
300 CalculateSigmaForBlurRadius(1.0, Matrix());
301 ASSERT_TRUE(sigma_radius_1.ok());
302 auto contents = std::make_unique<GaussianBlurFilterContents>(
303 sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
304 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
305 /*mask_geometry=*/nullptr);
306 contents->SetInputs({FilterInput::Make(texture)});
307 std::shared_ptr<ContentContext> renderer = GetContentContext();
308
309 Entity entity;
310 entity.SetTransform(Matrix::MakeTranslation({100, 200, 0}));
311 std::optional<Entity> result =
312 contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
313
314 EXPECT_TRUE(result.has_value());
315 if (result.has_value()) {
316 EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
317 std::optional<Rect> result_coverage = result.value().GetCoverage();
318 std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
319 EXPECT_TRUE(result_coverage.has_value());
320 EXPECT_TRUE(contents_coverage.has_value());
321 if (result_coverage.has_value() && contents_coverage.has_value()) {
322 EXPECT_TRUE(RectNear(contents_coverage.value(),
323 Rect::MakeLTRB(99, 199, 201, 301)));
324 EXPECT_TRUE(
325 RectNear(result_coverage.value(), Rect::MakeLTRB(99, 199, 201, 301)));
326 }
327 }
328}
329
331 RenderCoverageMatchesGetCoverageRotated) {
332 std::shared_ptr<Texture> texture = MakeTexture(ISize(400, 300));
333 fml::StatusOr<Scalar> sigma_radius_1 =
334 CalculateSigmaForBlurRadius(1.0, Matrix());
335 auto contents = std::make_unique<GaussianBlurFilterContents>(
336 sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
337 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
338 /*mask_geometry=*/nullptr);
339 contents->SetInputs({FilterInput::Make(texture)});
340 std::shared_ptr<ContentContext> renderer = GetContentContext();
341
342 Entity entity;
343 // Rotate around the top left corner, then push it over to (100, 100).
344 entity.SetTransform(Matrix::MakeTranslation({400, 100, 0}) *
346 std::optional<Entity> result =
347 contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
348 EXPECT_TRUE(result.has_value());
349 if (result.has_value()) {
350 EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
351 std::optional<Rect> result_coverage = result.value().GetCoverage();
352 std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
353 EXPECT_TRUE(result_coverage.has_value());
354 EXPECT_TRUE(contents_coverage.has_value());
355 if (result_coverage.has_value() && contents_coverage.has_value()) {
356 EXPECT_TRUE(RectNear(contents_coverage.value(),
357 Rect::MakeLTRB(99, 99, 401, 501)));
358 EXPECT_TRUE(
359 RectNear(result_coverage.value(), Rect::MakeLTRB(99, 99, 401, 501)));
360 }
361 }
362}
363
365 std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
366 auto filter_input = FilterInput::Make(texture);
367 Entity entity;
369 filter_input, entity, Rect::MakeSize(ISize(100, 100)), ISize(100, 100));
370 std::optional<Rect> uvs_bounds = Rect::MakePointBounds(uvs);
371 EXPECT_TRUE(uvs_bounds.has_value());
372 if (uvs_bounds.has_value()) {
373 EXPECT_TRUE(RectNear(uvs_bounds.value(), Rect::MakeXYWH(0, 0, 1, 1)));
374 }
375}
376
377TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithDestinationRect) {
378 std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
379 auto texture_contents = std::make_shared<TextureContents>();
380 texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
381 texture_contents->SetTexture(texture);
382 texture_contents->SetDestinationRect(Rect::MakeXYWH(
383 50, 40, texture->GetSize().width, texture->GetSize().height));
384
385 fml::StatusOr<Scalar> sigma_radius_1 =
386 CalculateSigmaForBlurRadius(1.0, Matrix());
387 auto contents = std::make_unique<GaussianBlurFilterContents>(
388 sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
389 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
390 /*mask_geometry=*/nullptr);
391 contents->SetInputs({FilterInput::Make(texture_contents)});
392 std::shared_ptr<ContentContext> renderer = GetContentContext();
393
394 Entity entity;
395 std::optional<Entity> result =
396 contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
397 EXPECT_TRUE(result.has_value());
398 if (result.has_value()) {
399 EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
400 std::optional<Rect> result_coverage = result.value().GetCoverage();
401 std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
402 EXPECT_TRUE(result_coverage.has_value());
403 EXPECT_TRUE(contents_coverage.has_value());
404 if (result_coverage.has_value() && contents_coverage.has_value()) {
405 EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
406 EXPECT_TRUE(RectNear(result_coverage.value(),
407 Rect::MakeLTRB(49.f, 39.f, 151.f, 141.f)));
408 }
409 }
410}
411
413 TextureContentsWithDestinationRectScaled) {
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, Matrix());
423 auto contents = std::make_unique<GaussianBlurFilterContents>(
424 sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
425 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
426 /*mask_geometry=*/nullptr);
427 contents->SetInputs({FilterInput::Make(texture_contents)});
428 std::shared_ptr<ContentContext> renderer = GetContentContext();
429
430 Entity entity;
431 entity.SetTransform(Matrix::MakeScale({2.0, 2.0, 1.0}));
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::kSrcOver);
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 // Scaling a blurred entity doesn't seem to scale the blur radius linearly
444 // when comparing results with rrect_blur. That's why this is not
445 // Rect::MakeXYWH(98.f, 78.f, 204.0f, 204.f).
446 EXPECT_TRUE(RectNear(contents_coverage.value(),
447 Rect::MakeXYWH(94.f, 74.f, 212.0f, 212.f)));
448 }
449 }
450}
451
452TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithEffectTransform) {
453 Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0});
454 std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
455 auto texture_contents = std::make_shared<TextureContents>();
456 texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
457 texture_contents->SetTexture(texture);
458 texture_contents->SetDestinationRect(Rect::MakeXYWH(
459 50, 40, texture->GetSize().width, texture->GetSize().height));
460
461 fml::StatusOr<Scalar> sigma_radius_1 =
462 CalculateSigmaForBlurRadius(1.0, effect_transform);
463 ASSERT_TRUE(sigma_radius_1.ok());
464 auto contents = std::make_unique<GaussianBlurFilterContents>(
465 sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
466 /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
467 /*mask_geometry=*/nullptr);
468 contents->SetInputs({FilterInput::Make(texture_contents)});
469 contents->SetEffectTransform(effect_transform);
470 std::shared_ptr<ContentContext> renderer = GetContentContext();
471
472 Entity entity;
473 std::optional<Entity> result =
474 contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
475 EXPECT_TRUE(result.has_value());
476 if (result.has_value()) {
477 EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
478 std::optional<Rect> result_coverage = result.value().GetCoverage();
479 std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
480 EXPECT_TRUE(result_coverage.has_value());
481 EXPECT_TRUE(contents_coverage.has_value());
482 if (result_coverage.has_value() && contents_coverage.has_value()) {
483 EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
484 EXPECT_TRUE(RectNear(contents_coverage.value(),
485 Rect::MakeXYWH(49.f, 39.f, 102.f, 102.f)));
486 }
487 }
488}
489
490TEST(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius) {
491 Scalar sigma = 1.0;
494 fml::StatusOr<Scalar> derived_sigma =
495 CalculateSigmaForBlurRadius(radius, Matrix());
496 ASSERT_TRUE(derived_sigma.ok());
497 EXPECT_NEAR(sigma, derived_sigma.value(), 0.01f);
498}
499
501 BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
502 .blur_sigma = 1,
503 .blur_radius = 5,
504 .step_size = 1};
505 KernelSamples samples = GenerateBlurInfo(parameters);
506 EXPECT_EQ(samples.sample_count, 11);
507
508 // Coefficients should add up to 1.
509 Scalar tally = 0;
510 for (int i = 0; i < samples.sample_count; ++i) {
511 tally += samples.samples[i].coefficient;
512 }
513 EXPECT_FLOAT_EQ(tally, 1.0f);
514
515 // Verify the shape of the curve.
516 for (int i = 0; i < 4; ++i) {
517 EXPECT_FLOAT_EQ(samples.samples[i].coefficient,
518 samples.samples[10 - i].coefficient);
519 EXPECT_TRUE(samples.samples[i + 1].coefficient >
520 samples.samples[i].coefficient);
521 }
522}
523
524TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) {
525 KernelSamples kernel_samples = {
526 .sample_count = 5,
527 .samples =
528 {
529 {
530 .uv_offset = Vector2(-2, 0),
531 .coefficient = 0.1f,
532 },
533 {
534 .uv_offset = Vector2(-1, 0),
535 .coefficient = 0.2f,
536 },
537 {
538 .uv_offset = Vector2(0, 0),
539 .coefficient = 0.4f,
540 },
541 {
542 .uv_offset = Vector2(1, 0),
543 .coefficient = 0.2f,
544 },
545 {
546 .uv_offset = Vector2(2, 0),
547 .coefficient = 0.1f,
548 },
549 },
550 };
551
552 GaussianBlurPipeline::FragmentShader::KernelSamples blur_info =
553 LerpHackKernelSamples(kernel_samples);
554 EXPECT_EQ(blur_info.sample_count, 3);
555
556 KernelSample* samples = kernel_samples.samples;
557
558 //////////////////////////////////////////////////////////////////////////////
559 // Check output kernel.
560
561 EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[0]),
562 Point(-1.3333333, 0));
563 EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[0]), 0.3);
564
565 EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[1]), Point(0, 0));
566 EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[1]), 0.4);
567
568 EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[2]), Point(1.333333, 0));
569 EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[2]), 0.3);
570
571 //////////////////////////////////////////////////////////////////////////////
572 // Check output of fast kernel versus original kernel.
573
574 Scalar data[5] = {0.25, 0.5, 0.5, 1.0, 0.2};
575 Scalar original_output =
576 samples[0].coefficient * data[0] + samples[1].coefficient * data[1] +
577 samples[2].coefficient * data[2] + samples[3].coefficient * data[3] +
578 samples[4].coefficient * data[4];
579
580 auto lerp = [](const Point& point, Scalar left, Scalar right) {
581 Scalar int_part;
582 Scalar fract = fabsf(modf(point.x, &int_part));
583 if (point.x < 0) {
584 return left * fract + right * (1.0 - fract);
585 } else {
586 return left * (1.0 - fract) + right * fract;
587 }
588 };
589 Scalar fast_output =
590 /*1st*/ lerp(GetUVOffset(blur_info.sample_data[0]), data[0], data[1]) *
591 GetCoefficient(blur_info.sample_data[0]) +
592 /*2nd*/ data[2] * GetCoefficient(blur_info.sample_data[1]) +
593 /*3rd*/ lerp(GetUVOffset(blur_info.sample_data[2]), data[3], data[4]) *
594 GetCoefficient(blur_info.sample_data[2]);
595
596 EXPECT_NEAR(original_output, fast_output, 0.01);
597}
598
599TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) {
600 Scalar sigma = 10.0f;
601 int32_t blur_radius = static_cast<int32_t>(
603 BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
604 .blur_sigma = sigma,
605 .blur_radius = blur_radius,
606 .step_size = 1};
607 KernelSamples kernel_samples = GenerateBlurInfo(parameters);
608 EXPECT_EQ(kernel_samples.sample_count, 33);
609 GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples =
610 LerpHackKernelSamples(kernel_samples);
611 EXPECT_EQ(fast_kernel_samples.sample_count, 17);
612 float data[33];
613 srand(0);
614 for (int i = 0; i < 33; i++) {
615 data[i] = 255.0 * static_cast<double>(IMPELLER_RAND()) / RAND_MAX;
616 }
617
618 auto sampler = [data](Point point) -> Scalar {
619 FML_CHECK(point.y == 0.0f);
620 FML_CHECK(point.x >= -16);
621 FML_CHECK(point.x <= 16);
622 Scalar fint_part;
623 Scalar fract = fabsf(modf(point.x, &fint_part));
624 if (fract == 0) {
625 int32_t int_part = static_cast<int32_t>(fint_part) + 16;
626 return data[int_part];
627 } else {
628 int32_t left = static_cast<int32_t>(floor(point.x)) + 16;
629 int32_t right = static_cast<int32_t>(ceil(point.x)) + 16;
630 if (point.x < 0) {
631 return fract * data[left] + (1.0 - fract) * data[right];
632 } else {
633 return (1.0 - fract) * data[left] + fract * data[right];
634 }
635 }
636 };
637
638 Scalar output = 0.0;
639 for (int i = 0; i < kernel_samples.sample_count; ++i) {
640 auto sample = kernel_samples.samples[i];
641 output += sample.coefficient * sampler(sample.uv_offset);
642 }
643
644 Scalar fast_output = 0.0;
645 for (int i = 0; i < fast_kernel_samples.sample_count; i++) {
646 fast_output += GetCoefficient(fast_kernel_samples.sample_data[i]) *
647 sampler(GetUVOffset(fast_kernel_samples.sample_data[i]));
648 }
649
650 EXPECT_NEAR(output, fast_output, 0.1);
651}
652
654 Scalar sigma = 30.5f;
655 int32_t blur_radius = static_cast<int32_t>(
657 BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
658 .blur_sigma = sigma,
659 .blur_radius = blur_radius,
660 .step_size = 1};
661 KernelSamples kernel_samples = GenerateBlurInfo(parameters);
662 GaussianBlurPipeline::FragmentShader::KernelSamples frag_kernel_samples =
663 LerpHackKernelSamples(kernel_samples);
664 EXPECT_TRUE(frag_kernel_samples.sample_count <= kGaussianBlurMaxKernelSize);
665}
666
667} // namespace testing
668} // namespace impeller
double x() const
Definition geometry.h:22
const T & value() const
Definition status_or.h:77
bool ok() const
Definition status_or.h:75
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition entity.cc:62
std::shared_ptr< ContentContext > GetContentContext() const
@ kNormal
Blurred inside and outside.
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
std::vector< FilterInput::Ref > Vector
Performs a bidirectional Gaussian blur.
std::optional< Rect > GetFilterCoverage(const FilterInput::Vector &inputs, const Entity &entity, const Matrix &effect_transform) const override
Internal utility method for |GetLocalCoverage| that computes the output coverage of this filter acros...
static Quad CalculateUVs(const std::shared_ptr< FilterInput > &filter_input, const Entity &entity, const Rect &source_rect, const ISize &texture_size)
std::shared_ptr< Context > GetContext() const
Definition playground.cc:94
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition render_pass.h:30
std::shared_ptr< Texture > MakeTexture(ISize size)
Create a texture that has been cleared to transparent black.
int32_t x
uint32_t * target
#define FML_CHECK(condition)
Definition logging.h:104
Vector2 blur_radius
Blur radius in source pixels based on scaled_sigma.
Vector2 scaled_sigma
Sigma when considering an entity's scale and the effect transform.
inline ::testing::AssertionResult RectNear(impeller::Rect a, impeller::Rect b)
#define EXPECT_RECT_NEAR(a, b)
#define EXPECT_POINT_NEAR(a, b)
FlTexture * texture
TEST(FrameTimingsRecorderTest, RecordVsync)
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switch_defs.h:36
TEST_P(AiksTest, DrawAtlasNoColor)
Point Vector2
Definition point.h:430
static constexpr int32_t kGaussianBlurMaxKernelSize
float Scalar
Definition scalar.h:19
KernelSamples GenerateBlurInfo(BlurParameters parameters)
ISize64 ISize
Definition size.h:162
std::array< Point, 4 > Quad
Definition point.h:431
GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(KernelSamples parameters)
#define INSTANTIATE_PLAYGROUND_SUITE(playground)
KernelSample samples[kMaxKernelSize]
A 4x4 matrix using column-major storage.
Definition matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition matrix.h:95
static Matrix MakeRotationZ(Radians r)
Definition matrix.h:223
static constexpr Matrix MakeScale(const Vector3 &s)
Definition matrix.h:104
static constexpr TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition rect.h:136
static constexpr TRect MakeSize(const TSize< U > &size)
Definition rect.h:150
static constexpr std::optional< TRect > MakePointBounds(const U &value)
Definition rect.h:189
static constexpr TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition rect.h:129