mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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  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:
parent
ba91beb2e8
commit
e3a20e4fb6
@ -7,6 +7,8 @@
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
struct Range {
|
||||
|
||||
@ -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(),
|
||||
};
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "impeller/geometry/path_builder.h"
|
||||
#include "impeller/tessellator/tessellator.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define IMPELLER_API __declspec(dllexport)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user