Reverts "[Impeller] remove most temporary allocation during polyline generation. (#52131)" (flutter/engine#52177)

Reverts: flutter/engine#52131
Initiated by: jonahwilliams
Reason for reverting: breaking flutter logo rendering

![image](https://github.com/flutter/engine/assets/8975114/90a6d70f-db22-4684-80f9-1cea3dc21ac5)

Original PR Author: jonahwilliams

Reviewed By: {chinmaygarde}

This change reverts the following previous change:
Part of https://github.com/flutter/flutter/issues/143077

Only allocate into reused arenas instead of allocating a new vector of data. Fixes https://github.com/flutter/flutter/issues/133348

Also moves tessellation logic into the c/tessellator and out of the impeller tessellator. This was necessary to fix a compilation error. introduced by including host_buffer -> allocator -> fml mapping -> window.h include which has a function definition that conflicts with the c tessellator definition.
This commit is contained in:
auto-submit[bot] 2024-04-16 22:55:47 +00:00 committed by GitHub
parent ba91beb2e8
commit e3a20e4fb6
14 changed files with 423 additions and 474 deletions

View File

@ -7,6 +7,8 @@
#include <cstddef>
#include "flutter/fml/macros.h"
namespace impeller {
struct Range {

View File

@ -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<VS::PerVertexData> 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(),
};

View File

@ -59,22 +59,39 @@ template <class... Args>
static void BM_Polyline(benchmark::State& state, Args&&... args) {
auto args_tuple = std::make_tuple(std::move(args)...);
auto path = std::get<Path>(args_tuple);
bool tessellate = std::get<bool>(args_tuple);
size_t point_count = 0u;
size_t single_point_count = 0u;
auto points = std::make_unique<std::vector<Point>>();
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<std::vector<Point>>();
auto indices = std::make_unique<std::vector<uint16_t>>();
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);

View File

@ -349,45 +349,4 @@ std::optional<Rect> 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<const LinearPathComponent*>(
&path_points[path_component.index]);
writer.Write(linear->p2);
break;
}
case ComponentType::kQuadratic: {
const QuadraticPathComponent* quad =
reinterpret_cast<const QuadraticPathComponent*>(
&path_points[path_component.index]);
quad->ToLinearPathComponents(scale, writer);
break;
}
case ComponentType::kCubic: {
const CubicPathComponent* cubic =
reinterpret_cast<const CubicPathComponent*>(
&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

View File

@ -11,7 +11,6 @@
#include <vector>
#include "impeller/geometry/path_component.h"
#include "impeller/geometry/rect.h"
namespace impeller {
@ -169,13 +168,6 @@ class Path {
std::make_unique<std::vector<Point>>(),
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<Rect> GetBoundingBox() const;
std::optional<Rect> GetTransformedBoundingBox(const Matrix& transform) const;

View File

@ -10,66 +10,6 @@
namespace impeller {
VertexWriter::VertexWriter(std::vector<Point>& points,
std::vector<uint16_t>& indices,
std::optional<Matrix> 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<Point> 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));
}

View File

@ -10,34 +10,12 @@
#include <variant>
#include <vector>
#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<Point>& points,
std::vector<uint16_t>& indices,
std::optional<Matrix> uv_transform);
~VertexWriter() = default;
void EndContour();
void Write(Point point);
private:
bool previous_contour_odd_points_ = false;
size_t contour_start_ = 0u;
std::vector<Point>& points_;
std::vector<uint16_t>& indices_;
std::optional<Matrix> 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<Point> 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 {

View File

@ -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<VS::PerVertexData> 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 =

View File

@ -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 = [

View File

@ -4,205 +4,9 @@
#include "tessellator.h"
#include <memory>
#include <vector>
#include "third_party/libtess2/Include/tesselator.h"
namespace impeller {
void DestroyTessellator(TESStesselator* tessellator) {
if (tessellator != nullptr) {
::tessDeleteTess(tessellator);
}
}
using CTessellator =
std::unique_ptr<TESStesselator, decltype(&DestroyTessellator)>;
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<bool(const float* vertices,
size_t vertices_count,
const uint16_t* indices,
size_t indices_count)>;
//----------------------------------------------------------------------------
/// @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<std::vector<Point>> buffer =
std::make_unique<std::vector<Point>>();
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<uint16_t> indices(element_item_count);
for (int i = 0; i < element_item_count; i++) {
indices[i] = static_cast<uint16_t>(elements[i]);
}
if (!callback(vertices, vertex_item_count, indices.data(),
element_item_count)) {
return Result::kInputError;
}
} else {
std::vector<Point> points;
std::vector<float> 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<FillType>(fill_type));
std::vector<float> 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;
}

View File

@ -8,6 +8,7 @@
#include <cstdint>
#include "impeller/geometry/path_builder.h"
#include "impeller/tessellator/tessellator.h"
#ifdef _WIN32
#define IMPELLER_API __declspec(dllexport)

View File

@ -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<std::vector<Point>>()),
index_buffer_(std::make_unique<std::vector<uint16_t>>()) {
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<uint16_t> indices(element_item_count);
for (int i = 0; i < element_item_count; i++) {
indices[i] = static_cast<uint16_t>(elements[i]);
}
if (!callback(vertices, vertex_item_count, indices.data(),
element_item_count)) {
return Result::kInputError;
}
} else {
std::vector<Point> points;
std::vector<float> 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<Matrix> uv_transform) {
std::vector<Point> 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,
};
std::vector<Point> 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;
}
BufferView vertex_buffer = host_buffer.Emplace(
point_buffer_->data(), sizeof(Point) * point_buffer_->size(),
alignof(Point));
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);
BufferView index_buffer = host_buffer.Emplace(
index_buffer_->data(), sizeof(uint16_t) * index_buffer_->size(),
alignof(uint16_t));
// 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--;
}
return VertexBuffer{
.vertex_buffer = std::move(vertex_buffer),
.index_buffer = std::move(index_buffer),
.vertex_count = index_buffer_->size(),
.index_type = IndexType::k16bit,
};
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;
}
void Tessellator::TessellateConvexInternal(const Path& path,
std::vector<Point>& point_buffer,
std::vector<uint16_t>& index_buffer,
Scalar tolerance,
std::optional<Matrix> uv_transform) {
index_buffer_->clear();
point_buffer_->clear();
VertexWriter writer(point_buffer, index_buffer, uv_transform);
path.WritePolyline(tolerance, writer);
writer.EndContour();
void DestroyTessellator(TESStesselator* tessellator) {
if (tessellator != nullptr) {
::tessDeleteTess(tessellator);
}
}
static constexpr int kPrecomputedDivisionCount = 1024;

View File

@ -10,8 +10,6 @@
#include <vector>
#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<TESStesselator, decltype(&DestroyTessellator)>;
//------------------------------------------------------------------------------
/// @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<void(const Point& p)>;
@ -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<bool(const float* vertices,
size_t vertices_count,
const uint16_t* indices,
size_t indices_count)>;
//----------------------------------------------------------------------------
/// @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<Matrix> 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>& point_buffer,
std::vector<uint16_t>& index_buffer,
Scalar tolerance,
std::optional<Matrix> uv_transform = std::nullopt);
std::vector<Point> 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<std::vector<Point>> point_buffer_;
std::unique_ptr<std::vector<uint16_t>> 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;

View File

@ -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<Point> points;
std::vector<uint16_t> indices;
// Sanity check simple rectangle.
t.TessellateConvexInternal(
PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 10, 10)).TakePath(), points,
indices, 1.0);
auto pts = t.TessellateConvex(
PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 10, 10)).TakePath(), 1.0);
std::vector<Point> expected = {{10, 0}, {10, 10}, {0, 10}, {0, 0}};
std::vector<uint16_t> expected_indices = {0, 1, 3, 2};
EXPECT_EQ(points, expected);
EXPECT_EQ(indices, expected_indices);
std::vector<Point> expected = {{0, 0}, {10, 0}, {0, 10}, {10, 10}};
EXPECT_EQ(pts, expected);
}
{
Tessellator t;
std::vector<Point> points;
std::vector<uint16_t> indices;
t.TessellateConvexInternal(PathBuilder{}
.AddRect(Rect::MakeLTRB(0, 0, 10, 10))
.AddRect(Rect::MakeLTRB(20, 20, 30, 30))
.TakePath(),
points, indices, 1.0);
auto pts = t.TessellateConvex(PathBuilder{}
.AddRect(Rect::MakeLTRB(0, 0, 10, 10))
.AddRect(Rect::MakeLTRB(20, 20, 30, 30))
.TakePath(),
1.0);
std::vector<Point> expected = {{10, 0}, {10, 10}, {0, 10}, {0, 0},
{30, 20}, {30, 30}, {20, 30}, {20, 20}};
std::vector<uint16_t> expected_indices = {0, 1, 3, 2, 2, 4, 4, 5, 7, 6};
EXPECT_EQ(points, expected);
EXPECT_EQ(indices, expected_indices);
std::vector<Point> 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<Point> points;
std::vector<uint16_t> indices;
tessellator->TessellateConvexInternal(builder.TakePath(), points, indices,
3.0);
auto points = tessellator->TessellateConvex(builder.TakePath(), 3.0);
EXPECT_TRUE(points.empty());
}