Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Impeller] remove most temporary allocation during polyline generation. #52131

Merged
merged 9 commits into from
Apr 16, 2024
2 changes: 0 additions & 2 deletions impeller/core/range.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

#include <cstddef>

#include "flutter/fml/macros.h"

namespace impeller {

struct Range {
Expand Down
30 changes: 6 additions & 24 deletions impeller/entity/geometry/fill_path_geometry.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,8 @@ GeometryResult FillPathGeometry::GetPositionBuffer(
};
}

VertexBuffer vertex_buffer;

auto points = renderer.GetTessellator()->TessellateConvex(
path_, entity.GetTransform().GetMaxBasisLength());

vertex_buffer.vertex_buffer = host_buffer.Emplace(
points.data(), points.size() * sizeof(Point), alignof(Point));
vertex_buffer.index_buffer = {}, vertex_buffer.vertex_count = points.size();
vertex_buffer.index_type = IndexType::kNone;
VertexBuffer vertex_buffer = renderer.GetTessellator()->TessellateConvex(
path_, host_buffer, entity.GetTransform().GetMaxBasisLength());

return GeometryResult{
.type = PrimitiveType::kTriangleStrip,
Expand All @@ -61,8 +54,6 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer(
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
using VS = TextureFillVertexShader;

const auto& bounding_box = path_.GetBoundingBox();
if (bounding_box.has_value() && bounding_box->IsEmpty()) {
return GeometryResult{
Expand All @@ -80,22 +71,13 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer(
auto uv_transform =
texture_coverage.GetNormalizingTransform() * effect_transform;

auto points = renderer.GetTessellator()->TessellateConvex(
path_, entity.GetTransform().GetMaxBasisLength());

VertexBufferBuilder<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);
}
VertexBuffer vertex_buffer = renderer.GetTessellator()->TessellateConvex(
path_, renderer.GetTransientsBuffer(),
entity.GetTransform().GetMaxBasisLength(), uv_transform);

return GeometryResult{
.type = PrimitiveType::kTriangleStrip,
.vertex_buffer =
vertex_builder.CreateVertexBuffer(renderer.GetTransientsBuffer()),
.vertex_buffer = vertex_buffer,
.transform = entity.GetShaderTransform(pass),
.mode = GetResultMode(),
};
Expand Down
55 changes: 15 additions & 40 deletions impeller/geometry/geometry_benchmarks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,39 +59,22 @@ 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()) {
if (tessellate) {
tess.Tessellate(path, 1.0f,
[&point_count, &single_point_count](
const float* vertices, size_t vertices_count,
const uint16_t* indices, size_t indices_count) {
if (indices_count > 0) {
single_point_count = indices_count;
point_count += indices_count;
} else {
single_point_count = vertices_count;
point_count += vertices_count;
}
return true;
});
} else {
auto polyline = path.CreatePolyline(
// Clang-tidy doesn't know that the points get moved back before
// getting moved again in this loop.
// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move)
1.0f, std::move(points),
[&points](Path::Polyline::PointBufferPtr reclaimed) {
points = std::move(reclaimed);
});
single_point_count = polyline.points->size();
point_count += single_point_count;
}
auto polyline = path.CreatePolyline(
// Clang-tidy doesn't know that the points get moved back before
// getting moved again in this loop.
// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move)
1.0f, std::move(points),
[&points](Path::Polyline::PointBufferPtr reclaimed) {
points = std::move(reclaimed);
});
single_point_count = polyline.points->size();
point_count += single_point_count;
}
state.counters["SinglePointCount"] = single_point_count;
state.counters["TotalPointCount"] = point_count;
Expand Down Expand Up @@ -155,11 +138,13 @@ static void BM_Convex(benchmark::State& state, Args&&... args) {
size_t point_count = 0u;
size_t single_point_count = 0u;
auto points = std::make_unique<std::vector<Point>>();
auto indices = std::make_unique<std::vector<uint16_t>>();
points->reserve(2048);
indices->reserve(2048);
while (state.KeepRunning()) {
auto points = tess.TessellateConvex(path, 1.0f);
single_point_count = points.size();
point_count += points.size();
tess.TessellateConvexInternal(path, *points, *indices, 1.0f);
single_point_count = indices->size();
point_count += indices->size();
}
state.counters["SinglePointCount"] = single_point_count;
state.counters["TotalPointCount"] = point_count;
Expand All @@ -182,27 +167,17 @@ static void BM_Convex(benchmark::State& state, Args&&... args) {
MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, _uvNoTx, UVMode::kUVRect)

BENCHMARK_CAPTURE(BM_Polyline, cubic_polyline, CreateCubic(true), false);
BENCHMARK_CAPTURE(BM_Polyline, cubic_polyline_tess, CreateCubic(true), true);
BENCHMARK_CAPTURE(BM_Polyline,
unclosed_cubic_polyline,
CreateCubic(false),
false);
BENCHMARK_CAPTURE(BM_Polyline,
unclosed_cubic_polyline_tess,
CreateCubic(false),
true);
MAKE_STROKE_BENCHMARK_CAPTURE_UVS(Cubic);

BENCHMARK_CAPTURE(BM_Polyline, quad_polyline, CreateQuadratic(true), false);
BENCHMARK_CAPTURE(BM_Polyline, quad_polyline_tess, CreateQuadratic(true), true);
BENCHMARK_CAPTURE(BM_Polyline,
unclosed_quad_polyline,
CreateQuadratic(false),
false);
BENCHMARK_CAPTURE(BM_Polyline,
unclosed_quad_polyline_tess,
CreateQuadratic(false),
true);
MAKE_STROKE_BENCHMARK_CAPTURE_UVS(Quadratic);

BENCHMARK_CAPTURE(BM_Convex, rrect_convex, CreateRRect(), true);
Expand Down
41 changes: 41 additions & 0 deletions impeller/geometry/path.cc
Original file line number Diff line number Diff line change
Expand Up @@ -349,4 +349,45 @@ 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
8 changes: 8 additions & 0 deletions impeller/geometry/path.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <vector>

#include "impeller/geometry/path_component.h"
#include "impeller/geometry/rect.h"

namespace impeller {

Expand Down Expand Up @@ -168,6 +169,13 @@ 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;
Expand Down
79 changes: 79 additions & 0 deletions impeller/geometry/path_component.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,66 @@

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()) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit wonky, but I have a plan to remove all CPU computation of uvs for path drawing: #52106

points_.emplace_back(*uv_transform_ * point);
}
}

/*
* Based on: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Specific_cases
*/
Expand Down Expand Up @@ -119,6 +179,16 @@ void QuadraticPathComponent::ToLinearPathComponents(
proc(p2);
}

void QuadraticPathComponent::ToLinearPathComponents(
Scalar scale,
VertexWriter& writer) const {
Scalar line_count = std::ceilf(ComputeQuadradicSubdivisions(scale, *this));
for (size_t i = 1; i < line_count; i += 1) {
writer.Write(Solve(i / line_count));
}
writer.Write(p2);
}

std::vector<Point> QuadraticPathComponent::Extrema() const {
CubicPathComponent elevated(*this);
return elevated.Extrema();
Expand Down Expand Up @@ -189,6 +259,15 @@ void CubicPathComponent::ToLinearPathComponents(Scalar scale,
proc(p2);
}

void CubicPathComponent::ToLinearPathComponents(Scalar scale,
VertexWriter& writer) const {
Scalar line_count = std::ceilf(ComputeCubicSubdivisions(scale, *this));
for (size_t i = 1; i < line_count; i++) {
writer.Write(Solve(i / line_count));
}
writer.Write(p2);
}

static inline bool NearEqual(Scalar a, Scalar b, Scalar epsilon) {
return (a > (b - epsilon)) && (a < (b + epsilon));
}
Expand Down
28 changes: 27 additions & 1 deletion impeller/geometry/path_component.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,34 @@
#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;
Expand Down Expand Up @@ -64,6 +86,8 @@ 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 {
Expand Down Expand Up @@ -109,6 +133,8 @@ struct CubicPathComponent {

void ToLinearPathComponents(Scalar scale, const PointProc& proc) const;

void ToLinearPathComponents(Scalar scale, VertexWriter& writer) const;

CubicPathComponent Subsegment(Scalar t0, Scalar t1) const;

bool operator==(const CubicPathComponent& other) const {
Expand Down
Loading