| Flutter Impeller |

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/gaussian_blur_filter_contents.h"
6
7 #include <cmath>
8
9 #include "flutter/fml/make_copyable.h"
10 #include "impeller/entity/contents/clip_contents.h"
11 #include "impeller/entity/contents/color_source_contents.h"
12 #include "impeller/entity/contents/content_context.h"
13 #include "impeller/entity/texture_fill.frag.h"
14 #include "impeller/entity/texture_fill.vert.h"
15 #include "impeller/renderer/command.h"
16 #include "impeller/renderer/render_pass.h"
17 #include "impeller/renderer/texture_mipmap.h"
18 #include "impeller/renderer/vertex_buffer_builder.h"
19
20 namespace impeller {
21
22 using GaussianBlurVertexShader = KernelPipeline::VertexShader;
23 using GaussianBlurFragmentShader = KernelPipeline::FragmentShader;
24
25 const int32_t GaussianBlurFilterContents::kBlurFilterRequiredMipCount = 4;
26
27 namespace {
28
29 // 48 comes from kernel.glsl.
30 const int32_t kMaxKernelSize = 48;
31
32 SamplerDescriptor MakeSamplerDescriptor(MinMagFilter filter,
33 SamplerAddressMode address_mode) {
34 SamplerDescriptor sampler_desc;
35 sampler_desc.min_filter = filter;
36 sampler_desc.mag_filter = filter;
37 sampler_desc.width_address_mode = address_mode;
38 sampler_desc.height_address_mode = address_mode;
39 return sampler_desc;
40 }
41
42 template <typename T>
43 void BindVertices(RenderPass& pass,
44 HostBuffer& host_buffer,
45 std::initializer_list<typename T::PerVertexData>&& vertices) {
46 VertexBufferBuilder<typename T::PerVertexData> vtx_builder;
47 vtx_builder.AddVertices(vertices);
48 pass.SetVertexBuffer(vtx_builder.CreateVertexBuffer(host_buffer));
49 }
50
51 void SetTileMode(SamplerDescriptor* descriptor,
52 const ContentContext& renderer,
53 Entity::TileMode tile_mode) {
54 switch (tile_mode) {
55 case Entity::TileMode::kDecal:
56 if (renderer.GetDeviceCapabilities().SupportsDecalSamplerAddressMode()) {
57 descriptor->width_address_mode = SamplerAddressMode::kDecal;
58 descriptor->height_address_mode = SamplerAddressMode::kDecal;
59 }
60 break;
61 case Entity::TileMode::kClamp:
62 descriptor->width_address_mode = SamplerAddressMode::kClampToEdge;
63 descriptor->height_address_mode = SamplerAddressMode::kClampToEdge;
64 break;
65 case Entity::TileMode::kMirror:
66 descriptor->width_address_mode = SamplerAddressMode::kMirror;
67 descriptor->height_address_mode = SamplerAddressMode::kMirror;
68 break;
69 case Entity::TileMode::kRepeat:
70 descriptor->width_address_mode = SamplerAddressMode::kRepeat;
71 descriptor->height_address_mode = SamplerAddressMode::kRepeat;
72 break;
73 }
74 }
75
76 /// Makes a subpass that will render the scaled down input and add the
77 /// transparent gutter required for the blur halo.
78 fml::StatusOr<RenderTarget> MakeDownsampleSubpass(
79 const ContentContext& renderer,
80 std::shared_ptr<Texture> input_texture,
81 const SamplerDescriptor& sampler_descriptor,
82 const Quad& uvs,
83 const ISize& subpass_size,
84 Entity::TileMode tile_mode) {
85 ContentContext::SubpassCallback subpass_callback =
86 [&](const ContentContext& renderer, RenderPass& pass) {
87 HostBuffer& host_buffer = renderer.GetTransientsBuffer();
88
89 pass.SetCommandLabel("Gaussian blur downsample");
90 auto pipeline_options = OptionsFromPass(pass);
91 pipeline_options.primitive_type = PrimitiveType::kTriangleStrip;
92 pass.SetPipeline(renderer.GetTexturePipeline(pipeline_options));
93
94 TextureFillVertexShader::FrameInfo frame_info;
95 frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1));
96 frame_info.texture_sampler_y_coord_scale = 1.0;
97 frame_info.alpha = 1.0;
98
99 BindVertices<TextureFillVertexShader>(pass, host_buffer,
100 {
101 {Point(0, 0), uvs[0]},
102 {Point(1, 0), uvs[1]},
103 {Point(0, 1), uvs[2]},
104 {Point(1, 1), uvs[3]},
105 });
106
107 SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
108 SetTileMode(&linear_sampler_descriptor, renderer, tile_mode);
109 linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
110 linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
111 TextureFillVertexShader::BindFrameInfo(
112 pass, host_buffer.EmplaceUniform(frame_info));
113 TextureFillFragmentShader::BindTextureSampler(
114 pass, input_texture,
115 renderer.GetContext()->GetSamplerLibrary()->GetSampler(
116 linear_sampler_descriptor));
117
118 return pass.Draw().ok();
119 };
120 fml::StatusOr<RenderTarget> render_target = renderer.MakeSubpass(
121 "Gaussian Blur Filter", subpass_size, subpass_callback);
122 return render_target;
123 }
124
125 fml::StatusOr<RenderTarget> MakeBlurSubpass(
126 const ContentContext& renderer,
127 const RenderTarget& input_pass,
128 const SamplerDescriptor& sampler_descriptor,
129 Entity::TileMode tile_mode,
130 const BlurParameters& blur_info,
131 std::optional<RenderTarget> destination_target,
132 const Quad& blur_uvs) {
133 if (blur_info.blur_sigma < kEhCloseEnough) {
134 return input_pass;
135 }
136
137 std::shared_ptr<Texture> input_texture = input_pass.GetRenderTargetTexture();
138
139 // TODO(gaaclarke): This blurs the whole image, but because we know the clip
140 // region we could focus on just blurring that.
141 ISize subpass_size = input_texture->GetSize();
142 ContentContext::SubpassCallback subpass_callback =
143 [&](const ContentContext& renderer, RenderPass& pass) {
144 GaussianBlurVertexShader::FrameInfo frame_info{
145 .mvp = Matrix::MakeOrthographic(ISize(1, 1)),
146 .texture_sampler_y_coord_scale = 1.0};
147
148 HostBuffer& host_buffer = renderer.GetTransientsBuffer();
149
150 ContentContextOptions options = OptionsFromPass(pass);
151 options.primitive_type = PrimitiveType::kTriangleStrip;
152
153 if (tile_mode == Entity::TileMode::kDecal &&
154 !renderer.GetDeviceCapabilities()
155 .SupportsDecalSamplerAddressMode()) {
156 pass.SetPipeline(renderer.GetKernelDecalPipeline(options));
157 } else {
158 pass.SetPipeline(renderer.GetKernelPipeline(options));
159 }
160
161 BindVertices<GaussianBlurVertexShader>(pass, host_buffer,
162 {
163 {blur_uvs[0], blur_uvs[0]},
164 {blur_uvs[1], blur_uvs[1]},
165 {blur_uvs[2], blur_uvs[2]},
166 {blur_uvs[3], blur_uvs[3]},
167 });
168
169 SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
170 linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
171 linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
172 GaussianBlurFragmentShader::BindTextureSampler(
173 pass, input_texture,
174 renderer.GetContext()->GetSamplerLibrary()->GetSampler(
175 linear_sampler_descriptor));
176 GaussianBlurVertexShader::BindFrameInfo(
177 pass, host_buffer.EmplaceUniform(frame_info));
178 KernelPipeline::FragmentShader::KernelSamples kernel_samples =
179 LerpHackKernelSamples(GenerateBlurInfo(blur_info));
180 FML_CHECK(kernel_samples.sample_count < kMaxKernelSize);
181 GaussianBlurFragmentShader::BindKernelSamples(
182 pass, host_buffer.EmplaceUniform(kernel_samples));
183 return pass.Draw().ok();
184 };
185 if (destination_target.has_value()) {
186 return renderer.MakeSubpass("Gaussian Blur Filter",
187 destination_target.value(), subpass_callback);
188 } else {
189 return renderer.MakeSubpass("Gaussian Blur Filter", subpass_size,
190 subpass_callback);
191 }
192 }
193
194 /// Returns `rect` relative to `reference`, where Rect::MakeXYWH(0,0,1,1) will
195 /// be returned when `rect` == `reference`.
196 Rect MakeReferenceUVs(const Rect& reference, const Rect& rect) {
197 Rect result = Rect::MakeOriginSize(rect.GetOrigin() - reference.GetOrigin(),
198 rect.GetSize());
199 return result.Scale(1.0f / Vector2(reference.GetSize()));
200 }
201
202 int ScaleBlurRadius(Scalar radius, Scalar scalar) {
203 return static_cast<int>(std::round(radius * scalar));
204 }
205
206 Entity ApplyClippedBlurStyle(Entity::ClipOperation clip_operation,
207 const Entity& entity,
208 const std::shared_ptr<FilterInput>& input,
209 const Snapshot& input_snapshot,
210 Entity blur_entity,
211 const std::shared_ptr<Geometry>& geometry) {
212 auto clip_contents = std::make_shared<ClipContents>();
213 clip_contents->SetClipOperation(clip_operation);
214 clip_contents->SetGeometry(geometry);
215 Entity clipper;
216 clipper.SetContents(clip_contents);
217 auto restore = std::make_unique<ClipRestoreContents>();
218 Matrix entity_transform = entity.GetTransform();
219 Matrix blur_transform = blur_entity.GetTransform();
220 auto renderer = fml::MakeCopyable(
221 [blur_entity = blur_entity.Clone(), clipper = std::move(clipper),
222 restore = std::move(restore), entity_transform,
223 blur_transform](const ContentContext& renderer, const Entity& entity,
224 RenderPass& pass) mutable {
225 bool result = true;
226 clipper.SetNewClipDepth(entity.GetNewClipDepth());
227 clipper.SetTransform(entity.GetTransform() * entity_transform);
228 result = clipper.Render(renderer, pass) && result;
229 blur_entity.SetNewClipDepth(entity.GetNewClipDepth());
230 blur_entity.SetTransform(entity.GetTransform() * blur_transform);
231 result = blur_entity.Render(renderer, pass) && result;
232 if constexpr (!ContentContext::kEnableStencilThenCover) {
233 result = restore->Render(renderer, entity, pass) && result;
234 }
235 return result;
236 });
237 auto coverage =
238 fml::MakeCopyable([blur_entity = std::move(blur_entity),
239 blur_transform](const Entity& entity) mutable {
240 blur_entity.SetTransform(entity.GetTransform() * blur_transform);
241 return blur_entity.GetCoverage();
242 });
243 Entity result;
244 result.SetContents(Contents::MakeAnonymous(renderer, coverage));
245 return result;
246 }
247
248 Entity ApplyBlurStyle(FilterContents::BlurStyle blur_style,
249 const Entity& entity,
250 const std::shared_ptr<FilterInput>& input,
251 const Snapshot& input_snapshot,
252 Entity blur_entity,
253 const std::shared_ptr<Geometry>& geometry) {
254 switch (blur_style) {
255 case FilterContents::BlurStyle::kNormal:
256 return blur_entity;
257 case FilterContents::BlurStyle::kInner:
258 return ApplyClippedBlurStyle(Entity::ClipOperation::kIntersect, entity,
259 input, input_snapshot,
260 std::move(blur_entity), geometry);
261 break;
262 case FilterContents::BlurStyle::kOuter:
263 return ApplyClippedBlurStyle(Entity::ClipOperation::kDifference, entity,
264 input, input_snapshot,
265 std::move(blur_entity), geometry);
266 case FilterContents::BlurStyle::kSolid: {
267 Entity snapshot_entity = Entity::FromSnapshot(
268 input_snapshot, entity.GetBlendMode(), entity.GetClipDepth());
269 Entity result;
270 Matrix blurred_transform = blur_entity.GetTransform();
271 Matrix snapshot_transform = snapshot_entity.GetTransform();
272 result.SetContents(Contents::MakeAnonymous(
273 fml::MakeCopyable([blur_entity = blur_entity.Clone(),
274 blurred_transform, snapshot_transform,
275 snapshot_entity = std::move(snapshot_entity)](
276 const ContentContext& renderer,
277 const Entity& entity,
278 RenderPass& pass) mutable {
279 bool result = true;
280 blur_entity.SetNewClipDepth(entity.GetNewClipDepth());
281 blur_entity.SetTransform(entity.GetTransform() * blurred_transform);
282 result = result && blur_entity.Render(renderer, pass);
283 snapshot_entity.SetTransform(entity.GetTransform() *
284 snapshot_transform);
285 snapshot_entity.SetNewClipDepth(entity.GetNewClipDepth());
286 result = result && snapshot_entity.Render(renderer, pass);
287 return result;
288 }),
289 fml::MakeCopyable([blur_entity = blur_entity.Clone(),
290 blurred_transform](const Entity& entity) mutable {
291 blur_entity.SetTransform(entity.GetTransform() * blurred_transform);
292 return blur_entity.GetCoverage();
293 })));
294 return result;
295 }
296 }
297 }
298 } // namespace
299
300 std::string_view GaussianBlurFilterContents::kNoMipsError =
301 "Applying gaussian blur without mipmap.";
302
303 GaussianBlurFilterContents::GaussianBlurFilterContents(
304 Scalar sigma_x,
305 Scalar sigma_y,
306 Entity::TileMode tile_mode,
307 BlurStyle mask_blur_style,
308 const std::shared_ptr<Geometry>& mask_geometry)
309 : sigma_x_(sigma_x),
310 sigma_y_(sigma_y),
311 tile_mode_(tile_mode),
312 mask_blur_style_(mask_blur_style),
313 mask_geometry_(mask_geometry) {
314 // This is supposed to be enforced at a higher level.
315 FML_DCHECK(mask_blur_style == BlurStyle::kNormal || mask_geometry);
316 }
317
318 // This value was extracted from Skia, see:
319 // * https://github.com/google/skia/blob/d29cc3fe182f6e8a8539004a6a4ee8251677a6fd/src/gpu/ganesh/GrBlurUtils.cpp#L2561-L2576
320 // * https://github.com/google/skia/blob/d29cc3fe182f6e8a8539004a6a4ee8251677a6fd/src/gpu/BlurUtils.h#L57
321 Scalar GaussianBlurFilterContents::CalculateScale(Scalar sigma) {
322 if (sigma <= 4) {
323 return 1.0;
324 }
325 Scalar raw_result = 4.0 / sigma;
326 // Round to the nearest 1/(2^n) to get the best quality down scaling.
327 Scalar exponent = round(log2f(raw_result));
328 // Don't scale down below 1/16th to preserve signal.
329 exponent = std::max(-4.0f, exponent);
330 Scalar rounded = powf(2.0f, exponent);
331 Scalar result = rounded;
332 // Extend the range of the 1/8th downsample based on the effective kernel size
333 // for the blur.
334 if (rounded < 0.125f) {
335 Scalar rounded_plus = powf(2.0f, exponent + 1);
336 Scalar blur_radius = CalculateBlurRadius(sigma);
337 int kernel_size_plus = (ScaleBlurRadius(blur_radius, rounded_plus) * 2) + 1;
338 // This constant was picked by looking at the results to make sure no
339 // shimmering was introduced at the highest sigma values that downscale to
340 // 1/16th.
341 static constexpr int32_t kEighthDownsampleKernalWidthMax = 41;
342 result = kernel_size_plus <= kEighthDownsampleKernalWidthMax ? rounded_plus
343 : rounded;
344 }
345 return result;
346 };
347
348 std::optional<Rect> GaussianBlurFilterContents::GetFilterSourceCoverage(
349 const Matrix& effect_transform,
350 const Rect& output_limit) const {
351 Vector2 scaled_sigma = {ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)};
352 Vector2 blur_radius = {CalculateBlurRadius(scaled_sigma.x),
353 CalculateBlurRadius(scaled_sigma.y)};
354 Vector3 blur_radii =
355 effect_transform.Basis() * Vector3{blur_radius.x, blur_radius.y, 0.0};
356 return output_limit.Expand(Point(blur_radii.x, blur_radii.y));
357 }
358
359 std::optional<Rect> GaussianBlurFilterContents::GetFilterCoverage(
360 const FilterInput::Vector& inputs,
361 const Entity& entity,
362 const Matrix& effect_transform) const {
363 if (inputs.empty()) {
364 return {};
365 }
366
367 std::optional<Rect> input_coverage = inputs[0]->GetCoverage(entity);
368 if (!input_coverage.has_value()) {
369 return {};
370 }
371
372 Vector2 scaled_sigma = (effect_transform.Basis() *
373 Vector2(ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)))
374 .Abs();
375 Vector2 blur_radius = Vector2(CalculateBlurRadius(scaled_sigma.x),
376 CalculateBlurRadius(scaled_sigma.y));
377 Vector2 padding(ceil(blur_radius.x), ceil(blur_radius.y));
378 Vector2 local_padding = (entity.GetTransform().Basis() * padding).Abs();
379 return input_coverage.value().Expand(Point(local_padding.x, local_padding.y));
380 }
381
382 std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
383 const FilterInput::Vector& inputs,
384 const ContentContext& renderer,
385 const Entity& entity,
386 const Matrix& effect_transform,
387 const Rect& coverage,
388 const std::optional<Rect>& coverage_hint) const {
389 if (inputs.empty()) {
390 return std::nullopt;
391 }
392
393 Vector2 scaled_sigma = (effect_transform.Basis() *
394 Vector2(ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)))
395 .Abs();
396 Vector2 blur_radius = Vector2(CalculateBlurRadius(scaled_sigma.x),
397 CalculateBlurRadius(scaled_sigma.y));
398 Vector2 padding(ceil(blur_radius.x), ceil(blur_radius.y));
399 Vector2 local_padding = (entity.GetTransform().Basis() * padding).Abs();
400
401 // Apply as much of the desired padding as possible from the source. This may
402 // be ignored so must be accounted for in the downsample pass by adding a
403 // transparent gutter.
404 std::optional<Rect> expanded_coverage_hint;
405 if (coverage_hint.has_value()) {
406 expanded_coverage_hint = coverage_hint->Expand(local_padding);
407 }
408
409 int32_t mip_count = kBlurFilterRequiredMipCount;
410 if (renderer.GetContext()->GetBackendType() ==
411 Context::BackendType::kOpenGLES) {
412 // TODO(https://github.com/flutter/flutter/issues/141732): Implement mip map
413 // generation on opengles.
414 mip_count = 1;
415 }
416
417 std::optional<Snapshot> input_snapshot =
418 inputs[0]->GetSnapshot("GaussianBlur", renderer, entity,
419 /*coverage_limit=*/expanded_coverage_hint,
420 /*mip_count=*/mip_count);
421 if (!input_snapshot.has_value()) {
422 return std::nullopt;
423 }
424
425 if (scaled_sigma.x < kEhCloseEnough && scaled_sigma.y < kEhCloseEnough) {
426 return Entity::FromSnapshot(input_snapshot.value(), entity.GetBlendMode(),
427 entity.GetClipDepth()); // No blur to render.
428 }
429
430 // In order to avoid shimmering in downsampling step, we should have mips.
431 if (input_snapshot->texture->GetMipCount() <= 1) {
432 FML_DLOG(ERROR) << kNoMipsError;
433 }
434 FML_DCHECK(!input_snapshot->texture->NeedsMipmapGeneration());
435
436 Scalar desired_scalar =
437 std::min(CalculateScale(scaled_sigma.x), CalculateScale(scaled_sigma.y));
438 // TODO(jonahwilliams): If desired_scalar is 1.0 and we fully acquired the
439 // gutter from the expanded_coverage_hint, we can skip the downsample pass.
440 // pass.
441 Vector2 downsample_scalar(desired_scalar, desired_scalar);
442 Rect source_rect = Rect::MakeSize(input_snapshot->texture->GetSize());
443 Rect source_rect_padded = source_rect.Expand(padding);
444 Matrix padding_snapshot_adjustment = Matrix::MakeTranslation(-padding);
445 // TODO(gaaclarke): The padding could be removed if we know it's not needed or
446 // resized to account for the expanded_clip_coverage. There doesn't appear
447 // to be the math to make those calculations though. The following
448 // optimization works, but causes a shimmer as a result of
449 // https://github.com/flutter/flutter/issues/140193 so it isn't applied.
450 //
451 // !input_snapshot->GetCoverage()->Expand(-local_padding)
452 // .Contains(coverage_hint.value()))
453 Vector2 downsampled_size = source_rect_padded.GetSize() * downsample_scalar;
454 ISize subpass_size =
455 ISize(round(downsampled_size.x), round(downsampled_size.y));
456 Vector2 effective_scalar =
457 Vector2(subpass_size) / source_rect_padded.GetSize();
458
459 Quad uvs = CalculateUVs(inputs[0], entity, source_rect_padded,
460 input_snapshot->texture->GetSize());
461
462 fml::StatusOr<RenderTarget> pass1_out = MakeDownsampleSubpass(
463 renderer, input_snapshot->texture, input_snapshot->sampler_descriptor,
464 uvs, subpass_size, tile_mode_);
465
466 if (!pass1_out.ok()) {
467 return std::nullopt;
468 }
469
470 Vector2 pass1_pixel_size =
471 1.0 / Vector2(pass1_out.value().GetRenderTargetTexture()->GetSize());
472
473 std::optional<Rect> input_snapshot_coverage = input_snapshot->GetCoverage();
474 Quad blur_uvs = {Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)};
475 if (expanded_coverage_hint.has_value() &&
476 input_snapshot_coverage.has_value() &&
477 // TODO(https://github.com/flutter/flutter/issues/140890): Remove this
478 // condition. There is some flaw in coverage stopping us from using this
479 // today. I attempted to use source coordinates to calculate the uvs,
480 // but that didn't work either.
481 input_snapshot.has_value() &&
482 input_snapshot.value().transform.IsTranslationScaleOnly()) {
483 // Only process the uvs where the blur is happening, not the whole texture.
484 std::optional<Rect> uvs = MakeReferenceUVs(input_snapshot_coverage.value(),
485 expanded_coverage_hint.value())
486 .Intersection(Rect::MakeSize(Size(1, 1)));
487 FML_DCHECK(uvs.has_value());
488 if (uvs.has_value()) {
489 blur_uvs[0] = uvs->GetLeftTop();
490 blur_uvs[1] = uvs->GetRightTop();
491 blur_uvs[2] = uvs->GetLeftBottom();
492 blur_uvs[3] = uvs->GetRightBottom();
493 }
494 }
495
496 fml::StatusOr<RenderTarget> pass2_out = MakeBlurSubpass(
497 renderer, /*input_pass=*/pass1_out.value(),
498 input_snapshot->sampler_descriptor, tile_mode_,
499 BlurParameters{
500 .blur_uv_offset = Point(0.0, pass1_pixel_size.y),
501 .blur_sigma = scaled_sigma.y * effective_scalar.y,
502 .blur_radius = ScaleBlurRadius(blur_radius.y, effective_scalar.y),
503 .step_size = 1,
504 },
505 /*destination_target=*/std::nullopt, blur_uvs);
506
507 if (!pass2_out.ok()) {
508 return std::nullopt;
509 }
510
511 // Only ping pong if the first pass actually created a render target.
512 auto pass3_destination = pass2_out.value().GetRenderTargetTexture() !=
513 pass1_out.value().GetRenderTargetTexture()
514 ? std::optional<RenderTarget>(pass1_out.value())
515 : std::optional<RenderTarget>(std::nullopt);
516
517 fml::StatusOr<RenderTarget> pass3_out = MakeBlurSubpass(
518 renderer, /*input_pass=*/pass2_out.value(),
519 input_snapshot->sampler_descriptor, tile_mode_,
520 BlurParameters{
521 .blur_uv_offset = Point(pass1_pixel_size.x, 0.0),
522 .blur_sigma = scaled_sigma.x * effective_scalar.x,
523 .blur_radius = ScaleBlurRadius(blur_radius.x, effective_scalar.x),
524 .step_size = 1,
525 },
526 pass3_destination, blur_uvs);
527
528 if (!pass3_out.ok()) {
529 return std::nullopt;
530 }
531
532 // The ping-pong approach requires that each render pass output has the same
533 // size.
534 FML_DCHECK((pass1_out.value().GetRenderTargetSize() ==
535 pass2_out.value().GetRenderTargetSize()) &&
536 (pass2_out.value().GetRenderTargetSize() ==
537 pass3_out.value().GetRenderTargetSize()));
538
539 SamplerDescriptor sampler_desc = MakeSamplerDescriptor(
540 MinMagFilter::kLinear, SamplerAddressMode::kClampToEdge);
541
542 Entity blur_output_entity = Entity::FromSnapshot(
543 Snapshot{.texture = pass3_out.value().GetRenderTargetTexture(),
544 .transform = input_snapshot->transform *
545 padding_snapshot_adjustment *
546 Matrix::MakeScale(1 / effective_scalar),
547 .sampler_descriptor = sampler_desc,
548 .opacity = input_snapshot->opacity},
549 entity.GetBlendMode(), entity.GetClipDepth());
550
551 return ApplyBlurStyle(mask_blur_style_, entity, inputs[0],
552 input_snapshot.value(), std::move(blur_output_entity),
553 mask_geometry_);
554 }
555
556 Scalar GaussianBlurFilterContents::CalculateBlurRadius(Scalar sigma) {
557 return static_cast<Radius>(Sigma(sigma)).radius;
558 }
559
560 Quad GaussianBlurFilterContents::CalculateUVs(
561 const std::shared_ptr<FilterInput>& filter_input,
562 const Entity& entity,
563 const Rect& source_rect,
564 const ISize& texture_size) {
565 Matrix input_transform = filter_input->GetLocalTransform(entity);
566 Quad coverage_quad = source_rect.GetTransformedPoints(input_transform);
567
568 Matrix uv_transform = Matrix::MakeScale(
569 {1.0f / texture_size.width, 1.0f / texture_size.height, 1.0f});
570 return uv_transform.Transform(coverage_quad);
571 }
572
573 // This function was calculated by observing Skia's behavior. Its blur at 500
574 // seemed to be 0.15. Since we clamp at 500 I solved the quadratic equation
575 // that puts the minima there and a f(0)=1.
576 Scalar GaussianBlurFilterContents::ScaleSigma(Scalar sigma) {
577 // Limit the kernel size to 1000x1000 pixels, like Skia does.
578 Scalar clamped = std::min(sigma, 500.0f);
579 constexpr Scalar a = 3.4e-06;
580 constexpr Scalar b = -3.4e-3;
581 constexpr Scalar c = 1.f;
582 Scalar scalar = c + b * clamped + a * clamped * clamped;
583 return clamped * scalar;
584 }
585
586 KernelPipeline::FragmentShader::KernelSamples GenerateBlurInfo(
587 BlurParameters parameters) {
588 KernelPipeline::FragmentShader::KernelSamples result;
589 result.sample_count =
590 ((2 * parameters.blur_radius) / parameters.step_size) + 1;
591
592 // Chop off the last samples if the radius >= 3 where they account for < 1.56%
593 // of the result.
594 int x_offset = 0;
595 if (parameters.blur_radius >= 3) {
596 result.sample_count -= 2;
597 x_offset = 1;
598 }
599
600 Scalar tally = 0.0f;
601 for (int i = 0; i < result.sample_count; ++i) {
602 int x = x_offset + (i * parameters.step_size) - parameters.blur_radius;
603 result.samples[i] = KernelPipeline::FragmentShader::KernelSample{
604 .uv_offset = parameters.blur_uv_offset * x,
605 .coefficient = expf(-0.5f * (x * x) /
606 (parameters.blur_sigma * parameters.blur_sigma)) /
607 (sqrtf(2.0f * M_PI) * parameters.blur_sigma),
608 };
609 tally += result.samples[i].coefficient;
610 }
611
612 // Make sure everything adds up to 1.
613 for (auto& sample : result.samples) {
614 sample.coefficient /= tally;
615 }
616
617 return result;
618 }
619
620 // This works by shrinking the kernel size by 2 and relying on lerp to read
621 // between the samples.
622 KernelPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(
623 KernelPipeline::FragmentShader::KernelSamples parameters) {
624 KernelPipeline::FragmentShader::KernelSamples result;
625 result.sample_count = ((parameters.sample_count - 1) / 2) + 1;
626 int32_t middle = result.sample_count / 2;
627 int32_t j = 0;
628 for (int i = 0; i < result.sample_count; i++) {
629 if (i == middle) {
630 result.samples[i] = parameters.samples[j++];
631 } else {
632 KernelPipeline::FragmentShader::KernelSample left = parameters.samples[j];
633 KernelPipeline::FragmentShader::KernelSample right =
634 parameters.samples[j + 1];
635 result.samples[i] = KernelPipeline::FragmentShader::KernelSample{
636 .uv_offset = (left.uv_offset * left.coefficient +
637 right.uv_offset * right.coefficient) /
638 (left.coefficient + right.coefficient),
639 .coefficient = left.coefficient + right.coefficient,
640 };
641 j += 2;
642 }
643 }
644
645 return result;
646 }
647
648 } // namespace impeller
impeller::ContentContext::GetTexturePipeline
std::shared_ptr< Pipeline< PipelineDescriptor > > GetTexturePipeline(ContentContextOptions opts) const
Definition: content_context.h:485
impeller::Entity::ClipOperation::kIntersect
@ kIntersect
impeller::Entity::TileMode::kClamp
@ kClamp
impeller::OptionsFromPass
ContentContextOptions OptionsFromPass(const RenderPass &pass)
Definition: contents.cc:20
impeller::ContentContext::GetKernelDecalPipeline
std::shared_ptr< Pipeline< PipelineDescriptor > > GetKernelDecalPipeline(ContentContextOptions opts) const
Definition: content_context.h:531
impeller::TPoint::y
Type y
Definition: point.h:31
impeller::Entity::ClipOperation::kDifference
@ kDifference
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::BlurParameters::blur_uv_offset
Point blur_uv_offset
Definition: gaussian_blur_filter_contents.h:16
impeller::FilterContents::BlurStyle
BlurStyle
Definition: filter_contents.h:26
impeller::kEhCloseEnough
constexpr float kEhCloseEnough
Definition: constants.h:56
impeller::SamplerAddressMode
SamplerAddressMode
Definition: formats.h:423
impeller::HostBuffer
Definition: host_buffer.h:28
impeller::Entity::TileMode::kDecal
@ kDecal
impeller::Vector2
Point Vector2
Definition: point.h:320
impeller::Entity::FromSnapshot
static Entity FromSnapshot(const 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:22
impeller::RenderPipelineT::FragmentShader
FragmentShader_ FragmentShader
Definition: pipeline.h:94
impeller::RenderPass::SetVertexBuffer
virtual bool SetVertexBuffer(VertexBuffer buffer)
Specify the vertex and index buffer to use for this command.
Definition: render_pass.cc:123
impeller::VertexBufferBuilder::AddVertices
VertexBufferBuilder & AddVertices(std::initializer_list< VertexType_ > vertices)
Definition: vertex_buffer_builder.h:70
impeller::SamplerAddressMode::kClampToEdge
@ kClampToEdge
gaussian_blur_filter_contents.h
impeller::Size
TSize< Scalar > Size
Definition: size.h:137
impeller::FilterContents::BlurStyle::kNormal
@ kNormal
Blurred inside and outside.
impeller::GaussianBlurFilterContents::CalculateBlurRadius
static Scalar CalculateBlurRadius(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:556
impeller::Entity::TileMode::kRepeat
@ kRepeat
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
impeller::ContentContext::GetKernelPipeline
std::shared_ptr< Pipeline< PipelineDescriptor > > GetKernelPipeline(ContentContextOptions opts) const
Definition: content_context.h:536
impeller::GenerateBlurInfo
KernelPipeline::FragmentShader::KernelSamples GenerateBlurInfo(BlurParameters parameters)
Definition: gaussian_blur_filter_contents.cc:586
impeller::RenderPass::SetCommandLabel
virtual void SetCommandLabel(std::string_view label)
The debugging label to use for the command.
Definition: render_pass.cc:97
impeller::TRect::GetOrigin
constexpr TPoint< Type > GetOrigin() const
Returns the upper left corner of the rectangle as specified by the left/top or x/y values when it was...
Definition: rect.h:287
impeller::Vector3::x
Scalar x
Definition: vector.h:23
impeller::LerpHackKernelSamples
KernelPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(KernelPipeline::FragmentShader::KernelSamples parameters)
Definition: gaussian_blur_filter_contents.cc:622
impeller::Entity::TileMode::kMirror
@ kMirror
impeller::RenderPass::Draw
virtual fml::Status Draw()
Record the currently pending command.
Definition: render_pass.cc:127
impeller::Entity::SetContents
void SetContents(std::shared_ptr< Contents > contents)
Definition: entity.cc:93
impeller::SamplerDescriptor::mag_filter
MinMagFilter mag_filter
Definition: sampler_descriptor.h:17
impeller::SamplerDescriptor
Definition: sampler_descriptor.h:15
impeller::GaussianBlurFilterContents::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: gaussian_blur_filter_contents.cc:359
impeller::BlurParameters::blur_radius
int blur_radius
Definition: gaussian_blur_filter_contents.h:18
impeller::Entity
Definition: entity.h:21
command.h
impeller::Matrix::Basis
constexpr Matrix Basis() const
The Matrix without its w components (without translation).
Definition: matrix.h:224
impeller::TSize< int64_t >
impeller::PrimitiveType::kTriangleStrip
@ kTriangleStrip
impeller::SamplerDescriptor::min_filter
MinMagFilter min_filter
Definition: sampler_descriptor.h:16
impeller::Point
TPoint< Scalar > Point
Definition: point.h:316
impeller::Quad
std::array< Point, 4 > Quad
Definition: point.h:321
render_pass.h
impeller::RenderTarget::GetRenderTargetTexture
std::shared_ptr< Texture > GetRenderTargetTexture() const
Definition: render_target.cc:144
color_source_contents.h
impeller::Context::BackendType::kOpenGLES
@ kOpenGLES
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::FilterContents::BlurStyle::kSolid
@ kSolid
Solid inside, blurred outside.
impeller::TRect::GetTransformedPoints
constexpr std::array< TPoint< T >, 4 > GetTransformedPoints(const Matrix &transform) const
Definition: rect.h:394
impeller::ContentContext::MakeSubpass
fml::StatusOr< RenderTarget > MakeSubpass(const std::string &label, ISize texture_size, const SubpassCallback &subpass_callback, bool msaa_enabled=true, bool depth_stencil_enabled=false, int32_t mip_count=1) const
Creates a new texture of size texture_size and calls subpass_callback with a RenderPass for drawing t...
Definition: content_context.cc:478
impeller::ContentContext::GetContext
std::shared_ptr< Context > GetContext() const
Definition: content_context.cc:564
impeller::VertexBufferBuilder
Definition: vertex_buffer_builder.h:24
impeller::MinMagFilter::kLinear
@ kLinear
impeller::GaussianBlurFilterContents::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: gaussian_blur_filter_contents.cc:348
impeller::TRect< Scalar >::MakeOriginSize
constexpr static TRect MakeOriginSize(const TPoint< Type > &origin, const TSize< Type > &size)
Definition: rect.h:140
impeller::Snapshot
Represents a texture and its intended draw transform/sampler configuration.
Definition: snapshot.h:25
impeller::SamplerDescriptor::width_address_mode
SamplerAddressMode width_address_mode
Definition: sampler_descriptor.h:20
impeller::BlurParameters::step_size
int step_size
Definition: gaussian_blur_filter_contents.h:19
texture_mipmap.h
impeller::Sigma
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
impeller::TRect::Scale
constexpr TRect Scale(Type scale) const
Definition: rect.h:188
impeller::MinMagFilter
MinMagFilter
Definition: formats.h:407
clip_contents.h
impeller::RenderTarget
Definition: render_target.h:38
impeller::Rect
TRect< Scalar > Rect
Definition: rect.h:661
impeller::GaussianBlurFragmentShader
KernelPipeline::FragmentShader GaussianBlurFragmentShader
Definition: gaussian_blur_filter_contents.cc:23
impeller::Vector3::y
Scalar y
Definition: vector.h:24
impeller::FilterContents::BlurStyle::kInner
@ kInner
Blurred inside, nothing outside.
impeller::BlurParameters
Definition: gaussian_blur_filter_contents.h:15
impeller::Entity::TileMode
TileMode
Definition: entity.h:42
impeller::TSize::width
Type width
Definition: size.h:22
impeller::Entity::GetBlendMode
BlendMode GetBlendMode() const
Definition: entity.cc:134
impeller::Matrix::Transform
constexpr Quad Transform(const Quad &quad) const
Definition: matrix.h:452
impeller::TPoint::x
Type x
Definition: point.h:30
impeller::ContentContextOptions::primitive_type
PrimitiveType primitive_type
Definition: content_context.h:337
impeller::VertexBufferBuilder::CreateVertexBuffer
VertexBuffer CreateVertexBuffer(HostBuffer &host_buffer) const
Definition: vertex_buffer_builder.h:84
impeller::ContentContext::SubpassCallback
std::function< bool(const ContentContext &, RenderPass &)> SubpassCallback
Definition: content_context.h:783
impeller::ISize
TSize< int64_t > ISize
Definition: size.h:138
impeller::RenderPass
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:33
impeller::Entity::GetClipDepth
uint32_t GetClipDepth() const
Definition: entity.cc:105
content_context.h
impeller::FilterContents::BlurStyle::kOuter
@ kOuter
Nothing inside, blurred outside.
impeller::Entity::GetCoverage
std::optional< Rect > GetCoverage() const
Definition: entity.cc:69
impeller::Entity::SetTransform
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:65
impeller::TRect::GetSize
constexpr TSize< Type > GetSize() const
Returns the size of the rectangle which may be negative in either width or height and may have been c...
Definition: rect.h:294
impeller::ContentContext::GetDeviceCapabilities
const Capabilities & GetDeviceCapabilities() const
Definition: content_context.cc:568
impeller::SamplerAddressMode::kMirror
@ kMirror
impeller::TRect< Scalar >::MakeSize
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:146
std
Definition: comparable.h:95
impeller::GaussianBlurFilterContents::kNoMipsError
static std::string_view kNoMipsError
Definition: gaussian_blur_filter_contents.h:36
impeller::Entity::Clone
Entity Clone() const
Definition: entity.cc:210
impeller::TPoint< Scalar >
impeller::saturated::b
SI b
Definition: saturated_math.h:87
impeller::HostBuffer::EmplaceUniform
BufferView EmplaceUniform(const UniformType &uniform)
Emplace uniform data onto the host buffer. Ensure that backend specific uniform alignment requirement...
Definition: host_buffer.h:50
impeller::Matrix::MakeOrthographic
static constexpr Matrix MakeOrthographic(TSize< T > size)
Definition: matrix.h:462
impeller::Entity::ClipOperation
ClipOperation
Definition: entity.h:61
impeller::GaussianBlurFilterContents::CalculateScale
static Scalar CalculateScale(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:321
impeller::Contents::MakeAnonymous
static std::shared_ptr< Contents > MakeAnonymous(RenderProc render_proc, CoverageProc coverage_proc)
Definition: contents.cc:44
impeller::TSize::height
Type height
Definition: size.h:23
impeller::GaussianBlurFilterContents::CalculateUVs
static Quad CalculateUVs(const std::shared_ptr< FilterInput > &filter_input, const Entity &entity, const Rect &source_rect, const ISize &texture_size)
Definition: gaussian_blur_filter_contents.cc:560
impeller::RenderPass::SetPipeline
virtual void SetPipeline(const std::shared_ptr< Pipeline< PipelineDescriptor >> &pipeline)
The pipeline to use for this command.
Definition: render_pass.cc:92
impeller::FilterInput::Vector
std::vector< FilterInput::Ref > Vector
Definition: filter_input.h:33
impeller::Capabilities::SupportsDecalSamplerAddressMode
virtual bool SupportsDecalSamplerAddressMode() const =0
Whether the context backend supports SamplerAddressMode::Decal.
impeller::RenderPipelineT::VertexShader
VertexShader_ VertexShader
Definition: pipeline.h:93
impeller::ContentContextOptions
Definition: content_context.h:288
impeller::SamplerDescriptor::height_address_mode
SamplerAddressMode height_address_mode
Definition: sampler_descriptor.h:21
impeller
Definition: aiks_blur_unittests.cc:20
impeller::Matrix::MakeScale
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
impeller::BlurParameters::blur_sigma
Scalar blur_sigma
Definition: gaussian_blur_filter_contents.h:17
impeller::ContentContext
Definition: content_context.h:392
impeller::TRect
Definition: rect.h:122
impeller::GaussianBlurFilterContents::kBlurFilterRequiredMipCount
static const int32_t kBlurFilterRequiredMipCount
Definition: gaussian_blur_filter_contents.h:37
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::Vector3
Definition: vector.h:20
impeller::ContentContext::GetTransientsBuffer
HostBuffer & GetTransientsBuffer() const
Retrieve the currnent host buffer for transient storage.
Definition: content_context.h:833
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:547
vertex_buffer_builder.h
impeller::GaussianBlurFilterContents::ScaleSigma
static Scalar ScaleSigma(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:576
impeller::SamplerAddressMode::kRepeat
@ kRepeat
impeller::GaussianBlurVertexShader
KernelPipeline::VertexShader GaussianBlurVertexShader
Definition: gaussian_blur_filter_contents.cc:22
impeller::SamplerAddressMode::kDecal
@ kDecal
decal sampling mode is only supported on devices that pass the Capabilities.SupportsDecalSamplerAddre...