Flutter Engine
The Flutter Engine
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
5#include "flutter/testing/testing.h"
6#include "fml/status_or.h"
7#include "gmock/gmock.h"
14
15#if FML_OS_MACOSX
16#define IMPELLER_RAND arc4random
17#else
18#define IMPELLER_RAND rand
19#endif
20
21namespace impeller {
22namespace testing {
23
24namespace {
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.
29fml::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) {
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
54fml::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() *
61 .Abs();
62 Vector2 blur_radius = Vector2(
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
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
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};
103
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
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
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
137 fml::StatusOr<Scalar> sigma_radius_1 =
138 CalculateSigmaForBlurRadius(1.0, Matrix());
139 ASSERT_TRUE(sigma_radius_1.ok());
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
157 fml::StatusOr<Scalar> sigma_radius_1 =
158 CalculateSigmaForBlurRadius(1.0, Matrix());
159 ASSERT_TRUE(sigma_radius_1.ok());
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));
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
177TEST_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());
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));
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
199TEST(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
216TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) {
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
229TEST_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)));
254 RectNear(result_coverage.value(), Rect::MakeLTRB(-1, -1, 101, 101)));
255 }
256 }
257}
258
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)));
287 RectNear(result_coverage.value(), Rect::MakeLTRB(99, 199, 201, 301)));
288 }
289 }
290}
291
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}) *
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)));
320 RectNear(result_coverage.value(), Rect::MakeLTRB(99, 99, 401, 501)));
321 }
322 }
323}
324
326 std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
327 auto filter_input = FilterInput::Make(texture);
328 Entity entity;
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
338TEST_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
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, /*mask_geometry=*/nullptr);
386 contents->SetInputs({FilterInput::Make(texture_contents)});
387 std::shared_ptr<ContentContext> renderer = GetContentContext();
388
389 Entity entity;
390 entity.SetTransform(Matrix::MakeScale({2.0, 2.0, 1.0}));
391 std::optional<Entity> result =
392 contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
393 EXPECT_TRUE(result.has_value());
394 if (result.has_value()) {
395 EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver);
396 std::optional<Rect> result_coverage = result.value().GetCoverage();
397 std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
398 EXPECT_TRUE(result_coverage.has_value());
399 EXPECT_TRUE(contents_coverage.has_value());
400 if (result_coverage.has_value() && contents_coverage.has_value()) {
401 EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
402 EXPECT_TRUE(RectNear(contents_coverage.value(),
403 Rect::MakeLTRB(98.f, 78.f, 302.f, 282.f)));
404 }
405 }
406}
407
408TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithEffectTransform) {
409 Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0});
410 std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
411 auto texture_contents = std::make_shared<TextureContents>();
412 texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
413 texture_contents->SetTexture(texture);
414 texture_contents->SetDestinationRect(Rect::MakeXYWH(
415 50, 40, texture->GetSize().width, texture->GetSize().height));
416
417 fml::StatusOr<Scalar> sigma_radius_1 =
418 CalculateSigmaForBlurRadius(1.0, effect_transform);
419 ASSERT_TRUE(sigma_radius_1.ok());
420 auto contents = std::make_unique<GaussianBlurFilterContents>(
421 sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
422 FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
423 contents->SetInputs({FilterInput::Make(texture_contents)});
424 contents->SetEffectTransform(effect_transform);
425 std::shared_ptr<ContentContext> renderer = GetContentContext();
426
427 Entity entity;
428 std::optional<Entity> result =
429 contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
430 EXPECT_TRUE(result.has_value());
431 if (result.has_value()) {
432 EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver);
433 std::optional<Rect> result_coverage = result.value().GetCoverage();
434 std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
435 EXPECT_TRUE(result_coverage.has_value());
436 EXPECT_TRUE(contents_coverage.has_value());
437 if (result_coverage.has_value() && contents_coverage.has_value()) {
438 EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
439 EXPECT_TRUE(RectNear(contents_coverage.value(),
440 Rect::MakeXYWH(49.f, 39.f, 102.f, 102.f)));
441 }
442 }
443}
444
445TEST(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius) {
446 Scalar sigma = 1.0;
449 fml::StatusOr<Scalar> derived_sigma =
450 CalculateSigmaForBlurRadius(radius, Matrix());
451 ASSERT_TRUE(derived_sigma.ok());
452 EXPECT_NEAR(sigma, derived_sigma.value(), 0.01f);
453}
454
456 BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
457 .blur_sigma = 1,
458 .blur_radius = 5,
459 .step_size = 1};
460 GaussianBlurPipeline::FragmentShader::KernelSamples samples =
461 GenerateBlurInfo(parameters);
462 EXPECT_EQ(samples.sample_count, 9);
463
464 // Coefficients should add up to 1.
465 Scalar tally = 0;
466 for (int i = 0; i < samples.sample_count; ++i) {
467 tally += samples.samples[i].coefficient;
468 }
469 EXPECT_FLOAT_EQ(tally, 1.0f);
470
471 // Verify the shape of the curve.
472 for (int i = 0; i < 4; ++i) {
473 EXPECT_FLOAT_EQ(samples.samples[i].coefficient,
474 samples.samples[8 - i].coefficient);
475 EXPECT_TRUE(samples.samples[i + 1].coefficient >
476 samples.samples[i].coefficient);
477 }
478}
479
480TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) {
481 GaussianBlurPipeline::FragmentShader::KernelSamples kernel_samples = {
482 .sample_count = 5,
483 .samples =
484 {
485 {
486 .uv_offset = Vector2(-2, 0),
487 .coefficient = 0.1f,
488 },
489 {
490 .uv_offset = Vector2(-1, 0),
491 .coefficient = 0.2f,
492 },
493 {
494 .uv_offset = Vector2(0, 0),
495 .coefficient = 0.4f,
496 },
497 {
498 .uv_offset = Vector2(1, 0),
499 .coefficient = 0.2f,
500 },
501 {
502 .uv_offset = Vector2(2, 0),
503 .coefficient = 0.1f,
504 },
505 },
506 };
507
508 GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples =
509 LerpHackKernelSamples(kernel_samples);
510 EXPECT_EQ(fast_kernel_samples.sample_count, 3);
511
512 GaussianBlurPipeline::FragmentShader::KernelSample* samples =
513 kernel_samples.samples;
514 GaussianBlurPipeline::FragmentShader::KernelSample* fast_samples =
515 fast_kernel_samples.samples;
516
517 //////////////////////////////////////////////////////////////////////////////
518 // Check output kernel.
519
520 EXPECT_FLOAT_EQ(fast_samples[0].uv_offset.x, -1.3333333);
521 EXPECT_FLOAT_EQ(fast_samples[0].uv_offset.y, 0);
522 EXPECT_FLOAT_EQ(fast_samples[0].coefficient, 0.3);
523 EXPECT_FLOAT_EQ(fast_samples[1].uv_offset.x, 0);
524 EXPECT_FLOAT_EQ(fast_samples[1].uv_offset.y, 0);
525 EXPECT_FLOAT_EQ(fast_samples[1].coefficient, 0.4);
526 EXPECT_FLOAT_EQ(fast_samples[2].uv_offset.x, 1.3333333);
527 EXPECT_FLOAT_EQ(fast_samples[2].uv_offset.y, 0);
528 EXPECT_FLOAT_EQ(fast_samples[2].coefficient, 0.3);
529
530 //////////////////////////////////////////////////////////////////////////////
531 // Check output of fast kernel versus original kernel.
532
533 Scalar data[5] = {0.25, 0.5, 0.5, 1.0, 0.2};
534 Scalar original_output =
535 samples[0].coefficient * data[0] + samples[1].coefficient * data[1] +
536 samples[2].coefficient * data[2] + samples[3].coefficient * data[3] +
537 samples[4].coefficient * data[4];
538
539 auto lerp = [](const Point& point, Scalar left, Scalar right) {
540 Scalar int_part;
541 Scalar fract = fabsf(modf(point.x, &int_part));
542 if (point.x < 0) {
543 return left * fract + right * (1.0 - fract);
544 } else {
545 return left * (1.0 - fract) + right * fract;
546 }
547 };
548 Scalar fast_output =
549 /*1st*/ lerp(fast_samples[0].uv_offset, data[0], data[1]) *
550 fast_samples[0].coefficient +
551 /*2nd*/ data[2] * fast_samples[1].coefficient +
552 /*3rd*/ lerp(fast_samples[2].uv_offset, data[3], data[4]) *
553 fast_samples[2].coefficient;
554
555 EXPECT_NEAR(original_output, fast_output, 0.01);
556}
557
558TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) {
559 Scalar sigma = 10.0f;
560 int32_t blur_radius = static_cast<int32_t>(
562 BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
563 .blur_sigma = sigma,
564 .blur_radius = blur_radius,
565 .step_size = 1};
566 GaussianBlurPipeline::FragmentShader::KernelSamples kernel_samples =
567 GenerateBlurInfo(parameters);
568 EXPECT_EQ(kernel_samples.sample_count, 33);
569 GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples =
570 LerpHackKernelSamples(kernel_samples);
571 EXPECT_EQ(fast_kernel_samples.sample_count, 17);
572 float data[33];
573 srand(0);
574 for (int i = 0; i < 33; i++) {
575 data[i] = 255.0 * static_cast<double>(IMPELLER_RAND()) / RAND_MAX;
576 }
577
578 auto sampler = [data](Point point) -> Scalar {
579 FML_CHECK(point.y == 0.0f);
580 FML_CHECK(point.x >= -16);
581 FML_CHECK(point.x <= 16);
582 Scalar fint_part;
583 Scalar fract = fabsf(modf(point.x, &fint_part));
584 if (fract == 0) {
585 int32_t int_part = static_cast<int32_t>(fint_part) + 16;
586 return data[int_part];
587 } else {
588 int32_t left = static_cast<int32_t>(floor(point.x)) + 16;
589 int32_t right = static_cast<int32_t>(ceil(point.x)) + 16;
590 if (point.x < 0) {
591 return fract * data[left] + (1.0 - fract) * data[right];
592 } else {
593 return (1.0 - fract) * data[left] + fract * data[right];
594 }
595 }
596 };
597
598 Scalar output = 0.0;
599 for (int i = 0; i < kernel_samples.sample_count; ++i) {
600 auto sample = kernel_samples.samples[i];
601 output += sample.coefficient * sampler(sample.uv_offset);
602 }
603
604 Scalar fast_output = 0.0;
605 for (int i = 0; i < fast_kernel_samples.sample_count; ++i) {
606 auto sample = fast_kernel_samples.samples[i];
607 fast_output += sample.coefficient * sampler(sample.uv_offset);
608 }
609
610 EXPECT_NEAR(output, fast_output, 0.1);
611}
612
613} // namespace testing
614} // namespace impeller
#define TEST(S, s, D, expected)
int count
static sk_sp< Effect > Create()
static bool ok(int result)
static bool left(const SkPoint &p0, const SkPoint &p1)
static bool right(const SkPoint &p0, const SkPoint &p1)
const T & value() const
Definition status_or.h:77
bool ok() const
Definition status_or.h:75
std::shared_ptr< ContentContext > GetContentContext() 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
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:89
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition render_pass.h:33
std::shared_ptr< Texture > MakeTexture(ISize size)
Create a texture that has been cleared to transparent black.
GAsyncResult * result
uint32_t * target
#define FML_CHECK(condition)
Definition logging.h:85
inline ::testing::AssertionResult RectNear(impeller::Rect a, impeller::Rect b)
#define EXPECT_RECT_NEAR(a, b)
FlTexture * texture
double x
TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer)
GaussianBlurPipeline::FragmentShader::KernelSamples GenerateBlurInfo(BlurParameters parameters)
Point Vector2
Definition point.h:320
GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(GaussianBlurPipeline::FragmentShader::KernelSamples parameters)
float Scalar
Definition scalar.h:18
TPoint< Scalar > Point
Definition point.h:316
TSize< int64_t > ISize
Definition size.h:138
std::array< Point, 4 > Quad
Definition point.h:321
#define INSTANTIATE_PLAYGROUND_SUITE(playground)
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:213
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:146
static constexpr std::optional< TRect > MakePointBounds(const U &value)
Definition rect.h:151
static constexpr TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition rect.h:129
static SkPoint lerp(const SkPoint &a, const SkPoint &b, float T)
#define EXPECT_TRUE(handle)
Definition unit_test.h:685