| Flutter Impeller |

directional_gaussian_blur_filter_contents.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "impeller/entity/contents/filters/directional_gaussian_blur_filter_contents.h"
6
7 #include <cmath>
8 #include <utility>
9
10 #include "impeller/base/strings.h"
11 #include "impeller/core/formats.h"
12 #include "impeller/core/sampler_descriptor.h"
13 #include "impeller/entity/contents/content_context.h"
14 #include "impeller/entity/contents/filters/filter_contents.h"
15 #include "impeller/geometry/rect.h"
16 #include "impeller/geometry/scalar.h"
17 #include "impeller/renderer/command_buffer.h"
18 #include "impeller/renderer/render_pass.h"
19 #include "impeller/renderer/render_target.h"
20 #include "impeller/renderer/vertex_buffer_builder.h"
21
22 namespace impeller {
23
24 // This function was calculated by observing Skia's behavior. Its blur at 500
25 // seemed to be 0.15. Since we clamp at 500 I solved the quadratic equation
26 // that puts the minima there and a f(0)=1.
27 Sigma ScaleSigma(Sigma sigma) {
28 // Limit the kernel size to 1000x1000 pixels, like Skia does.
29 Scalar clamped = std::min(sigma.sigma, 500.0f);
30 Scalar scalar = 1.0 - 3.4e-3 * clamped + 3.4e-06 * clamped * clamped;
31 return Sigma(clamped * scalar);
32 }
33
34 DirectionalGaussianBlurFilterContents::DirectionalGaussianBlurFilterContents() =
35 default;
36
37 DirectionalGaussianBlurFilterContents::
38 ~DirectionalGaussianBlurFilterContents() = default;
39
40 void DirectionalGaussianBlurFilterContents::SetSigma(Sigma sigma) {
41 blur_sigma_ = sigma;
42 }
43
44 void DirectionalGaussianBlurFilterContents::SetSecondarySigma(Sigma sigma) {
45 secondary_blur_sigma_ = sigma;
46 }
47
48 void DirectionalGaussianBlurFilterContents::SetDirection(Vector2 direction) {
49 blur_direction_ = direction.Normalize();
50 if (blur_direction_.IsZero()) {
51 blur_direction_ = Vector2(0, 1);
52 }
53 }
54
55 void DirectionalGaussianBlurFilterContents::SetBlurStyle(BlurStyle blur_style) {
56 blur_style_ = blur_style;
57 }
58
59 void DirectionalGaussianBlurFilterContents::SetTileMode(
60 Entity::TileMode tile_mode) {
61 tile_mode_ = tile_mode;
62 }
63
64 void DirectionalGaussianBlurFilterContents::SetIsSecondPass(
65 bool is_second_pass) {
66 is_second_pass_ = is_second_pass;
67 }
68
69 std::optional<Entity> DirectionalGaussianBlurFilterContents::RenderFilter(
70 const FilterInput::Vector& inputs,
71 const ContentContext& renderer,
72 const Entity& entity,
73 const Matrix& effect_transform,
74 const Rect& coverage,
75 const std::optional<Rect>& coverage_hint) const {
76 using VS = GaussianBlurPipeline::VertexShader;
77 using FS = GaussianBlurPipeline::FragmentShader;
78
79 //----------------------------------------------------------------------------
80 /// Handle inputs.
81 ///
82
83 if (inputs.empty()) {
84 return std::nullopt;
85 }
86
87 auto radius = Radius{ScaleSigma(blur_sigma_)}.radius;
88
89 auto transform = entity.GetTransform() * effect_transform.Basis();
90 auto transformed_blur_radius =
91 transform.TransformDirection(blur_direction_ * radius);
92
93 auto transformed_blur_radius_length = transformed_blur_radius.GetLength();
94
95 // Input 0 snapshot.
96
97 std::optional<Rect> expanded_coverage_hint;
98 if (coverage_hint.has_value()) {
99 auto r =
100 Point(transformed_blur_radius_length, transformed_blur_radius_length)
101 .Abs();
102 expanded_coverage_hint =
103 is_second_pass_ ? coverage_hint : coverage_hint->Expand(r);
104 }
105 auto input_snapshot = inputs[0]->GetSnapshot("GaussianBlur", renderer, entity,
106 expanded_coverage_hint);
107 if (!input_snapshot.has_value()) {
108 return std::nullopt;
109 }
110
111 if (blur_sigma_.sigma < kEhCloseEnough) {
112 return Entity::FromSnapshot(input_snapshot.value(), entity.GetBlendMode(),
113 entity.GetClipDepth()); // No blur to render.
114 }
115
116 // If the radius length is < .5, the shader will take at most 1 sample,
117 // resulting in no blur.
118 if (transformed_blur_radius_length < .5) {
119 return Entity::FromSnapshot(input_snapshot.value(), entity.GetBlendMode(),
120 entity.GetClipDepth()); // No blur to render.
121 }
122
123 // A matrix that rotates the snapshot space such that the blur direction is
124 // +X.
125 auto texture_rotate = Matrix::MakeRotationZ(
126 transformed_blur_radius.Normalize().AngleTo({1, 0}));
127
128 // Converts local pass space to screen space. This is just the snapshot space
129 // rotated such that the blur direction is +X.
130 auto pass_transform = texture_rotate * input_snapshot->transform;
131
132 // The pass texture coverage, but rotated such that the blur is in the +X
133 // direction, and expanded to include the blur radius. This is used for UV
134 // projection and as a source for the pass size. Note that it doesn't matter
135 // which direction the space is rotated in when grabbing the pass size.
136 auto pass_texture_rect = Rect::MakeSize(input_snapshot->texture->GetSize())
137 .TransformBounds(pass_transform)
138 .Expand(transformed_blur_radius_length, 0);
139
140 // UV mapping.
141
142 auto pass_uv_project = [&texture_rotate,
143 &pass_texture_rect](Snapshot& input) {
144 auto uv_matrix = Matrix::MakeScale(1 / Vector2(input.texture->GetSize())) *
145 (texture_rotate * input.transform).Invert();
146 return pass_texture_rect.GetTransformedPoints(uv_matrix);
147 };
148
149 auto input_uvs = pass_uv_project(input_snapshot.value());
150
151 //----------------------------------------------------------------------------
152 /// Render to texture.
153 ///
154
155 ContentContext::SubpassCallback subpass_callback = [&](const ContentContext&
156 renderer,
157 RenderPass& pass) {
158 auto& host_buffer = pass.GetTransientsBuffer();
159
160 VertexBufferBuilder<VS::PerVertexData> vtx_builder;
161 vtx_builder.AddVertices({
162 {Point(0, 0), input_uvs[0]},
163 {Point(1, 0), input_uvs[1]},
164 {Point(0, 1), input_uvs[2]},
165 {Point(1, 1), input_uvs[3]},
166 });
167
168 VS::FrameInfo frame_info;
169 frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1));
170 frame_info.texture_sampler_y_coord_scale =
171 input_snapshot->texture->GetYCoordScale();
172
173 FS::BlurInfo frag_info;
174 auto r = Radius{transformed_blur_radius_length};
175 frag_info.blur_sigma = Sigma{r}.sigma;
176 frag_info.blur_radius = std::round(r.radius);
177 frag_info.step_size = 2.0;
178
179 // The blur direction is in input UV space.
180 frag_info.blur_uv_offset =
181 pass_transform.Invert().TransformDirection(Vector2(1, 0)).Normalize() /
182 Point(input_snapshot->GetCoverage().value().GetSize());
183
184 Command cmd;
185 DEBUG_COMMAND_INFO(cmd, SPrintF("Gaussian Blur Filter (Radius=%.2f)",
186 transformed_blur_radius_length));
187 cmd.BindVertices(vtx_builder.CreateVertexBuffer(host_buffer));
188
189 auto options = OptionsFromPass(pass);
190 options.primitive_type = PrimitiveType::kTriangleStrip;
191 options.blend_mode = BlendMode::kSource;
192 auto input_descriptor = input_snapshot->sampler_descriptor;
193 switch (tile_mode_) {
194 case Entity::TileMode::kDecal:
195 if (renderer.GetDeviceCapabilities()
196 .SupportsDecalSamplerAddressMode()) {
197 input_descriptor.width_address_mode = SamplerAddressMode::kDecal;
198 input_descriptor.height_address_mode = SamplerAddressMode::kDecal;
199 }
200 break;
201 case Entity::TileMode::kClamp:
202 input_descriptor.width_address_mode = SamplerAddressMode::kClampToEdge;
203 input_descriptor.height_address_mode = SamplerAddressMode::kClampToEdge;
204 break;
205 case Entity::TileMode::kMirror:
206 input_descriptor.width_address_mode = SamplerAddressMode::kMirror;
207 input_descriptor.height_address_mode = SamplerAddressMode::kMirror;
208 break;
209 case Entity::TileMode::kRepeat:
210 input_descriptor.width_address_mode = SamplerAddressMode::kRepeat;
211 input_descriptor.height_address_mode = SamplerAddressMode::kRepeat;
212 break;
213 }
214 input_descriptor.mag_filter = MinMagFilter::kLinear;
215 input_descriptor.min_filter = MinMagFilter::kLinear;
216
217 bool has_decal_specialization =
218 tile_mode_ == Entity::TileMode::kDecal &&
219 !renderer.GetDeviceCapabilities().SupportsDecalSamplerAddressMode();
220
221 if (has_decal_specialization) {
222 cmd.pipeline = renderer.GetGaussianBlurDecalPipeline(options);
223 } else {
224 cmd.pipeline = renderer.GetGaussianBlurPipeline(options);
225 }
226
227 FS::BindTextureSampler(
228 cmd, input_snapshot->texture,
229 renderer.GetContext()->GetSamplerLibrary()->GetSampler(
230 input_descriptor));
231 VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info));
232 FS::BindBlurInfo(cmd, host_buffer.EmplaceUniform(frag_info));
233
234 return pass.AddCommand(std::move(cmd));
235 };
236
237 Vector2 scale;
238 auto scale_curve = [](Scalar radius) {
239 constexpr Scalar decay = 4.0; // Larger is more gradual.
240 constexpr Scalar limit = 0.95; // The maximum percentage of the scaledown.
241 const Scalar curve =
242 std::min(1.0, decay / (std::max(1.0f, radius) + decay - 1.0));
243 return (curve - 1) * limit + 1;
244 };
245 {
246 scale.x = scale_curve(transformed_blur_radius_length);
247
248 Scalar y_radius = std::abs(pass_transform.GetDirectionScale(Vector2(
249 0, !is_second_pass_ ? 1 : Radius{secondary_blur_sigma_}.radius)));
250 scale.y = scale_curve(y_radius);
251 }
252
253 Vector2 scaled_size = pass_texture_rect.GetSize() * scale;
254 ISize floored_size = ISize(scaled_size.x, scaled_size.y);
255
256 fml::StatusOr<RenderTarget> render_target = renderer.MakeSubpass(
257 "Directional Gaussian Blur Filter", floored_size, subpass_callback);
258
259 if (!render_target.ok()) {
260 return std::nullopt;
261 }
262
263 SamplerDescriptor sampler_desc;
264 sampler_desc.min_filter = MinMagFilter::kLinear;
265 sampler_desc.mag_filter = MinMagFilter::kLinear;
266 sampler_desc.width_address_mode = SamplerAddressMode::kClampToEdge;
267 sampler_desc.width_address_mode = SamplerAddressMode::kClampToEdge;
268
269 return Entity::FromSnapshot(
270 Snapshot{
271 .texture = render_target.value().GetRenderTargetTexture(),
272 .transform =
273 texture_rotate.Invert() *
274 Matrix::MakeTranslation(pass_texture_rect.GetOrigin()) *
275 Matrix::MakeScale((1 / scale) * (scaled_size / floored_size)),
276 .sampler_descriptor = sampler_desc,
277 .opacity = input_snapshot->opacity},
278 entity.GetBlendMode(), entity.GetClipDepth());
279 }
280
281 std::optional<Rect>
282 DirectionalGaussianBlurFilterContents::GetFilterSourceCoverage(
283 const Matrix& effect_transform,
284 const Rect& output_limit) const {
285 auto transform = effect_transform.Basis();
286 auto transformed_blur_vector =
287 transform.TransformDirection(blur_direction_ * Radius{blur_sigma_}.radius)
288 .Abs();
289 return output_limit.Expand(transformed_blur_vector);
290 }
291
292 std::optional<Rect> DirectionalGaussianBlurFilterContents::GetFilterCoverage(
293 const FilterInput::Vector& inputs,
294 const Entity& entity,
295 const Matrix& effect_transform) const {
296 if (inputs.empty()) {
297 return std::nullopt;
298 }
299
300 auto coverage = inputs[0]->GetCoverage(entity);
301 if (!coverage.has_value()) {
302 return std::nullopt;
303 }
304
305 auto transform = inputs[0]->GetTransform(entity) * effect_transform.Basis();
306 auto transformed_blur_vector =
307 transform
308 .TransformDirection(blur_direction_ *
309 Radius{ScaleSigma(blur_sigma_)}.radius)
310 .Abs();
311 return coverage->Expand(transformed_blur_vector);
312 }
313
314 } // namespace impeller
impeller::Sigma::sigma
Scalar sigma
Definition: sigma.h:33
impeller::Entity::TileMode::kClamp
@ kClamp
impeller::OptionsFromPass
ContentContextOptions OptionsFromPass(const RenderPass &pass)
Definition: contents.cc:20
DEBUG_COMMAND_INFO
#define DEBUG_COMMAND_INFO(obj, arg)
Definition: command.h:28
impeller::DirectionalGaussianBlurFilterContents::SetIsSecondPass
void SetIsSecondPass(bool is_second_pass)
Definition: directional_gaussian_blur_filter_contents.cc:64
impeller::DirectionalGaussianBlurFilterContents::SetBlurStyle
void SetBlurStyle(BlurStyle blur_style)
Definition: directional_gaussian_blur_filter_contents.cc:55
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::Entity::GetTransform
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:49
impeller::DirectionalGaussianBlurFilterContents::SetSecondarySigma
void SetSecondarySigma(Sigma sigma)
Definition: directional_gaussian_blur_filter_contents.cc:44
impeller::FilterContents::BlurStyle
BlurStyle
Definition: filter_contents.h:23
impeller::kEhCloseEnough
constexpr float kEhCloseEnough
Definition: constants.h:56
impeller::Entity::TileMode::kDecal
@ kDecal
impeller::BlendMode::kSource
@ kSource
impeller::TRect::TransformBounds
constexpr TRect TransformBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle.
Definition: rect.h:264
impeller::ScaleSigma
Sigma ScaleSigma(Sigma sigma)
Definition: directional_gaussian_blur_filter_contents.cc:27
formats.h
impeller::Vector2
Point Vector2
Definition: point.h:312
impeller::RenderPipelineT::FragmentShader
FragmentShader_ FragmentShader
Definition: pipeline.h:93
impeller::SamplerAddressMode::kClampToEdge
@ kClampToEdge
impeller::Entity::TileMode::kRepeat
@ kRepeat
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
impeller::Entity::TileMode::kMirror
@ kMirror
impeller::DirectionalGaussianBlurFilterContents::GetFilterSourceCoverage
std::optional< Rect > GetFilterSourceCoverage(const Matrix &effect_transform, const Rect &output_limit) const override
Internal utility method for |GetSourceCoverage| that computes the inverse effect of this transform on...
Definition: directional_gaussian_blur_filter_contents.cc:282
impeller::TPoint::IsZero
constexpr bool IsZero() const
Definition: point.h:229
impeller::Entity
Definition: entity.h:21
impeller::Matrix::Basis
constexpr Matrix Basis() const
The Matrix without its w components (without translation).
Definition: matrix.h:224
impeller::PrimitiveType::kTriangleStrip
@ kTriangleStrip
impeller::Point
TPoint< Scalar > Point
Definition: point.h:308
filter_contents.h
render_pass.h
impeller::Radius
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition: sigma.h:48
impeller::SPrintF
std::string SPrintF(const char *format,...)
Definition: strings.cc:12
impeller::MinMagFilter::kLinear
@ kLinear
impeller::Sigma
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
strings.h
impeller::Entity::TileMode
TileMode
Definition: entity.h:40
impeller::Entity::GetBlendMode
BlendMode GetBlendMode() const
Definition: entity.cc:105
impeller::DirectionalGaussianBlurFilterContents::SetTileMode
void SetTileMode(Entity::TileMode tile_mode)
Definition: directional_gaussian_blur_filter_contents.cc:59
scalar.h
impeller::ContentContext::SubpassCallback
std::function< bool(const ContentContext &, RenderPass &)> SubpassCallback
Definition: content_context.h:708
impeller::Matrix::TransformDirection
constexpr Vector4 TransformDirection(const Vector4 &v) const
Definition: matrix.h:433
impeller::ISize
TSize< int64_t > ISize
Definition: size.h:138
impeller::Entity::GetClipDepth
uint32_t GetClipDepth() const
Definition: entity.cc:93
sampler_descriptor.h
command_buffer.h
impeller::TPoint::Abs
constexpr TPoint Abs() const
Definition: point.h:211
impeller::DirectionalGaussianBlurFilterContents::DirectionalGaussianBlurFilterContents
DirectionalGaussianBlurFilterContents()
content_context.h
impeller::Matrix::MakeRotationZ
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:209
impeller::DirectionalGaussianBlurFilterContents::SetSigma
void SetSigma(Sigma sigma)
Set sigma (stddev) used for 'direction_'.
Definition: directional_gaussian_blur_filter_contents.cc:40
impeller::SamplerAddressMode::kMirror
@ kMirror
impeller::TRect< Scalar >::MakeSize
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:44
directional_gaussian_blur_filter_contents.h
impeller::DirectionalGaussianBlurFilterContents::~DirectionalGaussianBlurFilterContents
~DirectionalGaussianBlurFilterContents() override
impeller::DirectionalGaussianBlurFilterContents::GetFilterCoverage
std::optional< Rect > GetFilterCoverage(const FilterInput::Vector &inputs, const Entity &entity, const Matrix &effect_transform) const override
Internal utility method for |GetLocalCoverage| that computes the output coverage of this filter acros...
Definition: directional_gaussian_blur_filter_contents.cc:292
rect.h
impeller::DirectionalGaussianBlurFilterContents::SetDirection
void SetDirection(Vector2 direction)
Definition: directional_gaussian_blur_filter_contents.cc:48
impeller::TPoint< Scalar >
impeller::Matrix::MakeOrthographic
static constexpr Matrix MakeOrthographic(TSize< T > size)
Definition: matrix.h:459
render_target.h
impeller::FilterInput::Vector
std::vector< FilterInput::Ref > Vector
Definition: filter_input.h:33
impeller::Entity::FromSnapshot
static std::optional< Entity > FromSnapshot(const std::optional< Snapshot > &snapshot, BlendMode blend_mode=BlendMode::kSourceOver, uint32_t clip_depth=0)
Create an entity that can be used to render a given snapshot.
Definition: entity.cc:21
impeller::TPoint::Normalize
constexpr TPoint Normalize() const
Definition: point.h:203
impeller::RenderPipelineT::VertexShader
VertexShader_ VertexShader
Definition: pipeline.h:92
impeller
Definition: aiks_context.cc:10
impeller::Matrix::MakeScale
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
impeller::ContentContext
Definition: content_context.h:332
impeller::TRect
Definition: rect.h:22
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::TRect::Expand
constexpr TRect< T > Expand(T left, T top, T right, T bottom) const
Returns a rectangle with expanded edges. Negative expansion results in shrinking.
Definition: rect.h:373
vertex_buffer_builder.h
impeller::SamplerAddressMode::kRepeat
@ kRepeat
impeller::SamplerAddressMode::kDecal
@ kDecal
decal sampling mode is only supported on devices that pass the Capabilities.SupportsDecalSamplerAddre...