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_downsample_bounded.frag.h"
15 #include "impeller/entity/texture_fill.frag.h"
16 #include "impeller/entity/texture_fill.vert.h"
20 
21 namespace impeller {
22 
25 
26 namespace {
27 
28 constexpr Scalar kMaxSigma = 500.0f;
29 
30 SamplerDescriptor MakeSamplerDescriptor(MinMagFilter filter,
31  SamplerAddressMode address_mode) {
32  SamplerDescriptor sampler_desc;
33  sampler_desc.min_filter = filter;
34  sampler_desc.mag_filter = filter;
35  sampler_desc.width_address_mode = address_mode;
36  sampler_desc.height_address_mode = address_mode;
37  return sampler_desc;
38 }
39 
40 void SetTileMode(SamplerDescriptor* descriptor,
41  const ContentContext& renderer,
42  Entity::TileMode tile_mode) {
43  switch (tile_mode) {
48  }
49  break;
53  break;
57  break;
61  break;
62  }
63 }
64 
65 Vector2 Clamp(Vector2 vec2, Scalar min, Scalar max) {
66  return Vector2(std::clamp(vec2.x, /*lo=*/min, /*hi=*/max),
67  std::clamp(vec2.y, /*lo=*/min, /*hi=*/max));
68 }
69 
70 Vector2 ExtractScale(const Matrix& matrix) {
71  Vector2 entity_scale_x = matrix * Vector2(1.0, 0.0);
72  Vector2 entity_scale_y = matrix * Vector2(0.0, 1.0);
73  return Vector2(entity_scale_x.GetLength(), entity_scale_y.GetLength());
74 }
75 
76 struct BlurInfo {
77  /// The scalar that is used to get from source space to unrotated local space.
79  /// The translation that is used to get from source space to unrotated local
80  /// space.
82  /// Sigma when considering an entity's scale and the effect transform.
84  /// Blur radius in source pixels based on scaled_sigma.
86  /// The halo padding in source space.
88  /// Padding in unrotated local space.
90 };
91 
92 /// Calculates sigma derivatives necessary for rendering or calculating
93 /// coverage.
94 BlurInfo CalculateBlurInfo(const Entity& entity,
95  const Matrix& effect_transform,
96  Vector2 sigma) {
97  // Source space here is scaled by the entity's transform. This is a
98  // requirement for text to be rendered correctly. You can think of this as
99  // "scaled source space" or "un-rotated local space". The entity's rotation is
100  // applied to the result of the blur as part of the result's transform.
102  ExtractScale(entity.GetTransform().Basis());
104  Vector2(entity.GetTransform().m[12], entity.GetTransform().m[13]);
105 
107  (effect_transform.Basis() * Matrix::MakeScale(source_space_scalar) * //
110  .Abs();
111  scaled_sigma = Clamp(scaled_sigma, 0, kMaxSigma);
115  Vector2 padding(ceil(blur_radius.x), ceil(blur_radius.y));
118  return {
119  .source_space_scalar = source_space_scalar,
120  .source_space_offset = source_space_offset,
121  .scaled_sigma = scaled_sigma,
122  .blur_radius = blur_radius,
123  .padding = padding,
124  .local_padding = local_padding,
125  };
126 }
127 
128 /// Perform FilterInput::GetSnapshot with safety checks.
129 std::optional<Snapshot> GetSnapshot(const std::shared_ptr<FilterInput>& input,
130  const ContentContext& renderer,
131  const Entity& entity,
132  const std::optional<Rect>& coverage_hint) {
133  std::optional<Snapshot> input_snapshot =
134  input->GetSnapshot("GaussianBlur", renderer, entity,
135  /*coverage_limit=*/coverage_hint);
136  if (!input_snapshot.has_value()) {
137  return std::nullopt;
138  }
139 
140  return input_snapshot;
141 }
142 
143 /// Returns `rect` relative to `reference`, where Rect::MakeXYWH(0,0,1,1) will
144 /// be returned when `rect` == `reference`.
145 Rect MakeReferenceUVs(const Rect& reference, const Rect& rect) {
146  Rect result = Rect::MakeOriginSize(rect.GetOrigin() - reference.GetOrigin(),
147  rect.GetSize());
148  return result.Scale(1.0f / Vector2(reference.GetSize()));
149 }
150 
151 Quad MakeReferenceUVs(const Rect& reference, const Quad& target_quad) {
152  Matrix transform =
153  Matrix::MakeScale(Vector3(1.0f / reference.GetWidth(),
154  1.0f / reference.GetHeight(), 1.0f)) *
156  Vector3(-reference.GetLeft(), -reference.GetTop(), 0));
157  return transform.Transform(target_quad);
158 }
159 
160 Quad CalculateSnapshotUVs(
161  const Snapshot& input_snapshot,
162  const std::optional<Rect>& source_expanded_coverage_hint) {
163  std::optional<Rect> input_snapshot_coverage = input_snapshot.GetCoverage();
164  Quad blur_uvs = {Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)};
165  FML_DCHECK(input_snapshot.transform.IsTranslationScaleOnly());
166  if (source_expanded_coverage_hint.has_value() &&
167  input_snapshot_coverage.has_value()) {
168  // Only process the uvs where the blur is happening, not the whole texture.
169  std::optional<Rect> uvs =
170  MakeReferenceUVs(input_snapshot_coverage.value(),
171  source_expanded_coverage_hint.value())
172  .Intersection(Rect::MakeSize(Size(1, 1)));
173  FML_DCHECK(uvs.has_value());
174  if (uvs.has_value()) {
175  blur_uvs[0] = uvs->GetLeftTop();
176  blur_uvs[1] = uvs->GetRightTop();
177  blur_uvs[2] = uvs->GetLeftBottom();
178  blur_uvs[3] = uvs->GetRightBottom();
179  }
180  }
181  return blur_uvs;
182 }
183 
184 Scalar CeilToDivisible(Scalar val, Scalar divisor) {
185  if (divisor == 0.0f) {
186  return val;
187  }
188 
189  Scalar remainder = fmod(val, divisor);
190  if (remainder != 0.0f) {
191  return val + (divisor - remainder);
192  } else {
193  return val;
194  }
195 }
196 
197 Scalar FloorToDivisible(Scalar val, Scalar divisor) {
198  if (divisor == 0.0f) {
199  return val;
200  }
201 
202  Scalar remainder = fmod(val, divisor);
203  if (remainder != 0.0f) {
204  return val - remainder;
205  } else {
206  return val;
207  }
208 }
209 
210 // If `y_coord_scale` < 0.0, the Y coordinate is flipped. This is useful
211 // for Impeller graphics backends that use a flipped framebuffer coordinate
212 // space.
213 //
214 // Both input and output quads are expected to be in the order of [Top-Left,
215 // Top-Right, Bottom-Left, Bottom-Right], a "Z-order" layout that conforms to
216 // the return format of Rect::GetPoints().
217 //
218 // See also: IPRemapCoords in convergions.glsl.
219 Quad RemapQuadCoords(const Quad& input, Scalar texture_sampler_y_coord_scale) {
220  if (texture_sampler_y_coord_scale < 0.0f) {
221  // The 4 points need reordering, because vertically flipping the quad:
222  //
223  // 0 → 1
224  // ↙
225  // 2 → 3
226  //
227  // should be flipped to:
228  //
229  // 2 → 3
230  // ↙
231  // 0 → 1
232  auto remap_point = [&](const Point& p) -> Point {
233  return Point(p.x, 1.0f - p.y);
234  };
235  return Quad{remap_point(input[2]), remap_point(input[3]),
236  remap_point(input[0]), remap_point(input[1])};
237  } else {
238  return input;
239  }
240 }
241 
242 // Precomputes the line equation parameters for a quadrilateral's bounds.
243 //
244 // This function takes an array of 4 vertices and returns an array of 4
245 // Vector4s. Each Vector4 represents the line equation (Ax + By + C = 0) for one
246 // edge of the quadrilateral.
247 //
248 // - Vector4.x stores 'A' (the x-component of the normal)
249 // - Vector4.y stores 'B' (the y-component of the normal)
250 // - Vector4.z stores 'C' (the distance/offset)
251 // - Vector4.w is padding.
252 //
253 // Packing the data this way allows calculating signed distance from the point
254 // to the line in GLSL with a single dot product: dot(vec3(point.xy, 1.0),
255 // lineParams). The signed distances from the point to all four edges can be
256 // used to determine whether the point is inside the quadrilateral.
257 //
258 // The `bounds` contains the 4 vertices of the quadrilateral, ordered as
259 // [Top-Left, Top-Right, Bottom-Left, Bottom-Right] (a "Z-order" layout,
260 // conforming to the return format of Rect::GetPoints()).
261 Matrix PrecomputeQuadLineParameters(const Quad& bounds) {
262  auto computeLine = [](const Point& p0, const Point& p1) -> Vector4 {
263  // We are deriving the 2D line equation Ax + By + C = 0.
264 
265  // The normal vector N = (A, B) is perpendicular to the line's
266  // direction vector V = p1 - p0 = (p1.x - p0.x, p1.y - p0.y)
267  // A 2D perpendicular (normal) is found by swapping V's components
268  // and negating one: N = (-(p1.y - p0.y), p1.x - p0.x)
269  // This is equivalent to the 2D components of the 3D cross product
270  // between the Z-axis (0,0,1) and V: (0,0,1) X (V.x, V.y, 0).
271  Scalar A = p0.y - p1.y; // -(p1.y - p0.y)
272  Scalar B = p1.x - p0.x;
273 
274  // The constant C is solved by ensuring the line passes through p0:
275  // A*p0.x + B*p0.y + C = 0
276  Scalar C = -(A * p0.x + B * p0.y);
277  return Vector4(A, B, C, 0.0);
278  };
279 
280  const Point& topLeft = bounds[0];
281  const Point& topRight = bounds[1];
282  const Point& botLeft = bounds[2];
283  const Point& botRight = bounds[3];
284 
285  Matrix result;
286  result.vec[0] = computeLine(topLeft, topRight); // Top
287  result.vec[1] = computeLine(topRight, botRight); // Right
288  result.vec[2] = computeLine(botRight, botLeft); // Bottom
289  result.vec[3] = computeLine(botLeft, topLeft); // Left
290  return result;
291 }
292 
293 struct DownsamplePassArgs {
294  /// The output size of the down-sampling pass.
296  /// The UVs that will be used for drawing to the down-sampling pass.
297  /// This effectively is chopping out a region of the input.
299  /// The bounds used for the downsampling pass of a bounded blur, in the same
300  /// UV space as the texture input of the downsampling pass.
301  ///
302  /// During downsampling, out-of-bound pixels are treated as transparent.
303  std::optional<Quad> uv_bounds;
304  /// The effective scalar of the down-sample pass.
305  /// This isn't usually exactly as we'd calculate because it has to be rounded
306  /// to integer boundaries for generating the texture for the output.
308  /// Transforms from unrotated local space to position the output from the
309  /// down-sample pass.
310  /// This can differ if we request a coverage hint but it is rejected, as is
311  /// the case with backdrop filters.
312  Matrix transform;
313 };
314 
315 /// Calculates info required for the down-sampling pass.
316 DownsamplePassArgs CalculateDownsamplePassArgs(
319  const Snapshot& input_snapshot,
320  const std::optional<Rect>& source_expanded_coverage_hint,
321  const std::optional<Quad>& source_bounds,
322  const std::shared_ptr<FilterInput>& input,
323  const Entity& snapshot_entity) {
324  Scalar desired_scalar =
327 
328  // TODO(jonahwilliams): If desired_scalar is 1.0 and we fully acquired the
329  // gutter from the expanded_coverage_hint, we can skip the downsample pass.
330  // pass.
331  Vector2 downsample_scalar(desired_scalar, desired_scalar);
332  // TODO(gaaclarke): The padding could be removed if we know it's not needed or
333  // resized to account for the expanded_clip_coverage. There doesn't appear
334  // to be the math to make those calculations though. The following
335  // optimization works, but causes a shimmer as a result of
336  // https://github.com/flutter/flutter/issues/140193 so it isn't applied.
337  //
338  // !input_snapshot->GetCoverage()->Expand(-local_padding)
339  // .Contains(coverage_hint.value()))
340 
341  std::optional<Rect> snapshot_coverage = input_snapshot.GetCoverage();
342  if (input_snapshot.transform.Equals(snapshot_entity.GetTransform()) &&
343  source_expanded_coverage_hint.has_value() &&
344  snapshot_coverage.has_value() &&
345  snapshot_coverage->Contains(source_expanded_coverage_hint.value())) {
346  // If the snapshot's transform is the identity transform and we have
347  // coverage hint that fits inside of the snapshots coverage that means the
348  // coverage hint was ignored so we will trim out the area we are interested
349  // in the down-sample pass. This usually means we have a backdrop image
350  // filter.
351  //
352  // The region we cut out will be aligned with the down-sample divisor to
353  // avoid pixel alignment problems that create shimmering.
354  int32_t divisor = std::round(1.0f / desired_scalar);
355  Rect aligned_coverage_hint = Rect::MakeLTRB(
356  FloorToDivisible(source_expanded_coverage_hint->GetLeft(), divisor),
357  FloorToDivisible(source_expanded_coverage_hint->GetTop(), divisor),
358  source_expanded_coverage_hint->GetRight(),
359  source_expanded_coverage_hint->GetBottom());
360  aligned_coverage_hint = Rect::MakeXYWH(
361  aligned_coverage_hint.GetX(), aligned_coverage_hint.GetY(),
362  CeilToDivisible(aligned_coverage_hint.GetWidth(), divisor),
363  CeilToDivisible(aligned_coverage_hint.GetHeight(), divisor));
364  ISize source_size = ISize(aligned_coverage_hint.GetSize().width,
365  aligned_coverage_hint.GetSize().height);
366  Vector2 downsampled_size = source_size * downsample_scalar;
367  Scalar int_part;
368  FML_DCHECK(std::modf(downsampled_size.x, &int_part) == 0.0f);
369  FML_DCHECK(std::modf(downsampled_size.y, &int_part) == 0.0f);
370  (void)int_part;
371  ISize subpass_size = ISize(downsampled_size.x, downsampled_size.y);
372  Vector2 effective_scalar = Vector2(subpass_size) / source_size;
373  FML_DCHECK(effective_scalar == downsample_scalar);
374 
375  Quad uvs = CalculateSnapshotUVs(input_snapshot, aligned_coverage_hint);
376  std::optional<Quad> uv_bounds;
377  if (source_bounds.has_value()) {
378  uv_bounds = MakeReferenceUVs(input_snapshot.GetCoverage().value(),
379  source_bounds.value());
380  }
381  return {
382  .subpass_size = subpass_size,
383  .uvs = uvs,
384  .uv_bounds = uv_bounds,
385  .effective_scalar = effective_scalar,
386  .transform = Matrix::MakeTranslation(
387  {aligned_coverage_hint.GetX(), aligned_coverage_hint.GetY(), 0})};
388  } else {
389  //////////////////////////////////////////////////////////////////////////////
390  auto input_snapshot_size = input_snapshot.texture->GetSize();
391  Rect source_rect = Rect::MakeSize(input_snapshot_size);
392  Rect source_rect_padded = source_rect.Expand(padding);
393  Vector2 downsampled_size = source_rect_padded.GetSize() * downsample_scalar;
395  ISize(ceil(downsampled_size.x), ceil(downsampled_size.y));
396  Vector2 divisible_size(CeilToDivisible(source_rect_padded.GetSize().width,
397  1.0 / downsample_scalar.x),
398  CeilToDivisible(source_rect_padded.GetSize().height,
399  1.0 / downsample_scalar.y));
400  // Only make the padding divisible if we already have padding. If we don't
401  // have padding adding more can add artifacts to hard blur edges.
402  Vector2 divisible_padding(
403  padding.x > 0
404  ? padding.x +
405  (divisible_size.x - source_rect_padded.GetSize().width) / 2.0
406  : 0.f,
407  padding.y > 0
408  ? padding.y +
409  (divisible_size.y - source_rect_padded.GetSize().height) / 2.0
410  : 0.f);
411  source_rect_padded = source_rect.Expand(divisible_padding);
412 
414  Vector2(subpass_size) / source_rect_padded.GetSize();
416  input, snapshot_entity, source_rect_padded, input_snapshot_size);
417  std::optional<Quad> uv_bounds;
418  if (source_bounds.has_value()) {
419  uv_bounds = MakeReferenceUVs(
420  source_rect,
421  input_snapshot.transform.Invert().Transform(source_bounds.value()));
422  }
423 
424  return {
425  .subpass_size = subpass_size,
426  .uvs = uvs,
427  .uv_bounds = uv_bounds,
428  .effective_scalar = effective_scalar,
429  .transform = input_snapshot.transform *
430  Matrix::MakeTranslation(-divisible_padding),
431  };
432  }
433 }
434 
435 /// Makes a subpass that will render the scaled down input and add the
436 /// transparent gutter required for the blur halo.
437 fml::StatusOr<RenderTarget> MakeDownsampleSubpass(
438  const ContentContext& renderer,
439  const std::shared_ptr<CommandBuffer>& command_buffer,
440  const std::shared_ptr<Texture>& input_texture,
441  const SamplerDescriptor& sampler_descriptor,
442  const DownsamplePassArgs& pass_args,
443  Entity::TileMode tile_mode) {
444  using VS = TextureFillVertexShader;
445 
446  // If the texture already had mip levels generated, then we can use the
447  // original downsample shader.
448  //
449  // Bounded blur must not use existing mip levels, since bounded blurs need to
450  // treat out-of-bounds pixels as transparent.
451  bool may_reuse_mipmap =
452  !pass_args.uv_bounds.has_value() &&
453  (pass_args.effective_scalar.x >= 0.5f ||
454  (!input_texture->NeedsMipmapGeneration() &&
455  input_texture->GetTextureDescriptor().mip_count > 1));
456  if (may_reuse_mipmap) {
457  ContentContext::SubpassCallback subpass_callback =
458  [&](const ContentContext& renderer, RenderPass& pass) {
459  HostBuffer& data_host_buffer = renderer.GetTransientsDataBuffer();
460 
461  pass.SetCommandLabel("Gaussian blur downsample");
462  auto pipeline_options = OptionsFromPass(pass);
463  pipeline_options.primitive_type = PrimitiveType::kTriangleStrip;
464  pass.SetPipeline(renderer.GetTexturePipeline(pipeline_options));
465 
466  TextureFillVertexShader::FrameInfo frame_info;
467  frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1));
468  frame_info.texture_sampler_y_coord_scale =
469  input_texture->GetYCoordScale();
470 
471  TextureFillFragmentShader::FragInfo frag_info;
472  frag_info.alpha = 1.0;
473 
474  const Quad& uvs = pass_args.uvs;
475  std::array<VS::PerVertexData, 4> vertices = {
476  VS::PerVertexData{Point(0, 0), uvs[0]},
477  VS::PerVertexData{Point(1, 0), uvs[1]},
478  VS::PerVertexData{Point(0, 1), uvs[2]},
479  VS::PerVertexData{Point(1, 1), uvs[3]},
480  };
481  pass.SetVertexBuffer(CreateVertexBuffer(vertices, data_host_buffer));
482 
483  SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
484  SetTileMode(&linear_sampler_descriptor, renderer, tile_mode);
485  linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
486  linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
487  TextureFillVertexShader::BindFrameInfo(
488  pass, data_host_buffer.EmplaceUniform(frame_info));
489  TextureFillFragmentShader::BindFragInfo(
490  pass, data_host_buffer.EmplaceUniform(frag_info));
491  TextureFillFragmentShader::BindTextureSampler(
492  pass, input_texture,
493  renderer.GetContext()->GetSamplerLibrary()->GetSampler(
494  linear_sampler_descriptor));
495 
496  return pass.Draw().ok();
497  };
498  return renderer.MakeSubpass("Gaussian Blur Filter", pass_args.subpass_size,
499  command_buffer, subpass_callback,
500  /*msaa_enabled=*/false,
501  /*depth_stencil_enabled=*/false);
502  } else {
503  // This assumes we don't scale below 1/16.
504  Scalar edge = 1.0;
505  Scalar ratio = 0.25;
506  if (pass_args.effective_scalar.x <= 0.0625f) {
507  edge = 7.0;
508  ratio = 1.0f / 64.0f;
509  } else if (pass_args.effective_scalar.x <= 0.125f) {
510  edge = 3.0;
511  ratio = 1.0f / 16.0f;
512  }
513  ContentContext::SubpassCallback subpass_callback =
514  [&](const ContentContext& renderer, RenderPass& pass) {
515  HostBuffer& data_host_buffer = renderer.GetTransientsDataBuffer();
516 
517  pass.SetCommandLabel("Gaussian blur downsample");
518  auto pipeline_options = OptionsFromPass(pass);
519  pipeline_options.primitive_type = PrimitiveType::kTriangleStrip;
520  if (pass_args.uv_bounds.has_value()) {
521  pass.SetPipeline(
522  renderer.GetDownsampleBoundedPipeline(pipeline_options));
523 
524  TextureDownsampleBoundedFragmentShader::BoundInfo bound_info;
525  bound_info.quad_line_params = PrecomputeQuadLineParameters(
526  RemapQuadCoords(pass_args.uv_bounds.value(),
527  input_texture->GetYCoordScale()));
528  TextureDownsampleBoundedFragmentShader::BindBoundInfo(
529  pass, data_host_buffer.EmplaceUniform(bound_info));
530  } else {
531 #ifdef IMPELLER_ENABLE_OPENGLES
532  // The GLES backend conditionally supports decal tile mode, while
533  // decal is always supported for Vulkan and Metal.
534  if (renderer.GetDeviceCapabilities()
535  .SupportsDecalSamplerAddressMode() ||
536  tile_mode != Entity::TileMode::kDecal) {
537  pass.SetPipeline(
538  renderer.GetDownsamplePipeline(pipeline_options));
539  } else {
540  pass.SetPipeline(
541  renderer.GetDownsampleTextureGlesPipeline(pipeline_options));
542  }
543 #else
544  pass.SetPipeline(renderer.GetDownsamplePipeline(pipeline_options));
545 #endif // IMPELLER_ENABLE_OPENGLES
546  }
547 
548  TextureFillVertexShader::FrameInfo frame_info;
549  frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1));
550  frame_info.texture_sampler_y_coord_scale =
551  input_texture->GetYCoordScale();
552 
553  TextureDownsampleFragmentShader::FragInfo frag_info;
554  frag_info.edge = edge;
555  frag_info.ratio = ratio;
556  frag_info.pixel_size = Vector2(1.0f / Size(input_texture->GetSize()));
557 
558  const Quad& uvs = pass_args.uvs;
559  std::array<VS::PerVertexData, 4> vertices = {
560  VS::PerVertexData{Point(0, 0), uvs[0]},
561  VS::PerVertexData{Point(1, 0), uvs[1]},
562  VS::PerVertexData{Point(0, 1), uvs[2]},
563  VS::PerVertexData{Point(1, 1), uvs[3]},
564  };
565  pass.SetVertexBuffer(CreateVertexBuffer(vertices, data_host_buffer));
566 
567  SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
568  SetTileMode(&linear_sampler_descriptor, renderer, tile_mode);
569  linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
570  linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
571  TextureFillVertexShader::BindFrameInfo(
572  pass, data_host_buffer.EmplaceUniform(frame_info));
573  TextureDownsampleFragmentShader::BindFragInfo(
574  pass, data_host_buffer.EmplaceUniform(frag_info));
575  TextureDownsampleFragmentShader::BindTextureSampler(
576  pass, input_texture,
577  renderer.GetContext()->GetSamplerLibrary()->GetSampler(
578  linear_sampler_descriptor));
579 
580  return pass.Draw().ok();
581  };
582  return renderer.MakeSubpass("Gaussian Blur Filter", pass_args.subpass_size,
583  command_buffer, subpass_callback,
584  /*msaa_enabled=*/false,
585  /*depth_stencil_enabled=*/false);
586  }
587 }
588 
589 fml::StatusOr<RenderTarget> MakeBlurSubpass(
590  const ContentContext& renderer,
591  const std::shared_ptr<CommandBuffer>& command_buffer,
592  const RenderTarget& input_pass,
593  const SamplerDescriptor& sampler_descriptor,
594  const BlurParameters& blur_info,
595  std::optional<RenderTarget> destination_target,
596  const Quad& blur_uvs) {
598 
599  if (blur_info.blur_sigma < kEhCloseEnough) {
600  return input_pass;
601  }
602 
603  const std::shared_ptr<Texture>& input_texture =
604  input_pass.GetRenderTargetTexture();
605 
606  // TODO(gaaclarke): This blurs the whole image, but because we know the clip
607  // region we could focus on just blurring that.
608  ISize subpass_size = input_texture->GetSize();
609  ContentContext::SubpassCallback subpass_callback =
610  [&](const ContentContext& renderer, RenderPass& pass) {
611  GaussianBlurVertexShader::FrameInfo frame_info;
612  frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)),
613  frame_info.texture_sampler_y_coord_scale =
614  input_texture->GetYCoordScale();
615 
616  HostBuffer& data_host_buffer = renderer.GetTransientsDataBuffer();
617 
618  ContentContextOptions options = OptionsFromPass(pass);
619  options.primitive_type = PrimitiveType::kTriangleStrip;
620  pass.SetPipeline(renderer.GetGaussianBlurPipeline(options));
621 
622  GaussianBlurFragmentShader::FragInfo frag_info;
623  frag_info.unpremultiply = blur_info.apply_unpremultiply;
624  GaussianBlurFragmentShader::BindFragInfo(
625  pass, data_host_buffer.EmplaceUniform(frag_info));
626 
627  std::array<VS::PerVertexData, 4> vertices = {
628  VS::PerVertexData{blur_uvs[0], blur_uvs[0]},
629  VS::PerVertexData{blur_uvs[1], blur_uvs[1]},
630  VS::PerVertexData{blur_uvs[2], blur_uvs[2]},
631  VS::PerVertexData{blur_uvs[3], blur_uvs[3]},
632  };
633  pass.SetVertexBuffer(CreateVertexBuffer(vertices, data_host_buffer));
634 
635  SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
636  linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
637  linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
638  GaussianBlurFragmentShader::BindTextureSampler(
639  pass, input_texture,
640  renderer.GetContext()->GetSamplerLibrary()->GetSampler(
641  linear_sampler_descriptor));
642  GaussianBlurVertexShader::BindFrameInfo(
643  pass, data_host_buffer.EmplaceUniform(frame_info));
644  GaussianBlurFragmentShader::BindKernelSamples(
645  pass, data_host_buffer.EmplaceUniform(
647  return pass.Draw().ok();
648  };
649  if (destination_target.has_value()) {
650  return renderer.MakeSubpass("Gaussian Blur Filter",
651  destination_target.value(), command_buffer,
652  subpass_callback);
653  } else {
654  return renderer.MakeSubpass(
655  "Gaussian Blur Filter", subpass_size, command_buffer, subpass_callback,
656  /*msaa_enabled=*/false, /*depth_stencil_enabled=*/false);
657  }
658 }
659 
660 int ScaleBlurRadius(Scalar radius, Scalar scalar) {
661  return static_cast<int>(std::round(radius * scalar));
662 }
663 
664 Entity ApplyClippedBlurStyle(Entity::ClipOperation clip_operation,
665  const Entity& entity,
666  const std::shared_ptr<FilterInput>& input,
667  const Snapshot& input_snapshot,
668  Entity blur_entity,
669  const Geometry* geometry) {
670  Matrix entity_transform = entity.GetTransform();
671  Matrix blur_transform = blur_entity.GetTransform();
672 
673  auto renderer =
674  fml::MakeCopyable([blur_entity = blur_entity.Clone(), clip_operation,
675  entity_transform, blur_transform, geometry](
676  const ContentContext& renderer,
677  const Entity& entity, RenderPass& pass) mutable {
678  Entity clipper;
679  clipper.SetClipDepth(entity.GetClipDepth());
680  clipper.SetTransform(entity.GetTransform() * entity_transform);
681 
682  auto geom_result = geometry->GetPositionBuffer(renderer, clipper, pass);
683 
684  ClipContents clip_contents(geometry->GetCoverage(clipper.GetTransform())
685  .value_or(Rect::MakeLTRB(0, 0, 0, 0)),
686  /*is_axis_aligned_rect=*/false);
687  clip_contents.SetClipOperation(clip_operation);
688  clip_contents.SetGeometry(std::move(geom_result));
689 
690  if (!clip_contents.Render(renderer, pass, entity.GetClipDepth())) {
691  return false;
692  }
693  blur_entity.SetClipDepth(entity.GetClipDepth());
694  blur_entity.SetTransform(entity.GetTransform() * blur_transform);
695 
696  return blur_entity.Render(renderer, pass);
697  });
698  auto coverage =
699  fml::MakeCopyable([blur_entity = std::move(blur_entity),
700  blur_transform](const Entity& entity) mutable {
701  blur_entity.SetTransform(entity.GetTransform() * blur_transform);
702  return blur_entity.GetCoverage();
703  });
704  Entity result;
705  result.SetContents(Contents::MakeAnonymous(renderer, coverage));
706  return result;
707 }
708 
709 Entity ApplyBlurStyle(FilterContents::BlurStyle blur_style,
710  const Entity& entity,
711  const std::shared_ptr<FilterInput>& input,
712  const Snapshot& input_snapshot,
713  Entity blur_entity,
714  const Geometry* geometry,
717  switch (blur_style) {
719  return blur_entity;
721  return ApplyClippedBlurStyle(Entity::ClipOperation::kIntersect, entity,
722  input, input_snapshot,
723  std::move(blur_entity), geometry);
724  break;
726  return ApplyClippedBlurStyle(Entity::ClipOperation::kDifference, entity,
727  input, input_snapshot,
728  std::move(blur_entity), geometry);
730  Entity snapshot_entity =
731  Entity::FromSnapshot(input_snapshot, entity.GetBlendMode());
732  Entity result;
733  Matrix blurred_transform = blur_entity.GetTransform();
734  Matrix snapshot_transform =
735  entity.GetTransform() * //
738  input_snapshot.transform;
739  result.SetContents(Contents::MakeAnonymous(
740  fml::MakeCopyable([blur_entity = blur_entity.Clone(),
741  blurred_transform, snapshot_transform,
742  snapshot_entity = std::move(snapshot_entity)](
743  const ContentContext& renderer,
744  const Entity& entity,
745  RenderPass& pass) mutable {
746  snapshot_entity.SetTransform(entity.GetTransform() *
747  snapshot_transform);
748  snapshot_entity.SetClipDepth(entity.GetClipDepth());
749  if (!snapshot_entity.Render(renderer, pass)) {
750  return false;
751  }
752  blur_entity.SetClipDepth(entity.GetClipDepth());
753  blur_entity.SetTransform(entity.GetTransform() * blurred_transform);
754  return blur_entity.Render(renderer, pass);
755  }),
756  fml::MakeCopyable([blur_entity = blur_entity.Clone(),
757  blurred_transform](const Entity& entity) mutable {
758  blur_entity.SetTransform(entity.GetTransform() * blurred_transform);
759  return blur_entity.GetCoverage();
760  })));
761  return result;
762  }
763  }
764 }
765 } // namespace
766 
767 GaussianBlurFilterContents::GaussianBlurFilterContents(
768  Scalar sigma_x,
769  Scalar sigma_y,
770  Entity::TileMode tile_mode,
771  std::optional<Rect> bounds,
772  BlurStyle mask_blur_style,
773  const Geometry* mask_geometry)
774  : sigma_(sigma_x, sigma_y),
775  tile_mode_(tile_mode),
776  bounds_(bounds),
777  mask_blur_style_(mask_blur_style),
778  mask_geometry_(mask_geometry) {
779  // This is supposed to be enforced at a higher level.
780  FML_DCHECK(mask_blur_style == BlurStyle::kNormal || mask_geometry);
781 }
782 
783 // This value was extracted from Skia, see:
784 // * https://github.com/google/skia/blob/d29cc3fe182f6e8a8539004a6a4ee8251677a6fd/src/gpu/ganesh/GrBlurUtils.cpp#L2561-L2576
785 // * https://github.com/google/skia/blob/d29cc3fe182f6e8a8539004a6a4ee8251677a6fd/src/gpu/BlurUtils.h#L57
787  if (sigma <= 4) {
788  return 1.0;
789  }
790  Scalar raw_result = 4.0 / sigma;
791  // Round to the nearest 1/(2^n) to get the best quality down scaling.
792  Scalar exponent = round(log2f(raw_result));
793  // Don't scale down below 1/16th to preserve signal.
794  exponent = std::max(-4.0f, exponent);
795  Scalar rounded = powf(2.0f, exponent);
796  Scalar result = rounded;
797  // Extend the range of the 1/8th downsample based on the effective kernel size
798  // for the blur.
799  if (rounded < 0.125f) {
800  Scalar rounded_plus = powf(2.0f, exponent + 1);
802  int kernel_size_plus = (ScaleBlurRadius(blur_radius, rounded_plus) * 2) + 1;
803  // This constant was picked by looking at the results to make sure no
804  // shimmering was introduced at the highest sigma values that downscale to
805  // 1/16th.
806  static constexpr int32_t kEighthDownsampleKernalWidthMax = 41;
807  result = kernel_size_plus <= kEighthDownsampleKernalWidthMax ? rounded_plus
808  : rounded;
809  }
810  return result;
811 };
812 
814  const Matrix& effect_transform,
815  const Rect& output_limit) const {
816  Vector2 scaled_sigma = {ScaleSigma(sigma_.x), ScaleSigma(sigma_.y)};
819  Vector3 blur_radii =
820  effect_transform.Basis() * Vector3{blur_radius.x, blur_radius.y, 0.0};
821  return output_limit.Expand(Point(blur_radii.x, blur_radii.y));
822 }
823 
825  const FilterInput::Vector& inputs,
826  const Entity& entity,
827  const Matrix& effect_transform) const {
828  if (inputs.empty()) {
829  return {};
830  }
831  std::optional<Rect> input_coverage = inputs[0]->GetCoverage(entity);
832  if (!input_coverage.has_value()) {
833  return {};
834  }
835 
836  BlurInfo blur_info = CalculateBlurInfo(entity, effect_transform, sigma_);
837  return input_coverage.value().Expand(
838  Point(blur_info.local_padding.x, blur_info.local_padding.y));
839 }
840 
841 std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
842  const FilterInput::Vector& inputs,
843  const ContentContext& renderer,
844  const Entity& entity,
845  const Matrix& effect_transform,
846  const Rect& coverage,
847  const std::optional<Rect>& coverage_hint) const {
848  if (inputs.empty()) {
849  return std::nullopt;
850  }
851 
852  BlurInfo blur_info = CalculateBlurInfo(entity, effect_transform, sigma_);
853 
854  // Apply as much of the desired padding as possible from the source. This may
855  // be ignored so must be accounted for in the downsample pass by adding a
856  // transparent gutter.
857  std::optional<Rect> expanded_coverage_hint;
858  if (coverage_hint.has_value()) {
859  expanded_coverage_hint = coverage_hint->Expand(blur_info.local_padding);
860  }
861 
862  Entity snapshot_entity = entity.Clone();
863  snapshot_entity.SetTransform(
864  Matrix::MakeTranslation(blur_info.source_space_offset) *
865  Matrix::MakeScale(blur_info.source_space_scalar));
866 
867  std::optional<Rect> source_expanded_coverage_hint;
868  if (expanded_coverage_hint.has_value()) {
869  source_expanded_coverage_hint = expanded_coverage_hint->TransformBounds(
870  snapshot_entity.GetTransform() * entity.GetTransform().Invert());
871  }
872 
873  std::optional<Snapshot> input_snapshot = GetSnapshot(
874  inputs[0], renderer, snapshot_entity, source_expanded_coverage_hint);
875  if (!input_snapshot.has_value()) {
876  return std::nullopt;
877  }
878 
879  std::optional<Quad> source_bounds;
880  if (bounds_.has_value()) {
881  Matrix transform = snapshot_entity.GetTransform() * effect_transform;
882  source_bounds = bounds_->GetTransformedPoints(transform);
883  }
884 
885  if (blur_info.scaled_sigma.x < kEhCloseEnough &&
886  blur_info.scaled_sigma.y < kEhCloseEnough) {
887  Entity result =
888  Entity::FromSnapshot(input_snapshot.value(),
889  entity.GetBlendMode()); // No blur to render.
890  result.SetTransform(
891  entity.GetTransform() *
892  Matrix::MakeScale(1.f / blur_info.source_space_scalar) *
893  Matrix::MakeTranslation(-1 * blur_info.source_space_offset) *
894  input_snapshot->transform);
895  return result;
896  }
897 
898  // Note: The code below uses three different command buffers when it would be
899  // possible to combine the operations into a single buffer. From testing and
900  // user bug reports (see https://github.com/flutter/flutter/issues/154046 ),
901  // this sometimes causes deviceLost errors on older Adreno devices. Breaking
902  // the work up into three different command buffers seems to prevent this
903  // crash.
904  std::shared_ptr<CommandBuffer> command_buffer_1 =
905  renderer.GetContext()->CreateCommandBuffer();
906  if (!command_buffer_1) {
907  return std::nullopt;
908  }
909 
910  DownsamplePassArgs downsample_pass_args = CalculateDownsamplePassArgs(
911  blur_info.scaled_sigma, blur_info.padding, input_snapshot.value(),
912  source_expanded_coverage_hint, source_bounds, inputs[0], snapshot_entity);
913 
914  fml::StatusOr<RenderTarget> pass1_out = MakeDownsampleSubpass(
915  renderer, command_buffer_1, input_snapshot->texture,
916  input_snapshot->sampler_descriptor, downsample_pass_args, tile_mode_);
917 
918  if (!pass1_out.ok()) {
919  return std::nullopt;
920  }
921 
922  Vector2 pass1_pixel_size =
923  1.0 / Vector2(pass1_out.value().GetRenderTargetTexture()->GetSize());
924 
925  Quad blur_uvs = {Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)};
926 
927  std::shared_ptr<CommandBuffer> command_buffer_2 =
928  renderer.GetContext()->CreateCommandBuffer();
929  if (!command_buffer_2) {
930  return std::nullopt;
931  }
932 
933  fml::StatusOr<RenderTarget> pass2_out = MakeBlurSubpass(
934  renderer, command_buffer_2, /*input_pass=*/pass1_out.value(),
935  input_snapshot->sampler_descriptor,
936  BlurParameters{
937  .blur_uv_offset = Point(0.0, pass1_pixel_size.y),
938  .blur_sigma = blur_info.scaled_sigma.y *
939  downsample_pass_args.effective_scalar.y,
940  .blur_radius = ScaleBlurRadius(
941  blur_info.blur_radius.y, downsample_pass_args.effective_scalar.y),
942  .step_size = 1,
943  .apply_unpremultiply = false,
944  },
945  /*destination_target=*/std::nullopt, blur_uvs);
946 
947  if (!pass2_out.ok()) {
948  return std::nullopt;
949  }
950 
951  std::shared_ptr<CommandBuffer> command_buffer_3 =
952  renderer.GetContext()->CreateCommandBuffer();
953  if (!command_buffer_3) {
954  return std::nullopt;
955  }
956 
957  // Only ping pong if the first pass actually created a render target.
958  auto pass3_destination = pass2_out.value().GetRenderTargetTexture() !=
959  pass1_out.value().GetRenderTargetTexture()
960  ? std::optional<RenderTarget>(pass1_out.value())
961  : std::optional<RenderTarget>(std::nullopt);
962 
963  fml::StatusOr<RenderTarget> pass3_out = MakeBlurSubpass(
964  renderer, command_buffer_3, /*input_pass=*/pass2_out.value(),
965  input_snapshot->sampler_descriptor,
966  BlurParameters{
967  .blur_uv_offset = Point(pass1_pixel_size.x, 0.0),
968  .blur_sigma = blur_info.scaled_sigma.x *
969  downsample_pass_args.effective_scalar.x,
970  .blur_radius = ScaleBlurRadius(
971  blur_info.blur_radius.x, downsample_pass_args.effective_scalar.x),
972  .step_size = 1,
973  .apply_unpremultiply = bounds_.has_value(),
974  },
975  pass3_destination, blur_uvs);
976 
977  if (!pass3_out.ok()) {
978  return std::nullopt;
979  }
980 
981  if (!(renderer.GetContext()->EnqueueCommandBuffer(
982  std::move(command_buffer_1)) &&
983  renderer.GetContext()->EnqueueCommandBuffer(
984  std::move(command_buffer_2)) &&
985  renderer.GetContext()->EnqueueCommandBuffer(
986  std::move(command_buffer_3)))) {
987  return std::nullopt;
988  }
989 
990  // The ping-pong approach requires that each render pass output has the same
991  // size.
992  FML_DCHECK((pass1_out.value().GetRenderTargetSize() ==
993  pass2_out.value().GetRenderTargetSize()) &&
994  (pass2_out.value().GetRenderTargetSize() ==
995  pass3_out.value().GetRenderTargetSize()));
996 
997  SamplerDescriptor sampler_desc = MakeSamplerDescriptor(
999 
1000  Entity blur_output_entity = Entity::FromSnapshot(
1001  Snapshot{.texture = pass3_out.value().GetRenderTargetTexture(),
1002  .transform =
1003  entity.GetTransform() * //
1004  Matrix::MakeScale(1.f / blur_info.source_space_scalar) * //
1005  Matrix::MakeTranslation(-1 * blur_info.source_space_offset) *
1006  downsample_pass_args.transform * //
1007  Matrix::MakeScale(1 / downsample_pass_args.effective_scalar),
1008  .sampler_descriptor = sampler_desc,
1009  .opacity = input_snapshot->opacity,
1010  .needs_rasterization_for_runtime_effects = true},
1011  entity.GetBlendMode());
1012 
1013  return ApplyBlurStyle(mask_blur_style_, entity, inputs[0],
1014  input_snapshot.value(), std::move(blur_output_entity),
1015  mask_geometry_, blur_info.source_space_scalar,
1016  blur_info.source_space_offset);
1017 }
1018 
1020  return static_cast<Radius>(Sigma(sigma)).radius;
1021 }
1022 
1024  const std::shared_ptr<FilterInput>& filter_input,
1025  const Entity& entity,
1026  const Rect& source_rect,
1027  const ISize& texture_size) {
1028  Matrix input_transform = filter_input->GetLocalTransform(entity);
1029  Quad coverage_quad = source_rect.GetTransformedPoints(input_transform);
1030 
1031  Matrix uv_transform = Matrix::MakeScale(
1032  {1.0f / texture_size.width, 1.0f / texture_size.height, 1.0f});
1033  return uv_transform.Transform(coverage_quad);
1034 }
1035 
1036 // This function was calculated by observing Skia's behavior. Its blur at 500
1037 // seemed to be 0.15. Since we clamp at 500 I solved the quadratic equation
1038 // that puts the minima there and a f(0)=1.
1040  // Limit the kernel size to 1000x1000 pixels, like Skia does.
1041  Scalar clamped = std::min(sigma, kMaxSigma);
1042  constexpr Scalar a = 3.4e-06;
1043  constexpr Scalar b = -3.4e-3;
1044  constexpr Scalar c = 1.f;
1045  Scalar scalar = c + b * clamped + a * clamped * clamped;
1046  return clamped * scalar;
1047 }
1048 
1050  KernelSamples result;
1051  result.sample_count =
1052  ((2 * parameters.blur_radius) / parameters.step_size) + 1;
1053 
1054  // Chop off the last samples if the radius >= 16 where they can account for
1055  // < 1.56% of the result.
1056  int x_offset = 0;
1057  if (parameters.blur_radius >= 16) {
1058  result.sample_count -= 2;
1059  x_offset = 1;
1060  }
1061 
1062  // This is a safe-guard to make sure we don't overflow the fragment shader.
1063  // The kernel size is multiplied by 2 since we'll use the lerp hack on the
1064  // result. In practice this isn't throwing away much data since the blur radii
1065  // are around 53 before the down-sampling and max sigma of 500 kick in.
1066  //
1067  // TODO(https://github.com/flutter/flutter/issues/150462): Come up with a more
1068  // wholistic remedy for this. A proper downsample size should not make this
1069  // required. Or we can increase the kernel size.
1072  }
1073 
1074  Scalar tally = 0.0f;
1075  for (int i = 0; i < result.sample_count; ++i) {
1076  int x = x_offset + (i * parameters.step_size) - parameters.blur_radius;
1077  result.samples[i] = KernelSample{
1078  .uv_offset = parameters.blur_uv_offset * x,
1079  .coefficient = expf(-0.5f * (x * x) /
1080  (parameters.blur_sigma * parameters.blur_sigma)) /
1081  (sqrtf(2.0f * M_PI) * parameters.blur_sigma),
1082  };
1083  tally += result.samples[i].coefficient;
1084  }
1085 
1086  // Make sure everything adds up to 1.
1087  for (auto& sample : result.samples) {
1088  sample.coefficient /= tally;
1089  }
1090 
1091  return result;
1092 }
1093 
1094 // This works by shrinking the kernel size by 2 and relying on lerp to read
1095 // between the samples.
1096 GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(
1097  KernelSamples parameters) {
1098  GaussianBlurPipeline::FragmentShader::KernelSamples result = {};
1099  result.sample_count = ((parameters.sample_count - 1) / 2) + 1;
1100  int32_t middle = result.sample_count / 2;
1101  int32_t j = 0;
1102  FML_DCHECK(result.sample_count <= kGaussianBlurMaxKernelSize);
1103  static_assert(sizeof(result.sample_data) ==
1104  sizeof(std::array<Vector4, kGaussianBlurMaxKernelSize>));
1105 
1106  for (int i = 0; i < result.sample_count; i++) {
1107  if (i == middle) {
1108  result.sample_data[i].x = parameters.samples[j].uv_offset.x;
1109  result.sample_data[i].y = parameters.samples[j].uv_offset.y;
1110  result.sample_data[i].z = parameters.samples[j].coefficient;
1111  j++;
1112  } else {
1113  KernelSample left = parameters.samples[j];
1114  KernelSample right = parameters.samples[j + 1];
1115 
1116  result.sample_data[i].z = left.coefficient + right.coefficient;
1117 
1118  Point uv = (left.uv_offset * left.coefficient +
1119  right.uv_offset * right.coefficient) /
1120  (left.coefficient + right.coefficient);
1121  result.sample_data[i].x = uv.x;
1122  result.sample_data[i].y = uv.y;
1123  j += 2;
1124  }
1125  }
1126 
1127  return result;
1128 }
1129 
1130 } // 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:62
BlendMode GetBlendMode() const
Definition: entity.cc:102
Entity Clone() const
Definition: entity.cc:159
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:46
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.
std::optional< Quad > uv_bounds
Vector2 scaled_sigma
Sigma when considering an entity's scale and the effect transform.
Point Vector2
Definition: point.h:429
static constexpr int32_t kGaussianBlurMaxKernelSize
float Scalar
Definition: scalar.h:19
SamplerAddressMode
Definition: formats.h:444
@ 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:788
TPoint< Scalar > Point
Definition: point.h:425
VertexBuffer CreateVertexBuffer(std::array< VertexType, size > input, HostBuffer &data_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:418
std::array< Point, 4 > Quad
Definition: point.h:430
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:633
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:99
constexpr Quad Transform(const Quad &quad) const
Definition: matrix.h:623
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:209
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:618
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:426
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