blob: b6bdffd2b7d81b18e08c9db93d77fef0eec85d7b [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "impeller/entity/contents/solid_rrect_blur_contents.h"
#include <optional>
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/entity.h"
#include "impeller/geometry/color.h"
#include "impeller/geometry/constants.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/vertex_buffer_builder.h"
namespace impeller {
namespace {
// Generous padding to make sure blurs with large sigmas are fully visible. Used
// to expand the geometry around the rrect. Larger sigmas have more subtle
// gradients so they need larger padding to avoid hard cutoffs. Sigma is
// maximized to 3.5 since that should cover 99.95% of all samples. 3.0 should
// cover 99.7% but that was seen to be not enough for large sigmas.
Scalar PadForSigma(Scalar sigma) {
Scalar scalar = std::min((1.0f / 47.6f) * sigma + 2.5f, 3.5f);
return sigma * scalar;
}
} // namespace
SolidRRectBlurContents::SolidRRectBlurContents() = default;
SolidRRectBlurContents::~SolidRRectBlurContents() = default;
void SolidRRectBlurContents::SetRRect(std::optional<Rect> rect,
Size corner_radii) {
rect_ = rect;
corner_radii_ = corner_radii;
}
void SolidRRectBlurContents::SetSigma(Sigma sigma) {
sigma_ = sigma;
}
void SolidRRectBlurContents::SetColor(Color color) {
color_ = color.Premultiply();
}
Color SolidRRectBlurContents::GetColor() const {
return color_;
}
std::optional<Rect> SolidRRectBlurContents::GetCoverage(
const Entity& entity) const {
if (!rect_.has_value()) {
return std::nullopt;
}
Scalar radius = PadForSigma(sigma_.sigma);
return rect_->Expand(radius).TransformBounds(entity.GetTransform());
}
bool SolidRRectBlurContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
if (!rect_.has_value()) {
return true;
}
using VS = RRectBlurPipeline::VertexShader;
using FS = RRectBlurPipeline::FragmentShader;
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
// Clamp the max kernel width/height to 1000 to limit the extent
// of the blur and to kEhCloseEnough to prevent NaN calculations
// trying to evaluate a Guassian distribution with a sigma of 0.
auto blur_sigma = std::clamp(sigma_.sigma, kEhCloseEnough, 250.0f);
// Increase quality by making the radius a bit bigger than the typical
// sigma->radius conversion we use for slower blurs.
auto blur_radius = PadForSigma(blur_sigma);
auto positive_rect = rect_->GetPositive();
{
auto left = -blur_radius;
auto top = -blur_radius;
auto right = positive_rect.GetWidth() + blur_radius;
auto bottom = positive_rect.GetHeight() + blur_radius;
vtx_builder.AddVertices({
{Point(left, top)},
{Point(right, top)},
{Point(left, bottom)},
{Point(right, bottom)},
});
}
ContentContextOptions opts = OptionsFromPassAndEntity(pass, entity);
opts.primitive_type = PrimitiveType::kTriangleStrip;
Color color = color_;
if (entity.GetBlendMode() == BlendMode::kClear) {
opts.is_for_rrect_blur_clear = true;
color = Color::White();
}
VS::FrameInfo frame_info;
frame_info.mvp = Entity::GetShaderTransform(
entity.GetShaderClipDepth(), pass,
entity.GetTransform() *
Matrix::MakeTranslation(positive_rect.GetOrigin()));
FS::FragInfo frag_info;
frag_info.color = color;
frag_info.blur_sigma = blur_sigma;
frag_info.rect_size = Point(positive_rect.GetSize());
frag_info.corner_radii = {std::clamp(corner_radii_.width, kEhCloseEnough,
positive_rect.GetWidth() * 0.5f),
std::clamp(corner_radii_.width, kEhCloseEnough,
positive_rect.GetHeight() * 0.5f)};
pass.SetCommandLabel("RRect Shadow");
pass.SetPipeline(renderer.GetRRectBlurPipeline(opts));
pass.SetVertexBuffer(
vtx_builder.CreateVertexBuffer(renderer.GetTransientsBuffer()));
VS::BindFrameInfo(pass,
renderer.GetTransientsBuffer().EmplaceUniform(frame_info));
FS::BindFragInfo(pass,
renderer.GetTransientsBuffer().EmplaceUniform(frag_info));
if (!pass.Draw().ok()) {
return false;
}
return true;
}
bool SolidRRectBlurContents::ApplyColorFilter(
const ColorFilterProc& color_filter_proc) {
color_ = color_filter_proc(color_);
return true;
}
} // namespace impeller