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",