22 class PositionWriter {
24 explicit PositionWriter(std::vector<Point>& points)
25 : points_(points), oversized_() {
29 void AppendVertex(
const Point& point) {
31 oversized_.push_back(point);
33 points_[offset_++] = point;
39 std::pair<size_t, size_t> GetUsedSize()
const {
40 return std::make_pair(offset_, oversized_.size());
43 bool HasOversizedBuffer()
const {
return !oversized_.empty(); }
45 const std::vector<Point>& GetOversizedBuffer()
const {
return oversized_; }
48 std::vector<Point>& points_;
49 std::vector<Point> oversized_;
53 using CapProc = std::function<void(PositionWriter& vtx_builder,
54 const Point& position,
59 using JoinProc = std::function<void(PositionWriter& vtx_builder,
60 const Point& position,
61 const Point& start_offset,
62 const Point& end_offset,
66 class StrokeGenerator {
68 StrokeGenerator(
const Path::Polyline& p_polyline,
69 const Scalar p_stroke_width,
70 const Scalar p_scaled_miter_limit,
71 const JoinProc& p_join_proc,
72 const CapProc& p_cap_proc,
81 void Generate(PositionWriter& vtx_builder) {
82 for (
size_t contour_i = 0; contour_i <
polyline.contours.size();
84 const Path::PolylineContour& contour =
polyline.contours[contour_i];
85 size_t contour_start_point_i, contour_end_point_i;
86 std::tie(contour_start_point_i, contour_end_point_i) =
87 polyline.GetContourPointBounds(contour_i);
89 size_t contour_delta = contour_end_point_i - contour_start_point_i;
90 if (contour_delta == 0) {
102 vtx.position =
polyline.GetPoint(contour_start_point_i - 1);
106 vtx_builder.AppendVertex(
vtx.position);
107 vtx_builder.AppendVertex(
vtx.position);
109 vtx.position =
polyline.GetPoint(contour_start_point_i);
112 vtx_builder.AppendVertex(
vtx.position);
113 vtx_builder.AppendVertex(
vtx.position);
116 if (contour_delta == 1) {
126 offset = ComputeOffset(contour_start_point_i, contour_start_point_i,
127 contour_end_point_i, contour);
128 const Point contour_first_offset =
offset.GetVector();
131 if (!
polyline.contours[contour_i].is_closed) {
133 Vector2(-contour.start_direction.y, contour.start_direction.x) *
136 cap_offset,
scale,
true);
139 for (
size_t contour_component_i = 0;
140 contour_component_i < contour.components.size();
141 contour_component_i++) {
142 const Path::PolylineContour::Component& component =
143 contour.components[contour_component_i];
144 bool is_last_component =
145 contour_component_i == contour.components.size() - 1;
147 size_t component_start_index = component.component_start_index;
148 size_t component_end_index =
149 is_last_component ? contour_end_point_i - 1
150 : contour.components[contour_component_i + 1]
151 .component_start_index;
152 if (component.is_curve) {
153 AddVerticesForCurveComponent(
154 vtx_builder, component_start_index, component_end_index,
155 contour_start_point_i, contour_end_point_i, contour);
157 AddVerticesForLinearComponent(
158 vtx_builder, component_start_index, component_end_index,
159 contour_start_point_i, contour_end_point_i, contour);
164 if (!contour.is_closed) {
166 Vector2(-contour.end_direction.y, contour.end_direction.x) *
169 cap_offset,
scale,
false);
181 SeparatedVector2 ComputeOffset(
const size_t point_i,
182 const size_t contour_start_point_i,
183 const size_t contour_end_point_i,
184 const Path::PolylineContour& contour)
const {
186 if (point_i >= contour_end_point_i) {
187 direction = contour.end_direction;
188 }
else if (point_i <= contour_start_point_i) {
189 direction = -contour.start_direction;
194 return SeparatedVector2(
Vector2{-direction.
y, direction.x},
198 void AddVerticesForLinearComponent(PositionWriter& vtx_builder,
199 const size_t component_start_index,
200 const size_t component_end_index,
201 const size_t contour_start_point_i,
202 const size_t contour_end_point_i,
203 const Path::PolylineContour& contour) {
204 bool is_last_component = component_start_index ==
205 contour.components.back().component_start_index;
207 for (
size_t point_i = component_start_index; point_i < component_end_index;
209 bool is_end_of_component = point_i == component_end_index - 1;
213 vtx.position =
polyline.GetPoint(point_i) + offset_vector;
214 vtx_builder.AppendVertex(
vtx.position);
215 vtx.position =
polyline.GetPoint(point_i) - offset_vector;
216 vtx_builder.AppendVertex(
vtx.position);
220 vtx.position =
polyline.GetPoint(point_i + 1) + offset_vector;
221 vtx_builder.AppendVertex(
vtx.position);
222 vtx.position =
polyline.GetPoint(point_i + 1) - offset_vector;
223 vtx_builder.AppendVertex(
vtx.position);
226 offset = ComputeOffset(point_i + 2, contour_start_point_i,
227 contour_end_point_i, contour);
228 if (!is_last_component && is_end_of_component) {
237 void AddVerticesForCurveComponent(PositionWriter& vtx_builder,
238 const size_t component_start_index,
239 const size_t component_end_index,
240 const size_t contour_start_point_i,
241 const size_t contour_end_point_i,
242 const Path::PolylineContour& contour) {
243 bool is_last_component = component_start_index ==
244 contour.components.back().component_start_index;
246 for (
size_t point_i = component_start_index; point_i < component_end_index;
248 bool is_end_of_component = point_i == component_end_index - 1;
251 vtx_builder.AppendVertex(
vtx.position);
253 vtx_builder.AppendVertex(
vtx.position);
256 offset = ComputeOffset(point_i + 2, contour_start_point_i,
257 contour_end_point_i, contour);
260 if (!is_end_of_component) {
261 constexpr
Scalar kAngleThreshold = 10 *
kPi / 180;
264 constexpr
Scalar kAlignmentThreshold =
270 Scalar angle = kAngleThreshold;
274 while (angle < std::abs(angle_total)) {
275 Scalar signed_angle = angle_total < 0 ? -angle : angle;
279 vtx_builder.AppendVertex(
vtx.position);
281 vtx_builder.AppendVertex(
vtx.position);
283 angle += kAngleThreshold;
290 if (is_end_of_component) {
297 Point last_component_offset = is_last_component
300 vtx.position =
polyline.GetPoint(point_i + 1) + last_component_offset;
301 vtx_builder.AppendVertex(
vtx.position);
302 vtx.position =
polyline.GetPoint(point_i + 1) - last_component_offset;
303 vtx_builder.AppendVertex(
vtx.position);
305 if (!is_last_component) {
323 SolidFillVertexShader::PerVertexData
vtx;
326 void CreateButtCap(PositionWriter& vtx_builder,
327 const Point& position,
332 vtx_builder.AppendVertex(position + orientation);
333 vtx_builder.AppendVertex(position - orientation);
336 void CreateRoundCap(PositionWriter& vtx_builder,
337 const Point& position,
345 CubicPathComponent arc;
347 arc = CubicPathComponent(
352 arc = CubicPathComponent(
359 vtx_builder.AppendVertex(
vtx);
360 vtx = position - orientation;
361 vtx_builder.AppendVertex(
vtx);
364 for (
size_t i = 1; i < line_count; i++) {
365 Point point = arc.Solve(i / line_count);
366 vtx = position + point;
367 vtx_builder.AppendVertex(
vtx);
368 vtx = position + (-point).Reflect(forward_normal);
369 vtx_builder.AppendVertex(
vtx);
372 Point point = arc.p2;
373 vtx = position + point;
374 vtx_builder.AppendVertex(position + point);
375 vtx = position + (-point).Reflect(forward_normal);
376 vtx_builder.AppendVertex(
vtx);
379 void CreateSquareCap(PositionWriter& vtx_builder,
380 const Point& position,
388 vtx_builder.AppendVertex(
vtx);
389 vtx = position - orientation;
390 vtx_builder.AppendVertex(
vtx);
391 vtx = position + orientation + forward;
392 vtx_builder.AppendVertex(
vtx);
393 vtx = position - orientation + forward;
394 vtx_builder.AppendVertex(
vtx);
397 Scalar CreateBevelAndGetDirection(PositionWriter& vtx_builder,
398 const Point& position,
399 const Point& start_offset,
400 const Point& end_offset) {
402 vtx_builder.AppendVertex(
vtx);
404 Scalar dir = start_offset.Cross(end_offset) > 0 ? -1 : 1;
405 vtx = position + start_offset * dir;
406 vtx_builder.AppendVertex(
vtx);
407 vtx = position + end_offset * dir;
408 vtx_builder.AppendVertex(
vtx);
413 void CreateMiterJoin(PositionWriter& vtx_builder,
414 const Point& position,
415 const Point& start_offset,
416 const Point& end_offset,
423 Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2;
428 Scalar direction = CreateBevelAndGetDirection(vtx_builder, position,
429 start_offset, end_offset);
431 Point miter_point = (((start_offset + end_offset) / 2) / alignment);
432 if (miter_point.GetDistanceSquared({0, 0}) > miter_limit * miter_limit) {
437 vtx_builder.AppendVertex(position + miter_point * direction);
440 void CreateRoundJoin(PositionWriter& vtx_builder,
441 const Point& position,
442 const Point& start_offset,
443 const Point& end_offset,
450 Scalar alignment = 1 - (start_normal.Dot(end_normal) + 1) / 2;
455 Scalar direction = CreateBevelAndGetDirection(vtx_builder, position,
456 start_offset, end_offset);
459 (start_offset + end_offset).Normalize() * start_offset.
GetLength();
462 Point middle_handle = middle +
Point(-middle.y, middle.x) *
464 alignment * direction;
465 Point start_handle = start_offset +
Point(start_offset.y, -start_offset.x) *
467 alignment * direction;
469 CubicPathComponent arc(start_offset, start_handle, middle_handle, middle);
471 for (
size_t i = 1; i < line_count; i++) {
472 Point point = arc.Solve(i / line_count);
473 vtx_builder.AppendVertex(position + point * direction);
474 vtx_builder.AppendVertex(position +
475 (-point * direction).Reflect(middle_normal));
477 vtx_builder.AppendVertex(position + arc.p2 * direction);
478 vtx_builder.AppendVertex(position +
479 (-arc.p2 * direction).Reflect(middle_normal));
482 void CreateBevelJoin(PositionWriter& vtx_builder,
483 const Point& position,
484 const Point& start_offset,
485 const Point& end_offset,
488 CreateBevelAndGetDirection(vtx_builder, position, start_offset, end_offset);
491 void CreateSolidStrokeVertices(PositionWriter& vtx_builder,
500 stroke_generator.Generate(vtx_builder);
505 JoinProc GetJoinProc(
Join stroke_join) {
506 switch (stroke_join) {
508 return &CreateBevelJoin;
510 return &CreateMiterJoin;
512 return &CreateRoundJoin;
516 CapProc GetCapProc(
Cap stroke_cap) {
517 switch (stroke_cap) {
519 return &CreateButtCap;
521 return &CreateRoundCap;
523 return &CreateSquareCap;
528 std::vector<Point> StrokePathGeometry::GenerateSolidStrokeVertices(
536 JoinProc
join_proc = GetJoinProc(stroke_join);
537 CapProc
cap_proc = GetCapProc(stroke_cap);
540 std::vector<Point> points(4096);
541 PositionWriter vtx_builder(points);
542 stroke_generator.Generate(vtx_builder);
546 StrokePathGeometry::StrokePathGeometry(
const Path& path,
553 miter_limit_(miter_limit),
554 stroke_cap_(stroke_cap),
555 stroke_join_(stroke_join) {}
560 return stroke_width_;
583 if (stroke_width_ < 0.0) {
587 if (max_basis == 0) {
597 PositionWriter position_writer(
603 miter_limit_ * stroke_width_ * 0.5f,
604 GetJoinProc(stroke_join_), GetCapProc(stroke_cap_),
607 const auto [arena_length, oversized_length] = position_writer.GetUsedSize();
608 if (!position_writer.HasOversizedBuffer()) {
611 arena_length *
sizeof(
Point),
alignof(
Point));
617 .vertex_count = arena_length,
623 const std::vector<Point>& oversized_data =
624 position_writer.GetOversizedBuffer();
627 (arena_length + oversized_length) *
sizeof(
Point),
633 arena_length *
sizeof(
Point)
637 oversized_data.data(),
638 oversized_data.size() *
sizeof(
Point)
646 .vertex_count = arena_length + oversized_length,
657 std::optional<Rect> StrokePathGeometry::GetCoverage(
660 if (!path_bounds.has_value()) {
666 max_radius = max_radius *
kSqrt2;
669 max_radius = std::max(max_radius, miter_limit_ * 0.5f);
672 if (max_basis == 0) {
677 max_radius *= std::max(stroke_width_, min_size);
678 return path_bounds->Expand(max_radius).TransformBounds(
transform);
HostBuffer & GetTransientsBuffer() const
Retrieve the currnent host buffer for transient storage.
Tessellator & GetTessellator() const
Matrix GetShaderTransform(const RenderPass &pass) const
Get the vertex shader transform used for drawing this Entity.
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
static Scalar ComputeStrokeAlphaCoverage(const Matrix &entity, Scalar stroke_width)
Compute an alpha value to simulate lower coverage of fractional pixel strokes.
constexpr static const Scalar kArcApproximationMagic
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
std::optional< Rect > GetBoundingBox() const
Render passes encode render commands directed as one specific render target into an underlying comman...
~StrokePathGeometry() override
Scalar GetMiterLimit() const
Scalar GetStrokeWidth() const
Scalar ComputeAlphaCoverage(const Matrix &transform) const override
Join GetStrokeJoin() const
std::vector< Point > & GetStrokePointCache()
Retrieve a pre-allocated arena of kPointArenaSize points.
Path::Polyline CreateTempPolyline(const Path &path, Scalar tolerance)
Create a temporary polyline. Only one per-process can exist at a time.
@ kNone
Does not use the index buffer.
static constexpr size_t kPointArenaSize
The size of the point arena buffer stored on the tessellator.
static constexpr Scalar kMinStrokeSize
constexpr bool ScalarNearlyEqual(Scalar x, Scalar y, Scalar tolerance=kEhCloseEnough)
Scalar ComputeCubicSubdivisions(Scalar scale_factor, Point p0, Point p1, Point p2, Point p3)
SeparatedVector2 previous_offset
const JoinProc & join_proc
SolidFillVertexShader::PerVertexData vtx
const Scalar scaled_miter_limit
const Scalar stroke_width
const Path::Polyline & polyline
A 4x4 matrix using column-major storage.
constexpr Scalar GetMaxBasisLengthXY() const
constexpr Type GetLength() const
constexpr TPoint Normalize() const