blob: ac63c66078fb96badddbd414bcad790895b1f0a9 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "impeller/tessellator/tessellator.h"
#include "impeller/core/buffer_view.h"
#include "impeller/core/formats.h"
#include "impeller/core/vertex_buffer.h"
namespace impeller {
Tessellator::Tessellator()
: point_buffer_(std::make_unique<std::vector<Point>>()),
index_buffer_(std::make_unique<std::vector<uint16_t>>()) {
point_buffer_->reserve(2048);
index_buffer_->reserve(2048);
}
Tessellator::~Tessellator() = default;
Path::Polyline Tessellator::CreateTempPolyline(const Path& path,
Scalar tolerance) {
FML_DCHECK(point_buffer_);
point_buffer_->clear();
auto polyline =
path.CreatePolyline(tolerance, std::move(point_buffer_),
[this](Path::Polyline::PointBufferPtr point_buffer) {
point_buffer_ = std::move(point_buffer);
});
return polyline;
}
VertexBuffer Tessellator::TessellateConvex(const Path& path,
HostBuffer& host_buffer,
Scalar tolerance,
std::optional<Matrix> uv_transform) {
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,
};
}
BufferView vertex_buffer = host_buffer.Emplace(
point_buffer_->data(), sizeof(Point) * point_buffer_->size(),
alignof(Point));
BufferView index_buffer = host_buffer.Emplace(
index_buffer_->data(), sizeof(uint16_t) * index_buffer_->size(),
alignof(uint16_t));
return VertexBuffer{
.vertex_buffer = std::move(vertex_buffer),
.index_buffer = std::move(index_buffer),
.vertex_count = index_buffer_->size(),
.index_type = IndexType::k16bit,
};
}
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();
}
static constexpr int kPrecomputedDivisionCount = 1024;
static int kPrecomputedDivisions[kPrecomputedDivisionCount] = {
// clang-format off
1, 2, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7,
8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10,
10, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 13,
13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14,
15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16,
16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25,
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26,
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27,
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33,
33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33,
33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 35, 35, 35,
35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 36, 36,
36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38,
38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 40, 40,
40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 43, 43, 43,
43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
43, 43, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 44, 44,
44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
45, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 47,
47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 48, 48, 48,
48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49,
49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50,
50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52,
52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 53,
53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 54,
54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
54, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57,
// clang-format on
};
static size_t ComputeQuadrantDivisions(Scalar pixel_radius) {
if (pixel_radius <= 0.0) {
return 1;
}
int radius_index = ceil(pixel_radius);
if (radius_index < kPrecomputedDivisionCount) {
return kPrecomputedDivisions[radius_index];
}
// For a circle with N divisions per quadrant, the maximum deviation of
// the polgyon approximation from the true circle will be at the center
// of the base of each triangular pie slice. We can compute that distance
// by finding the midpoint of the line of the first slice and compare
// its distance from the center of the circle to the radius. We will aim
// to have the length of that bisector to be within |kCircleTolerance|
// from the radius in pixels.
//
// Each vertex will appear at an angle of:
// theta(i) = (kPi / 2) * (i / N) // for i in [0..N]
// with each point falling at:
// point(i) = r * (cos(theta), sin(theta))
// If we consider the unit circle to simplify the calculations below then
// we need to scale the tolerance from its absolute quantity into a unit
// circle fraction:
// k = tolerance / radius
// Using this scaled tolerance below to avoid multiplying by the radius
// throughout all of the math, we have:
// first point = (1, 0) // theta(0) == 0
// theta = kPi / 2 / N // theta(1)
// second point = (cos(theta), sin(theta)) = (c, s)
// midpoint = (first + second) * 0.5 = ((1 + c)/2, s/2)
// |midpoint| = sqrt((1 + c)*(1 + c)/4 + s*s/4)
// = sqrt((1 + c + c + c*c + s*s) / 4)
// = sqrt((1 + 2c + 1) / 4)
// = sqrt((2 + 2c) / 4)
// = sqrt((1 + c) / 2)
// = cos(theta / 2) // using half-angle cosine formula
// error = 1 - |midpoint| = 1 - cos(theta / 2)
// cos(theta/2) = 1 - error
// theta/2 = acos(1 - error)
// kPi / 2 / N / 2 = acos(1 - error)
// kPi / 4 / acos(1 - error) = N
// Since we need error <= k, we want divisions >= N, so we use:
// N = ceil(kPi / 4 / acos(1 - k))
//
// Math is confirmed in https://math.stackexchange.com/a/4132095
// (keeping in mind that we are computing quarter circle divisions here)
// which also points out a performance optimization that is accurate
// to within an over-estimation of 1 division would be:
// N = ceil(kPi / 4 / sqrt(2 * k))
// Since we have precomputed the divisions for radii up to 1024, we can
// afford to be more accurate using the acos formula here for larger radii.
double k = Tessellator::kCircleTolerance / pixel_radius;
return ceil(kPiOver4 / std::acos(1 - k));
}
void Tessellator::Trigs::init(size_t divisions) {
if (!trigs_.empty()) {
return;
}
// Either not cached yet, or we are using the temp storage...
trigs_.reserve(divisions + 1);
double angle_scale = kPiOver2 / divisions;
trigs_.emplace_back(1.0, 0.0);
for (size_t i = 1; i < divisions; i++) {
trigs_.emplace_back(Radians(i * angle_scale));
}
trigs_.emplace_back(0.0, 1.0);
}
Tessellator::Trigs Tessellator::GetTrigsForDivisions(size_t divisions) {
return divisions < Tessellator::kCachedTrigCount
? Trigs(precomputed_trigs_[divisions], divisions)
: Trigs(divisions);
}
using TessellatedVertexProc = Tessellator::TessellatedVertexProc;
using EllipticalVertexGenerator = Tessellator::EllipticalVertexGenerator;
EllipticalVertexGenerator::EllipticalVertexGenerator(
EllipticalVertexGenerator::GeneratorProc& generator,
Trigs&& trigs,
PrimitiveType triangle_type,
size_t vertices_per_trig,
Data&& data)
: impl_(generator),
trigs_(std::move(trigs)),
data_(data),
vertices_per_trig_(vertices_per_trig) {}
EllipticalVertexGenerator Tessellator::FilledCircle(
const Matrix& view_transform,
const Point& center,
Scalar radius) {
auto divisions =
ComputeQuadrantDivisions(view_transform.GetMaxBasisLength() * radius);
return EllipticalVertexGenerator(Tessellator::GenerateFilledCircle,
GetTrigsForDivisions(divisions),
PrimitiveType::kTriangleStrip, 4,
{
.reference_centers = {center, center},
.radii = {radius, radius},
.half_width = -1.0f,
});
}
EllipticalVertexGenerator Tessellator::StrokedCircle(
const Matrix& view_transform,
const Point& center,
Scalar radius,
Scalar half_width) {
if (half_width > 0) {
auto divisions = ComputeQuadrantDivisions(
view_transform.GetMaxBasisLength() * radius + half_width);
return EllipticalVertexGenerator(Tessellator::GenerateStrokedCircle,
GetTrigsForDivisions(divisions),
PrimitiveType::kTriangleStrip, 8,
{
.reference_centers = {center, center},
.radii = {radius, radius},
.half_width = half_width,
});
} else {
return FilledCircle(view_transform, center, radius);
}
}
EllipticalVertexGenerator Tessellator::RoundCapLine(
const Matrix& view_transform,
const Point& p0,
const Point& p1,
Scalar radius) {
auto along = p1 - p0;
auto length = along.GetLength();
if (length > kEhCloseEnough) {
auto divisions =
ComputeQuadrantDivisions(view_transform.GetMaxBasisLength() * radius);
return EllipticalVertexGenerator(Tessellator::GenerateRoundCapLine,
GetTrigsForDivisions(divisions),
PrimitiveType::kTriangleStrip, 4,
{
.reference_centers = {p0, p1},
.radii = {radius, radius},
.half_width = -1.0f,
});
} else {
return FilledCircle(view_transform, p0, radius);
}
}
EllipticalVertexGenerator Tessellator::FilledEllipse(
const Matrix& view_transform,
const Rect& bounds) {
if (bounds.IsSquare()) {
return FilledCircle(view_transform, bounds.GetCenter(),
bounds.GetWidth() * 0.5f);
}
auto max_radius = bounds.GetSize().MaxDimension();
auto divisions =
ComputeQuadrantDivisions(view_transform.GetMaxBasisLength() * max_radius);
auto center = bounds.GetCenter();
return EllipticalVertexGenerator(Tessellator::GenerateFilledEllipse,
GetTrigsForDivisions(divisions),
PrimitiveType::kTriangleStrip, 4,
{
.reference_centers = {center, center},
.radii = bounds.GetSize() * 0.5f,
.half_width = -1.0f,
});
}
EllipticalVertexGenerator Tessellator::FilledRoundRect(
const Matrix& view_transform,
const Rect& bounds,
const Size& radii) {
if (radii.width * 2 < bounds.GetWidth() ||
radii.height * 2 < bounds.GetHeight()) {
auto max_radius = radii.MaxDimension();
auto divisions = ComputeQuadrantDivisions(
view_transform.GetMaxBasisLength() * max_radius);
auto upper_left = bounds.GetLeftTop() + radii;
auto lower_right = bounds.GetRightBottom() - radii;
return EllipticalVertexGenerator(Tessellator::GenerateFilledRoundRect,
GetTrigsForDivisions(divisions),
PrimitiveType::kTriangleStrip, 4,
{
.reference_centers =
{
upper_left,
lower_right,
},
.radii = radii,
.half_width = -1.0f,
});
} else {
return FilledEllipse(view_transform, bounds);
}
}
void Tessellator::GenerateFilledCircle(
const Trigs& trigs,
const EllipticalVertexGenerator::Data& data,
const TessellatedVertexProc& proc) {
auto center = data.reference_centers[0];
auto radius = data.radii.width;
FML_DCHECK(center == data.reference_centers[1]);
FML_DCHECK(radius == data.radii.height);
FML_DCHECK(data.half_width < 0);
// Quadrant 1 connecting with Quadrant 4:
for (auto& trig : trigs) {
auto offset = trig * radius;
proc({center.x - offset.x, center.y + offset.y});
proc({center.x - offset.x, center.y - offset.y});
}
// The second half of the circle should be iterated in reverse, but
// we can instead iterate forward and swap the x/y values of the
// offset as the angles should be symmetric and thus should generate
// symmetrically reversed trig vectors.
// Quadrant 2 connecting with Quadrant 2:
for (auto& trig : trigs) {
auto offset = trig * radius;
proc({center.x + offset.y, center.y + offset.x});
proc({center.x + offset.y, center.y - offset.x});
}
}
void Tessellator::GenerateStrokedCircle(
const Trigs& trigs,
const EllipticalVertexGenerator::Data& data,
const TessellatedVertexProc& proc) {
auto center = data.reference_centers[0];
FML_DCHECK(center == data.reference_centers[1]);
FML_DCHECK(data.radii.IsSquare());
FML_DCHECK(data.half_width > 0 && data.half_width < data.radii.width);
auto outer_radius = data.radii.width + data.half_width;
auto inner_radius = data.radii.width - data.half_width;
// Zig-zag back and forth between points on the outer circle and the
// inner circle. Both circles are evaluated at the same number of
// quadrant divisions so the points for a given division should match
// 1 for 1 other than their applied radius.
// Quadrant 1:
for (auto& trig : trigs) {
auto outer = trig * outer_radius;
auto inner = trig * inner_radius;
proc({center.x - outer.x, center.y - outer.y});
proc({center.x - inner.x, center.y - inner.y});
}
// The even quadrants of the circle should be iterated in reverse, but
// we can instead iterate forward and swap the x/y values of the
// offset as the angles should be symmetric and thus should generate
// symmetrically reversed trig vectors.
// Quadrant 2:
for (auto& trig : trigs) {
auto outer = trig * outer_radius;
auto inner = trig * inner_radius;
proc({center.x + outer.y, center.y - outer.x});
proc({center.x + inner.y, center.y - inner.x});
}
// Quadrant 3:
for (auto& trig : trigs) {
auto outer = trig * outer_radius;
auto inner = trig * inner_radius;
proc({center.x + outer.x, center.y + outer.y});
proc({center.x + inner.x, center.y + inner.y});
}
// Quadrant 4:
for (auto& trig : trigs) {
auto outer = trig * outer_radius;
auto inner = trig * inner_radius;
proc({center.x - outer.y, center.y + outer.x});
proc({center.x - inner.y, center.y + inner.x});
}
}
void Tessellator::GenerateRoundCapLine(
const Trigs& trigs,
const EllipticalVertexGenerator::Data& data,
const TessellatedVertexProc& proc) {
auto p0 = data.reference_centers[0];
auto p1 = data.reference_centers[1];
auto radius = data.radii.width;
FML_DCHECK(radius == data.radii.height);
FML_DCHECK(data.half_width < 0);
auto along = p1 - p0;
along *= radius / along.GetLength();
auto across = Point(-along.y, along.x);
for (auto& trig : trigs) {
auto relative_along = along * trig.cos;
auto relative_across = across * trig.sin;
proc(p0 - relative_along + relative_across);
proc(p0 - relative_along - relative_across);
}
// The second half of the round caps should be iterated in reverse, but
// we can instead iterate forward and swap the sin/cos values as they
// should be symmetric.
for (auto& trig : trigs) {
auto relative_along = along * trig.sin;
auto relative_across = across * trig.cos;
proc(p1 + relative_along + relative_across);
proc(p1 + relative_along - relative_across);
}
}
void Tessellator::GenerateFilledEllipse(
const Trigs& trigs,
const EllipticalVertexGenerator::Data& data,
const TessellatedVertexProc& proc) {
auto center = data.reference_centers[0];
auto radii = data.radii;
FML_DCHECK(center == data.reference_centers[1]);
FML_DCHECK(data.half_width < 0);
// Quadrant 1 connecting with Quadrant 4:
for (auto& trig : trigs) {
auto offset = trig * radii;
proc({center.x - offset.x, center.y + offset.y});
proc({center.x - offset.x, center.y - offset.y});
}
// The second half of the circle should be iterated in reverse, but
// we can instead iterate forward and swap the x/y values of the
// offset as the angles should be symmetric and thus should generate
// symmetrically reversed trig vectors.
// Quadrant 2 connecting with Quadrant 2:
for (auto& trig : trigs) {
auto offset = Point(trig.sin * radii.width, trig.cos * radii.height);
proc({center.x + offset.x, center.y + offset.y});
proc({center.x + offset.x, center.y - offset.y});
}
}
void Tessellator::GenerateFilledRoundRect(
const Trigs& trigs,
const EllipticalVertexGenerator::Data& data,
const TessellatedVertexProc& proc) {
Scalar left = data.reference_centers[0].x;
Scalar top = data.reference_centers[0].y;
Scalar right = data.reference_centers[1].x;
Scalar bottom = data.reference_centers[1].y;
auto radii = data.radii;
FML_DCHECK(data.half_width < 0);
// Quadrant 1 connecting with Quadrant 4:
for (auto& trig : trigs) {
auto offset = trig * radii;
proc({left - offset.x, bottom + offset.y});
proc({left - offset.x, top - offset.y});
}
// The second half of the round rect should be iterated in reverse, but
// we can instead iterate forward and swap the x/y values of the
// offset as the angles should be symmetric and thus should generate
// symmetrically reversed trig vectors.
// Quadrant 2 connecting with Quadrant 2:
for (auto& trig : trigs) {
auto offset = Point(trig.sin * radii.width, trig.cos * radii.height);
proc({right + offset.x, bottom + offset.y});
proc({right + offset.x, top - offset.y});
}
}
} // namespace impeller