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 
6 
7 #include <cmath>
8 
9 #include "flutter/fml/make_copyable.h"
12 #include "impeller/entity/entity.h"
13 #include "impeller/entity/texture_downsample.frag.h"
14 #include "impeller/entity/texture_fill.frag.h"
15 #include "impeller/entity/texture_fill.vert.h"
19 
20 namespace impeller {
21 
24 
25 namespace {
26 
27 constexpr Scalar kMaxSigma = 500.0f;
28 
29 SamplerDescriptor MakeSamplerDescriptor(MinMagFilter filter,
30  SamplerAddressMode address_mode) {
31  SamplerDescriptor sampler_desc;
32  sampler_desc.min_filter = filter;
33  sampler_desc.mag_filter = filter;
34  sampler_desc.width_address_mode = address_mode;
35  sampler_desc.height_address_mode = address_mode;
36  return sampler_desc;
37 }
38 
39 void SetTileMode(SamplerDescriptor* descriptor,
40  const ContentContext& renderer,
41  Entity::TileMode tile_mode) {
42  switch (tile_mode) {
47  }
48  break;
52  break;
56  break;
60  break;
61  }
62 }
63 
64 Vector2 Clamp(Vector2 vec2, Scalar min, Scalar max) {
65  return Vector2(std::clamp(vec2.x, /*lo=*/min, /*hi=*/max),
66  std::clamp(vec2.y, /*lo=*/min, /*hi=*/max));
67 }
68 
69 Vector2 ExtractScale(const Matrix& matrix) {
70  Vector2 entity_scale_x = matrix * Vector2(1.0, 0.0);
71  Vector2 entity_scale_y = matrix * Vector2(0.0, 1.0);
72  return Vector2(entity_scale_x.GetLength(), entity_scale_y.GetLength());
73 }
74 
75 struct BlurInfo {
76  /// The scalar that is used to get from source space to unrotated local space.
78  /// The translation that is used to get from source space to unrotated local
79  /// space.
81  /// Sigma when considering an entity's scale and the effect transform.
83  /// Blur radius in source pixels based on scaled_sigma.
85  /// The halo padding in source space.
87  /// Padding in unrotated local space.
89 };
90 
91 /// Calculates sigma derivatives necessary for rendering or calculating
92 /// coverage.
93 BlurInfo CalculateBlurInfo(const Entity& entity,
94  const Matrix& effect_transform,
95  Vector2 sigma) {
96  // Source space here is scaled by the entity's transform. This is a
97  // requirement for text to be rendered correctly. You can think of this as
98  // "scaled source space" or "un-rotated local space". The entity's rotation is
99  // applied to the result of the blur as part of the result's transform.
101  ExtractScale(entity.GetTransform().Basis());
103  Vector2(entity.GetTransform().m[12], entity.GetTransform().m[13]);
104 
106  (effect_transform.Basis() * Matrix::MakeScale(source_space_scalar) * //
109  .Abs();
110  scaled_sigma = Clamp(scaled_sigma, 0, kMaxSigma);
114  Vector2 padding(ceil(blur_radius.x), ceil(blur_radius.y));
117  return {
118  .source_space_scalar = source_space_scalar,
119  .source_space_offset = source_space_offset,
120  .scaled_sigma = scaled_sigma,
121  .blur_radius = blur_radius,
122  .padding = padding,
123  .local_padding = local_padding,
124  };
125 }
126 
127 /// Perform FilterInput::GetSnapshot with safety checks.
128 std::optional<Snapshot> GetSnapshot(const std::shared_ptr<FilterInput>& input,
129  const ContentContext& renderer,
130  const Entity& entity,
131  const std::optional<Rect>& coverage_hint) {
132  std::optional<Snapshot> input_snapshot =
133  input->GetSnapshot("GaussianBlur", renderer, entity,
134  /*coverage_limit=*/coverage_hint);
135  if (!input_snapshot.has_value()) {
136  return std::nullopt;
137  }
138 
139  return input_snapshot;
140 }
141 
142 /// Returns `rect` relative to `reference`, where Rect::MakeXYWH(0,0,1,1) will
143 /// be returned when `rect` == `reference`.
144 Rect MakeReferenceUVs(const Rect& reference, const Rect& rect) {
145  Rect result = Rect::MakeOriginSize(rect.GetOrigin() - reference.GetOrigin(),
146  rect.GetSize());
147  return result.Scale(1.0f / Vector2(reference.GetSize()));
148 }
149 
150 Quad CalculateSnapshotUVs(
151  const Snapshot& input_snapshot,
152  const std::optional<Rect>& source_expanded_coverage_hint) {
153  std::optional<Rect> input_snapshot_coverage = input_snapshot.GetCoverage();
154  Quad blur_uvs = {Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)};
155  FML_DCHECK(input_snapshot.transform.IsTranslationScaleOnly());
156  if (source_expanded_coverage_hint.has_value() &&
157  input_snapshot_coverage.has_value()) {
158  // Only process the uvs where the blur is happening, not the whole texture.
159  std::optional<Rect> uvs =
160  MakeReferenceUVs(input_snapshot_coverage.value(),
161  source_expanded_coverage_hint.value())
162  .Intersection(Rect::MakeSize(Size(1, 1)));
163  FML_DCHECK(uvs.has_value());
164  if (uvs.has_value()) {
165  blur_uvs[0] = uvs->GetLeftTop();
166  blur_uvs[1] = uvs->GetRightTop();
167  blur_uvs[2] = uvs->GetLeftBottom();
168  blur_uvs[3] = uvs->GetRightBottom();
169  }
170  }
171  return blur_uvs;
172 }
173 
174 Scalar CeilToDivisible(Scalar val, Scalar divisor) {
175  if (divisor == 0.0f) {
176  return val;
177  }
178 
179  Scalar remainder = fmod(val, divisor);
180  if (remainder != 0.0f) {
181  return val + (divisor - remainder);
182  } else {
183  return val;
184  }
185 }
186 
187 Scalar FloorToDivisible(Scalar val, Scalar divisor) {
188  if (divisor == 0.0f) {
189  return val;
190  }
191 
192  Scalar remainder = fmod(val, divisor);
193  if (remainder != 0.0f) {
194  return val - remainder;
195  } else {
196  return val;
197  }
198 }
199 
200 struct DownsamplePassArgs {
201  /// The output size of the down-sampling pass.
203  /// The UVs that will be used for drawing to the down-sampling pass.
204  /// This effectively is chopping out a region of the input.
206  /// The effective scalar of the down-sample pass.
207  /// This isn't usually exactly as we'd calculate because it has to be rounded
208  /// to integer boundaries for generating the texture for the output.
210  /// Transforms from unrotated local space to position the output from the
211  /// down-sample pass.
212  /// This can differ if we request a coverage hint but it is rejected, as is
213  /// the case with backdrop filters.
214  Matrix transform;
215 };
216 
217 /// Calculates info required for the down-sampling pass.
218 DownsamplePassArgs CalculateDownsamplePassArgs(
221  const Snapshot& input_snapshot,
222  const std::optional<Rect>& source_expanded_coverage_hint,
223  const std::shared_ptr<FilterInput>& input,
224  const Entity& snapshot_entity) {
225  Scalar desired_scalar =
228 
229  // TODO(jonahwilliams): If desired_scalar is 1.0 and we fully acquired the
230  // gutter from the expanded_coverage_hint, we can skip the downsample pass.
231  // pass.
232  Vector2 downsample_scalar(desired_scalar, desired_scalar);
233  // TODO(gaaclarke): The padding could be removed if we know it's not needed or
234  // resized to account for the expanded_clip_coverage. There doesn't appear
235  // to be the math to make those calculations though. The following
236  // optimization works, but causes a shimmer as a result of
237  // https://github.com/flutter/flutter/issues/140193 so it isn't applied.
238  //
239  // !input_snapshot->GetCoverage()->Expand(-local_padding)
240  // .Contains(coverage_hint.value()))
241 
242  std::optional<Rect> snapshot_coverage = input_snapshot.GetCoverage();
243  if (input_snapshot.transform.Equals(snapshot_entity.GetTransform()) &&
244  source_expanded_coverage_hint.has_value() &&
245  snapshot_coverage.has_value() &&
246  snapshot_coverage->Contains(source_expanded_coverage_hint.value())) {
247  // If the snapshot's transform is the identity transform and we have
248  // coverage hint that fits inside of the snapshots coverage that means the
249  // coverage hint was ignored so we will trim out the area we are interested
250  // in the down-sample pass. This usually means we have a backdrop image
251  // filter.
252  //
253  // The region we cut out will be aligned with the down-sample divisor to
254  // avoid pixel alignment problems that create shimmering.
255  int32_t divisor = std::round(1.0f / desired_scalar);
256  Rect aligned_coverage_hint = Rect::MakeLTRB(
257  FloorToDivisible(source_expanded_coverage_hint->GetLeft(), divisor),
258  FloorToDivisible(source_expanded_coverage_hint->GetTop(), divisor),
259  source_expanded_coverage_hint->GetRight(),
260  source_expanded_coverage_hint->GetBottom());
261  aligned_coverage_hint = Rect::MakeXYWH(
262  aligned_coverage_hint.GetX(), aligned_coverage_hint.GetY(),
263  CeilToDivisible(aligned_coverage_hint.GetWidth(), divisor),
264  CeilToDivisible(aligned_coverage_hint.GetHeight(), divisor));
265  ISize source_size = ISize(aligned_coverage_hint.GetSize().width,
266  aligned_coverage_hint.GetSize().height);
267  Vector2 downsampled_size = source_size * downsample_scalar;
268  Scalar int_part;
269  FML_DCHECK(std::modf(downsampled_size.x, &int_part) == 0.0f);
270  FML_DCHECK(std::modf(downsampled_size.y, &int_part) == 0.0f);
271  (void)int_part;
272  ISize subpass_size = ISize(downsampled_size.x, downsampled_size.y);
273  Vector2 effective_scalar = Vector2(subpass_size) / source_size;
274  FML_DCHECK(effective_scalar == downsample_scalar);
275 
276  Quad uvs = CalculateSnapshotUVs(input_snapshot, aligned_coverage_hint);
277  return {
278  .subpass_size = subpass_size,
279  .uvs = uvs,
280  .effective_scalar = effective_scalar,
281  .transform = Matrix::MakeTranslation(
282  {aligned_coverage_hint.GetX(), aligned_coverage_hint.GetY(), 0})};
283  } else {
284  //////////////////////////////////////////////////////////////////////////////
285  auto input_snapshot_size = input_snapshot.texture->GetSize();
286  Rect source_rect = Rect::MakeSize(input_snapshot_size);
287  Rect source_rect_padded = source_rect.Expand(padding);
288  Vector2 downsampled_size = source_rect_padded.GetSize() * downsample_scalar;
290  ISize(ceil(downsampled_size.x), ceil(downsampled_size.y));
291  Vector2 divisible_size(CeilToDivisible(source_rect_padded.GetSize().width,
292  1.0 / downsample_scalar.x),
293  CeilToDivisible(source_rect_padded.GetSize().height,
294  1.0 / downsample_scalar.y));
295  // Only make the padding divisible if we already have padding. If we don't
296  // have padding adding more can add artifacts to hard blur edges.
297  Vector2 divisible_padding(
298  padding.x > 0
299  ? padding.x +
300  (divisible_size.x - source_rect_padded.GetSize().width) / 2.0
301  : 0.f,
302  padding.y > 0
303  ? padding.y +
304  (divisible_size.y - source_rect_padded.GetSize().height) / 2.0
305  : 0.f);
306  source_rect_padded = source_rect.Expand(divisible_padding);
307 
309  Vector2(subpass_size) / source_rect_padded.GetSize();
311  input, snapshot_entity, source_rect_padded, input_snapshot_size);
312  return {
313  .subpass_size = subpass_size,
314  .uvs = uvs,
315  .effective_scalar = effective_scalar,
316  .transform = input_snapshot.transform *
317  Matrix::MakeTranslation(-divisible_padding),
318  };
319  }
320 }
321 
322 /// Makes a subpass that will render the scaled down input and add the
323 /// transparent gutter required for the blur halo.
324 fml::StatusOr<RenderTarget> MakeDownsampleSubpass(
325  const ContentContext& renderer,
326  const std::shared_ptr<CommandBuffer>& command_buffer,
327  const std::shared_ptr<Texture>& input_texture,
328  const SamplerDescriptor& sampler_descriptor,
329  const DownsamplePassArgs& pass_args,
330  Entity::TileMode tile_mode) {
331  using VS = TextureFillVertexShader;
332 
333  // If the texture already had mip levels generated, then we can use the
334  // original downsample shader.
335  if (pass_args.effective_scalar.x >= 0.5f ||
336  (!input_texture->NeedsMipmapGeneration() &&
337  input_texture->GetTextureDescriptor().mip_count > 1)) {
338  ContentContext::SubpassCallback subpass_callback =
339  [&](const ContentContext& renderer, RenderPass& pass) {
340  HostBuffer& host_buffer = renderer.GetTransientsBuffer();
341 
342  pass.SetCommandLabel("Gaussian blur downsample");
343  auto pipeline_options = OptionsFromPass(pass);
344  pipeline_options.primitive_type = PrimitiveType::kTriangleStrip;
345  pass.SetPipeline(renderer.GetTexturePipeline(pipeline_options));
346 
347  TextureFillVertexShader::FrameInfo frame_info;
348  frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1));
349  frame_info.texture_sampler_y_coord_scale =
350  input_texture->GetYCoordScale();
351 
352  TextureFillFragmentShader::FragInfo frag_info;
353  frag_info.alpha = 1.0;
354 
355  const Quad& uvs = pass_args.uvs;
356  std::array<VS::PerVertexData, 4> vertices = {
357  VS::PerVertexData{Point(0, 0), uvs[0]},
358  VS::PerVertexData{Point(1, 0), uvs[1]},
359  VS::PerVertexData{Point(0, 1), uvs[2]},
360  VS::PerVertexData{Point(1, 1), uvs[3]},
361  };
362  pass.SetVertexBuffer(CreateVertexBuffer(vertices, host_buffer));
363 
364  SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
365  SetTileMode(&linear_sampler_descriptor, renderer, tile_mode);
366  linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
367  linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
368  TextureFillVertexShader::BindFrameInfo(
369  pass, host_buffer.EmplaceUniform(frame_info));
370  TextureFillFragmentShader::BindFragInfo(
371  pass, host_buffer.EmplaceUniform(frag_info));
372  TextureFillFragmentShader::BindTextureSampler(
373  pass, input_texture,
374  renderer.GetContext()->GetSamplerLibrary()->GetSampler(
375  linear_sampler_descriptor));
376 
377  return pass.Draw().ok();
378  };
379  return renderer.MakeSubpass("Gaussian Blur Filter", pass_args.subpass_size,
380  command_buffer, subpass_callback,
381  /*msaa_enabled=*/false,
382  /*depth_stencil_enabled=*/false);
383  } else {
384  // This assumes we don't scale below 1/16.
385  Scalar edge = 1.0;
386  Scalar ratio = 0.25;
387  if (pass_args.effective_scalar.x <= 0.0625f) {
388  edge = 7.0;
389  ratio = 1.0f / 64.0f;
390  } else if (pass_args.effective_scalar.x <= 0.125f) {
391  edge = 3.0;
392  ratio = 1.0f / 16.0f;
393  }
394  ContentContext::SubpassCallback subpass_callback =
395  [&](const ContentContext& renderer, RenderPass& pass) {
396  HostBuffer& host_buffer = renderer.GetTransientsBuffer();
397 
398  pass.SetCommandLabel("Gaussian blur downsample");
399  auto pipeline_options = OptionsFromPass(pass);
400  pipeline_options.primitive_type = PrimitiveType::kTriangleStrip;
401 #ifdef IMPELLER_ENABLE_OPENGLES
402  // The GLES backend conditionally supports decal tile mode, while
403  // decal is always supported for Vulkan and Metal.
404  if (renderer.GetDeviceCapabilities()
405  .SupportsDecalSamplerAddressMode() ||
406  tile_mode != Entity::TileMode::kDecal) {
407  pass.SetPipeline(renderer.GetDownsamplePipeline(pipeline_options));
408  } else {
409  pass.SetPipeline(
410  renderer.GetDownsampleTextureGlesPipeline(pipeline_options));
411  }
412 #else
413  pass.SetPipeline(renderer.GetDownsamplePipeline(pipeline_options));
414 #endif // IMPELLER_ENABLE_OPENGLES
415 
416  TextureFillVertexShader::FrameInfo frame_info;
417  frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1));
418  frame_info.texture_sampler_y_coord_scale =
419  input_texture->GetYCoordScale();
420 
421  TextureDownsampleFragmentShader::FragInfo frag_info;
422  frag_info.edge = edge;
423  frag_info.ratio = ratio;
424  frag_info.pixel_size = Vector2(1.0f / Size(input_texture->GetSize()));
425 
426  const Quad& uvs = pass_args.uvs;
427  std::array<VS::PerVertexData, 4> vertices = {
428  VS::PerVertexData{Point(0, 0), uvs[0]},
429  VS::PerVertexData{Point(1, 0), uvs[1]},
430  VS::PerVertexData{Point(0, 1), uvs[2]},
431  VS::PerVertexData{Point(1, 1), uvs[3]},
432  };
433  pass.SetVertexBuffer(CreateVertexBuffer(vertices, host_buffer));
434 
435  SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
436  SetTileMode(&linear_sampler_descriptor, renderer, tile_mode);
437  linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
438  linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
439  TextureFillVertexShader::BindFrameInfo(
440  pass, host_buffer.EmplaceUniform(frame_info));
441  TextureDownsampleFragmentShader::BindFragInfo(
442  pass, host_buffer.EmplaceUniform(frag_info));
443  TextureDownsampleFragmentShader::BindTextureSampler(
444  pass, input_texture,
445  renderer.GetContext()->GetSamplerLibrary()->GetSampler(
446  linear_sampler_descriptor));
447 
448  return pass.Draw().ok();
449  };
450  return renderer.MakeSubpass("Gaussian Blur Filter", pass_args.subpass_size,
451  command_buffer, subpass_callback,
452  /*msaa_enabled=*/false,
453  /*depth_stencil_enabled=*/false);
454  }
455 }
456 
457 fml::StatusOr<RenderTarget> MakeBlurSubpass(
458  const ContentContext& renderer,
459  const std::shared_ptr<CommandBuffer>& command_buffer,
460  const RenderTarget& input_pass,
461  const SamplerDescriptor& sampler_descriptor,
462  Entity::TileMode tile_mode,
463  const BlurParameters& blur_info,
464  std::optional<RenderTarget> destination_target,
465  const Quad& blur_uvs) {
467 
468  if (blur_info.blur_sigma < kEhCloseEnough) {
469  return input_pass;
470  }
471 
472  const std::shared_ptr<Texture>& input_texture =
473  input_pass.GetRenderTargetTexture();
474 
475  // TODO(gaaclarke): This blurs the whole image, but because we know the clip
476  // region we could focus on just blurring that.
477  ISize subpass_size = input_texture->GetSize();
478  ContentContext::SubpassCallback subpass_callback =
479  [&](const ContentContext& renderer, RenderPass& pass) {
480  GaussianBlurVertexShader::FrameInfo frame_info;
481  frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)),
482  frame_info.texture_sampler_y_coord_scale =
483  input_texture->GetYCoordScale();
484 
485  HostBuffer& host_buffer = renderer.GetTransientsBuffer();
486 
487  ContentContextOptions options = OptionsFromPass(pass);
488  options.primitive_type = PrimitiveType::kTriangleStrip;
489  pass.SetPipeline(renderer.GetGaussianBlurPipeline(options));
490 
491  std::array<VS::PerVertexData, 4> vertices = {
492  VS::PerVertexData{blur_uvs[0], blur_uvs[0]},
493  VS::PerVertexData{blur_uvs[1], blur_uvs[1]},
494  VS::PerVertexData{blur_uvs[2], blur_uvs[2]},
495  VS::PerVertexData{blur_uvs[3], blur_uvs[3]},
496  };
497  pass.SetVertexBuffer(CreateVertexBuffer(vertices, host_buffer));
498 
499  SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
500  linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
501  linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
502  GaussianBlurFragmentShader::BindTextureSampler(
503  pass, input_texture,
504  renderer.GetContext()->GetSamplerLibrary()->GetSampler(
505  linear_sampler_descriptor));
506  GaussianBlurVertexShader::BindFrameInfo(
507  pass, host_buffer.EmplaceUniform(frame_info));
508  GaussianBlurFragmentShader::BindKernelSamples(
509  pass, host_buffer.EmplaceUniform(
511  return pass.Draw().ok();
512  };
513  if (destination_target.has_value()) {
514  return renderer.MakeSubpass("Gaussian Blur Filter",
515  destination_target.value(), command_buffer,
516  subpass_callback);
517  } else {
518  return renderer.MakeSubpass(
519  "Gaussian Blur Filter", subpass_size, command_buffer, subpass_callback,
520  /*msaa_enabled=*/false, /*depth_stencil_enabled=*/false);
521  }
522 }
523 
524 int ScaleBlurRadius(Scalar radius, Scalar scalar) {
525  return static_cast<int>(std::round(radius * scalar));
526 }
527 
528 Entity ApplyClippedBlurStyle(Entity::ClipOperation clip_operation,
529  const Entity& entity,
530  const std::shared_ptr<FilterInput>& input,
531  const Snapshot& input_snapshot,
532  Entity blur_entity,
533  const Geometry* geometry) {
534  Matrix entity_transform = entity.GetTransform();
535  Matrix blur_transform = blur_entity.GetTransform();
536 
537  auto renderer =
538  fml::MakeCopyable([blur_entity = blur_entity.Clone(), clip_operation,
539  entity_transform, blur_transform, geometry](
540  const ContentContext& renderer,
541  const Entity& entity, RenderPass& pass) mutable {
542  Entity clipper;
543  clipper.SetClipDepth(entity.GetClipDepth());
544  clipper.SetTransform(entity.GetTransform() * entity_transform);
545 
546  auto geom_result = geometry->GetPositionBuffer(renderer, clipper, pass);
547 
548  ClipContents clip_contents(geometry->GetCoverage(clipper.GetTransform())
549  .value_or(Rect::MakeLTRB(0, 0, 0, 0)),
550  /*is_axis_aligned_rect=*/false);
551  clip_contents.SetClipOperation(clip_operation);
552  clip_contents.SetGeometry(std::move(geom_result));
553 
554  if (!clip_contents.Render(renderer, pass, entity.GetClipDepth())) {
555  return false;
556  }
557  blur_entity.SetClipDepth(entity.GetClipDepth());
558  blur_entity.SetTransform(entity.GetTransform() * blur_transform);
559 
560  return blur_entity.Render(renderer, pass);
561  });
562  auto coverage =
563  fml::MakeCopyable([blur_entity = std::move(blur_entity),
564  blur_transform](const Entity& entity) mutable {
565  blur_entity.SetTransform(entity.GetTransform() * blur_transform);
566  return blur_entity.GetCoverage();
567  });
568  Entity result;
569  result.SetContents(Contents::MakeAnonymous(renderer, coverage));
570  return result;
571 }
572 
573 Entity ApplyBlurStyle(FilterContents::BlurStyle blur_style,
574  const Entity& entity,
575  const std::shared_ptr<FilterInput>& input,
576  const Snapshot& input_snapshot,
577  Entity blur_entity,
578  const Geometry* geometry,
581  switch (blur_style) {
583  return blur_entity;
585  return ApplyClippedBlurStyle(Entity::ClipOperation::kIntersect, entity,
586  input, input_snapshot,
587  std::move(blur_entity), geometry);
588  break;
590  return ApplyClippedBlurStyle(Entity::ClipOperation::kDifference, entity,
591  input, input_snapshot,
592  std::move(blur_entity), geometry);
594  Entity snapshot_entity =
595  Entity::FromSnapshot(input_snapshot, entity.GetBlendMode());
596  Entity result;
597  Matrix blurred_transform = blur_entity.GetTransform();
598  Matrix snapshot_transform =
599  entity.GetTransform() * //
602  input_snapshot.transform;
603  result.SetContents(Contents::MakeAnonymous(
604  fml::MakeCopyable([blur_entity = blur_entity.Clone(),
605  blurred_transform, snapshot_transform,
606  snapshot_entity = std::move(snapshot_entity)](
607  const ContentContext& renderer,
608  const Entity& entity,
609  RenderPass& pass) mutable {
610  snapshot_entity.SetTransform(entity.GetTransform() *
611  snapshot_transform);
612  snapshot_entity.SetClipDepth(entity.GetClipDepth());
613  if (!snapshot_entity.Render(renderer, pass)) {
614  return false;
615  }
616  blur_entity.SetClipDepth(entity.GetClipDepth());
617  blur_entity.SetTransform(entity.GetTransform() * blurred_transform);
618  return blur_entity.Render(renderer, pass);
619  }),
620  fml::MakeCopyable([blur_entity = blur_entity.Clone(),
621  blurred_transform](const Entity& entity) mutable {
622  blur_entity.SetTransform(entity.GetTransform() * blurred_transform);
623  return blur_entity.GetCoverage();
624  })));
625  return result;
626  }
627  }
628 }
629 } // namespace
630 
631 GaussianBlurFilterContents::GaussianBlurFilterContents(
632  Scalar sigma_x,
633  Scalar sigma_y,
634  Entity::TileMode tile_mode,
635  BlurStyle mask_blur_style,
636  const Geometry* mask_geometry)
637  : sigma_(sigma_x, sigma_y),
638  tile_mode_(tile_mode),
639  mask_blur_style_(mask_blur_style),
640  mask_geometry_(mask_geometry) {
641  // This is supposed to be enforced at a higher level.
642  FML_DCHECK(mask_blur_style == BlurStyle::kNormal || mask_geometry);
643 }
644 
645 // This value was extracted from Skia, see:
646 // * https://github.com/google/skia/blob/d29cc3fe182f6e8a8539004a6a4ee8251677a6fd/src/gpu/ganesh/GrBlurUtils.cpp#L2561-L2576
647 // * https://github.com/google/skia/blob/d29cc3fe182f6e8a8539004a6a4ee8251677a6fd/src/gpu/BlurUtils.h#L57
649  if (sigma <= 4) {
650  return 1.0;
651  }
652  Scalar raw_result = 4.0 / sigma;
653  // Round to the nearest 1/(2^n) to get the best quality down scaling.
654  Scalar exponent = round(log2f(raw_result));
655  // Don't scale down below 1/16th to preserve signal.
656  exponent = std::max(-4.0f, exponent);
657  Scalar rounded = powf(2.0f, exponent);
658  Scalar result = rounded;
659  // Extend the range of the 1/8th downsample based on the effective kernel size
660  // for the blur.
661  if (rounded < 0.125f) {
662  Scalar rounded_plus = powf(2.0f, exponent + 1);
664  int kernel_size_plus = (ScaleBlurRadius(blur_radius, rounded_plus) * 2) + 1;
665  // This constant was picked by looking at the results to make sure no
666  // shimmering was introduced at the highest sigma values that downscale to
667  // 1/16th.
668  static constexpr int32_t kEighthDownsampleKernalWidthMax = 41;
669  result = kernel_size_plus <= kEighthDownsampleKernalWidthMax ? rounded_plus
670  : rounded;
671  }
672  return result;
673 };
674 
676  const Matrix& effect_transform,
677  const Rect& output_limit) const {
678  Vector2 scaled_sigma = {ScaleSigma(sigma_.x), ScaleSigma(sigma_.y)};
681  Vector3 blur_radii =
682  effect_transform.Basis() * Vector3{blur_radius.x, blur_radius.y, 0.0};
683  return output_limit.Expand(Point(blur_radii.x, blur_radii.y));
684 }
685 
687  const FilterInput::Vector& inputs,
688  const Entity& entity,
689  const Matrix& effect_transform) const {
690  if (inputs.empty()) {
691  return {};
692  }
693  std::optional<Rect> input_coverage = inputs[0]->GetCoverage(entity);
694  if (!input_coverage.has_value()) {
695  return {};
696  }
697 
698  BlurInfo blur_info = CalculateBlurInfo(entity, effect_transform, sigma_);
699  return input_coverage.value().Expand(
700  Point(blur_info.local_padding.x, blur_info.local_padding.y));
701 }
702 
703 // A brief overview how this works:
704 // 1) Snapshot the filter input.
705 // 2) Perform downsample pass. This also inserts the gutter around the input
706 // snapshot since the blur can render outside the bounds of the snapshot.
707 // 3) Perform 1D horizontal blur pass.
708 // 4) Perform 1D vertical blur pass.
709 // 5) Apply the blur style to the blur result. This may just mask the output or
710 // draw the original snapshot over the result.
711 std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
712  const FilterInput::Vector& inputs,
713  const ContentContext& renderer,
714  const Entity& entity,
715  const Matrix& effect_transform,
716  const Rect& coverage,
717  const std::optional<Rect>& coverage_hint) const {
718  if (inputs.empty()) {
719  return std::nullopt;
720  }
721 
722  BlurInfo blur_info = CalculateBlurInfo(entity, effect_transform, sigma_);
723 
724  // Apply as much of the desired padding as possible from the source. This may
725  // be ignored so must be accounted for in the downsample pass by adding a
726  // transparent gutter.
727  std::optional<Rect> expanded_coverage_hint;
728  if (coverage_hint.has_value()) {
729  expanded_coverage_hint = coverage_hint->Expand(blur_info.local_padding);
730  }
731 
732  Entity snapshot_entity = entity.Clone();
733  snapshot_entity.SetTransform(
734  Matrix::MakeTranslation(blur_info.source_space_offset) *
735  Matrix::MakeScale(blur_info.source_space_scalar));
736 
737  std::optional<Rect> source_expanded_coverage_hint;
738  if (expanded_coverage_hint.has_value()) {
739  source_expanded_coverage_hint = expanded_coverage_hint->TransformBounds(
740  Matrix::MakeTranslation(blur_info.source_space_offset) *
741  Matrix::MakeScale(blur_info.source_space_scalar) *
742  entity.GetTransform().Invert());
743  }
744 
745  std::optional<Snapshot> input_snapshot = GetSnapshot(
746  inputs[0], renderer, snapshot_entity, source_expanded_coverage_hint);
747  if (!input_snapshot.has_value()) {
748  return std::nullopt;
749  }
750 
751  if (blur_info.scaled_sigma.x < kEhCloseEnough &&
752  blur_info.scaled_sigma.y < kEhCloseEnough) {
753  Entity result =
754  Entity::FromSnapshot(input_snapshot.value(),
755  entity.GetBlendMode()); // No blur to render.
756  result.SetTransform(
757  entity.GetTransform() *
758  Matrix::MakeScale(1.f / blur_info.source_space_scalar) *
759  Matrix::MakeTranslation(-1 * blur_info.source_space_offset) *
760  input_snapshot->transform);
761  return result;
762  }
763 
764  // Note: The code below uses three different command buffers when it would be
765  // possible to combine the operations into a single buffer. From testing and
766  // user bug reports (see https://github.com/flutter/flutter/issues/154046 ),
767  // this sometimes causes deviceLost errors on older Adreno devices. Breaking
768  // the work up into three different command buffers seems to prevent this
769  // crash.
770  std::shared_ptr<CommandBuffer> command_buffer_1 =
771  renderer.GetContext()->CreateCommandBuffer();
772  if (!command_buffer_1) {
773  return std::nullopt;
774  }
775 
776  DownsamplePassArgs downsample_pass_args = CalculateDownsamplePassArgs(
777  blur_info.scaled_sigma, blur_info.padding, input_snapshot.value(),
778  source_expanded_coverage_hint, inputs[0], snapshot_entity);
779 
780  fml::StatusOr<RenderTarget> pass1_out = MakeDownsampleSubpass(
781  renderer, command_buffer_1, input_snapshot->texture,
782  input_snapshot->sampler_descriptor, downsample_pass_args, tile_mode_);
783 
784  if (!pass1_out.ok()) {
785  return std::nullopt;
786  }
787 
788  Vector2 pass1_pixel_size =
789  1.0 / Vector2(pass1_out.value().GetRenderTargetTexture()->GetSize());
790 
791  Quad blur_uvs = {Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)};
792 
793  std::shared_ptr<CommandBuffer> command_buffer_2 =
794  renderer.GetContext()->CreateCommandBuffer();
795  if (!command_buffer_2) {
796  return std::nullopt;
797  }
798 
799  fml::StatusOr<RenderTarget> pass2_out = MakeBlurSubpass(
800  renderer, command_buffer_2, /*input_pass=*/pass1_out.value(),
801  input_snapshot->sampler_descriptor, tile_mode_,
802  BlurParameters{
803  .blur_uv_offset = Point(0.0, pass1_pixel_size.y),
804  .blur_sigma = blur_info.scaled_sigma.y *
805  downsample_pass_args.effective_scalar.y,
806  .blur_radius = ScaleBlurRadius(
807  blur_info.blur_radius.y, downsample_pass_args.effective_scalar.y),
808  .step_size = 1,
809  },
810  /*destination_target=*/std::nullopt, blur_uvs);
811 
812  if (!pass2_out.ok()) {
813  return std::nullopt;
814  }
815 
816  std::shared_ptr<CommandBuffer> command_buffer_3 =
817  renderer.GetContext()->CreateCommandBuffer();
818  if (!command_buffer_3) {
819  return std::nullopt;
820  }
821 
822  // Only ping pong if the first pass actually created a render target.
823  auto pass3_destination = pass2_out.value().GetRenderTargetTexture() !=
824  pass1_out.value().GetRenderTargetTexture()
825  ? std::optional<RenderTarget>(pass1_out.value())
826  : std::optional<RenderTarget>(std::nullopt);
827 
828  fml::StatusOr<RenderTarget> pass3_out = MakeBlurSubpass(
829  renderer, command_buffer_3, /*input_pass=*/pass2_out.value(),
830  input_snapshot->sampler_descriptor, tile_mode_,
831  BlurParameters{
832  .blur_uv_offset = Point(pass1_pixel_size.x, 0.0),
833  .blur_sigma = blur_info.scaled_sigma.x *
834  downsample_pass_args.effective_scalar.x,
835  .blur_radius = ScaleBlurRadius(
836  blur_info.blur_radius.x, downsample_pass_args.effective_scalar.x),
837  .step_size = 1,
838  },
839  pass3_destination, blur_uvs);
840 
841  if (!pass3_out.ok()) {
842  return std::nullopt;
843  }
844 
845  if (!(renderer.GetContext()->EnqueueCommandBuffer(
846  std::move(command_buffer_1)) &&
847  renderer.GetContext()->EnqueueCommandBuffer(
848  std::move(command_buffer_2)) &&
849  renderer.GetContext()->EnqueueCommandBuffer(
850  std::move(command_buffer_3)))) {
851  return std::nullopt;
852  }
853 
854  // The ping-pong approach requires that each render pass output has the same
855  // size.
856  FML_DCHECK((pass1_out.value().GetRenderTargetSize() ==
857  pass2_out.value().GetRenderTargetSize()) &&
858  (pass2_out.value().GetRenderTargetSize() ==
859  pass3_out.value().GetRenderTargetSize()));
860 
861  SamplerDescriptor sampler_desc = MakeSamplerDescriptor(
863 
864  Entity blur_output_entity = Entity::FromSnapshot(
865  Snapshot{.texture = pass3_out.value().GetRenderTargetTexture(),
866  .transform =
867  entity.GetTransform() * //
868  Matrix::MakeScale(1.f / blur_info.source_space_scalar) * //
869  Matrix::MakeTranslation(-1 * blur_info.source_space_offset) *
870  downsample_pass_args.transform * //
871  Matrix::MakeScale(1 / downsample_pass_args.effective_scalar),
872  .sampler_descriptor = sampler_desc,
873  .opacity = input_snapshot->opacity},
874  entity.GetBlendMode());
875 
876  return ApplyBlurStyle(mask_blur_style_, entity, inputs[0],
877  input_snapshot.value(), std::move(blur_output_entity),
878  mask_geometry_, blur_info.source_space_scalar,
879  blur_info.source_space_offset);
880 }
881 
883  return static_cast<Radius>(Sigma(sigma)).radius;
884 }
885 
887  const std::shared_ptr<FilterInput>& filter_input,
888  const Entity& entity,
889  const Rect& source_rect,
890  const ISize& texture_size) {
891  Matrix input_transform = filter_input->GetLocalTransform(entity);
892  Quad coverage_quad = source_rect.GetTransformedPoints(input_transform);
893 
894  Matrix uv_transform = Matrix::MakeScale(
895  {1.0f / texture_size.width, 1.0f / texture_size.height, 1.0f});
896  return uv_transform.Transform(coverage_quad);
897 }
898 
899 // This function was calculated by observing Skia's behavior. Its blur at 500
900 // seemed to be 0.15. Since we clamp at 500 I solved the quadratic equation
901 // that puts the minima there and a f(0)=1.
903  // Limit the kernel size to 1000x1000 pixels, like Skia does.
904  Scalar clamped = std::min(sigma, kMaxSigma);
905  constexpr Scalar a = 3.4e-06;
906  constexpr Scalar b = -3.4e-3;
907  constexpr Scalar c = 1.f;
908  Scalar scalar = c + b * clamped + a * clamped * clamped;
909  return clamped * scalar;
910 }
911 
913  KernelSamples result;
914  result.sample_count =
915  ((2 * parameters.blur_radius) / parameters.step_size) + 1;
916 
917  // Chop off the last samples if the radius >= 16 where they can account for
918  // < 1.56% of the result.
919  int x_offset = 0;
920  if (parameters.blur_radius >= 16) {
921  result.sample_count -= 2;
922  x_offset = 1;
923  }
924 
925  // This is a safe-guard to make sure we don't overflow the fragment shader.
926  // The kernel size is multiplied by 2 since we'll use the lerp hack on the
927  // result. In practice this isn't throwing away much data since the blur radii
928  // are around 53 before the down-sampling and max sigma of 500 kick in.
929  //
930  // TODO(https://github.com/flutter/flutter/issues/150462): Come up with a more
931  // wholistic remedy for this. A proper downsample size should not make this
932  // required. Or we can increase the kernel size.
935  }
936 
937  Scalar tally = 0.0f;
938  for (int i = 0; i < result.sample_count; ++i) {
939  int x = x_offset + (i * parameters.step_size) - parameters.blur_radius;
940  result.samples[i] = KernelSample{
941  .uv_offset = parameters.blur_uv_offset * x,
942  .coefficient = expf(-0.5f * (x * x) /
943  (parameters.blur_sigma * parameters.blur_sigma)) /
944  (sqrtf(2.0f * M_PI) * parameters.blur_sigma),
945  };
946  tally += result.samples[i].coefficient;
947  }
948 
949  // Make sure everything adds up to 1.
950  for (auto& sample : result.samples) {
951  sample.coefficient /= tally;
952  }
953 
954  return result;
955 }
956 
957 // This works by shrinking the kernel size by 2 and relying on lerp to read
958 // between the samples.
959 GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(
960  KernelSamples parameters) {
961  GaussianBlurPipeline::FragmentShader::KernelSamples result = {};
962  result.sample_count = ((parameters.sample_count - 1) / 2) + 1;
963  int32_t middle = result.sample_count / 2;
964  int32_t j = 0;
965  FML_DCHECK(result.sample_count <= kGaussianBlurMaxKernelSize);
966  static_assert(sizeof(result.sample_data) ==
967  sizeof(std::array<Vector4, kGaussianBlurMaxKernelSize>));
968 
969  for (int i = 0; i < result.sample_count; i++) {
970  if (i == middle) {
971  result.sample_data[i].x = parameters.samples[j].uv_offset.x;
972  result.sample_data[i].y = parameters.samples[j].uv_offset.y;
973  result.sample_data[i].z = parameters.samples[j].coefficient;
974  j++;
975  } else {
976  KernelSample left = parameters.samples[j];
977  KernelSample right = parameters.samples[j + 1];
978 
979  result.sample_data[i].z = left.coefficient + right.coefficient;
980 
981  Point uv = (left.uv_offset * left.coefficient +
982  right.uv_offset * right.coefficient) /
983  (left.coefficient + right.coefficient);
984  result.sample_data[i].x = uv.x;
985  result.sample_data[i].y = uv.y;
986  j += 2;
987  }
988  }
989 
990  return result;
991 }
992 
993 } // namespace impeller
virtual bool SupportsDecalSamplerAddressMode() const =0
Whether the context backend supports SamplerAddressMode::Decal.
const Capabilities & GetDeviceCapabilities() const
std::function< bool(const ContentContext &, RenderPass &)> SubpassCallback
std::shared_ptr< Context > GetContext() const
static std::shared_ptr< Contents > MakeAnonymous(RenderProc render_proc, CoverageProc coverage_proc)
Definition: contents.cc:41
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:60
BlendMode GetBlendMode() const
Definition: entity.cc:101
Entity Clone() const
Definition: entity.cc:158
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:44
static Entity FromSnapshot(const Snapshot &snapshot, BlendMode blend_mode=BlendMode::kSrcOver)
Create an entity that can be used to render a given snapshot.
Definition: entity.cc:18
@ kNormal
Blurred inside and outside.
@ kOuter
Nothing inside, blurred outside.
@ kInner
Blurred inside, nothing outside.
@ kSolid
Solid inside, blurred outside.
std::vector< FilterInput::Ref > Vector
Definition: filter_input.h:33
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...
std::optional< Rect > GetFilterCoverage(const FilterInput::Vector &inputs, const Entity &entity, const Matrix &effect_transform) const override
Internal utility method for |GetLocalCoverage| that computes the output coverage of this filter acros...
static Quad CalculateUVs(const std::shared_ptr< FilterInput > &filter_input, const Entity &entity, const Rect &source_rect, const ISize &texture_size)
FragmentShader_ FragmentShader
Definition: pipeline.h:164
int32_t x
Vector2 local_padding
Padding in unrotated local space.
Vector2 effective_scalar
Vector2 blur_radius
Blur radius in source pixels based on scaled_sigma.
ISize subpass_size
The output size of the down-sampling pass.
Vector2 source_space_offset
Vector2 source_space_scalar
The scalar that is used to get from source space to unrotated local space.
Vector2 padding
The halo padding in source space.
Vector2 scaled_sigma
Sigma when considering an entity's scale and the effect transform.
Point Vector2
Definition: point.h:331
static constexpr int32_t kGaussianBlurMaxKernelSize
float Scalar
Definition: scalar.h:19
SamplerAddressMode
Definition: formats.h:441
@ kDecal
decal sampling mode is only supported on devices that pass the Capabilities.SupportsDecalSamplerAddre...
constexpr float kEhCloseEnough
Definition: constants.h:57
TRect< Scalar > Rect
Definition: rect.h:792
TPoint< Scalar > Point
Definition: point.h:327
VertexBuffer CreateVertexBuffer(std::array< VertexType, size > input, HostBuffer &host_buffer)
Create an index-less vertex buffer from a fixed size array.
TSize< Scalar > Size
Definition: size.h:159
LinePipeline::VertexShader VS
KernelSamples GenerateBlurInfo(BlurParameters parameters)
ContentContextOptions OptionsFromPass(const RenderPass &pass)
Definition: contents.cc:19
ISize64 ISize
Definition: size.h:162
MinMagFilter
Describes how the texture should be sampled when the texture is being shrunk (minified) or expanded (...
Definition: formats.h:415
std::array< Point, 4 > Quad
Definition: point.h:332
GaussianBlurPipeline::FragmentShader GaussianBlurFragmentShader
GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(KernelSamples parameters)
GaussianBlurPipeline::VertexShader GaussianBlurVertexShader
Definition: comparable.h:95
KernelSample samples[kMaxKernelSize]
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeOrthographic(TSize< T > size)
Definition: matrix.h:566
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
constexpr Matrix Basis() const
The Matrix without its w components (without translation).
Definition: matrix.h:239
Matrix Invert() const
Definition: matrix.cc:97
constexpr Quad Transform(const Quad &quad) const
Definition: matrix.h:556
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition: sigma.h:48
SamplerAddressMode width_address_mode
SamplerAddressMode height_address_mode
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
constexpr Type GetLength() const
Definition: point.h:206
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:622
constexpr static TRect MakeOriginSize(const TPoint< Type > &origin, const TSize< Type > &size)
Definition: rect.h:144
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr std::array< TPoint< T >, 4 > GetTransformedPoints(const Matrix &transform) const
Definition: rect.h:430
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:150
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
Type height
Definition: size.h:29
Type width
Definition: size.h:28