From 2f90472a9ee5086730ad57e8212a24116eb1e218 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Sat, 21 Sep 2024 09:57:14 -0700 Subject: [PATCH 1/5] [Impeller] Fix GLES gaussian implementation. --- impeller/core/shader_types.h | 4 +- .../filters/gaussian_blur_filter_contents.cc | 50 +- .../filters/gaussian_blur_filter_contents.h | 9 +- ...gaussian_blur_filter_contents_unittests.cc | 1208 ++++++++--------- impeller/entity/shaders/filters/gaussian.frag | 18 +- 5 files changed, 646 insertions(+), 643 deletions(-) diff --git a/impeller/core/shader_types.h b/impeller/core/shader_types.h index f3759552f4877..bacd3414021c1 100644 --- a/impeller/core/shader_types.h +++ b/impeller/core/shader_types.h @@ -182,7 +182,9 @@ struct Padded { T value; Padding _PADDING_; - Padded(T p_value) : value(p_value){}; // NOLINT(google-explicit-constructor) + Padded(T p_value) : value(p_value) {}; // NOLINT(google-explicit-constructor) + + Padded() {}; // NOLINT(google-explicit-constructor) }; inline constexpr Vector4 ToVector(Color color) { diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index 7c2dbc316a52b..9c512bd344d29 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -325,7 +325,7 @@ DownsamplePassArgs CalculateDownsamplePassArgs( fml::StatusOr MakeDownsampleSubpass( const ContentContext& renderer, const std::shared_ptr& command_buffer, - std::shared_ptr input_texture, + const std::shared_ptr& input_texture, const SamplerDescriptor& sampler_descriptor, const DownsamplePassArgs& pass_args, Entity::TileMode tile_mode) { @@ -345,7 +345,8 @@ fml::StatusOr MakeDownsampleSubpass( TextureFillVertexShader::FrameInfo frame_info; frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); - frame_info.texture_sampler_y_coord_scale = 1.0; + frame_info.texture_sampler_y_coord_scale = + input_texture->GetYCoordScale(); TextureFillFragmentShader::FragInfo frag_info; frag_info.alpha = 1.0; @@ -447,16 +448,18 @@ fml::StatusOr MakeBlurSubpass( return input_pass; } - std::shared_ptr input_texture = input_pass.GetRenderTargetTexture(); + const std::shared_ptr& input_texture = + input_pass.GetRenderTargetTexture(); // TODO(gaaclarke): This blurs the whole image, but because we know the clip // region we could focus on just blurring that. ISize subpass_size = input_texture->GetSize(); ContentContext::SubpassCallback subpass_callback = [&](const ContentContext& renderer, RenderPass& pass) { - GaussianBlurVertexShader::FrameInfo frame_info{ - .mvp = Matrix::MakeOrthographic(ISize(1, 1)), - .texture_sampler_y_coord_scale = 1.0}; + GaussianBlurVertexShader::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)), + frame_info.texture_sampler_y_coord_scale = + input_texture->GetYCoordScale(); HostBuffer& host_buffer = renderer.GetTransientsBuffer(); @@ -481,11 +484,9 @@ fml::StatusOr MakeBlurSubpass( linear_sampler_descriptor)); GaussianBlurVertexShader::BindFrameInfo( pass, host_buffer.EmplaceUniform(frame_info)); - GaussianBlurPipeline::FragmentShader::KernelSamples kernel_samples = - LerpHackKernelSamples(GenerateBlurInfo(blur_info)); - FML_CHECK(kernel_samples.sample_count <= kGaussianBlurMaxKernelSize); - GaussianBlurFragmentShader::BindKernelSamples( - pass, host_buffer.EmplaceUniform(kernel_samples)); + GaussianBlurFragmentShader::BindBlurInfo( + pass, host_buffer.EmplaceUniform( + LerpHackKernelSamples(GenerateBlurInfo(blur_info)))); return pass.Draw().ok(); }; if (destination_target.has_value()) { @@ -898,7 +899,7 @@ KernelSamples GenerateBlurInfo(BlurParameters parameters) { Scalar tally = 0.0f; for (int i = 0; i < result.sample_count; ++i) { int x = x_offset + (i * parameters.step_size) - parameters.blur_radius; - result.samples[i] = GaussianBlurPipeline::FragmentShader::KernelSample{ + result.samples[i] = KernelSample{ .uv_offset = parameters.blur_uv_offset * x, .coefficient = expf(-0.5f * (x * x) / (parameters.blur_sigma * parameters.blur_sigma)) / @@ -917,27 +918,26 @@ KernelSamples GenerateBlurInfo(BlurParameters parameters) { // This works by shrinking the kernel size by 2 and relying on lerp to read // between the samples. -GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples( +GaussianBlurPipeline::FragmentShader::BlurInfo LerpHackKernelSamples( KernelSamples parameters) { - GaussianBlurPipeline::FragmentShader::KernelSamples result; + GaussianBlurPipeline::FragmentShader::BlurInfo result = {}; result.sample_count = ((parameters.sample_count - 1) / 2) + 1; int32_t middle = result.sample_count / 2; int32_t j = 0; FML_DCHECK(result.sample_count <= kGaussianBlurMaxKernelSize); + for (int i = 0; i < result.sample_count; i++) { if (i == middle) { - result.samples[i] = parameters.samples[j++]; + result.coefficients[i] = parameters.samples[j].coefficient; + result.uv_offsets[i] = parameters.samples[j++].uv_offset; } else { - GaussianBlurPipeline::FragmentShader::KernelSample left = - parameters.samples[j]; - GaussianBlurPipeline::FragmentShader::KernelSample right = - parameters.samples[j + 1]; - result.samples[i] = GaussianBlurPipeline::FragmentShader::KernelSample{ - .uv_offset = (left.uv_offset * left.coefficient + - right.uv_offset * right.coefficient) / - (left.coefficient + right.coefficient), - .coefficient = left.coefficient + right.coefficient, - }; + KernelSample left = parameters.samples[j]; + KernelSample right = parameters.samples[j + 1]; + + result.coefficients[i] = left.coefficient + right.coefficient; + result.uv_offsets[i] = (left.uv_offset * left.coefficient + + right.uv_offset * right.coefficient) / + (left.coefficient + right.coefficient); j += 2; } } diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h index 4408858e08b2c..1d1bdb1ca04a6 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h @@ -22,6 +22,11 @@ struct BlurParameters { int step_size; }; +struct KernelSample { + Vector2 uv_offset; + float coefficient; +}; + /// A larger mirror of GaussianBlurPipeline::FragmentShader::KernelSamples. /// /// This is a mirror of GaussianBlurPipeline::FragmentShader::KernelSamples that @@ -30,14 +35,14 @@ struct BlurParameters { struct KernelSamples { static constexpr int kMaxKernelSize = kGaussianBlurMaxKernelSize * 2; int sample_count; - GaussianBlurPipeline::FragmentShader::KernelSample samples[kMaxKernelSize]; + KernelSample samples[kMaxKernelSize]; }; KernelSamples GenerateBlurInfo(BlurParameters parameters); /// This will shrink the size of a kernel by roughly half by sampling between /// samples and relying on linear interpolation between the samples. -GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples( +GaussianBlurPipeline::FragmentShader::BlurInfo LerpHackKernelSamples( KernelSamples samples); /// Performs a bidirectional Gaussian blur. diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc index 59618db565d14..6fe01d7ae5d40 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc @@ -21,610 +21,610 @@ namespace impeller { namespace testing { -namespace { - -// Use newtonian method to give the closest answer to target where -// f(x) is less than the target. We do this because the value is `ceil`'d to -// grab fractional pixels. -fml::StatusOr LowerBoundNewtonianMethod( - const std::function& func, - float target, - float guess, - float tolerance) { - const double delta = 1e-6; - double x = guess; - double fx; - static const int kMaxIterations = 1000; - int count = 0; - - do { - fx = func(x) - target; - double derivative = (func(x + delta) - func(x)) / delta; - x = x - fx / derivative; - if (++count > kMaxIterations) { - return fml::Status(fml::StatusCode::kDeadlineExceeded, - "Did not converge on answer."); - } - } while (std::abs(fx) > tolerance || - fx < 0.0); // fx < 0.0 makes this lower bound. - - return x; -} - -fml::StatusOr CalculateSigmaForBlurRadius( - Scalar radius, - const Matrix& effect_transform) { - auto f = [effect_transform](Scalar x) -> Scalar { - Vector2 scaled_sigma = (effect_transform.Basis() * - Vector2(GaussianBlurFilterContents::ScaleSigma(x), - GaussianBlurFilterContents::ScaleSigma(x))) - .Abs(); - Vector2 blur_radius = Vector2( - GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.x), - GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.y)); - return std::max(blur_radius.x, blur_radius.y); - }; - // The newtonian method is used here since inverting the function is - // non-trivial because of conditional logic and would be fragile to changes. - return LowerBoundNewtonianMethod(f, radius, 2.f, 0.001f); -} - -} // namespace - -class GaussianBlurFilterContentsTest : public EntityPlayground { - public: - /// Create a texture that has been cleared to transparent black. - std::shared_ptr MakeTexture(ISize size) { - std::shared_ptr command_buffer = - GetContentContext()->GetContext()->CreateCommandBuffer(); - if (!command_buffer) { - return nullptr; - } - - auto render_target = GetContentContext()->MakeSubpass( - "Clear Subpass", size, command_buffer, - [](const ContentContext&, RenderPass&) { return true; }); - - if (!GetContentContext() - ->GetContext() - ->GetCommandQueue() - ->Submit(/*buffers=*/{command_buffer}) - .ok()) { - return nullptr; - } - - if (render_target.ok()) { - return render_target.value().GetRenderTargetTexture(); - } - return nullptr; - } -}; -INSTANTIATE_PLAYGROUND_SUITE(GaussianBlurFilterContentsTest); - -TEST(GaussianBlurFilterContentsTest, Create) { - GaussianBlurFilterContents contents( - /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - EXPECT_EQ(contents.GetSigmaX(), 0.0); - EXPECT_EQ(contents.GetSigmaY(), 0.0); -} - -TEST(GaussianBlurFilterContentsTest, CoverageEmpty) { - GaussianBlurFilterContents contents( - /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - FilterInput::Vector inputs = {}; - Entity entity; - std::optional coverage = - contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); - ASSERT_FALSE(coverage.has_value()); -} - -TEST(GaussianBlurFilterContentsTest, CoverageSimple) { - GaussianBlurFilterContents contents( - /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - FilterInput::Vector inputs = { - FilterInput::Make(Rect::MakeLTRB(10, 10, 110, 110))}; - Entity entity; - std::optional coverage = - contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); - - ASSERT_EQ(coverage, Rect::MakeLTRB(10, 10, 110, 110)); -} - -TEST(GaussianBlurFilterContentsTest, CoverageWithSigma) { - fml::StatusOr sigma_radius_1 = - CalculateSigmaForBlurRadius(1.0, Matrix()); - ASSERT_TRUE(sigma_radius_1.ok()); - GaussianBlurFilterContents contents( - /*sigma_x=*/sigma_radius_1.value(), - /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - FilterInput::Vector inputs = { - FilterInput::Make(Rect::MakeLTRB(100, 100, 200, 200))}; - Entity entity; - std::optional coverage = - contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); - - EXPECT_TRUE(coverage.has_value()); - if (coverage.has_value()) { - EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201)); - } -} - -TEST_P(GaussianBlurFilterContentsTest, CoverageWithTexture) { - fml::StatusOr sigma_radius_1 = - CalculateSigmaForBlurRadius(1.0, Matrix()); - ASSERT_TRUE(sigma_radius_1.ok()); - GaussianBlurFilterContents contents( - /*sigma_X=*/sigma_radius_1.value(), - /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - std::shared_ptr texture = MakeTexture(ISize(100, 100)); - FilterInput::Vector inputs = {FilterInput::Make(texture)}; - Entity entity; - entity.SetTransform(Matrix::MakeTranslation({100, 100, 0})); - std::optional coverage = - contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); - - EXPECT_TRUE(coverage.has_value()); - if (coverage.has_value()) { - EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201)); - } -} - -TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) { - Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0}); - fml::StatusOr sigma_radius_1 = - CalculateSigmaForBlurRadius(1.0, effect_transform); - ASSERT_TRUE(sigma_radius_1.ok()); - GaussianBlurFilterContents contents( - /*sigma_x=*/sigma_radius_1.value(), - /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - std::shared_ptr texture = MakeTexture(ISize(100, 100)); - FilterInput::Vector inputs = {FilterInput::Make(texture)}; - Entity entity; - entity.SetTransform(Matrix::MakeTranslation({100, 100, 0})); - std::optional coverage = - contents.GetFilterCoverage(inputs, entity, effect_transform); - EXPECT_TRUE(coverage.has_value()); - if (coverage.has_value()) { - EXPECT_RECT_NEAR(coverage.value(), - Rect::MakeLTRB(100 - 1, 100 - 1, 200 + 1, 200 + 1)); - } -} - -TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) { - fml::StatusOr sigma_radius_1 = - CalculateSigmaForBlurRadius(1.0, Matrix()); - ASSERT_TRUE(sigma_radius_1.ok()); - auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - std::optional coverage = contents->GetFilterSourceCoverage( - /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}), - /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200)); - EXPECT_TRUE(coverage.has_value()); - if (coverage.has_value()) { - EXPECT_RECT_NEAR(coverage.value(), - Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2)); - } -} - -TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) { - EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1.0f), 1); - EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(2.0f), 1); - EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(3.0f), 1); - EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(4.0f), 1); - EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(16.0f), 0.25); - // Hang on to 1/8 as long as possible. - EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(95.0f), 0.125); - EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(96.0f), 0.0625); - // Downsample clamped to 1/16th. - EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1024.0f), 0.0625); -} - -TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) { - std::shared_ptr texture = MakeTexture(ISize(100, 100)); - fml::StatusOr sigma_radius_1 = - CalculateSigmaForBlurRadius(1.0, Matrix()); - ASSERT_TRUE(sigma_radius_1.ok()); - auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - contents->SetInputs({FilterInput::Make(texture)}); - std::shared_ptr renderer = GetContentContext(); - - Entity entity; - std::optional result = - contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); - EXPECT_TRUE(result.has_value()); - if (result.has_value()) { - EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); - std::optional result_coverage = result.value().GetCoverage(); - std::optional contents_coverage = contents->GetCoverage(entity); - EXPECT_TRUE(result_coverage.has_value()); - EXPECT_TRUE(contents_coverage.has_value()); - if (result_coverage.has_value() && contents_coverage.has_value()) { - EXPECT_TRUE(RectNear(contents_coverage.value(), - Rect::MakeLTRB(-1, -1, 101, 101))); - EXPECT_TRUE( - RectNear(result_coverage.value(), Rect::MakeLTRB(-1, -1, 101, 101))); - } - } -} - -TEST_P(GaussianBlurFilterContentsTest, - RenderCoverageMatchesGetCoverageTranslate) { - std::shared_ptr texture = MakeTexture(ISize(100, 100)); - fml::StatusOr sigma_radius_1 = - CalculateSigmaForBlurRadius(1.0, Matrix()); - ASSERT_TRUE(sigma_radius_1.ok()); - auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - contents->SetInputs({FilterInput::Make(texture)}); - std::shared_ptr renderer = GetContentContext(); - - Entity entity; - entity.SetTransform(Matrix::MakeTranslation({100, 200, 0})); - std::optional result = - contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); - - EXPECT_TRUE(result.has_value()); - if (result.has_value()) { - EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); - std::optional result_coverage = result.value().GetCoverage(); - std::optional contents_coverage = contents->GetCoverage(entity); - EXPECT_TRUE(result_coverage.has_value()); - EXPECT_TRUE(contents_coverage.has_value()); - if (result_coverage.has_value() && contents_coverage.has_value()) { - EXPECT_TRUE(RectNear(contents_coverage.value(), - Rect::MakeLTRB(99, 199, 201, 301))); - EXPECT_TRUE( - RectNear(result_coverage.value(), Rect::MakeLTRB(99, 199, 201, 301))); - } - } -} - -TEST_P(GaussianBlurFilterContentsTest, - RenderCoverageMatchesGetCoverageRotated) { - std::shared_ptr texture = MakeTexture(ISize(400, 300)); - fml::StatusOr sigma_radius_1 = - CalculateSigmaForBlurRadius(1.0, Matrix()); - auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - contents->SetInputs({FilterInput::Make(texture)}); - std::shared_ptr renderer = GetContentContext(); - - Entity entity; - // Rotate around the top left corner, then push it over to (100, 100). - entity.SetTransform(Matrix::MakeTranslation({400, 100, 0}) * - Matrix::MakeRotationZ(Degrees(90.0))); - std::optional result = - contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); - EXPECT_TRUE(result.has_value()); - if (result.has_value()) { - EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); - std::optional result_coverage = result.value().GetCoverage(); - std::optional contents_coverage = contents->GetCoverage(entity); - EXPECT_TRUE(result_coverage.has_value()); - EXPECT_TRUE(contents_coverage.has_value()); - if (result_coverage.has_value() && contents_coverage.has_value()) { - EXPECT_TRUE(RectNear(contents_coverage.value(), - Rect::MakeLTRB(99, 99, 401, 501))); - EXPECT_TRUE( - RectNear(result_coverage.value(), Rect::MakeLTRB(99, 99, 401, 501))); - } - } -} - -TEST_P(GaussianBlurFilterContentsTest, CalculateUVsSimple) { - std::shared_ptr texture = MakeTexture(ISize(100, 100)); - auto filter_input = FilterInput::Make(texture); - Entity entity; - Quad uvs = GaussianBlurFilterContents::CalculateUVs( - filter_input, entity, Rect::MakeSize(ISize(100, 100)), ISize(100, 100)); - std::optional uvs_bounds = Rect::MakePointBounds(uvs); - EXPECT_TRUE(uvs_bounds.has_value()); - if (uvs_bounds.has_value()) { - EXPECT_TRUE(RectNear(uvs_bounds.value(), Rect::MakeXYWH(0, 0, 1, 1))); - } -} - -TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithDestinationRect) { - std::shared_ptr texture = MakeTexture(ISize(100, 100)); - auto texture_contents = std::make_shared(); - texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); - texture_contents->SetTexture(texture); - texture_contents->SetDestinationRect(Rect::MakeXYWH( - 50, 40, texture->GetSize().width, texture->GetSize().height)); - - fml::StatusOr sigma_radius_1 = - CalculateSigmaForBlurRadius(1.0, Matrix()); - auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - contents->SetInputs({FilterInput::Make(texture_contents)}); - std::shared_ptr renderer = GetContentContext(); - - Entity entity; - std::optional result = - contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); - EXPECT_TRUE(result.has_value()); - if (result.has_value()) { - EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); - std::optional result_coverage = result.value().GetCoverage(); - std::optional contents_coverage = contents->GetCoverage(entity); - EXPECT_TRUE(result_coverage.has_value()); - EXPECT_TRUE(contents_coverage.has_value()); - if (result_coverage.has_value() && contents_coverage.has_value()) { - EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); - EXPECT_TRUE(RectNear(result_coverage.value(), - Rect::MakeLTRB(49.f, 39.f, 151.f, 141.f))); - } - } -} - -TEST_P(GaussianBlurFilterContentsTest, - TextureContentsWithDestinationRectScaled) { - std::shared_ptr texture = MakeTexture(ISize(100, 100)); - auto texture_contents = std::make_shared(); - texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); - texture_contents->SetTexture(texture); - texture_contents->SetDestinationRect(Rect::MakeXYWH( - 50, 40, texture->GetSize().width, texture->GetSize().height)); - - fml::StatusOr sigma_radius_1 = - CalculateSigmaForBlurRadius(1.0, Matrix()); - auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, - /*mask_geometry=*/nullptr); - contents->SetInputs({FilterInput::Make(texture_contents)}); - std::shared_ptr renderer = GetContentContext(); - - Entity entity; - entity.SetTransform(Matrix::MakeScale({2.0, 2.0, 1.0})); - std::optional result = - contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); - EXPECT_TRUE(result.has_value()); - if (result.has_value()) { - EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); - std::optional result_coverage = result.value().GetCoverage(); - std::optional contents_coverage = contents->GetCoverage(entity); - EXPECT_TRUE(result_coverage.has_value()); - EXPECT_TRUE(contents_coverage.has_value()); - if (result_coverage.has_value() && contents_coverage.has_value()) { - EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); - // Scaling a blurred entity doesn't seem to scale the blur radius linearly - // when comparing results with rrect_blur. That's why this is not - // Rect::MakeXYWH(98.f, 78.f, 204.0f, 204.f). - EXPECT_TRUE(RectNear(contents_coverage.value(), - Rect::MakeXYWH(94.f, 74.f, 212.0f, 212.f))); - } - } -} - -TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithEffectTransform) { - Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0}); - std::shared_ptr texture = MakeTexture(ISize(100, 100)); - auto texture_contents = std::make_shared(); - texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); - texture_contents->SetTexture(texture); - texture_contents->SetDestinationRect(Rect::MakeXYWH( - 50, 40, texture->GetSize().width, texture->GetSize().height)); - - fml::StatusOr sigma_radius_1 = - CalculateSigmaForBlurRadius(1.0, effect_transform); - ASSERT_TRUE(sigma_radius_1.ok()); - auto contents = std::make_unique( - sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, - FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); - contents->SetInputs({FilterInput::Make(texture_contents)}); - contents->SetEffectTransform(effect_transform); - std::shared_ptr renderer = GetContentContext(); - - Entity entity; - std::optional result = - contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); - EXPECT_TRUE(result.has_value()); - if (result.has_value()) { - EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); - std::optional result_coverage = result.value().GetCoverage(); - std::optional contents_coverage = contents->GetCoverage(entity); - EXPECT_TRUE(result_coverage.has_value()); - EXPECT_TRUE(contents_coverage.has_value()); - if (result_coverage.has_value() && contents_coverage.has_value()) { - EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); - EXPECT_TRUE(RectNear(contents_coverage.value(), - Rect::MakeXYWH(49.f, 39.f, 102.f, 102.f))); - } - } -} - -TEST(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius) { - Scalar sigma = 1.0; - Scalar radius = GaussianBlurFilterContents::CalculateBlurRadius( - GaussianBlurFilterContents::ScaleSigma(sigma)); - fml::StatusOr derived_sigma = - CalculateSigmaForBlurRadius(radius, Matrix()); - ASSERT_TRUE(derived_sigma.ok()); - EXPECT_NEAR(sigma, derived_sigma.value(), 0.01f); -} - -TEST(GaussianBlurFilterContentsTest, Coefficients) { - BlurParameters parameters = {.blur_uv_offset = Point(1, 0), - .blur_sigma = 1, - .blur_radius = 5, - .step_size = 1}; - KernelSamples samples = GenerateBlurInfo(parameters); - EXPECT_EQ(samples.sample_count, 9); - - // Coefficients should add up to 1. - Scalar tally = 0; - for (int i = 0; i < samples.sample_count; ++i) { - tally += samples.samples[i].coefficient; - } - EXPECT_FLOAT_EQ(tally, 1.0f); - - // Verify the shape of the curve. - for (int i = 0; i < 4; ++i) { - EXPECT_FLOAT_EQ(samples.samples[i].coefficient, - samples.samples[8 - i].coefficient); - EXPECT_TRUE(samples.samples[i + 1].coefficient > - samples.samples[i].coefficient); - } -} - -TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) { - KernelSamples kernel_samples = { - .sample_count = 5, - .samples = - { - { - .uv_offset = Vector2(-2, 0), - .coefficient = 0.1f, - }, - { - .uv_offset = Vector2(-1, 0), - .coefficient = 0.2f, - }, - { - .uv_offset = Vector2(0, 0), - .coefficient = 0.4f, - }, - { - .uv_offset = Vector2(1, 0), - .coefficient = 0.2f, - }, - { - .uv_offset = Vector2(2, 0), - .coefficient = 0.1f, - }, - }, - }; - - GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples = - LerpHackKernelSamples(kernel_samples); - EXPECT_EQ(fast_kernel_samples.sample_count, 3); - - GaussianBlurPipeline::FragmentShader::KernelSample* samples = - kernel_samples.samples; - GaussianBlurPipeline::FragmentShader::KernelSample* fast_samples = - fast_kernel_samples.samples; - - ////////////////////////////////////////////////////////////////////////////// - // Check output kernel. - - EXPECT_FLOAT_EQ(fast_samples[0].uv_offset.x, -1.3333333); - EXPECT_FLOAT_EQ(fast_samples[0].uv_offset.y, 0); - EXPECT_FLOAT_EQ(fast_samples[0].coefficient, 0.3); - EXPECT_FLOAT_EQ(fast_samples[1].uv_offset.x, 0); - EXPECT_FLOAT_EQ(fast_samples[1].uv_offset.y, 0); - EXPECT_FLOAT_EQ(fast_samples[1].coefficient, 0.4); - EXPECT_FLOAT_EQ(fast_samples[2].uv_offset.x, 1.3333333); - EXPECT_FLOAT_EQ(fast_samples[2].uv_offset.y, 0); - EXPECT_FLOAT_EQ(fast_samples[2].coefficient, 0.3); - - ////////////////////////////////////////////////////////////////////////////// - // Check output of fast kernel versus original kernel. - - Scalar data[5] = {0.25, 0.5, 0.5, 1.0, 0.2}; - Scalar original_output = - samples[0].coefficient * data[0] + samples[1].coefficient * data[1] + - samples[2].coefficient * data[2] + samples[3].coefficient * data[3] + - samples[4].coefficient * data[4]; - - auto lerp = [](const Point& point, Scalar left, Scalar right) { - Scalar int_part; - Scalar fract = fabsf(modf(point.x, &int_part)); - if (point.x < 0) { - return left * fract + right * (1.0 - fract); - } else { - return left * (1.0 - fract) + right * fract; - } - }; - Scalar fast_output = - /*1st*/ lerp(fast_samples[0].uv_offset, data[0], data[1]) * - fast_samples[0].coefficient + - /*2nd*/ data[2] * fast_samples[1].coefficient + - /*3rd*/ lerp(fast_samples[2].uv_offset, data[3], data[4]) * - fast_samples[2].coefficient; - - EXPECT_NEAR(original_output, fast_output, 0.01); -} - -TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) { - Scalar sigma = 10.0f; - int32_t blur_radius = static_cast( - std::ceil(GaussianBlurFilterContents::CalculateBlurRadius(sigma))); - BlurParameters parameters = {.blur_uv_offset = Point(1, 0), - .blur_sigma = sigma, - .blur_radius = blur_radius, - .step_size = 1}; - KernelSamples kernel_samples = GenerateBlurInfo(parameters); - EXPECT_EQ(kernel_samples.sample_count, 33); - GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples = - LerpHackKernelSamples(kernel_samples); - EXPECT_EQ(fast_kernel_samples.sample_count, 17); - float data[33]; - srand(0); - for (int i = 0; i < 33; i++) { - data[i] = 255.0 * static_cast(IMPELLER_RAND()) / RAND_MAX; - } - - auto sampler = [data](Point point) -> Scalar { - FML_CHECK(point.y == 0.0f); - FML_CHECK(point.x >= -16); - FML_CHECK(point.x <= 16); - Scalar fint_part; - Scalar fract = fabsf(modf(point.x, &fint_part)); - if (fract == 0) { - int32_t int_part = static_cast(fint_part) + 16; - return data[int_part]; - } else { - int32_t left = static_cast(floor(point.x)) + 16; - int32_t right = static_cast(ceil(point.x)) + 16; - if (point.x < 0) { - return fract * data[left] + (1.0 - fract) * data[right]; - } else { - return (1.0 - fract) * data[left] + fract * data[right]; - } - } - }; - - Scalar output = 0.0; - for (int i = 0; i < kernel_samples.sample_count; ++i) { - auto sample = kernel_samples.samples[i]; - output += sample.coefficient * sampler(sample.uv_offset); - } - - Scalar fast_output = 0.0; - for (int i = 0; i < fast_kernel_samples.sample_count; ++i) { - auto sample = fast_kernel_samples.samples[i]; - fast_output += sample.coefficient * sampler(sample.uv_offset); - } - - EXPECT_NEAR(output, fast_output, 0.1); -} - -TEST(GaussianBlurFilterContentsTest, ChopHugeBlurs) { - Scalar sigma = 30.5f; - int32_t blur_radius = static_cast( - std::ceil(GaussianBlurFilterContents::CalculateBlurRadius(sigma))); - BlurParameters parameters = {.blur_uv_offset = Point(1, 0), - .blur_sigma = sigma, - .blur_radius = blur_radius, - .step_size = 1}; - KernelSamples kernel_samples = GenerateBlurInfo(parameters); - GaussianBlurPipeline::FragmentShader::KernelSamples frag_kernel_samples = - LerpHackKernelSamples(kernel_samples); - EXPECT_TRUE(frag_kernel_samples.sample_count <= kGaussianBlurMaxKernelSize); -} +// namespace { + +// // Use newtonian method to give the closest answer to target where +// // f(x) is less than the target. We do this because the value is `ceil`'d to +// // grab fractional pixels. +// fml::StatusOr LowerBoundNewtonianMethod( +// const std::function& func, +// float target, +// float guess, +// float tolerance) { +// const double delta = 1e-6; +// double x = guess; +// double fx; +// static const int kMaxIterations = 1000; +// int count = 0; + +// do { +// fx = func(x) - target; +// double derivative = (func(x + delta) - func(x)) / delta; +// x = x - fx / derivative; +// if (++count > kMaxIterations) { +// return fml::Status(fml::StatusCode::kDeadlineExceeded, +// "Did not converge on answer."); +// } +// } while (std::abs(fx) > tolerance || +// fx < 0.0); // fx < 0.0 makes this lower bound. + +// return x; +// } + +// fml::StatusOr CalculateSigmaForBlurRadius( +// Scalar radius, +// const Matrix& effect_transform) { +// auto f = [effect_transform](Scalar x) -> Scalar { +// Vector2 scaled_sigma = (effect_transform.Basis() * +// Vector2(GaussianBlurFilterContents::ScaleSigma(x), +// GaussianBlurFilterContents::ScaleSigma(x))) +// .Abs(); +// Vector2 blur_radius = Vector2( +// GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.x), +// GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.y)); +// return std::max(blur_radius.x, blur_radius.y); +// }; +// // The newtonian method is used here since inverting the function is +// // non-trivial because of conditional logic and would be fragile to changes. +// return LowerBoundNewtonianMethod(f, radius, 2.f, 0.001f); +// } + +// } // namespace + +// class GaussianBlurFilterContentsTest : public EntityPlayground { +// public: +// /// Create a texture that has been cleared to transparent black. +// std::shared_ptr MakeTexture(ISize size) { +// std::shared_ptr command_buffer = +// GetContentContext()->GetContext()->CreateCommandBuffer(); +// if (!command_buffer) { +// return nullptr; +// } + +// auto render_target = GetContentContext()->MakeSubpass( +// "Clear Subpass", size, command_buffer, +// [](const ContentContext&, RenderPass&) { return true; }); + +// if (!GetContentContext() +// ->GetContext() +// ->GetCommandQueue() +// ->Submit(/*buffers=*/{command_buffer}) +// .ok()) { +// return nullptr; +// } + +// if (render_target.ok()) { +// return render_target.value().GetRenderTargetTexture(); +// } +// return nullptr; +// } +// }; +// INSTANTIATE_PLAYGROUND_SUITE(GaussianBlurFilterContentsTest); + +// TEST(GaussianBlurFilterContentsTest, Create) { +// GaussianBlurFilterContents contents( +// /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// EXPECT_EQ(contents.GetSigmaX(), 0.0); +// EXPECT_EQ(contents.GetSigmaY(), 0.0); +// } + +// TEST(GaussianBlurFilterContentsTest, CoverageEmpty) { +// GaussianBlurFilterContents contents( +// /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// FilterInput::Vector inputs = {}; +// Entity entity; +// std::optional coverage = +// contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); +// ASSERT_FALSE(coverage.has_value()); +// } + +// TEST(GaussianBlurFilterContentsTest, CoverageSimple) { +// GaussianBlurFilterContents contents( +// /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// FilterInput::Vector inputs = { +// FilterInput::Make(Rect::MakeLTRB(10, 10, 110, 110))}; +// Entity entity; +// std::optional coverage = +// contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); + +// ASSERT_EQ(coverage, Rect::MakeLTRB(10, 10, 110, 110)); +// } + +// TEST(GaussianBlurFilterContentsTest, CoverageWithSigma) { +// fml::StatusOr sigma_radius_1 = +// CalculateSigmaForBlurRadius(1.0, Matrix()); +// ASSERT_TRUE(sigma_radius_1.ok()); +// GaussianBlurFilterContents contents( +// /*sigma_x=*/sigma_radius_1.value(), +// /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// FilterInput::Vector inputs = { +// FilterInput::Make(Rect::MakeLTRB(100, 100, 200, 200))}; +// Entity entity; +// std::optional coverage = +// contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); + +// EXPECT_TRUE(coverage.has_value()); +// if (coverage.has_value()) { +// EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201)); +// } +// } + +// TEST_P(GaussianBlurFilterContentsTest, CoverageWithTexture) { +// fml::StatusOr sigma_radius_1 = +// CalculateSigmaForBlurRadius(1.0, Matrix()); +// ASSERT_TRUE(sigma_radius_1.ok()); +// GaussianBlurFilterContents contents( +// /*sigma_X=*/sigma_radius_1.value(), +// /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// std::shared_ptr texture = MakeTexture(ISize(100, 100)); +// FilterInput::Vector inputs = {FilterInput::Make(texture)}; +// Entity entity; +// entity.SetTransform(Matrix::MakeTranslation({100, 100, 0})); +// std::optional coverage = +// contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); + +// EXPECT_TRUE(coverage.has_value()); +// if (coverage.has_value()) { +// EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201)); +// } +// } + +// TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) { +// Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0}); +// fml::StatusOr sigma_radius_1 = +// CalculateSigmaForBlurRadius(1.0, effect_transform); +// ASSERT_TRUE(sigma_radius_1.ok()); +// GaussianBlurFilterContents contents( +// /*sigma_x=*/sigma_radius_1.value(), +// /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// std::shared_ptr texture = MakeTexture(ISize(100, 100)); +// FilterInput::Vector inputs = {FilterInput::Make(texture)}; +// Entity entity; +// entity.SetTransform(Matrix::MakeTranslation({100, 100, 0})); +// std::optional coverage = +// contents.GetFilterCoverage(inputs, entity, effect_transform); +// EXPECT_TRUE(coverage.has_value()); +// if (coverage.has_value()) { +// EXPECT_RECT_NEAR(coverage.value(), +// Rect::MakeLTRB(100 - 1, 100 - 1, 200 + 1, 200 + 1)); +// } +// } + +// TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) { +// fml::StatusOr sigma_radius_1 = +// CalculateSigmaForBlurRadius(1.0, Matrix()); +// ASSERT_TRUE(sigma_radius_1.ok()); +// auto contents = std::make_unique( +// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// std::optional coverage = contents->GetFilterSourceCoverage( +// /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}), +// /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200)); +// EXPECT_TRUE(coverage.has_value()); +// if (coverage.has_value()) { +// EXPECT_RECT_NEAR(coverage.value(), +// Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2)); +// } +// } + +// TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) { +// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1.0f), 1); +// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(2.0f), 1); +// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(3.0f), 1); +// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(4.0f), 1); +// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(16.0f), 0.25); +// // Hang on to 1/8 as long as possible. +// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(95.0f), 0.125); +// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(96.0f), 0.0625); +// // Downsample clamped to 1/16th. +// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1024.0f), 0.0625); +// } + +// TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) { +// std::shared_ptr texture = MakeTexture(ISize(100, 100)); +// fml::StatusOr sigma_radius_1 = +// CalculateSigmaForBlurRadius(1.0, Matrix()); +// ASSERT_TRUE(sigma_radius_1.ok()); +// auto contents = std::make_unique( +// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// contents->SetInputs({FilterInput::Make(texture)}); +// std::shared_ptr renderer = GetContentContext(); + +// Entity entity; +// std::optional result = +// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); +// EXPECT_TRUE(result.has_value()); +// if (result.has_value()) { +// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); +// std::optional result_coverage = result.value().GetCoverage(); +// std::optional contents_coverage = contents->GetCoverage(entity); +// EXPECT_TRUE(result_coverage.has_value()); +// EXPECT_TRUE(contents_coverage.has_value()); +// if (result_coverage.has_value() && contents_coverage.has_value()) { +// EXPECT_TRUE(RectNear(contents_coverage.value(), +// Rect::MakeLTRB(-1, -1, 101, 101))); +// EXPECT_TRUE( +// RectNear(result_coverage.value(), Rect::MakeLTRB(-1, -1, 101, 101))); +// } +// } +// } + +// TEST_P(GaussianBlurFilterContentsTest, +// RenderCoverageMatchesGetCoverageTranslate) { +// std::shared_ptr texture = MakeTexture(ISize(100, 100)); +// fml::StatusOr sigma_radius_1 = +// CalculateSigmaForBlurRadius(1.0, Matrix()); +// ASSERT_TRUE(sigma_radius_1.ok()); +// auto contents = std::make_unique( +// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// contents->SetInputs({FilterInput::Make(texture)}); +// std::shared_ptr renderer = GetContentContext(); + +// Entity entity; +// entity.SetTransform(Matrix::MakeTranslation({100, 200, 0})); +// std::optional result = +// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); + +// EXPECT_TRUE(result.has_value()); +// if (result.has_value()) { +// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); +// std::optional result_coverage = result.value().GetCoverage(); +// std::optional contents_coverage = contents->GetCoverage(entity); +// EXPECT_TRUE(result_coverage.has_value()); +// EXPECT_TRUE(contents_coverage.has_value()); +// if (result_coverage.has_value() && contents_coverage.has_value()) { +// EXPECT_TRUE(RectNear(contents_coverage.value(), +// Rect::MakeLTRB(99, 199, 201, 301))); +// EXPECT_TRUE( +// RectNear(result_coverage.value(), Rect::MakeLTRB(99, 199, 201, 301))); +// } +// } +// } + +// TEST_P(GaussianBlurFilterContentsTest, +// RenderCoverageMatchesGetCoverageRotated) { +// std::shared_ptr texture = MakeTexture(ISize(400, 300)); +// fml::StatusOr sigma_radius_1 = +// CalculateSigmaForBlurRadius(1.0, Matrix()); +// auto contents = std::make_unique( +// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// contents->SetInputs({FilterInput::Make(texture)}); +// std::shared_ptr renderer = GetContentContext(); + +// Entity entity; +// // Rotate around the top left corner, then push it over to (100, 100). +// entity.SetTransform(Matrix::MakeTranslation({400, 100, 0}) * +// Matrix::MakeRotationZ(Degrees(90.0))); +// std::optional result = +// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); +// EXPECT_TRUE(result.has_value()); +// if (result.has_value()) { +// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); +// std::optional result_coverage = result.value().GetCoverage(); +// std::optional contents_coverage = contents->GetCoverage(entity); +// EXPECT_TRUE(result_coverage.has_value()); +// EXPECT_TRUE(contents_coverage.has_value()); +// if (result_coverage.has_value() && contents_coverage.has_value()) { +// EXPECT_TRUE(RectNear(contents_coverage.value(), +// Rect::MakeLTRB(99, 99, 401, 501))); +// EXPECT_TRUE( +// RectNear(result_coverage.value(), Rect::MakeLTRB(99, 99, 401, 501))); +// } +// } +// } + +// TEST_P(GaussianBlurFilterContentsTest, CalculateUVsSimple) { +// std::shared_ptr texture = MakeTexture(ISize(100, 100)); +// auto filter_input = FilterInput::Make(texture); +// Entity entity; +// Quad uvs = GaussianBlurFilterContents::CalculateUVs( +// filter_input, entity, Rect::MakeSize(ISize(100, 100)), ISize(100, 100)); +// std::optional uvs_bounds = Rect::MakePointBounds(uvs); +// EXPECT_TRUE(uvs_bounds.has_value()); +// if (uvs_bounds.has_value()) { +// EXPECT_TRUE(RectNear(uvs_bounds.value(), Rect::MakeXYWH(0, 0, 1, 1))); +// } +// } + +// TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithDestinationRect) { +// std::shared_ptr texture = MakeTexture(ISize(100, 100)); +// auto texture_contents = std::make_shared(); +// texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); +// texture_contents->SetTexture(texture); +// texture_contents->SetDestinationRect(Rect::MakeXYWH( +// 50, 40, texture->GetSize().width, texture->GetSize().height)); + +// fml::StatusOr sigma_radius_1 = +// CalculateSigmaForBlurRadius(1.0, Matrix()); +// auto contents = std::make_unique( +// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// contents->SetInputs({FilterInput::Make(texture_contents)}); +// std::shared_ptr renderer = GetContentContext(); + +// Entity entity; +// std::optional result = +// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); +// EXPECT_TRUE(result.has_value()); +// if (result.has_value()) { +// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); +// std::optional result_coverage = result.value().GetCoverage(); +// std::optional contents_coverage = contents->GetCoverage(entity); +// EXPECT_TRUE(result_coverage.has_value()); +// EXPECT_TRUE(contents_coverage.has_value()); +// if (result_coverage.has_value() && contents_coverage.has_value()) { +// EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); +// EXPECT_TRUE(RectNear(result_coverage.value(), +// Rect::MakeLTRB(49.f, 39.f, 151.f, 141.f))); +// } +// } +// } + +// TEST_P(GaussianBlurFilterContentsTest, +// TextureContentsWithDestinationRectScaled) { +// std::shared_ptr texture = MakeTexture(ISize(100, 100)); +// auto texture_contents = std::make_shared(); +// texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); +// texture_contents->SetTexture(texture); +// texture_contents->SetDestinationRect(Rect::MakeXYWH( +// 50, 40, texture->GetSize().width, texture->GetSize().height)); + +// fml::StatusOr sigma_radius_1 = +// CalculateSigmaForBlurRadius(1.0, Matrix()); +// auto contents = std::make_unique( +// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, +// /*mask_geometry=*/nullptr); +// contents->SetInputs({FilterInput::Make(texture_contents)}); +// std::shared_ptr renderer = GetContentContext(); + +// Entity entity; +// entity.SetTransform(Matrix::MakeScale({2.0, 2.0, 1.0})); +// std::optional result = +// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); +// EXPECT_TRUE(result.has_value()); +// if (result.has_value()) { +// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); +// std::optional result_coverage = result.value().GetCoverage(); +// std::optional contents_coverage = contents->GetCoverage(entity); +// EXPECT_TRUE(result_coverage.has_value()); +// EXPECT_TRUE(contents_coverage.has_value()); +// if (result_coverage.has_value() && contents_coverage.has_value()) { +// EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); +// // Scaling a blurred entity doesn't seem to scale the blur radius linearly +// // when comparing results with rrect_blur. That's why this is not +// // Rect::MakeXYWH(98.f, 78.f, 204.0f, 204.f). +// EXPECT_TRUE(RectNear(contents_coverage.value(), +// Rect::MakeXYWH(94.f, 74.f, 212.0f, 212.f))); +// } +// } +// } + +// TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithEffectTransform) { +// Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0}); +// std::shared_ptr texture = MakeTexture(ISize(100, 100)); +// auto texture_contents = std::make_shared(); +// texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); +// texture_contents->SetTexture(texture); +// texture_contents->SetDestinationRect(Rect::MakeXYWH( +// 50, 40, texture->GetSize().width, texture->GetSize().height)); + +// fml::StatusOr sigma_radius_1 = +// CalculateSigmaForBlurRadius(1.0, effect_transform); +// ASSERT_TRUE(sigma_radius_1.ok()); +// auto contents = std::make_unique( +// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, +// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); +// contents->SetInputs({FilterInput::Make(texture_contents)}); +// contents->SetEffectTransform(effect_transform); +// std::shared_ptr renderer = GetContentContext(); + +// Entity entity; +// std::optional result = +// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); +// EXPECT_TRUE(result.has_value()); +// if (result.has_value()) { +// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); +// std::optional result_coverage = result.value().GetCoverage(); +// std::optional contents_coverage = contents->GetCoverage(entity); +// EXPECT_TRUE(result_coverage.has_value()); +// EXPECT_TRUE(contents_coverage.has_value()); +// if (result_coverage.has_value() && contents_coverage.has_value()) { +// EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); +// EXPECT_TRUE(RectNear(contents_coverage.value(), +// Rect::MakeXYWH(49.f, 39.f, 102.f, 102.f))); +// } +// } +// } + +// TEST(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius) { +// Scalar sigma = 1.0; +// Scalar radius = GaussianBlurFilterContents::CalculateBlurRadius( +// GaussianBlurFilterContents::ScaleSigma(sigma)); +// fml::StatusOr derived_sigma = +// CalculateSigmaForBlurRadius(radius, Matrix()); +// ASSERT_TRUE(derived_sigma.ok()); +// EXPECT_NEAR(sigma, derived_sigma.value(), 0.01f); +// } + +// TEST(GaussianBlurFilterContentsTest, Coefficients) { +// BlurParameters parameters = {.blur_uv_offset = Point(1, 0), +// .blur_sigma = 1, +// .blur_radius = 5, +// .step_size = 1}; +// KernelSamples samples = GenerateBlurInfo(parameters); +// EXPECT_EQ(samples.sample_count, 9); + +// // Coefficients should add up to 1. +// Scalar tally = 0; +// for (int i = 0; i < samples.sample_count; ++i) { +// tally += samples.samples[i].coefficient; +// } +// EXPECT_FLOAT_EQ(tally, 1.0f); + +// // Verify the shape of the curve. +// for (int i = 0; i < 4; ++i) { +// EXPECT_FLOAT_EQ(samples.samples[i].coefficient, +// samples.samples[8 - i].coefficient); +// EXPECT_TRUE(samples.samples[i + 1].coefficient > +// samples.samples[i].coefficient); +// } +// } + +// TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) { +// KernelSamples kernel_samples = { +// .sample_count = 5, +// .samples = +// { +// { +// .uv_offset = Vector2(-2, 0), +// .coefficient = 0.1f, +// }, +// { +// .uv_offset = Vector2(-1, 0), +// .coefficient = 0.2f, +// }, +// { +// .uv_offset = Vector2(0, 0), +// .coefficient = 0.4f, +// }, +// { +// .uv_offset = Vector2(1, 0), +// .coefficient = 0.2f, +// }, +// { +// .uv_offset = Vector2(2, 0), +// .coefficient = 0.1f, +// }, +// }, +// }; + +// GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples = +// LerpHackKernelSamples(kernel_samples); +// EXPECT_EQ(fast_kernel_samples.sample_count, 3); + +// GaussianBlurPipeline::FragmentShader::KernelSample* samples = +// kernel_samples.samples; +// GaussianBlurPipeline::FragmentShader::KernelSample* fast_samples = +// fast_kernel_samples.samples; + +// ////////////////////////////////////////////////////////////////////////////// +// // Check output kernel. + +// EXPECT_FLOAT_EQ(fast_samples[0].uv_offset.x, -1.3333333); +// EXPECT_FLOAT_EQ(fast_samples[0].uv_offset.y, 0); +// EXPECT_FLOAT_EQ(fast_samples[0].coefficient, 0.3); +// EXPECT_FLOAT_EQ(fast_samples[1].uv_offset.x, 0); +// EXPECT_FLOAT_EQ(fast_samples[1].uv_offset.y, 0); +// EXPECT_FLOAT_EQ(fast_samples[1].coefficient, 0.4); +// EXPECT_FLOAT_EQ(fast_samples[2].uv_offset.x, 1.3333333); +// EXPECT_FLOAT_EQ(fast_samples[2].uv_offset.y, 0); +// EXPECT_FLOAT_EQ(fast_samples[2].coefficient, 0.3); + +// ////////////////////////////////////////////////////////////////////////////// +// // Check output of fast kernel versus original kernel. + +// Scalar data[5] = {0.25, 0.5, 0.5, 1.0, 0.2}; +// Scalar original_output = +// samples[0].coefficient * data[0] + samples[1].coefficient * data[1] + +// samples[2].coefficient * data[2] + samples[3].coefficient * data[3] + +// samples[4].coefficient * data[4]; + +// auto lerp = [](const Point& point, Scalar left, Scalar right) { +// Scalar int_part; +// Scalar fract = fabsf(modf(point.x, &int_part)); +// if (point.x < 0) { +// return left * fract + right * (1.0 - fract); +// } else { +// return left * (1.0 - fract) + right * fract; +// } +// }; +// Scalar fast_output = +// /*1st*/ lerp(fast_samples[0].uv_offset, data[0], data[1]) * +// fast_samples[0].coefficient + +// /*2nd*/ data[2] * fast_samples[1].coefficient + +// /*3rd*/ lerp(fast_samples[2].uv_offset, data[3], data[4]) * +// fast_samples[2].coefficient; + +// EXPECT_NEAR(original_output, fast_output, 0.01); +// } + +// TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) { +// Scalar sigma = 10.0f; +// int32_t blur_radius = static_cast( +// std::ceil(GaussianBlurFilterContents::CalculateBlurRadius(sigma))); +// BlurParameters parameters = {.blur_uv_offset = Point(1, 0), +// .blur_sigma = sigma, +// .blur_radius = blur_radius, +// .step_size = 1}; +// KernelSamples kernel_samples = GenerateBlurInfo(parameters); +// EXPECT_EQ(kernel_samples.sample_count, 33); +// GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples = +// LerpHackKernelSamples(kernel_samples); +// EXPECT_EQ(fast_kernel_samples.sample_count, 17); +// float data[33]; +// srand(0); +// for (int i = 0; i < 33; i++) { +// data[i] = 255.0 * static_cast(IMPELLER_RAND()) / RAND_MAX; +// } + +// auto sampler = [data](Point point) -> Scalar { +// FML_CHECK(point.y == 0.0f); +// FML_CHECK(point.x >= -16); +// FML_CHECK(point.x <= 16); +// Scalar fint_part; +// Scalar fract = fabsf(modf(point.x, &fint_part)); +// if (fract == 0) { +// int32_t int_part = static_cast(fint_part) + 16; +// return data[int_part]; +// } else { +// int32_t left = static_cast(floor(point.x)) + 16; +// int32_t right = static_cast(ceil(point.x)) + 16; +// if (point.x < 0) { +// return fract * data[left] + (1.0 - fract) * data[right]; +// } else { +// return (1.0 - fract) * data[left] + fract * data[right]; +// } +// } +// }; + +// Scalar output = 0.0; +// for (int i = 0; i < kernel_samples.sample_count; ++i) { +// auto sample = kernel_samples.samples[i]; +// output += sample.coefficient * sampler(sample.uv_offset); +// } + +// Scalar fast_output = 0.0; +// for (int i = 0; i < fast_kernel_samples.sample_count; ++i) { +// auto sample = fast_kernel_samples.samples[i]; +// fast_output += sample.coefficient * sampler(sample.uv_offset); +// } + +// EXPECT_NEAR(output, fast_output, 0.1); +// } + +// TEST(GaussianBlurFilterContentsTest, ChopHugeBlurs) { +// Scalar sigma = 30.5f; +// int32_t blur_radius = static_cast( +// std::ceil(GaussianBlurFilterContents::CalculateBlurRadius(sigma))); +// BlurParameters parameters = {.blur_uv_offset = Point(1, 0), +// .blur_sigma = sigma, +// .blur_radius = blur_radius, +// .step_size = 1}; +// KernelSamples kernel_samples = GenerateBlurInfo(parameters); +// GaussianBlurPipeline::FragmentShader::KernelSamples frag_kernel_samples = +// LerpHackKernelSamples(kernel_samples); +// EXPECT_TRUE(frag_kernel_samples.sample_count <= kGaussianBlurMaxKernelSize); +// } } // namespace testing } // namespace impeller diff --git a/impeller/entity/shaders/filters/gaussian.frag b/impeller/entity/shaders/filters/gaussian.frag index f83a59940896d..b831498ac4979 100644 --- a/impeller/entity/shaders/filters/gaussian.frag +++ b/impeller/entity/shaders/filters/gaussian.frag @@ -11,14 +11,10 @@ uniform f16sampler2D texture_sampler; layout(constant_id = 0) const float supports_decal = 1.0; -struct KernelSample { - vec2 uv_offset; - float coefficient; -}; - -uniform KernelSamples { - int sample_count; - KernelSample samples[50]; +uniform BlurInfo { + float sample_count; + vec2 uv_offsets[50]; + float coefficients[50]; } blur_info; @@ -36,11 +32,11 @@ out f16vec4 frag_color; void main() { f16vec4 total_color = f16vec4(0.0hf); - for (int i = 0; i < blur_info.sample_count; ++i) { - float16_t coefficient = float16_t(blur_info.samples[i].coefficient); + for (int i = 0; i < int(blur_info.sample_count); i++) { + float16_t coefficient = float16_t(blur_info.coefficients[i]); total_color += coefficient * Sample(texture_sampler, - v_texture_coords + blur_info.samples[i].uv_offset); + v_texture_coords + blur_info.uv_offsets[i]); } frag_color = total_color; From fee9a39586899fcc0ac389345940eae0994bf521 Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Sat, 21 Sep 2024 10:54:16 -0700 Subject: [PATCH 2/5] ++ --- impeller/core/shader_types.h | 4 +- ...gaussian_blur_filter_contents_unittests.cc | 1205 ++++++++--------- impeller/entity/shaders/filters/gaussian.frag | 4 +- 3 files changed, 605 insertions(+), 608 deletions(-) diff --git a/impeller/core/shader_types.h b/impeller/core/shader_types.h index bacd3414021c1..391b24daf908f 100644 --- a/impeller/core/shader_types.h +++ b/impeller/core/shader_types.h @@ -182,9 +182,9 @@ struct Padded { T value; Padding _PADDING_; - Padded(T p_value) : value(p_value) {}; // NOLINT(google-explicit-constructor) + Padded(T p_value) : value(p_value){}; // NOLINT(google-explicit-constructor) - Padded() {}; // NOLINT(google-explicit-constructor) + Padded(){}; // NOLINT(google-explicit-constructor) }; inline constexpr Vector4 ToVector(Color color) { diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc index 6fe01d7ae5d40..8d16acb0d192f 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc @@ -21,610 +21,607 @@ namespace impeller { namespace testing { -// namespace { - -// // Use newtonian method to give the closest answer to target where -// // f(x) is less than the target. We do this because the value is `ceil`'d to -// // grab fractional pixels. -// fml::StatusOr LowerBoundNewtonianMethod( -// const std::function& func, -// float target, -// float guess, -// float tolerance) { -// const double delta = 1e-6; -// double x = guess; -// double fx; -// static const int kMaxIterations = 1000; -// int count = 0; - -// do { -// fx = func(x) - target; -// double derivative = (func(x + delta) - func(x)) / delta; -// x = x - fx / derivative; -// if (++count > kMaxIterations) { -// return fml::Status(fml::StatusCode::kDeadlineExceeded, -// "Did not converge on answer."); -// } -// } while (std::abs(fx) > tolerance || -// fx < 0.0); // fx < 0.0 makes this lower bound. - -// return x; -// } - -// fml::StatusOr CalculateSigmaForBlurRadius( -// Scalar radius, -// const Matrix& effect_transform) { -// auto f = [effect_transform](Scalar x) -> Scalar { -// Vector2 scaled_sigma = (effect_transform.Basis() * -// Vector2(GaussianBlurFilterContents::ScaleSigma(x), -// GaussianBlurFilterContents::ScaleSigma(x))) -// .Abs(); -// Vector2 blur_radius = Vector2( -// GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.x), -// GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.y)); -// return std::max(blur_radius.x, blur_radius.y); -// }; -// // The newtonian method is used here since inverting the function is -// // non-trivial because of conditional logic and would be fragile to changes. -// return LowerBoundNewtonianMethod(f, radius, 2.f, 0.001f); -// } - -// } // namespace - -// class GaussianBlurFilterContentsTest : public EntityPlayground { -// public: -// /// Create a texture that has been cleared to transparent black. -// std::shared_ptr MakeTexture(ISize size) { -// std::shared_ptr command_buffer = -// GetContentContext()->GetContext()->CreateCommandBuffer(); -// if (!command_buffer) { -// return nullptr; -// } - -// auto render_target = GetContentContext()->MakeSubpass( -// "Clear Subpass", size, command_buffer, -// [](const ContentContext&, RenderPass&) { return true; }); - -// if (!GetContentContext() -// ->GetContext() -// ->GetCommandQueue() -// ->Submit(/*buffers=*/{command_buffer}) -// .ok()) { -// return nullptr; -// } - -// if (render_target.ok()) { -// return render_target.value().GetRenderTargetTexture(); -// } -// return nullptr; -// } -// }; -// INSTANTIATE_PLAYGROUND_SUITE(GaussianBlurFilterContentsTest); - -// TEST(GaussianBlurFilterContentsTest, Create) { -// GaussianBlurFilterContents contents( -// /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// EXPECT_EQ(contents.GetSigmaX(), 0.0); -// EXPECT_EQ(contents.GetSigmaY(), 0.0); -// } - -// TEST(GaussianBlurFilterContentsTest, CoverageEmpty) { -// GaussianBlurFilterContents contents( -// /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// FilterInput::Vector inputs = {}; -// Entity entity; -// std::optional coverage = -// contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); -// ASSERT_FALSE(coverage.has_value()); -// } - -// TEST(GaussianBlurFilterContentsTest, CoverageSimple) { -// GaussianBlurFilterContents contents( -// /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// FilterInput::Vector inputs = { -// FilterInput::Make(Rect::MakeLTRB(10, 10, 110, 110))}; -// Entity entity; -// std::optional coverage = -// contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); - -// ASSERT_EQ(coverage, Rect::MakeLTRB(10, 10, 110, 110)); -// } - -// TEST(GaussianBlurFilterContentsTest, CoverageWithSigma) { -// fml::StatusOr sigma_radius_1 = -// CalculateSigmaForBlurRadius(1.0, Matrix()); -// ASSERT_TRUE(sigma_radius_1.ok()); -// GaussianBlurFilterContents contents( -// /*sigma_x=*/sigma_radius_1.value(), -// /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// FilterInput::Vector inputs = { -// FilterInput::Make(Rect::MakeLTRB(100, 100, 200, 200))}; -// Entity entity; -// std::optional coverage = -// contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); - -// EXPECT_TRUE(coverage.has_value()); -// if (coverage.has_value()) { -// EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201)); -// } -// } - -// TEST_P(GaussianBlurFilterContentsTest, CoverageWithTexture) { -// fml::StatusOr sigma_radius_1 = -// CalculateSigmaForBlurRadius(1.0, Matrix()); -// ASSERT_TRUE(sigma_radius_1.ok()); -// GaussianBlurFilterContents contents( -// /*sigma_X=*/sigma_radius_1.value(), -// /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// std::shared_ptr texture = MakeTexture(ISize(100, 100)); -// FilterInput::Vector inputs = {FilterInput::Make(texture)}; -// Entity entity; -// entity.SetTransform(Matrix::MakeTranslation({100, 100, 0})); -// std::optional coverage = -// contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); - -// EXPECT_TRUE(coverage.has_value()); -// if (coverage.has_value()) { -// EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201)); -// } -// } - -// TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) { -// Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0}); -// fml::StatusOr sigma_radius_1 = -// CalculateSigmaForBlurRadius(1.0, effect_transform); -// ASSERT_TRUE(sigma_radius_1.ok()); -// GaussianBlurFilterContents contents( -// /*sigma_x=*/sigma_radius_1.value(), -// /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// std::shared_ptr texture = MakeTexture(ISize(100, 100)); -// FilterInput::Vector inputs = {FilterInput::Make(texture)}; -// Entity entity; -// entity.SetTransform(Matrix::MakeTranslation({100, 100, 0})); -// std::optional coverage = -// contents.GetFilterCoverage(inputs, entity, effect_transform); -// EXPECT_TRUE(coverage.has_value()); -// if (coverage.has_value()) { -// EXPECT_RECT_NEAR(coverage.value(), -// Rect::MakeLTRB(100 - 1, 100 - 1, 200 + 1, 200 + 1)); -// } -// } - -// TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) { -// fml::StatusOr sigma_radius_1 = -// CalculateSigmaForBlurRadius(1.0, Matrix()); -// ASSERT_TRUE(sigma_radius_1.ok()); -// auto contents = std::make_unique( -// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// std::optional coverage = contents->GetFilterSourceCoverage( -// /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}), -// /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200)); -// EXPECT_TRUE(coverage.has_value()); -// if (coverage.has_value()) { -// EXPECT_RECT_NEAR(coverage.value(), -// Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2)); -// } -// } - -// TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) { -// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1.0f), 1); -// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(2.0f), 1); -// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(3.0f), 1); -// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(4.0f), 1); -// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(16.0f), 0.25); -// // Hang on to 1/8 as long as possible. -// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(95.0f), 0.125); -// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(96.0f), 0.0625); -// // Downsample clamped to 1/16th. -// EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1024.0f), 0.0625); -// } - -// TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) { -// std::shared_ptr texture = MakeTexture(ISize(100, 100)); -// fml::StatusOr sigma_radius_1 = -// CalculateSigmaForBlurRadius(1.0, Matrix()); -// ASSERT_TRUE(sigma_radius_1.ok()); -// auto contents = std::make_unique( -// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// contents->SetInputs({FilterInput::Make(texture)}); -// std::shared_ptr renderer = GetContentContext(); - -// Entity entity; -// std::optional result = -// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); -// EXPECT_TRUE(result.has_value()); -// if (result.has_value()) { -// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); -// std::optional result_coverage = result.value().GetCoverage(); -// std::optional contents_coverage = contents->GetCoverage(entity); -// EXPECT_TRUE(result_coverage.has_value()); -// EXPECT_TRUE(contents_coverage.has_value()); -// if (result_coverage.has_value() && contents_coverage.has_value()) { -// EXPECT_TRUE(RectNear(contents_coverage.value(), -// Rect::MakeLTRB(-1, -1, 101, 101))); -// EXPECT_TRUE( -// RectNear(result_coverage.value(), Rect::MakeLTRB(-1, -1, 101, 101))); -// } -// } -// } - -// TEST_P(GaussianBlurFilterContentsTest, -// RenderCoverageMatchesGetCoverageTranslate) { -// std::shared_ptr texture = MakeTexture(ISize(100, 100)); -// fml::StatusOr sigma_radius_1 = -// CalculateSigmaForBlurRadius(1.0, Matrix()); -// ASSERT_TRUE(sigma_radius_1.ok()); -// auto contents = std::make_unique( -// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// contents->SetInputs({FilterInput::Make(texture)}); -// std::shared_ptr renderer = GetContentContext(); - -// Entity entity; -// entity.SetTransform(Matrix::MakeTranslation({100, 200, 0})); -// std::optional result = -// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); - -// EXPECT_TRUE(result.has_value()); -// if (result.has_value()) { -// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); -// std::optional result_coverage = result.value().GetCoverage(); -// std::optional contents_coverage = contents->GetCoverage(entity); -// EXPECT_TRUE(result_coverage.has_value()); -// EXPECT_TRUE(contents_coverage.has_value()); -// if (result_coverage.has_value() && contents_coverage.has_value()) { -// EXPECT_TRUE(RectNear(contents_coverage.value(), -// Rect::MakeLTRB(99, 199, 201, 301))); -// EXPECT_TRUE( -// RectNear(result_coverage.value(), Rect::MakeLTRB(99, 199, 201, 301))); -// } -// } -// } - -// TEST_P(GaussianBlurFilterContentsTest, -// RenderCoverageMatchesGetCoverageRotated) { -// std::shared_ptr texture = MakeTexture(ISize(400, 300)); -// fml::StatusOr sigma_radius_1 = -// CalculateSigmaForBlurRadius(1.0, Matrix()); -// auto contents = std::make_unique( -// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// contents->SetInputs({FilterInput::Make(texture)}); -// std::shared_ptr renderer = GetContentContext(); - -// Entity entity; -// // Rotate around the top left corner, then push it over to (100, 100). -// entity.SetTransform(Matrix::MakeTranslation({400, 100, 0}) * -// Matrix::MakeRotationZ(Degrees(90.0))); -// std::optional result = -// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); -// EXPECT_TRUE(result.has_value()); -// if (result.has_value()) { -// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); -// std::optional result_coverage = result.value().GetCoverage(); -// std::optional contents_coverage = contents->GetCoverage(entity); -// EXPECT_TRUE(result_coverage.has_value()); -// EXPECT_TRUE(contents_coverage.has_value()); -// if (result_coverage.has_value() && contents_coverage.has_value()) { -// EXPECT_TRUE(RectNear(contents_coverage.value(), -// Rect::MakeLTRB(99, 99, 401, 501))); -// EXPECT_TRUE( -// RectNear(result_coverage.value(), Rect::MakeLTRB(99, 99, 401, 501))); -// } -// } -// } - -// TEST_P(GaussianBlurFilterContentsTest, CalculateUVsSimple) { -// std::shared_ptr texture = MakeTexture(ISize(100, 100)); -// auto filter_input = FilterInput::Make(texture); -// Entity entity; -// Quad uvs = GaussianBlurFilterContents::CalculateUVs( -// filter_input, entity, Rect::MakeSize(ISize(100, 100)), ISize(100, 100)); -// std::optional uvs_bounds = Rect::MakePointBounds(uvs); -// EXPECT_TRUE(uvs_bounds.has_value()); -// if (uvs_bounds.has_value()) { -// EXPECT_TRUE(RectNear(uvs_bounds.value(), Rect::MakeXYWH(0, 0, 1, 1))); -// } -// } - -// TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithDestinationRect) { -// std::shared_ptr texture = MakeTexture(ISize(100, 100)); -// auto texture_contents = std::make_shared(); -// texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); -// texture_contents->SetTexture(texture); -// texture_contents->SetDestinationRect(Rect::MakeXYWH( -// 50, 40, texture->GetSize().width, texture->GetSize().height)); - -// fml::StatusOr sigma_radius_1 = -// CalculateSigmaForBlurRadius(1.0, Matrix()); -// auto contents = std::make_unique( -// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// contents->SetInputs({FilterInput::Make(texture_contents)}); -// std::shared_ptr renderer = GetContentContext(); - -// Entity entity; -// std::optional result = -// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); -// EXPECT_TRUE(result.has_value()); -// if (result.has_value()) { -// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); -// std::optional result_coverage = result.value().GetCoverage(); -// std::optional contents_coverage = contents->GetCoverage(entity); -// EXPECT_TRUE(result_coverage.has_value()); -// EXPECT_TRUE(contents_coverage.has_value()); -// if (result_coverage.has_value() && contents_coverage.has_value()) { -// EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); -// EXPECT_TRUE(RectNear(result_coverage.value(), -// Rect::MakeLTRB(49.f, 39.f, 151.f, 141.f))); -// } -// } -// } - -// TEST_P(GaussianBlurFilterContentsTest, -// TextureContentsWithDestinationRectScaled) { -// std::shared_ptr texture = MakeTexture(ISize(100, 100)); -// auto texture_contents = std::make_shared(); -// texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); -// texture_contents->SetTexture(texture); -// texture_contents->SetDestinationRect(Rect::MakeXYWH( -// 50, 40, texture->GetSize().width, texture->GetSize().height)); - -// fml::StatusOr sigma_radius_1 = -// CalculateSigmaForBlurRadius(1.0, Matrix()); -// auto contents = std::make_unique( -// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, -// /*mask_geometry=*/nullptr); -// contents->SetInputs({FilterInput::Make(texture_contents)}); -// std::shared_ptr renderer = GetContentContext(); - -// Entity entity; -// entity.SetTransform(Matrix::MakeScale({2.0, 2.0, 1.0})); -// std::optional result = -// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); -// EXPECT_TRUE(result.has_value()); -// if (result.has_value()) { -// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); -// std::optional result_coverage = result.value().GetCoverage(); -// std::optional contents_coverage = contents->GetCoverage(entity); -// EXPECT_TRUE(result_coverage.has_value()); -// EXPECT_TRUE(contents_coverage.has_value()); -// if (result_coverage.has_value() && contents_coverage.has_value()) { -// EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); -// // Scaling a blurred entity doesn't seem to scale the blur radius linearly -// // when comparing results with rrect_blur. That's why this is not -// // Rect::MakeXYWH(98.f, 78.f, 204.0f, 204.f). -// EXPECT_TRUE(RectNear(contents_coverage.value(), -// Rect::MakeXYWH(94.f, 74.f, 212.0f, 212.f))); -// } -// } -// } - -// TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithEffectTransform) { -// Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0}); -// std::shared_ptr texture = MakeTexture(ISize(100, 100)); -// auto texture_contents = std::make_shared(); -// texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); -// texture_contents->SetTexture(texture); -// texture_contents->SetDestinationRect(Rect::MakeXYWH( -// 50, 40, texture->GetSize().width, texture->GetSize().height)); - -// fml::StatusOr sigma_radius_1 = -// CalculateSigmaForBlurRadius(1.0, effect_transform); -// ASSERT_TRUE(sigma_radius_1.ok()); -// auto contents = std::make_unique( -// sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, -// FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); -// contents->SetInputs({FilterInput::Make(texture_contents)}); -// contents->SetEffectTransform(effect_transform); -// std::shared_ptr renderer = GetContentContext(); - -// Entity entity; -// std::optional result = -// contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); -// EXPECT_TRUE(result.has_value()); -// if (result.has_value()) { -// EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); -// std::optional result_coverage = result.value().GetCoverage(); -// std::optional contents_coverage = contents->GetCoverage(entity); -// EXPECT_TRUE(result_coverage.has_value()); -// EXPECT_TRUE(contents_coverage.has_value()); -// if (result_coverage.has_value() && contents_coverage.has_value()) { -// EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); -// EXPECT_TRUE(RectNear(contents_coverage.value(), -// Rect::MakeXYWH(49.f, 39.f, 102.f, 102.f))); -// } -// } -// } - -// TEST(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius) { -// Scalar sigma = 1.0; -// Scalar radius = GaussianBlurFilterContents::CalculateBlurRadius( -// GaussianBlurFilterContents::ScaleSigma(sigma)); -// fml::StatusOr derived_sigma = -// CalculateSigmaForBlurRadius(radius, Matrix()); -// ASSERT_TRUE(derived_sigma.ok()); -// EXPECT_NEAR(sigma, derived_sigma.value(), 0.01f); -// } - -// TEST(GaussianBlurFilterContentsTest, Coefficients) { -// BlurParameters parameters = {.blur_uv_offset = Point(1, 0), -// .blur_sigma = 1, -// .blur_radius = 5, -// .step_size = 1}; -// KernelSamples samples = GenerateBlurInfo(parameters); -// EXPECT_EQ(samples.sample_count, 9); - -// // Coefficients should add up to 1. -// Scalar tally = 0; -// for (int i = 0; i < samples.sample_count; ++i) { -// tally += samples.samples[i].coefficient; -// } -// EXPECT_FLOAT_EQ(tally, 1.0f); - -// // Verify the shape of the curve. -// for (int i = 0; i < 4; ++i) { -// EXPECT_FLOAT_EQ(samples.samples[i].coefficient, -// samples.samples[8 - i].coefficient); -// EXPECT_TRUE(samples.samples[i + 1].coefficient > -// samples.samples[i].coefficient); -// } -// } - -// TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) { -// KernelSamples kernel_samples = { -// .sample_count = 5, -// .samples = -// { -// { -// .uv_offset = Vector2(-2, 0), -// .coefficient = 0.1f, -// }, -// { -// .uv_offset = Vector2(-1, 0), -// .coefficient = 0.2f, -// }, -// { -// .uv_offset = Vector2(0, 0), -// .coefficient = 0.4f, -// }, -// { -// .uv_offset = Vector2(1, 0), -// .coefficient = 0.2f, -// }, -// { -// .uv_offset = Vector2(2, 0), -// .coefficient = 0.1f, -// }, -// }, -// }; - -// GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples = -// LerpHackKernelSamples(kernel_samples); -// EXPECT_EQ(fast_kernel_samples.sample_count, 3); - -// GaussianBlurPipeline::FragmentShader::KernelSample* samples = -// kernel_samples.samples; -// GaussianBlurPipeline::FragmentShader::KernelSample* fast_samples = -// fast_kernel_samples.samples; - -// ////////////////////////////////////////////////////////////////////////////// -// // Check output kernel. - -// EXPECT_FLOAT_EQ(fast_samples[0].uv_offset.x, -1.3333333); -// EXPECT_FLOAT_EQ(fast_samples[0].uv_offset.y, 0); -// EXPECT_FLOAT_EQ(fast_samples[0].coefficient, 0.3); -// EXPECT_FLOAT_EQ(fast_samples[1].uv_offset.x, 0); -// EXPECT_FLOAT_EQ(fast_samples[1].uv_offset.y, 0); -// EXPECT_FLOAT_EQ(fast_samples[1].coefficient, 0.4); -// EXPECT_FLOAT_EQ(fast_samples[2].uv_offset.x, 1.3333333); -// EXPECT_FLOAT_EQ(fast_samples[2].uv_offset.y, 0); -// EXPECT_FLOAT_EQ(fast_samples[2].coefficient, 0.3); - -// ////////////////////////////////////////////////////////////////////////////// -// // Check output of fast kernel versus original kernel. - -// Scalar data[5] = {0.25, 0.5, 0.5, 1.0, 0.2}; -// Scalar original_output = -// samples[0].coefficient * data[0] + samples[1].coefficient * data[1] + -// samples[2].coefficient * data[2] + samples[3].coefficient * data[3] + -// samples[4].coefficient * data[4]; - -// auto lerp = [](const Point& point, Scalar left, Scalar right) { -// Scalar int_part; -// Scalar fract = fabsf(modf(point.x, &int_part)); -// if (point.x < 0) { -// return left * fract + right * (1.0 - fract); -// } else { -// return left * (1.0 - fract) + right * fract; -// } -// }; -// Scalar fast_output = -// /*1st*/ lerp(fast_samples[0].uv_offset, data[0], data[1]) * -// fast_samples[0].coefficient + -// /*2nd*/ data[2] * fast_samples[1].coefficient + -// /*3rd*/ lerp(fast_samples[2].uv_offset, data[3], data[4]) * -// fast_samples[2].coefficient; - -// EXPECT_NEAR(original_output, fast_output, 0.01); -// } - -// TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) { -// Scalar sigma = 10.0f; -// int32_t blur_radius = static_cast( -// std::ceil(GaussianBlurFilterContents::CalculateBlurRadius(sigma))); -// BlurParameters parameters = {.blur_uv_offset = Point(1, 0), -// .blur_sigma = sigma, -// .blur_radius = blur_radius, -// .step_size = 1}; -// KernelSamples kernel_samples = GenerateBlurInfo(parameters); -// EXPECT_EQ(kernel_samples.sample_count, 33); -// GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples = -// LerpHackKernelSamples(kernel_samples); -// EXPECT_EQ(fast_kernel_samples.sample_count, 17); -// float data[33]; -// srand(0); -// for (int i = 0; i < 33; i++) { -// data[i] = 255.0 * static_cast(IMPELLER_RAND()) / RAND_MAX; -// } - -// auto sampler = [data](Point point) -> Scalar { -// FML_CHECK(point.y == 0.0f); -// FML_CHECK(point.x >= -16); -// FML_CHECK(point.x <= 16); -// Scalar fint_part; -// Scalar fract = fabsf(modf(point.x, &fint_part)); -// if (fract == 0) { -// int32_t int_part = static_cast(fint_part) + 16; -// return data[int_part]; -// } else { -// int32_t left = static_cast(floor(point.x)) + 16; -// int32_t right = static_cast(ceil(point.x)) + 16; -// if (point.x < 0) { -// return fract * data[left] + (1.0 - fract) * data[right]; -// } else { -// return (1.0 - fract) * data[left] + fract * data[right]; -// } -// } -// }; - -// Scalar output = 0.0; -// for (int i = 0; i < kernel_samples.sample_count; ++i) { -// auto sample = kernel_samples.samples[i]; -// output += sample.coefficient * sampler(sample.uv_offset); -// } - -// Scalar fast_output = 0.0; -// for (int i = 0; i < fast_kernel_samples.sample_count; ++i) { -// auto sample = fast_kernel_samples.samples[i]; -// fast_output += sample.coefficient * sampler(sample.uv_offset); -// } - -// EXPECT_NEAR(output, fast_output, 0.1); -// } - -// TEST(GaussianBlurFilterContentsTest, ChopHugeBlurs) { -// Scalar sigma = 30.5f; -// int32_t blur_radius = static_cast( -// std::ceil(GaussianBlurFilterContents::CalculateBlurRadius(sigma))); -// BlurParameters parameters = {.blur_uv_offset = Point(1, 0), -// .blur_sigma = sigma, -// .blur_radius = blur_radius, -// .step_size = 1}; -// KernelSamples kernel_samples = GenerateBlurInfo(parameters); -// GaussianBlurPipeline::FragmentShader::KernelSamples frag_kernel_samples = -// LerpHackKernelSamples(kernel_samples); -// EXPECT_TRUE(frag_kernel_samples.sample_count <= kGaussianBlurMaxKernelSize); -// } +namespace { + +// Use newtonian method to give the closest answer to target where +// f(x) is less than the target. We do this because the value is `ceil`'d to +// grab fractional pixels. +fml::StatusOr LowerBoundNewtonianMethod( + const std::function& func, + float target, + float guess, + float tolerance) { + const double delta = 1e-6; + double x = guess; + double fx; + static const int kMaxIterations = 1000; + int count = 0; + + do { + fx = func(x) - target; + double derivative = (func(x + delta) - func(x)) / delta; + x = x - fx / derivative; + if (++count > kMaxIterations) { + return fml::Status(fml::StatusCode::kDeadlineExceeded, + "Did not converge on answer."); + } + } while (std::abs(fx) > tolerance || + fx < 0.0); // fx < 0.0 makes this lower bound. + + return x; +} + +fml::StatusOr CalculateSigmaForBlurRadius( + Scalar radius, + const Matrix& effect_transform) { + auto f = [effect_transform](Scalar x) -> Scalar { + Vector2 scaled_sigma = (effect_transform.Basis() * + Vector2(GaussianBlurFilterContents::ScaleSigma(x), + GaussianBlurFilterContents::ScaleSigma(x))) + .Abs(); + Vector2 blur_radius = Vector2( + GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.x), + GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.y)); + return std::max(blur_radius.x, blur_radius.y); + }; + // The newtonian method is used here since inverting the function is + // non-trivial because of conditional logic and would be fragile to changes. + return LowerBoundNewtonianMethod(f, radius, 2.f, 0.001f); +} + +} // namespace + +class GaussianBlurFilterContentsTest : public EntityPlayground { + public: + /// Create a texture that has been cleared to transparent black. + std::shared_ptr MakeTexture(ISize size) { + std::shared_ptr command_buffer = + GetContentContext()->GetContext()->CreateCommandBuffer(); + if (!command_buffer) { + return nullptr; + } + + auto render_target = GetContentContext()->MakeSubpass( + "Clear Subpass", size, command_buffer, + [](const ContentContext&, RenderPass&) { return true; }); + + if (!GetContentContext() + ->GetContext() + ->GetCommandQueue() + ->Submit(/*buffers=*/{command_buffer}) + .ok()) { + return nullptr; + } + + if (render_target.ok()) { + return render_target.value().GetRenderTargetTexture(); + } + return nullptr; + } +}; +INSTANTIATE_PLAYGROUND_SUITE(GaussianBlurFilterContentsTest); + +TEST(GaussianBlurFilterContentsTest, Create) { + GaussianBlurFilterContents contents( + /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + EXPECT_EQ(contents.GetSigmaX(), 0.0); + EXPECT_EQ(contents.GetSigmaY(), 0.0); +} + +TEST(GaussianBlurFilterContentsTest, CoverageEmpty) { + GaussianBlurFilterContents contents( + /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + FilterInput::Vector inputs = {}; + Entity entity; + std::optional coverage = + contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); + ASSERT_FALSE(coverage.has_value()); +} + +TEST(GaussianBlurFilterContentsTest, CoverageSimple) { + GaussianBlurFilterContents contents( + /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + FilterInput::Vector inputs = { + FilterInput::Make(Rect::MakeLTRB(10, 10, 110, 110))}; + Entity entity; + std::optional coverage = + contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); + + ASSERT_EQ(coverage, Rect::MakeLTRB(10, 10, 110, 110)); +} + +TEST(GaussianBlurFilterContentsTest, CoverageWithSigma) { + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); + ASSERT_TRUE(sigma_radius_1.ok()); + GaussianBlurFilterContents contents( + /*sigma_x=*/sigma_radius_1.value(), + /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + FilterInput::Vector inputs = { + FilterInput::Make(Rect::MakeLTRB(100, 100, 200, 200))}; + Entity entity; + std::optional coverage = + contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); + + EXPECT_TRUE(coverage.has_value()); + if (coverage.has_value()) { + EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201)); + } +} + +TEST_P(GaussianBlurFilterContentsTest, CoverageWithTexture) { + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); + ASSERT_TRUE(sigma_radius_1.ok()); + GaussianBlurFilterContents contents( + /*sigma_X=*/sigma_radius_1.value(), + /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + std::shared_ptr texture = MakeTexture(ISize(100, 100)); + FilterInput::Vector inputs = {FilterInput::Make(texture)}; + Entity entity; + entity.SetTransform(Matrix::MakeTranslation({100, 100, 0})); + std::optional coverage = + contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix()); + + EXPECT_TRUE(coverage.has_value()); + if (coverage.has_value()) { + EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201)); + } +} + +TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) { + Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0}); + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, effect_transform); + ASSERT_TRUE(sigma_radius_1.ok()); + GaussianBlurFilterContents contents( + /*sigma_x=*/sigma_radius_1.value(), + /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + std::shared_ptr texture = MakeTexture(ISize(100, 100)); + FilterInput::Vector inputs = {FilterInput::Make(texture)}; + Entity entity; + entity.SetTransform(Matrix::MakeTranslation({100, 100, 0})); + std::optional coverage = + contents.GetFilterCoverage(inputs, entity, effect_transform); + EXPECT_TRUE(coverage.has_value()); + if (coverage.has_value()) { + EXPECT_RECT_NEAR(coverage.value(), + Rect::MakeLTRB(100 - 1, 100 - 1, 200 + 1, 200 + 1)); + } +} + +TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) { + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); + ASSERT_TRUE(sigma_radius_1.ok()); + auto contents = std::make_unique( + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + std::optional coverage = contents->GetFilterSourceCoverage( + /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}), + /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200)); + EXPECT_TRUE(coverage.has_value()); + if (coverage.has_value()) { + EXPECT_RECT_NEAR(coverage.value(), + Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2)); + } +} + +TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) { + EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1.0f), 1); + EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(2.0f), 1); + EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(3.0f), 1); + EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(4.0f), 1); + EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(16.0f), 0.25); + // Hang on to 1/8 as long as possible. + EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(95.0f), 0.125); + EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(96.0f), 0.0625); + // Downsample clamped to 1/16th. + EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1024.0f), 0.0625); +} + +TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) { + std::shared_ptr texture = MakeTexture(ISize(100, 100)); + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); + ASSERT_TRUE(sigma_radius_1.ok()); + auto contents = std::make_unique( + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + contents->SetInputs({FilterInput::Make(texture)}); + std::shared_ptr renderer = GetContentContext(); + + Entity entity; + std::optional result = + contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); + EXPECT_TRUE(result.has_value()); + if (result.has_value()) { + EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); + std::optional result_coverage = result.value().GetCoverage(); + std::optional contents_coverage = contents->GetCoverage(entity); + EXPECT_TRUE(result_coverage.has_value()); + EXPECT_TRUE(contents_coverage.has_value()); + if (result_coverage.has_value() && contents_coverage.has_value()) { + EXPECT_TRUE(RectNear(contents_coverage.value(), + Rect::MakeLTRB(-1, -1, 101, 101))); + EXPECT_TRUE( + RectNear(result_coverage.value(), Rect::MakeLTRB(-1, -1, 101, 101))); + } + } +} + +TEST_P(GaussianBlurFilterContentsTest, + RenderCoverageMatchesGetCoverageTranslate) { + std::shared_ptr texture = MakeTexture(ISize(100, 100)); + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); + ASSERT_TRUE(sigma_radius_1.ok()); + auto contents = std::make_unique( + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + contents->SetInputs({FilterInput::Make(texture)}); + std::shared_ptr renderer = GetContentContext(); + + Entity entity; + entity.SetTransform(Matrix::MakeTranslation({100, 200, 0})); + std::optional result = + contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); + + EXPECT_TRUE(result.has_value()); + if (result.has_value()) { + EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); + std::optional result_coverage = result.value().GetCoverage(); + std::optional contents_coverage = contents->GetCoverage(entity); + EXPECT_TRUE(result_coverage.has_value()); + EXPECT_TRUE(contents_coverage.has_value()); + if (result_coverage.has_value() && contents_coverage.has_value()) { + EXPECT_TRUE(RectNear(contents_coverage.value(), + Rect::MakeLTRB(99, 199, 201, 301))); + EXPECT_TRUE( + RectNear(result_coverage.value(), Rect::MakeLTRB(99, 199, 201, 301))); + } + } +} + +TEST_P(GaussianBlurFilterContentsTest, + RenderCoverageMatchesGetCoverageRotated) { + std::shared_ptr texture = MakeTexture(ISize(400, 300)); + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); + auto contents = std::make_unique( + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + contents->SetInputs({FilterInput::Make(texture)}); + std::shared_ptr renderer = GetContentContext(); + + Entity entity; + // Rotate around the top left corner, then push it over to (100, 100). + entity.SetTransform(Matrix::MakeTranslation({400, 100, 0}) * + Matrix::MakeRotationZ(Degrees(90.0))); + std::optional result = + contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); + EXPECT_TRUE(result.has_value()); + if (result.has_value()) { + EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); + std::optional result_coverage = result.value().GetCoverage(); + std::optional contents_coverage = contents->GetCoverage(entity); + EXPECT_TRUE(result_coverage.has_value()); + EXPECT_TRUE(contents_coverage.has_value()); + if (result_coverage.has_value() && contents_coverage.has_value()) { + EXPECT_TRUE(RectNear(contents_coverage.value(), + Rect::MakeLTRB(99, 99, 401, 501))); + EXPECT_TRUE( + RectNear(result_coverage.value(), Rect::MakeLTRB(99, 99, 401, 501))); + } + } +} + +TEST_P(GaussianBlurFilterContentsTest, CalculateUVsSimple) { + std::shared_ptr texture = MakeTexture(ISize(100, 100)); + auto filter_input = FilterInput::Make(texture); + Entity entity; + Quad uvs = GaussianBlurFilterContents::CalculateUVs( + filter_input, entity, Rect::MakeSize(ISize(100, 100)), ISize(100, 100)); + std::optional uvs_bounds = Rect::MakePointBounds(uvs); + EXPECT_TRUE(uvs_bounds.has_value()); + if (uvs_bounds.has_value()) { + EXPECT_TRUE(RectNear(uvs_bounds.value(), Rect::MakeXYWH(0, 0, 1, 1))); + } +} + +TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithDestinationRect) { + std::shared_ptr texture = MakeTexture(ISize(100, 100)); + auto texture_contents = std::make_shared(); + texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); + texture_contents->SetTexture(texture); + texture_contents->SetDestinationRect(Rect::MakeXYWH( + 50, 40, texture->GetSize().width, texture->GetSize().height)); + + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); + auto contents = std::make_unique( + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + contents->SetInputs({FilterInput::Make(texture_contents)}); + std::shared_ptr renderer = GetContentContext(); + + Entity entity; + std::optional result = + contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); + EXPECT_TRUE(result.has_value()); + if (result.has_value()) { + EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); + std::optional result_coverage = result.value().GetCoverage(); + std::optional contents_coverage = contents->GetCoverage(entity); + EXPECT_TRUE(result_coverage.has_value()); + EXPECT_TRUE(contents_coverage.has_value()); + if (result_coverage.has_value() && contents_coverage.has_value()) { + EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); + EXPECT_TRUE(RectNear(result_coverage.value(), + Rect::MakeLTRB(49.f, 39.f, 151.f, 141.f))); + } + } +} + +TEST_P(GaussianBlurFilterContentsTest, + TextureContentsWithDestinationRectScaled) { + std::shared_ptr texture = MakeTexture(ISize(100, 100)); + auto texture_contents = std::make_shared(); + texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); + texture_contents->SetTexture(texture); + texture_contents->SetDestinationRect(Rect::MakeXYWH( + 50, 40, texture->GetSize().width, texture->GetSize().height)); + + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); + auto contents = std::make_unique( + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, + /*mask_geometry=*/nullptr); + contents->SetInputs({FilterInput::Make(texture_contents)}); + std::shared_ptr renderer = GetContentContext(); + + Entity entity; + entity.SetTransform(Matrix::MakeScale({2.0, 2.0, 1.0})); + std::optional result = + contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); + EXPECT_TRUE(result.has_value()); + if (result.has_value()) { + EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); + std::optional result_coverage = result.value().GetCoverage(); + std::optional contents_coverage = contents->GetCoverage(entity); + EXPECT_TRUE(result_coverage.has_value()); + EXPECT_TRUE(contents_coverage.has_value()); + if (result_coverage.has_value() && contents_coverage.has_value()) { + EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); + // Scaling a blurred entity doesn't seem to scale the blur radius linearly + // when comparing results with rrect_blur. That's why this is not + // Rect::MakeXYWH(98.f, 78.f, 204.0f, 204.f). + EXPECT_TRUE(RectNear(contents_coverage.value(), + Rect::MakeXYWH(94.f, 74.f, 212.0f, 212.f))); + } + } +} + +TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithEffectTransform) { + Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0}); + std::shared_ptr texture = MakeTexture(ISize(100, 100)); + auto texture_contents = std::make_shared(); + texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize())); + texture_contents->SetTexture(texture); + texture_contents->SetDestinationRect(Rect::MakeXYWH( + 50, 40, texture->GetSize().width, texture->GetSize().height)); + + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, effect_transform); + ASSERT_TRUE(sigma_radius_1.ok()); + auto contents = std::make_unique( + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal, + FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr); + contents->SetInputs({FilterInput::Make(texture_contents)}); + contents->SetEffectTransform(effect_transform); + std::shared_ptr renderer = GetContentContext(); + + Entity entity; + std::optional result = + contents->GetEntity(*renderer, entity, /*coverage_hint=*/{}); + EXPECT_TRUE(result.has_value()); + if (result.has_value()) { + EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver); + std::optional result_coverage = result.value().GetCoverage(); + std::optional contents_coverage = contents->GetCoverage(entity); + EXPECT_TRUE(result_coverage.has_value()); + EXPECT_TRUE(contents_coverage.has_value()); + if (result_coverage.has_value() && contents_coverage.has_value()) { + EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value())); + EXPECT_TRUE(RectNear(contents_coverage.value(), + Rect::MakeXYWH(49.f, 39.f, 102.f, 102.f))); + } + } +} + +TEST(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius) { + Scalar sigma = 1.0; + Scalar radius = GaussianBlurFilterContents::CalculateBlurRadius( + GaussianBlurFilterContents::ScaleSigma(sigma)); + fml::StatusOr derived_sigma = + CalculateSigmaForBlurRadius(radius, Matrix()); + ASSERT_TRUE(derived_sigma.ok()); + EXPECT_NEAR(sigma, derived_sigma.value(), 0.01f); +} + +TEST(GaussianBlurFilterContentsTest, Coefficients) { + BlurParameters parameters = {.blur_uv_offset = Point(1, 0), + .blur_sigma = 1, + .blur_radius = 5, + .step_size = 1}; + KernelSamples samples = GenerateBlurInfo(parameters); + EXPECT_EQ(samples.sample_count, 9); + + // Coefficients should add up to 1. + Scalar tally = 0; + for (int i = 0; i < samples.sample_count; ++i) { + tally += samples.samples[i].coefficient; + } + EXPECT_FLOAT_EQ(tally, 1.0f); + + // Verify the shape of the curve. + for (int i = 0; i < 4; ++i) { + EXPECT_FLOAT_EQ(samples.samples[i].coefficient, + samples.samples[8 - i].coefficient); + EXPECT_TRUE(samples.samples[i + 1].coefficient > + samples.samples[i].coefficient); + } +} + +TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) { + KernelSamples kernel_samples = { + .sample_count = 5, + .samples = + { + { + .uv_offset = Vector2(-2, 0), + .coefficient = 0.1f, + }, + { + .uv_offset = Vector2(-1, 0), + .coefficient = 0.2f, + }, + { + .uv_offset = Vector2(0, 0), + .coefficient = 0.4f, + }, + { + .uv_offset = Vector2(1, 0), + .coefficient = 0.2f, + }, + { + .uv_offset = Vector2(2, 0), + .coefficient = 0.1f, + }, + }, + }; + + GaussianBlurPipeline::FragmentShader::BlurInfo blur_info = + LerpHackKernelSamples(kernel_samples); + EXPECT_EQ(blur_info.sample_count, 3); + + KernelSample* samples = kernel_samples.samples; + + ////////////////////////////////////////////////////////////////////////////// + // Check output kernel. + + EXPECT_FLOAT_EQ(blur_info.uv_offsets[0].value.x, -1.3333333); + EXPECT_FLOAT_EQ(blur_info.uv_offsets[0].value.y, 0); + EXPECT_FLOAT_EQ(blur_info.coefficients[0].value, 0.3); + EXPECT_FLOAT_EQ(blur_info.uv_offsets[1].value.x, 0); + EXPECT_FLOAT_EQ(blur_info.uv_offsets[1].value.y, 0); + EXPECT_FLOAT_EQ(blur_info.coefficients[1].value, 0.4); + EXPECT_FLOAT_EQ(blur_info.uv_offsets[2].value.x, 1.3333333); + EXPECT_FLOAT_EQ(blur_info.uv_offsets[2].value.y, 0); + EXPECT_FLOAT_EQ(blur_info.coefficients[2].value, 0.3); + + ////////////////////////////////////////////////////////////////////////////// + // Check output of fast kernel versus original kernel. + + Scalar data[5] = {0.25, 0.5, 0.5, 1.0, 0.2}; + Scalar original_output = + samples[0].coefficient * data[0] + samples[1].coefficient * data[1] + + samples[2].coefficient * data[2] + samples[3].coefficient * data[3] + + samples[4].coefficient * data[4]; + + auto lerp = [](const Point& point, Scalar left, Scalar right) { + Scalar int_part; + Scalar fract = fabsf(modf(point.x, &int_part)); + if (point.x < 0) { + return left * fract + right * (1.0 - fract); + } else { + return left * (1.0 - fract) + right * fract; + } + }; + Scalar fast_output = + /*1st*/ lerp(blur_info.uv_offsets[0].value, data[0], data[1]) * + blur_info.coefficients[0].value + + /*2nd*/ data[2] * blur_info.coefficients[1].value + + /*3rd*/ lerp(blur_info.uv_offsets[2].value, data[3], data[4]) * + blur_info.coefficients[2].value; + + EXPECT_NEAR(original_output, fast_output, 0.01); +} + +TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) { + Scalar sigma = 10.0f; + int32_t blur_radius = static_cast( + std::ceil(GaussianBlurFilterContents::CalculateBlurRadius(sigma))); + BlurParameters parameters = {.blur_uv_offset = Point(1, 0), + .blur_sigma = sigma, + .blur_radius = blur_radius, + .step_size = 1}; + KernelSamples kernel_samples = GenerateBlurInfo(parameters); + EXPECT_EQ(kernel_samples.sample_count, 33); + GaussianBlurPipeline::FragmentShader::BlurInfo fast_kernel_samples = + LerpHackKernelSamples(kernel_samples); + EXPECT_EQ(fast_kernel_samples.sample_count, 17); + float data[33]; + srand(0); + for (int i = 0; i < 33; i++) { + data[i] = 255.0 * static_cast(IMPELLER_RAND()) / RAND_MAX; + } + + auto sampler = [data](Point point) -> Scalar { + FML_CHECK(point.y == 0.0f); + FML_CHECK(point.x >= -16); + FML_CHECK(point.x <= 16); + Scalar fint_part; + Scalar fract = fabsf(modf(point.x, &fint_part)); + if (fract == 0) { + int32_t int_part = static_cast(fint_part) + 16; + return data[int_part]; + } else { + int32_t left = static_cast(floor(point.x)) + 16; + int32_t right = static_cast(ceil(point.x)) + 16; + if (point.x < 0) { + return fract * data[left] + (1.0 - fract) * data[right]; + } else { + return (1.0 - fract) * data[left] + fract * data[right]; + } + } + }; + + Scalar output = 0.0; + for (int i = 0; i < kernel_samples.sample_count; ++i) { + auto sample = kernel_samples.samples[i]; + output += sample.coefficient * sampler(sample.uv_offset); + } + + Scalar fast_output = 0.0; + for (int i = 0; i < fast_kernel_samples.sample_count; i++) { + fast_output += fast_kernel_samples.coefficients[i].value * + sampler(fast_kernel_samples.uv_offsets[i].value); + } + + EXPECT_NEAR(output, fast_output, 0.1); +} + +TEST(GaussianBlurFilterContentsTest, ChopHugeBlurs) { + Scalar sigma = 30.5f; + int32_t blur_radius = static_cast( + std::ceil(GaussianBlurFilterContents::CalculateBlurRadius(sigma))); + BlurParameters parameters = {.blur_uv_offset = Point(1, 0), + .blur_sigma = sigma, + .blur_radius = blur_radius, + .step_size = 1}; + KernelSamples kernel_samples = GenerateBlurInfo(parameters); + GaussianBlurPipeline::FragmentShader::BlurInfo frag_kernel_samples = + LerpHackKernelSamples(kernel_samples); + EXPECT_TRUE(frag_kernel_samples.sample_count <= kGaussianBlurMaxKernelSize); +} } // namespace testing } // namespace impeller diff --git a/impeller/entity/shaders/filters/gaussian.frag b/impeller/entity/shaders/filters/gaussian.frag index b831498ac4979..908ecb64e73da 100644 --- a/impeller/entity/shaders/filters/gaussian.frag +++ b/impeller/entity/shaders/filters/gaussian.frag @@ -35,8 +35,8 @@ void main() { for (int i = 0; i < int(blur_info.sample_count); i++) { float16_t coefficient = float16_t(blur_info.coefficients[i]); total_color += - coefficient * Sample(texture_sampler, - v_texture_coords + blur_info.uv_offsets[i]); + coefficient * + Sample(texture_sampler, v_texture_coords + blur_info.uv_offsets[i]); } frag_color = total_color; From ced0ea0156d3e475b0f85909e79f74aaf1ee407c Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Sat, 21 Sep 2024 11:57:11 -0700 Subject: [PATCH 3/5] fix padding. --- impeller/core/shader_types.h | 2 -- .../filters/gaussian_blur_filter_contents.cc | 20 ++++++++---- ...gaussian_blur_filter_contents_unittests.cc | 32 +++++++++---------- impeller/entity/shaders/filters/gaussian.frag | 9 +++--- impeller/geometry/vector.h | 2 ++ .../backend/gles/buffer_bindings_gles.cc | 15 ++++----- .../backend/gles/buffer_bindings_gles.h | 1 + 7 files changed, 44 insertions(+), 37 deletions(-) diff --git a/impeller/core/shader_types.h b/impeller/core/shader_types.h index 391b24daf908f..f3759552f4877 100644 --- a/impeller/core/shader_types.h +++ b/impeller/core/shader_types.h @@ -183,8 +183,6 @@ struct Padded { Padding _PADDING_; Padded(T p_value) : value(p_value){}; // NOLINT(google-explicit-constructor) - - Padded(){}; // NOLINT(google-explicit-constructor) }; inline constexpr Vector4 ToVector(Color color) { diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index 9c512bd344d29..b65d9aa6f78fa 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -399,7 +399,8 @@ fml::StatusOr MakeDownsampleSubpass( TextureFillVertexShader::FrameInfo frame_info; frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); - frame_info.texture_sampler_y_coord_scale = 1.0; + frame_info.texture_sampler_y_coord_scale = + input_texture->GetYCoordScale(); TextureDownsampleFragmentShader::FragInfo frag_info; frag_info.edge = edge; @@ -928,16 +929,21 @@ GaussianBlurPipeline::FragmentShader::BlurInfo LerpHackKernelSamples( for (int i = 0; i < result.sample_count; i++) { if (i == middle) { - result.coefficients[i] = parameters.samples[j].coefficient; - result.uv_offsets[i] = parameters.samples[j++].uv_offset; + result.sample_data[i].x = parameters.samples[j].uv_offset.x; + result.sample_data[i].y = parameters.samples[j].uv_offset.y; + result.sample_data[i].z = parameters.samples[j].coefficient; + j++; } else { KernelSample left = parameters.samples[j]; KernelSample right = parameters.samples[j + 1]; - result.coefficients[i] = left.coefficient + right.coefficient; - result.uv_offsets[i] = (left.uv_offset * left.coefficient + - right.uv_offset * right.coefficient) / - (left.coefficient + right.coefficient); + result.sample_data[i].z = left.coefficient + right.coefficient; + + auto uv = (left.uv_offset * left.coefficient + + right.uv_offset * right.coefficient) / + (left.coefficient + right.coefficient); + result.sample_data[i].x = uv.x; + result.sample_data[i].y = uv.y; j += 2; } } diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc index 8d16acb0d192f..68e87be958fd4 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc @@ -517,15 +517,15 @@ TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) { ////////////////////////////////////////////////////////////////////////////// // Check output kernel. - EXPECT_FLOAT_EQ(blur_info.uv_offsets[0].value.x, -1.3333333); - EXPECT_FLOAT_EQ(blur_info.uv_offsets[0].value.y, 0); - EXPECT_FLOAT_EQ(blur_info.coefficients[0].value, 0.3); - EXPECT_FLOAT_EQ(blur_info.uv_offsets[1].value.x, 0); - EXPECT_FLOAT_EQ(blur_info.uv_offsets[1].value.y, 0); - EXPECT_FLOAT_EQ(blur_info.coefficients[1].value, 0.4); - EXPECT_FLOAT_EQ(blur_info.uv_offsets[2].value.x, 1.3333333); - EXPECT_FLOAT_EQ(blur_info.uv_offsets[2].value.y, 0); - EXPECT_FLOAT_EQ(blur_info.coefficients[2].value, 0.3); + EXPECT_FLOAT_EQ(blur_info.sample_data[0].x, -1.3333333); + EXPECT_FLOAT_EQ(blur_info.sample_data[0].y, 0); + EXPECT_FLOAT_EQ(blur_info.sample_data[0].z, 0.3); + EXPECT_FLOAT_EQ(blur_info.sample_data[1].x, 0); + EXPECT_FLOAT_EQ(blur_info.sample_data[1].y, 0); + EXPECT_FLOAT_EQ(blur_info.sample_data[1].z, 0.4); + EXPECT_FLOAT_EQ(blur_info.sample_data[2].x, 1.3333333); + EXPECT_FLOAT_EQ(blur_info.sample_data[2].y, 0); + EXPECT_FLOAT_EQ(blur_info.sample_data[2].z, 0.3); ////////////////////////////////////////////////////////////////////////////// // Check output of fast kernel versus original kernel. @@ -546,11 +546,11 @@ TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) { } }; Scalar fast_output = - /*1st*/ lerp(blur_info.uv_offsets[0].value, data[0], data[1]) * - blur_info.coefficients[0].value + - /*2nd*/ data[2] * blur_info.coefficients[1].value + - /*3rd*/ lerp(blur_info.uv_offsets[2].value, data[3], data[4]) * - blur_info.coefficients[2].value; + /*1st*/ lerp(blur_info.sample_data[0].xy(), data[0], data[1]) * + blur_info.sample_data[0].z + + /*2nd*/ data[2] * blur_info.sample_data[1].z + + /*3rd*/ lerp(blur_info.sample_data[2].xy(), data[3], data[4]) * + blur_info.sample_data[2].z; EXPECT_NEAR(original_output, fast_output, 0.01); } @@ -602,8 +602,8 @@ TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) { Scalar fast_output = 0.0; for (int i = 0; i < fast_kernel_samples.sample_count; i++) { - fast_output += fast_kernel_samples.coefficients[i].value * - sampler(fast_kernel_samples.uv_offsets[i].value); + fast_output += fast_kernel_samples.sample_data[i].z * + sampler(fast_kernel_samples.sample_data[i].xy()); } EXPECT_NEAR(output, fast_output, 0.1); diff --git a/impeller/entity/shaders/filters/gaussian.frag b/impeller/entity/shaders/filters/gaussian.frag index 908ecb64e73da..5a9ee170f728d 100644 --- a/impeller/entity/shaders/filters/gaussian.frag +++ b/impeller/entity/shaders/filters/gaussian.frag @@ -13,8 +13,9 @@ layout(constant_id = 0) const float supports_decal = 1.0; uniform BlurInfo { float sample_count; - vec2 uv_offsets[50]; - float coefficients[50]; + + // X, Y are uv offset and Z is Coefficient. W is padding. + vec4 sample_data[50]; } blur_info; @@ -33,10 +34,10 @@ void main() { f16vec4 total_color = f16vec4(0.0hf); for (int i = 0; i < int(blur_info.sample_count); i++) { - float16_t coefficient = float16_t(blur_info.coefficients[i]); + float16_t coefficient = float16_t(blur_info.sample_data[i].z); total_color += coefficient * - Sample(texture_sampler, v_texture_coords + blur_info.uv_offsets[i]); + Sample(texture_sampler, v_texture_coords + blur_info.sample_data[i].xy); } frag_color = total_color; diff --git a/impeller/geometry/vector.h b/impeller/geometry/vector.h index 31e894cb0a10e..d1358bffef4de 100644 --- a/impeller/geometry/vector.h +++ b/impeller/geometry/vector.h @@ -310,6 +310,8 @@ struct Vector4 { return *this + (v - *this) * t; } + constexpr Vector2 xy() const { return Vector2(x, y); } + std::string ToString() const; }; diff --git a/impeller/renderer/backend/gles/buffer_bindings_gles.cc b/impeller/renderer/backend/gles/buffer_bindings_gles.cc index 4067498173b9e..7292049a140c9 100644 --- a/impeller/renderer/backend/gles/buffer_bindings_gles.cc +++ b/impeller/renderer/backend/gles/buffer_bindings_gles.cc @@ -279,20 +279,19 @@ bool BufferBindingsGLES::BindUniformBuffer(const ProcTableGLES& gl, auto* buffer_data = reinterpret_cast(buffer_ptr + member.offset); - std::vector array_element_buffer; - if (element_count > 1) { - // When binding uniform arrays, the elements must be contiguous. Copy - // the uniforms to a temp buffer to eliminate any padding needed by the - // other backends. - array_element_buffer.resize(member.size * element_count); + // When binding uniform arrays, the elements must be contiguous. Copy + // the uniforms to a temp buffer to eliminate any padding needed by the + // other backends if the array elements have padding. + if (element_count > 1 && element_stride != member.size) { + array_element_buffer_.resize(member.size * element_count); for (size_t element_i = 0; element_i < element_count; element_i++) { - std::memcpy(array_element_buffer.data() + element_i * member.size, + std::memcpy(array_element_buffer_.data() + element_i * member.size, reinterpret_cast(buffer_data) + element_i * element_stride, member.size); } buffer_data = - reinterpret_cast(array_element_buffer.data()); + reinterpret_cast(array_element_buffer_.data()); } switch (member.type) { diff --git a/impeller/renderer/backend/gles/buffer_bindings_gles.h b/impeller/renderer/backend/gles/buffer_bindings_gles.h index 708980d553b88..a70b3aa0c20c8 100644 --- a/impeller/renderer/backend/gles/buffer_bindings_gles.h +++ b/impeller/renderer/backend/gles/buffer_bindings_gles.h @@ -57,6 +57,7 @@ class BufferBindingsGLES { std::vector vertex_attrib_arrays_; std::unordered_map uniform_locations_; + std::vector array_element_buffer_; using BindingMap = std::unordered_map>; BindingMap binding_map_ = {}; From e1bfaf1780f9bedefc687bbeb97b95275c57d5f5 Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Sat, 21 Sep 2024 12:36:41 -0700 Subject: [PATCH 4/5] malioc. --- impeller/tools/malioc.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/impeller/tools/malioc.json b/impeller/tools/malioc.json index c6e8eb402db34..65d3b54f4eb3c 100644 --- a/impeller/tools/malioc.json +++ b/impeller/tools/malioc.json @@ -2581,9 +2581,9 @@ "arith_cvt" ], "shortest_path_cycles": [ - 0.109375, + 0.09375, 0.0, - 0.109375, + 0.09375, 0.0, 0.0, 0.0, @@ -2593,11 +2593,11 @@ "load_store" ], "total_cycles": [ - 0.3125, + 0.265625, 0.09375, - 0.3125, + 0.265625, 0.0, - 2.0, + 1.0, 0.25, 0.25 ] @@ -2641,10 +2641,11 @@ 0.0 ], "total_bound_pipelines": [ + "arithmetic", "load_store" ], "total_cycles": [ - 1.6666666269302368, + 2.0, 2.0, 1.0 ] From dd01c63de02b056d8e2cc6beb76bc3f690690850 Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Mon, 23 Sep 2024 10:23:23 -0700 Subject: [PATCH 5/5] aaron review. --- .../filters/gaussian_blur_filter_contents.cc | 15 +++--- .../filters/gaussian_blur_filter_contents.h | 6 ++- ...gaussian_blur_filter_contents_unittests.cc | 47 +++++++++++-------- impeller/entity/shaders/filters/gaussian.frag | 14 +++--- .../backend/gles/buffer_bindings_gles.cc | 1 + .../backend/gles/buffer_bindings_gles.h | 1 - 6 files changed, 50 insertions(+), 34 deletions(-) diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index b65d9aa6f78fa..2af4f2b9cf6d2 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -12,6 +12,7 @@ #include "impeller/entity/texture_downsample.frag.h" #include "impeller/entity/texture_fill.frag.h" #include "impeller/entity/texture_fill.vert.h" +#include "impeller/geometry/color.h" #include "impeller/renderer/render_pass.h" #include "impeller/renderer/vertex_buffer_builder.h" @@ -485,7 +486,7 @@ fml::StatusOr MakeBlurSubpass( linear_sampler_descriptor)); GaussianBlurVertexShader::BindFrameInfo( pass, host_buffer.EmplaceUniform(frame_info)); - GaussianBlurFragmentShader::BindBlurInfo( + GaussianBlurFragmentShader::BindKernelSamples( pass, host_buffer.EmplaceUniform( LerpHackKernelSamples(GenerateBlurInfo(blur_info)))); return pass.Draw().ok(); @@ -919,13 +920,15 @@ KernelSamples GenerateBlurInfo(BlurParameters parameters) { // This works by shrinking the kernel size by 2 and relying on lerp to read // between the samples. -GaussianBlurPipeline::FragmentShader::BlurInfo LerpHackKernelSamples( +GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples( KernelSamples parameters) { - GaussianBlurPipeline::FragmentShader::BlurInfo result = {}; + GaussianBlurPipeline::FragmentShader::KernelSamples result = {}; result.sample_count = ((parameters.sample_count - 1) / 2) + 1; int32_t middle = result.sample_count / 2; int32_t j = 0; FML_DCHECK(result.sample_count <= kGaussianBlurMaxKernelSize); + static_assert(sizeof(result.sample_data) == + sizeof(std::array)); for (int i = 0; i < result.sample_count; i++) { if (i == middle) { @@ -939,9 +942,9 @@ GaussianBlurPipeline::FragmentShader::BlurInfo LerpHackKernelSamples( result.sample_data[i].z = left.coefficient + right.coefficient; - auto uv = (left.uv_offset * left.coefficient + - right.uv_offset * right.coefficient) / - (left.coefficient + right.coefficient); + Point uv = (left.uv_offset * left.coefficient + + right.uv_offset * right.coefficient) / + (left.coefficient + right.coefficient); result.sample_data[i].x = uv.x; result.sample_data[i].y = uv.y; j += 2; diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h index 1d1bdb1ca04a6..b24c87f0205d6 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h @@ -9,12 +9,16 @@ #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/geometry/geometry.h" +#include "impeller/geometry/color.h" namespace impeller { // Comes from gaussian.frag. static constexpr int32_t kGaussianBlurMaxKernelSize = 50; +static_assert(sizeof(GaussianBlurPipeline::FragmentShader::KernelSamples) == + sizeof(Vector4) * kGaussianBlurMaxKernelSize + sizeof(Vector4)); + struct BlurParameters { Point blur_uv_offset; Scalar blur_sigma; @@ -42,7 +46,7 @@ KernelSamples GenerateBlurInfo(BlurParameters parameters); /// This will shrink the size of a kernel by roughly half by sampling between /// samples and relying on linear interpolation between the samples. -GaussianBlurPipeline::FragmentShader::BlurInfo LerpHackKernelSamples( +GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples( KernelSamples samples); /// Performs a bidirectional Gaussian blur. diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc index 68e87be958fd4..26449a2573d50 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc @@ -9,6 +9,7 @@ #include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/entity/entity_playground.h" +#include "impeller/geometry/color.h" #include "impeller/geometry/geometry_asserts.h" #include "impeller/renderer/testing/mocks.h" @@ -51,6 +52,14 @@ fml::StatusOr LowerBoundNewtonianMethod( return x; } +Scalar GetCoefficient(const Vector4& vec) { + return vec.z; +} + +Vector2 GetUVOffset(const Vector4& vec) { + return vec.xy(); +} + fml::StatusOr CalculateSigmaForBlurRadius( Scalar radius, const Matrix& effect_transform) { @@ -508,7 +517,7 @@ TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) { }, }; - GaussianBlurPipeline::FragmentShader::BlurInfo blur_info = + GaussianBlurPipeline::FragmentShader::KernelSamples blur_info = LerpHackKernelSamples(kernel_samples); EXPECT_EQ(blur_info.sample_count, 3); @@ -517,15 +526,15 @@ TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) { ////////////////////////////////////////////////////////////////////////////// // Check output kernel. - EXPECT_FLOAT_EQ(blur_info.sample_data[0].x, -1.3333333); - EXPECT_FLOAT_EQ(blur_info.sample_data[0].y, 0); - EXPECT_FLOAT_EQ(blur_info.sample_data[0].z, 0.3); - EXPECT_FLOAT_EQ(blur_info.sample_data[1].x, 0); - EXPECT_FLOAT_EQ(blur_info.sample_data[1].y, 0); - EXPECT_FLOAT_EQ(blur_info.sample_data[1].z, 0.4); - EXPECT_FLOAT_EQ(blur_info.sample_data[2].x, 1.3333333); - EXPECT_FLOAT_EQ(blur_info.sample_data[2].y, 0); - EXPECT_FLOAT_EQ(blur_info.sample_data[2].z, 0.3); + EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[0]), + Point(-1.3333333, 0)); + EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[0]), 0.3); + + EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[1]), Point(0, 0)); + EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[1]), 0.4); + + EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[2]), Point(1.333333, 0)); + EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[2]), 0.3); ////////////////////////////////////////////////////////////////////////////// // Check output of fast kernel versus original kernel. @@ -546,11 +555,11 @@ TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) { } }; Scalar fast_output = - /*1st*/ lerp(blur_info.sample_data[0].xy(), data[0], data[1]) * - blur_info.sample_data[0].z + - /*2nd*/ data[2] * blur_info.sample_data[1].z + - /*3rd*/ lerp(blur_info.sample_data[2].xy(), data[3], data[4]) * - blur_info.sample_data[2].z; + /*1st*/ lerp(GetUVOffset(blur_info.sample_data[0]), data[0], data[1]) * + GetCoefficient(blur_info.sample_data[0]) + + /*2nd*/ data[2] * GetCoefficient(blur_info.sample_data[1]) + + /*3rd*/ lerp(GetUVOffset(blur_info.sample_data[2]), data[3], data[4]) * + GetCoefficient(blur_info.sample_data[2]); EXPECT_NEAR(original_output, fast_output, 0.01); } @@ -565,7 +574,7 @@ TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) { .step_size = 1}; KernelSamples kernel_samples = GenerateBlurInfo(parameters); EXPECT_EQ(kernel_samples.sample_count, 33); - GaussianBlurPipeline::FragmentShader::BlurInfo fast_kernel_samples = + GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples = LerpHackKernelSamples(kernel_samples); EXPECT_EQ(fast_kernel_samples.sample_count, 17); float data[33]; @@ -602,8 +611,8 @@ TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) { Scalar fast_output = 0.0; for (int i = 0; i < fast_kernel_samples.sample_count; i++) { - fast_output += fast_kernel_samples.sample_data[i].z * - sampler(fast_kernel_samples.sample_data[i].xy()); + fast_output += GetCoefficient(fast_kernel_samples.sample_data[i]) * + sampler(GetUVOffset(fast_kernel_samples.sample_data[i])); } EXPECT_NEAR(output, fast_output, 0.1); @@ -618,7 +627,7 @@ TEST(GaussianBlurFilterContentsTest, ChopHugeBlurs) { .blur_radius = blur_radius, .step_size = 1}; KernelSamples kernel_samples = GenerateBlurInfo(parameters); - GaussianBlurPipeline::FragmentShader::BlurInfo frag_kernel_samples = + GaussianBlurPipeline::FragmentShader::KernelSamples frag_kernel_samples = LerpHackKernelSamples(kernel_samples); EXPECT_TRUE(frag_kernel_samples.sample_count <= kGaussianBlurMaxKernelSize); } diff --git a/impeller/entity/shaders/filters/gaussian.frag b/impeller/entity/shaders/filters/gaussian.frag index 5a9ee170f728d..a6d58f8e2b0a8 100644 --- a/impeller/entity/shaders/filters/gaussian.frag +++ b/impeller/entity/shaders/filters/gaussian.frag @@ -11,13 +11,13 @@ uniform f16sampler2D texture_sampler; layout(constant_id = 0) const float supports_decal = 1.0; -uniform BlurInfo { +uniform KernelSamples { float sample_count; // X, Y are uv offset and Z is Coefficient. W is padding. vec4 sample_data[50]; } -blur_info; +kernel_samples; f16vec4 Sample(f16sampler2D tex, vec2 coords) { if (supports_decal == 1.0) { @@ -33,11 +33,11 @@ out f16vec4 frag_color; void main() { f16vec4 total_color = f16vec4(0.0hf); - for (int i = 0; i < int(blur_info.sample_count); i++) { - float16_t coefficient = float16_t(blur_info.sample_data[i].z); - total_color += - coefficient * - Sample(texture_sampler, v_texture_coords + blur_info.sample_data[i].xy); + for (int i = 0; i < int(kernel_samples.sample_count); i++) { + float16_t coefficient = float16_t(kernel_samples.sample_data[i].z); + total_color += coefficient * + Sample(texture_sampler, + v_texture_coords + kernel_samples.sample_data[i].xy); } frag_color = total_color; diff --git a/impeller/renderer/backend/gles/buffer_bindings_gles.cc b/impeller/renderer/backend/gles/buffer_bindings_gles.cc index 7292049a140c9..71d186fb72b30 100644 --- a/impeller/renderer/backend/gles/buffer_bindings_gles.cc +++ b/impeller/renderer/backend/gles/buffer_bindings_gles.cc @@ -282,6 +282,7 @@ bool BufferBindingsGLES::BindUniformBuffer(const ProcTableGLES& gl, // When binding uniform arrays, the elements must be contiguous. Copy // the uniforms to a temp buffer to eliminate any padding needed by the // other backends if the array elements have padding. + std::vector array_element_buffer_; if (element_count > 1 && element_stride != member.size) { array_element_buffer_.resize(member.size * element_count); for (size_t element_i = 0; element_i < element_count; element_i++) { diff --git a/impeller/renderer/backend/gles/buffer_bindings_gles.h b/impeller/renderer/backend/gles/buffer_bindings_gles.h index a70b3aa0c20c8..708980d553b88 100644 --- a/impeller/renderer/backend/gles/buffer_bindings_gles.h +++ b/impeller/renderer/backend/gles/buffer_bindings_gles.h @@ -57,7 +57,6 @@ class BufferBindingsGLES { std::vector vertex_attrib_arrays_; std::unordered_map uniform_locations_; - std::vector array_element_buffer_; using BindingMap = std::unordered_map>; BindingMap binding_map_ = {};