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 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 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 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 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 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 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 LerpHackResult blur_info = LerpHackKernelSamples(kernel_samples);
553 EXPECT_EQ(blur_info.sample_count, 3);
554
555 KernelSample* samples = kernel_samples.samples;
556
557 //////////////////////////////////////////////////////////////////////////////
558 // Check output kernel.
559
560 EXPECT_POINT_NEAR(GetUVOffset(blur_info.kernel_samples.sample_data[0]),
561 Point(-1.3333333, 0));
562 EXPECT_FLOAT_EQ(GetCoefficient(blur_info.kernel_samples.sample_data[0]), 0.3);
563
564 EXPECT_POINT_NEAR(GetUVOffset(blur_info.kernel_samples.sample_data[1]),
565 Point(0, 0));
566 EXPECT_FLOAT_EQ(GetCoefficient(blur_info.kernel_samples.sample_data[1]), 0.4);
567
568 EXPECT_POINT_NEAR(GetUVOffset(blur_info.kernel_samples.sample_data[2]),
569 Point(1.333333, 0));
570 EXPECT_FLOAT_EQ(GetCoefficient(blur_info.kernel_samples.sample_data[2]), 0.3);
571
572 //////////////////////////////////////////////////////////////////////////////
573 // Check output of fast kernel versus original kernel.
574
575 Scalar data[5] = {0.25, 0.5, 0.5, 1.0, 0.2};
576 Scalar original_output =
577 samples[0].coefficient * data[0] + samples[1].coefficient * data[1] +
578 samples[2].coefficient * data[2] + samples[3].coefficient * data[3] +
579 samples[4].coefficient * data[4];
580
581 auto lerp = [](const Point& point, Scalar left, Scalar right) {
582 Scalar int_part;
583 Scalar fract = fabsf(modf(point.x, &int_part));
584 if (point.x < 0) {
585 return left * fract + right * (1.0 - fract);
586 } else {
587 return left * (1.0 - fract) + right * fract;
588 }
589 };
590 Scalar fast_output =
591 /*1st*/ lerp(GetUVOffset(blur_info.kernel_samples.sample_data[0]),
592 data[0], data[1]) *
593 GetCoefficient(blur_info.kernel_samples.sample_data[0]) +
594 /*2nd*/ data[2] *
595 GetCoefficient(blur_info.kernel_samples.sample_data[1]) +
596 /*3rd*/
597 lerp(GetUVOffset(blur_info.kernel_samples.sample_data[2]), data[3],
598 data[4]) *
599 GetCoefficient(blur_info.kernel_samples.sample_data[2]);
600
601 EXPECT_NEAR(original_output, fast_output, 0.01);
602}
603
604TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) {
605 Scalar sigma = 10.0f;
606 int32_t blur_radius = static_cast<int32_t>(
608 BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
609 .blur_sigma = sigma,
610 .blur_radius = blur_radius,
611 .step_size = 1};
612 KernelSamples kernel_samples = GenerateBlurInfo(parameters);
613 EXPECT_EQ(kernel_samples.sample_count, 33);
614 LerpHackResult fast_kernel_samples = LerpHackKernelSamples(kernel_samples);
615 EXPECT_EQ(fast_kernel_samples.sample_count, 17);
616 float data[33];
617 srand(0);
618 for (int i = 0; i < 33; i++) {
619 data[i] = 255.0 * static_cast<double>(IMPELLER_RAND()) / RAND_MAX;
620 }
621
622 auto sampler = [data](Point point) -> Scalar {
623 FML_CHECK(point.y == 0.0f);
624 FML_CHECK(point.x >= -16);
625 FML_CHECK(point.x <= 16);
626 Scalar fint_part;
627 Scalar fract = fabsf(modf(point.x, &fint_part));
628 if (fract == 0) {
629 int32_t int_part = static_cast<int32_t>(fint_part) + 16;
630 return data[int_part];
631 } else {
632 int32_t left = static_cast<int32_t>(floor(point.x)) + 16;
633 int32_t right = static_cast<int32_t>(ceil(point.x)) + 16;
634 if (point.x < 0) {
635 return fract * data[left] + (1.0 - fract) * data[right];
636 } else {
637 return (1.0 - fract) * data[left] + fract * data[right];
638 }
639 }
640 };
641
642 Scalar output = 0.0;
643 for (int i = 0; i < kernel_samples.sample_count; ++i) {
644 auto sample = kernel_samples.samples[i];
645 output += sample.coefficient * sampler(sample.uv_offset);
646 }
647
648 Scalar fast_output = 0.0;
649 for (int i = 0; i < fast_kernel_samples.sample_count; i++) {
650 fast_output +=
651 GetCoefficient(fast_kernel_samples.kernel_samples.sample_data[i]) *
652 sampler(GetUVOffset(fast_kernel_samples.kernel_samples.sample_data[i]));
653 }
654
655 EXPECT_NEAR(output, fast_output, 0.1);
656}
657
659 Scalar sigma = 30.5f;
660 int32_t blur_radius = static_cast<int32_t>(
662 BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
663 .blur_sigma = sigma,
664 .blur_radius = blur_radius,
665 .step_size = 1};
666 KernelSamples kernel_samples = GenerateBlurInfo(parameters);
667 LerpHackResult frag_kernel_samples = LerpHackKernelSamples(kernel_samples);
668 EXPECT_TRUE(frag_kernel_samples.sample_count <= kGaussianBlurMaxKernelSize);
669}
670
671} // namespace testing
672} // 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
fml::StatusOr< RenderTarget > MakeSubpass(std::string_view label, ISize texture_size, const std::shared_ptr< CommandBuffer > &command_buffer, const SubpassCallback &subpass_callback, bool msaa_enabled=true, bool depth_stencil_enabled=false, int32_t mip_count=1) const
Creates a new texture of size texture_size and calls subpass_callback with a RenderPass for drawing t...
std::shared_ptr< Context > GetContext() const
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition entity.cc:62
@ 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)
ContentContext & GetContentContext() const
std::shared_ptr< Context > GetContext() const
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)
LerpHackResult LerpHackKernelSamples(const KernelSamples &parameters)
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
#define INSTANTIATE_PLAYGROUND_SUITE(playground)
std::shared_ptr< CommandBuffer > command_buffer
KernelSample samples[kMaxKernelSize]
GaussianBlurPipeline::FragmentShader::KernelSamples kernel_samples
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