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

Commit ee39a17

Browse files
committed
[Impeller] Add direct tesselation of circles for DrawCircle and Round end caps
1 parent 0e3a62e commit ee39a17

15 files changed

+809
-89
lines changed

ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
../../../flutter/impeller/entity/contents/vertices_contents_unittests.cc
144144
../../../flutter/impeller/entity/entity_pass_target_unittests.cc
145145
../../../flutter/impeller/entity/entity_unittests.cc
146+
../../../flutter/impeller/entity/geometry/circle_tessellator_unittests.cc
146147
../../../flutter/impeller/entity/geometry/geometry_unittests.cc
147148
../../../flutter/impeller/entity/render_target_cache_unittests.cc
148149
../../../flutter/impeller/fixtures

ci/licenses_golden/licenses_flutter

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3118,8 +3118,12 @@ ORIGIN: ../../../flutter/impeller/entity/entity_pass_target.cc + ../../../flutte
31183118
ORIGIN: ../../../flutter/impeller/entity/entity_pass_target.h + ../../../flutter/LICENSE
31193119
ORIGIN: ../../../flutter/impeller/entity/entity_playground.cc + ../../../flutter/LICENSE
31203120
ORIGIN: ../../../flutter/impeller/entity/entity_playground.h + ../../../flutter/LICENSE
3121+
ORIGIN: ../../../flutter/impeller/entity/geometry/circle_tessellator.cc + ../../../flutter/LICENSE
3122+
ORIGIN: ../../../flutter/impeller/entity/geometry/circle_tessellator.h + ../../../flutter/LICENSE
31213123
ORIGIN: ../../../flutter/impeller/entity/geometry/cover_geometry.cc + ../../../flutter/LICENSE
31223124
ORIGIN: ../../../flutter/impeller/entity/geometry/cover_geometry.h + ../../../flutter/LICENSE
3125+
ORIGIN: ../../../flutter/impeller/entity/geometry/ellipse_geometry.cc + ../../../flutter/LICENSE
3126+
ORIGIN: ../../../flutter/impeller/entity/geometry/ellipse_geometry.h + ../../../flutter/LICENSE
31233127
ORIGIN: ../../../flutter/impeller/entity/geometry/fill_path_geometry.cc + ../../../flutter/LICENSE
31243128
ORIGIN: ../../../flutter/impeller/entity/geometry/fill_path_geometry.h + ../../../flutter/LICENSE
31253129
ORIGIN: ../../../flutter/impeller/entity/geometry/geometry.cc + ../../../flutter/LICENSE
@@ -5880,8 +5884,12 @@ FILE: ../../../flutter/impeller/entity/entity_pass_target.cc
58805884
FILE: ../../../flutter/impeller/entity/entity_pass_target.h
58815885
FILE: ../../../flutter/impeller/entity/entity_playground.cc
58825886
FILE: ../../../flutter/impeller/entity/entity_playground.h
5887+
FILE: ../../../flutter/impeller/entity/geometry/circle_tessellator.cc
5888+
FILE: ../../../flutter/impeller/entity/geometry/circle_tessellator.h
58835889
FILE: ../../../flutter/impeller/entity/geometry/cover_geometry.cc
58845890
FILE: ../../../flutter/impeller/entity/geometry/cover_geometry.h
5891+
FILE: ../../../flutter/impeller/entity/geometry/ellipse_geometry.cc
5892+
FILE: ../../../flutter/impeller/entity/geometry/ellipse_geometry.h
58855893
FILE: ../../../flutter/impeller/entity/geometry/fill_path_geometry.cc
58865894
FILE: ../../../flutter/impeller/entity/geometry/fill_path_geometry.h
58875895
FILE: ../../../flutter/impeller/entity/geometry/geometry.cc

impeller/aiks/aiks_unittests.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2042,6 +2042,32 @@ TEST_P(AiksTest, DrawLinesRenderCorrectly) {
20422042
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
20432043
}
20442044

2045+
TEST_P(AiksTest, FillCirclesRenderCorrectly) {
2046+
Canvas canvas;
2047+
canvas.Scale(GetContentScale());
2048+
Paint paint;
2049+
const int color_count = 3;
2050+
Color colors[color_count] = {
2051+
Color::Blue(),
2052+
Color::Green(),
2053+
Color::Crimson(),
2054+
};
2055+
2056+
int c_index = 0;
2057+
int radius = 600;
2058+
while (radius > 0) {
2059+
paint.color = colors[(c_index++) % color_count];
2060+
canvas.DrawCircle({10, 10}, radius, paint);
2061+
if (radius > 30) {
2062+
radius -= 10;
2063+
} else {
2064+
radius -= 2;
2065+
}
2066+
}
2067+
2068+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2069+
}
2070+
20452071
TEST_P(AiksTest, GradientStrokesRenderCorrectly) {
20462072
// Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
20472073
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {

impeller/aiks/canvas.cc

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -227,17 +227,6 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect,
227227
}
228228

229229
void Canvas::DrawLine(const Point& p0, const Point& p1, const Paint& paint) {
230-
if (paint.stroke_cap == Cap::kRound) {
231-
auto path = PathBuilder{}
232-
.AddLine((p0), (p1))
233-
.SetConvexity(Convexity::kConvex)
234-
.TakePath();
235-
Paint stroke_paint = paint;
236-
stroke_paint.style = Paint::Style::kStroke;
237-
DrawPath(path, stroke_paint);
238-
return;
239-
}
240-
241230
Entity entity;
242231
entity.SetTransformation(GetCurrentTransformation());
243232
entity.SetClipDepth(GetClipDepth());
@@ -293,20 +282,33 @@ void Canvas::DrawRRect(Rect rect, Point corner_radii, const Paint& paint) {
293282
}
294283

295284
void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
285+
if (paint.style == Paint::Style::kStroke) {
286+
auto circle_path =
287+
PathBuilder{}
288+
.AddCircle(center, radius)
289+
.SetConvexity(Convexity::kConvex)
290+
.SetBounds(Rect::MakeLTRB(center.x - radius, center.y - radius,
291+
center.x + radius, center.y + radius))
292+
.TakePath();
293+
DrawPath(circle_path, paint);
294+
return;
295+
}
296+
296297
Size half_size(radius, radius);
297298
if (AttemptDrawBlurredRRect(
298299
Rect::MakeOriginSize(center - half_size, half_size * 2), radius,
299300
paint)) {
300301
return;
301302
}
302-
auto circle_path =
303-
PathBuilder{}
304-
.AddCircle(center, radius)
305-
.SetConvexity(Convexity::kConvex)
306-
.SetBounds(Rect::MakeLTRB(center.x - radius, center.y - radius,
307-
center.x + radius, center.y + radius))
308-
.TakePath();
309-
DrawPath(circle_path, paint);
303+
304+
Entity entity;
305+
entity.SetTransformation(GetCurrentTransformation());
306+
entity.SetClipDepth(GetClipDepth());
307+
entity.SetBlendMode(paint.blend_mode);
308+
entity.SetContents(paint.WithFilters(
309+
paint.CreateContentsForGeometry(Geometry::MakeCircle(center, radius))));
310+
311+
GetCurrentPass().AddEntity(entity);
310312
}
311313

312314
void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) {

impeller/entity/BUILD.gn

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,12 @@ impeller_component("entity") {
188188
"entity_pass_delegate.h",
189189
"entity_pass_target.cc",
190190
"entity_pass_target.h",
191+
"geometry/circle_tessellator.cc",
192+
"geometry/circle_tessellator.h",
191193
"geometry/cover_geometry.cc",
192194
"geometry/cover_geometry.h",
195+
"geometry/ellipse_geometry.cc",
196+
"geometry/ellipse_geometry.h",
193197
"geometry/fill_path_geometry.cc",
194198
"geometry/fill_path_geometry.h",
195199
"geometry/geometry.cc",
@@ -261,6 +265,7 @@ impeller_component("entity_unittests") {
261265
"entity_playground.cc",
262266
"entity_playground.h",
263267
"entity_unittests.cc",
268+
"geometry/circle_tessellator_unittests.cc",
264269
"geometry/geometry_unittests.cc",
265270
"render_target_cache_unittests.cc",
266271
]
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/impeller/entity/geometry/circle_tessellator.h"
6+
7+
#include "flutter/fml/logging.h"
8+
9+
namespace impeller {
10+
11+
std::vector<Trig> CircleTessellator::trigs_[MAX_DIVISIONS_ + 1];
12+
13+
size_t CircleTessellator::ComputeQuadrantDivisions(Scalar pixel_radius) {
14+
// Note: these values are approximated based on the values returned from
15+
// the decomposition of 4 cubics performed by Path::CreatePolyline.
16+
if (pixel_radius < 1.0) {
17+
return 1;
18+
}
19+
if (pixel_radius < 2.0) {
20+
return 2;
21+
}
22+
if (pixel_radius < 12.0) {
23+
return 6;
24+
}
25+
if (pixel_radius <= 36.0) {
26+
return 9;
27+
}
28+
pixel_radius /= 4;
29+
if (pixel_radius > (MAX_DIVISIONS_ - 1)) {
30+
return MAX_DIVISIONS_;
31+
}
32+
return static_cast<int>(ceil(pixel_radius));
33+
}
34+
35+
const std::vector<Trig>& CircleTessellator::GetTrigForDivisions(
36+
size_t divisions) {
37+
FML_DCHECK(divisions > 0 && divisions <= MAX_DIVISIONS_);
38+
std::vector<Trig>& trigs = trigs_[divisions];
39+
40+
if (trigs.empty()) {
41+
double angle_scale = kPiOver2 / divisions;
42+
43+
trigs.emplace_back(1.0, 0.0);
44+
for (size_t i = 1; i < divisions; i++) {
45+
trigs.emplace_back(Radians(i * angle_scale));
46+
}
47+
trigs.emplace_back(0.0, 1.0);
48+
49+
FML_DCHECK(trigs.size() == divisions + 1);
50+
}
51+
52+
return trigs;
53+
}
54+
55+
void CircleTessellator::ExtendRelativeQuadrantToAbsoluteCircle(
56+
std::vector<Point>& points,
57+
const Point& center) {
58+
auto quadrant_points = points.size();
59+
60+
// The 1st quadrant points are reversed in order, reflected around
61+
// the Y axis, and translated to become absolute 2nd quadrant points.
62+
for (size_t i = 1; i <= quadrant_points; i++) {
63+
auto point = points[quadrant_points - i];
64+
points.emplace_back(center.x + point.x, center.y - point.y);
65+
}
66+
67+
// The 1st quadrant points are reflected around the X & Y axes
68+
// and translated to become absolute 3rd quadrant points.
69+
for (size_t i = 0; i < quadrant_points; i++) {
70+
auto point = points[i];
71+
points.emplace_back(center.x - point.x, center.y - point.y);
72+
}
73+
74+
// The 1st quadrant points are reversed in order, reflected around
75+
// the X axis and translated to become absolute 4th quadrant points.
76+
// The 1st quadrant points are also translated to the center point as
77+
// well since this is the last time we will use them.
78+
for (size_t i = 1; i <= quadrant_points; i++) {
79+
auto point = points[quadrant_points - i];
80+
points.emplace_back(center.x - point.x, center.y + point.y);
81+
82+
// This is the last loop where we need the first quadrant to be
83+
// relative so we convert them to absolute as we go.
84+
points[quadrant_points - i] = center + point;
85+
}
86+
}
87+
88+
void CircleTessellator::FillQuadrantTriangles(std::vector<Point>& points,
89+
const Point& center,
90+
const Point& start_vector,
91+
const Point& end_vector) const {
92+
// We only deal with circles for now
93+
FML_DCHECK(start_vector.GetLength() - end_vector.GetLength() <
94+
kEhCloseEnough);
95+
// And only for perpendicular vectors
96+
FML_DCHECK(start_vector.Dot(end_vector) < kEhCloseEnough);
97+
98+
auto trigs = GetTrigForDivisions(quadrant_divisions_);
99+
100+
auto prev = center + (trigs[0].cos * start_vector + //
101+
trigs[0].sin * end_vector);
102+
for (size_t i = 1; i < trigs.size(); i++) {
103+
points.emplace_back(center);
104+
points.emplace_back(prev);
105+
prev = center + (trigs[i].cos * start_vector + //
106+
trigs[i].sin * end_vector);
107+
points.emplace_back(prev);
108+
}
109+
}
110+
111+
std::vector<Point> CircleTessellator::GetCircleTriangles(const Point& center,
112+
Scalar radius) const {
113+
std::vector<Point> points = std::vector<Point>();
114+
const size_t quadrant_points = quadrant_divisions_ * 3;
115+
points.reserve(quadrant_points * 4);
116+
117+
// Start with the quadrant top-center to right-center using coordinates
118+
// relative to the (0, 0). The coordinates will be made absolute relative
119+
// to the center during the extend method below.
120+
FillQuadrantTriangles(points, {}, {0, -radius}, {radius, 0});
121+
FML_DCHECK(points.size() == quadrant_points);
122+
123+
ExtendRelativeQuadrantToAbsoluteCircle(points, center);
124+
125+
FML_DCHECK(points.size() == quadrant_points * 4);
126+
127+
return points;
128+
}
129+
130+
} // namespace impeller

0 commit comments

Comments
 (0)