diff --git a/impeller/entity/geometry/fill_path_geometry.cc b/impeller/entity/geometry/fill_path_geometry.cc index b608ed0428ae1..1153572f25c08 100644 --- a/impeller/entity/geometry/fill_path_geometry.cc +++ b/impeller/entity/geometry/fill_path_geometry.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "impeller/entity/geometry/fill_path_geometry.h" +#include "impeller/core/formats.h" namespace impeller { @@ -47,11 +48,17 @@ GeometryResult FillPathGeometry::GetPositionBuffer( const float* vertices, size_t vertices_count, const uint16_t* indices, size_t indices_count) { vertex_buffer.vertex_buffer = host_buffer.Emplace( - vertices, vertices_count * sizeof(float), alignof(float)); - vertex_buffer.index_buffer = host_buffer.Emplace( - indices, indices_count * sizeof(uint16_t), alignof(uint16_t)); - vertex_buffer.vertex_count = indices_count; - vertex_buffer.index_type = IndexType::k16bit; + vertices, vertices_count * sizeof(float) * 2, alignof(float)); + if (indices != nullptr) { + vertex_buffer.index_buffer = host_buffer.Emplace( + indices, indices_count * sizeof(uint16_t), alignof(uint16_t)); + vertex_buffer.vertex_count = indices_count; + vertex_buffer.index_type = IndexType::k16bit; + } else { + vertex_buffer.index_buffer = {}; + vertex_buffer.vertex_count = vertices_count; + vertex_buffer.index_type = IndexType::kNone; + } return true; }); if (tesselation_result != Tessellator::Result::kSuccess) { @@ -112,7 +119,7 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer( [&vertex_builder, &texture_coverage, &effect_transform]( const float* vertices, size_t vertices_count, const uint16_t* indices, size_t indices_count) { - for (auto i = 0u; i < vertices_count; i += 2) { + for (auto i = 0u; i < vertices_count * 2; i += 2) { VS::PerVertexData data; Point vtx = {vertices[i], vertices[i + 1]}; data.position = vtx; @@ -121,9 +128,11 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer( texture_coverage.size; vertex_builder.AppendVertex(data); } - FML_DCHECK(vertex_builder.GetVertexCount() == vertices_count / 2); - for (auto i = 0u; i < indices_count; i++) { - vertex_builder.AppendIndex(indices[i]); + FML_DCHECK(vertex_builder.GetVertexCount() == vertices_count); + if (indices != nullptr) { + for (auto i = 0u; i < indices_count; i++) { + vertex_builder.AppendIndex(indices[i]); + } } return true; }); diff --git a/impeller/geometry/geometry_benchmarks.cc b/impeller/geometry/geometry_benchmarks.cc index d3c1f9ea525c5..8bff21f3282bf 100644 --- a/impeller/geometry/geometry_benchmarks.cc +++ b/impeller/geometry/geometry_benchmarks.cc @@ -35,8 +35,8 @@ static void BM_Polyline(benchmark::State& state, Args&&... args) { if (tessellate) { tess.Tessellate( FillType::kNonZero, polyline, - [](const float* vertices, size_t vertices_size, - const uint16_t* indices, size_t indices_size) { return true; }); + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return true; }); } } state.counters["SinglePointCount"] = single_point_count; diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc index 48772d09198e1..2ecc6fab2a004 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -404,14 +404,14 @@ TEST_P(RendererTest, CanRenderInstanced) { .AddRect(Rect::MakeXYWH(10, 10, 100, 100)) .TakePath() .CreatePolyline(1.0f), - [&builder](const float* vertices, size_t vertices_size, - const uint16_t* indices, size_t indices_size) { - for (auto i = 0u; i < vertices_size; i += 2) { + [&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_size; i++) { + for (auto i = 0u; i < indices_count; i++) { builder.AppendIndex(indices[i]); } return true; diff --git a/impeller/tessellator/c/tessellator.cc b/impeller/tessellator/c/tessellator.cc index 1bdbfde66db84..b576d4393e809 100644 --- a/impeller/tessellator/c/tessellator.cc +++ b/impeller/tessellator/c/tessellator.cc @@ -45,14 +45,14 @@ struct Vertices* Tessellate(PathBuilder* builder, std::vector points; if (Tessellator{}.Tessellate( path.GetFillType(), polyline, - [&points](const float* vertices, size_t vertices_size, - const uint16_t* indices, size_t indices_size) { + [&points](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { // Results are expected to be re-duplicated. std::vector raw_points; - for (auto i = 0u; i < vertices_size; i += 2) { + for (auto i = 0u; i < vertices_count * 2; i += 2) { raw_points.emplace_back(Point{vertices[i], vertices[i + 1]}); } - for (auto i = 0u; i < indices_size; i++) { + for (auto i = 0u; i < indices_count; i++) { auto point = raw_points[indices[i]]; points.push_back(point.x); points.push_back(point.y); diff --git a/impeller/tessellator/tessellator.cc b/impeller/tessellator/tessellator.cc index f6b27215cca31..f7c80fe747ea0 100644 --- a/impeller/tessellator/tessellator.cc +++ b/impeller/tessellator/tessellator.cc @@ -78,51 +78,141 @@ Tessellator::Result Tessellator::Tessellate( 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 // + // If we have a larger polyline and the fill type is non-zero, we can split + // the tessellation up per contour. Since in general the complexity is at + // least nlog(n), this speeds up the processes substantially. + if (polyline.contours.size() > kMultiContourThreshold && + fill_type == FillType::kNonZero) { + std::vector points; + std::vector data; + + //---------------------------------------------------------------------------- + /// Feed contour information to the tessellator. + /// + size_t total = 0u; + 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 vertex_item_count = tessGetVertexCount(tessellator) * kVertexSize; + auto vertices = tessGetVertices(tessellator); + 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); + total += 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); + } + points.clear(); + } + if (!callback(data.data(), total, nullptr, 0u)) { + return Result::kInputError; + } + } else { + //---------------------------------------------------------------------------- + /// 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) ); - } - - //---------------------------------------------------------------------------- - /// 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 vertexItemCount = tessGetVertexCount(tessellator) * kVertexSize; - auto vertices = tessGetVertices(tessellator); - int elementItemCount = tessGetElementCount(tessellator) * kPolygonSize; - auto elements = tessGetElements(tessellator); - // libtess uses an int index internally due to usage of -1 as a sentinel - // value. - std::vector indices(elementItemCount); - for (int i = 0; i < elementItemCount; i++) { - indices[i] = static_cast(elements[i]); - } - if (!callback(vertices, vertexItemCount, indices.data(), elementItemCount)) { - return Result::kInputError; + 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); + 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); + 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; diff --git a/impeller/tessellator/tessellator.h b/impeller/tessellator/tessellator.h index ff93b7091a3f6..b6d444c5a81ba 100644 --- a/impeller/tessellator/tessellator.h +++ b/impeller/tessellator/tessellator.h @@ -44,10 +44,18 @@ class Tessellator { ~Tessellator(); + /// @brief An arbitrary value to determine when a multi-contour non-zero fill + /// path should be split into multiple tessellations. + static constexpr size_t kMultiContourThreshold = 30u; + + /// @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; + size_t indices_count)>; //---------------------------------------------------------------------------- /// @brief Generates filled triangles from the polyline. A callback is diff --git a/impeller/tessellator/tessellator_unittests.cc b/impeller/tessellator/tessellator_unittests.cc index 0b943d1391254..8fca675a9a9a6 100644 --- a/impeller/tessellator/tessellator_unittests.cc +++ b/impeller/tessellator/tessellator_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/testing/testing.h" #include "gtest/gtest.h" +#include "impeller/geometry/path.h" #include "impeller/geometry/path_builder.h" #include "impeller/tessellator/tessellator.h" @@ -17,8 +18,8 @@ TEST(TessellatorTest, TessellatorBuilderReturnsCorrectResultStatus) { auto polyline = PathBuilder{}.TakePath().CreatePolyline(1.0f); Tessellator::Result result = t.Tessellate( FillType::kPositive, polyline, - [](const float* vertices, size_t vertices_size, const uint16_t* indices, - size_t indices_size) { return true; }); + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return true; }); ASSERT_EQ(polyline.points.size(), 0u); ASSERT_EQ(result, Tessellator::Result::kInputError); @@ -29,10 +30,11 @@ TEST(TessellatorTest, TessellatorBuilderReturnsCorrectResultStatus) { Tessellator t; auto polyline = PathBuilder{}.LineTo({0, 0}).TakePath().CreatePolyline(1.0f); - Tessellator::Result result = t.Tessellate( - FillType::kPositive, polyline, - [](const float* vertices, size_t vertices_size, const uint16_t* indices, - size_t indices_size) { return true; }); + Tessellator::Result result = + t.Tessellate(FillType::kPositive, polyline, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices_count, + size_t indices_size) { return true; }); ASSERT_EQ(polyline.points.size(), 1u); ASSERT_EQ(result, Tessellator::Result::kSuccess); } @@ -42,10 +44,11 @@ TEST(TessellatorTest, TessellatorBuilderReturnsCorrectResultStatus) { Tessellator t; auto polyline = PathBuilder{}.AddLine({0, 0}, {0, 1}).TakePath().CreatePolyline(1.0f); - Tessellator::Result result = t.Tessellate( - FillType::kPositive, polyline, - [](const float* vertices, size_t vertices_size, const uint16_t* indices, - size_t indices_size) { return true; }); + Tessellator::Result result = + t.Tessellate(FillType::kPositive, polyline, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices_count, + size_t indices_size) { return true; }); ASSERT_EQ(polyline.points.size(), 2u); ASSERT_EQ(result, Tessellator::Result::kSuccess); @@ -60,10 +63,11 @@ TEST(TessellatorTest, TessellatorBuilderReturnsCorrectResultStatus) { builder.AddLine({coord, coord}, {coord + 1, coord + 1}); } auto polyline = builder.TakePath().CreatePolyline(1.0f); - Tessellator::Result result = t.Tessellate( - FillType::kPositive, polyline, - [](const float* vertices, size_t vertices_size, const uint16_t* indices, - size_t indices_size) { return true; }); + Tessellator::Result result = + t.Tessellate(FillType::kPositive, polyline, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices_count, + size_t indices_size) { return true; }); ASSERT_EQ(polyline.points.size(), 2000u); ASSERT_EQ(result, Tessellator::Result::kSuccess); @@ -74,14 +78,60 @@ TEST(TessellatorTest, TessellatorBuilderReturnsCorrectResultStatus) { Tessellator t; auto polyline = PathBuilder{}.AddLine({0, 0}, {0, 1}).TakePath().CreatePolyline(1.0f); - Tessellator::Result result = t.Tessellate( - FillType::kPositive, polyline, - [](const float* vertices, size_t vertices_size, const uint16_t* indices, - size_t indices_size) { return false; }); + Tessellator::Result result = + t.Tessellate(FillType::kPositive, polyline, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices_count, + size_t indices_size) { return false; }); ASSERT_EQ(polyline.points.size(), 2u); ASSERT_EQ(result, Tessellator::Result::kInputError); } + + // More than 30 contours, non-zero fill mode. + { + Tessellator t; + PathBuilder builder = {}; + for (auto i = 0u; i < Tessellator::kMultiContourThreshold + 1; i++) { + builder.AddCircle(Point(i, i), 4); + } + auto polyline = builder.TakePath().CreatePolyline(1.0f); + bool no_indices = false; + Tessellator::Result result = t.Tessellate( + FillType::kNonZero, polyline, + [&no_indices](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { + no_indices = indices == nullptr; + return true; + }); + + ASSERT_TRUE(no_indices); + ASSERT_EQ(result, Tessellator::Result::kSuccess); + } + + // More than uint16 points, odd fill mode. + { + Tessellator t; + PathBuilder builder = {}; + for (auto i = 0; i < 1000; i++) { + builder.AddCircle(Point(i, i), 4); + } + auto polyline = builder.TakePath(FillType::kOdd).CreatePolyline(1.0f); + bool no_indices = false; + size_t count = 0u; + Tessellator::Result result = t.Tessellate( + FillType::kOdd, polyline, + [&no_indices, &count](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { + no_indices = indices == nullptr; + count = vertices_count; + return true; + }); + + ASSERT_TRUE(no_indices); + ASSERT_TRUE(count >= USHRT_MAX); + ASSERT_EQ(result, Tessellator::Result::kSuccess); + } } } // namespace testing