From ea1c1bae8fcc104a5faeb7c36dc62b2c50c0400e Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 23 May 2025 13:20:37 -0700 Subject: [PATCH] [Engine] Fast blurring algorithm for RSuperellipse (#169187) This PR adds a fast blurring algorithm for `RSuperellipse`s with uniform corner radius, similar to `AttemptDrawBlurredRRect`. Fixes https://github.com/flutter/flutter/issues/163893. Fixes https://github.com/flutter/flutter/issues/167366. This approximate algorithm is implemented by adding additional retraction to RRect's algorithm. Since they share most the logic, much effort is made to ensure reasonable code share. I've also built a playground test `RoundSuperellipseShadowComparison` to compare the effect between the fast algorithm and the bruteforce algorithm, and added a macrobenchmark "rsuperellipse_blur". ### Result The following video shows the reproduction app from https://github.com/flutter/flutter/issues/167366, which shows much better framerate on RSE after the PR, almost the same as RRect. (I've verified that it was much worse without the PR.) https://github.com/user-attachments/assets/5433af91-c0a1-4b15-9161-cf2280543e27 The following video shows the `RoundSuperellipseShadowComparison` playground, which compares the fast algorithm (left) against the bruteforce algorithm (right). * Pay attention to the cases with small but non zero sigma. They should be of almost the same shape as those with zero sigma, which skips the blurring altogether. With this algorithm, the difference between the two cases are about 1~2 pixel at most. https://github.com/user-attachments/assets/e26d2d8f-d29e-4db8-9c20-67103d77891c The following images compare macrobenchmarks "rrect_blur" against the new "rsuperellipse_blur". (Notice that the avg frame time fluctuates a lot but at least it shows that they're on par.) image image ### Open questions * Should I improve the code share of shader files? Currently they're not shared mostly because I'm not sure about its gain and cost. ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../macrobenchmarks/lib/common.dart | 1 + dev/benchmarks/macrobenchmarks/lib/main.dart | 9 + .../lib/src/rsuperellipse_blur.dart | 99 +++++++++ .../ci/licenses_golden/licenses_flutter | 16 +- .../compiler/shader_lib/impeller/rrect.glsl | 51 +++++ .../flutter/impeller/display_list/canvas.cc | 64 ++++-- .../flutter/impeller/display_list/canvas.h | 38 ++++ .../impeller/display_list/canvas_unittests.cc | 75 +++++++ engine/src/flutter/impeller/entity/BUILD.gn | 7 +- .../entity/contents/content_context.cc | 8 + .../entity/contents/content_context.h | 1 + .../impeller/entity/contents/pipelines.h | 6 +- .../contents/solid_rrect_blur_contents.cc | 198 ++--------------- .../contents/solid_rrect_blur_contents.h | 33 +-- .../solid_rrect_like_blur_contents.cc | 197 +++++++++++++++++ .../contents/solid_rrect_like_blur_contents.h | 91 ++++++++ .../solid_rsuperellipse_blur_contents.cc | 91 ++++++++ .../solid_rsuperellipse_blur_contents.h | 44 ++++ .../impeller/entity/entity_unittests.cc | 2 +- .../impeller/entity/shaders/rrect_blur.frag | 53 ++--- .../{rrect_blur.vert => rrect_like_blur.vert} | 0 .../entity/shaders/rsuperellipse_blur.frag | 122 +++++++++++ .../geometry/round_superellipse_param.cc | 12 ++ .../geometry/round_superellipse_param.h | 4 + engine/src/flutter/impeller/tools/malioc.json | 201 +++++++++++++++++- 25 files changed, 1157 insertions(+), 266 deletions(-) create mode 100644 dev/benchmarks/macrobenchmarks/lib/src/rsuperellipse_blur.dart create mode 100644 engine/src/flutter/impeller/compiler/shader_lib/impeller/rrect.glsl create mode 100644 engine/src/flutter/impeller/entity/contents/solid_rrect_like_blur_contents.cc create mode 100644 engine/src/flutter/impeller/entity/contents/solid_rrect_like_blur_contents.h create mode 100644 engine/src/flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.cc create mode 100644 engine/src/flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.h rename engine/src/flutter/impeller/entity/shaders/{rrect_blur.vert => rrect_like_blur.vert} (100%) create mode 100644 engine/src/flutter/impeller/entity/shaders/rsuperellipse_blur.frag diff --git a/dev/benchmarks/macrobenchmarks/lib/common.dart b/dev/benchmarks/macrobenchmarks/lib/common.dart index a0f05a443ab..36a155cc909 100644 --- a/dev/benchmarks/macrobenchmarks/lib/common.dart +++ b/dev/benchmarks/macrobenchmarks/lib/common.dart @@ -42,6 +42,7 @@ const String kDrawVerticesPageRouteName = '/draw_vertices'; const String kDrawAtlasPageRouteName = '/draw_atlas'; const String kAnimatedAdvancedBlend = '/animated_advanced_blend'; const String kRRectBlurRouteName = '/rrect_blur'; +const String kRSuperellipseBlurRouteName = '/rsuperellipse_blur'; const String kOpacityPeepholeOneRectRouteName = '$kOpacityPeepholeRouteName/one_big_rect'; const String kOpacityPeepholeColumnOfOpacityRouteName = diff --git a/dev/benchmarks/macrobenchmarks/lib/main.dart b/dev/benchmarks/macrobenchmarks/lib/main.dart index a037f87d411..2142b183244 100644 --- a/dev/benchmarks/macrobenchmarks/lib/main.dart +++ b/dev/benchmarks/macrobenchmarks/lib/main.dart @@ -37,6 +37,7 @@ import 'src/picture_cache_complexity_scoring.dart'; import 'src/post_backdrop_filter.dart'; import 'src/raster_cache_use_memory.dart'; import 'src/rrect_blur.dart' show RRectBlur; +import 'src/rsuperellipse_blur.dart' show RSuperellipseBlur; import 'src/shader_mask_cache.dart'; import 'src/simple_animation.dart'; import 'src/simple_scroll.dart'; @@ -109,6 +110,7 @@ class MacrobenchmarksApp extends StatelessWidget { kDrawAtlasPageRouteName: (BuildContext context) => const DrawAtlasPage(), kAnimatedAdvancedBlend: (BuildContext context) => const AnimatedAdvancedBlend(), kRRectBlurRouteName: (BuildContext context) => const RRectBlur(), + kRSuperellipseBlurRouteName: (BuildContext context) => const RSuperellipseBlur(), kVeryLongPictureScrollingRouteName: (BuildContext context) => const VeryLongPictureScrollingPerf(), }, @@ -394,6 +396,13 @@ class HomePage extends StatelessWidget { Navigator.pushNamed(context, kRRectBlurRouteName); }, ), + ElevatedButton( + key: const Key(kRSuperellipseBlurRouteName), + child: const Text('Rounded superellipse Blur'), + onPressed: () { + Navigator.pushNamed(context, kRSuperellipseBlurRouteName); + }, + ), ElevatedButton( key: const Key(kVeryLongPictureScrollingRouteName), child: const Text('Very Long Picture Scrolling'), diff --git a/dev/benchmarks/macrobenchmarks/lib/src/rsuperellipse_blur.dart b/dev/benchmarks/macrobenchmarks/lib/src/rsuperellipse_blur.dart new file mode 100644 index 00000000000..6cf466a0b0b --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/rsuperellipse_blur.dart @@ -0,0 +1,99 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:math' show cos, sin; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; + +class RSuperellipseBlur extends StatefulWidget { + const RSuperellipseBlur({super.key}); + + @override + State createState() => _RSuperellipseBlurPageState(); +} + +class _RSuperellipseBlurPageState extends State + with SingleTickerProviderStateMixin { + late final AnimationController controller; + double tick = 0.0; + + @override + void initState() { + super.initState(); + controller = AnimationController(vsync: this, duration: const Duration(hours: 1)); + controller.addListener(() { + setState(() { + tick += 1; + }); + }); + controller.forward(from: 0); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return CustomPaint( + size: const Size(500, 500), + painter: PointsPainter(tick), + child: Container(), + ); + } +} + +class PointsPainter extends CustomPainter { + PointsPainter(this.tick); + + final double tick; + + final Float32List data = Float32List(8000); + + static const List kColors = [ + Colors.red, + Colors.blue, + Colors.green, + Colors.yellow, + Colors.orange, + Colors.purple, + Colors.pink, + Colors.deepPurple, + ]; + + @override + void paint(Canvas canvas, Size size) { + if (size.width == 0) { + return; + } + final double halfHeight = size.height / 2.0; + const double freq = 0.25; + const int circleCount = 40; + for (int i = 0; i < circleCount; ++i) { + final double radius = 25 * cos(i + (1.0 * 2.0 * 3.1415 * tick) / 60.0) + 25; + final Paint paint = + Paint() + ..style = PaintingStyle.fill + ..filterQuality = FilterQuality.low + ..maskFilter = MaskFilter.blur(BlurStyle.normal, radius); + final double yval = halfHeight * sin(i + (freq * 2.0 * 3.1415 * tick) / 60.0) + halfHeight; + final double xval = (i.toDouble() / circleCount) * size.width; + canvas.drawRSuperellipse( + RSuperellipse.fromRectAndRadius( + Rect.fromCircle(center: Offset(xval, yval), radius: 50), + const Radius.circular(40), + ), + paint..color = kColors[i % kColors.length], + ); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 21a5621774e..5af61b2dc25 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -51121,6 +51121,7 @@ ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/gradient.glsl + . ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/math.glsl + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/path.glsl + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/prefix_sum.glsl + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/rrect.glsl + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/texture.glsl + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/tile_mode.glsl + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/compiler/shader_lib/impeller/transform.glsl + ../../../flutter/LICENSE @@ -51265,6 +51266,10 @@ ORIGIN: ../../../flutter/impeller/entity/contents/solid_color_contents.cc + ../. ORIGIN: ../../../flutter/impeller/entity/contents/solid_color_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/solid_rrect_blur_contents.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/solid_rrect_blur_contents.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/contents/solid_rrect_like_blur_contents.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/contents/solid_rrect_like_blur_contents.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/sweep_gradient_contents.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/sweep_gradient_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/text_contents.cc + ../../../flutter/LICENSE @@ -51367,7 +51372,8 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/gradients/sweep_gradient_unifor ORIGIN: ../../../flutter/impeller/entity/shaders/line.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/line.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/rrect_blur.frag + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/shaders/rrect_blur.vert + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/shaders/rrect_like_blur.vert + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/shaders/rsuperellipse_blur.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/runtime_effect.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/solid_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/solid_fill.vert + ../../../flutter/LICENSE @@ -54124,6 +54130,7 @@ FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/gradient.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/math.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/path.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/prefix_sum.glsl +FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/rrect.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/texture.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/tile_mode.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/transform.glsl @@ -54268,6 +54275,10 @@ FILE: ../../../flutter/impeller/entity/contents/solid_color_contents.cc FILE: ../../../flutter/impeller/entity/contents/solid_color_contents.h FILE: ../../../flutter/impeller/entity/contents/solid_rrect_blur_contents.cc FILE: ../../../flutter/impeller/entity/contents/solid_rrect_blur_contents.h +FILE: ../../../flutter/impeller/entity/contents/solid_rrect_like_blur_contents.cc +FILE: ../../../flutter/impeller/entity/contents/solid_rrect_like_blur_contents.h +FILE: ../../../flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.cc +FILE: ../../../flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.h FILE: ../../../flutter/impeller/entity/contents/sweep_gradient_contents.cc FILE: ../../../flutter/impeller/entity/contents/sweep_gradient_contents.h FILE: ../../../flutter/impeller/entity/contents/text_contents.cc @@ -54370,7 +54381,8 @@ FILE: ../../../flutter/impeller/entity/shaders/gradients/sweep_gradient_uniform_ FILE: ../../../flutter/impeller/entity/shaders/line.frag FILE: ../../../flutter/impeller/entity/shaders/line.vert FILE: ../../../flutter/impeller/entity/shaders/rrect_blur.frag -FILE: ../../../flutter/impeller/entity/shaders/rrect_blur.vert +FILE: ../../../flutter/impeller/entity/shaders/rrect_like_blur.vert +FILE: ../../../flutter/impeller/entity/shaders/rsuperellipse_blur.frag FILE: ../../../flutter/impeller/entity/shaders/runtime_effect.vert FILE: ../../../flutter/impeller/entity/shaders/solid_fill.frag FILE: ../../../flutter/impeller/entity/shaders/solid_fill.vert diff --git a/engine/src/flutter/impeller/compiler/shader_lib/impeller/rrect.glsl b/engine/src/flutter/impeller/compiler/shader_lib/impeller/rrect.glsl new file mode 100644 index 00000000000..96caaef7b3b --- /dev/null +++ b/engine/src/flutter/impeller/compiler/shader_lib/impeller/rrect.glsl @@ -0,0 +1,51 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RRECT_GLSL_ +#define RRECT_GLSL_ + +#include + +const float kTwoOverSqrtPi = 2.0 / sqrt(3.1415926); + +float maxXY(vec2 v) { + return max(v.x, v.y); +} + +// use crate::math::compute_erf7; +float computeErf7(float x) { + x *= kTwoOverSqrtPi; + float xx = x * x; + x = x + (0.24295 + (0.03395 + 0.0104 * xx) * xx) * (x * xx); + return x / sqrt(1.0 + x * x); +} + +// The length formula, but with an exponent other than 2 +float powerDistance(vec2 p, float exponent, float exponentInv) { + float xp = POW(p.x, exponent); + float yp = POW(p.y, exponent); + return POW(xp + yp, exponentInv); +} + +float computeRRectDistance(vec2 position, + vec2 adjust, + vec3 r1_exponent_exponentInv) { + float r1 = r1_exponent_exponentInv[0]; + float exponent = r1_exponent_exponentInv[1]; + float exponentInv = r1_exponent_exponentInv[2]; + + vec2 adjusted = position - adjust; + float dPos = powerDistance(max(adjusted, 0.0), exponent, exponentInv); + float dNeg = min(maxXY(adjusted), 0.0); + return dPos + dNeg - r1; +} + +float computeRRectFade(float d, vec3 sInv_minEdge_scale) { + float sInv = sInv_minEdge_scale[0]; + float minEdge = sInv_minEdge_scale[1]; + float scale = sInv_minEdge_scale[2]; + return scale * (computeErf7(sInv * (minEdge + d)) - computeErf7(sInv * d)); +} + +#endif // RRECT_GLSL_ diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index 9354778974a..084db12a810 100644 --- a/engine/src/flutter/impeller/display_list/canvas.cc +++ b/engine/src/flutter/impeller/display_list/canvas.cc @@ -31,6 +31,7 @@ #include "impeller/entity/contents/framebuffer_blend_contents.h" #include "impeller/entity/contents/line_contents.h" #include "impeller/entity/contents/solid_rrect_blur_contents.h" +#include "impeller/entity/contents/solid_rsuperellipse_blur_contents.h" #include "impeller/entity/contents/text_contents.h" #include "impeller/entity/contents/text_shadow_cache.h" #include "impeller/entity/contents/texture_contents.h" @@ -43,8 +44,6 @@ #include "impeller/entity/geometry/line_geometry.h" #include "impeller/entity/geometry/point_field_geometry.h" #include "impeller/entity/geometry/rect_geometry.h" -#include "impeller/entity/geometry/round_rect_geometry.h" -#include "impeller/entity/geometry/round_superellipse_geometry.h" #include "impeller/entity/geometry/stroke_path_geometry.h" #include "impeller/entity/save_layer_utils.h" #include "impeller/geometry/color.h" @@ -176,6 +175,25 @@ static std::unique_ptr CreateRenderTarget( } // namespace +std::shared_ptr +Canvas::RRectBlurShape::BuildBlurContent() { + return std::make_shared(); +} + +Geometry& Canvas::RRectBlurShape::BuildGeometry(Rect rect, Scalar radius) { + return geom_.emplace(rect, Size{radius, radius}); +} + +std::shared_ptr +Canvas::RSuperellipseBlurShape::BuildBlurContent() { + return std::make_shared(); +} + +Geometry& Canvas::RSuperellipseBlurShape::BuildGeometry(Rect rect, + Scalar radius) { + return geom_.emplace(rect, radius); +} + Canvas::Canvas(ContentContext& renderer, const RenderTarget& render_target, bool is_onscreen, @@ -403,6 +421,22 @@ bool Canvas::AttemptColorFilterOptimization( bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, Size corner_radii, const Paint& paint) { + RRectBlurShape rrect_shape; + return AttemptDrawBlurredRRectLike(rect, corner_radii, paint, rrect_shape); +} + +bool Canvas::AttemptDrawBlurredRSuperellipse(const Rect& rect, + Size corner_radii, + const Paint& paint) { + RSuperellipseBlurShape rsuperellipse_shape; + return AttemptDrawBlurredRRectLike(rect, corner_radii, paint, + rsuperellipse_shape); +} + +bool Canvas::AttemptDrawBlurredRRectLike(const Rect& rect, + Size corner_radii, + const Paint& paint, + RRectLikeBlurShape& shape) { if (paint.style != Paint::Style::kFill) { return false; } @@ -424,6 +458,7 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, if (fabsf(corner_radii.width - corner_radii.height) > kEhCloseEnough) { return false; } + Scalar corner_radius = corner_radii.width; // For symmetrically mask blurred solid RRects, absorb the mask blur and use // a faster SDF approximation. @@ -483,12 +518,13 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, Save(1u); } - auto draw_blurred_rrect = [this, &rect, &corner_radii, &rrect_paint]() { - auto contents = std::make_shared(); + auto draw_blurred_rrect = [this, &rect, corner_radius, &rrect_paint, + &shape]() { + auto contents = shape.BuildBlurContent(); contents->SetColor(rrect_paint.color); contents->SetSigma(rrect_paint.mask_blur_descriptor->sigma); - contents->SetRRect(rect, corner_radii); + contents->SetShape(rect, corner_radius); Entity blurred_rrect_entity; blurred_rrect_entity.SetTransform(GetCurrentTransform()); @@ -513,19 +549,19 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(rrect_paint.blend_mode); - RoundRectGeometry geom(rect, corner_radii); + Geometry& geom = shape.BuildGeometry(rect, corner_radius); AddRenderEntityWithFiltersToCurrentPass(entity, &geom, rrect_paint, /*reuse_depth=*/true); break; } case FilterContents::BlurStyle::kOuter: { - RoundRectGeometry geom(rect, corner_radii); + Geometry& geom = shape.BuildGeometry(rect, corner_radius); ClipGeometry(geom, Entity::ClipOperation::kDifference); draw_blurred_rrect(); break; } case FilterContents::BlurStyle::kInner: { - RoundRectGeometry geom(rect, corner_radii); + Geometry& geom = shape.BuildGeometry(rect, corner_radius); ClipGeometry(geom, Entity::ClipOperation::kIntersect); draw_blurred_rrect(); break; @@ -696,15 +732,19 @@ void Canvas::DrawDiffRoundRect(const RoundRect& outer, void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& rse, const Paint& paint) { + auto& rect = rse.GetBounds(); + auto& radii = rse.GetRadii(); + if (radii.AreAllCornersSame() && + AttemptDrawBlurredRSuperellipse(rect, radii.top_left, paint)) { + return; + } + if (paint.style == Paint::Style::kFill) { - // TODO(dkwingsmt): Investigate if RSE can use the `AttemptDrawBlurredRRect` - // optimization at some point, such as a large enough mask radius. - // https://github.com/flutter/flutter/issues/163893 Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - RoundSuperellipseGeometry geom(rse.GetBounds(), rse.GetRadii()); + RoundSuperellipseGeometry geom(rect, radii); AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); return; } diff --git a/engine/src/flutter/impeller/display_list/canvas.h b/engine/src/flutter/impeller/display_list/canvas.h index 78aebba0ae0..0c4ac8af3c8 100644 --- a/engine/src/flutter/impeller/display_list/canvas.h +++ b/engine/src/flutter/impeller/display_list/canvas.h @@ -18,10 +18,13 @@ #include "impeller/display_list/paint.h" #include "impeller/entity/contents/atlas_contents.h" #include "impeller/entity/contents/clip_contents.h" +#include "impeller/entity/contents/solid_rrect_like_blur_contents.h" #include "impeller/entity/contents/text_contents.h" #include "impeller/entity/entity.h" #include "impeller/entity/entity_pass_clip_stack.h" #include "impeller/entity/geometry/geometry.h" +#include "impeller/entity/geometry/round_rect_geometry.h" +#include "impeller/entity/geometry/round_superellipse_geometry.h" #include "impeller/entity/geometry/vertices_geometry.h" #include "impeller/entity/inline_pass_context.h" #include "impeller/geometry/matrix.h" @@ -279,6 +282,32 @@ class Canvas { bool EnsureFinalMipmapGeneration() const; private: + class RRectLikeBlurShape { + public: + virtual ~RRectLikeBlurShape() = default; + virtual std::shared_ptr BuildBlurContent() = 0; + virtual Geometry& BuildGeometry(Rect rect, Scalar radius) = 0; + }; + + class RRectBlurShape : public RRectLikeBlurShape { + public: + std::shared_ptr BuildBlurContent() override; + Geometry& BuildGeometry(Rect rect, Scalar radius) override; + + private: + std::optional geom_; // optional stack allocation + }; + + class RSuperellipseBlurShape : public RRectLikeBlurShape { + public: + std::shared_ptr BuildBlurContent() override; + Geometry& BuildGeometry(Rect rect, Scalar radius) override; + + private: + std::optional + geom_; // optional stack allocation + }; + ContentContext& renderer_; RenderTarget render_target_; const bool is_onscreen_; @@ -369,6 +398,15 @@ class Canvas { Size corner_radii, const Paint& paint); + bool AttemptDrawBlurredRSuperellipse(const Rect& rect, + Size corner_radii, + const Paint& paint); + + bool AttemptDrawBlurredRRectLike(const Rect& rect, + Size corner_radii, + const Paint& paint, + RRectLikeBlurShape& shape); + /// For simple DrawImageRect calls, optimize any draws with a color filter /// into the corresponding atlas draw. /// diff --git a/engine/src/flutter/impeller/display_list/canvas_unittests.cc b/engine/src/flutter/impeller/display_list/canvas_unittests.cc index ab78138b546..3750b6838ab 100644 --- a/engine/src/flutter/impeller/display_list/canvas_unittests.cc +++ b/engine/src/flutter/impeller/display_list/canvas_unittests.cc @@ -16,6 +16,7 @@ #include "impeller/display_list/dl_vertices_geometry.h" #include "impeller/geometry/geometry_asserts.h" #include "impeller/playground/playground.h" +#include "impeller/playground/widgets.h" #include "impeller/renderer/render_target.h" namespace impeller { @@ -385,5 +386,79 @@ TEST_P(AiksTest, SupportsBlitToOnscreen) { } } +TEST_P(AiksTest, RoundSuperellipseShadowComparison) { + // Config + Size default_size(600, 400); + Point left_center(400, 700); + Point right_center(1300, 700); + Color color = Color::Red(); + + // Convert `color` to a `color_source`. This forces + // `canvas.DrawRoundSuperellipse` to use the regular shadow algorithm + // (blurring) instead of the fast shadow algorithm. + std::shared_ptr color_source; + { + flutter::DlColor dl_color = flutter::DlColor(color.ToARGB()); + std::vector colors = {dl_color, dl_color}; + std::vector stops = {0.0, 1.0}; + color_source = flutter::DlColorSource::MakeLinear( + {0, 0}, {1000, 1000}, 2, colors.data(), stops.data(), + flutter::DlTileMode::kClamp); + } + + auto RectMakeCenterHalfSize = [](Point center, Point half_size) { + Size size(half_size.x * 2, half_size.y * 2); + return Rect::MakeOriginSize(center - half_size, size); + }; + + RenderCallback callback = [&](RenderTarget& render_target) { + ContentContext context(GetContext(), nullptr); + Canvas canvas(context, render_target, true, false); + // Somehow there's a scaling factor between PlaygroundPoint and Canvas. + Matrix ctm = Matrix::MakeScale(Vector2(1, 1) * 0.5); + Matrix i_ctm = ctm.Invert(); + + static Scalar sigma = 0.05; + static Scalar radius = 200; + + // Define the ImGui + ImGui::Begin("Shadow", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + ImGui::SliderFloat("Sigma", &sigma, 0, 100); + ImGui::SliderFloat("Radius", &radius, 0, 1000); + } + ImGui::End(); + + static PlaygroundPoint right_reference_var( + ctm * (right_center + default_size / 2), 30, Color::White()); + Point right_reference = i_ctm * DrawPlaygroundPoint(right_reference_var); + Point half_size = (right_reference - right_center).Abs(); + Rect left_bounds = RectMakeCenterHalfSize(left_center, half_size); + Rect right_bounds = RectMakeCenterHalfSize(right_center, half_size); + + Paint paint{ + .color = color, + .mask_blur_descriptor = + Paint::MaskBlurDescriptor{ + .sigma = Sigma(sigma), + }, + }; + + // Left: Draw with canvas + canvas.DrawRoundSuperellipse( + RoundSuperellipse::MakeRectRadius(left_bounds, radius), paint); + + // Right: Direct draw + paint.color_source = color_source.get(); + canvas.DrawRoundSuperellipse( + RoundSuperellipse::MakeRectRadius(right_bounds, radius), paint); + + canvas.EndReplay(); + return true; + }; + + ASSERT_TRUE(Playground::OpenPlaygroundHere(callback)); +} + } // namespace testing } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/BUILD.gn b/engine/src/flutter/impeller/entity/BUILD.gn index a08919f2e38..2264682efd7 100644 --- a/engine/src/flutter/impeller/entity/BUILD.gn +++ b/engine/src/flutter/impeller/entity/BUILD.gn @@ -37,8 +37,9 @@ impeller_shaders("entity_shaders") { "shaders/gradients/sweep_gradient_uniform_fill.frag", "shaders/line.frag", "shaders/line.vert", - "shaders/rrect_blur.vert", "shaders/rrect_blur.frag", + "shaders/rrect_like_blur.vert", + "shaders/rsuperellipse_blur.frag", "shaders/runtime_effect.vert", "shaders/solid_fill.frag", "shaders/solid_fill.vert", @@ -175,6 +176,10 @@ impeller_component("entity") { "contents/solid_color_contents.h", "contents/solid_rrect_blur_contents.cc", "contents/solid_rrect_blur_contents.h", + "contents/solid_rrect_like_blur_contents.cc", + "contents/solid_rrect_like_blur_contents.h", + "contents/solid_rsuperellipse_blur_contents.cc", + "contents/solid_rsuperellipse_blur_contents.h", "contents/sweep_gradient_contents.cc", "contents/sweep_gradient_contents.h", "contents/text_contents.cc", diff --git a/engine/src/flutter/impeller/entity/contents/content_context.cc b/engine/src/flutter/impeller/entity/contents/content_context.cc index aa81e774bde..50de621e42b 100644 --- a/engine/src/flutter/impeller/entity/contents/content_context.cc +++ b/engine/src/flutter/impeller/entity/contents/content_context.cc @@ -285,6 +285,7 @@ struct ContentContext::Pipelines { Variants radial_gradient_ssbo_fill; Variants radial_gradient_uniform_fill; Variants rrect_blur; + Variants rsuperellipse_blur; Variants solid_fill; Variants srgb_to_linear_filter; Variants sweep_gradient_fill; @@ -685,6 +686,8 @@ ContentContext::ContentContext( pipelines_->texture_downsample.CreateDefault( *context_, options_no_msaa_no_depth_stencil); pipelines_->rrect_blur.CreateDefault(*context_, options_trianglestrip); + pipelines_->rsuperellipse_blur.CreateDefault(*context_, + options_trianglestrip); pipelines_->texture_strict_src.CreateDefault(*context_, options); pipelines_->tiled_texture.CreateDefault(*context_, options, {supports_decal}); @@ -1087,6 +1090,11 @@ PipelineRef ContentContext::GetRRectBlurPipeline( return GetPipeline(this, pipelines_->rrect_blur, opts); } +PipelineRef ContentContext::GetRSuperellipseBlurPipeline( + ContentContextOptions opts) const { + return GetPipeline(this, pipelines_->rsuperellipse_blur, opts); +} + PipelineRef ContentContext::GetSweepGradientFillPipeline( ContentContextOptions opts) const { return GetPipeline(this, pipelines_->sweep_gradient_fill, opts); diff --git a/engine/src/flutter/impeller/entity/contents/content_context.h b/engine/src/flutter/impeller/entity/contents/content_context.h index da3db495b9d..28be0eed419 100644 --- a/engine/src/flutter/impeller/entity/contents/content_context.h +++ b/engine/src/flutter/impeller/entity/contents/content_context.h @@ -209,6 +209,7 @@ class ContentContext { PipelineRef GetRadialGradientSSBOFillPipeline(ContentContextOptions opts) const; PipelineRef GetRadialGradientUniformFillPipeline(ContentContextOptions opts) const; PipelineRef GetRRectBlurPipeline(ContentContextOptions opts) const; + PipelineRef GetRSuperellipseBlurPipeline(ContentContextOptions opts) const; PipelineRef GetScreenBlendPipeline(ContentContextOptions opts) const; PipelineRef GetSolidFillPipeline(ContentContextOptions opts) const; PipelineRef GetSourceATopBlendPipeline(ContentContextOptions opts) const; diff --git a/engine/src/flutter/impeller/entity/contents/pipelines.h b/engine/src/flutter/impeller/entity/contents/pipelines.h index f3998a779ea..0e83ecf6e72 100644 --- a/engine/src/flutter/impeller/entity/contents/pipelines.h +++ b/engine/src/flutter/impeller/entity/contents/pipelines.h @@ -43,7 +43,8 @@ #include "impeller/entity/radial_gradient_ssbo_fill.frag.h" #include "impeller/entity/radial_gradient_uniform_fill.frag.h" #include "impeller/entity/rrect_blur.frag.h" -#include "impeller/entity/rrect_blur.vert.h" +#include "impeller/entity/rrect_like_blur.vert.h" +#include "impeller/entity/rsuperellipse_blur.frag.h" #include "impeller/entity/solid_fill.frag.h" #include "impeller/entity/solid_fill.vert.h" #include "impeller/entity/srgb_to_linear_filter.frag.h" @@ -137,7 +138,8 @@ using PorterDuffBlendPipeline = RenderPipelineHandle; using RadialGradientSSBOFillPipeline = GradientPipelineHandle; using RadialGradientUniformFillPipeline = GradientPipelineHandle; -using RRectBlurPipeline = RenderPipelineHandle; +using RRectBlurPipeline = RenderPipelineHandle; +using RSuperellipseBlurPipeline = RenderPipelineHandle; using SolidFillPipeline = RenderPipelineHandle; using SrgbToLinearFilterPipeline = RenderPipelineHandle; using SweepGradientFillPipeline = GradientPipelineHandle; diff --git a/engine/src/flutter/impeller/entity/contents/solid_rrect_blur_contents.cc b/engine/src/flutter/impeller/entity/contents/solid_rrect_blur_contents.cc index c38f553469f..2f1d8cd8817 100644 --- a/engine/src/flutter/impeller/entity/contents/solid_rrect_blur_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/solid_rrect_blur_contents.cc @@ -14,189 +14,29 @@ namespace impeller { -namespace { -// Generous padding to make sure blurs with large sigmas are fully visible. Used -// to expand the geometry around the rrect. Larger sigmas have more subtle -// gradients so they need larger padding to avoid hard cutoffs. Sigma is -// maximized to 3.5 since that should cover 99.95% of all samples. 3.0 should -// cover 99.7% but that was seen to be not enough for large sigmas. -Scalar PadForSigma(Scalar sigma) { - Scalar scalar = std::min((1.0f / 47.6f) * sigma + 2.5f, 3.5f); - return sigma * scalar; +bool SolidRRectBlurContents::SetPassInfo(RenderPass& pass, + const ContentContext& renderer, + PassContext& pass_context) const { + using FS = RRectBlurPipeline::FragmentShader; + + FS::FragInfo frag_info; + frag_info.color = GetColor(); + frag_info.center_adjust = Concat(pass_context.center, pass_context.adjust); + frag_info.r1_exponent_exponentInv = + Vector3(pass_context.r1, pass_context.exponent, pass_context.exponentInv); + frag_info.sInv_minEdge_scale = + Vector3(pass_context.sInv, pass_context.minEdge, pass_context.scale); + + auto& host_buffer = renderer.GetTransientsBuffer(); + pass.SetCommandLabel("RRect Shadow"); + pass.SetPipeline(renderer.GetRRectBlurPipeline(pass_context.opts)); + + FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); + return true; } -} // namespace SolidRRectBlurContents::SolidRRectBlurContents() = default; SolidRRectBlurContents::~SolidRRectBlurContents() = default; -void SolidRRectBlurContents::SetRRect(std::optional rect, - Size corner_radii) { - rect_ = rect; - corner_radii_ = corner_radii; -} - -void SolidRRectBlurContents::SetSigma(Sigma sigma) { - sigma_ = sigma; -} - -void SolidRRectBlurContents::SetColor(Color color) { - color_ = color.Premultiply(); -} - -Color SolidRRectBlurContents::GetColor() const { - return color_; -} - -static Point eccentricity(Point v, double sInverse) { - Point vOverS = v * sInverse * 0.5; - Point vOverS_squared = -(vOverS * vOverS); - return {std::exp(vOverS_squared.x), std::exp(vOverS_squared.y)}; -} - -static Scalar kTwoOverSqrtPi = 2.0 / std::sqrt(kPi); - -// use crate::math::compute_erf7; -static Scalar computeErf7(Scalar x) { - x *= kTwoOverSqrtPi; - float xx = x * x; - x = x + (0.24295 + (0.03395 + 0.0104 * xx) * xx) * (x * xx); - return x / sqrt(1.0 + x * x); -} - -static Point NegPos(Scalar v) { - return {std::min(v, 0.0f), std::max(v, 0.0f)}; -} - -static bool SetupFragInfo( - RRectBlurPipeline::FragmentShader::FragInfo& frag_info, - Scalar blurSigma, - Point center, - Point rSize, - Scalar radius) { - Scalar sigma = std::max(blurSigma * kSqrt2, 1.f); - - frag_info.center = rSize * 0.5f; - frag_info.minEdge = std::min(rSize.x, rSize.y); - double rMax = 0.5 * frag_info.minEdge; - double r0 = std::min(std::hypot(radius, sigma * 1.15), rMax); - frag_info.r1 = std::min(std::hypot(radius, sigma * 2.0), rMax); - - frag_info.exponent = 2.0 * frag_info.r1 / r0; - - frag_info.sInv = 1.0 / sigma; - - // Pull in long end (make less eccentric). - Point eccentricV = eccentricity(rSize, frag_info.sInv); - double delta = 1.25 * sigma * (eccentricV.x - eccentricV.y); - rSize += NegPos(delta); - - frag_info.adjust = rSize * 0.5 - frag_info.r1; - frag_info.exponentInv = 1.0 / frag_info.exponent; - frag_info.scale = - 0.5 * computeErf7(frag_info.sInv * 0.5 * - (std::max(rSize.x, rSize.y) - 0.5 * radius)); - - return frag_info.center.IsFinite() && // - frag_info.adjust.IsFinite() && // - std::isfinite(frag_info.minEdge) && // - std::isfinite(frag_info.r1) && // - std::isfinite(frag_info.exponent) && // - std::isfinite(frag_info.sInv) && // - std::isfinite(frag_info.exponentInv) && // - std::isfinite(frag_info.scale); -} - -std::optional SolidRRectBlurContents::GetCoverage( - const Entity& entity) const { - if (!rect_.has_value()) { - return std::nullopt; - } - - Scalar radius = PadForSigma(sigma_.sigma); - - return rect_->Expand(radius).TransformBounds(entity.GetTransform()); -} - -bool SolidRRectBlurContents::Render(const ContentContext& renderer, - const Entity& entity, - RenderPass& pass) const { - if (!rect_.has_value()) { - return true; - } - - using VS = RRectBlurPipeline::VertexShader; - using FS = RRectBlurPipeline::FragmentShader; - - Matrix basis_invert = entity.GetTransform().Basis().Invert(); - Vector2 max_sigmas = - Vector2((basis_invert * Vector2(500.f, 0.f)).GetLength(), - (basis_invert * Vector2(0.f, 500.f)).GetLength()); - Scalar max_sigma = std::min(max_sigmas.x, max_sigmas.y); - // Clamp the max kernel width/height to 1000 (@ 2x) to limit the extent - // of the blur and to kEhCloseEnough to prevent NaN calculations - // trying to evaluate a Gaussian distribution with a sigma of 0. - auto blur_sigma = std::clamp(sigma_.sigma, kEhCloseEnough, max_sigma); - // Increase quality by making the radius a bit bigger than the typical - // sigma->radius conversion we use for slower blurs. - Scalar blur_radius = PadForSigma(blur_sigma); - Rect positive_rect = rect_->GetPositive(); - Scalar left = -blur_radius; - Scalar top = -blur_radius; - Scalar right = positive_rect.GetWidth() + blur_radius; - Scalar bottom = positive_rect.GetHeight() + blur_radius; - - std::array vertices = { - VS::PerVertexData{Point(left, top)}, - VS::PerVertexData{Point(right, top)}, - VS::PerVertexData{Point(left, bottom)}, - VS::PerVertexData{Point(right, bottom)}, - }; - - ContentContextOptions opts = OptionsFromPassAndEntity(pass, entity); - opts.primitive_type = PrimitiveType::kTriangleStrip; - Color color = color_; - if (entity.GetBlendMode() == BlendMode::kClear) { - opts.is_for_rrect_blur_clear = true; - color = Color::White(); - } - - VS::FrameInfo frame_info; - frame_info.mvp = Entity::GetShaderTransform( - entity.GetShaderClipDepth(), pass, - entity.GetTransform() * - Matrix::MakeTranslation(positive_rect.GetOrigin())); - - FS::FragInfo frag_info; - frag_info.color = color; - Scalar radius = std::min(std::clamp(corner_radii_.width, kEhCloseEnough, - positive_rect.GetWidth() * 0.5f), - std::clamp(corner_radii_.height, kEhCloseEnough, - positive_rect.GetHeight() * 0.5f)); - if (!SetupFragInfo(frag_info, blur_sigma, positive_rect.GetCenter(), - Point(positive_rect.GetSize()), radius)) { - return true; - } - - auto& host_buffer = renderer.GetTransientsBuffer(); - pass.SetCommandLabel("RRect Shadow"); - pass.SetPipeline(renderer.GetRRectBlurPipeline(opts)); - pass.SetVertexBuffer(CreateVertexBuffer(vertices, host_buffer)); - - VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info)); - FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); - - if (!pass.Draw().ok()) { - return false; - } - - return true; -} - -bool SolidRRectBlurContents::ApplyColorFilter( - const ColorFilterProc& color_filter_proc) { - color_ = color_filter_proc(color_); - return true; -} - } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/solid_rrect_blur_contents.h b/engine/src/flutter/impeller/entity/contents/solid_rrect_blur_contents.h index d0aa55890ba..a7c6b446778 100644 --- a/engine/src/flutter/impeller/entity/contents/solid_rrect_blur_contents.h +++ b/engine/src/flutter/impeller/entity/contents/solid_rrect_blur_contents.h @@ -11,6 +11,7 @@ #include "impeller/entity/contents/contents.h" #include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/solid_rrect_like_blur_contents.h" #include "impeller/geometry/color.h" namespace impeller { @@ -18,39 +19,19 @@ namespace impeller { /// @brief Draws a fast solid color blur of an rounded rectangle. Only supports /// RRects with fully symmetrical radii. Also produces correct results for /// rectangles (corner_radius=0) and circles (corner_radius=width/2=height/2). -class SolidRRectBlurContents final : public Contents { +class SolidRRectBlurContents final : public SolidRRectLikeBlurContents { public: SolidRRectBlurContents(); ~SolidRRectBlurContents() override; - void SetRRect(std::optional rect, Size corner_radii = {}); - - void SetSigma(Sigma sigma); - - void SetColor(Color color); - - Color GetColor() const; - - // |Contents| - std::optional GetCoverage(const Entity& entity) const override; - - // |Contents| - bool Render(const ContentContext& renderer, - const Entity& entity, - RenderPass& pass) const override; - - // |Contents| - [[nodiscard]] bool ApplyColorFilter( - const ColorFilterProc& color_filter_proc) override; + protected: + // |SolidRRectLikeBlurContents| + bool SetPassInfo(RenderPass& pass, + const ContentContext& renderer, + PassContext& pass_context) const override; private: - std::optional rect_; - Size corner_radii_; - Sigma sigma_; - - Color color_; - SolidRRectBlurContents(const SolidRRectBlurContents&) = delete; SolidRRectBlurContents& operator=(const SolidRRectBlurContents&) = delete; diff --git a/engine/src/flutter/impeller/entity/contents/solid_rrect_like_blur_contents.cc b/engine/src/flutter/impeller/entity/contents/solid_rrect_like_blur_contents.cc new file mode 100644 index 00000000000..12f7b754951 --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/solid_rrect_like_blur_contents.cc @@ -0,0 +1,197 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/entity/contents/solid_rrect_like_blur_contents.h" +#include + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/color.h" +#include "impeller/geometry/constants.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/vertex_buffer_builder.h" + +namespace impeller { + +namespace { +// Generous padding to make sure blurs with large sigmas are fully visible. Used +// to expand the geometry around the rrect. Larger sigmas have more subtle +// gradients so they need larger padding to avoid hard cutoffs. Sigma is +// maximized to 3.5 since that should cover 99.95% of all samples. 3.0 should +// cover 99.7% but that was seen to be not enough for large sigmas. +Scalar PadForSigma(Scalar sigma) { + Scalar scalar = std::min((1.0f / 47.6f) * sigma + 2.5f, 3.5f); + return sigma * scalar; +} +} // namespace + +SolidRRectLikeBlurContents::SolidRRectLikeBlurContents() = default; + +SolidRRectLikeBlurContents::~SolidRRectLikeBlurContents() = default; + +void SolidRRectLikeBlurContents::SetShape(Rect rect, Scalar corner_radius) { + rect_ = rect; + corner_radius_ = corner_radius; +} + +void SolidRRectLikeBlurContents::SetSigma(Sigma sigma) { + sigma_ = sigma; +} + +void SolidRRectLikeBlurContents::SetColor(Color color) { + color_ = color.Premultiply(); +} + +Color SolidRRectLikeBlurContents::GetColor() const { + return color_; +} + +static Point eccentricity(Point v, double sInverse) { + Point vOverS = v * sInverse * 0.5; + Point vOverS_squared = -(vOverS * vOverS); + return {std::exp(vOverS_squared.x), std::exp(vOverS_squared.y)}; +} + +static Scalar kTwoOverSqrtPi = 2.0 / std::sqrt(kPi); + +// use crate::math::compute_erf7; +static Scalar computeErf7(Scalar x) { + x *= kTwoOverSqrtPi; + float xx = x * x; + x = x + (0.24295 + (0.03395 + 0.0104 * xx) * xx) * (x * xx); + return x / sqrt(1.0 + x * x); +} + +static Point NegPos(Scalar v) { + return {std::min(v, 0.0f), std::max(v, 0.0f)}; +} + +bool SolidRRectLikeBlurContents::PopulateFragContext(PassContext& pass_context, + Scalar blurSigma, + Point center, + Point rSize, + Scalar radius) { + Scalar sigma = std::max(blurSigma * kSqrt2, 1.f); + + pass_context.center = rSize * 0.5f; + pass_context.minEdge = std::min(rSize.x, rSize.y); + double rMax = 0.5 * pass_context.minEdge; + double r0 = std::min(std::hypot(radius, sigma * 1.15), rMax); + pass_context.r1 = std::min(std::hypot(radius, sigma * 2.0), rMax); + + pass_context.exponent = 2.0 * pass_context.r1 / r0; + + pass_context.sInv = 1.0 / sigma; + + // Pull in long end (make less eccentric). + Point eccentricV = eccentricity(rSize, pass_context.sInv); + double delta = 1.25 * sigma * (eccentricV.x - eccentricV.y); + rSize += NegPos(delta); + + pass_context.adjust = rSize * 0.5 - pass_context.r1; + pass_context.exponentInv = 1.0 / pass_context.exponent; + pass_context.scale = + 0.5 * computeErf7(pass_context.sInv * 0.5 * + (std::max(rSize.x, rSize.y) - 0.5 * radius)); + + return pass_context.center.IsFinite() && // + pass_context.adjust.IsFinite() && // + std::isfinite(pass_context.minEdge) && // + std::isfinite(pass_context.r1) && // + std::isfinite(pass_context.exponent) && // + std::isfinite(pass_context.sInv) && // + std::isfinite(pass_context.exponentInv) && // + std::isfinite(pass_context.scale); +} + +std::optional SolidRRectLikeBlurContents::GetCoverage( + const Entity& entity) const { + Scalar radius = PadForSigma(sigma_.sigma); + + return rect_.Expand(radius).TransformBounds(entity.GetTransform()); +} + +bool SolidRRectLikeBlurContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + using VS = RrectLikeBlurVertexShader; + + Matrix basis_invert = entity.GetTransform().Basis().Invert(); + Vector2 max_sigmas = + Vector2((basis_invert * Vector2(500.f, 0.f)).GetLength(), + (basis_invert * Vector2(0.f, 500.f)).GetLength()); + Scalar max_sigma = std::min(max_sigmas.x, max_sigmas.y); + // Clamp the max kernel width/height to 1000 (@ 2x) to limit the extent + // of the blur and to kEhCloseEnough to prevent NaN calculations + // trying to evaluate a Gaussian distribution with a sigma of 0. + auto blur_sigma = std::clamp(sigma_.sigma, kEhCloseEnough, max_sigma); + // Increase quality by making the radius a bit bigger than the typical + // sigma->radius conversion we use for slower blurs. + Scalar blur_radius = PadForSigma(blur_sigma); + Rect positive_rect = rect_.GetPositive(); + Scalar left = -blur_radius; + Scalar top = -blur_radius; + Scalar right = positive_rect.GetWidth() + blur_radius; + Scalar bottom = positive_rect.GetHeight() + blur_radius; + + ContentContextOptions opts = OptionsFromPassAndEntity(pass, entity); + opts.primitive_type = PrimitiveType::kTriangleStrip; + Color color = color_; + if (entity.GetBlendMode() == BlendMode::kClear) { + opts.is_for_rrect_blur_clear = true; + color = Color::White(); + } + + std::array vertices = { + VS::PerVertexData{Point(left, top)}, + VS::PerVertexData{Point(right, top)}, + VS::PerVertexData{Point(left, bottom)}, + VS::PerVertexData{Point(right, bottom)}, + }; + + PassContext pass_context = { + .opts = opts, + }; + + Scalar radius = std::min(std::clamp(corner_radius_, kEhCloseEnough, + positive_rect.GetWidth() * 0.5f), + std::clamp(corner_radius_, kEhCloseEnough, + positive_rect.GetHeight() * 0.5f)); + if (!PopulateFragContext(pass_context, blur_sigma, positive_rect.GetCenter(), + Point(positive_rect.GetSize()), radius)) { + return true; + } + + if (!SetPassInfo(pass, renderer, pass_context)) { + return true; + } + + VS::FrameInfo frame_info; + frame_info.mvp = Entity::GetShaderTransform( + entity.GetShaderClipDepth(), pass, + entity.GetTransform() * + Matrix::MakeTranslation(positive_rect.GetOrigin())); + + auto& host_buffer = renderer.GetTransientsBuffer(); + pass.SetVertexBuffer(CreateVertexBuffer(vertices, host_buffer)); + VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info)); + + if (!pass.Draw().ok()) { + return false; + } + + return true; +} + +bool SolidRRectLikeBlurContents::ApplyColorFilter( + const ColorFilterProc& color_filter_proc) { + color_ = color_filter_proc(color_); + return true; +} + +Vector4 SolidRRectLikeBlurContents::Concat(Vector2& a, Vector2& b) { + return {a.x, a.y, b.x, b.y}; +} + +} // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/solid_rrect_like_blur_contents.h b/engine/src/flutter/impeller/entity/contents/solid_rrect_like_blur_contents.h new file mode 100644 index 00000000000..eb5ca8021b0 --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/solid_rrect_like_blur_contents.h @@ -0,0 +1,91 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_ENTITY_CONTENTS_SOLID_RRECT_LIKE_BLUR_CONTENTS_H_ +#define FLUTTER_IMPELLER_ENTITY_CONTENTS_SOLID_RRECT_LIKE_BLUR_CONTENTS_H_ + +#include +#include +#include + +#include "impeller/entity/contents/contents.h" +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/geometry/color.h" + +namespace impeller { + +/// @brief A base class for SolidRRectBlurContents and +/// SolidRSuperellipseBlurContents. +class SolidRRectLikeBlurContents : public Contents { + public: + ~SolidRRectLikeBlurContents() override; + + void SetShape(Rect rect, Scalar corner_radius); + + void SetSigma(Sigma sigma); + + void SetColor(Color color); + + Color GetColor() const; + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + // |Contents| + [[nodiscard]] bool ApplyColorFilter( + const ColorFilterProc& color_filter_proc) override; + + protected: + struct PassContext { + // General info + ContentContextOptions opts; + // Frag info + Point center; + Point adjust; + Scalar minEdge; + Scalar r1; + Scalar exponent; + Scalar sInv; + Scalar exponentInv; + Scalar scale; + }; + + SolidRRectLikeBlurContents(); + + virtual bool SetPassInfo(RenderPass& pass, + const ContentContext& renderer, + PassContext& pass_context) const = 0; + + Rect GetRect() const { return rect_; } + Scalar GetCornerRadius() const { return corner_radius_; } + Sigma GetSigma() const { return sigma_; } + + static Vector4 Concat(Vector2& a, Vector2& b); + + private: + static bool PopulateFragContext(PassContext& pass_context, + Scalar blurSigma, + Point center, + Point rSize, + Scalar radius); + + Rect rect_; + Scalar corner_radius_; + Sigma sigma_; + Color color_; + + SolidRRectLikeBlurContents(const SolidRRectLikeBlurContents&) = delete; + + SolidRRectLikeBlurContents& operator=(const SolidRRectLikeBlurContents&) = + delete; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_SOLID_RRECT_LIKE_BLUR_CONTENTS_H_ diff --git a/engine/src/flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.cc b/engine/src/flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.cc new file mode 100644 index 00000000000..f9fbe4b0222 --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.cc @@ -0,0 +1,91 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/entity/contents/solid_rsuperellipse_blur_contents.h" +#include + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/color.h" +#include "impeller/geometry/constants.h" +#include "impeller/geometry/round_superellipse_param.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/vertex_buffer_builder.h" + +namespace impeller { + +namespace { +Vector3 Concat3(Size a, Scalar b) { + return {a.width, a.height, b}; +} +} // namespace + +SolidRSuperellipseBlurContents::SolidRSuperellipseBlurContents() = default; + +SolidRSuperellipseBlurContents::~SolidRSuperellipseBlurContents() = default; + +bool SolidRSuperellipseBlurContents::SetPassInfo( + RenderPass& pass, + const ContentContext& renderer, + PassContext& pass_context) const { + using FS = RSuperellipseBlurPipeline::FragmentShader; + + FS::FragInfo frag_info; + frag_info.color = GetColor(); + frag_info.center_adjust = Concat(pass_context.center, pass_context.adjust); + frag_info.r1_exponent_exponentInv = + Vector3(pass_context.r1, pass_context.exponent, pass_context.exponentInv); + frag_info.sInv_minEdge_scale = + Vector3(pass_context.sInv, pass_context.minEdge, pass_context.scale); + + // Additional math for RSuperellipse. See the frag file for explanation. + Scalar radius = GetCornerRadius(); + Rect rect = GetRect(); + RoundSuperellipseParam param = + RoundSuperellipseParam::MakeBoundsRadius(rect, radius); + + // Avoid 0-division error when radius is 0. + Scalar retractionDepth = fmax(radius, 0.001); + + frag_info.halfAxes_retractionDepth = + Concat3(rect.GetSize() / 2, retractionDepth); + + auto compute_info = [radius](RoundSuperellipseParam::Octant& octant) { + if (octant.se_n < 1) { // A rectangular corner + // Use a large split_radian, which is equivalent to kPiOver4 but avoids + // floating errors. + const Scalar kLargeSplitRadian = kPiOver2; + const Scalar kReallyLargeN = 1e10; + return Vector4(kLargeSplitRadian, 0, kReallyLargeN, -1.0 / kReallyLargeN); + } + Scalar n = octant.se_n; + Point split_point = Point(octant.se_a - radius, octant.se_a); + Scalar split_radian = std::atan2(split_point.x, split_point.y); + Scalar split_retraction = + (1 - pow(1 + pow(tan(split_radian), n), -1.0 / n)) * octant.se_a; + return Vector4(split_radian, split_retraction, n, -1.0 / n); + }; + frag_info.infoTop = compute_info(param.top_right.top); + frag_info.infoRight = compute_info(param.top_right.right); + + auto compute_poly = [radius](RoundSuperellipseParam::Octant& octant) { + // Imperical formula that decreases the initial slope as the a/r ratio + // increases. + Scalar v0 = radius / octant.se_a * 3; + // A polynomial that satisfies f(0) = 1, f'(0) = v0, f(1) = 0, f'(1) = 0 + return Vector4(v0 + 2.0, -2.0 * v0 - 3.0, v0, 1.0); + }; + frag_info.polyTop = compute_poly(param.top_right.top); + frag_info.polyRight = compute_poly(param.top_right.right); + + // Back to pass setup. + auto& host_buffer = renderer.GetTransientsBuffer(); + pass.SetCommandLabel("RSuperellipse Shadow"); + pass.SetPipeline(renderer.GetRSuperellipseBlurPipeline(pass_context.opts)); + + FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); + return true; +} + +} // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.h b/engine/src/flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.h new file mode 100644 index 00000000000..1896b6e1f5a --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/solid_rsuperellipse_blur_contents.h @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_ENTITY_CONTENTS_SOLID_RSUPERELLIPSE_BLUR_CONTENTS_H_ +#define FLUTTER_IMPELLER_ENTITY_CONTENTS_SOLID_RSUPERELLIPSE_BLUR_CONTENTS_H_ + +#include +#include +#include + +#include "impeller/entity/contents/contents.h" +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/solid_rrect_like_blur_contents.h" +#include "impeller/geometry/color.h" + +namespace impeller { + +/// @brief Draws a fast solid color blur of an rounded superellipse. Only +/// supports RSuperellipses with fully symmetrical radii. Also produces correct +/// results for rectangles (corner_radius=0) and circles +/// (corner_radius=width/2=height/2). +class SolidRSuperellipseBlurContents final : public SolidRRectLikeBlurContents { + public: + SolidRSuperellipseBlurContents(); + + ~SolidRSuperellipseBlurContents() override; + + private: + // |SolidRRectLikeBlurContents| + bool SetPassInfo(RenderPass& pass, + const ContentContext& renderer, + PassContext& pass_context) const override; + + SolidRSuperellipseBlurContents(const SolidRSuperellipseBlurContents&) = + delete; + + SolidRSuperellipseBlurContents& operator=( + const SolidRSuperellipseBlurContents&) = delete; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_SOLID_RSUPERELLIPSE_BLUR_CONTENTS_H_ diff --git a/engine/src/flutter/impeller/entity/entity_unittests.cc b/engine/src/flutter/impeller/entity/entity_unittests.cc index 6dca2c7b893..b0967198d45 100644 --- a/engine/src/flutter/impeller/entity/entity_unittests.cc +++ b/engine/src/flutter/impeller/entity/entity_unittests.cc @@ -1417,7 +1417,7 @@ TEST_P(EntityTest, RRectShadowTest) { Rect::MakeLTRB(top_left.x, top_left.y, bottom_right.x, bottom_right.y); auto contents = std::make_unique(); - contents->SetRRect(rect, {corner_radius, corner_radius}); + contents->SetShape(rect, corner_radius); contents->SetColor(color); contents->SetSigma(Radius(blur_radius)); diff --git a/engine/src/flutter/impeller/entity/shaders/rrect_blur.frag b/engine/src/flutter/impeller/entity/shaders/rrect_blur.frag index 934262de90b..b20851f87c4 100644 --- a/engine/src/flutter/impeller/entity/shaders/rrect_blur.frag +++ b/engine/src/flutter/impeller/entity/shaders/rrect_blur.frag @@ -6,59 +6,36 @@ // post "Blurred rounded rectangles": // https://web.archive.org/web/20231103044404/https://raphlinus.github.io/graphics/2020/04/21/blurred-rounded-rects.html +// NOTICE: Changes made to this file should be mirrored to +// rsuperellipse_blur.frag, which is based on this algorithm. + precision highp float; #include #include +#include #include uniform FragInfo { - f16vec4 color; - vec2 center; - vec2 adjust; - float minEdge; - float r1; - float exponent; - float sInv; - float exponentInv; - float scale; + vec4 color; + vec4 center_adjust; + vec3 r1_exponent_exponentInv; + vec3 sInv_minEdge_scale; } frag_info; in vec2 v_position; -out f16vec4 frag_color; - -const float kTwoOverSqrtPi = 2.0 / sqrt(3.1415926); - -float maxXY(vec2 v) { - return max(v.x, v.y); -} - -// use crate::math::compute_erf7; -float computeErf7(float x) { - x *= kTwoOverSqrtPi; - float xx = x * x; - x = x + (0.24295 + (0.03395 + 0.0104 * xx) * xx) * (x * xx); - return x / sqrt(1.0 + x * x); -} - -// The length formula, but with an exponent other than 2 -float powerDistance(vec2 p) { - float xp = POW(p.x, frag_info.exponent); - float yp = POW(p.y, frag_info.exponent); - return POW(xp + yp, frag_info.exponentInv); -} +out vec4 frag_color; void main() { - vec2 adjusted = abs(v_position - frag_info.center) - frag_info.adjust; + vec2 center = frag_info.center_adjust.xy; + vec2 adjust = frag_info.center_adjust.zw; - float dPos = powerDistance(max(adjusted, 0.0)); - float dNeg = min(maxXY(adjusted), 0.0); - float d = dPos + dNeg - frag_info.r1; - float z = - frag_info.scale * (computeErf7(frag_info.sInv * (frag_info.minEdge + d)) - - computeErf7(frag_info.sInv * d)); + vec2 centered = abs(v_position - center); + float d = + computeRRectDistance(centered, adjust, frag_info.r1_exponent_exponentInv); + float z = computeRRectFade(d, frag_info.sInv_minEdge_scale); frag_color = frag_info.color * float16_t(z); } diff --git a/engine/src/flutter/impeller/entity/shaders/rrect_blur.vert b/engine/src/flutter/impeller/entity/shaders/rrect_like_blur.vert similarity index 100% rename from engine/src/flutter/impeller/entity/shaders/rrect_blur.vert rename to engine/src/flutter/impeller/entity/shaders/rrect_like_blur.vert diff --git a/engine/src/flutter/impeller/entity/shaders/rsuperellipse_blur.frag b/engine/src/flutter/impeller/entity/shaders/rsuperellipse_blur.frag new file mode 100644 index 00000000000..f1bddcbd2cc --- /dev/null +++ b/engine/src/flutter/impeller/entity/shaders/rsuperellipse_blur.frag @@ -0,0 +1,122 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This algorithm is an adaptation of the RRect blur technique +// (`rrect_blur.frag`), tailored specifically for the RSuperellipse shape. The +// core distinction lies in how RSuperellipse curves are slightly drawn inward +// compared to RRect's. +// +// We begin by mapping the fragment's position to polar coordinates within the +// octant, with the angle `theta` ranging from 0 to pi/4. From this angle, we +// calculate a `baseRetraction`. This `baseRetraction` is crucial because it +// ensures the shape precisely matches an RSuperellipse when the blur `sigma` is +// very small. +// +// As we move further away from the edge (indicated by `d`, the radial +// distance), this retraction is progressively scaled down by `retractionDepth`. +// This scaling diminishes the influence of the retraction when the blur is +// significant, reflecting the idea that for larger blur values, subtle +// geometric differences like this retraction become visually insignificant. +// +// Essentially, the `baseRetraction` represents the exact distance between the +// RRect and RSuperellipse curves at a given `theta`. +// +// We split the angular range at `splitRadian`. This is a critical point because +// the RRect's geometry, and thus its formula, changes significantly: one side +// is a straight edge, the other a rounded corner. Our retraction calculation +// must adapt to these distinct behaviors. +// +// When `theta` is less than `splitRadian`, the RRect's edge is a straight line, +// and the RSuperellipse follows a well-defined superellipse formula. This +// allows us to directly compute the distance between the two curves. +// +// However, for `theta` greater than `splitRadian`, the exact mathematical +// expressions for both curves become too complex to evaluate efficiently. In +// these cases, we approximate the distance using a heuristic polynomial fit. +// This approximation is based on the observed behavior of the difference curve: +// it smoothly rises, then falls, eventually reaching zero with a zero slope. +// +// To see a visual representation of how this algorithm affects the shadow, +// refer to the `RoundSuperellipseShadowComparison` playground. +// +// (Note that the `theta` used throughout this file is distinct from the `theta` +// variable used in `RoundSuperellipseParam`.) + +precision highp float; + +#include +#include +#include +#include + +uniform FragInfo { + vec4 color; + vec4 center_adjust; + vec3 r1_exponent_exponentInv; + vec3 sInv_minEdge_scale; + + vec4 halfAxes_retractionDepth; + // Information to compute retraction for two octants respectively. + // [splitRadian, splitGap, n, nInvNeg] + vec4 infoTop; + vec4 infoRight; + // Polynomial coeffs + vec4 polyTop; + vec4 polyRight; +} +frag_info; + +in vec2 v_position; + +out vec4 frag_color; + +const float kPiOverFour = 3.1415926 / 4.0; + +void main() { + vec2 center = frag_info.center_adjust.xy; + vec2 adjust = frag_info.center_adjust.zw; + + vec2 centered = abs(v_position - center); + float d = + computeRRectDistance(centered, adjust, frag_info.r1_exponent_exponentInv); + + /**** Start of RSuperellipse math ****/ + + vec2 halfAxes = frag_info.halfAxes_retractionDepth.xy; + float retractionDepth = frag_info.halfAxes_retractionDepth[2]; + float octantOffset = halfAxes.y - halfAxes.x; + + bool useTop = (centered.y - octantOffset) > centered.x; + vec4 angularInfo = useTop ? frag_info.infoTop : frag_info.infoRight; + float theta = atan(useTop ? centered.x / (centered.y - octantOffset) + : centered.y / (centered.x + octantOffset)); + + float splitRadian = angularInfo[0]; + float splitGap = angularInfo[1]; + float n = angularInfo[2]; + float nInvNeg = angularInfo[3]; + + float baseRetraction; + if (theta < splitRadian) { + float a = useTop ? halfAxes.x : halfAxes.y; + baseRetraction = (1.0 - POW(1.0 + POW(tan(theta), n), nInvNeg)) * a; + } else { + float t = (theta - splitRadian) / (kPiOverFour - splitRadian); + float tt = t * t; + float ttt = tt * t; + float retProg = dot(vec4(ttt, tt, t, 1.0), + useTop ? frag_info.polyTop : frag_info.polyRight); + // Squaring `retProg` improves results empirically by boosting values > 1 + // and dampening values < 1. + baseRetraction = retProg * retProg * splitGap; + } + float depthProg = smoothstep(-retractionDepth, 0.0, -abs(d)); + d += baseRetraction * depthProg; + + /**** End of RSuperellipse math ****/ + + float z = computeRRectFade(d, frag_info.sInv_minEdge_scale); + + frag_color = frag_info.color * float16_t(z); +} diff --git a/engine/src/flutter/impeller/geometry/round_superellipse_param.cc b/engine/src/flutter/impeller/geometry/round_superellipse_param.cc index d67873b67da..0f0f0c78731 100644 --- a/engine/src/flutter/impeller/geometry/round_superellipse_param.cc +++ b/engine/src/flutter/impeller/geometry/round_superellipse_param.cc @@ -153,6 +153,8 @@ RoundSuperellipseParam::Octant ComputeOctant(Point center, .se_a = a, .se_n = 0, + + .circle_start = {a, a}, }; } @@ -468,6 +470,16 @@ class RoundSuperellipseBuilder { } // namespace +RoundSuperellipseParam RoundSuperellipseParam::MakeBoundsRadius( + const Rect& bounds, + Scalar radius) { + return RoundSuperellipseParam{ + .top_right = ComputeQuadrant(bounds.GetCenter(), bounds.GetRightTop(), + {radius, radius}, {-1, 1}), + .all_corners_same = true, + }; +} + RoundSuperellipseParam RoundSuperellipseParam::MakeBoundsRadii( const Rect& bounds, const RoundingRadii& radii) { diff --git a/engine/src/flutter/impeller/geometry/round_superellipse_param.h b/engine/src/flutter/impeller/geometry/round_superellipse_param.h index bed210adf33..06e90d7f135 100644 --- a/engine/src/flutter/impeller/geometry/round_superellipse_param.h +++ b/engine/src/flutter/impeller/geometry/round_superellipse_param.h @@ -100,6 +100,10 @@ struct RoundSuperellipseParam { const Rect& bounds, const RoundingRadii& radii); + [[nodiscard]] static RoundSuperellipseParam MakeBoundsRadius( + const Rect& bounds, + Scalar radius); + // Returns whether this rounded superellipse contains the point. // // This method does not perform any prescreening such as comparing the point diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index 499e73d3ade..c4b266e34a0 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -5770,10 +5770,10 @@ } } }, - "flutter/impeller/entity/gles/rrect_blur.vert.gles": { + "flutter/impeller/entity/gles/rrect_like_blur.vert.gles": { "Mali-G78": { "core": "Mali-G78", - "filename": "flutter/impeller/entity/gles/rrect_blur.vert.gles", + "filename": "flutter/impeller/entity/gles/rrect_like_blur.vert.gles", "has_uniform_computation": false, "type": "Vertex", "variants": { @@ -5883,7 +5883,7 @@ }, "Mali-T880": { "core": "Mali-T880", - "filename": "flutter/impeller/entity/gles/rrect_blur.vert.gles", + "filename": "flutter/impeller/entity/gles/rrect_like_blur.vert.gles", "has_uniform_computation": false, "type": "Vertex", "variants": { @@ -5927,6 +5927,124 @@ } } }, + "flutter/impeller/entity/gles/rsuperellipse_blur.frag.gles": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/gles/rsuperellipse_blur.frag.gles", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 0, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + "arith_total", + "arith_sfu" + ], + "longest_path_cycles": [ + 1.3125, + 1.2625000476837158, + 0.5625, + 1.3125, + 0.0, + 0.25, + 0.0 + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arith_total", + "arith_fma" + ], + "shortest_path_cycles": [ + 1.125, + 1.125, + 0.484375, + 0.9375, + 0.0, + 0.25, + 0.0 + ], + "total_bound_pipelines": [ + "arith_total", + "arith_fma" + ], + "total_cycles": [ + 1.4249999523162842, + 1.4249999523162842, + 0.737500011920929, + 1.375, + 0.0, + 0.25, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 48, + "work_registers_used": 26 + } + } + }, + "Mali-T880": { + "core": "Mali-T880", + "filename": "flutter/impeller/entity/gles/rsuperellipse_blur.frag.gles", + "has_uniform_computation": false, + "type": "Fragment", + "variants": { + "Main": { + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + "arithmetic" + ], + "longest_path_cycles": [ + 12.210000038146973, + 1.0, + 0.0 + ], + "pipelines": [ + "arithmetic", + "load_store", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arithmetic" + ], + "shortest_path_cycles": [ + 11.550000190734863, + 1.0, + 0.0 + ], + "total_bound_pipelines": [ + "arithmetic" + ], + "total_cycles": [ + 14.666666984558105, + 1.0, + 0.0 + ] + }, + "thread_occupancy": 100, + "uniform_registers_used": 9, + "work_registers_used": 4 + } + } + } + }, "flutter/impeller/entity/gles/runtime_effect.vert.gles": { "Mali-G78": { "core": "Mali-G78", @@ -9351,10 +9469,10 @@ } } }, - "flutter/impeller/entity/rrect_blur.vert.vkspv": { + "flutter/impeller/entity/rrect_like_blur.vert.vkspv": { "Mali-G78": { "core": "Mali-G78", - "filename": "flutter/impeller/entity/rrect_blur.vert.vkspv", + "filename": "flutter/impeller/entity/rrect_like_blur.vert.vkspv", "has_uniform_computation": true, "type": "Vertex", "variants": { @@ -9463,6 +9581,79 @@ } } }, + "flutter/impeller/entity/rsuperellipse_blur.frag.vkspv": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/rsuperellipse_blur.frag.vkspv", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 0, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + "arith_total", + "arith_sfu" + ], + "longest_path_cycles": [ + 1.3125, + 1.2625000476837158, + 0.53125, + 1.3125, + 0.0, + 0.25, + 0.0 + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arith_total", + "arith_fma" + ], + "shortest_path_cycles": [ + 1.125, + 1.125, + 0.484375, + 0.9375, + 0.0, + 0.25, + 0.0 + ], + "total_bound_pipelines": [ + "arith_total", + "arith_fma" + ], + "total_cycles": [ + 1.4249999523162842, + 1.4249999523162842, + 0.699999988079071, + 1.375, + 0.0, + 0.25, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 48, + "work_registers_used": 28 + } + } + } + }, "flutter/impeller/entity/runtime_effect.vert.vkspv": { "Mali-G78": { "core": "Mali-G78",