Skip to content

Commit e206d86

Browse files
authored
Add oval drawing support to the SDF uber shader (flutter#184903)
Adds Oval rendering to the UberSDF shader, uses UberSDF oval rendering in the canvas behind the feature flag. The SDF doesn't actually find the minimum distance to the oval, it find the distance from the test point to the center of the oval along a straight line - visually looks good though. Fixes flutter#183037. Before this change: <img width="803" height="628" alt="before" src="https://github.com/user-attachments/assets/2a662e3b-3cb2-4ff5-81b1-14dd16457304" /> After this change: <img width="803" height="628" alt="after" src="https://github.com/user-attachments/assets/52985b3d-f2a1-41ff-a05a-c35d9f0198c6" /> Diffs highlighted: <img width="803" height="628" alt="diff" src="https://github.com/user-attachments/assets/fdfdda2a-d9f9-4f18-8cd5-50f96c09718c" /> ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [ ] 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.
1 parent 0911a6e commit e206d86

8 files changed

Lines changed: 199 additions & 32 deletions

File tree

engine/src/flutter/impeller/display_list/canvas.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,24 @@ void Canvas::DrawOval(const Rect& rect, const Paint& paint) {
884884
entity.SetTransform(GetCurrentTransform());
885885
entity.SetBlendMode(paint.blend_mode);
886886

887+
if (renderer_.GetContext()->GetFlags().use_sdfs &&
888+
!paint.mask_blur_descriptor.has_value()) {
889+
UberSDFParameters params;
890+
891+
if (paint.style == Paint::Style::kStroke) {
892+
params = UberSDFParameters::MakeOval(paint.color, rect, paint.stroke);
893+
} else {
894+
params = UberSDFParameters::MakeOval(paint.color, rect, std::nullopt);
895+
}
896+
auto geom = std::make_unique<UberSDFGeometry>(params);
897+
auto contents = UberSDFContents::Make(params, std::move(geom));
898+
899+
auto g = contents->GetGeometry();
900+
901+
AddRenderSDFEntityToCurrentPass(entity, g, paint, std::move(contents));
902+
return;
903+
}
904+
887905
if (paint.style == Paint::Style::kStroke) {
888906
StrokeEllipseGeometry geom(rect, paint.stroke);
889907
AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);

engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Scalar ToShaderType(UberSDFParameters::Type type) {
2626
return 0.0f;
2727
case UberSDFParameters::Type::kRect:
2828
return 1.0f;
29+
case UberSDFParameters::Type::kOval:
30+
return 2.0f;
2931
}
3032
}
3133

engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,18 @@ UberSDFParameters UberSDFParameters::MakeCircle(
4747
.stroke = stroke};
4848
}
4949

50+
UberSDFParameters UberSDFParameters::MakeOval(
51+
Color color,
52+
const Rect& bounds,
53+
std::optional<StrokeParameters> stroke) {
54+
// Size here refers to the extent of the oval along each axis from the center
55+
Point size = Point(bounds.GetSize() * 0.5);
56+
57+
return UberSDFParameters{.type = Type::kOval,
58+
.color = color,
59+
.center = bounds.GetCenter(),
60+
.size = size,
61+
.stroke = stroke};
62+
}
63+
5064
} // namespace impeller

engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ struct UberSDFParameters {
2424
enum class Type {
2525
kCircle,
2626
kRect,
27+
kOval,
2728
};
2829

2930
/// Creates UberSDFParameters for a rectangle.
@@ -37,6 +38,11 @@ struct UberSDFParameters {
3738
Scalar radius,
3839
std::optional<StrokeParameters> stroke);
3940

41+
/// Creates UberSDFParameters for an Oval.
42+
static UberSDFParameters MakeOval(Color color,
43+
const Rect& bounds,
44+
std::optional<StrokeParameters> stroke);
45+
4046
/// The type of shape to render.
4147
Type type;
4248

@@ -46,8 +52,9 @@ struct UberSDFParameters {
4652
/// The center point of the shape in local coordinates.
4753
Point center;
4854

49-
/// The half-extents of the shape. For a rectangle, this is half the width
50-
/// and height. For a circle, this is the radius in both dimensions.
55+
/// For a rectangle, this is half the width and height.
56+
/// For a circle, this is the radius in both dimensions.
57+
/// For an oval, this is half the width and height of the bounds.
5158
Point size;
5259

5360
/// The stroke parameters. If std::nullopt, the shape is filled.

engine/src/flutter/impeller/entity/contents/uber_sdf_parameters_unittests.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,34 @@ TEST(UberSDFParametersTest, MakeStrokeCircle) {
7171
EXPECT_EQ(params.stroke, stroke);
7272
}
7373

74+
TEST(UberSDFParametersTest, MakeFillOval) {
75+
Point center = {25, 25};
76+
Size size = Size(50, 50);
77+
Rect bounds = Rect::MakeOriginSize(Point(), size);
78+
79+
auto params = UberSDFParameters::MakeOval(Color::Red(), bounds, std::nullopt);
80+
81+
EXPECT_EQ(params.type, UberSDFParameters::Type::kOval);
82+
EXPECT_EQ(params.color, Color::Red());
83+
EXPECT_EQ(params.center, center);
84+
EXPECT_EQ(params.size, Point(size) * 0.5f);
85+
EXPECT_FALSE(params.stroke.has_value());
86+
}
87+
88+
TEST(UberSDFParametersTest, MakeStrokeOval) {
89+
Point center = {25, 25};
90+
Size size = Size(50, 50);
91+
Rect bounds = Rect::MakeOriginSize(Point(), size);
92+
StrokeParameters stroke = {.width = 4.0f};
93+
94+
auto params = UberSDFParameters::MakeOval(Color::Red(), bounds, stroke);
95+
96+
EXPECT_EQ(params.type, UberSDFParameters::Type::kOval);
97+
EXPECT_EQ(params.color, Color::Red());
98+
EXPECT_EQ(params.center, center);
99+
EXPECT_EQ(params.size, Point(size) * 0.5f);
100+
EXPECT_EQ(params.stroke, stroke);
101+
}
102+
74103
} // namespace testing
75104
} // namespace impeller

engine/src/flutter/impeller/entity/shaders/uber_sdf.frag

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ out vec4 frag_color;
2323

2424
highp in vec2 v_position;
2525

26+
bool typeIsCircle() {
27+
return abs(frag_info.type - 0.0) < 0.01;
28+
}
29+
30+
bool typeIsRect() {
31+
return abs(frag_info.type - 1.0) < 0.01;
32+
}
33+
34+
bool typeIsOval() {
35+
return abs(frag_info.type - 2.0) < 0.01;
36+
}
37+
2638
float distanceFromCircle(vec2 p, float radius) {
2739
return length(p) - radius;
2840
}
@@ -32,6 +44,67 @@ float distanceFromRect(vec2 p, vec2 b) {
3244
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
3345
}
3446

47+
// Define an ellipse as q(w) = (a*cos(w), b*sin(w)), and p = (x, y) on the
48+
// plane. Let q(w0) be the closest point on q to p, then q(w0) - p is tangent to
49+
// q(w0), and (q(w0) - p) dot q'(w0) = 0. This function uses the Newton-Raphson
50+
// method to find q(w0).
51+
//
52+
// `p` is the coordinate of the point relative to the center of the oval
53+
// `ab` is the extent of the oval from the center to the x and y axis
54+
//
55+
// https://iquilezles.org/articles/ellipsedist/
56+
//
57+
// The MIT License
58+
// Copyright © 2015 Inigo Quilez
59+
// Permission is hereby granted, free of charge, to any person obtaining a copy
60+
// of this software and associated documentation files (the "Software"), to deal
61+
// in the Software without restriction, including without limitation the rights
62+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
63+
// copies of the Software, and to permit persons to whom the Software is
64+
// furnished to do so, subject to the following conditions: The above copyright
65+
// notice and this permission notice shall be included in all copies or
66+
// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS",
67+
// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
68+
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
69+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
70+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
71+
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
72+
// THE USE OR OTHER DEALINGS IN THE SOFTWARE.
73+
// https://www.youtube.com/c/InigoQuilez
74+
// https://iquilezles.org
75+
76+
float distanceFromOval(vec2 p, vec2 ab) {
77+
// The ellipse is symmetric along both axes, do the calculation in the upper
78+
// right quadrant.
79+
p = abs(p);
80+
81+
// Initial guess for w0. Determine whether q is closer to the top of the
82+
// ellipse or closer to the righthand side. Use the top (0) or righthand side
83+
// (pi/2) as the initial guess for w0.
84+
vec2 q = ab * (p - ab);
85+
float w = (q.x < q.y) ? 1.570796327 : 0.0;
86+
for (int i = 0; i < 5; i++) {
87+
vec2 cs = vec2(cos(w), sin(w));
88+
89+
// u = q(w) = (a*cos(w), b*sin(w))
90+
vec2 u = ab * vec2(cs.x, cs.y);
91+
92+
// v = q'(w) = (a*-sin(w), b*cos(w))
93+
vec2 v = ab * vec2(-cs.y, cs.x);
94+
95+
// Newton-Raphson update step, w_n = w_n-1 + f(w_n-1)/f'(w_n-1)
96+
// In this case f(w) = (p - q(w)) dot q'(w) = (p - u) dot v
97+
w = w + dot(p - u, v) / (dot(p - u, u) + dot(v, v));
98+
}
99+
100+
// Compute final point and distance
101+
float d = length(p - ab * vec2(cos(w), sin(w)));
102+
103+
// Return signed distance.
104+
// p is outside the ellipse if (p.x/a)^2 + (p.y/b)^2 > 0
105+
return (dot(p / ab, p / ab) > 1.0) ? d : -d;
106+
}
107+
35108
float distanceFromChamferRect(vec2 p, vec2 b, float chamfer) {
36109
vec2 d = abs(p) - b;
37110

@@ -51,10 +124,12 @@ float distanceFromChamferRect(vec2 p, vec2 b, float chamfer) {
51124
}
52125

53126
float filledSDF(vec2 p) {
54-
if (frag_info.type < 0.5) { // Circle
127+
if (typeIsCircle()) { // Circle
55128
return distanceFromCircle(p, frag_info.size.x);
56-
} else { // Rect
129+
} else if (typeIsRect()) { // Rect
57130
return distanceFromRect(p, frag_info.size);
131+
} else {
132+
return distanceFromOval(p, frag_info.size);
58133
}
59134
}
60135

@@ -63,10 +138,10 @@ float strokedSDF(vec2 p) {
63138
float outer;
64139
float inner;
65140

66-
if (frag_info.type < 0.5) { // Circle
141+
if (typeIsCircle()) {
67142
outer = distanceFromCircle(p, frag_info.size.x + half_stroke);
68143
inner = distanceFromCircle(p, frag_info.size.x - half_stroke);
69-
} else { // Rect
144+
} else if (typeIsRect()) {
70145
if (frag_info.stroke_join < 0.5) { // Miter
71146
// Rectangle expanded by half_stroke
72147
outer = distanceFromRect(p, frag_info.size + half_stroke);
@@ -80,6 +155,9 @@ float strokedSDF(vec2 p) {
80155
outer = distanceFromRect(p, frag_info.size) - half_stroke;
81156
}
82157
inner = distanceFromRect(p, frag_info.size - half_stroke);
158+
} else {
159+
outer = distanceFromOval(p, frag_info.size) - half_stroke;
160+
inner = distanceFromOval(p, frag_info.size) + half_stroke;
83161
}
84162

85163
return max(outer, -inner);

engine/src/flutter/impeller/tools/malioc.json

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8680,18 +8680,18 @@
86808680
"uses_late_zs_update": false,
86818681
"variants": {
86828682
"Main": {
8683-
"fp16_arithmetic": 54,
8683+
"fp16_arithmetic": 55,
86848684
"has_stack_spilling": false,
86858685
"performance": {
86868686
"longest_path_bound_pipelines": [
86878687
"arith_total",
86888688
"arith_fma"
86898689
],
86908690
"longest_path_cycles": [
8691-
0.53125,
8692-
0.53125,
8693-
0.296875,
8694-
0.375,
8691+
2.299999952316284,
8692+
2.299999952316284,
8693+
0.234375,
8694+
1.375,
86958695
0.0,
86968696
0.25,
86978697
0.0
@@ -8723,19 +8723,19 @@
87238723
"arith_fma"
87248724
],
87258725
"total_cycles": [
8726-
1.0750000476837158,
8727-
1.0750000476837158,
8728-
0.484375,
8729-
0.6875,
8726+
5.012499809265137,
8727+
5.012499809265137,
8728+
0.8125,
8729+
2.9375,
87308730
0.0,
87318731
0.25,
87328732
0.0
87338733
]
87348734
},
87358735
"stack_spill_bytes": 0,
87368736
"thread_occupancy": 100,
8737-
"uniform_registers_used": 16,
8738-
"work_registers_used": 20
8737+
"uniform_registers_used": 24,
8738+
"work_registers_used": 32
87398739
}
87408740
}
87418741
},
@@ -8752,7 +8752,7 @@
87528752
"arithmetic"
87538753
],
87548754
"longest_path_cycles": [
8755-
9.239999771118164,
8755+
31.020000457763672,
87568756
1.0,
87578757
2.0
87588758
],
@@ -8765,22 +8765,22 @@
87658765
"arithmetic"
87668766
],
87678767
"shortest_path_cycles": [
8768-
4.619999885559082,
8768+
4.949999809265137,
87698769
1.0,
87708770
2.0
87718771
],
87728772
"total_bound_pipelines": [
87738773
"arithmetic"
87748774
],
87758775
"total_cycles": [
8776-
16.33333396911621,
8776+
45.0,
87778777
1.0,
87788778
2.0
87798779
]
87808780
},
87818781
"thread_occupancy": 100,
87828782
"uniform_registers_used": 3,
8783-
"work_registers_used": 2
8783+
"work_registers_used": 3
87848784
}
87858785
}
87868786
}
@@ -12088,18 +12088,18 @@
1208812088
"uses_late_zs_update": false,
1208912089
"variants": {
1209012090
"Main": {
12091-
"fp16_arithmetic": 56,
12091+
"fp16_arithmetic": 55,
1209212092
"has_stack_spilling": false,
1209312093
"performance": {
1209412094
"longest_path_bound_pipelines": [
1209512095
"arith_total",
1209612096
"arith_fma"
1209712097
],
1209812098
"longest_path_cycles": [
12099-
0.46875,
12100-
0.46875,
12101-
0.28125,
12102-
0.375,
12099+
2.237499952316284,
12100+
2.237499952316284,
12101+
0.21875,
12102+
1.375,
1210312103
0.0,
1210412104
0.25,
1210512105
0.0
@@ -12132,19 +12132,19 @@
1213212132
"arith_fma"
1213312133
],
1213412134
"total_cycles": [
12135-
1.0,
12136-
1.0,
12137-
0.46875,
12138-
0.6875,
12135+
4.925000190734863,
12136+
4.925000190734863,
12137+
0.800000011920929,
12138+
2.9375,
1213912139
0.0,
1214012140
0.25,
1214112141
0.0
1214212142
]
1214312143
},
1214412144
"stack_spill_bytes": 0,
1214512145
"thread_occupancy": 100,
12146-
"uniform_registers_used": 16,
12147-
"work_registers_used": 6
12146+
"uniform_registers_used": 24,
12147+
"work_registers_used": 32
1214812148
}
1214912149
}
1215012150
}

0 commit comments

Comments
 (0)