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