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