diff --git a/impeller/entity/contents/linear_gradient_contents.cc b/impeller/entity/contents/linear_gradient_contents.cc index 55b9416c86269..bed837a8aa6df 100644 --- a/impeller/entity/contents/linear_gradient_contents.cc +++ b/impeller/entity/contents/linear_gradient_contents.cc @@ -42,12 +42,13 @@ bool LinearGradientContents::Render(const ContentContext& renderer, auto vertices_builder = VertexBufferBuilder(); { - auto result = Tessellator{entity.GetPath().GetFillType()}.Tessellate( - entity.GetPath().CreatePolyline(), [&vertices_builder](Point point) { - VS::PerVertexData vtx; - vtx.vertices = point; - vertices_builder.AppendVertex(vtx); - }); + auto result = Tessellator{}.Tessellate(entity.GetPath().GetFillType(), + entity.GetPath().CreatePolyline(), + [&vertices_builder](Point point) { + VS::PerVertexData vtx; + vtx.vertices = point; + vertices_builder.AppendVertex(vtx); + }); if (!result) { return false; } diff --git a/impeller/entity/contents/solid_color_contents.cc b/impeller/entity/contents/solid_color_contents.cc index 71318f8cb6dc5..5d2d63dc19452 100644 --- a/impeller/entity/contents/solid_color_contents.cc +++ b/impeller/entity/contents/solid_color_contents.cc @@ -30,8 +30,8 @@ VertexBuffer SolidColorContents::CreateSolidFillVertices(const Path& path, VertexBufferBuilder vtx_builder; - auto tesselation_result = Tessellator{path.GetFillType()}.Tessellate( - path.CreatePolyline(), [&vtx_builder](auto point) { + auto tesselation_result = Tessellator{}.Tessellate( + path.GetFillType(), path.CreatePolyline(), [&vtx_builder](auto point) { VS::PerVertexData vtx; vtx.vertices = point; vtx_builder.AppendVertex(vtx); diff --git a/impeller/entity/contents/text_contents.cc b/impeller/entity/contents/text_contents.cc index 149f142e930ad..1abbd1f340166 100644 --- a/impeller/entity/contents/text_contents.cc +++ b/impeller/entity/contents/text_contents.cc @@ -78,7 +78,8 @@ bool TextContents::Render(const ContentContext& renderer, // atlas. { VertexBufferBuilder vertex_builder; - if (!Tessellator{FillType::kPositive}.Tessellate( + if (!Tessellator{}.Tessellate( + FillType::kPositive, PathBuilder{} .AddRect(Rect::MakeXYWH(0.0, 0.0, 1.0, 1.0)) .TakePath() diff --git a/impeller/entity/contents/texture_contents.cc b/impeller/entity/contents/texture_contents.cc index 7e57278afb019..79bea6651cb6b 100644 --- a/impeller/entity/contents/texture_contents.cc +++ b/impeller/entity/contents/texture_contents.cc @@ -61,19 +61,18 @@ bool TextureContents::Render(const ContentContext& renderer, VertexBufferBuilder vertex_builder; { - const auto tess_result = - Tessellator{entity.GetPath().GetFillType()}.Tessellate( - entity.GetPath().CreatePolyline(), - [this, &vertex_builder, &coverage_rect, &texture_size](Point vtx) { - VS::PerVertexData data; - data.vertices = vtx; - auto coverage_coords = - (vtx - coverage_rect->origin) / coverage_rect->size; - data.texture_coords = - (source_rect_.origin + source_rect_.size * coverage_coords) / - texture_size; - vertex_builder.AppendVertex(data); - }); + const auto tess_result = Tessellator{}.Tessellate( + entity.GetPath().GetFillType(), entity.GetPath().CreatePolyline(), + [this, &vertex_builder, &coverage_rect, &texture_size](Point vtx) { + VS::PerVertexData data; + data.vertices = vtx; + auto coverage_coords = + (vtx - coverage_rect->origin) / coverage_rect->size; + data.texture_coords = + (source_rect_.origin + source_rect_.size * coverage_coords) / + texture_size; + vertex_builder.AppendVertex(data); + }); if (!tess_result) { return false; } diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 149ded9942af5..b0fca2aa83139 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -502,10 +502,9 @@ TEST_F(EntityTest, BlendingModeOptions) { // test GUI. const Entity::BlendMode b{}; - static_assert( - b == Entity::BlendMode::kClear); // Ensure the first item in - // the switch is the first - // item in the enum. + static_assert(b == Entity::BlendMode::kClear); // Ensure the first item in + // the switch is the first + // item in the enum. switch (b) { case Entity::BlendMode::kClear: blend_mode_names.push_back("Clear"); @@ -521,8 +520,7 @@ TEST_F(EntityTest, BlendingModeOptions) { blend_mode_values.push_back(Entity::BlendMode::kSourceOver); case Entity::BlendMode::kDestinationOver: blend_mode_names.push_back("DestinationOver"); - blend_mode_values.push_back( - Entity::BlendMode::kDestinationOver); + blend_mode_values.push_back(Entity::BlendMode::kDestinationOver); }; } @@ -534,9 +532,8 @@ TEST_F(EntityTest, BlendingModeOptions) { ImGui::SetNextWindowPos({200, 450}); } - auto draw_rect = [&context, &pass]( - Rect rect, Color color, - Entity::BlendMode blend_mode) -> bool { + auto draw_rect = [&context, &pass](Rect rect, Color color, + Entity::BlendMode blend_mode) -> bool { using VS = SolidFillPipeline::VertexShader; VertexBufferBuilder vtx_builder; { @@ -579,8 +576,7 @@ TEST_F(EntityTest, BlendingModeOptions) { blend_mode_names.data(), blend_mode_names.size()); ImGui::End(); - Entity::BlendMode selected_mode = - blend_mode_values[current_blend_index]; + Entity::BlendMode selected_mode = blend_mode_values[current_blend_index]; Point a, b, c, d; std::tie(a, b) = IMPELLER_PLAYGROUND_LINE( @@ -601,5 +597,29 @@ TEST_F(EntityTest, BlendingModeOptions) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } +TEST_F(EntityTest, BezierCircleScaled) { + Entity entity; + auto path = PathBuilder{} + .MoveTo({97.325, 34.818}) + .CubicCurveTo({98.50862885295136, 34.81812293973836}, + {99.46822048142015, 33.85863261475589}, + {99.46822048142015, 32.67499810206613}) + .CubicCurveTo({99.46822048142015, 31.491363589376355}, + {98.50862885295136, 30.53187326439389}, + {97.32499434685802, 30.531998226542708}) + .CubicCurveTo({96.14153655073771, 30.532123170035373}, + {95.18222070648729, 31.491540299350355}, + {95.18222070648729, 32.67499810206613}) + .CubicCurveTo({95.18222070648729, 33.85845590478189}, + {96.14153655073771, 34.81787303409686}, + {97.32499434685802, 34.81799797758954}) + .Close() + .TakePath(); + entity.SetPath(path); + entity.SetTransformation(Matrix::MakeScale({20.0, 20.0, 1.0}).Translate({-80, -15, 0})); + entity.SetContents(SolidColorContents::Make(Color::Red())); + ASSERT_TRUE(OpenPlaygroundHere(entity)); +} + } // namespace testing } // namespace impeller diff --git a/impeller/geometry/path_component.cc b/impeller/geometry/path_component.cc index eb842e509e9c1..70f83c3f1a18c 100644 --- a/impeller/geometry/path_component.cc +++ b/impeller/geometry/path_component.cc @@ -112,7 +112,7 @@ Point CubicPathComponent::SolveDerivative(Scalar time) const { /* * Paul de Casteljau's subdivision with modifications as described in - * http://www.antigrain.com/research/adaptive_bezier/index.html. + * 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, diff --git a/impeller/geometry/path_component.h b/impeller/geometry/path_component.h index 60331ef899232..4fd513370cd2c 100644 --- a/impeller/geometry/path_component.h +++ b/impeller/geometry/path_component.h @@ -12,10 +12,31 @@ 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 */) diff --git a/impeller/tessellator/c/tessellator.cc b/impeller/tessellator/c/tessellator.cc index 119835dd3fb80..e6c4661ebae7e 100644 --- a/impeller/tessellator/c/tessellator.cc +++ b/impeller/tessellator/c/tessellator.cc @@ -37,16 +37,21 @@ void Close(PathBuilder* builder) { builder->Close(); } -struct Vertices* Tessellate(PathBuilder* builder) { - auto path = builder->CopyPath(); - auto polyline = path.CreatePolyline(); +struct Vertices* Tessellate(PathBuilder* builder, + int fill_type, + Scalar scale, + Scalar angle_tolerance, + Scalar cusp_limit) { + auto path = builder->CopyPath(static_cast(fill_type)); + auto smoothing = SmoothingApproximation(scale, angle_tolerance, cusp_limit); + auto polyline = path.CreatePolyline(smoothing); std::vector points; - if (!Tessellator{path.GetFillType()}.Tessellate(polyline, - [&points](Point vertex) { - points.push_back(vertex.x); - points.push_back(vertex.y); - })) { + if (!Tessellator{}.Tessellate(path.GetFillType(), polyline, + [&points](Point vertex) { + points.push_back(vertex.x); + points.push_back(vertex.y); + })) { return nullptr; } diff --git a/impeller/tessellator/c/tessellator.h b/impeller/tessellator/c/tessellator.h index 1ca6efd88002f..23a24e1f9603f 100644 --- a/impeller/tessellator/c/tessellator.h +++ b/impeller/tessellator/c/tessellator.h @@ -7,7 +7,11 @@ #include "impeller/geometry/path_builder.h" #include "impeller/tessellator/tessellator.h" +#ifdef _WIN32 +#define IMPELLER_API __declspec(dllexport) +#else #define IMPELLER_API __attribute__((visibility("default"))) +#endif extern "C" { @@ -36,7 +40,11 @@ IMPELLER_API void CubicTo(PathBuilder* builder, IMPELLER_API void Close(PathBuilder* builder); -IMPELLER_API struct Vertices* Tessellate(PathBuilder* builder); +IMPELLER_API struct Vertices* Tessellate(PathBuilder* builder, + int fill_type, + Scalar scale, + Scalar angle_tolerance, + Scalar cusp_limit); IMPELLER_API void DestroyVertices(Vertices* vertices); diff --git a/impeller/tessellator/dart/lib/tessellator.dart b/impeller/tessellator/dart/lib/tessellator.dart index 2cc9358ebb85e..1e88d49253d1f 100644 --- a/impeller/tessellator/dart/lib/tessellator.dart +++ b/impeller/tessellator/dart/lib/tessellator.dart @@ -7,6 +7,53 @@ import 'dart:ffi' as ffi; import 'dart:io'; import 'dart:typed_data'; +/// Determines the winding rule that decides how the interior of a Path is +/// calculated. +/// +/// This enum is used by the [VerticesBuilder.tessellate] method. +// must match ordering in geometry/path.h +enum FillType { + /// The interior is defined by a non-zero sum of signed edge crossings. + nonZero, + + /// The interior is defined by an odd number of edge crossings. + evenOdd, +} + +/// 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. +/// +/// Used by [VerticesBuilder.tessellate]. +class SmoothingApproximation { + /// Creates a new smoothing approximation instance with default values. + const SmoothingApproximation({ + this.scale = 1.0, + this.angleTolerance = 0.0, + this.cuspLimit = 0.0, + }); + + /// 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. + final double 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. + final double angleTolerance; + + /// An angle in radians at which to introduce bevel cuts. + /// + /// Values greater than zero will restirct the sharpness of bevel cuts on + /// turns. + final double cuspLimit; +} + /// Creates vertices from path commands. /// /// First, build up the path contours with the [moveTo], [lineTo], [cubicTo], @@ -56,10 +103,19 @@ class VerticesBuilder { /// Tessellates the path created by the previous method calls into a list of /// vertices. - Float32List tessellate() { + Float32List tessellate({ + FillType fillType = FillType.nonZero, + SmoothingApproximation smoothing = const SmoothingApproximation(), + }) { assert(_vertices.isEmpty); assert(_builder != null); - final ffi.Pointer<_Vertices> vertices = _tessellateFn(_builder!); + final ffi.Pointer<_Vertices> vertices = _tessellateFn( + _builder!, + fillType.index, + smoothing.scale, + smoothing.angleTolerance, + smoothing.cuspLimit, + ); _vertices.add(vertices); return vertices.ref.points.asTypedList(vertices.ref.size); } @@ -103,26 +159,49 @@ typedef _create_path_builder_type = ffi.Pointer<_PathBuilder> Function(); final _CreatePathBuilderType _createPathFn = _dylib.lookupFunction<_create_path_builder_type, _CreatePathBuilderType>( - 'CreatePathBuilder'); + 'CreatePathBuilder', +); typedef _MoveToType = void Function(ffi.Pointer<_PathBuilder>, double, double); typedef _move_to_type = ffi.Void Function( - ffi.Pointer<_PathBuilder>, ffi.Float, ffi.Float); + ffi.Pointer<_PathBuilder>, + ffi.Float, + ffi.Float, +); -final _MoveToType _moveToFn = - _dylib.lookupFunction<_move_to_type, _MoveToType>('MoveTo'); +final _MoveToType _moveToFn = _dylib.lookupFunction<_move_to_type, _MoveToType>( + 'MoveTo', +); typedef _LineToType = void Function(ffi.Pointer<_PathBuilder>, double, double); typedef _line_to_type = ffi.Void Function( - ffi.Pointer<_PathBuilder>, ffi.Float, ffi.Float); + ffi.Pointer<_PathBuilder>, + ffi.Float, + ffi.Float, +); -final _LineToType _lineToFn = - _dylib.lookupFunction<_line_to_type, _LineToType>('LineTo'); +final _LineToType _lineToFn = _dylib.lookupFunction<_line_to_type, _LineToType>( + 'LineTo', +); typedef _CubicToType = void Function( - ffi.Pointer<_PathBuilder>, double, double, double, double, double, double); -typedef _cubic_to_type = ffi.Void Function(ffi.Pointer<_PathBuilder>, ffi.Float, - ffi.Float, ffi.Float, ffi.Float, ffi.Float, ffi.Float); + ffi.Pointer<_PathBuilder>, + double, + double, + double, + double, + double, + double, +); +typedef _cubic_to_type = ffi.Void Function( + ffi.Pointer<_PathBuilder>, + ffi.Float, + ffi.Float, + ffi.Float, + ffi.Float, + ffi.Float, + ffi.Float, +); final _CubicToType _cubicToFn = _dylib.lookupFunction<_cubic_to_type, _CubicToType>('CubicTo'); @@ -134,9 +213,19 @@ final _CloseType closeFn = _dylib.lookupFunction<_close_type, _CloseType>('Close'); typedef _TessellateType = ffi.Pointer<_Vertices> Function( - ffi.Pointer<_PathBuilder>); + ffi.Pointer<_PathBuilder>, + int, + double, + double, + double, +); typedef _tessellate_type = ffi.Pointer<_Vertices> Function( - ffi.Pointer<_PathBuilder>); + ffi.Pointer<_PathBuilder>, + ffi.Int, + ffi.Float, + ffi.Float, + ffi.Float, +); final _TessellateType _tessellateFn = _dylib.lookupFunction<_tessellate_type, _TessellateType>('Tessellate'); @@ -145,11 +234,14 @@ typedef _DestroyType = void Function(ffi.Pointer<_PathBuilder>); typedef _destroy_type = ffi.Void Function(ffi.Pointer<_PathBuilder>); final _DestroyType _destroyFn = - _dylib.lookupFunction<_destroy_type, _DestroyType>('DestroyPathBuilder'); + _dylib.lookupFunction<_destroy_type, _DestroyType>( + 'DestroyPathBuilder', +); typedef _DestroyVerticesType = void Function(ffi.Pointer<_Vertices>); typedef _destroy_vertices_type = ffi.Void Function(ffi.Pointer<_Vertices>); final _DestroyVerticesType _destroyVerticesFn = _dylib.lookupFunction<_destroy_vertices_type, _DestroyVerticesType>( - 'DestroyVertices'); + 'DestroyVertices', +); diff --git a/impeller/tessellator/tessellator.cc b/impeller/tessellator/tessellator.cc index d63ac18461bb3..542cefbbb2737 100644 --- a/impeller/tessellator/tessellator.cc +++ b/impeller/tessellator/tessellator.cc @@ -8,7 +8,7 @@ namespace impeller { -Tessellator::Tessellator(FillType type) : fill_type_(type) {} +Tessellator::Tessellator() = default; Tessellator::~Tessellator() = default; @@ -34,7 +34,8 @@ static void DestroyTessellator(TESStesselator* tessellator) { } } -bool Tessellator::Tessellate(const Path::Polyline& polyline, +bool Tessellator::Tessellate(FillType fill_type, + const Path::Polyline& polyline, VertexCallback callback) const { if (!callback) { return false; @@ -75,11 +76,11 @@ bool Tessellator::Tessellate(const Path::Polyline& polyline, //---------------------------------------------------------------------------- /// Let's tessellate. /// - auto result = ::tessTesselate(tessellator.get(), // tessellator - ToTessWindingRule(fill_type_), // winding - TESS_POLYGONS, // element type - kPolygonSize, // polygon size - kVertexSize, // vertex size + auto result = ::tessTesselate(tessellator.get(), // tessellator + ToTessWindingRule(fill_type), // winding + TESS_POLYGONS, // element type + kPolygonSize, // polygon size + kVertexSize, // vertex size nullptr // normal (null is automatic) ); @@ -111,8 +112,4 @@ bool Tessellator::Tessellate(const Path::Polyline& polyline, return true; } -WindingOrder Tessellator::GetFrontFaceWinding() const { - return WindingOrder::kClockwise; -} - } // namespace impeller diff --git a/impeller/tessellator/tessellator.h b/impeller/tessellator/tessellator.h index d60e078315334..d7c00132eee94 100644 --- a/impeller/tessellator/tessellator.h +++ b/impeller/tessellator/tessellator.h @@ -26,28 +26,26 @@ enum class WindingOrder { /// class Tessellator { public: - explicit Tessellator(FillType type); + Tessellator(); ~Tessellator(); - WindingOrder GetFrontFaceWinding() const; - using VertexCallback = std::function; //---------------------------------------------------------------------------- - /// @brief Generates triangles from the polyline. A callback is invoked - /// for each vertex of the triangle. + /// @brief Generates filled triangles from the polyline. A callback is + /// invoked for each vertex of the triangle. /// + /// @param[in] fill_type The fill rule to use when filling. /// @param[in] polyline The polyline /// @param[in] callback The callback /// /// @return If tessellation was successful. /// - bool Tessellate(const Path::Polyline& polyline, + bool Tessellate(FillType fill_type, + const Path::Polyline& polyline, VertexCallback callback) const; private: - const FillType fill_type_ = FillType::kNonZero; - FML_DISALLOW_COPY_AND_ASSIGN(Tessellator); };