21 class PositionWriter {
23 explicit PositionWriter(std::vector<Point>& points)
24 : points_(points), oversized_() {
28 void AppendVertex(
const Point& point) {
30 oversized_.push_back(point);
32 points_[offset_++] = point;
38 std::pair<size_t, size_t> GetUsedSize()
const {
39 return std::make_pair(offset_, oversized_.size());
42 bool HasOversizedBuffer()
const {
return !oversized_.empty(); }
44 const std::vector<Point>& GetOversizedBuffer()
const {
return oversized_; }
47 std::vector<Point>& points_;
48 std::vector<Point> oversized_;
52 using CapProc = std::function<void(PositionWriter& vtx_builder,
53 const Point& position,
58 using JoinProc = std::function<void(PositionWriter& vtx_builder,
59 const Point& position,
60 const Point& start_offset,
61 const Point& end_offset,
65 class StrokeGenerator {
67 StrokeGenerator(
const Path::Polyline& p_polyline,
68 const Scalar p_stroke_width,
69 const Scalar p_scaled_miter_limit,
70 const JoinProc& p_join_proc,
71 const CapProc& p_cap_proc,
80 void Generate(PositionWriter& vtx_builder) {
81 for (
size_t contour_i = 0; contour_i <
polyline.contours.size();
83 const Path::PolylineContour& contour =
polyline.contours[contour_i];
84 size_t contour_start_point_i, contour_end_point_i;
85 std::tie(contour_start_point_i, contour_end_point_i) =
86 polyline.GetContourPointBounds(contour_i);
88 size_t contour_delta = contour_end_point_i - contour_start_point_i;
89 if (contour_delta == 1) {
96 }
else if (contour_delta == 0) {
101 offset = ComputeOffset(contour_start_point_i, contour_start_point_i,
102 contour_end_point_i, contour);
103 const Point contour_first_offset =
offset.GetVector();
113 vtx.position =
polyline.GetPoint(contour_start_point_i - 1);
117 vtx_builder.AppendVertex(
vtx.position);
118 vtx_builder.AppendVertex(
vtx.position);
120 vtx.position =
polyline.GetPoint(contour_start_point_i);
123 vtx_builder.AppendVertex(
vtx.position);
124 vtx_builder.AppendVertex(
vtx.position);
128 if (!
polyline.contours[contour_i].is_closed) {
130 Vector2(-contour.start_direction.y, contour.start_direction.x) *
133 cap_offset,
scale,
true);
136 for (
size_t contour_component_i = 0;
137 contour_component_i < contour.components.size();
138 contour_component_i++) {
139 const Path::PolylineContour::Component& component =
140 contour.components[contour_component_i];
141 bool is_last_component =
142 contour_component_i == contour.components.size() - 1;
144 size_t component_start_index = component.component_start_index;
145 size_t component_end_index =
146 is_last_component ? contour_end_point_i - 1
147 : contour.components[contour_component_i + 1]
148 .component_start_index;
149 if (component.is_curve) {
150 AddVerticesForCurveComponent(
151 vtx_builder, component_start_index, component_end_index,
152 contour_start_point_i, contour_end_point_i, contour);
154 AddVerticesForLinearComponent(
155 vtx_builder, component_start_index, component_end_index,
156 contour_start_point_i, contour_end_point_i, contour);
161 if (!contour.is_closed) {
163 Vector2(-contour.end_direction.y, contour.end_direction.x) *
166 cap_offset,
scale,
false);
178 SeparatedVector2 ComputeOffset(
const size_t point_i,
179 const size_t contour_start_point_i,
180 const size_t contour_end_point_i,
181 const Path::PolylineContour& contour)
const {
183 if (point_i >= contour_end_point_i) {
184 direction = contour.end_direction;
185 }
else if (point_i <= contour_start_point_i) {
186 direction = -contour.start_direction;
191 return SeparatedVector2(
Vector2{-direction.
y, direction.x},
195 void AddVerticesForLinearComponent(PositionWriter& vtx_builder,
196 const size_t component_start_index,
197 const size_t component_end_index,
198 const size_t contour_start_point_i,
199 const size_t contour_end_point_i,
200 const Path::PolylineContour& contour) {
201 bool is_last_component = component_start_index ==
202 contour.components.back().component_start_index;
204 for (
size_t point_i = component_start_index; point_i < component_end_index;
206 bool is_end_of_component = point_i == component_end_index - 1;
210 vtx.position =
polyline.GetPoint(point_i) + offset_vector;
211 vtx_builder.AppendVertex(
vtx.position);
212 vtx.position =
polyline.GetPoint(point_i) - offset_vector;
213 vtx_builder.AppendVertex(
vtx.position);
217 vtx.position =
polyline.GetPoint(point_i + 1) + offset_vector;
218 vtx_builder.AppendVertex(
vtx.position);
219 vtx.position =
polyline.GetPoint(point_i + 1) - offset_vector;
220 vtx_builder.AppendVertex(
vtx.position);
223 offset = ComputeOffset(point_i + 2, contour_start_point_i,
224 contour_end_point_i, contour);
225 if (!is_last_component && is_end_of_component) {
234 void AddVerticesForCurveComponent(PositionWriter& vtx_builder,
235 const size_t component_start_index,
236 const size_t component_end_index,
237 const size_t contour_start_point_i,
238 const size_t contour_end_point_i,
239 const Path::PolylineContour& contour) {
240 bool is_last_component = component_start_index ==
241 contour.components.back().component_start_index;
243 for (
size_t point_i = component_start_index; point_i < component_end_index;
245 bool is_end_of_component = point_i == component_end_index - 1;
248 vtx_builder.AppendVertex(
vtx.position);
250 vtx_builder.AppendVertex(
vtx.position);
253 offset = ComputeOffset(point_i + 2, contour_start_point_i,
254 contour_end_point_i, contour);
257 if (!is_end_of_component) {
258 constexpr
Scalar kAngleThreshold = 10 *
kPi / 180;
261 constexpr
Scalar kAlignmentThreshold =
267 Scalar angle = kAngleThreshold;
271 while (angle < std::abs(angle_total)) {
272 Scalar signed_angle = angle_total < 0 ? -angle : angle;
276 vtx_builder.AppendVertex(
vtx.position);
278 vtx_builder.AppendVertex(
vtx.position);
280 angle += kAngleThreshold;
287 if (is_end_of_component) {
294 Point last_component_offset = is_last_component
297 vtx.position =
polyline.GetPoint(point_i + 1) + last_component_offset;
298 vtx_builder.AppendVertex(
vtx.position);
299 vtx.position =
polyline.GetPoint(point_i + 1) - last_component_offset;
300 vtx_builder.AppendVertex(
vtx.position);
302 if (!is_last_component) {
320 SolidFillVertexShader::PerVertexData
vtx;
323 void CreateButtCap(PositionWriter& vtx_builder,
324 const Point& position,
329 vtx_builder.AppendVertex(position + orientation);
330 vtx_builder.AppendVertex(position - orientation);
333 void CreateRoundCap(PositionWriter& vtx_builder,
334 const Point& position,
342 CubicPathComponent arc;
344 arc = CubicPathComponent(
349 arc = CubicPathComponent(
356 vtx_builder.AppendVertex(
vtx);
357 vtx = position - orientation;
358 vtx_builder.AppendVertex(
vtx);
361 for (
size_t i = 1; i < line_count; i++) {
362 Point point = arc.Solve(i / line_count);
363 vtx = position + point;
364 vtx_builder.AppendVertex(
vtx);
365 vtx = position + (-point).Reflect(forward_normal);
366 vtx_builder.AppendVertex(
vtx);
369 Point point = arc.p2;
370 vtx = position + point;
371 vtx_builder.AppendVertex(position + point);
372 vtx = position + (-point).Reflect(forward_normal);
373 vtx_builder.AppendVertex(
vtx);
376 void CreateSquareCap(PositionWriter& vtx_builder,
377 const Point& position,
385 vtx_builder.AppendVertex(
vtx);
386 vtx = position - orientation;
387 vtx_builder.AppendVertex(
vtx);
388 vtx = position + orientation + forward;
389 vtx_builder.AppendVertex(
vtx);
390 vtx = position - orientation + forward;
391 vtx_builder.AppendVertex(
vtx);
394 Scalar CreateBevelAndGetDirection(PositionWriter& vtx_builder,
395 const Point& position,
396 const Point& start_offset,
397 const Point& end_offset) {
399 vtx_builder.AppendVertex(
vtx);
401 Scalar dir = start_offset.Cross(end_offset) > 0 ? -1 : 1;
402 vtx = position + start_offset * dir;
403 vtx_builder.AppendVertex(
vtx);
404 vtx = position + end_offset * dir;
405 vtx_builder.AppendVertex(
vtx);
410 void CreateMiterJoin(PositionWriter& vtx_builder,
411 const Point& position,
412 const Point& start_offset,
413 const Point& end_offset,
420 Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2;
425 Scalar direction = CreateBevelAndGetDirection(vtx_builder, position,
426 start_offset, end_offset);
428 Point miter_point = (((start_offset + end_offset) / 2) / alignment);
429 if (miter_point.GetDistanceSquared({0, 0}) > miter_limit * miter_limit) {
434 vtx_builder.AppendVertex(position + miter_point * direction);
437 void CreateRoundJoin(PositionWriter& vtx_builder,
438 const Point& position,
439 const Point& start_offset,
440 const Point& end_offset,
447 Scalar alignment = 1 - (start_normal.Dot(end_normal) + 1) / 2;
452 Scalar direction = CreateBevelAndGetDirection(vtx_builder, position,
453 start_offset, end_offset);
456 (start_offset + end_offset).Normalize() * start_offset.
GetLength();
459 Point middle_handle = middle +
Point(-middle.y, middle.x) *
461 alignment * direction;
462 Point start_handle = start_offset +
Point(start_offset.y, -start_offset.x) *
464 alignment * direction;
466 CubicPathComponent arc(start_offset, start_handle, middle_handle, middle);
468 for (
size_t i = 1; i < line_count; i++) {
469 Point point = arc.Solve(i / line_count);
470 vtx_builder.AppendVertex(position + point * direction);
471 vtx_builder.AppendVertex(position +
472 (-point * direction).Reflect(middle_normal));
474 vtx_builder.AppendVertex(position + arc.p2 * direction);
475 vtx_builder.AppendVertex(position +
476 (-arc.p2 * direction).Reflect(middle_normal));
479 void CreateBevelJoin(PositionWriter& vtx_builder,
480 const Point& position,
481 const Point& start_offset,
482 const Point& end_offset,
485 CreateBevelAndGetDirection(vtx_builder, position, start_offset, end_offset);
488 void CreateSolidStrokeVertices(PositionWriter& vtx_builder,
497 stroke_generator.Generate(vtx_builder);
502 JoinProc GetJoinProc(
Join stroke_join) {
503 switch (stroke_join) {
505 return &CreateBevelJoin;
507 return &CreateMiterJoin;
509 return &CreateRoundJoin;
513 CapProc GetCapProc(
Cap stroke_cap) {
514 switch (stroke_cap) {
516 return &CreateButtCap;
518 return &CreateRoundCap;
520 return &CreateSquareCap;
525 std::vector<Point> StrokePathGeometry::GenerateSolidStrokeVertices(
533 JoinProc
join_proc = GetJoinProc(stroke_join);
534 CapProc
cap_proc = GetCapProc(stroke_cap);
537 std::vector<Point> points(4096);
538 PositionWriter vtx_builder(points);
539 stroke_generator.Generate(vtx_builder);
543 StrokePathGeometry::StrokePathGeometry(
const Path& path,
550 miter_limit_(miter_limit),
551 stroke_cap_(stroke_cap),
552 stroke_join_(stroke_join) {}
557 return stroke_width_;
580 if (stroke_width_ < 0.0) {
584 if (max_basis == 0) {
594 PositionWriter position_writer(
600 miter_limit_ * stroke_width_ * 0.5f,
601 GetJoinProc(stroke_join_), GetCapProc(stroke_cap_),
604 const auto [arena_length, oversized_length] = position_writer.GetUsedSize();
605 if (!position_writer.HasOversizedBuffer()) {
608 arena_length *
sizeof(
Point),
alignof(
Point));
614 .vertex_count = arena_length,
620 const std::vector<Point>& oversized_data =
621 position_writer.GetOversizedBuffer();
624 (arena_length + oversized_length) *
sizeof(
Point),
630 arena_length *
sizeof(
Point)
634 oversized_data.data(),
635 oversized_data.size() *
sizeof(
Point)
643 .vertex_count = arena_length + oversized_length,
654 std::optional<Rect> StrokePathGeometry::GetCoverage(
657 if (!path_bounds.has_value()) {
663 max_radius = max_radius *
kSqrt2;
666 max_radius = std::max(max_radius, miter_limit_ * 0.5f);
669 if (max_basis == 0) {
674 max_radius *= std::max(stroke_width_, min_size);
675 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