| 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
5 #include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h"
6
7 #include <cmath>
8
9 #include "flutter/fml/make_copyable.h"
10 #include "impeller/entity/contents/clip_contents.h"
11 #include "impeller/entity/contents/content_context.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"
17 #include "impeller/geometry/color.h"
18 #include "impeller/renderer/render_pass.h"
19 #include "impeller/renderer/vertex_buffer_builder.h"
20
21 namespace impeller {
22
23 using GaussianBlurVertexShader = GaussianBlurPipeline::VertexShader;
24 using GaussianBlurFragmentShader = GaussianBlurPipeline::FragmentShader;
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) {
44 case Entity::TileMode::kDecal:
45 if (renderer.GetDeviceCapabilities().SupportsDecalSamplerAddressMode()) {
46 descriptor->width_address_mode = SamplerAddressMode::kDecal;
47 descriptor->height_address_mode = SamplerAddressMode::kDecal;
48 }
49 break;
50 case Entity::TileMode::kClamp:
51 descriptor->width_address_mode = SamplerAddressMode::kClampToEdge;
52 descriptor->height_address_mode = SamplerAddressMode::kClampToEdge;
53 break;
54 case Entity::TileMode::kMirror:
55 descriptor->width_address_mode = SamplerAddressMode::kMirror;
56 descriptor->height_address_mode = SamplerAddressMode::kMirror;
57 break;
58 case Entity::TileMode::kRepeat:
59 descriptor->width_address_mode = SamplerAddressMode::kRepeat;
60 descriptor->height_address_mode = SamplerAddressMode::kRepeat;
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.
78 Vector2 source_space_scalar;
79 /// The translation that is used to get from source space to unrotated local
80 /// space.
81 Vector2 source_space_offset;
82 /// Sigma when considering an entity's scale and the effect transform.
83 Vector2 scaled_sigma;
84 /// Blur radius in source pixels based on scaled_sigma.
85 Vector2 blur_radius;
86 /// The halo padding in source space.
87 Vector2 padding;
88 /// Padding in unrotated local space.
89 Vector2 local_padding;
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.
101 const Vector2 source_space_scalar =
102 ExtractScale(entity.GetTransform().Basis());
103 const Vector2 source_space_offset =
104 Vector2(entity.GetTransform().m[12], entity.GetTransform().m[13]);
105
106 Vector2 scaled_sigma =
107 (effect_transform.Basis() * Matrix::MakeScale(source_space_scalar) * //
108 Vector2(GaussianBlurFilterContents::ScaleSigma(sigma.x),
109 GaussianBlurFilterContents::ScaleSigma(sigma.y)))
110 .Abs();
111 scaled_sigma = Clamp(scaled_sigma, 0, kMaxSigma);
112 Vector2 blur_radius =
113 Vector2(GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.x),
114 GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.y));
115 Vector2 padding(ceil(blur_radius.x), ceil(blur_radius.y));
116 Vector2 local_padding =
117 (Matrix::MakeScale(source_space_scalar) * padding).Abs();
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)) *
155 Matrix::MakeTranslation(
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.
295 ISize subpass_size;
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.
298 Quad uvs;
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.
307 Vector2 effective_scalar;
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(
317 Vector2 scaled_sigma,
318 Vector2 padding,
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 =
325 std::min(GaussianBlurFilterContents::CalculateScale(scaled_sigma.x),
326 GaussianBlurFilterContents::CalculateScale(scaled_sigma.y));
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;
394 ISize subpass_size =
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
413 Vector2 effective_scalar =
414 Vector2(subpass_size) / source_rect_padded.GetSize();
415 Quad uvs = GaussianBlurFilterContents::CalculateUVs(
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) {
597 using VS = GaussianBlurVertexShader;
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(
646 LerpHackKernelSamples(GenerateBlurInfo(blur_info))));
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,
715 Vector2 source_space_scalar,
716 Vector2 source_space_offset) {
717 switch (blur_style) {
718 case FilterContents::BlurStyle::kNormal:
719 return blur_entity;
720 case FilterContents::BlurStyle::kInner:
721 return ApplyClippedBlurStyle(Entity::ClipOperation::kIntersect, entity,
722 input, input_snapshot,
723 std::move(blur_entity), geometry);
724 break;
725 case FilterContents::BlurStyle::kOuter:
726 return ApplyClippedBlurStyle(Entity::ClipOperation::kDifference, entity,
727 input, input_snapshot,
728 std::move(blur_entity), geometry);
729 case FilterContents::BlurStyle::kSolid: {
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() * //
736 Matrix::MakeScale(1.f / source_space_scalar) *
737 Matrix::MakeTranslation(-1 * source_space_offset) *
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
786 Scalar GaussianBlurFilterContents::CalculateScale(Scalar sigma) {
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);
801 Scalar blur_radius = CalculateBlurRadius(sigma);
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
813 std::optional<Rect> GaussianBlurFilterContents::GetFilterSourceCoverage(
814 const Matrix& effect_transform,
815 const Rect& output_limit) const {
816 Vector2 scaled_sigma = {ScaleSigma(sigma_.x), ScaleSigma(sigma_.y)};
817 Vector2 blur_radius = {CalculateBlurRadius(scaled_sigma.x),
818 CalculateBlurRadius(scaled_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
824 std::optional<Rect> GaussianBlurFilterContents::GetFilterCoverage(
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(
998 MinMagFilter::kLinear, SamplerAddressMode::kClampToEdge);
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
1019 Scalar GaussianBlurFilterContents::CalculateBlurRadius(Scalar sigma) {
1020 return static_cast<Radius>(Sigma(sigma)).radius;
1021 }
1022
1023 Quad GaussianBlurFilterContents::CalculateUVs(
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.
1039 Scalar GaussianBlurFilterContents::ScaleSigma(Scalar sigma) {
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
1049 KernelSamples GenerateBlurInfo(BlurParameters parameters) {
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.
1070 if (result.sample_count > KernelSamples::kMaxKernelSize) {
1071 result.sample_count = KernelSamples::kMaxKernelSize;
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
impeller::Capabilities::SupportsDecalSamplerAddressMode
virtual bool SupportsDecalSamplerAddressMode() const =0
Whether the context backend supports SamplerAddressMode::Decal.
impeller::ContentContext
Definition: content_context.h:122
impeller::ContentContext::GetDeviceCapabilities
const Capabilities & GetDeviceCapabilities() const
Definition: content_context.cc:968
impeller::ContentContext::SubpassCallback
std::function< bool(const ContentContext &, RenderPass &)> SubpassCallback
Definition: content_context.h:234
impeller::ContentContext::GetContext
std::shared_ptr< Context > GetContext() const
Definition: content_context.cc:964
impeller::Contents::MakeAnonymous
static std::shared_ptr< Contents > MakeAnonymous(RenderProc render_proc, CoverageProc coverage_proc)
Definition: contents.cc:41
impeller::Entity
Definition: entity.h:26
impeller::Entity::SetTransform
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:62
impeller::Entity::TileMode
TileMode
Definition: entity.h:48
impeller::Entity::TileMode::kDecal
@ kDecal
impeller::Entity::TileMode::kRepeat
@ kRepeat
impeller::Entity::TileMode::kClamp
@ kClamp
impeller::Entity::TileMode::kMirror
@ kMirror
impeller::Entity::GetBlendMode
BlendMode GetBlendMode() const
Definition: entity.cc:102
impeller::Entity::Clone
Entity Clone() const
Definition: entity.cc:159
impeller::Entity::GetTransform
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:46
impeller::Entity::FromSnapshot
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
impeller::Entity::ClipOperation
ClipOperation
Definition: entity.h:67
impeller::Entity::ClipOperation::kIntersect
@ kIntersect
impeller::Entity::ClipOperation::kDifference
@ kDifference
impeller::FilterContents::BlurStyle
BlurStyle
Definition: filter_contents.h:28
impeller::FilterContents::BlurStyle::kNormal
@ kNormal
Blurred inside and outside.
impeller::FilterContents::BlurStyle::kOuter
@ kOuter
Nothing inside, blurred outside.
impeller::FilterContents::BlurStyle::kInner
@ kInner
Blurred inside, nothing outside.
impeller::FilterContents::BlurStyle::kSolid
@ kSolid
Solid inside, blurred outside.
impeller::FilterInput::Vector
std::vector< FilterInput::Ref > Vector
Definition: filter_input.h:33
impeller::GaussianBlurFilterContents::CalculateBlurRadius
static Scalar CalculateBlurRadius(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:1019
impeller::GaussianBlurFilterContents::ScaleSigma
static Scalar ScaleSigma(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:1039
impeller::GaussianBlurFilterContents::GetFilterSourceCoverage
std::optional< Rect > GetFilterSourceCoverage(const Matrix &effect_transform, const Rect &output_limit) const override
Internal utility method for |GetSourceCoverage| that computes the inverse effect of this transform on...
Definition: gaussian_blur_filter_contents.cc:813
impeller::GaussianBlurFilterContents::GetFilterCoverage
std::optional< Rect > GetFilterCoverage(const FilterInput::Vector &inputs, const Entity &entity, const Matrix &effect_transform) const override
Internal utility method for |GetLocalCoverage| that computes the output coverage of this filter acros...
Definition: gaussian_blur_filter_contents.cc:824
impeller::GaussianBlurFilterContents::CalculateScale
static Scalar CalculateScale(Scalar sigma)
Definition: gaussian_blur_filter_contents.cc:786
impeller::GaussianBlurFilterContents::CalculateUVs
static Quad CalculateUVs(const std::shared_ptr< FilterInput > &filter_input, const Entity &entity, const Rect &source_rect, const ISize &texture_size)
Definition: gaussian_blur_filter_contents.cc:1023
impeller::Geometry
Definition: geometry.h:50
impeller::RenderPipelineHandle::VertexShader
VertexShader_ VertexShader
Definition: pipeline.h:168
impeller::RenderPipelineHandle::FragmentShader
FragmentShader_ FragmentShader
Definition: pipeline.h:169
clip_contents.h
color.h
content_context.h
x
int32_t x
Definition: dl_golden_unittests.cc:458
entity.h
local_padding
Vector2 local_padding
Padding in unrotated local space.
Definition: gaussian_blur_filter_contents.cc:89
effective_scalar
Vector2 effective_scalar
Definition: gaussian_blur_filter_contents.cc:307
uvs
Quad uvs
Definition: gaussian_blur_filter_contents.cc:298
blur_radius
Vector2 blur_radius
Blur radius in source pixels based on scaled_sigma.
Definition: gaussian_blur_filter_contents.cc:85
subpass_size
ISize subpass_size
The output size of the down-sampling pass.
Definition: gaussian_blur_filter_contents.cc:295
transform
Matrix transform
Definition: gaussian_blur_filter_contents.cc:312
source_space_offset
Vector2 source_space_offset
Definition: gaussian_blur_filter_contents.cc:81
source_space_scalar
Vector2 source_space_scalar
The scalar that is used to get from source space to unrotated local space.
Definition: gaussian_blur_filter_contents.cc:78
padding
Vector2 padding
The halo padding in source space.
Definition: gaussian_blur_filter_contents.cc:87
uv_bounds
std::optional< Quad > uv_bounds
Definition: gaussian_blur_filter_contents.cc:303
scaled_sigma
Vector2 scaled_sigma
Sigma when considering an entity's scale and the effect transform.
Definition: gaussian_blur_filter_contents.cc:83
gaussian_blur_filter_contents.h
impeller::saturated::b
SI b
Definition: saturated_math.h:87
impeller
Definition: allocation.cc:12
impeller::PrimitiveType::kTriangleStrip
@ kTriangleStrip
impeller::Vector2
Point Vector2
Definition: point.h:429
impeller::kGaussianBlurMaxKernelSize
static constexpr int32_t kGaussianBlurMaxKernelSize
Definition: gaussian_blur_filter_contents.h:17
impeller::Scalar
float Scalar
Definition: scalar.h:19
impeller::SamplerAddressMode
SamplerAddressMode
Definition: formats.h:444
impeller::SamplerAddressMode::kDecal
@ kDecal
decal sampling mode is only supported on devices that pass the Capabilities.SupportsDecalSamplerAddre...
impeller::SamplerAddressMode::kRepeat
@ kRepeat
impeller::SamplerAddressMode::kMirror
@ kMirror
impeller::SamplerAddressMode::kClampToEdge
@ kClampToEdge
impeller::kEhCloseEnough
constexpr float kEhCloseEnough
Definition: constants.h:57
impeller::Rect
TRect< Scalar > Rect
Definition: rect.h:788
impeller::Point
TPoint< Scalar > Point
Definition: point.h:425
impeller::CreateVertexBuffer
VertexBuffer CreateVertexBuffer(std::array< VertexType, size > input, HostBuffer &data_host_buffer)
Create an index-less vertex buffer from a fixed size array.
Definition: vertex_buffer_builder.h:24
impeller::Size
TSize< Scalar > Size
Definition: size.h:159
impeller::VS
LinePipeline::VertexShader VS
Definition: line_contents.cc:13
impeller::GenerateBlurInfo
KernelSamples GenerateBlurInfo(BlurParameters parameters)
Definition: gaussian_blur_filter_contents.cc:1049
impeller::OptionsFromPass
ContentContextOptions OptionsFromPass(const RenderPass &pass)
Definition: contents.cc:19
impeller::ISize
ISize64 ISize
Definition: size.h:162
impeller::MinMagFilter
MinMagFilter
Describes how the texture should be sampled when the texture is being shrunk (minified) or expanded (...
Definition: formats.h:418
impeller::MinMagFilter::kLinear
@ kLinear
impeller::Quad
std::array< Point, 4 > Quad
Definition: point.h:430
impeller::GaussianBlurFragmentShader
GaussianBlurPipeline::FragmentShader GaussianBlurFragmentShader
Definition: gaussian_blur_filter_contents.cc:24
impeller::LerpHackKernelSamples
GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(KernelSamples parameters)
Definition: gaussian_blur_filter_contents.cc:1096
impeller::GaussianBlurVertexShader
GaussianBlurPipeline::VertexShader GaussianBlurVertexShader
Definition: gaussian_blur_filter_contents.cc:23
std
Definition: comparable.h:95
render_pass.h
p1
Point p1
Definition: round_superellipse_param.cc:313
c
Point c
Definition: round_superellipse_param.cc:315
impeller::BlurParameters
Definition: gaussian_blur_filter_contents.h:22
impeller::BlurParameters::blur_uv_offset
Point blur_uv_offset
Definition: gaussian_blur_filter_contents.h:23
impeller::BlurParameters::step_size
int step_size
Definition: gaussian_blur_filter_contents.h:26
impeller::BlurParameters::blur_radius
int blur_radius
Definition: gaussian_blur_filter_contents.h:25
impeller::BlurParameters::blur_sigma
Scalar blur_sigma
Definition: gaussian_blur_filter_contents.h:24
impeller::KernelSample
Definition: gaussian_blur_filter_contents.h:30
impeller::KernelSample::uv_offset
Vector2 uv_offset
Definition: gaussian_blur_filter_contents.h:31
impeller::KernelSample::coefficient
float coefficient
Definition: gaussian_blur_filter_contents.h:32
impeller::KernelSamples
Definition: gaussian_blur_filter_contents.h:40
impeller::KernelSamples::kMaxKernelSize
static constexpr int kMaxKernelSize
Definition: gaussian_blur_filter_contents.h:41
impeller::KernelSamples::samples
KernelSample samples[kMaxKernelSize]
Definition: gaussian_blur_filter_contents.h:43
impeller::KernelSamples::sample_count
int sample_count
Definition: gaussian_blur_filter_contents.h:42
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::Matrix::MakeOrthographic
static constexpr Matrix MakeOrthographic(TSize< T > size)
Definition: matrix.h:633
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
impeller::Matrix::Basis
constexpr Matrix Basis() const
The Matrix without its w components (without translation).
Definition: matrix.h:239
impeller::Matrix::Invert
Matrix Invert() const
Definition: matrix.cc:99
impeller::Matrix::Transform
constexpr Quad Transform(const Quad &quad) const
Definition: matrix.h:623
impeller::Matrix::MakeScale
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
impeller::Radius
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition: sigma.h:48
impeller::SamplerDescriptor
Definition: sampler_descriptor.h:14
impeller::SamplerDescriptor::width_address_mode
SamplerAddressMode width_address_mode
Definition: sampler_descriptor.h:19
impeller::SamplerDescriptor::height_address_mode
SamplerAddressMode height_address_mode
Definition: sampler_descriptor.h:20
impeller::SamplerDescriptor::mag_filter
MinMagFilter mag_filter
Definition: sampler_descriptor.h:16
impeller::SamplerDescriptor::min_filter
MinMagFilter min_filter
Definition: sampler_descriptor.h:15
impeller::Sigma
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
impeller::TPoint< Scalar >
impeller::TPoint::GetLength
constexpr Type GetLength() const
Definition: point.h:209
impeller::TPoint::y
Type y
Definition: point.h:32
impeller::TPoint::x
Type x
Definition: point.h:31
impeller::TRect< Scalar >
impeller::TRect::Expand
constexpr TRect< T > Expand(T left, T top, T right, T bottom) const
Returns a rectangle with expanded edges. Negative expansion results in shrinking.
Definition: rect.h:618
impeller::TRect< Scalar >::MakeOriginSize
constexpr static TRect MakeOriginSize(const TPoint< Type > &origin, const TSize< Type > &size)
Definition: rect.h:144
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
impeller::TRect::GetTransformedPoints
constexpr std::array< TPoint< T >, 4 > GetTransformedPoints(const Matrix &transform) const
Definition: rect.h:426
impeller::TRect< Scalar >::MakeSize
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:150
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
impeller::TSize
Definition: size.h:25
impeller::TSize::height
Type height
Definition: size.h:29
impeller::TSize::width
Type width
Definition: size.h:28
impeller::Vector3
Definition: vector.h:20
impeller::Vector3::x
Scalar x
Definition: vector.h:23
impeller::Vector3::y
Scalar y
Definition: vector.h:24
vertex_buffer_builder.h