Skip to content

Instantly share code, notes, and snippets.

@dnfield
Last active October 13, 2022 23:00
Show Gist options
  • Save dnfield/182fed97bf5dd092a0e6ef43af1d3207 to your computer and use it in GitHub Desktop.
Save dnfield/182fed97bf5dd092a0e6ef43af1d3207 to your computer and use it in GitHub Desktop.
diff --git a/impeller/entity/contents/clip_contents.cc b/impeller/entity/contents/clip_contents.cc
index 2497e4daa9..a7ba8e6ec7 100644
--- a/impeller/entity/contents/clip_contents.cc
+++ b/impeller/entity/contents/clip_contents.cc
@@ -121,7 +121,8 @@ bool ClipContents::Render(const ContentContext& renderer,
auto allocator = renderer.GetContext()->GetResourceAllocator();
auto geometry_result = geometry_->GetPositionBuffer(
allocator, host_buffer, renderer.GetTessellator(),
- pass.GetRenderTargetSize());
+ pass.GetRenderTargetSize(),
+ entity.GetTransformation().GetMaxBasisLength());
cmd.BindVertices(geometry_result.vertex_buffer);
cmd.primitive_type = geometry_result.type;
info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
diff --git a/impeller/entity/contents/linear_gradient_contents.cc b/impeller/entity/contents/linear_gradient_contents.cc
index eef7c5d42f..1b4d3a77e4 100644
--- a/impeller/entity/contents/linear_gradient_contents.cc
+++ b/impeller/entity/contents/linear_gradient_contents.cc
@@ -80,7 +80,8 @@ bool LinearGradientContents::Render(const ContentContext& renderer,
auto allocator = renderer.GetContext()->GetResourceAllocator();
auto geometry_result = GetGeometry()->GetPositionBuffer(
allocator, host_buffer, renderer.GetTessellator(),
- pass.GetRenderTargetSize());
+ pass.GetRenderTargetSize(),
+ entity.GetTransformation().GetMaxBasisLength());
cmd.BindVertices(geometry_result.vertex_buffer);
cmd.primitive_type = geometry_result.type;
FS::BindGradientInfo(
diff --git a/impeller/entity/contents/radial_gradient_contents.cc b/impeller/entity/contents/radial_gradient_contents.cc
index e674ebce19..65352a56ce 100644
--- a/impeller/entity/contents/radial_gradient_contents.cc
+++ b/impeller/entity/contents/radial_gradient_contents.cc
@@ -79,7 +79,8 @@ bool RadialGradientContents::Render(const ContentContext& renderer,
auto allocator = renderer.GetContext()->GetResourceAllocator();
auto geometry_result = GetGeometry()->GetPositionBuffer(
allocator, host_buffer, renderer.GetTessellator(),
- pass.GetRenderTargetSize());
+ pass.GetRenderTargetSize(),
+ entity.GetTransformation().GetMaxBasisLength());
cmd.BindVertices(geometry_result.vertex_buffer);
cmd.primitive_type = geometry_result.type;
FS::BindGradientInfo(
diff --git a/impeller/entity/contents/solid_color_contents.cc b/impeller/entity/contents/solid_color_contents.cc
index bae283cb42..e16dc54b8d 100644
--- a/impeller/entity/contents/solid_color_contents.cc
+++ b/impeller/entity/contents/solid_color_contents.cc
@@ -63,7 +63,8 @@ bool SolidColorContents::Render(const ContentContext& renderer,
auto allocator = renderer.GetContext()->GetResourceAllocator();
auto geometry_result = geometry_->GetPositionBuffer(
allocator, host_buffer, renderer.GetTessellator(),
- pass.GetRenderTargetSize());
+ pass.GetRenderTargetSize(),
+ entity.GetTransformation().GetMaxBasisLength());
cmd.BindVertices(geometry_result.vertex_buffer);
cmd.primitive_type = geometry_result.type;
diff --git a/impeller/entity/contents/solid_stroke_contents.cc b/impeller/entity/contents/solid_stroke_contents.cc
index 8ff982c667..bf66c5e2a5 100644
--- a/impeller/entity/contents/solid_stroke_contents.cc
+++ b/impeller/entity/contents/solid_stroke_contents.cc
@@ -72,7 +72,7 @@ static VertexBuffer CreateSolidStrokeVertices(
const SolidStrokeContents::CapProc& cap_proc,
const SolidStrokeContents::JoinProc& join_proc,
Scalar miter_limit,
- const SmoothingApproximation& smoothing) {
+ Scalar tolerance) {
using VS = SolidFillVertexShader;
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
@@ -101,8 +101,8 @@ static VertexBuffer CreateSolidStrokeVertices(
switch (contour_end_point_i - contour_start_point_i) {
case 1: {
Point p = polyline.points[contour_start_point_i];
- cap_proc(vtx_builder, p, {-stroke_width * 0.5f, 0}, smoothing);
- cap_proc(vtx_builder, p, {stroke_width * 0.5f, 0}, smoothing);
+ cap_proc(vtx_builder, p, {-stroke_width * 0.5f, 0}, tolerance);
+ cap_proc(vtx_builder, p, {stroke_width * 0.5f, 0}, tolerance);
continue;
}
case 0:
@@ -140,7 +140,7 @@ static VertexBuffer CreateSolidStrokeVertices(
// Generate start cap.
if (!polyline.contours[contour_i].is_closed) {
cap_proc(vtx_builder, polyline.points[contour_start_point_i], -offset,
- smoothing);
+ tolerance);
}
// Generate contour geometry.
@@ -162,7 +162,7 @@ static VertexBuffer CreateSolidStrokeVertices(
// Generate join from the current line to the next line.
join_proc(vtx_builder, polyline.points[point_i], previous_offset,
- offset, miter_limit, smoothing);
+ offset, miter_limit, tolerance);
}
}
}
@@ -170,10 +170,10 @@ static VertexBuffer CreateSolidStrokeVertices(
// Generate end cap or join.
if (!polyline.contours[contour_i].is_closed) {
cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1], offset,
- smoothing);
+ tolerance);
} else {
join_proc(vtx_builder, polyline.points[contour_start_point_i], offset,
- contour_first_offset, miter_limit, smoothing);
+ contour_first_offset, miter_limit, tolerance);
}
}
@@ -209,15 +209,15 @@ bool SolidStrokeContents::Render(const ContentContext& renderer,
cmd.pipeline = renderer.GetSolidFillPipeline(options);
cmd.stencil_reference = entity.GetStencilDepth();
- auto smoothing = SmoothingApproximation(
- 60.0 / (stroke_size_ * entity.GetTransformation().GetMaxBasisLength()),
- 0.0, 0.0);
+ auto tolerance =
+ kDefaultCurveTolerance /
+ (stroke_size_ * entity.GetTransformation().GetMaxBasisLength());
Scalar min_size = 1.0f / sqrt(std::abs(determinant));
Scalar stroke_width = std::max(stroke_size_, min_size);
cmd.BindVertices(CreateSolidStrokeVertices(
path_, pass.GetTransientsBuffer(), stroke_width, cap_proc_, join_proc_,
- miter_limit_ * stroke_width * 0.5, smoothing));
+ miter_limit_ * stroke_width * 0.5, tolerance));
VS::BindVertInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(vert_info));
FS::BindFragInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frag_info));
@@ -256,12 +256,12 @@ void SolidStrokeContents::SetStrokeCap(Cap cap) {
case Cap::kButt:
cap_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& offset,
- const SmoothingApproximation& smoothing) {};
+ Scalar tolerance) {};
break;
case Cap::kRound:
cap_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& offset,
- const SmoothingApproximation& smoothing) {
+ Scalar tolerance) {
VS::PerVertexData vtx;
Point forward(offset.y, -offset.x);
@@ -271,7 +271,7 @@ void SolidStrokeContents::SetStrokeCap(Cap cap) {
CubicPathComponent(
offset, offset + forward * PathBuilder::kArcApproximationMagic,
forward + offset * PathBuilder::kArcApproximationMagic, forward)
- .CreatePolyline(smoothing);
+ .CreatePolyline(tolerance);
vtx.position = position + offset;
vtx_builder.AppendVertex(vtx);
@@ -288,7 +288,7 @@ void SolidStrokeContents::SetStrokeCap(Cap cap) {
case Cap::kSquare:
cap_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& offset,
- const SmoothingApproximation& smoothing) {
+ Scalar tolerance) {
VS::PerVertexData vtx;
vtx.position = position;
@@ -337,7 +337,7 @@ void SolidStrokeContents::SetStrokeJoin(Join join) {
join_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& start_offset,
const Point& end_offset, Scalar miter_limit,
- const SmoothingApproximation& smoothing) {
+ Scalar tolerance) {
CreateBevelAndGetDirection(vtx_builder, position, start_offset,
end_offset);
};
@@ -346,7 +346,7 @@ void SolidStrokeContents::SetStrokeJoin(Join join) {
join_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& start_offset,
const Point& end_offset, Scalar miter_limit,
- const SmoothingApproximation& smoothing) {
+ Scalar tolerance) {
Point start_normal = start_offset.Normalize();
Point end_normal = end_offset.Normalize();
@@ -375,7 +375,7 @@ void SolidStrokeContents::SetStrokeJoin(Join join) {
join_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& start_offset,
const Point& end_offset, Scalar miter_limit,
- const SmoothingApproximation& smoothing) {
+ Scalar tolerance) {
Point start_normal = start_offset.Normalize();
Point end_normal = end_offset.Normalize();
@@ -402,7 +402,7 @@ void SolidStrokeContents::SetStrokeJoin(Join join) {
auto arc_points = CubicPathComponent(start_offset, start_handle,
middle_handle, middle)
- .CreatePolyline(smoothing);
+ .CreatePolyline(tolerance);
VS::PerVertexData vtx;
for (const auto& point : arc_points) {
diff --git a/impeller/entity/contents/solid_stroke_contents.h b/impeller/entity/contents/solid_stroke_contents.h
index 526d0d3c7d..101daf616e 100644
--- a/impeller/entity/contents/solid_stroke_contents.h
+++ b/impeller/entity/contents/solid_stroke_contents.h
@@ -40,14 +40,14 @@ class SolidStrokeContents final : public Contents {
std::function<void(VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position,
const Point& offset,
- const SmoothingApproximation& smoothing)>;
+ Scalar tolerance)>;
using JoinProc =
std::function<void(VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position,
const Point& start_offset,
const Point& end_offset,
Scalar miter_limit,
- const SmoothingApproximation& smoothing)>;
+ Scalar tolerance)>;
SolidStrokeContents();
diff --git a/impeller/entity/contents/sweep_gradient_contents.cc b/impeller/entity/contents/sweep_gradient_contents.cc
index 2371b6ecfc..921c8fc967 100644
--- a/impeller/entity/contents/sweep_gradient_contents.cc
+++ b/impeller/entity/contents/sweep_gradient_contents.cc
@@ -85,7 +85,8 @@ bool SweepGradientContents::Render(const ContentContext& renderer,
auto allocator = renderer.GetContext()->GetResourceAllocator();
auto geometry_result = GetGeometry()->GetPositionBuffer(
allocator, host_buffer, renderer.GetTessellator(),
- pass.GetRenderTargetSize());
+ pass.GetRenderTargetSize(),
+ entity.GetTransformation().GetMaxBasisLength());
cmd.BindVertices(geometry_result.vertex_buffer);
cmd.primitive_type = geometry_result.type;
FS::BindGradientInfo(
diff --git a/impeller/entity/contents/tiled_texture_contents.cc b/impeller/entity/contents/tiled_texture_contents.cc
index 58a535f7c3..18c4691dac 100644
--- a/impeller/entity/contents/tiled_texture_contents.cc
+++ b/impeller/entity/contents/tiled_texture_contents.cc
@@ -70,7 +70,8 @@ bool TiledTextureContents::Render(const ContentContext& renderer,
auto allocator = renderer.GetContext()->GetResourceAllocator();
auto geometry_result = GetGeometry()->GetPositionBuffer(
allocator, host_buffer, renderer.GetTessellator(),
- pass.GetRenderTargetSize());
+ pass.GetRenderTargetSize(),
+ entity.GetTransformation().GetMaxBasisLength());
cmd.BindVertices(geometry_result.vertex_buffer);
cmd.primitive_type = geometry_result.type;
VS::BindVertInfo(cmd, host_buffer.EmplaceUniform(vert_info));
diff --git a/impeller/entity/contents/vertices_contents.cc b/impeller/entity/contents/vertices_contents.cc
index 31f1f48c53..e1a2f50fe9 100644
--- a/impeller/entity/contents/vertices_contents.cc
+++ b/impeller/entity/contents/vertices_contents.cc
@@ -72,7 +72,8 @@ bool VerticesContents::Render(const ContentContext& renderer,
auto geometry_result = geometry_->GetPositionBuffer(
allocator, host_buffer, renderer.GetTessellator(),
- pass.GetRenderTargetSize());
+ pass.GetRenderTargetSize(),
+ entity.GetTransformation().GetMaxBasisLength());
cmd.pipeline = renderer.GetGeometryPositionPipeline(
OptionsFromPassAndEntity(pass, entity));
cmd.primitive_type = geometry_result.type;
diff --git a/impeller/entity/geometry.cc b/impeller/entity/geometry.cc
index 5cbaf2707e..000872f30b 100644
--- a/impeller/entity/geometry.cc
+++ b/impeller/entity/geometry.cc
@@ -48,7 +48,8 @@ GeometryResult VerticesGeometry::GetPositionBuffer(
std::shared_ptr<Allocator> device_allocator,
HostBuffer& host_buffer,
std::shared_ptr<Tessellator> tessellator,
- ISize render_target_size) {
+ ISize render_target_size,
+ Scalar max_basis_length) {
if (!vertices_.IsValid()) {
return {};
}
@@ -182,10 +183,12 @@ GeometryResult PathGeometry::GetPositionBuffer(
std::shared_ptr<Allocator> device_allocator,
HostBuffer& host_buffer,
std::shared_ptr<Tessellator> tessellator,
- ISize render_target_size) {
+ ISize render_target_size,
+ Scalar max_basis_length) {
VertexBuffer vertex_buffer;
auto tesselation_result = tessellator->TessellateBuilder(
- path_.GetFillType(), path_.CreatePolyline(),
+ path_.GetFillType(),
+ path_.CreatePolyline(kDefaultCurveTolerance / max_basis_length),
[&vertex_buffer, &host_buffer](
const float* vertices, size_t vertices_count, const uint16_t* indices,
size_t indices_count) {
@@ -244,7 +247,8 @@ GeometryResult CoverGeometry::GetPositionBuffer(
std::shared_ptr<Allocator> device_allocator,
HostBuffer& host_buffer,
std::shared_ptr<Tessellator> tessellator,
- ISize render_target_size) {
+ ISize render_target_size,
+ Scalar max_basis_length) {
auto rect = Rect(Size(render_target_size));
constexpr uint16_t kRectIndicies[4] = {0, 1, 2, 3};
return GeometryResult{
diff --git a/impeller/entity/geometry.h b/impeller/entity/geometry.h
index 293140d2a2..666da614f6 100644
--- a/impeller/entity/geometry.h
+++ b/impeller/entity/geometry.h
@@ -43,7 +43,8 @@ class Geometry {
std::shared_ptr<Allocator> device_allocator,
HostBuffer& host_buffer,
std::shared_ptr<Tessellator> tessellator,
- ISize render_target_size) = 0;
+ ISize render_target_size,
+ Scalar max_basis_length) = 0;
virtual GeometryResult GetPositionColorBuffer(
std::shared_ptr<Allocator> device_allocator,
@@ -75,7 +76,8 @@ class VerticesGeometry : public Geometry {
GeometryResult GetPositionBuffer(std::shared_ptr<Allocator> device_allocator,
HostBuffer& host_buffer,
std::shared_ptr<Tessellator> tessellator,
- ISize render_target_size) override;
+ ISize render_target_size,
+ Scalar max_basis_length) override;
// |Geometry|
GeometryResult GetPositionColorBuffer(
@@ -115,7 +117,8 @@ class PathGeometry : public Geometry {
GeometryResult GetPositionBuffer(std::shared_ptr<Allocator> device_allocator,
HostBuffer& host_buffer,
std::shared_ptr<Tessellator> tessellator,
- ISize render_target_size) override;
+ ISize render_target_size,
+ Scalar max_basis_length) override;
// |Geometry|
GeometryResult GetPositionColorBuffer(
@@ -156,7 +159,8 @@ class CoverGeometry : public Geometry {
GeometryResult GetPositionBuffer(std::shared_ptr<Allocator> device_allocator,
HostBuffer& host_buffer,
std::shared_ptr<Tessellator> tessellator,
- ISize render_target_size) override;
+ ISize render_target_size,
+ Scalar max_basis_length) override;
// |Geometry|
GeometryResult GetPositionColorBuffer(
diff --git a/impeller/geometry/BUILD.gn b/impeller/geometry/BUILD.gn
index f3bdf11b26..e8d518950b 100644
--- a/impeller/geometry/BUILD.gn
+++ b/impeller/geometry/BUILD.gn
@@ -63,6 +63,7 @@ executable("geometry_benchmarks") {
sources = [ "geometry_benchmarks.cc" ]
deps = [
":geometry",
+ "../tessellator",
"//flutter/benchmarking",
]
}
diff --git a/impeller/geometry/geometry_benchmarks.cc b/impeller/geometry/geometry_benchmarks.cc
index 8d612d9d05..622847ae1b 100644
--- a/impeller/geometry/geometry_benchmarks.cc
+++ b/impeller/geometry/geometry_benchmarks.cc
@@ -6,6 +6,7 @@
#include "impeller/geometry/path.h"
#include "impeller/geometry/path_builder.h"
+#include "impeller/tessellator/tessellator.h"
namespace impeller {
@@ -232,9 +233,18 @@ static void BM_ManyCubicPolyline(benchmark::State& state) {
.TakePath();
size_t point_count = 0u;
size_t single_point_count = 0u;
+<<<<<<< HEAD
+ Tessellator tess;
+ while (state.KeepRunning()) {
+ auto polyline = path.CreatePolyline();
+ single_point_count = polyline.points.size();
+ point_count += single_point_count;
+ tess.Tessellate(FillType::kNonZero, polyline, [](Point) {});
+=======
while (state.KeepRunning()) {
single_point_count = path.CreatePolyline().points.size();
point_count += single_point_count;
+>>>>>>> upstream/main
}
state.counters["SinglePointCount"] = single_point_count;
state.counters["TotalPointCount"] = point_count;
@@ -373,9 +383,18 @@ static void BM_ManyQuadPolyline(benchmark::State& state) {
.TakePath();
size_t point_count = 0u;
size_t single_point_count = 0u;
+<<<<<<< HEAD
+ Tessellator tess;
+ while (state.KeepRunning()) {
+ auto polyline = path.CreatePolyline();
+ single_point_count = polyline.points.size();
+ point_count += single_point_count;
+ tess.Tessellate(FillType::kNonZero, polyline, [](Point) {});
+=======
while (state.KeepRunning()) {
single_point_count = path.CreatePolyline().points.size();
point_count += single_point_count;
+>>>>>>> upstream/main
}
state.counters["SinglePointCount"] = single_point_count;
state.counters["TotalPointCount"] = point_count;
diff --git a/impeller/geometry/geometry_unittests.cc b/impeller/geometry/geometry_unittests.cc
index a199c689d7..0a227cb2e9 100644
--- a/impeller/geometry/geometry_unittests.cc
+++ b/impeller/geometry/geometry_unittests.cc
@@ -17,6 +17,7 @@
#include "impeller/geometry/rect.h"
#include "impeller/geometry/scalar.h"
#include "impeller/geometry/size.h"
+#include "path_component.h"
namespace impeller {
namespace testing {
@@ -1426,8 +1427,7 @@ TEST(GeometryTest, RectGetPositive) {
TEST(GeometryTest, CubicPathComponentPolylineDoesNotIncludePointOne) {
CubicPathComponent component({10, 10}, {20, 35}, {35, 20}, {40, 40});
- SmoothingApproximation approximation;
- auto polyline = component.CreatePolyline(approximation);
+ auto polyline = component.CreatePolyline();
ASSERT_NE(polyline.front().x, 10);
ASSERT_NE(polyline.front().y, 10);
ASSERT_EQ(polyline.back().x, 40);
@@ -1713,5 +1713,135 @@ TEST(GeometryTest, Gradient) {
}
}
+static void LogPoint(Point p, bool comma) {
+ if (comma) {
+ std::cout << "{x: " << p.x << ", y: " << p.y << "}," << std::endl;
+ } else {
+ std::cout << "{x: " << p.x << ", y: " << p.y << "}" << std::endl;
+ }
+}
+
+static Scalar ApproximateParabolaIntegral(Scalar x) {
+ constexpr Scalar d = 0.67;
+ return x / (1.0 - d + sqrt(sqrt(pow(d, 4) + 0.25 * x * x)));
+}
+
+static std::vector<Point> CreateQuadPolyline(
+ const QuadraticPathComponent& component) {
+ auto tolerance = .1f;
+ auto sqrt_tol = sqrt(tolerance);
+
+ auto d01 = component.cp - component.p1;
+ auto d12 = component.p2 - component.cp;
+ auto dd = d01 - d12;
+ auto cross = (component.p2 - component.p1).Cross(dd);
+ auto x0 = d01.Dot(dd) * 1 / cross;
+ auto x2 = d12.Dot(dd) * 1 / cross;
+ auto scale = abs(cross / (hypot(dd.x, dd.y) * (x2 - x0)));
+
+ auto a0 = ApproximateParabolaIntegral(x0);
+ auto a2 = ApproximateParabolaIntegral(x2);
+ Scalar val = 0.f;
+ if (isfinite(scale)) {
+ auto da = abs(a2 - a0);
+ auto sqrt_scale = sqrt(scale);
+ if ((x0 < 0 && x2 < 0) || (x0 >= 0 && x2 >= 0)) {
+ val = da * sqrt_scale;
+ } else {
+ // cusp case
+ auto xmin = sqrt_tol / sqrt_scale;
+ val = sqrt_tol * da / ApproximateParabolaIntegral(xmin);
+ }
+ }
+ auto u0 = ApproximateParabolaIntegral(a0);
+ auto u2 = ApproximateParabolaIntegral(a2);
+ auto uscale = 1 / (u2 - u0);
+
+ auto line_count = ceil(0.5 * val / sqrt_tol);
+ auto step = 1 / line_count;
+ FML_LOG(ERROR) << "Line count: " << line_count;
+ for (size_t i = 1; i < line_count; i += 1) {
+ auto u = i * step;
+ auto a = a0 + (a2 - a0) * u;
+ auto t = (ApproximateParabolaIntegral(a) - u0) * uscale;
+ LogPoint(component.Solve(t), true);
+ }
+ LogPoint(component.p2, false);
+
+ return {};
+}
+
+static std::vector<Point> CreateCubicPolyline(
+ const CubicPathComponent& component) {
+ FML_LOG(ERROR) << "New";
+
+ Scalar t = 0;
+
+ CubicPathComponent current_component = component;
+ int count = 0;
+ while (t < 1.0) {
+ count += 1;
+
+ auto v1 = current_component.cp1 - current_component.p1;
+ auto v2 = current_component.cp2 - current_component.p1;
+
+ auto s = abs(v2.Cross(v1) / hypot(v1.x, v1.y));
+
+ if (s < kEhCloseEnough) {
+ break;
+ }
+
+ constexpr Scalar f = 0.005;
+ t = 2 * sqrt(f / (3.f * s));
+
+ // P'1
+ auto ab = current_component.p1 +
+ t * (current_component.cp1 - current_component.p1);
+ // P'2
+ auto bc = current_component.cp1 +
+ t * (current_component.cp2 - current_component.cp1);
+ // P'3
+ auto cd = current_component.cp2 +
+ t * (current_component.p2 - current_component.cp2);
+
+ // P''1
+ auto abc = ab + t * (bc - ab);
+ // P''2
+ auto bcd = bc + t * (cd - bc);
+
+ // P'''1
+ auto abcd = abc + t * (bcd - abc);
+
+ current_component = {abcd, bcd, cd, current_component.p2};
+ LogPoint(abcd, true);
+ }
+ LogPoint(component.p2, false);
+ FML_LOG(ERROR) << count;
+ return {};
+}
+
+TEST(GeometryTest, NewPolylineAlgo) {
+ // CubicPathComponent component({10, 10}, {25, 12}, {40, 15}, {38, 24});
+ CubicPathComponent component({100, 100}, {300, 200}, {200, 200}, {200, 100});
+ auto points = component.CreatePolyline();
+ LogPoint({100, 100}, true);
+ for (auto point : points) {
+ LogPoint(point, point != points.back());
+ }
+ FML_LOG(ERROR) << points.size();
+ CreateCubicPolyline(component);
+}
+
+TEST(GeometryTest, NewQuadPolylineAlgo) {
+ QuadraticPathComponent component({100, 100}, {300, 200}, {200, 100});
+ auto points = component.CreatePolyline();
+ // LogPoint({100, 100}, true);
+ for (auto point : points) {
+ LogPoint(point, point != points.back());
+ }
+ FML_LOG(ERROR) << points.size();
+ CreateQuadPolyline(component);
+}
+
} // namespace testing
} // namespace impeller
diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc
index 7e632d7287..6b2ecf847b 100644
--- a/impeller/geometry/path.cc
+++ b/impeller/geometry/path.cc
@@ -221,8 +221,7 @@ bool Path::UpdateContourComponentAtIndex(size_t index,
return true;
}
-Path::Polyline Path::CreatePolyline(
- const SmoothingApproximation& approximation) const {
+Path::Polyline Path::CreatePolyline(Scalar tolerance) const {
Polyline polyline;
std::optional<Point> previous_contour_point;
@@ -251,10 +250,10 @@ Path::Polyline Path::CreatePolyline(
collect_points(linears_[component.index].CreatePolyline());
break;
case ComponentType::kQuadratic:
- collect_points(quads_[component.index].CreatePolyline(approximation));
+ collect_points(quads_[component.index].CreatePolyline(tolerance));
break;
case ComponentType::kCubic:
- collect_points(cubics_[component.index].CreatePolyline(approximation));
+ collect_points(cubics_[component.index].CreatePolyline(tolerance));
break;
case ComponentType::kContour:
if (component_i == components_.size() - 1) {
diff --git a/impeller/geometry/path.h b/impeller/geometry/path.h
index 77a20fda0e..22879e5de3 100644
--- a/impeller/geometry/path.h
+++ b/impeller/geometry/path.h
@@ -117,8 +117,7 @@ class Path {
bool UpdateContourComponentAtIndex(size_t index,
const ContourComponent& contour);
- Polyline CreatePolyline(
- const SmoothingApproximation& approximation = {}) const;
+ Polyline CreatePolyline(Scalar tolerance = kDefaultCurveTolerance) const;
std::optional<Rect> GetBoundingBox() const;
diff --git a/impeller/geometry/path_component.cc b/impeller/geometry/path_component.cc
index f9c208386e..92eb1f6d1d 100644
--- a/impeller/geometry/path_component.cc
+++ b/impeller/geometry/path_component.cc
@@ -8,10 +8,6 @@
namespace impeller {
-static const size_t kRecursionLimit = 32;
-static const Scalar kCurveCollinearityEpsilon = 1e-30;
-static const Scalar kCurveAngleToleranceEpsilon = 0.01;
-
/*
* Based on: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Specific_cases
*/
@@ -85,10 +81,57 @@ Point QuadraticPathComponent::SolveDerivative(Scalar time) const {
};
}
+static Scalar ApproximateParabolaIntegral(Scalar x) {
+ constexpr Scalar d = 0.67;
+ return x / (1.0 - d + sqrt(sqrt(pow(d, 4) + 0.25 * x * x)));
+}
+
std::vector<Point> QuadraticPathComponent::CreatePolyline(
- const SmoothingApproximation& approximation) const {
- CubicPathComponent elevated(*this);
- return elevated.CreatePolyline(approximation);
+ Scalar tolerance) const {
+ std::vector<Point> points;
+ FillPointsForPolyline(points, tolerance);
+ return points;
+}
+
+void QuadraticPathComponent::FillPointsForPolyline(std::vector<Point>& points,
+ Scalar tolerance) const {
+ auto sqrt_tolerance = sqrt(tolerance);
+
+ auto d01 = cp - p1;
+ auto d12 = p2 - cp;
+ auto dd = d01 - d12;
+ auto cross = (p2 - p1).Cross(dd);
+ auto x0 = d01.Dot(dd) * 1 / cross;
+ auto x2 = d12.Dot(dd) * 1 / cross;
+ auto scale = abs(cross / (hypot(dd.x, dd.y) * (x2 - x0)));
+
+ auto a0 = ApproximateParabolaIntegral(x0);
+ auto a2 = ApproximateParabolaIntegral(x2);
+ Scalar val = 0.f;
+ if (isfinite(scale)) {
+ auto da = abs(a2 - a0);
+ auto sqrt_scale = sqrt(scale);
+ if ((x0 < 0 && x2 < 0) || (x0 >= 0 && x2 >= 0)) {
+ val = da * sqrt_scale;
+ } else {
+ // cusp case
+ auto xmin = sqrt_tolerance / sqrt_scale;
+ val = sqrt_tolerance * da / ApproximateParabolaIntegral(xmin);
+ }
+ }
+ auto u0 = ApproximateParabolaIntegral(a0);
+ auto u2 = ApproximateParabolaIntegral(a2);
+ auto uscale = 1 / (u2 - u0);
+
+ auto line_count = std::max(1., ceil(0.5 * val / sqrt_tolerance));
+ auto step = 1 / line_count;
+ for (size_t i = 1; i < line_count; i += 1) {
+ auto u = i * step;
+ auto a = a0 + (a2 - a0) * u;
+ auto t = (ApproximateParabolaIntegral(a) - u0) * uscale;
+ points.emplace_back(Solve(t));
+ }
+ points.emplace_back(p2);
}
std::vector<Point> QuadraticPathComponent::Extrema() const {
@@ -110,238 +153,61 @@ Point CubicPathComponent::SolveDerivative(Scalar time) const {
};
}
-/*
- * Paul de Casteljau's subdivision with modifications as described in
- * http://agg.sourceforge.net/antigrain.com/research/adaptive_bezier/index.html.
- * Refer to the diagram on that page for a description of the points.
- */
-static void CubicPathSmoothenRecursive(const SmoothingApproximation& approx,
- std::vector<Point>& points,
- Point p1,
- Point p2,
- Point p3,
- Point p4,
- size_t level) {
- if (level >= kRecursionLimit) {
- return;
+std::vector<Point> CubicPathComponent::CreatePolyline(Scalar tolerance) const {
+ auto quads = ToQuadratics(.1);
+ std::vector<Point> points;
+ for (const auto& quad : quads) {
+ quad.FillPointsForPolyline(points, tolerance);
}
+ return points;
+}
- /*
- * Find all midpoints.
- */
- auto p12 = (p1 + p2) / 2.0;
- auto p23 = (p2 + p3) / 2.0;
- auto p34 = (p3 + p4) / 2.0;
-
- auto p123 = (p12 + p23) / 2.0;
- auto p234 = (p23 + p34) / 2.0;
-
- auto p1234 = (p123 + p234) / 2.0;
-
- /*
- * Attempt approximation using single straight line.
- */
- auto d = p4 - p1;
- Scalar d2 = fabs(((p2.x - p4.x) * d.y - (p2.y - p4.y) * d.x));
- Scalar d3 = fabs(((p3.x - p4.x) * d.y - (p3.y - p4.y) * d.x));
-
- Scalar da1 = 0;
- Scalar da2 = 0;
- Scalar k = 0;
-
- switch ((static_cast<int>(d2 > kCurveCollinearityEpsilon) << 1) +
- static_cast<int>(d3 > kCurveCollinearityEpsilon)) {
- case 0:
- /*
- * All collinear OR p1 == p4.
- */
- k = d.x * d.x + d.y * d.y;
- if (k == 0) {
- d2 = p1.GetDistanceSquared(p2);
- d3 = p4.GetDistanceSquared(p3);
- } else {
- k = 1.0 / k;
- da1 = p2.x - p1.x;
- da2 = p2.y - p1.y;
- d2 = k * (da1 * d.x + da2 * d.y);
- da1 = p3.x - p1.x;
- da2 = p3.y - p1.y;
- d3 = k * (da1 * d.x + da2 * d.y);
-
- if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) {
- /*
- * Simple collinear case, 1---2---3---4. Leave just two endpoints.
- */
- return;
- }
-
- if (d2 <= 0) {
- d2 = p2.GetDistanceSquared(p1);
- } else if (d2 >= 1) {
- d2 = p2.GetDistanceSquared(p4);
- } else {
- d2 = p2.GetDistanceSquared({p1.x + d2 * d.x, p1.y + d2 * d.y});
- }
-
- if (d3 <= 0) {
- d3 = p3.GetDistanceSquared(p1);
- } else if (d3 >= 1) {
- d3 = p3.GetDistanceSquared(p4);
- } else {
- d3 = p3.GetDistanceSquared({p1.x + d3 * d.x, p1.y + d3 * d.y});
- }
- }
-
- if (d2 > d3) {
- if (d2 < approx.distance_tolerance_square) {
- points.emplace_back(p2);
- return;
- }
- } else {
- if (d3 < approx.distance_tolerance_square) {
- points.emplace_back(p3);
- return;
- }
- }
- break;
- case 1:
- /*
- * p1, p2, p4 are collinear, p3 is significant.
- */
- if (d3 * d3 <=
- approx.distance_tolerance_square * (d.x * d.x + d.y * d.y)) {
- if (approx.angle_tolerance < kCurveAngleToleranceEpsilon) {
- points.emplace_back(p23);
- return;
- }
-
- /*
- * Angle Condition.
- */
- da1 = ::fabs(::atan2(p4.y - p3.y, p4.x - p3.x) -
- ::atan2(p3.y - p2.y, p3.x - p2.x));
-
- if (da1 >= kPi) {
- da1 = 2.0 * kPi - da1;
- }
-
- if (da1 < approx.angle_tolerance) {
- points.emplace_back(p2);
- points.emplace_back(p3);
- return;
- }
-
- if (approx.cusp_limit != 0.0) {
- if (da1 > approx.cusp_limit) {
- points.emplace_back(p3);
- return;
- }
- }
- }
- break;
-
- case 2:
- /*
- * p1,p3,p4 are collinear, p2 is significant.
- */
- if (d2 * d2 <=
- approx.distance_tolerance_square * (d.x * d.x + d.y * d.y)) {
- if (approx.angle_tolerance < kCurveAngleToleranceEpsilon) {
- points.emplace_back(p23);
- return;
- }
-
- /*
- * Angle Condition.
- */
- da1 = ::fabs(::atan2(p3.y - p2.y, p3.x - p2.x) -
- ::atan2(p2.y - p1.y, p2.x - p1.x));
-
- if (da1 >= kPi) {
- da1 = 2.0 * kPi - da1;
- }
-
- if (da1 < approx.angle_tolerance) {
- points.emplace_back(p2);
- points.emplace_back(p3);
- return;
- }
-
- if (approx.cusp_limit != 0.0) {
- if (da1 > approx.cusp_limit) {
- points.emplace_back(p2);
- return;
- }
- }
- }
- break;
-
- case 3:
- /*
- * Regular case.
- */
- if ((d2 + d3) * (d2 + d3) <=
- approx.distance_tolerance_square * (d.x * d.x + d.y * d.y)) {
- /*
- * If the curvature doesn't exceed the distance_tolerance value
- * we tend to finish subdivisions.
- */
- if (approx.angle_tolerance < kCurveAngleToleranceEpsilon) {
- points.emplace_back(p23);
- return;
- }
-
- /*
- * Angle & Cusp Condition.
- */
- k = ::atan2(p3.y - p2.y, p3.x - p2.x);
- da1 = ::fabs(k - ::atan2(p2.y - p1.y, p2.x - p1.x));
- da2 = ::fabs(::atan2(p4.y - p3.y, p4.x - p3.x) - k);
-
- if (da1 >= kPi) {
- da1 = 2.0 * kPi - da1;
- }
-
- if (da2 >= kPi) {
- da2 = 2.0 * kPi - da2;
- }
-
- if (da1 + da2 < approx.angle_tolerance) {
- /*
- * Finally we can stop the recursion.
- */
- points.emplace_back(p23);
- return;
- }
-
- if (approx.cusp_limit != 0.0) {
- if (da1 > approx.cusp_limit) {
- points.emplace_back(p2);
- return;
- }
-
- if (da2 > approx.cusp_limit) {
- points.emplace_back(p3);
- return;
- }
- }
- }
- break;
- }
+inline QuadraticPathComponent CubicPathComponent::Lower() const {
+ return QuadraticPathComponent(3.0 * (cp1 - p1), 3.0 * (cp2 - cp1),
+ 3.0 * (p2 - cp2));
+}
- /*
- * Continue subdivision.
- */
- CubicPathSmoothenRecursive(approx, points, p1, p12, p123, p1234, level + 1);
- CubicPathSmoothenRecursive(approx, points, p1234, p234, p34, p4, level + 1);
+CubicPathComponent CubicPathComponent::Subsegment(Scalar t0, Scalar t1) const {
+ auto p0 = Solve(t0);
+ auto p3 = Solve(t1);
+ auto d = Lower();
+ auto scale = (t1 - t0) * (1.0 / 3.0);
+ auto p1 = p0 + scale * d.Solve(t0);
+ auto p2 = p3 - scale * d.Solve(t1);
+ return CubicPathComponent(p0, p1, p2, p3);
}
-std::vector<Point> CubicPathComponent::CreatePolyline(
- const SmoothingApproximation& approximation) const {
- std::vector<Point> points;
- CubicPathSmoothenRecursive(approximation, points, p1, cp1, cp2, p2, 0);
- points.emplace_back(p2);
- return points;
+std::vector<QuadraticPathComponent> CubicPathComponent::ToQuadratics(
+ Scalar accuracy) const {
+ std::vector<QuadraticPathComponent> quads;
+ // The maximum error, as a vector from the cubic to the best approximating
+ // quadratic, is proportional to the third derivative, which is constant
+ // across the segment. Thus, the error scales down as the third power of
+ // the number of subdivisions. Our strategy then is to subdivide `t` evenly.
+ //
+ // This is an overestimate of the error because only the component
+ // perpendicular to the first derivative is important. But the simplicity is
+ // appealing.
+
+ // This magic number is the square of 36 / sqrt(3).
+ // See: http://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
+ auto max_hypot2 = 432.0 * accuracy * accuracy;
+ auto p1x2 = 3.0 * cp1 - p1;
+ auto p2x2 = 3.0 * cp2 - p2;
+ auto p = p2x2 - p1x2;
+ auto err = p.Dot(p);
+ auto quad_count = std::max(1., ceil(pow(err / max_hypot2, 1. / 6.0)));
+
+ for (size_t i = 0; i < quad_count; i++) {
+ auto t0 = i / quad_count;
+ auto t1 = (i + 1) / quad_count;
+ auto seg = Subsegment(t0, t1);
+ auto p1x2 = 3.0 * seg.cp1 - seg.p1;
+ auto p2x2 = 3.0 * seg.cp2 - seg.p2;
+ quads.emplace_back(
+ QuadraticPathComponent(seg.p1, ((p1x2 + p2x2) / 4.0), seg.p2));
+ }
+ return quads;
}
static inline bool NearEqual(Scalar a, Scalar b, Scalar epsilon) {
diff --git a/impeller/geometry/path_component.h b/impeller/geometry/path_component.h
index d1cdf3a0b0..cf7b275581 100644
--- a/impeller/geometry/path_component.h
+++ b/impeller/geometry/path_component.h
@@ -12,46 +12,15 @@
namespace impeller {
-/// Information about how to approximate points on a curved path segment.
-///
-/// In particular, the values in this object control how many vertices to
-/// generate when approximating curves, and what tolerances to use when
-/// calculating the sharpness of curves.
-struct SmoothingApproximation {
- /// The scaling coefficient to use when translating to screen coordinates.
- ///
- /// Values approaching 0.0 will generate smoother looking curves with a
- /// greater number of vertices, and will be more expensive to calculate.
- Scalar scale;
-
- /// The tolerance value in radians for calculating sharp angles.
- ///
- /// Values approaching 0.0 will provide more accurate approximation of sharp
- /// turns. A 0.0 value means angle conditions are not considered at all.
- Scalar angle_tolerance;
-
- /// An angle in radians at which to introduce bevel cuts.
- ///
- /// Values greater than zero will restirct the sharpness of bevel cuts on
- /// turns.
- Scalar cusp_limit;
-
- /// Used to more quickly detect colinear cases.
- Scalar distance_tolerance_square;
-
- SmoothingApproximation(/* default */)
- : SmoothingApproximation(1.0 /* scale */,
- 0.0 /* angle tolerance */,
- 0.0 /* cusp limit */) {}
-
- SmoothingApproximation(Scalar p_scale,
- Scalar p_angle_tolerance,
- Scalar p_cusp_limit)
- : scale(p_scale),
- angle_tolerance(p_angle_tolerance),
- cusp_limit(p_cusp_limit),
- distance_tolerance_square(0.5 * p_scale * 0.5 * p_scale) {}
-};
+// The default tolerance value for QuadraticCurveComponent::CreatePolyline and
+// CubicCurveComponent::CreatePolyline. It also impacts the number of quadratics
+// created when flattening a cubic curve to a polyline.
+//
+// Smaller numbers mean more points. This number seems suitable for particularly
+// curvy curves at scales close to 1.0. As the scale increases, this number
+// should be divided by Matrix::GetMaxBasisLength to avoid generating too few
+// points for the given scale.
+static constexpr Scalar kDefaultCurveTolerance = .1f;
struct LinearPathComponent {
Point p1;
@@ -86,8 +55,21 @@ struct QuadraticPathComponent {
Point SolveDerivative(Scalar time) const;
+ // Uses the algorithm described by Raph Levien in
+ // https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html.
+ //
+ // The algorithm has several benefits:
+ // - It does not require elevation to cubics for processing.
+ // - It generates fewer and more accurate points than recursive subdivision.
+ // - Each turn of the core iteration loop has no dependencies on other turns,
+ // making it trivially parallelizable.
+ //
+ // See also the implementation in kurbo: https://github.com/linebender/kurbo.
std::vector<Point> CreatePolyline(
- const SmoothingApproximation& approximation) const;
+ Scalar tolerance = kDefaultCurveTolerance) const;
+
+ void FillPointsForPolyline(std::vector<Point>& points,
+ Scalar tolerance = kDefaultCurveTolerance) const;
std::vector<Point> Extrema() const;
@@ -117,15 +99,26 @@ struct CubicPathComponent {
Point SolveDerivative(Scalar time) const;
+ // This method approximates the cubic component with quadratics, and then
+ // generates a polyline from those quadratics.
+ //
+ // See the note on QuadraticPathComponent::CreatePolyline for references.
std::vector<Point> CreatePolyline(
- const SmoothingApproximation& approximation) const;
+ Scalar tolerance = kDefaultCurveTolerance) const;
std::vector<Point> Extrema() const;
+ std::vector<QuadraticPathComponent> ToQuadratics(Scalar accuracy) const;
+
+ CubicPathComponent Subsegment(Scalar t0, Scalar t1) const;
+
bool operator==(const CubicPathComponent& other) const {
return p1 == other.p1 && cp1 == other.cp1 && cp2 == other.cp2 &&
p2 == other.p2;
}
+
+ private:
+ QuadraticPathComponent Lower() const;
};
struct ContourComponent {
diff --git a/impeller/tessellator/c/tessellator.cc b/impeller/tessellator/c/tessellator.cc
index 7a53d9cede..09b44b5014 100644
--- a/impeller/tessellator/c/tessellator.cc
+++ b/impeller/tessellator/c/tessellator.cc
@@ -39,12 +39,11 @@ void Close(PathBuilder* builder) {
struct Vertices* Tessellate(PathBuilder* builder,
int fill_type,
- Scalar scale,
+ Scalar tolerance,
Scalar angle_tolerance,
Scalar cusp_limit) {
auto path = builder->CopyPath(static_cast<FillType>(fill_type));
- auto smoothing = SmoothingApproximation(scale, angle_tolerance, cusp_limit);
- auto polyline = path.CreatePolyline(smoothing);
+ auto polyline = path.CreatePolyline(tolerance);
std::vector<float> points;
if (Tessellator{}.Tessellate(path.GetFillType(), polyline,
[&points](Point vertex) {
diff --git a/impeller/tessellator/c/tessellator.h b/impeller/tessellator/c/tessellator.h
index 23a24e1f96..0cb01d68f6 100644
--- a/impeller/tessellator/c/tessellator.h
+++ b/impeller/tessellator/c/tessellator.h
@@ -42,7 +42,8 @@ IMPELLER_API void Close(PathBuilder* builder);
IMPELLER_API struct Vertices* Tessellate(PathBuilder* builder,
int fill_type,
- Scalar scale,
+ Scalar tolerance,
+ // Unused, remaining for ABI stability.
Scalar angle_tolerance,
Scalar cusp_limit);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment