Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 43a1598

Browse files
authored
[Impeller] Directly tessellate stroked circles. (#48586)
Similar work as done for filled circles in #48103, stroked circles can also be directly tessellated using the same internal data structures used for filled circles.
1 parent 039439c commit 43a1598

11 files changed

+381
-63
lines changed

impeller/aiks/aiks_unittests.cc

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2060,7 +2060,7 @@ TEST_P(AiksTest, DrawLinesRenderCorrectly) {
20602060
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
20612061
}
20622062

2063-
TEST_P(AiksTest, FillCirclesRenderCorrectly) {
2063+
TEST_P(AiksTest, FilledCirclesRenderCorrectly) {
20642064
Canvas canvas;
20652065
canvas.Scale(GetContentScale());
20662066
Paint paint;
@@ -2071,6 +2071,9 @@ TEST_P(AiksTest, FillCirclesRenderCorrectly) {
20712071
Color::Crimson(),
20722072
};
20732073

2074+
paint.color = Color::White();
2075+
canvas.DrawPaint(paint);
2076+
20742077
int c_index = 0;
20752078
int radius = 600;
20762079
while (radius > 0) {
@@ -2083,6 +2086,100 @@ TEST_P(AiksTest, FillCirclesRenderCorrectly) {
20832086
}
20842087
}
20852088

2089+
std::vector<Color> gradient_colors = {
2090+
Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
2091+
Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
2092+
Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
2093+
Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
2094+
Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
2095+
Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
2096+
Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
2097+
std::vector<Scalar> stops = {
2098+
0.0,
2099+
(1.0 / 6.0) * 1,
2100+
(1.0 / 6.0) * 2,
2101+
(1.0 / 6.0) * 3,
2102+
(1.0 / 6.0) * 4,
2103+
(1.0 / 6.0) * 5,
2104+
1.0,
2105+
};
2106+
auto texture = CreateTextureForFixture("airplane.jpg",
2107+
/*enable_mipmapping=*/true);
2108+
2109+
paint.color_source = ColorSource::MakeRadialGradient(
2110+
{500, 600}, 75, std::move(gradient_colors), std::move(stops),
2111+
Entity::TileMode::kMirror, {});
2112+
canvas.DrawCircle({500, 600}, 100, paint);
2113+
2114+
paint.color_source = ColorSource::MakeImage(
2115+
texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {},
2116+
Matrix::MakeTranslation({700, 200}));
2117+
canvas.DrawCircle({800, 300}, 100, paint);
2118+
2119+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2120+
}
2121+
2122+
TEST_P(AiksTest, StrokedCirclesRenderCorrectly) {
2123+
Canvas canvas;
2124+
canvas.Scale(GetContentScale());
2125+
Paint paint;
2126+
const int color_count = 3;
2127+
Color colors[color_count] = {
2128+
Color::Blue(),
2129+
Color::Green(),
2130+
Color::Crimson(),
2131+
};
2132+
2133+
paint.color = Color::White();
2134+
canvas.DrawPaint(paint);
2135+
2136+
int c_index = 0;
2137+
2138+
auto draw = [&paint, &colors, &c_index](Canvas& canvas, Point center,
2139+
Scalar r, Scalar dr, int n) {
2140+
for (int i = 0; i < n; i++) {
2141+
paint.color = colors[(c_index++) % color_count];
2142+
canvas.DrawCircle(center, r, paint);
2143+
r += dr;
2144+
}
2145+
};
2146+
2147+
paint.style = Paint::Style::kStroke;
2148+
paint.stroke_width = 1;
2149+
draw(canvas, {10, 10}, 2, 2, 14); // r = [2, 28], covers [1,29]
2150+
paint.stroke_width = 5;
2151+
draw(canvas, {10, 10}, 35, 10, 56); // r = [35, 585], covers [30,590]
2152+
2153+
std::vector<Color> gradient_colors = {
2154+
Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
2155+
Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
2156+
Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
2157+
Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
2158+
Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
2159+
Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
2160+
Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
2161+
std::vector<Scalar> stops = {
2162+
0.0,
2163+
(1.0 / 6.0) * 1,
2164+
(1.0 / 6.0) * 2,
2165+
(1.0 / 6.0) * 3,
2166+
(1.0 / 6.0) * 4,
2167+
(1.0 / 6.0) * 5,
2168+
1.0,
2169+
};
2170+
auto texture = CreateTextureForFixture("airplane.jpg",
2171+
/*enable_mipmapping=*/true);
2172+
2173+
paint.color_source = ColorSource::MakeRadialGradient(
2174+
{500, 600}, 75, std::move(gradient_colors), std::move(stops),
2175+
Entity::TileMode::kMirror, {});
2176+
draw(canvas, {500, 600}, 5, 10, 10);
2177+
2178+
paint.color_source = ColorSource::MakeImage(
2179+
texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {},
2180+
Matrix::MakeTranslation({700, 200}));
2181+
draw(canvas, {800, 300}, 5, 10, 10);
2182+
20862183
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
20872184
}
20882185

impeller/aiks/canvas.cc

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -287,18 +287,6 @@ void Canvas::DrawRRect(Rect rect, Point corner_radii, const Paint& paint) {
287287
}
288288

289289
void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
290-
if (paint.style == Paint::Style::kStroke) {
291-
auto circle_path =
292-
PathBuilder{}
293-
.AddCircle(center, radius)
294-
.SetConvexity(Convexity::kConvex)
295-
.SetBounds(Rect::MakeLTRB(center.x - radius, center.y - radius,
296-
center.x + radius, center.y + radius))
297-
.TakePath();
298-
DrawPath(circle_path, paint);
299-
return;
300-
}
301-
302290
Size half_size(radius, radius);
303291
if (AttemptDrawBlurredRRect(
304292
Rect::MakeOriginSize(center - half_size, half_size * 2), radius,
@@ -310,8 +298,12 @@ void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
310298
entity.SetTransform(GetCurrentTransform());
311299
entity.SetClipDepth(GetClipDepth());
312300
entity.SetBlendMode(paint.blend_mode);
313-
entity.SetContents(paint.WithFilters(
314-
paint.CreateContentsForGeometry(Geometry::MakeCircle(center, radius))));
301+
auto geometry =
302+
paint.style == Paint::Style::kStroke
303+
? Geometry::MakeStrokedCircle(center, radius, paint.stroke_width)
304+
: Geometry::MakeCircle(center, radius);
305+
entity.SetContents(
306+
paint.WithFilters(paint.CreateContentsForGeometry(geometry)));
315307

316308
GetCurrentPass().AddEntity(entity);
317309
}

impeller/entity/geometry/ellipse_geometry.cc

Lines changed: 104 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,28 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
#include "impeller/entity/geometry/ellipse_geometry.h"
5+
#include <algorithm>
66

7+
#include "flutter/impeller/entity/geometry/ellipse_geometry.h"
8+
9+
#include "flutter/impeller/entity/geometry/line_geometry.h"
710
#include "flutter/impeller/tessellator/circle_tessellator.h"
811

912
namespace impeller {
1013

1114
EllipseGeometry::EllipseGeometry(Point center, Scalar radius)
12-
: center_(center), radius_(radius) {
15+
: center_(center), radius_(radius), stroke_width_(-1.0) {
16+
FML_DCHECK(radius >= 0);
17+
}
18+
19+
EllipseGeometry::EllipseGeometry(Point center,
20+
Scalar radius,
21+
Scalar stroke_width)
22+
: center_(center),
23+
radius_(radius),
24+
stroke_width_(std::max(stroke_width, 0.0f)) {
1325
FML_DCHECK(radius >= 0);
26+
FML_DCHECK(stroke_width >= 0);
1427
}
1528

1629
GeometryResult EllipseGeometry::GetPositionBuffer(
@@ -20,24 +33,52 @@ GeometryResult EllipseGeometry::GetPositionBuffer(
2033
auto& host_buffer = pass.GetTransientsBuffer();
2134
using VT = SolidFillVertexShader::PerVertexData;
2235

23-
Scalar radius = radius_;
36+
Scalar half_width = stroke_width_ < 0
37+
? 0.0
38+
: LineGeometry::ComputePixelHalfWidth(
39+
entity.GetTransform(), stroke_width_);
40+
Scalar outer_radius = radius_ + half_width;
41+
Scalar inner_radius = half_width <= 0 ? 0.0 : radius_ - half_width;
42+
2443
const Point& center = center_;
2544
std::shared_ptr<Tessellator> tessellator = renderer.GetTessellator();
2645
CircleTessellator circle_tessellator(tessellator, entity.GetTransform(),
27-
radius_);
28-
size_t count = circle_tessellator.GetCircleVertexCount();
29-
auto vertex_buffer = host_buffer.Emplace(
30-
count * sizeof(VT), alignof(VT),
31-
[&circle_tessellator, &center, radius](uint8_t* buffer) {
32-
auto vertices = reinterpret_cast<VT*>(buffer);
33-
circle_tessellator.GenerateCircleTriangleStrip(
34-
[&vertices](const Point& p) { //
35-
*vertices++ = {
36-
.position = p,
37-
};
38-
},
39-
center, radius);
40-
});
46+
outer_radius);
47+
48+
BufferView vertex_buffer;
49+
size_t count;
50+
if (inner_radius > 0) {
51+
count = circle_tessellator.GetStrokedCircleVertexCount();
52+
vertex_buffer = host_buffer.Emplace(
53+
count * sizeof(VT), alignof(VT),
54+
[&circle_tessellator, &count, &center, outer_radius,
55+
inner_radius](uint8_t* buffer) {
56+
auto vertices = reinterpret_cast<VT*>(buffer);
57+
circle_tessellator.GenerateStrokedCircleTriangleStrip(
58+
[&vertices](const Point& p) { //
59+
*vertices++ = {
60+
.position = p,
61+
};
62+
},
63+
center, outer_radius, inner_radius);
64+
FML_DCHECK(vertices == reinterpret_cast<VT*>(buffer) + count);
65+
});
66+
} else {
67+
count = circle_tessellator.GetCircleVertexCount();
68+
vertex_buffer = host_buffer.Emplace(
69+
count * sizeof(VT), alignof(VT),
70+
[&circle_tessellator, &count, &center, outer_radius](uint8_t* buffer) {
71+
auto vertices = reinterpret_cast<VT*>(buffer);
72+
circle_tessellator.GenerateCircleTriangleStrip(
73+
[&vertices](const Point& p) { //
74+
*vertices++ = {
75+
.position = p,
76+
};
77+
},
78+
center, outer_radius);
79+
FML_DCHECK(vertices == reinterpret_cast<VT*>(buffer) + count);
80+
});
81+
}
4182

4283
return GeometryResult{
4384
.type = PrimitiveType::kTriangleStrip,
@@ -65,25 +106,55 @@ GeometryResult EllipseGeometry::GetPositionUVBuffer(
65106
auto uv_transform =
66107
texture_coverage.GetNormalizingTransform() * effect_transform;
67108

68-
Scalar radius = radius_;
109+
Scalar half_width = stroke_width_ < 0
110+
? 0.0
111+
: LineGeometry::ComputePixelHalfWidth(
112+
entity.GetTransform(), stroke_width_);
113+
Scalar outer_radius = radius_ + half_width;
114+
Scalar inner_radius = half_width <= 0 ? 0.0 : radius_ - half_width;
115+
69116
const Point& center = center_;
70117
std::shared_ptr<Tessellator> tessellator = renderer.GetTessellator();
71118
CircleTessellator circle_tessellator(tessellator, entity.GetTransform(),
72-
radius_);
73-
size_t count = circle_tessellator.GetCircleVertexCount();
74-
auto vertex_buffer = host_buffer.Emplace(
75-
count * sizeof(VT), alignof(VT),
76-
[&circle_tessellator, &uv_transform, &center, radius](uint8_t* buffer) {
77-
auto vertices = reinterpret_cast<VT*>(buffer);
78-
circle_tessellator.GenerateCircleTriangleStrip(
79-
[&vertices, &uv_transform](const Point& p) { //
80-
*vertices++ = {
81-
.position = p,
82-
.texture_coords = uv_transform * p,
83-
};
84-
},
85-
center, radius);
86-
});
119+
outer_radius);
120+
121+
BufferView vertex_buffer;
122+
size_t count;
123+
if (inner_radius > 0) {
124+
count = circle_tessellator.GetStrokedCircleVertexCount();
125+
vertex_buffer = host_buffer.Emplace(
126+
count * sizeof(VT), alignof(VT),
127+
[&circle_tessellator, &uv_transform, &count, &center, outer_radius,
128+
inner_radius](uint8_t* buffer) {
129+
auto vertices = reinterpret_cast<VT*>(buffer);
130+
circle_tessellator.GenerateStrokedCircleTriangleStrip(
131+
[&vertices, &uv_transform](const Point& p) { //
132+
*vertices++ = {
133+
.position = p,
134+
.texture_coords = uv_transform * p,
135+
};
136+
},
137+
center, outer_radius, inner_radius);
138+
FML_DCHECK(vertices == reinterpret_cast<VT*>(buffer) + count);
139+
});
140+
} else {
141+
count = circle_tessellator.GetCircleVertexCount();
142+
vertex_buffer = host_buffer.Emplace(
143+
count * sizeof(VT), alignof(VT),
144+
[&circle_tessellator, &uv_transform, &count, &center,
145+
outer_radius](uint8_t* buffer) {
146+
auto vertices = reinterpret_cast<VT*>(buffer);
147+
circle_tessellator.GenerateCircleTriangleStrip(
148+
[&vertices, &uv_transform](const Point& p) { //
149+
*vertices++ = {
150+
.position = p,
151+
.texture_coords = uv_transform * p,
152+
};
153+
},
154+
center, outer_radius);
155+
FML_DCHECK(vertices == reinterpret_cast<VT*>(buffer) + count);
156+
});
157+
}
87158

88159
return GeometryResult{
89160
.type = PrimitiveType::kTriangleStrip,

impeller/entity/geometry/ellipse_geometry.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace impeller {
1111
class EllipseGeometry final : public Geometry {
1212
public:
1313
explicit EllipseGeometry(Point center, Scalar radius);
14+
explicit EllipseGeometry(Point center, Scalar radius, Scalar stroke_width);
1415

1516
~EllipseGeometry() = default;
1617

@@ -59,6 +60,7 @@ class EllipseGeometry final : public Geometry {
5960

6061
Point center_;
6162
Scalar radius_;
63+
Scalar stroke_width_;
6264

6365
EllipseGeometry(const EllipseGeometry&) = delete;
6466

impeller/entity/geometry/geometry.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ std::shared_ptr<Geometry> Geometry::MakeCircle(Point center, Scalar radius) {
123123
return std::make_shared<EllipseGeometry>(center, radius);
124124
}
125125

126+
std::shared_ptr<Geometry> Geometry::MakeStrokedCircle(Point center,
127+
Scalar radius,
128+
Scalar stroke_width) {
129+
return std::make_shared<EllipseGeometry>(center, radius, stroke_width);
130+
}
131+
126132
bool Geometry::CoversArea(const Matrix& transform, const Rect& rect) const {
127133
return false;
128134
}

impeller/entity/geometry/geometry.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ class Geometry {
7070

7171
static std::shared_ptr<Geometry> MakeCircle(Point center, Scalar radius);
7272

73+
static std::shared_ptr<Geometry> MakeStrokedCircle(Point center,
74+
Scalar radius,
75+
Scalar stroke_width);
76+
7377
static std::shared_ptr<Geometry> MakePointField(std::vector<Point> points,
7478
Scalar radius,
7579
bool round);

0 commit comments

Comments
 (0)