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