Skip to content

Commit

Permalink
[Impeller] Add direct tesselation of circles for DrawCircle and Round…
Browse files Browse the repository at this point in the history
… end caps
  • Loading branch information
flar committed Nov 16, 2023
1 parent 36dd138 commit fbe91b4
Show file tree
Hide file tree
Showing 15 changed files with 809 additions and 89 deletions.
1 change: 1 addition & 0 deletions ci/licenses_golden/excluded_files
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
../../../flutter/impeller/entity/contents/vertices_contents_unittests.cc
../../../flutter/impeller/entity/entity_pass_target_unittests.cc
../../../flutter/impeller/entity/entity_unittests.cc
../../../flutter/impeller/entity/geometry/circle_tessellator_unittests.cc
../../../flutter/impeller/entity/geometry/geometry_unittests.cc
../../../flutter/impeller/entity/render_target_cache_unittests.cc
../../../flutter/impeller/fixtures
Expand Down
8 changes: 8 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -3120,8 +3120,12 @@ ORIGIN: ../../../flutter/impeller/entity/entity_pass_target.cc + ../../../flutte
ORIGIN: ../../../flutter/impeller/entity/entity_pass_target.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/entity_playground.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/entity_playground.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/geometry/circle_tessellator.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/geometry/circle_tessellator.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/geometry/cover_geometry.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/geometry/cover_geometry.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/geometry/ellipse_geometry.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/geometry/ellipse_geometry.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/geometry/fill_path_geometry.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/geometry/fill_path_geometry.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/geometry/geometry.cc + ../../../flutter/LICENSE
Expand Down Expand Up @@ -5886,8 +5890,12 @@ FILE: ../../../flutter/impeller/entity/entity_pass_target.cc
FILE: ../../../flutter/impeller/entity/entity_pass_target.h
FILE: ../../../flutter/impeller/entity/entity_playground.cc
FILE: ../../../flutter/impeller/entity/entity_playground.h
FILE: ../../../flutter/impeller/entity/geometry/circle_tessellator.cc
FILE: ../../../flutter/impeller/entity/geometry/circle_tessellator.h
FILE: ../../../flutter/impeller/entity/geometry/cover_geometry.cc
FILE: ../../../flutter/impeller/entity/geometry/cover_geometry.h
FILE: ../../../flutter/impeller/entity/geometry/ellipse_geometry.cc
FILE: ../../../flutter/impeller/entity/geometry/ellipse_geometry.h
FILE: ../../../flutter/impeller/entity/geometry/fill_path_geometry.cc
FILE: ../../../flutter/impeller/entity/geometry/fill_path_geometry.h
FILE: ../../../flutter/impeller/entity/geometry/geometry.cc
Expand Down
26 changes: 26 additions & 0 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2053,6 +2053,32 @@ TEST_P(AiksTest, DrawLinesRenderCorrectly) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, FillCirclesRenderCorrectly) {
Canvas canvas;
canvas.Scale(GetContentScale());
Paint paint;
const int color_count = 3;
Color colors[color_count] = {
Color::Blue(),
Color::Green(),
Color::Crimson(),
};

int c_index = 0;
int radius = 600;
while (radius > 0) {
paint.color = colors[(c_index++) % color_count];
canvas.DrawCircle({10, 10}, radius, paint);
if (radius > 30) {
radius -= 10;
} else {
radius -= 2;
}
}

ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, GradientStrokesRenderCorrectly) {
// Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
Expand Down
40 changes: 21 additions & 19 deletions impeller/aiks/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -227,17 +227,6 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect,
}

void Canvas::DrawLine(const Point& p0, const Point& p1, const Paint& paint) {
if (paint.stroke_cap == Cap::kRound) {
auto path = PathBuilder{}
.AddLine((p0), (p1))
.SetConvexity(Convexity::kConvex)
.TakePath();
Paint stroke_paint = paint;
stroke_paint.style = Paint::Style::kStroke;
DrawPath(path, stroke_paint);
return;
}

Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetClipDepth(GetClipDepth());
Expand Down Expand Up @@ -293,20 +282,33 @@ void Canvas::DrawRRect(Rect rect, Point corner_radii, const Paint& paint) {
}

void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
if (paint.style == Paint::Style::kStroke) {
auto circle_path =
PathBuilder{}
.AddCircle(center, radius)
.SetConvexity(Convexity::kConvex)
.SetBounds(Rect::MakeLTRB(center.x - radius, center.y - radius,
center.x + radius, center.y + radius))
.TakePath();
DrawPath(circle_path, paint);
return;
}

Size half_size(radius, radius);
if (AttemptDrawBlurredRRect(
Rect::MakeOriginSize(center - half_size, half_size * 2), radius,
paint)) {
return;
}
auto circle_path =
PathBuilder{}
.AddCircle(center, radius)
.SetConvexity(Convexity::kConvex)
.SetBounds(Rect::MakeLTRB(center.x - radius, center.y - radius,
center.x + radius, center.y + radius))
.TakePath();
DrawPath(circle_path, paint);

Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.WithFilters(
paint.CreateContentsForGeometry(Geometry::MakeCircle(center, radius))));

GetCurrentPass().AddEntity(entity);
}

void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) {
Expand Down
5 changes: 5 additions & 0 deletions impeller/entity/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,12 @@ impeller_component("entity") {
"entity_pass_delegate.h",
"entity_pass_target.cc",
"entity_pass_target.h",
"geometry/circle_tessellator.cc",
"geometry/circle_tessellator.h",
"geometry/cover_geometry.cc",
"geometry/cover_geometry.h",
"geometry/ellipse_geometry.cc",
"geometry/ellipse_geometry.h",
"geometry/fill_path_geometry.cc",
"geometry/fill_path_geometry.h",
"geometry/geometry.cc",
Expand Down Expand Up @@ -261,6 +265,7 @@ impeller_component("entity_unittests") {
"entity_playground.cc",
"entity_playground.h",
"entity_unittests.cc",
"geometry/circle_tessellator_unittests.cc",
"geometry/geometry_unittests.cc",
"render_target_cache_unittests.cc",
]
Expand Down
130 changes: 130 additions & 0 deletions impeller/entity/geometry/circle_tessellator.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// 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 "flutter/impeller/entity/geometry/circle_tessellator.h"

#include "flutter/fml/logging.h"

namespace impeller {

std::vector<Trig> CircleTessellator::trigs_[MAX_DIVISIONS_ + 1];

size_t CircleTessellator::ComputeQuadrantDivisions(Scalar pixel_radius) {
// Note: these values are approximated based on the values returned from
// the decomposition of 4 cubics performed by Path::CreatePolyline.
if (pixel_radius < 1.0) {
return 1;
}
if (pixel_radius < 2.0) {
return 2;
}
if (pixel_radius < 12.0) {
return 6;
}
if (pixel_radius <= 36.0) {
return 9;
}
pixel_radius /= 4;
if (pixel_radius > (MAX_DIVISIONS_ - 1)) {
return MAX_DIVISIONS_;
}
return static_cast<int>(ceil(pixel_radius));
}

const std::vector<Trig>& CircleTessellator::GetTrigForDivisions(
size_t divisions) {
FML_DCHECK(divisions > 0 && divisions <= MAX_DIVISIONS_);
std::vector<Trig>& trigs = trigs_[divisions];

if (trigs.empty()) {
double angle_scale = kPiOver2 / divisions;

trigs.emplace_back(1.0, 0.0);
for (size_t i = 1; i < divisions; i++) {
trigs.emplace_back(Radians(i * angle_scale));
}
trigs.emplace_back(0.0, 1.0);

FML_DCHECK(trigs.size() == divisions + 1);
}

return trigs;
}

void CircleTessellator::ExtendRelativeQuadrantToAbsoluteCircle(
std::vector<Point>& points,
const Point& center) {
auto quadrant_points = points.size();

// The 1st quadrant points are reversed in order, reflected around
// the Y axis, and translated to become absolute 2nd quadrant points.
for (size_t i = 1; i <= quadrant_points; i++) {
auto point = points[quadrant_points - i];
points.emplace_back(center.x + point.x, center.y - point.y);
}

// The 1st quadrant points are reflected around the X & Y axes
// and translated to become absolute 3rd quadrant points.
for (size_t i = 0; i < quadrant_points; i++) {
auto point = points[i];
points.emplace_back(center.x - point.x, center.y - point.y);
}

// The 1st quadrant points are reversed in order, reflected around
// the X axis and translated to become absolute 4th quadrant points.
// The 1st quadrant points are also translated to the center point as
// well since this is the last time we will use them.
for (size_t i = 1; i <= quadrant_points; i++) {
auto point = points[quadrant_points - i];
points.emplace_back(center.x - point.x, center.y + point.y);

// This is the last loop where we need the first quadrant to be
// relative so we convert them to absolute as we go.
points[quadrant_points - i] = center + point;
}
}

void CircleTessellator::FillQuadrantTriangles(std::vector<Point>& points,
const Point& center,
const Point& start_vector,
const Point& end_vector) const {
// We only deal with circles for now
FML_DCHECK(start_vector.GetLength() - end_vector.GetLength() <
kEhCloseEnough);
// And only for perpendicular vectors
FML_DCHECK(start_vector.Dot(end_vector) < kEhCloseEnough);

auto trigs = GetTrigForDivisions(quadrant_divisions_);

auto prev = center + (trigs[0].cos * start_vector + //
trigs[0].sin * end_vector);
for (size_t i = 1; i < trigs.size(); i++) {
points.emplace_back(center);
points.emplace_back(prev);
prev = center + (trigs[i].cos * start_vector + //
trigs[i].sin * end_vector);
points.emplace_back(prev);
}
}

std::vector<Point> CircleTessellator::GetCircleTriangles(const Point& center,
Scalar radius) const {
std::vector<Point> points = std::vector<Point>();
const size_t quadrant_points = quadrant_divisions_ * 3;
points.reserve(quadrant_points * 4);

// Start with the quadrant top-center to right-center using coordinates
// relative to the (0, 0). The coordinates will be made absolute relative
// to the center during the extend method below.
FillQuadrantTriangles(points, {}, {0, -radius}, {radius, 0});
FML_DCHECK(points.size() == quadrant_points);

ExtendRelativeQuadrantToAbsoluteCircle(points, center);

FML_DCHECK(points.size() == quadrant_points * 4);

return points;
}

} // namespace impeller
Loading

0 comments on commit fbe91b4

Please sign in to comment.