diff --git a/impeller/core/range.h b/impeller/core/range.h index d0971725c6928..94c61baf3bb3a 100644 --- a/impeller/core/range.h +++ b/impeller/core/range.h @@ -7,8 +7,6 @@ #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 1b3dc51813266..89088c86e8e3f 100644 --- a/impeller/entity/geometry/fill_path_geometry.cc +++ b/impeller/entity/geometry/fill_path_geometry.cc @@ -36,15 +36,8 @@ GeometryResult FillPathGeometry::GetPositionBuffer( }; } - 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; + VertexBuffer vertex_buffer = renderer.GetTessellator()->TessellateConvex( + path_, host_buffer, entity.GetTransform().GetMaxBasisLength()); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, @@ -61,8 +54,6 @@ 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{ @@ -80,22 +71,13 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer( auto uv_transform = texture_coverage.GetNormalizingTransform() * effect_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); - } + VertexBuffer vertex_buffer = renderer.GetTessellator()->TessellateConvex( + path_, renderer.GetTransientsBuffer(), + entity.GetTransform().GetMaxBasisLength(), uv_transform); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, - .vertex_buffer = - vertex_builder.CreateVertexBuffer(renderer.GetTransientsBuffer()), + .vertex_buffer = vertex_buffer, .transform = entity.GetShaderTransform(pass), .mode = GetResultMode(), }; diff --git a/impeller/geometry/geometry_benchmarks.cc b/impeller/geometry/geometry_benchmarks.cc index 98d17cf7d8c15..3ea82c5ba1970 100644 --- a/impeller/geometry/geometry_benchmarks.cc +++ b/impeller/geometry/geometry_benchmarks.cc @@ -59,39 +59,22 @@ 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()) { - 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; - } + 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; @@ -155,11 +138,13 @@ 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()) { - auto points = tess.TessellateConvex(path, 1.0f); - single_point_count = points.size(); - point_count += points.size(); + tess.TessellateConvexInternal(path, *points, *indices, 1.0f); + single_point_count = indices->size(); + point_count += indices->size(); } state.counters["SinglePointCount"] = single_point_count; state.counters["TotalPointCount"] = point_count; @@ -182,27 +167,17 @@ 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 2eb682f5ff722..73daedaab3b97 100644 --- a/impeller/geometry/path.cc +++ b/impeller/geometry/path.cc @@ -349,4 +349,45 @@ 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 ba56a4101c1bd..3ba9ff98d1cd8 100644 --- a/impeller/geometry/path.h +++ b/impeller/geometry/path.h @@ -11,6 +11,7 @@ #include #include "impeller/geometry/path_component.h" +#include "impeller/geometry/rect.h" namespace impeller { @@ -168,6 +169,13 @@ 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 3a00414dfa804..7e1b558a2e49f 100644 --- a/impeller/geometry/path_component.cc +++ b/impeller/geometry/path_component.cc @@ -10,6 +10,66 @@ 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 */ @@ -119,6 +179,16 @@ 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(); @@ -189,6 +259,15 @@ 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 c310052a893dc..5e5e2106b7682 100644 --- a/impeller/geometry/path_component.h +++ b/impeller/geometry/path_component.h @@ -10,12 +10,34 @@ #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; @@ -64,6 +86,8 @@ 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 { @@ -109,6 +133,8 @@ 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 0024e42c9d861..5ff7bdd781b05 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -38,7 +38,6 @@ #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 @@ -392,25 +391,15 @@ TEST_P(RendererTest, CanRenderInstanced) { using FS = InstancedDrawFragmentShader; VertexBufferBuilder builder; - - 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; - })); + 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_NE(GetContext(), nullptr); auto pipeline = diff --git a/impeller/tessellator/BUILD.gn b/impeller/tessellator/BUILD.gn index bc56fb770f069..9bd2f853c75c7 100644 --- a/impeller/tessellator/BUILD.gn +++ b/impeller/tessellator/BUILD.gn @@ -15,7 +15,6 @@ impeller_component("tessellator") { deps = [ "../core", "//flutter/fml", - "//third_party/libtess2", ] } @@ -30,8 +29,6 @@ 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 f5300dcba1dee..0f2c9734fc3b6 100644 --- a/impeller/tessellator/c/tessellator.cc +++ b/impeller/tessellator/c/tessellator.cc @@ -4,9 +4,205 @@ #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(); } @@ -42,7 +238,7 @@ struct Vertices* Tessellate(PathBuilder* builder, Scalar tolerance) { auto path = builder->CopyPath(static_cast(fill_type)); std::vector points; - if (Tessellator{}.Tessellate( + if (LibtessTessellator{}.Tessellate( path, tolerance, [&points](const float* vertices, size_t vertices_count, const uint16_t* indices, size_t indices_count) { @@ -57,7 +253,7 @@ struct Vertices* Tessellate(PathBuilder* builder, points.push_back(point.y); } return true; - }) != Tessellator::Result::kSuccess) { + }) != LibtessTessellator::Result::kSuccess) { return nullptr; } diff --git a/impeller/tessellator/c/tessellator.h b/impeller/tessellator/c/tessellator.h index ef5bfc950f107..c8e19b0e4f248 100644 --- a/impeller/tessellator/c/tessellator.h +++ b/impeller/tessellator/c/tessellator.h @@ -8,7 +8,6 @@ #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 afbf01da90f83..ac63c66078fb9 100644 --- a/impeller/tessellator/tessellator.cc +++ b/impeller/tessellator/tessellator.cc @@ -4,166 +4,21 @@ #include "impeller/tessellator/tessellator.h" -#include "third_party/libtess2/Include/tesselator.h" +#include "impeller/core/buffer_view.h" +#include "impeller/core/formats.h" +#include "impeller/core/vertex_buffer.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>()), - c_tessellator_(nullptr, &DestroyTessellator) { + index_buffer_(std::make_unique>()) { point_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); - } + index_buffer_->reserve(2048); } 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_); @@ -176,73 +31,52 @@ Path::Polyline Tessellator::CreateTempPolyline(const Path& path, return polyline; } -std::vector Tessellator::TessellateConvex(const Path& path, - Scalar tolerance) { +VertexBuffer Tessellator::TessellateConvex(const Path& path, + HostBuffer& host_buffer, + Scalar tolerance, + std::optional uv_transform) { FML_DCHECK(point_buffer_); - - 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; + 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, + }; } - 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; + 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 DestroyTessellator(TESStesselator* tessellator) { - if (tessellator != nullptr) { - ::tessDeleteTess(tessellator); - } +void Tessellator::TessellateConvexInternal(const Path& path, + std::vector& point_buffer, + std::vector& index_buffer, + Scalar tolerance, + std::optional uv_transform) { + index_buffer_->clear(); + point_buffer_->clear(); + + VertexWriter writer(point_buffer, index_buffer, uv_transform); + + path.WritePolyline(tolerance, writer); + writer.EndContour(); } static constexpr int kPrecomputedDivisionCount = 1024; diff --git a/impeller/tessellator/tessellator.h b/impeller/tessellator/tessellator.h index 6251e814aa764..013bf4dff044b 100644 --- a/impeller/tessellator/tessellator.h +++ b/impeller/tessellator/tessellator.h @@ -10,6 +10,8 @@ #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" @@ -18,11 +20,6 @@ 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. @@ -69,12 +66,6 @@ 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; @@ -173,32 +164,6 @@ 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. /// @@ -207,10 +172,28 @@ 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 point vector containing the vertices in triangle strip format. + /// @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. /// - std::vector TessellateConvex(const Path& path, Scalar tolerance); + /// 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); //---------------------------------------------------------------------------- /// @brief Create a temporary polyline. Only one per-process can exist at @@ -299,9 +282,9 @@ class Tessellator { private: /// Used for polyline generation. std::unique_ptr> point_buffer_; - CTessellator c_tessellator_; + std::unique_ptr> index_buffer_; - // Data for variouos Circle/EllipseGenerator classes, cached per + // Data for various 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 2c54d4282db01..b967ee6b20467 100644 --- a/impeller/tessellator/tessellator_unittests.cc +++ b/impeller/tessellator/tessellator_unittests.cc @@ -13,96 +13,37 @@ 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. - 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); + 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); } { Tessellator t; - 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); + 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); } } @@ -474,7 +415,10 @@ TEST(TessellatorTest, EarlyReturnEmptyConvexShape) { builder.MoveTo({0, 0}); builder.MoveTo({10, 10}, /*relative=*/true); - auto points = tessellator->TessellateConvex(builder.TakePath(), 3.0); + std::vector points; + std::vector indices; + tessellator->TessellateConvexInternal(builder.TakePath(), points, indices, + 3.0); EXPECT_TRUE(points.empty()); }