Skip to content

Commit

Permalink
[Impeller] Directly tessellate stroked circles. (#48586)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
flar authored Dec 2, 2023
1 parent 039439c commit 43a1598
Show file tree
Hide file tree
Showing 11 changed files with 381 additions and 63 deletions.
99 changes: 98 additions & 1 deletion impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2060,7 +2060,7 @@ TEST_P(AiksTest, DrawLinesRenderCorrectly) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, FillCirclesRenderCorrectly) {
TEST_P(AiksTest, FilledCirclesRenderCorrectly) {
Canvas canvas;
canvas.Scale(GetContentScale());
Paint paint;
Expand All @@ -2071,6 +2071,9 @@ TEST_P(AiksTest, FillCirclesRenderCorrectly) {
Color::Crimson(),
};

paint.color = Color::White();
canvas.DrawPaint(paint);

int c_index = 0;
int radius = 600;
while (radius > 0) {
Expand All @@ -2083,6 +2086,100 @@ TEST_P(AiksTest, FillCirclesRenderCorrectly) {
}
}

std::vector<Color> gradient_colors = {
Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
std::vector<Scalar> stops = {
0.0,
(1.0 / 6.0) * 1,
(1.0 / 6.0) * 2,
(1.0 / 6.0) * 3,
(1.0 / 6.0) * 4,
(1.0 / 6.0) * 5,
1.0,
};
auto texture = CreateTextureForFixture("airplane.jpg",
/*enable_mipmapping=*/true);

paint.color_source = ColorSource::MakeRadialGradient(
{500, 600}, 75, std::move(gradient_colors), std::move(stops),
Entity::TileMode::kMirror, {});
canvas.DrawCircle({500, 600}, 100, paint);

paint.color_source = ColorSource::MakeImage(
texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {},
Matrix::MakeTranslation({700, 200}));
canvas.DrawCircle({800, 300}, 100, paint);

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

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

paint.color = Color::White();
canvas.DrawPaint(paint);

int c_index = 0;

auto draw = [&paint, &colors, &c_index](Canvas& canvas, Point center,
Scalar r, Scalar dr, int n) {
for (int i = 0; i < n; i++) {
paint.color = colors[(c_index++) % color_count];
canvas.DrawCircle(center, r, paint);
r += dr;
}
};

paint.style = Paint::Style::kStroke;
paint.stroke_width = 1;
draw(canvas, {10, 10}, 2, 2, 14); // r = [2, 28], covers [1,29]
paint.stroke_width = 5;
draw(canvas, {10, 10}, 35, 10, 56); // r = [35, 585], covers [30,590]

std::vector<Color> gradient_colors = {
Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
std::vector<Scalar> stops = {
0.0,
(1.0 / 6.0) * 1,
(1.0 / 6.0) * 2,
(1.0 / 6.0) * 3,
(1.0 / 6.0) * 4,
(1.0 / 6.0) * 5,
1.0,
};
auto texture = CreateTextureForFixture("airplane.jpg",
/*enable_mipmapping=*/true);

paint.color_source = ColorSource::MakeRadialGradient(
{500, 600}, 75, std::move(gradient_colors), std::move(stops),
Entity::TileMode::kMirror, {});
draw(canvas, {500, 600}, 5, 10, 10);

paint.color_source = ColorSource::MakeImage(
texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {},
Matrix::MakeTranslation({700, 200}));
draw(canvas, {800, 300}, 5, 10, 10);

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

Expand Down
20 changes: 6 additions & 14 deletions impeller/aiks/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -287,18 +287,6 @@ 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,
Expand All @@ -310,8 +298,12 @@ void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.WithFilters(
paint.CreateContentsForGeometry(Geometry::MakeCircle(center, radius))));
auto geometry =
paint.style == Paint::Style::kStroke
? Geometry::MakeStrokedCircle(center, radius, paint.stroke_width)
: Geometry::MakeCircle(center, radius);
entity.SetContents(
paint.WithFilters(paint.CreateContentsForGeometry(geometry)));

GetCurrentPass().AddEntity(entity);
}
Expand Down
137 changes: 104 additions & 33 deletions impeller/entity/geometry/ellipse_geometry.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "impeller/entity/geometry/ellipse_geometry.h"
#include <algorithm>

#include "flutter/impeller/entity/geometry/ellipse_geometry.h"

#include "flutter/impeller/entity/geometry/line_geometry.h"
#include "flutter/impeller/tessellator/circle_tessellator.h"

namespace impeller {

EllipseGeometry::EllipseGeometry(Point center, Scalar radius)
: center_(center), radius_(radius) {
: center_(center), radius_(radius), stroke_width_(-1.0) {
FML_DCHECK(radius >= 0);
}

EllipseGeometry::EllipseGeometry(Point center,
Scalar radius,
Scalar stroke_width)
: center_(center),
radius_(radius),
stroke_width_(std::max(stroke_width, 0.0f)) {
FML_DCHECK(radius >= 0);
FML_DCHECK(stroke_width >= 0);
}

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

Scalar radius = radius_;
Scalar half_width = stroke_width_ < 0
? 0.0
: LineGeometry::ComputePixelHalfWidth(
entity.GetTransform(), stroke_width_);
Scalar outer_radius = radius_ + half_width;
Scalar inner_radius = half_width <= 0 ? 0.0 : radius_ - half_width;

const Point& center = center_;
std::shared_ptr<Tessellator> tessellator = renderer.GetTessellator();
CircleTessellator circle_tessellator(tessellator, entity.GetTransform(),
radius_);
size_t count = circle_tessellator.GetCircleVertexCount();
auto vertex_buffer = host_buffer.Emplace(
count * sizeof(VT), alignof(VT),
[&circle_tessellator, &center, radius](uint8_t* buffer) {
auto vertices = reinterpret_cast<VT*>(buffer);
circle_tessellator.GenerateCircleTriangleStrip(
[&vertices](const Point& p) { //
*vertices++ = {
.position = p,
};
},
center, radius);
});
outer_radius);

BufferView vertex_buffer;
size_t count;
if (inner_radius > 0) {
count = circle_tessellator.GetStrokedCircleVertexCount();
vertex_buffer = host_buffer.Emplace(
count * sizeof(VT), alignof(VT),
[&circle_tessellator, &count, &center, outer_radius,
inner_radius](uint8_t* buffer) {
auto vertices = reinterpret_cast<VT*>(buffer);
circle_tessellator.GenerateStrokedCircleTriangleStrip(
[&vertices](const Point& p) { //
*vertices++ = {
.position = p,
};
},
center, outer_radius, inner_radius);
FML_DCHECK(vertices == reinterpret_cast<VT*>(buffer) + count);
});
} else {
count = circle_tessellator.GetCircleVertexCount();
vertex_buffer = host_buffer.Emplace(
count * sizeof(VT), alignof(VT),
[&circle_tessellator, &count, &center, outer_radius](uint8_t* buffer) {
auto vertices = reinterpret_cast<VT*>(buffer);
circle_tessellator.GenerateCircleTriangleStrip(
[&vertices](const Point& p) { //
*vertices++ = {
.position = p,
};
},
center, outer_radius);
FML_DCHECK(vertices == reinterpret_cast<VT*>(buffer) + count);
});
}

return GeometryResult{
.type = PrimitiveType::kTriangleStrip,
Expand Down Expand Up @@ -65,25 +106,55 @@ GeometryResult EllipseGeometry::GetPositionUVBuffer(
auto uv_transform =
texture_coverage.GetNormalizingTransform() * effect_transform;

Scalar radius = radius_;
Scalar half_width = stroke_width_ < 0
? 0.0
: LineGeometry::ComputePixelHalfWidth(
entity.GetTransform(), stroke_width_);
Scalar outer_radius = radius_ + half_width;
Scalar inner_radius = half_width <= 0 ? 0.0 : radius_ - half_width;

const Point& center = center_;
std::shared_ptr<Tessellator> tessellator = renderer.GetTessellator();
CircleTessellator circle_tessellator(tessellator, entity.GetTransform(),
radius_);
size_t count = circle_tessellator.GetCircleVertexCount();
auto vertex_buffer = host_buffer.Emplace(
count * sizeof(VT), alignof(VT),
[&circle_tessellator, &uv_transform, &center, radius](uint8_t* buffer) {
auto vertices = reinterpret_cast<VT*>(buffer);
circle_tessellator.GenerateCircleTriangleStrip(
[&vertices, &uv_transform](const Point& p) { //
*vertices++ = {
.position = p,
.texture_coords = uv_transform * p,
};
},
center, radius);
});
outer_radius);

BufferView vertex_buffer;
size_t count;
if (inner_radius > 0) {
count = circle_tessellator.GetStrokedCircleVertexCount();
vertex_buffer = host_buffer.Emplace(
count * sizeof(VT), alignof(VT),
[&circle_tessellator, &uv_transform, &count, &center, outer_radius,
inner_radius](uint8_t* buffer) {
auto vertices = reinterpret_cast<VT*>(buffer);
circle_tessellator.GenerateStrokedCircleTriangleStrip(
[&vertices, &uv_transform](const Point& p) { //
*vertices++ = {
.position = p,
.texture_coords = uv_transform * p,
};
},
center, outer_radius, inner_radius);
FML_DCHECK(vertices == reinterpret_cast<VT*>(buffer) + count);
});
} else {
count = circle_tessellator.GetCircleVertexCount();
vertex_buffer = host_buffer.Emplace(
count * sizeof(VT), alignof(VT),
[&circle_tessellator, &uv_transform, &count, &center,
outer_radius](uint8_t* buffer) {
auto vertices = reinterpret_cast<VT*>(buffer);
circle_tessellator.GenerateCircleTriangleStrip(
[&vertices, &uv_transform](const Point& p) { //
*vertices++ = {
.position = p,
.texture_coords = uv_transform * p,
};
},
center, outer_radius);
FML_DCHECK(vertices == reinterpret_cast<VT*>(buffer) + count);
});
}

return GeometryResult{
.type = PrimitiveType::kTriangleStrip,
Expand Down
2 changes: 2 additions & 0 deletions impeller/entity/geometry/ellipse_geometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace impeller {
class EllipseGeometry final : public Geometry {
public:
explicit EllipseGeometry(Point center, Scalar radius);
explicit EllipseGeometry(Point center, Scalar radius, Scalar stroke_width);

~EllipseGeometry() = default;

Expand Down Expand Up @@ -59,6 +60,7 @@ class EllipseGeometry final : public Geometry {

Point center_;
Scalar radius_;
Scalar stroke_width_;

EllipseGeometry(const EllipseGeometry&) = delete;

Expand Down
6 changes: 6 additions & 0 deletions impeller/entity/geometry/geometry.cc
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ std::shared_ptr<Geometry> Geometry::MakeCircle(Point center, Scalar radius) {
return std::make_shared<EllipseGeometry>(center, radius);
}

std::shared_ptr<Geometry> Geometry::MakeStrokedCircle(Point center,
Scalar radius,
Scalar stroke_width) {
return std::make_shared<EllipseGeometry>(center, radius, stroke_width);
}

bool Geometry::CoversArea(const Matrix& transform, const Rect& rect) const {
return false;
}
Expand Down
4 changes: 4 additions & 0 deletions impeller/entity/geometry/geometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ class Geometry {

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

static std::shared_ptr<Geometry> MakeStrokedCircle(Point center,
Scalar radius,
Scalar stroke_width);

static std::shared_ptr<Geometry> MakePointField(std::vector<Point> points,
Scalar radius,
bool round);
Expand Down
Loading

0 comments on commit 43a1598

Please sign in to comment.