Skip to content

Commit

Permalink
[Impeller] Directly tessellate filled ellipses (flutter#48770)
Browse files Browse the repository at this point in the history
Add support for filling ellipses/ovals and greatly simplify the primitive tessellation mechanisms.
  • Loading branch information
flar authored Dec 8, 2023
1 parent e9cb19f commit 5035846
Show file tree
Hide file tree
Showing 28 changed files with 1,410 additions and 901 deletions.
1 change: 0 additions & 1 deletion ci/licenses_golden/excluded_files
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@
../../../flutter/impeller/scene/importer/importer_unittests.cc
../../../flutter/impeller/scene/scene_unittests.cc
../../../flutter/impeller/shader_archive/shader_archive_unittests.cc
../../../flutter/impeller/tessellator/circle_tessellator_unittests.cc
../../../flutter/impeller/tessellator/dart/.dart_tool
../../../flutter/impeller/tessellator/dart/pubspec.lock
../../../flutter/impeller/tessellator/dart/pubspec.yaml
Expand Down
8 changes: 4 additions & 4 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -5099,6 +5099,8 @@ 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_geometry.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/geometry/circle_geometry.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
Expand Down Expand Up @@ -5527,8 +5529,6 @@ ORIGIN: ../../../flutter/impeller/shader_archive/shader_archive_writer.cc + ../.
ORIGIN: ../../../flutter/impeller/shader_archive/shader_archive_writer.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/tessellator/c/tessellator.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/tessellator/c/tessellator.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/tessellator/circle_tessellator.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/tessellator/circle_tessellator.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/tessellator/dart/lib/tessellator.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/tessellator/tessellator.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/tessellator/tessellator.h + ../../../flutter/LICENSE
Expand Down Expand Up @@ -7898,6 +7898,8 @@ 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_geometry.cc
FILE: ../../../flutter/impeller/entity/geometry/circle_geometry.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
Expand Down Expand Up @@ -8327,8 +8329,6 @@ FILE: ../../../flutter/impeller/shader_archive/shader_archive_writer.cc
FILE: ../../../flutter/impeller/shader_archive/shader_archive_writer.h
FILE: ../../../flutter/impeller/tessellator/c/tessellator.cc
FILE: ../../../flutter/impeller/tessellator/c/tessellator.h
FILE: ../../../flutter/impeller/tessellator/circle_tessellator.cc
FILE: ../../../flutter/impeller/tessellator/circle_tessellator.h
FILE: ../../../flutter/impeller/tessellator/dart/lib/tessellator.dart
FILE: ../../../flutter/impeller/tessellator/tessellator.cc
FILE: ../../../flutter/impeller/tessellator/tessellator.h
Expand Down
71 changes: 71 additions & 0 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2193,6 +2193,77 @@ TEST_P(AiksTest, StrokedCirclesRenderCorrectly) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, FilledEllipsesRenderCorrectly) {
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;
int long_radius = 600;
int short_radius = 600;
while (long_radius > 0 && short_radius > 0) {
paint.color = colors[(c_index++) % color_count];
canvas.DrawOval(Rect::MakeXYWH(10 - long_radius, 10 - short_radius,
long_radius * 2, short_radius * 2),
paint);
canvas.DrawOval(Rect::MakeXYWH(1000 - short_radius, 750 - long_radius,
short_radius * 2, long_radius * 2),
paint);
if (short_radius > 30) {
short_radius -= 10;
long_radius -= 5;
} else {
short_radius -= 2;
long_radius -= 1;
}
}

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 = Color::White().WithAlpha(0.5);

paint.color_source = ColorSource::MakeRadialGradient(
{300, 650}, 75, std::move(gradient_colors), std::move(stops),
Entity::TileMode::kMirror, {});
canvas.DrawOval(Rect::MakeXYWH(200, 625, 200, 50), paint);
canvas.DrawOval(Rect::MakeXYWH(275, 550, 50, 200), paint);

paint.color_source = ColorSource::MakeImage(
texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {},
Matrix::MakeTranslation({610, 15}));
canvas.DrawOval(Rect::MakeXYWH(610, 90, 200, 50), paint);
canvas.DrawOval(Rect::MakeXYWH(685, 15, 50, 200), paint);

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
33 changes: 31 additions & 2 deletions impeller/aiks/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ void Canvas::DrawLine(const Point& p0, const Point& p1, const Paint& paint) {
GetCurrentPass().AddEntity(std::move(entity));
}

void Canvas::DrawRect(Rect rect, const Paint& paint) {
void Canvas::DrawRect(const Rect& rect, const Paint& paint) {
if (paint.style == Paint::Style::kStroke) {
DrawPath(PathBuilder{}.AddRect(rect).TakePath(), paint);
return;
Expand All @@ -338,6 +338,33 @@ void Canvas::DrawRect(Rect rect, const Paint& paint) {
GetCurrentPass().AddEntity(std::move(entity));
}

void Canvas::DrawOval(const Rect& rect, const Paint& paint) {
if (rect.IsSquare()) {
// Circles have slightly less overhead and can do stroking
DrawCircle(rect.GetCenter(), rect.GetSize().width * 0.5f, paint);
return;
}

if (paint.style == Paint::Style::kStroke) {
// No stroked ellipses yet
DrawPath(PathBuilder{}.AddOval(rect).TakePath(), paint);
return;
}

if (AttemptDrawBlurredRRect(rect, 0, paint)) {
return;
}

Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(
CreateContentsForGeometryWithFilters(paint, Geometry::MakeOval(rect)));

GetCurrentPass().AddEntity(std::move(entity));
}

void Canvas::DrawRRect(Rect rect, Point corner_radii, const Paint& paint) {
if (corner_radii.x == corner_radii.y &&
AttemptDrawBlurredRRect(rect, corner_radii.x, paint)) {
Expand All @@ -362,7 +389,9 @@ void Canvas::DrawRRect(Rect rect, Point corner_radii, const Paint& paint) {
DrawPath(std::move(path), paint);
}

void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
void Canvas::DrawCircle(const Point& center,
Scalar radius,
const Paint& paint) {
Size half_size(radius, radius);
if (AttemptDrawBlurredRRect(
Rect::MakeOriginSize(center - half_size, half_size * 2), radius,
Expand Down
8 changes: 5 additions & 3 deletions impeller/aiks/canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,15 @@ class Canvas {

void DrawLine(const Point& p0, const Point& p1, const Paint& paint);

void DrawRect(Rect rect, const Paint& paint);
void DrawRect(const Rect& rect, const Paint& paint);

void DrawOval(const Rect& rect, const Paint& paint);

void DrawRRect(Rect rect, Point corner_radii, const Paint& paint);

void DrawCircle(Point center, Scalar radius, const Paint& paint);
void DrawCircle(const Point& center, Scalar radius, const Paint& paint);

void DrawPoints(std::vector<Point>,
void DrawPoints(std::vector<Point> points,
Scalar radius,
const Paint& paint,
PointStyle point_style);
Expand Down
16 changes: 3 additions & 13 deletions impeller/display_list/dl_dispatcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -770,16 +770,7 @@ void DlDispatcher::drawRect(const SkRect& rect) {

// |flutter::DlOpReceiver|
void DlDispatcher::drawOval(const SkRect& bounds) {
if (bounds.width() == bounds.height()) {
canvas_.DrawCircle(skia_conversions::ToPoint(bounds.center()),
bounds.width() * 0.5, paint_);
} else {
auto path = PathBuilder{}
.AddOval(skia_conversions::ToRect(bounds))
.SetConvexity(Convexity::kConvex)
.TakePath();
canvas_.DrawPath(std::move(path), paint_);
}
canvas_.DrawOval(skia_conversions::ToRect(bounds), paint_);
}

// |flutter::DlOpReceiver|
Expand Down Expand Up @@ -831,9 +822,8 @@ void DlDispatcher::SimplifyOrDrawPath(CanvasType& canvas,
}

SkRect oval;
if (path.isOval(&oval) && oval.width() == oval.height()) {
canvas.DrawCircle(skia_conversions::ToPoint(oval.center()),
oval.width() * 0.5, paint);
if (path.isOval(&oval)) {
canvas.DrawOval(skia_conversions::ToRect(oval), paint);
return;
}

Expand Down
2 changes: 2 additions & 0 deletions impeller/entity/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ impeller_component("entity") {
"entity_pass_delegate.h",
"entity_pass_target.cc",
"entity_pass_target.h",
"geometry/circle_geometry.cc",
"geometry/circle_geometry.h",
"geometry/cover_geometry.cc",
"geometry/cover_geometry.h",
"geometry/ellipse_geometry.cc",
Expand Down
98 changes: 98 additions & 0 deletions impeller/entity/geometry/circle_geometry.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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 <algorithm>

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

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

namespace impeller {

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

CircleGeometry::CircleGeometry(const 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 CircleGeometry::GetPositionBuffer(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
auto& transform = entity.GetTransform();

Scalar half_width = stroke_width_ < 0 ? 0.0
: LineGeometry::ComputePixelHalfWidth(
transform, stroke_width_);

std::shared_ptr<Tessellator> tessellator = renderer.GetTessellator();

// We call the StrokedCircle method which will simplify to a
// FilledCircleGenerator if the inner_radius is <= 0.
auto generator =
tessellator->StrokedCircle(transform, center_, radius_, half_width);

return ComputePositionGeometry(generator, entity, pass);
}

// |Geometry|
GeometryResult CircleGeometry::GetPositionUVBuffer(
Rect texture_coverage,
Matrix effect_transform,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
auto& transform = entity.GetTransform();
auto uv_transform =
texture_coverage.GetNormalizingTransform() * effect_transform;

Scalar half_width = stroke_width_ < 0 ? 0.0
: LineGeometry::ComputePixelHalfWidth(
transform, stroke_width_);
std::shared_ptr<Tessellator> tessellator = renderer.GetTessellator();

// We call the StrokedCircle method which will simplify to a
// FilledCircleGenerator if the inner_radius is <= 0.
auto generator =
tessellator->StrokedCircle(transform, center_, radius_, half_width);

return ComputePositionUVGeometry(generator, uv_transform, entity, pass);
}

GeometryVertexType CircleGeometry::GetVertexType() const {
return GeometryVertexType::kPosition;
}

std::optional<Rect> CircleGeometry::GetCoverage(const Matrix& transform) const {
Point corners[4]{
{center_.x, center_.y - radius_},
{center_.x + radius_, center_.y},
{center_.x, center_.y + radius_},
{center_.x - radius_, center_.y},
};

for (int i = 0; i < 4; i++) {
corners[i] = transform * corners[i];
}
return Rect::MakePointBounds(std::begin(corners), std::end(corners));
}

bool CircleGeometry::CoversArea(const Matrix& transform,
const Rect& rect) const {
return false;
}

bool CircleGeometry::IsAxisAlignedRect() const {
return false;
}

} // namespace impeller
57 changes: 57 additions & 0 deletions impeller/entity/geometry/circle_geometry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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.

#pragma once

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

namespace impeller {

// Geometry class that can generate vertices (with or without texture
// coordinates) for either filled or stroked circles
class CircleGeometry final : public Geometry {
public:
explicit CircleGeometry(const Point& center, Scalar radius);

explicit CircleGeometry(const Point& center,
Scalar radius,
Scalar stroke_width);

~CircleGeometry() = default;

// |Geometry|
bool CoversArea(const Matrix& transform, const Rect& rect) const override;

// |Geometry|
bool IsAxisAlignedRect() const override;

private:
// |Geometry|
GeometryResult GetPositionBuffer(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const override;

// |Geometry|
GeometryVertexType GetVertexType() const override;

// |Geometry|
std::optional<Rect> GetCoverage(const Matrix& transform) const override;

// |Geometry|
GeometryResult GetPositionUVBuffer(Rect texture_coverage,
Matrix effect_transform,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const override;

Point center_;
Scalar radius_;
Scalar stroke_width_;

CircleGeometry(const CircleGeometry&) = delete;

CircleGeometry& operator=(const CircleGeometry&) = delete;
};

} // namespace impeller
Loading

0 comments on commit 5035846

Please sign in to comment.