mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[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.) <img width="389" alt="image" src="https://github.com/user-attachments/assets/67cf4b10-f13f-4e55-bdfd-35d358617f38" /> <img width="365" alt="image" src="https://github.com/user-attachments/assets/6869fa9c-5752-4a11-babe-b6a2d590ebc9" /> ### 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]. <!-- Links --> [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
This commit is contained in:
parent
82a4e3e6db
commit
ea1c1bae8f
@ -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 =
|
||||
|
@ -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'),
|
||||
|
@ -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<RSuperellipseBlur> createState() => _RSuperellipseBlurPageState();
|
||||
}
|
||||
|
||||
class _RSuperellipseBlurPageState extends State<RSuperellipseBlur>
|
||||
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<Color> kColors = <Color>[
|
||||
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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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 <impeller/math.glsl>
|
||||
|
||||
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_
|
@ -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<EntityPassTarget> CreateRenderTarget(
|
||||
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<SolidRRectLikeBlurContents>
|
||||
Canvas::RRectBlurShape::BuildBlurContent() {
|
||||
return std::make_shared<SolidRRectBlurContents>();
|
||||
}
|
||||
|
||||
Geometry& Canvas::RRectBlurShape::BuildGeometry(Rect rect, Scalar radius) {
|
||||
return geom_.emplace(rect, Size{radius, radius});
|
||||
}
|
||||
|
||||
std::shared_ptr<SolidRRectLikeBlurContents>
|
||||
Canvas::RSuperellipseBlurShape::BuildBlurContent() {
|
||||
return std::make_shared<SolidRSuperellipseBlurContents>();
|
||||
}
|
||||
|
||||
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<SolidRRectBlurContents>();
|
||||
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;
|
||||
}
|
||||
|
@ -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<SolidRRectLikeBlurContents> BuildBlurContent() = 0;
|
||||
virtual Geometry& BuildGeometry(Rect rect, Scalar radius) = 0;
|
||||
};
|
||||
|
||||
class RRectBlurShape : public RRectLikeBlurShape {
|
||||
public:
|
||||
std::shared_ptr<SolidRRectLikeBlurContents> BuildBlurContent() override;
|
||||
Geometry& BuildGeometry(Rect rect, Scalar radius) override;
|
||||
|
||||
private:
|
||||
std::optional<RoundRectGeometry> geom_; // optional stack allocation
|
||||
};
|
||||
|
||||
class RSuperellipseBlurShape : public RRectLikeBlurShape {
|
||||
public:
|
||||
std::shared_ptr<SolidRRectLikeBlurContents> BuildBlurContent() override;
|
||||
Geometry& BuildGeometry(Rect rect, Scalar radius) override;
|
||||
|
||||
private:
|
||||
std::optional<RoundSuperellipseGeometry>
|
||||
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.
|
||||
///
|
||||
|
@ -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<flutter::DlColorSource> color_source;
|
||||
{
|
||||
flutter::DlColor dl_color = flutter::DlColor(color.ToARGB());
|
||||
std::vector<flutter::DlColor> colors = {dl_color, dl_color};
|
||||
std::vector<Scalar> 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
|
||||
|
@ -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",
|
||||
|
@ -285,6 +285,7 @@ struct ContentContext::Pipelines {
|
||||
Variants<RadialGradientSSBOFillPipeline> radial_gradient_ssbo_fill;
|
||||
Variants<RadialGradientUniformFillPipeline> radial_gradient_uniform_fill;
|
||||
Variants<RRectBlurPipeline> rrect_blur;
|
||||
Variants<RSuperellipseBlurPipeline> rsuperellipse_blur;
|
||||
Variants<SolidFillPipeline> solid_fill;
|
||||
Variants<SrgbToLinearFilterPipeline> srgb_to_linear_filter;
|
||||
Variants<SweepGradientFillPipeline> 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);
|
||||
|
@ -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;
|
||||
|
@ -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<PorterDuffBlendVertexShader
|
||||
using RadialGradientFillPipeline = GradientPipelineHandle<RadialGradientFillFragmentShader>;
|
||||
using RadialGradientSSBOFillPipeline = GradientPipelineHandle<RadialGradientSsboFillFragmentShader>;
|
||||
using RadialGradientUniformFillPipeline = GradientPipelineHandle<RadialGradientUniformFillFragmentShader>;
|
||||
using RRectBlurPipeline = RenderPipelineHandle<RrectBlurVertexShader, RrectBlurFragmentShader>;
|
||||
using RRectBlurPipeline = RenderPipelineHandle<RrectLikeBlurVertexShader, RrectBlurFragmentShader>;
|
||||
using RSuperellipseBlurPipeline = RenderPipelineHandle<RrectLikeBlurVertexShader, RsuperellipseBlurFragmentShader>;
|
||||
using SolidFillPipeline = RenderPipelineHandle<SolidFillVertexShader, SolidFillFragmentShader>;
|
||||
using SrgbToLinearFilterPipeline = RenderPipelineHandle<FilterPositionVertexShader, SrgbToLinearFilterFragmentShader>;
|
||||
using SweepGradientFillPipeline = GradientPipelineHandle<SweepGradientFillFragmentShader>;
|
||||
|
@ -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> 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<Rect> 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<VS::PerVertexData, 4> 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
|
||||
|
@ -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> rect, Size corner_radii = {});
|
||||
|
||||
void SetSigma(Sigma sigma);
|
||||
|
||||
void SetColor(Color color);
|
||||
|
||||
Color GetColor() const;
|
||||
|
||||
// |Contents|
|
||||
std::optional<Rect> 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> rect_;
|
||||
Size corner_radii_;
|
||||
Sigma sigma_;
|
||||
|
||||
Color color_;
|
||||
|
||||
SolidRRectBlurContents(const SolidRRectBlurContents&) = delete;
|
||||
|
||||
SolidRRectBlurContents& operator=(const SolidRRectBlurContents&) = delete;
|
||||
|
@ -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 <optional>
|
||||
|
||||
#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<Rect> 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<VS::PerVertexData, 4> 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
|
@ -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 <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#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<Rect> 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_
|
@ -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 <optional>
|
||||
|
||||
#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
|
@ -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 <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#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_
|
@ -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<SolidRRectBlurContents>();
|
||||
contents->SetRRect(rect, {corner_radius, corner_radius});
|
||||
contents->SetShape(rect, corner_radius);
|
||||
contents->SetColor(color);
|
||||
contents->SetSigma(Radius(blur_radius));
|
||||
|
||||
|
@ -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 <impeller/gaussian.glsl>
|
||||
#include <impeller/math.glsl>
|
||||
#include <impeller/rrect.glsl>
|
||||
#include <impeller/types.glsl>
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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 <impeller/gaussian.glsl>
|
||||
#include <impeller/math.glsl>
|
||||
#include <impeller/rrect.glsl>
|
||||
#include <impeller/types.glsl>
|
||||
|
||||
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);
|
||||
}
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user