diff --git a/impeller/core/range.h b/impeller/core/range.h index 94c61baf3bb3a..d0971725c6928 100644 --- a/impeller/core/range.h +++ b/impeller/core/range.h @@ -7,6 +7,8 @@ #include +#include "flutter/fml/macros.h" + namespace impeller { struct Range { diff --git a/impeller/entity/geometry/fill_path_geometry.cc b/impeller/entity/geometry/fill_path_geometry.cc index 89088c86e8e3f..1b3dc51813266 100644 --- a/impeller/entity/geometry/fill_path_geometry.cc +++ b/impeller/entity/geometry/fill_path_geometry.cc @@ -36,8 +36,15 @@ GeometryResult FillPathGeometry::GetPositionBuffer( }; } - VertexBuffer vertex_buffer = renderer.GetTessellator()->TessellateConvex( - path_, host_buffer, entity.GetTransform().GetMaxBasisLength()); + VertexBuffer vertex_buffer; + + auto points = renderer.GetTessellator()->TessellateConvex( + path_, entity.GetTransform().GetMaxBasisLength()); + + vertex_buffer.vertex_buffer = host_buffer.Emplace( + points.data(), points.size() * sizeof(Point), alignof(Point)); + vertex_buffer.index_buffer = {}, vertex_buffer.vertex_count = points.size(); + vertex_buffer.index_type = IndexType::kNone; return GeometryResult{ .type = PrimitiveType::kTriangleStrip, @@ -54,6 +61,8 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { + using VS = TextureFillVertexShader; + const auto& bounding_box = path_.GetBoundingBox(); if (bounding_box.has_value() && bounding_box->IsEmpty()) { return GeometryResult{ @@ -71,13 +80,22 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer( auto uv_transform = texture_coverage.GetNormalizingTransform() * effect_transform; - VertexBuffer vertex_buffer = renderer.GetTessellator()->TessellateConvex( - path_, renderer.GetTransientsBuffer(), - entity.GetTransform().GetMaxBasisLength(), uv_transform); + auto points = renderer.GetTessellator()->TessellateConvex( + path_, entity.GetTransform().GetMaxBasisLength()); + + VertexBufferBuilder vertex_builder; + vertex_builder.Reserve(points.size()); + for (auto i = 0u; i < points.size(); i++) { + VS::PerVertexData data; + data.position = points[i]; + data.texture_coords = uv_transform * points[i]; + vertex_builder.AppendVertex(data); + } return GeometryResult{ .type = PrimitiveType::kTriangleStrip, - .vertex_buffer = vertex_buffer, + .vertex_buffer = + vertex_builder.CreateVertexBuffer(renderer.GetTransientsBuffer()), .transform = entity.GetShaderTransform(pass), .mode = GetResultMode(), }; diff --git a/impeller/geometry/geometry_benchmarks.cc b/impeller/geometry/geometry_benchmarks.cc index 3ea82c5ba1970..98d17cf7d8c15 100644 --- a/impeller/geometry/geometry_benchmarks.cc +++ b/impeller/geometry/geometry_benchmarks.cc @@ -59,22 +59,39 @@ template static void BM_Polyline(benchmark::State& state, Args&&... args) { auto args_tuple = std::make_tuple(std::move(args)...); auto path = std::get(args_tuple); + bool tessellate = std::get(args_tuple); size_t point_count = 0u; size_t single_point_count = 0u; auto points = std::make_unique>(); points->reserve(2048); while (state.KeepRunning()) { - auto polyline = path.CreatePolyline( - // Clang-tidy doesn't know that the points get moved back before - // getting moved again in this loop. - // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move) - 1.0f, std::move(points), - [&points](Path::Polyline::PointBufferPtr reclaimed) { - points = std::move(reclaimed); - }); - single_point_count = polyline.points->size(); - point_count += single_point_count; + if (tessellate) { + tess.Tessellate(path, 1.0f, + [&point_count, &single_point_count]( + const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { + if (indices_count > 0) { + single_point_count = indices_count; + point_count += indices_count; + } else { + single_point_count = vertices_count; + point_count += vertices_count; + } + return true; + }); + } else { + auto polyline = path.CreatePolyline( + // Clang-tidy doesn't know that the points get moved back before + // getting moved again in this loop. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move) + 1.0f, std::move(points), + [&points](Path::Polyline::PointBufferPtr reclaimed) { + points = std::move(reclaimed); + }); + single_point_count = polyline.points->size(); + point_count += single_point_count; + } } state.counters["SinglePointCount"] = single_point_count; state.counters["TotalPointCount"] = point_count; @@ -138,13 +155,11 @@ static void BM_Convex(benchmark::State& state, Args&&... args) { size_t point_count = 0u; size_t single_point_count = 0u; auto points = std::make_unique>(); - auto indices = std::make_unique>(); points->reserve(2048); - indices->reserve(2048); while (state.KeepRunning()) { - tess.TessellateConvexInternal(path, *points, *indices, 1.0f); - single_point_count = indices->size(); - point_count += indices->size(); + auto points = tess.TessellateConvex(path, 1.0f); + single_point_count = points.size(); + point_count += points.size(); } state.counters["SinglePointCount"] = single_point_count; state.counters["TotalPointCount"] = point_count; @@ -167,17 +182,27 @@ static void BM_Convex(benchmark::State& state, Args&&... args) { MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, _uvNoTx, UVMode::kUVRect) BENCHMARK_CAPTURE(BM_Polyline, cubic_polyline, CreateCubic(true), false); +BENCHMARK_CAPTURE(BM_Polyline, cubic_polyline_tess, CreateCubic(true), true); BENCHMARK_CAPTURE(BM_Polyline, unclosed_cubic_polyline, CreateCubic(false), false); +BENCHMARK_CAPTURE(BM_Polyline, + unclosed_cubic_polyline_tess, + CreateCubic(false), + true); MAKE_STROKE_BENCHMARK_CAPTURE_UVS(Cubic); BENCHMARK_CAPTURE(BM_Polyline, quad_polyline, CreateQuadratic(true), false); +BENCHMARK_CAPTURE(BM_Polyline, quad_polyline_tess, CreateQuadratic(true), true); BENCHMARK_CAPTURE(BM_Polyline, unclosed_quad_polyline, CreateQuadratic(false), false); +BENCHMARK_CAPTURE(BM_Polyline, + unclosed_quad_polyline_tess, + CreateQuadratic(false), + true); MAKE_STROKE_BENCHMARK_CAPTURE_UVS(Quadratic); BENCHMARK_CAPTURE(BM_Convex, rrect_convex, CreateRRect(), true); diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc index 73daedaab3b97..2eb682f5ff722 100644 --- a/impeller/geometry/path.cc +++ b/impeller/geometry/path.cc @@ -349,45 +349,4 @@ std::optional Path::GetTransformedBoundingBox( return bounds->TransformBounds(transform); } -void Path::WritePolyline(Scalar scale, VertexWriter& writer) const { - auto& path_components = data_->components; - auto& path_points = data_->points; - - for (size_t component_i = 0; component_i < path_components.size(); - component_i++) { - const auto& path_component = path_components[component_i]; - switch (path_component.type) { - case ComponentType::kLinear: { - const LinearPathComponent* linear = - reinterpret_cast( - &path_points[path_component.index]); - writer.Write(linear->p2); - break; - } - case ComponentType::kQuadratic: { - const QuadraticPathComponent* quad = - reinterpret_cast( - &path_points[path_component.index]); - quad->ToLinearPathComponents(scale, writer); - break; - } - case ComponentType::kCubic: { - const CubicPathComponent* cubic = - reinterpret_cast( - &path_points[path_component.index]); - cubic->ToLinearPathComponents(scale, writer); - break; - } - case ComponentType::kContour: - if (component_i == path_components.size() - 1) { - // If the last component is a contour, that means it's an empty - // contour, so skip it. - continue; - } - writer.EndContour(); - break; - } - } -} - } // namespace impeller diff --git a/impeller/geometry/path.h b/impeller/geometry/path.h index 3ba9ff98d1cd8..ba56a4101c1bd 100644 --- a/impeller/geometry/path.h +++ b/impeller/geometry/path.h @@ -11,7 +11,6 @@ #include #include "impeller/geometry/path_component.h" -#include "impeller/geometry/rect.h" namespace impeller { @@ -169,13 +168,6 @@ class Path { std::make_unique>(), Polyline::ReclaimPointBufferCallback reclaim = nullptr) const; - /// Generate a polyline into the temporary storage held by the [writer]. - /// - /// It is suitable to use the max basis length of the matrix used to transform - /// the path. If the provided scale is 0, curves will revert to straight - /// lines. - void WritePolyline(Scalar scale, VertexWriter& writer) const; - std::optional GetBoundingBox() const; std::optional GetTransformedBoundingBox(const Matrix& transform) const; diff --git a/impeller/geometry/path_component.cc b/impeller/geometry/path_component.cc index 7e1b558a2e49f..3a00414dfa804 100644 --- a/impeller/geometry/path_component.cc +++ b/impeller/geometry/path_component.cc @@ -10,66 +10,6 @@ namespace impeller { -VertexWriter::VertexWriter(std::vector& points, - std::vector& indices, - std::optional uv_transform) - : points_(points), indices_(indices), uv_transform_(uv_transform) {} - -void VertexWriter::EndContour() { - if (points_.size() == 0u || contour_start_ == points_.size() - 1) { - // Empty or first contour. - return; - } - - auto start = contour_start_; - auto end = points_.size() - 1; - // Some polygons will not self close and an additional triangle - // must be inserted, others will self close and we need to avoid - // inserting an extra triangle. - if (points_[end] == points_[start]) { - end--; - } - - if (contour_start_ > 0) { - // Triangle strip break. - indices_.emplace_back(indices_.back()); - indices_.emplace_back(start); - indices_.emplace_back(start); - - // If the contour has an odd number of points, insert an extra point when - // bridging to the next contour to preserve the correct triangle winding - // order. - if (previous_contour_odd_points_) { - indices_.emplace_back(start); - } - } else { - indices_.emplace_back(start); - } - - size_t a = start + 1; - size_t b = end; - while (a < b) { - indices_.emplace_back(a); - indices_.emplace_back(b); - a++; - b--; - } - if (a == b) { - indices_.emplace_back(a); - previous_contour_odd_points_ = false; - } else { - previous_contour_odd_points_ = true; - } - contour_start_ = points_.size(); -} - -void VertexWriter::Write(Point point) { - points_.emplace_back(point); - if (uv_transform_.has_value()) { - points_.emplace_back(*uv_transform_ * point); - } -} - /* * Based on: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Specific_cases */ @@ -179,16 +119,6 @@ void QuadraticPathComponent::ToLinearPathComponents( proc(p2); } -void QuadraticPathComponent::ToLinearPathComponents( - Scalar scale, - VertexWriter& writer) const { - Scalar line_count = std::ceilf(ComputeQuadradicSubdivisions(scale, *this)); - for (size_t i = 1; i < line_count; i += 1) { - writer.Write(Solve(i / line_count)); - } - writer.Write(p2); -} - std::vector QuadraticPathComponent::Extrema() const { CubicPathComponent elevated(*this); return elevated.Extrema(); @@ -259,15 +189,6 @@ void CubicPathComponent::ToLinearPathComponents(Scalar scale, proc(p2); } -void CubicPathComponent::ToLinearPathComponents(Scalar scale, - VertexWriter& writer) const { - Scalar line_count = std::ceilf(ComputeCubicSubdivisions(scale, *this)); - for (size_t i = 1; i < line_count; i++) { - writer.Write(Solve(i / line_count)); - } - writer.Write(p2); -} - static inline bool NearEqual(Scalar a, Scalar b, Scalar epsilon) { return (a > (b - epsilon)) && (a < (b + epsilon)); } diff --git a/impeller/geometry/path_component.h b/impeller/geometry/path_component.h index 5e5e2106b7682..c310052a893dc 100644 --- a/impeller/geometry/path_component.h +++ b/impeller/geometry/path_component.h @@ -10,34 +10,12 @@ #include #include -#include "impeller/geometry/matrix.h" #include "impeller/geometry/point.h" +#include "impeller/geometry/rect.h" #include "impeller/geometry/scalar.h" namespace impeller { -/// @brief An interface for generating a multi contour polyline as a triangle -/// strip. -class VertexWriter { - public: - explicit VertexWriter(std::vector& points, - std::vector& indices, - std::optional uv_transform); - - ~VertexWriter() = default; - - void EndContour(); - - void Write(Point point); - - private: - bool previous_contour_odd_points_ = false; - size_t contour_start_ = 0u; - std::vector& points_; - std::vector& indices_; - std::optional uv_transform_; -}; - struct LinearPathComponent { Point p1; Point p2; @@ -86,8 +64,6 @@ struct QuadraticPathComponent { void ToLinearPathComponents(Scalar scale_factor, const PointProc& proc) const; - void ToLinearPathComponents(Scalar scale, VertexWriter& writer) const; - std::vector Extrema() const; bool operator==(const QuadraticPathComponent& other) const { @@ -133,8 +109,6 @@ struct CubicPathComponent { void ToLinearPathComponents(Scalar scale, const PointProc& proc) const; - void ToLinearPathComponents(Scalar scale, VertexWriter& writer) const; - CubicPathComponent Subsegment(Scalar t0, Scalar t1) const; bool operator==(const CubicPathComponent& other) const { diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc index 5ff7bdd781b05..0024e42c9d861 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -38,6 +38,7 @@ #include "impeller/renderer/render_target.h" #include "impeller/renderer/renderer.h" #include "impeller/renderer/vertex_buffer_builder.h" +#include "impeller/tessellator/tessellator.h" #include "third_party/imgui/imgui.h" // TODO(zanderso): https://github.com/flutter/flutter/issues/127701 @@ -391,15 +392,25 @@ TEST_P(RendererTest, CanRenderInstanced) { using FS = InstancedDrawFragmentShader; VertexBufferBuilder builder; - builder.AddVertices({ - VS::PerVertexData{.vtx = Point{10, 10}}, - VS::PerVertexData{.vtx = Point{110, 10}}, - VS::PerVertexData{.vtx = Point{10, 110}}, - - VS::PerVertexData{.vtx = Point{110, 10}}, - VS::PerVertexData{.vtx = Point{10, 110}}, - VS::PerVertexData{.vtx = Point{110, 100}}, - }); + + ASSERT_EQ(Tessellator::Result::kSuccess, + Tessellator{}.Tessellate( + PathBuilder{} + .AddRect(Rect::MakeXYWH(10, 10, 100, 100)) + .TakePath(FillType::kOdd), + 1.0f, + [&builder](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { + for (auto i = 0u; i < vertices_count * 2; i += 2) { + VS::PerVertexData data; + data.vtx = {vertices[i], vertices[i + 1]}; + builder.AppendVertex(data); + } + for (auto i = 0u; i < indices_count; i++) { + builder.AppendIndex(indices[i]); + } + return true; + })); ASSERT_NE(GetContext(), nullptr); auto pipeline = diff --git a/impeller/tessellator/BUILD.gn b/impeller/tessellator/BUILD.gn index 9bd2f853c75c7..bc56fb770f069 100644 --- a/impeller/tessellator/BUILD.gn +++ b/impeller/tessellator/BUILD.gn @@ -15,6 +15,7 @@ impeller_component("tessellator") { deps = [ "../core", "//flutter/fml", + "//third_party/libtess2", ] } @@ -29,6 +30,8 @@ impeller_component("tessellator_shared") { sources = [ "c/tessellator.cc", "c/tessellator.h", + "tessellator.cc", + "tessellator.h", ] deps = [ diff --git a/impeller/tessellator/c/tessellator.cc b/impeller/tessellator/c/tessellator.cc index 0f2c9734fc3b6..f5300dcba1dee 100644 --- a/impeller/tessellator/c/tessellator.cc +++ b/impeller/tessellator/c/tessellator.cc @@ -4,205 +4,9 @@ #include "tessellator.h" -#include #include -#include "third_party/libtess2/Include/tesselator.h" - namespace impeller { - -void DestroyTessellator(TESStesselator* tessellator) { - if (tessellator != nullptr) { - ::tessDeleteTess(tessellator); - } -} - -using CTessellator = - std::unique_ptr; - -static int ToTessWindingRule(FillType fill_type) { - switch (fill_type) { - case FillType::kOdd: - return TESS_WINDING_ODD; - case FillType::kNonZero: - return TESS_WINDING_NONZERO; - } - return TESS_WINDING_ODD; -} - -static void* HeapAlloc(void* userData, unsigned int size) { - return malloc(size); -} - -static void* HeapRealloc(void* userData, void* ptr, unsigned int size) { - return realloc(ptr, size); -} - -static void HeapFree(void* userData, void* ptr) { - free(ptr); -} - -// Note: these units are "number of entities" for bucket size and not in KB. -static const TESSalloc kAlloc = { - HeapAlloc, HeapRealloc, HeapFree, 0, /* =userData */ - 16, /* =meshEdgeBucketSize */ - 16, /* =meshVertexBucketSize */ - 16, /* =meshFaceBucketSize */ - 16, /* =dictNodeBucketSize */ - 16, /* =regionBucketSize */ - 0 /* =extraVertices */ -}; - -class LibtessTessellator { - public: - LibtessTessellator() : c_tessellator_(nullptr, &DestroyTessellator) { - TESSalloc alloc = kAlloc; - { - // libTess2 copies the TESSalloc despite the non-const argument. - CTessellator tessellator(::tessNewTess(&alloc), &DestroyTessellator); - c_tessellator_ = std::move(tessellator); - } - } - - ~LibtessTessellator() {} - - enum class Result { - kSuccess, - kInputError, - kTessellationError, - }; - - /// @brief A callback that returns the results of the tessellation. - /// - /// The index buffer may not be populated, in which case [indices] will - /// be nullptr and indices_count will be 0. - using BuilderCallback = std::function; - - //---------------------------------------------------------------------------- - /// @brief Generates filled triangles from the path. A callback is - /// invoked once for the entire tessellation. - /// - /// @param[in] path The path to tessellate. - /// @param[in] tolerance The tolerance value for conversion of the path to - /// a polyline. This value is often derived from the - /// Matrix::GetMaxBasisLength of the CTM applied to the - /// path for rendering. - /// @param[in] callback The callback, return false to indicate failure. - /// - /// @return The result status of the tessellation. - /// - Result Tessellate(const Path& path, - Scalar tolerance, - const BuilderCallback& callback) { - if (!callback) { - return Result::kInputError; - } - - std::unique_ptr> buffer = - std::make_unique>(); - auto polyline = path.CreatePolyline(tolerance, std::move(buffer)); - - auto fill_type = path.GetFillType(); - - if (polyline.points->empty()) { - return Result::kInputError; - } - - auto tessellator = c_tessellator_.get(); - if (!tessellator) { - return Result::kTessellationError; - } - - constexpr int kVertexSize = 2; - constexpr int kPolygonSize = 3; - - //---------------------------------------------------------------------------- - /// Feed contour information to the tessellator. - /// - static_assert(sizeof(Point) == 2 * sizeof(float)); - for (size_t contour_i = 0; contour_i < polyline.contours.size(); - contour_i++) { - size_t start_point_index, end_point_index; - std::tie(start_point_index, end_point_index) = - polyline.GetContourPointBounds(contour_i); - - ::tessAddContour(tessellator, // the C tessellator - kVertexSize, // - polyline.points->data() + start_point_index, // - sizeof(Point), // - end_point_index - start_point_index // - ); - } - - //---------------------------------------------------------------------------- - /// Let's tessellate. - /// - auto result = ::tessTesselate(tessellator, // tessellator - ToTessWindingRule(fill_type), // winding - TESS_POLYGONS, // element type - kPolygonSize, // polygon size - kVertexSize, // vertex size - nullptr // normal (null is automatic) - ); - - if (result != 1) { - return Result::kTessellationError; - } - - int element_item_count = tessGetElementCount(tessellator) * kPolygonSize; - - // We default to using a 16bit index buffer, but in cases where we generate - // more tessellated data than this can contain we need to fall back to - // dropping the index buffer entirely. Instead code could instead switch to - // a uint32 index buffer, but this is done for simplicity with the other - // fast path above. - if (element_item_count < USHRT_MAX) { - int vertex_item_count = tessGetVertexCount(tessellator); - auto vertices = tessGetVertices(tessellator); - auto elements = tessGetElements(tessellator); - - // libtess uses an int index internally due to usage of -1 as a sentinel - // value. - std::vector indices(element_item_count); - for (int i = 0; i < element_item_count; i++) { - indices[i] = static_cast(elements[i]); - } - if (!callback(vertices, vertex_item_count, indices.data(), - element_item_count)) { - return Result::kInputError; - } - } else { - std::vector points; - std::vector data; - - int vertex_item_count = tessGetVertexCount(tessellator) * kVertexSize; - auto vertices = tessGetVertices(tessellator); - points.reserve(vertex_item_count); - for (int i = 0; i < vertex_item_count; i += 2) { - points.emplace_back(vertices[i], vertices[i + 1]); - } - - int element_item_count = tessGetElementCount(tessellator) * kPolygonSize; - auto elements = tessGetElements(tessellator); - data.reserve(element_item_count); - for (int i = 0; i < element_item_count; i++) { - data.emplace_back(points[elements[i]].x); - data.emplace_back(points[elements[i]].y); - } - if (!callback(data.data(), element_item_count, nullptr, 0u)) { - return Result::kInputError; - } - } - - return Result::kSuccess; - } - - CTessellator c_tessellator_; -}; - PathBuilder* CreatePathBuilder() { return new PathBuilder(); } @@ -238,7 +42,7 @@ struct Vertices* Tessellate(PathBuilder* builder, Scalar tolerance) { auto path = builder->CopyPath(static_cast(fill_type)); std::vector points; - if (LibtessTessellator{}.Tessellate( + if (Tessellator{}.Tessellate( path, tolerance, [&points](const float* vertices, size_t vertices_count, const uint16_t* indices, size_t indices_count) { @@ -253,7 +57,7 @@ struct Vertices* Tessellate(PathBuilder* builder, points.push_back(point.y); } return true; - }) != LibtessTessellator::Result::kSuccess) { + }) != Tessellator::Result::kSuccess) { return nullptr; } diff --git a/impeller/tessellator/c/tessellator.h b/impeller/tessellator/c/tessellator.h index c8e19b0e4f248..ef5bfc950f107 100644 --- a/impeller/tessellator/c/tessellator.h +++ b/impeller/tessellator/c/tessellator.h @@ -8,6 +8,7 @@ #include #include "impeller/geometry/path_builder.h" +#include "impeller/tessellator/tessellator.h" #ifdef _WIN32 #define IMPELLER_API __declspec(dllexport) diff --git a/impeller/tessellator/tessellator.cc b/impeller/tessellator/tessellator.cc index ac63c66078fb9..afbf01da90f83 100644 --- a/impeller/tessellator/tessellator.cc +++ b/impeller/tessellator/tessellator.cc @@ -4,21 +4,166 @@ #include "impeller/tessellator/tessellator.h" -#include "impeller/core/buffer_view.h" -#include "impeller/core/formats.h" -#include "impeller/core/vertex_buffer.h" +#include "third_party/libtess2/Include/tesselator.h" namespace impeller { +static void* HeapAlloc(void* userData, unsigned int size) { + return malloc(size); +} + +static void* HeapRealloc(void* userData, void* ptr, unsigned int size) { + return realloc(ptr, size); +} + +static void HeapFree(void* userData, void* ptr) { + free(ptr); +} + +// Note: these units are "number of entities" for bucket size and not in KB. +static const TESSalloc kAlloc = { + HeapAlloc, HeapRealloc, HeapFree, 0, /* =userData */ + 16, /* =meshEdgeBucketSize */ + 16, /* =meshVertexBucketSize */ + 16, /* =meshFaceBucketSize */ + 16, /* =dictNodeBucketSize */ + 16, /* =regionBucketSize */ + 0 /* =extraVertices */ +}; + Tessellator::Tessellator() : point_buffer_(std::make_unique>()), - index_buffer_(std::make_unique>()) { + c_tessellator_(nullptr, &DestroyTessellator) { point_buffer_->reserve(2048); - index_buffer_->reserve(2048); + TESSalloc alloc = kAlloc; + { + // libTess2 copies the TESSalloc despite the non-const argument. + CTessellator tessellator(::tessNewTess(&alloc), &DestroyTessellator); + c_tessellator_ = std::move(tessellator); + } } Tessellator::~Tessellator() = default; +static int ToTessWindingRule(FillType fill_type) { + switch (fill_type) { + case FillType::kOdd: + return TESS_WINDING_ODD; + case FillType::kNonZero: + return TESS_WINDING_NONZERO; + } + return TESS_WINDING_ODD; +} + +Tessellator::Result Tessellator::Tessellate(const Path& path, + Scalar tolerance, + const BuilderCallback& callback) { + if (!callback) { + return Result::kInputError; + } + + point_buffer_->clear(); + auto polyline = + path.CreatePolyline(tolerance, std::move(point_buffer_), + [this](Path::Polyline::PointBufferPtr point_buffer) { + point_buffer_ = std::move(point_buffer); + }); + + auto fill_type = path.GetFillType(); + + if (polyline.points->empty()) { + return Result::kInputError; + } + + auto tessellator = c_tessellator_.get(); + if (!tessellator) { + return Result::kTessellationError; + } + + constexpr int kVertexSize = 2; + constexpr int kPolygonSize = 3; + + //---------------------------------------------------------------------------- + /// Feed contour information to the tessellator. + /// + static_assert(sizeof(Point) == 2 * sizeof(float)); + for (size_t contour_i = 0; contour_i < polyline.contours.size(); + contour_i++) { + size_t start_point_index, end_point_index; + std::tie(start_point_index, end_point_index) = + polyline.GetContourPointBounds(contour_i); + + ::tessAddContour(tessellator, // the C tessellator + kVertexSize, // + polyline.points->data() + start_point_index, // + sizeof(Point), // + end_point_index - start_point_index // + ); + } + + //---------------------------------------------------------------------------- + /// Let's tessellate. + /// + auto result = ::tessTesselate(tessellator, // tessellator + ToTessWindingRule(fill_type), // winding + TESS_POLYGONS, // element type + kPolygonSize, // polygon size + kVertexSize, // vertex size + nullptr // normal (null is automatic) + ); + + if (result != 1) { + return Result::kTessellationError; + } + + int element_item_count = tessGetElementCount(tessellator) * kPolygonSize; + + // We default to using a 16bit index buffer, but in cases where we generate + // more tessellated data than this can contain we need to fall back to + // dropping the index buffer entirely. Instead code could instead switch to + // a uint32 index buffer, but this is done for simplicity with the other + // fast path above. + if (element_item_count < USHRT_MAX) { + int vertex_item_count = tessGetVertexCount(tessellator); + auto vertices = tessGetVertices(tessellator); + auto elements = tessGetElements(tessellator); + + // libtess uses an int index internally due to usage of -1 as a sentinel + // value. + std::vector indices(element_item_count); + for (int i = 0; i < element_item_count; i++) { + indices[i] = static_cast(elements[i]); + } + if (!callback(vertices, vertex_item_count, indices.data(), + element_item_count)) { + return Result::kInputError; + } + } else { + std::vector points; + std::vector data; + + int vertex_item_count = tessGetVertexCount(tessellator) * kVertexSize; + auto vertices = tessGetVertices(tessellator); + points.reserve(vertex_item_count); + for (int i = 0; i < vertex_item_count; i += 2) { + points.emplace_back(vertices[i], vertices[i + 1]); + } + + int element_item_count = tessGetElementCount(tessellator) * kPolygonSize; + auto elements = tessGetElements(tessellator); + data.reserve(element_item_count); + for (int i = 0; i < element_item_count; i++) { + data.emplace_back(points[elements[i]].x); + data.emplace_back(points[elements[i]].y); + } + if (!callback(data.data(), element_item_count, nullptr, 0u)) { + return Result::kInputError; + } + } + + return Result::kSuccess; +} + Path::Polyline Tessellator::CreateTempPolyline(const Path& path, Scalar tolerance) { FML_DCHECK(point_buffer_); @@ -31,52 +176,73 @@ Path::Polyline Tessellator::CreateTempPolyline(const Path& path, return polyline; } -VertexBuffer Tessellator::TessellateConvex(const Path& path, - HostBuffer& host_buffer, - Scalar tolerance, - std::optional uv_transform) { +std::vector Tessellator::TessellateConvex(const Path& path, + Scalar tolerance) { FML_DCHECK(point_buffer_); - FML_DCHECK(index_buffer_); - TessellateConvexInternal(path, *point_buffer_, *index_buffer_, tolerance, - uv_transform); - - if (point_buffer_->empty()) { - return VertexBuffer{ - .vertex_buffer = {}, - .index_buffer = {}, - .vertex_count = 0u, - .index_type = IndexType::k16bit, - }; - } - - BufferView vertex_buffer = host_buffer.Emplace( - point_buffer_->data(), sizeof(Point) * point_buffer_->size(), - alignof(Point)); - BufferView index_buffer = host_buffer.Emplace( - index_buffer_->data(), sizeof(uint16_t) * index_buffer_->size(), - alignof(uint16_t)); - - return VertexBuffer{ - .vertex_buffer = std::move(vertex_buffer), - .index_buffer = std::move(index_buffer), - .vertex_count = index_buffer_->size(), - .index_type = IndexType::k16bit, - }; -} - -void Tessellator::TessellateConvexInternal(const Path& path, - std::vector& point_buffer, - std::vector& index_buffer, - Scalar tolerance, - std::optional uv_transform) { - index_buffer_->clear(); + std::vector output; point_buffer_->clear(); + auto polyline = + path.CreatePolyline(tolerance, std::move(point_buffer_), + [this](Path::Polyline::PointBufferPtr point_buffer) { + point_buffer_ = std::move(point_buffer); + }); + if (polyline.points->size() == 0) { + return output; + } - VertexWriter writer(point_buffer, index_buffer, uv_transform); + output.reserve(polyline.points->size() + + (4 * (polyline.contours.size() - 1))); + bool previous_contour_odd_points = false; + for (auto j = 0u; j < polyline.contours.size(); j++) { + auto [start, end] = polyline.GetContourPointBounds(j); + auto first_point = polyline.GetPoint(start); + + // Some polygons will not self close and an additional triangle + // must be inserted, others will self close and we need to avoid + // inserting an extra triangle. + if (polyline.GetPoint(end - 1) == first_point) { + end--; + } + + if (j > 0) { + // Triangle strip break. + output.emplace_back(output.back()); + output.emplace_back(first_point); + output.emplace_back(first_point); + + // If the contour has an odd number of points, insert an extra point when + // bridging to the next contour to preserve the correct triangle winding + // order. + if (previous_contour_odd_points) { + output.emplace_back(first_point); + } + } else { + output.emplace_back(first_point); + } + + size_t a = start + 1; + size_t b = end - 1; + while (a < b) { + output.emplace_back(polyline.GetPoint(a)); + output.emplace_back(polyline.GetPoint(b)); + a++; + b--; + } + if (a == b) { + previous_contour_odd_points = false; + output.emplace_back(polyline.GetPoint(a)); + } else { + previous_contour_odd_points = true; + } + } + return output; +} - path.WritePolyline(tolerance, writer); - writer.EndContour(); +void DestroyTessellator(TESStesselator* tessellator) { + if (tessellator != nullptr) { + ::tessDeleteTess(tessellator); + } } static constexpr int kPrecomputedDivisionCount = 1024; diff --git a/impeller/tessellator/tessellator.h b/impeller/tessellator/tessellator.h index 013bf4dff044b..6251e814aa764 100644 --- a/impeller/tessellator/tessellator.h +++ b/impeller/tessellator/tessellator.h @@ -10,8 +10,6 @@ #include #include "impeller/core/formats.h" -#include "impeller/core/host_buffer.h" -#include "impeller/core/vertex_buffer.h" #include "impeller/geometry/path.h" #include "impeller/geometry/point.h" #include "impeller/geometry/trig.h" @@ -20,6 +18,11 @@ struct TESStesselator; namespace impeller { +void DestroyTessellator(TESStesselator* tessellator); + +using CTessellator = + std::unique_ptr; + //------------------------------------------------------------------------------ /// @brief A utility that generates triangles of the specified fill type /// given a polyline. This happens on the CPU. @@ -66,6 +69,12 @@ class Tessellator { }; public: + enum class Result { + kSuccess, + kInputError, + kTessellationError, + }; + /// @brief A callback function for a |VertexGenerator| to deliver /// the vertices it computes as |Point| objects. using TessellatedVertexProc = std::function; @@ -164,6 +173,32 @@ class Tessellator { ~Tessellator(); + /// @brief A callback that returns the results of the tessellation. + /// + /// The index buffer may not be populated, in which case [indices] will + /// be nullptr and indices_count will be 0. + using BuilderCallback = std::function; + + //---------------------------------------------------------------------------- + /// @brief Generates filled triangles from the path. A callback is + /// invoked once for the entire tessellation. + /// + /// @param[in] path The path to tessellate. + /// @param[in] tolerance The tolerance value for conversion of the path to + /// a polyline. This value is often derived from the + /// Matrix::GetMaxBasisLength of the CTM applied to the + /// path for rendering. + /// @param[in] callback The callback, return false to indicate failure. + /// + /// @return The result status of the tessellation. + /// + Tessellator::Result Tessellate(const Path& path, + Scalar tolerance, + const BuilderCallback& callback); + //---------------------------------------------------------------------------- /// @brief Given a convex path, create a triangle fan structure. /// @@ -172,28 +207,10 @@ class Tessellator { /// a polyline. This value is often derived from the /// Matrix::GetMaxBasisLength of the CTM applied to the /// path for rendering. - /// @param[in] host_buffer The host buffer for allocation of vertices/index - /// data. - /// @param[in] uv_transform If provided, then uvs are also generated into the - /// point buffer. Defaults to std::nullopt. /// - /// @return A vertex buffer containing all data from the provided curve. - VertexBuffer TessellateConvex( - const Path& path, - HostBuffer& host_buffer, - Scalar tolerance, - std::optional uv_transform = std::nullopt); - - /// Visible for testing. + /// @return A point vector containing the vertices in triangle strip format. /// - /// This method only exists for the ease of benchmarking without using the - /// real allocator needed by the [host_buffer]. - void TessellateConvexInternal( - const Path& path, - std::vector& point_buffer, - std::vector& index_buffer, - Scalar tolerance, - std::optional uv_transform = std::nullopt); + std::vector TessellateConvex(const Path& path, Scalar tolerance); //---------------------------------------------------------------------------- /// @brief Create a temporary polyline. Only one per-process can exist at @@ -282,9 +299,9 @@ class Tessellator { private: /// Used for polyline generation. std::unique_ptr> point_buffer_; - std::unique_ptr> index_buffer_; + CTessellator c_tessellator_; - // Data for various Circle/EllipseGenerator classes, cached per + // Data for variouos Circle/EllipseGenerator classes, cached per // Tessellator instance which is usually the foreground life of an app // if not longer. static constexpr size_t kCachedTrigCount = 300; diff --git a/impeller/tessellator/tessellator_unittests.cc b/impeller/tessellator/tessellator_unittests.cc index b967ee6b20467..2c54d4282db01 100644 --- a/impeller/tessellator/tessellator_unittests.cc +++ b/impeller/tessellator/tessellator_unittests.cc @@ -13,37 +13,96 @@ namespace impeller { namespace testing { +TEST(TessellatorTest, TessellatorBuilderReturnsCorrectResultStatus) { + // Zero points. + { + Tessellator t; + auto path = PathBuilder{}.TakePath(FillType::kOdd); + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return true; }); + + ASSERT_EQ(result, Tessellator::Result::kInputError); + } + + // One point. + { + Tessellator t; + auto path = PathBuilder{}.LineTo({0, 0}).TakePath(FillType::kOdd); + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return true; }); + + ASSERT_EQ(result, Tessellator::Result::kSuccess); + } + + // Two points. + { + Tessellator t; + auto path = PathBuilder{}.AddLine({0, 0}, {0, 1}).TakePath(FillType::kOdd); + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return true; }); + + ASSERT_EQ(result, Tessellator::Result::kSuccess); + } + + // Many points. + { + Tessellator t; + PathBuilder builder; + for (int i = 0; i < 1000; i++) { + auto coord = i * 1.0f; + builder.AddLine({coord, coord}, {coord + 1, coord + 1}); + } + auto path = builder.TakePath(FillType::kOdd); + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return true; }); + + ASSERT_EQ(result, Tessellator::Result::kSuccess); + } + + // Closure fails. + { + Tessellator t; + auto path = PathBuilder{}.AddLine({0, 0}, {0, 1}).TakePath(FillType::kOdd); + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return false; }); + + ASSERT_EQ(result, Tessellator::Result::kInputError); + } +} + TEST(TessellatorTest, TessellateConvex) { { Tessellator t; - std::vector points; - std::vector indices; // Sanity check simple rectangle. - t.TessellateConvexInternal( - PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 10, 10)).TakePath(), points, - indices, 1.0); - - std::vector expected = {{10, 0}, {10, 10}, {0, 10}, {0, 0}}; - std::vector expected_indices = {0, 1, 3, 2}; - EXPECT_EQ(points, expected); - EXPECT_EQ(indices, expected_indices); + auto pts = t.TessellateConvex( + PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 10, 10)).TakePath(), 1.0); + + std::vector expected = {{0, 0}, {10, 0}, {0, 10}, {10, 10}}; + EXPECT_EQ(pts, expected); } { Tessellator t; - std::vector points; - std::vector indices; - t.TessellateConvexInternal(PathBuilder{} - .AddRect(Rect::MakeLTRB(0, 0, 10, 10)) - .AddRect(Rect::MakeLTRB(20, 20, 30, 30)) - .TakePath(), - points, indices, 1.0); - - std::vector expected = {{10, 0}, {10, 10}, {0, 10}, {0, 0}, - {30, 20}, {30, 30}, {20, 30}, {20, 20}}; - std::vector expected_indices = {0, 1, 3, 2, 2, 4, 4, 5, 7, 6}; - EXPECT_EQ(points, expected); - EXPECT_EQ(indices, expected_indices); + auto pts = t.TessellateConvex(PathBuilder{} + .AddRect(Rect::MakeLTRB(0, 0, 10, 10)) + .AddRect(Rect::MakeLTRB(20, 20, 30, 30)) + .TakePath(), + 1.0); + + std::vector expected = {{0, 0}, {10, 0}, {0, 10}, {10, 10}, + {10, 10}, {20, 20}, {20, 20}, {30, 20}, + {20, 30}, {30, 30}}; + EXPECT_EQ(pts, expected); } } @@ -415,10 +474,7 @@ TEST(TessellatorTest, EarlyReturnEmptyConvexShape) { builder.MoveTo({0, 0}); builder.MoveTo({10, 10}, /*relative=*/true); - std::vector points; - std::vector indices; - tessellator->TessellateConvexInternal(builder.TakePath(), points, indices, - 3.0); + auto points = tessellator->TessellateConvex(builder.TakePath(), 3.0); EXPECT_TRUE(points.empty()); }