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  } else {
382  // This assumes we don't scale below 1/16.
383  Scalar edge = 1.0;
384  Scalar ratio = 0.25;
385  if (pass_args.effective_scalar.x <= 0.0625f) {
386  edge = 7.0;
387  ratio = 1.0f / 64.0f;
388  } else if (pass_args.effective_scalar.x <= 0.125f) {
389  edge = 3.0;
390  ratio = 1.0f / 16.0f;
391  }
392  ContentContext::SubpassCallback subpass_callback =
393  [&](const ContentContext& renderer, RenderPass& pass) {
394  HostBuffer& host_buffer = renderer.GetTransientsBuffer();
395 
396  pass.SetCommandLabel("Gaussian blur downsample");
397  auto pipeline_options = OptionsFromPass(pass);
398  pipeline_options.primitive_type = PrimitiveType::kTriangleStrip;
399 #ifdef IMPELLER_ENABLE_OPENGLES
400  // The GLES backend conditionally supports decal tile mode, while
401  // decal is always supported for Vulkan and Metal.
402  if (renderer.GetDeviceCapabilities()
403  .SupportsDecalSamplerAddressMode() ||
404  tile_mode != Entity::TileMode::kDecal) {
405  pass.SetPipeline(renderer.GetDownsamplePipeline(pipeline_options));
406  } else {
407  pass.SetPipeline(
408  renderer.GetDownsampleTextureGlesPipeline(pipeline_options));
409  }
410 #else
411  pass.SetPipeline(renderer.GetDownsamplePipeline(pipeline_options));
412 #endif // IMPELLER_ENABLE_OPENGLES
413 
414  TextureFillVertexShader::FrameInfo frame_info;
415  frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1));
416  frame_info.texture_sampler_y_coord_scale =
417  input_texture->GetYCoordScale();
418 
419  TextureDownsampleFragmentShader::FragInfo frag_info;
420  frag_info.edge = edge;
421  frag_info.ratio = ratio;
422  frag_info.pixel_size = Vector2(1.0f / Size(input_texture->GetSize()));
423 
424  const Quad& uvs = pass_args.uvs;
425  std::array<VS::PerVertexData, 4> vertices = {
426  VS::PerVertexData{Point(0, 0), uvs[0]},
427  VS::PerVertexData{Point(1, 0), uvs[1]},
428  VS::PerVertexData{Point(0, 1), uvs[2]},
429  VS::PerVertexData{Point(1, 1), uvs[3]},
430  };
431  pass.SetVertexBuffer(CreateVertexBuffer(vertices, host_buffer));
432 
433  SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
434  SetTileMode(&linear_sampler_descriptor, renderer, tile_mode);
435  linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
436  linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
437  TextureFillVertexShader::BindFrameInfo(
438  pass, host_buffer.EmplaceUniform(frame_info));
439  TextureDownsampleFragmentShader::BindFragInfo(
440  pass, host_buffer.EmplaceUniform(frag_info));
441  TextureDownsampleFragmentShader::BindTextureSampler(
442  pass, input_texture,
443  renderer.GetContext()->GetSamplerLibrary()->GetSampler(
444  linear_sampler_descriptor));
445 
446  return pass.Draw().ok();
447  };
448  return renderer.MakeSubpass("Gaussian Blur Filter", pass_args.subpass_size,
449  command_buffer, subpass_callback);
450  }
451 }
452 
453 fml::StatusOr<RenderTarget> MakeBlurSubpass(
454  const ContentContext& renderer,
455  const std::shared_ptr<CommandBuffer>& command_buffer,
456  const RenderTarget& input_pass,
457  const SamplerDescriptor& sampler_descriptor,
458  Entity::TileMode tile_mode,
459  const BlurParameters& blur_info,
460  std::optional<RenderTarget> destination_target,
461  const Quad& blur_uvs) {
463 
464  if (blur_info.blur_sigma < kEhCloseEnough) {
465  return input_pass;
466  }
467 
468  const std::shared_ptr<Texture>& input_texture =
469  input_pass.GetRenderTargetTexture();
470 
471  // TODO(gaaclarke): This blurs the whole image, but because we know the clip
472  // region we could focus on just blurring that.
473  ISize subpass_size = input_texture->GetSize();
474  ContentContext::SubpassCallback subpass_callback =
475  [&](const ContentContext& renderer, RenderPass& pass) {
476  GaussianBlurVertexShader::FrameInfo frame_info;
477  frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)),
478  frame_info.texture_sampler_y_coord_scale =
479  input_texture->GetYCoordScale();
480 
481  HostBuffer& host_buffer = renderer.GetTransientsBuffer();
482 
483  ContentContextOptions options = OptionsFromPass(pass);
484  options.primitive_type = PrimitiveType::kTriangleStrip;
485  pass.SetPipeline(renderer.GetGaussianBlurPipeline(options));
486 
487  std::array<VS::PerVertexData, 4> vertices = {
488  VS::PerVertexData{blur_uvs[0], blur_uvs[0]},
489  VS::PerVertexData{blur_uvs[1], blur_uvs[1]},
490  VS::PerVertexData{blur_uvs[2], blur_uvs[2]},
491  VS::PerVertexData{blur_uvs[3], blur_uvs[3]},
492  };
493  pass.SetVertexBuffer(CreateVertexBuffer(vertices, host_buffer));
494 
495  SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
496  linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
497  linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
498  GaussianBlurFragmentShader::BindTextureSampler(
499  pass, input_texture,
500  renderer.GetContext()->GetSamplerLibrary()->GetSampler(
501  linear_sampler_descriptor));
502  GaussianBlurVertexShader::BindFrameInfo(
503  pass, host_buffer.EmplaceUniform(frame_info));
504  GaussianBlurFragmentShader::BindKernelSamples(
505  pass, host_buffer.EmplaceUniform(
507  return pass.Draw().ok();
508  };
509  if (destination_target.has_value()) {
510  return renderer.MakeSubpass("Gaussian Blur Filter",
511  destination_target.value(), command_buffer,
512  subpass_callback);
513  } else {
514  return renderer.MakeSubpass("Gaussian Blur Filter", subpass_size,
515  command_buffer, subpass_callback);
516  }
517 }
518 
519 int ScaleBlurRadius(Scalar radius, Scalar scalar) {
520  return static_cast<int>(std::round(radius * scalar));
521 }
522 
523 Entity ApplyClippedBlurStyle(Entity::ClipOperation clip_operation,
524  const Entity& entity,
525  const std::shared_ptr<FilterInput>& input,
526  const Snapshot& input_snapshot,
527  Entity blur_entity,
528  const Geometry* geometry) {
529  Matrix entity_transform = entity.GetTransform();
530  Matrix blur_transform = blur_entity.GetTransform();
531 
532  auto renderer =
533  fml::MakeCopyable([blur_entity = blur_entity.Clone(), clip_operation,
534  entity_transform, blur_transform, geometry](
535  const ContentContext& renderer,
536  const Entity& entity, RenderPass& pass) mutable {
537  Entity clipper;
538  clipper.SetClipDepth(entity.GetClipDepth());
539  clipper.SetTransform(entity.GetTransform() * entity_transform);
540 
541  auto geom_result = geometry->GetPositionBuffer(renderer, clipper, pass);
542 
543  ClipContents clip_contents(geometry->GetCoverage(clipper.GetTransform())
544  .value_or(Rect::MakeLTRB(0, 0, 0, 0)),
545  /*is_axis_aligned_rect=*/false);
546  clip_contents.SetClipOperation(clip_operation);
547  clip_contents.SetGeometry(std::move(geom_result));
548 
549  if (!clip_contents.Render(renderer, pass, entity.GetClipDepth())) {
550  return false;
551  }
552  blur_entity.SetClipDepth(entity.GetClipDepth());
553  blur_entity.SetTransform(entity.GetTransform() * blur_transform);
554 
555  return blur_entity.Render(renderer, pass);
556  });
557  auto coverage =
558  fml::MakeCopyable([blur_entity = std::move(blur_entity),
559  blur_transform](const Entity& entity) mutable {
560  blur_entity.SetTransform(entity.GetTransform() * blur_transform);
561  return blur_entity.GetCoverage();
562  });
563  Entity result;
564  result.SetContents(Contents::MakeAnonymous(renderer, coverage));
565  return result;
566 }
567 
568 Entity ApplyBlurStyle(FilterContents::BlurStyle blur_style,
569  const Entity& entity,
570  const std::shared_ptr<FilterInput>& input,
571  const Snapshot& input_snapshot,
572  Entity blur_entity,
573  const Geometry* geometry,
576  switch (blur_style) {
578  return blur_entity;
580  return ApplyClippedBlurStyle(Entity::ClipOperation::kIntersect, entity,
581  input, input_snapshot,
582  std::move(blur_entity), geometry);
583  break;
585  return ApplyClippedBlurStyle(Entity::ClipOperation::kDifference, entity,
586  input, input_snapshot,
587  std::move(blur_entity), geometry);
589  Entity snapshot_entity =
590  Entity::FromSnapshot(input_snapshot, entity.GetBlendMode());
591  Entity result;
592  Matrix blurred_transform = blur_entity.GetTransform();
593  Matrix snapshot_transform =
594  entity.GetTransform() * //
597  input_snapshot.transform;
598  result.SetContents(Contents::MakeAnonymous(
599  fml::MakeCopyable([blur_entity = blur_entity.Clone(),
600  blurred_transform, snapshot_transform,
601  snapshot_entity = std::move(snapshot_entity)](
602  const ContentContext& renderer,
603  const Entity& entity,
604  RenderPass& pass) mutable {
605  snapshot_entity.SetTransform(entity.GetTransform() *
606  snapshot_transform);
607  snapshot_entity.SetClipDepth(entity.GetClipDepth());
608  if (!snapshot_entity.Render(renderer, pass)) {
609  return false;
610  }
611  blur_entity.SetClipDepth(entity.GetClipDepth());
612  blur_entity.SetTransform(entity.GetTransform() * blurred_transform);
613  return blur_entity.Render(renderer, pass);
614  }),
615  fml::MakeCopyable([blur_entity = blur_entity.Clone(),
616  blurred_transform](const Entity& entity) mutable {
617  blur_entity.SetTransform(entity.GetTransform() * blurred_transform);
618  return blur_entity.GetCoverage();
619  })));
620  return result;
621  }
622  }
623 }
624 } // namespace
625 
626 GaussianBlurFilterContents::GaussianBlurFilterContents(
627  Scalar sigma_x,
628  Scalar sigma_y,
629  Entity::TileMode tile_mode,
630  BlurStyle mask_blur_style,
631  const Geometry* mask_geometry)
632  : sigma_(sigma_x, sigma_y),
633  tile_mode_(tile_mode),
634  mask_blur_style_(mask_blur_style),
635  mask_geometry_(mask_geometry) {
636  // This is supposed to be enforced at a higher level.
637  FML_DCHECK(mask_blur_style == BlurStyle::kNormal || mask_geometry);
638 }
639 
640 // This value was extracted from Skia, see:
641 // * https://github.com/google/skia/blob/d29cc3fe182f6e8a8539004a6a4ee8251677a6fd/src/gpu/ganesh/GrBlurUtils.cpp#L2561-L2576
642 // * https://github.com/google/skia/blob/d29cc3fe182f6e8a8539004a6a4ee8251677a6fd/src/gpu/BlurUtils.h#L57
644  if (sigma <= 4) {
645  return 1.0;
646  }
647  Scalar raw_result = 4.0 / sigma;
648  // Round to the nearest 1/(2^n) to get the best quality down scaling.
649  Scalar exponent = round(log2f(raw_result));
650  // Don't scale down below 1/16th to preserve signal.
651  exponent = std::max(-4.0f, exponent);
652  Scalar rounded = powf(2.0f, exponent);
653  Scalar result = rounded;
654  // Extend the range of the 1/8th downsample based on the effective kernel size
655  // for the blur.
656  if (rounded < 0.125f) {
657  Scalar rounded_plus = powf(2.0f, exponent + 1);
659  int kernel_size_plus = (ScaleBlurRadius(blur_radius, rounded_plus) * 2) + 1;
660  // This constant was picked by looking at the results to make sure no
661  // shimmering was introduced at the highest sigma values that downscale to
662  // 1/16th.
663  static constexpr int32_t kEighthDownsampleKernalWidthMax = 41;
664  result = kernel_size_plus <= kEighthDownsampleKernalWidthMax ? rounded_plus
665  : rounded;
666  }
667  return result;
668 };
669 
671  const Matrix& effect_transform,
672  const Rect& output_limit) const {
673  Vector2 scaled_sigma = {ScaleSigma(sigma_.x), ScaleSigma(sigma_.y)};
676  Vector3 blur_radii =
677  effect_transform.Basis() * Vector3{blur_radius.x, blur_radius.y, 0.0};
678  return output_limit.Expand(Point(blur_radii.x, blur_radii.y));
679 }
680 
682  const FilterInput::Vector& inputs,
683  const Entity& entity,
684  const Matrix& effect_transform) const {
685  if (inputs.empty()) {
686  return {};
687  }
688  std::optional<Rect> input_coverage = inputs[0]->GetCoverage(entity);
689  if (!input_coverage.has_value()) {
690  return {};
691  }
692 
693  BlurInfo blur_info = CalculateBlurInfo(entity, effect_transform, sigma_);
694  return input_coverage.value().Expand(
695  Point(blur_info.local_padding.x, blur_info.local_padding.y));
696 }
697 
698 // A brief overview how this works:
699 // 1) Snapshot the filter input.
700 // 2) Perform downsample pass. This also inserts the gutter around the input
701 // snapshot since the blur can render outside the bounds of the snapshot.
702 // 3) Perform 1D horizontal blur pass.
703 // 4) Perform 1D vertical blur pass.
704 // 5) Apply the blur style to the blur result. This may just mask the output or
705 // draw the original snapshot over the result.
706 std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
707  const FilterInput::Vector& inputs,
708  const ContentContext& renderer,
709  const Entity& entity,
710  const Matrix& effect_transform,
711  const Rect& coverage,
712  const std::optional<Rect>& coverage_hint) const {
713  if (inputs.empty()) {
714  return std::nullopt;
715  }
716 
717  BlurInfo blur_info = CalculateBlurInfo(entity, effect_transform, sigma_);
718 
719  // Apply as much of the desired padding as possible from the source. This may
720  // be ignored so must be accounted for in the downsample pass by adding a
721  // transparent gutter.
722  std::optional<Rect> expanded_coverage_hint;
723  if (coverage_hint.has_value()) {
724  expanded_coverage_hint = coverage_hint->Expand(blur_info.local_padding);
725  }
726 
727  Entity snapshot_entity = entity.Clone();
728  snapshot_entity.SetTransform(
729  Matrix::MakeTranslation(blur_info.source_space_offset) *
730  Matrix::MakeScale(blur_info.source_space_scalar));
731 
732  std::optional<Rect> source_expanded_coverage_hint;
733  if (expanded_coverage_hint.has_value()) {
734  source_expanded_coverage_hint = expanded_coverage_hint->TransformBounds(
735  Matrix::MakeTranslation(blur_info.source_space_offset) *
736  Matrix::MakeScale(blur_info.source_space_scalar) *
737  entity.GetTransform().Invert());
738  }
739 
740  std::optional<Snapshot> input_snapshot = GetSnapshot(
741  inputs[0], renderer, snapshot_entity, source_expanded_coverage_hint);
742  if (!input_snapshot.has_value()) {
743  return std::nullopt;
744  }
745 
746  if (blur_info.scaled_sigma.x < kEhCloseEnough &&
747  blur_info.scaled_sigma.y < kEhCloseEnough) {
748  Entity result =
749  Entity::FromSnapshot(input_snapshot.value(),
750  entity.GetBlendMode()); // No blur to render.
751  result.SetTransform(
752  entity.GetTransform() *
753  Matrix::MakeScale(1.f / blur_info.source_space_scalar) *
754  Matrix::MakeTranslation(-1 * blur_info.source_space_offset) *
755  input_snapshot->transform);
756  return result;
757  }
758 
759  // Note: The code below uses three different command buffers when it would be
760  // possible to combine the operations into a single buffer. From testing and
761  // user bug reports (see https://github.com/flutter/flutter/issues/154046 ),
762  // this sometimes causes deviceLost errors on older Adreno devices. Breaking
763  // the work up into three different command buffers seems to prevent this
764  // crash.
765  std::shared_ptr<CommandBuffer> command_buffer_1 =
766  renderer.GetContext()->CreateCommandBuffer();
767  if (!command_buffer_1) {
768  return std::nullopt;
769  }
770 
771  DownsamplePassArgs downsample_pass_args = CalculateDownsamplePassArgs(
772  blur_info.scaled_sigma, blur_info.padding, input_snapshot.value(),
773  source_expanded_coverage_hint, inputs[0], snapshot_entity);
774 
775  fml::StatusOr<RenderTarget> pass1_out = MakeDownsampleSubpass(
776  renderer, command_buffer_1, input_snapshot->texture,
777  input_snapshot->sampler_descriptor, downsample_pass_args, tile_mode_);
778 
779  if (!pass1_out.ok()) {
780  return std::nullopt;
781  }
782 
783  Vector2 pass1_pixel_size =
784  1.0 / Vector2(pass1_out.value().GetRenderTargetTexture()->GetSize());
785 
786  Quad blur_uvs = {Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)};
787 
788  std::shared_ptr<CommandBuffer> command_buffer_2 =
789  renderer.GetContext()->CreateCommandBuffer();
790  if (!command_buffer_2) {
791  return std::nullopt;
792  }
793 
794  fml::StatusOr<RenderTarget> pass2_out = MakeBlurSubpass(
795  renderer, command_buffer_2, /*input_pass=*/pass1_out.value(),
796  input_snapshot->sampler_descriptor, tile_mode_,
797  BlurParameters{
798  .blur_uv_offset = Point(0.0, pass1_pixel_size.y),
799  .blur_sigma = blur_info.scaled_sigma.y *
800  downsample_pass_args.effective_scalar.y,
801  .blur_radius = ScaleBlurRadius(
802  blur_info.blur_radius.y, downsample_pass_args.effective_scalar.y),
803  .step_size = 1,
804  },
805  /*destination_target=*/std::nullopt, blur_uvs);
806 
807  if (!pass2_out.ok()) {
808  return std::nullopt;
809  }
810 
811  std::shared_ptr<CommandBuffer> command_buffer_3 =
812  renderer.GetContext()->CreateCommandBuffer();
813  if (!command_buffer_3) {
814  return std::nullopt;
815  }
816 
817  // Only ping pong if the first pass actually created a render target.
818  auto pass3_destination = pass2_out.value().GetRenderTargetTexture() !=
819  pass1_out.value().GetRenderTargetTexture()
820  ? std::optional<RenderTarget>(pass1_out.value())
821  : std::optional<RenderTarget>(std::nullopt);
822 
823  fml::StatusOr<RenderTarget> pass3_out = MakeBlurSubpass(
824  renderer, command_buffer_3, /*input_pass=*/pass2_out.value(),
825  input_snapshot->sampler_descriptor, tile_mode_,
826  BlurParameters{
827  .blur_uv_offset = Point(pass1_pixel_size.x, 0.0),
828  .blur_sigma = blur_info.scaled_sigma.x *
829  downsample_pass_args.effective_scalar.x,
830  .blur_radius = ScaleBlurRadius(
831  blur_info.blur_radius.x, downsample_pass_args.effective_scalar.x),
832  .step_size = 1,
833  },
834  pass3_destination, blur_uvs);
835 
836  if (!pass3_out.ok()) {
837  return std::nullopt;
838  }
839 
840  if (!(renderer.GetContext()->EnqueueCommandBuffer(
841  std::move(command_buffer_1)) &&
842  renderer.GetContext()->EnqueueCommandBuffer(
843  std::move(command_buffer_2)) &&
844  renderer.GetContext()->EnqueueCommandBuffer(
845  std::move(command_buffer_3)))) {
846  return std::nullopt;
847  }
848 
849  // The ping-pong approach requires that each render pass output has the same
850  // size.
851  FML_DCHECK((pass1_out.value().GetRenderTargetSize() ==
852  pass2_out.value().GetRenderTargetSize()) &&
853  (pass2_out.value().GetRenderTargetSize() ==
854  pass3_out.value().GetRenderTargetSize()));
855 
856  SamplerDescriptor sampler_desc = MakeSamplerDescriptor(
858 
859  Entity blur_output_entity = Entity::FromSnapshot(
860  Snapshot{.texture = pass3_out.value().GetRenderTargetTexture(),
861  .transform =
862  entity.GetTransform() * //
863  Matrix::MakeScale(1.f / blur_info.source_space_scalar) * //
864  Matrix::MakeTranslation(-1 * blur_info.source_space_offset) *
865  downsample_pass_args.transform * //
866  Matrix::MakeScale(1 / downsample_pass_args.effective_scalar),
867  .sampler_descriptor = sampler_desc,
868  .opacity = input_snapshot->opacity},
869  entity.GetBlendMode());
870 
871  return ApplyBlurStyle(mask_blur_style_, entity, inputs[0],
872  input_snapshot.value(), std::move(blur_output_entity),
873  mask_geometry_, blur_info.source_space_scalar,
874  blur_info.source_space_offset);
875 }
876 
878  return static_cast<Radius>(Sigma(sigma)).radius;
879 }
880 
882  const std::shared_ptr<FilterInput>& filter_input,
883  const Entity& entity,
884  const Rect& source_rect,
885  const ISize& texture_size) {
886  Matrix input_transform = filter_input->GetLocalTransform(entity);
887  Quad coverage_quad = source_rect.GetTransformedPoints(input_transform);
888 
889  Matrix uv_transform = Matrix::MakeScale(
890  {1.0f / texture_size.width, 1.0f / texture_size.height, 1.0f});
891  return uv_transform.Transform(coverage_quad);
892 }
893 
894 // This function was calculated by observing Skia's behavior. Its blur at 500
895 // seemed to be 0.15. Since we clamp at 500 I solved the quadratic equation
896 // that puts the minima there and a f(0)=1.
898  // Limit the kernel size to 1000x1000 pixels, like Skia does.
899  Scalar clamped = std::min(sigma, kMaxSigma);
900  constexpr Scalar a = 3.4e-06;
901  constexpr Scalar b = -3.4e-3;
902  constexpr Scalar c = 1.f;
903  Scalar scalar = c + b * clamped + a * clamped * clamped;
904  return clamped * scalar;
905 }
906 
908  KernelSamples result;
909  result.sample_count =
910  ((2 * parameters.blur_radius) / parameters.step_size) + 1;
911 
912  // Chop off the last samples if the radius >= 3 where they account for < 1.56%
913  // of the result.
914  int x_offset = 0;
915  if (parameters.blur_radius >= 3) {
916  result.sample_count -= 2;
917  x_offset = 1;
918  }
919 
920  // This is a safe-guard to make sure we don't overflow the fragment shader.
921  // The kernel size is multiplied by 2 since we'll use the lerp hack on the
922  // result. In practice this isn't throwing away much data since the blur radii
923  // are around 53 before the down-sampling and max sigma of 500 kick in.
924  //
925  // TODO(https://github.com/flutter/flutter/issues/150462): Come up with a more
926  // wholistic remedy for this. A proper downsample size should not make this
927  // required. Or we can increase the kernel size.
930  }
931 
932  Scalar tally = 0.0f;
933  for (int i = 0; i < result.sample_count; ++i) {
934  int x = x_offset + (i * parameters.step_size) - parameters.blur_radius;
935  result.samples[i] = KernelSample{
936  .uv_offset = parameters.blur_uv_offset * x,
937  .coefficient = expf(-0.5f * (x * x) /
938  (parameters.blur_sigma * parameters.blur_sigma)) /
939  (sqrtf(2.0f * M_PI) * parameters.blur_sigma),
940  };
941  tally += result.samples[i].coefficient;
942  }
943 
944  // Make sure everything adds up to 1.
945  for (auto& sample : result.samples) {
946  sample.coefficient /= tally;
947  }
948 
949  return result;
950 }
951 
952 // This works by shrinking the kernel size by 2 and relying on lerp to read
953 // between the samples.
954 GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(
955  KernelSamples parameters) {
956  GaussianBlurPipeline::FragmentShader::KernelSamples result = {};
957  result.sample_count = ((parameters.sample_count - 1) / 2) + 1;
958  int32_t middle = result.sample_count / 2;
959  int32_t j = 0;
960  FML_DCHECK(result.sample_count <= kGaussianBlurMaxKernelSize);
961  static_assert(sizeof(result.sample_data) ==
962  sizeof(std::array<Vector4, kGaussianBlurMaxKernelSize>));
963 
964  for (int i = 0; i < result.sample_count; i++) {
965  if (i == middle) {
966  result.sample_data[i].x = parameters.samples[j].uv_offset.x;
967  result.sample_data[i].y = parameters.samples[j].uv_offset.y;
968  result.sample_data[i].z = parameters.samples[j].coefficient;
969  j++;
970  } else {
971  KernelSample left = parameters.samples[j];
972  KernelSample right = parameters.samples[j + 1];
973 
974  result.sample_data[i].z = left.coefficient + right.coefficient;
975 
976  Point uv = (left.uv_offset * left.coefficient +
977  right.uv_offset * right.coefficient) /
978  (left.coefficient + right.coefficient);
979  result.sample_data[i].x = uv.x;
980  result.sample_data[i].y = uv.y;
981  j += 2;
982  }
983  }
984 
985  return result;
986 }
987 
988 } // 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
static Entity FromSnapshot(const Snapshot &snapshot, BlendMode blend_mode=BlendMode::kSourceOver)
Create an entity that can be used to render a given snapshot.
Definition: entity.cc:18
BlendMode GetBlendMode() const
Definition: entity.cc:101
Entity Clone() const
Definition: entity.cc:159
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:44
@ 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:114
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:18
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:56
TRect< Scalar > Rect
Definition: rect.h:792
GlyphAtlasPipeline::VertexShader VS
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:171
KernelSamples GenerateBlurInfo(BlurParameters parameters)
ContentContextOptions OptionsFromPass(const RenderPass &pass)
Definition: contents.cc:19
ISize64 ISize
Definition: size.h:174
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:553
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:543
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