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