Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 35ba2a3

Browse files
[Impeller] Fallback to no index buffer when tesselation count is large, split up nonZero contours. (#46282)
See example app in flutter/flutter#135458 Because tessellation is generally nlogn best case (with allocation complexity as well), we can actually make it faster by breaking it into smaller pieces. For non zero fill modes, the contours can be tessellated individually without losing fidelity. In cases where this isn't possible, we need to check if we exceed the max uint16 index and fall back to no index buffer. Fixes flutter/flutter#135458 I Benchmarked signing my name. ### Before worst raster time 3+ seconds. Visual glitches ![flutter_03](https://github.com/flutter/engine/assets/8975114/7be57bd2-744f-4f0a-af0a-d2bd4fc9efaf) ### After worst raster time 30 ms, looks correct (though my signature is still wrong) ![flutter_04](https://github.com/flutter/engine/assets/8975114/1fdf504e-7b4e-447a-8c29-063dd0ff34d0)
1 parent 33c8d77 commit 35ba2a3

File tree

7 files changed

+239
-82
lines changed

7 files changed

+239
-82
lines changed

impeller/entity/geometry/fill_path_geometry.cc

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
#include "impeller/entity/geometry/fill_path_geometry.h"
6+
#include "impeller/core/formats.h"
67

78
namespace impeller {
89

@@ -47,11 +48,17 @@ GeometryResult FillPathGeometry::GetPositionBuffer(
4748
const float* vertices, size_t vertices_count, const uint16_t* indices,
4849
size_t indices_count) {
4950
vertex_buffer.vertex_buffer = host_buffer.Emplace(
50-
vertices, vertices_count * sizeof(float), alignof(float));
51-
vertex_buffer.index_buffer = host_buffer.Emplace(
52-
indices, indices_count * sizeof(uint16_t), alignof(uint16_t));
53-
vertex_buffer.vertex_count = indices_count;
54-
vertex_buffer.index_type = IndexType::k16bit;
51+
vertices, vertices_count * sizeof(float) * 2, alignof(float));
52+
if (indices != nullptr) {
53+
vertex_buffer.index_buffer = host_buffer.Emplace(
54+
indices, indices_count * sizeof(uint16_t), alignof(uint16_t));
55+
vertex_buffer.vertex_count = indices_count;
56+
vertex_buffer.index_type = IndexType::k16bit;
57+
} else {
58+
vertex_buffer.index_buffer = {};
59+
vertex_buffer.vertex_count = vertices_count;
60+
vertex_buffer.index_type = IndexType::kNone;
61+
}
5562
return true;
5663
});
5764
if (tesselation_result != Tessellator::Result::kSuccess) {
@@ -112,7 +119,7 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer(
112119
[&vertex_builder, &texture_coverage, &effect_transform](
113120
const float* vertices, size_t vertices_count, const uint16_t* indices,
114121
size_t indices_count) {
115-
for (auto i = 0u; i < vertices_count; i += 2) {
122+
for (auto i = 0u; i < vertices_count * 2; i += 2) {
116123
VS::PerVertexData data;
117124
Point vtx = {vertices[i], vertices[i + 1]};
118125
data.position = vtx;
@@ -121,9 +128,11 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer(
121128
texture_coverage.size;
122129
vertex_builder.AppendVertex(data);
123130
}
124-
FML_DCHECK(vertex_builder.GetVertexCount() == vertices_count / 2);
125-
for (auto i = 0u; i < indices_count; i++) {
126-
vertex_builder.AppendIndex(indices[i]);
131+
FML_DCHECK(vertex_builder.GetVertexCount() == vertices_count);
132+
if (indices != nullptr) {
133+
for (auto i = 0u; i < indices_count; i++) {
134+
vertex_builder.AppendIndex(indices[i]);
135+
}
127136
}
128137
return true;
129138
});

impeller/geometry/geometry_benchmarks.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ static void BM_Polyline(benchmark::State& state, Args&&... args) {
3535
if (tessellate) {
3636
tess.Tessellate(
3737
FillType::kNonZero, polyline,
38-
[](const float* vertices, size_t vertices_size,
39-
const uint16_t* indices, size_t indices_size) { return true; });
38+
[](const float* vertices, size_t vertices_count,
39+
const uint16_t* indices, size_t indices_count) { return true; });
4040
}
4141
}
4242
state.counters["SinglePointCount"] = single_point_count;

impeller/renderer/renderer_unittests.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,14 +404,14 @@ TEST_P(RendererTest, CanRenderInstanced) {
404404
.AddRect(Rect::MakeXYWH(10, 10, 100, 100))
405405
.TakePath()
406406
.CreatePolyline(1.0f),
407-
[&builder](const float* vertices, size_t vertices_size,
408-
const uint16_t* indices, size_t indices_size) {
409-
for (auto i = 0u; i < vertices_size; i += 2) {
407+
[&builder](const float* vertices, size_t vertices_count,
408+
const uint16_t* indices, size_t indices_count) {
409+
for (auto i = 0u; i < vertices_count * 2; i += 2) {
410410
VS::PerVertexData data;
411411
data.vtx = {vertices[i], vertices[i + 1]};
412412
builder.AppendVertex(data);
413413
}
414-
for (auto i = 0u; i < indices_size; i++) {
414+
for (auto i = 0u; i < indices_count; i++) {
415415
builder.AppendIndex(indices[i]);
416416
}
417417
return true;

impeller/tessellator/c/tessellator.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ struct Vertices* Tessellate(PathBuilder* builder,
4545
std::vector<float> points;
4646
if (Tessellator{}.Tessellate(
4747
path.GetFillType(), polyline,
48-
[&points](const float* vertices, size_t vertices_size,
49-
const uint16_t* indices, size_t indices_size) {
48+
[&points](const float* vertices, size_t vertices_count,
49+
const uint16_t* indices, size_t indices_count) {
5050
// Results are expected to be re-duplicated.
5151
std::vector<Point> raw_points;
52-
for (auto i = 0u; i < vertices_size; i += 2) {
52+
for (auto i = 0u; i < vertices_count * 2; i += 2) {
5353
raw_points.emplace_back(Point{vertices[i], vertices[i + 1]});
5454
}
55-
for (auto i = 0u; i < indices_size; i++) {
55+
for (auto i = 0u; i < indices_count; i++) {
5656
auto point = raw_points[indices[i]];
5757
points.push_back(point.x);
5858
points.push_back(point.y);

impeller/tessellator/tessellator.cc

Lines changed: 133 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -78,51 +78,141 @@ Tessellator::Result Tessellator::Tessellate(
7878
constexpr int kVertexSize = 2;
7979
constexpr int kPolygonSize = 3;
8080

81-
//----------------------------------------------------------------------------
82-
/// Feed contour information to the tessellator.
83-
///
84-
static_assert(sizeof(Point) == 2 * sizeof(float));
85-
for (size_t contour_i = 0; contour_i < polyline.contours.size();
86-
contour_i++) {
87-
size_t start_point_index, end_point_index;
88-
std::tie(start_point_index, end_point_index) =
89-
polyline.GetContourPointBounds(contour_i);
90-
91-
::tessAddContour(tessellator, // the C tessellator
92-
kVertexSize, //
93-
polyline.points.data() + start_point_index, //
94-
sizeof(Point), //
95-
end_point_index - start_point_index //
81+
// If we have a larger polyline and the fill type is non-zero, we can split
82+
// the tessellation up per contour. Since in general the complexity is at
83+
// least nlog(n), this speeds up the processes substantially.
84+
if (polyline.contours.size() > kMultiContourThreshold &&
85+
fill_type == FillType::kNonZero) {
86+
std::vector<Point> points;
87+
std::vector<float> data;
88+
89+
//----------------------------------------------------------------------------
90+
/// Feed contour information to the tessellator.
91+
///
92+
size_t total = 0u;
93+
static_assert(sizeof(Point) == 2 * sizeof(float));
94+
for (size_t contour_i = 0; contour_i < polyline.contours.size();
95+
contour_i++) {
96+
size_t start_point_index, end_point_index;
97+
std::tie(start_point_index, end_point_index) =
98+
polyline.GetContourPointBounds(contour_i);
99+
100+
::tessAddContour(tessellator, // the C tessellator
101+
kVertexSize, //
102+
polyline.points.data() + start_point_index, //
103+
sizeof(Point), //
104+
end_point_index - start_point_index //
105+
);
106+
107+
//----------------------------------------------------------------------------
108+
/// Let's tessellate.
109+
///
110+
auto result = ::tessTesselate(tessellator, // tessellator
111+
ToTessWindingRule(fill_type), // winding
112+
TESS_POLYGONS, // element type
113+
kPolygonSize, // polygon size
114+
kVertexSize, // vertex size
115+
nullptr // normal (null is automatic)
116+
);
117+
118+
if (result != 1) {
119+
return Result::kTessellationError;
120+
}
121+
122+
int vertex_item_count = tessGetVertexCount(tessellator) * kVertexSize;
123+
auto vertices = tessGetVertices(tessellator);
124+
for (int i = 0; i < vertex_item_count; i += 2) {
125+
points.emplace_back(vertices[i], vertices[i + 1]);
126+
}
127+
128+
int element_item_count = tessGetElementCount(tessellator) * kPolygonSize;
129+
auto elements = tessGetElements(tessellator);
130+
total += element_item_count;
131+
for (int i = 0; i < element_item_count; i++) {
132+
data.emplace_back(points[elements[i]].x);
133+
data.emplace_back(points[elements[i]].y);
134+
}
135+
points.clear();
136+
}
137+
if (!callback(data.data(), total, nullptr, 0u)) {
138+
return Result::kInputError;
139+
}
140+
} else {
141+
//----------------------------------------------------------------------------
142+
/// Feed contour information to the tessellator.
143+
///
144+
static_assert(sizeof(Point) == 2 * sizeof(float));
145+
for (size_t contour_i = 0; contour_i < polyline.contours.size();
146+
contour_i++) {
147+
size_t start_point_index, end_point_index;
148+
std::tie(start_point_index, end_point_index) =
149+
polyline.GetContourPointBounds(contour_i);
150+
151+
::tessAddContour(tessellator, // the C tessellator
152+
kVertexSize, //
153+
polyline.points.data() + start_point_index, //
154+
sizeof(Point), //
155+
end_point_index - start_point_index //
156+
);
157+
}
158+
159+
//----------------------------------------------------------------------------
160+
/// Let's tessellate.
161+
///
162+
auto result = ::tessTesselate(tessellator, // tessellator
163+
ToTessWindingRule(fill_type), // winding
164+
TESS_POLYGONS, // element type
165+
kPolygonSize, // polygon size
166+
kVertexSize, // vertex size
167+
nullptr // normal (null is automatic)
96168
);
97-
}
98-
99-
//----------------------------------------------------------------------------
100-
/// Let's tessellate.
101-
///
102-
auto result = ::tessTesselate(tessellator, // tessellator
103-
ToTessWindingRule(fill_type), // winding
104-
TESS_POLYGONS, // element type
105-
kPolygonSize, // polygon size
106-
kVertexSize, // vertex size
107-
nullptr // normal (null is automatic)
108-
);
109-
110-
if (result != 1) {
111-
return Result::kTessellationError;
112-
}
113169

114-
int vertexItemCount = tessGetVertexCount(tessellator) * kVertexSize;
115-
auto vertices = tessGetVertices(tessellator);
116-
int elementItemCount = tessGetElementCount(tessellator) * kPolygonSize;
117-
auto elements = tessGetElements(tessellator);
118-
// libtess uses an int index internally due to usage of -1 as a sentinel
119-
// value.
120-
std::vector<uint16_t> indices(elementItemCount);
121-
for (int i = 0; i < elementItemCount; i++) {
122-
indices[i] = static_cast<uint16_t>(elements[i]);
123-
}
124-
if (!callback(vertices, vertexItemCount, indices.data(), elementItemCount)) {
125-
return Result::kInputError;
170+
if (result != 1) {
171+
return Result::kTessellationError;
172+
}
173+
174+
int element_item_count = tessGetElementCount(tessellator) * kPolygonSize;
175+
176+
// We default to using a 16bit index buffer, but in cases where we generate
177+
// more tessellated data than this can contain we need to fall back to
178+
// dropping the index buffer entirely. Instead code could instead switch to
179+
// a uint32 index buffer, but this is done for simplicity with the other
180+
// fast path above.
181+
if (element_item_count < USHRT_MAX) {
182+
int vertex_item_count = tessGetVertexCount(tessellator);
183+
auto vertices = tessGetVertices(tessellator);
184+
auto elements = tessGetElements(tessellator);
185+
186+
// libtess uses an int index internally due to usage of -1 as a sentinel
187+
// value.
188+
std::vector<uint16_t> indices(element_item_count);
189+
for (int i = 0; i < element_item_count; i++) {
190+
indices[i] = static_cast<uint16_t>(elements[i]);
191+
}
192+
if (!callback(vertices, vertex_item_count, indices.data(),
193+
element_item_count)) {
194+
return Result::kInputError;
195+
}
196+
} else {
197+
std::vector<Point> points;
198+
std::vector<float> data;
199+
200+
int vertex_item_count = tessGetVertexCount(tessellator) * kVertexSize;
201+
auto vertices = tessGetVertices(tessellator);
202+
for (int i = 0; i < vertex_item_count; i += 2) {
203+
points.emplace_back(vertices[i], vertices[i + 1]);
204+
}
205+
206+
int element_item_count = tessGetElementCount(tessellator) * kPolygonSize;
207+
auto elements = tessGetElements(tessellator);
208+
for (int i = 0; i < element_item_count; i++) {
209+
data.emplace_back(points[elements[i]].x);
210+
data.emplace_back(points[elements[i]].y);
211+
}
212+
if (!callback(data.data(), element_item_count, nullptr, 0u)) {
213+
return Result::kInputError;
214+
}
215+
}
126216
}
127217

128218
return Result::kSuccess;

impeller/tessellator/tessellator.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,18 @@ class Tessellator {
4444

4545
~Tessellator();
4646

47+
/// @brief An arbitrary value to determine when a multi-contour non-zero fill
48+
/// path should be split into multiple tessellations.
49+
static constexpr size_t kMultiContourThreshold = 30u;
50+
51+
/// @brief A callback that returns the results of the tessellation.
52+
///
53+
/// The index buffer may not be populated, in which case [indices] will
54+
/// be nullptr and indices_count will be 0.
4755
using BuilderCallback = std::function<bool(const float* vertices,
48-
size_t vertices_size,
56+
size_t vertices_count,
4957
const uint16_t* indices,
50-
size_t indices_size)>;
58+
size_t indices_count)>;
5159

5260
//----------------------------------------------------------------------------
5361
/// @brief Generates filled triangles from the polyline. A callback is

0 commit comments

Comments
 (0)