| Flutter Impeller |

stroke_path_geometry.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "impeller/entity/geometry/stroke_path_geometry.h"
6
7 #include "impeller/geometry/path_builder.h"
8
9 namespace impeller {
10
11 StrokePathGeometry::StrokePathGeometry(Path path,
12 Scalar stroke_width,
13 Scalar miter_limit,
14 Cap stroke_cap,
15 Join stroke_join)
16 : path_(std::move(path)),
17 stroke_width_(stroke_width),
18 miter_limit_(miter_limit),
19 stroke_cap_(stroke_cap),
20 stroke_join_(stroke_join) {}
21
22 StrokePathGeometry::~StrokePathGeometry() = default;
23
24 Scalar StrokePathGeometry::GetStrokeWidth() const {
25 return stroke_width_;
26 }
27
28 Scalar StrokePathGeometry::GetMiterLimit() const {
29 return miter_limit_;
30 }
31
32 Cap StrokePathGeometry::GetStrokeCap() const {
33 return stroke_cap_;
34 }
35
36 Join StrokePathGeometry::GetStrokeJoin() const {
37 return stroke_join_;
38 }
39
40 // static
41 Scalar StrokePathGeometry::CreateBevelAndGetDirection(
42 VertexBufferBuilder<SolidFillVertexShader::PerVertexData>& vtx_builder,
43 const Point& position,
44 const Point& start_offset,
45 const Point& end_offset) {
46 SolidFillVertexShader::PerVertexData vtx;
47 vtx.position = position;
48 vtx_builder.AppendVertex(vtx);
49
50 Scalar dir = start_offset.Cross(end_offset) > 0 ? -1 : 1;
51 vtx.position = position + start_offset * dir;
52 vtx_builder.AppendVertex(vtx);
53 vtx.position = position + end_offset * dir;
54 vtx_builder.AppendVertex(vtx);
55
56 return dir;
57 }
58
59 // static
60 StrokePathGeometry::JoinProc StrokePathGeometry::GetJoinProc(Join stroke_join) {
61 using VS = SolidFillVertexShader;
62 StrokePathGeometry::JoinProc join_proc;
63 switch (stroke_join) {
64 case Join::kBevel:
65 join_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
66 const Point& position, const Point& start_offset,
67 const Point& end_offset, Scalar miter_limit,
68 Scalar scale) {
69 CreateBevelAndGetDirection(vtx_builder, position, start_offset,
70 end_offset);
71 };
72 break;
73 case Join::kMiter:
74 join_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
75 const Point& position, const Point& start_offset,
76 const Point& end_offset, Scalar miter_limit,
77 Scalar scale) {
78 Point start_normal = start_offset.Normalize();
79 Point end_normal = end_offset.Normalize();
80
81 // 1 for no joint (straight line), 0 for max joint (180 degrees).
82 Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2;
83 if (ScalarNearlyEqual(alignment, 1)) {
84 return;
85 }
86
87 Scalar dir = CreateBevelAndGetDirection(vtx_builder, position,
88 start_offset, end_offset);
89
90 Point miter_point = (start_offset + end_offset) / 2 / alignment;
91 if (miter_point.GetDistanceSquared({0, 0}) >
92 miter_limit * miter_limit) {
93 return; // Convert to bevel when we exceed the miter limit.
94 }
95
96 // Outer miter point.
97 VS::PerVertexData vtx;
98 vtx.position = position + miter_point * dir;
99 vtx_builder.AppendVertex(vtx);
100 };
101 break;
102 case Join::kRound:
103 join_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
104 const Point& position, const Point& start_offset,
105 const Point& end_offset, Scalar miter_limit,
106 Scalar scale) {
107 Point start_normal = start_offset.Normalize();
108 Point end_normal = end_offset.Normalize();
109
110 // 0 for no joint (straight line), 1 for max joint (180 degrees).
111 Scalar alignment = 1 - (start_normal.Dot(end_normal) + 1) / 2;
112 if (ScalarNearlyEqual(alignment, 0)) {
113 return;
114 }
115
116 Scalar dir = CreateBevelAndGetDirection(vtx_builder, position,
117 start_offset, end_offset);
118
119 Point middle =
120 (start_offset + end_offset).Normalize() * start_offset.GetLength();
121 Point middle_normal = middle.Normalize();
122
123 Point middle_handle = middle + Point(-middle.y, middle.x) *
124 PathBuilder::kArcApproximationMagic *
125 alignment * dir;
126 Point start_handle =
127 start_offset + Point(start_offset.y, -start_offset.x) *
128 PathBuilder::kArcApproximationMagic * alignment *
129 dir;
130
131 std::vector<Point> arc_points;
132 CubicPathComponent(start_offset, start_handle, middle_handle, middle)
133 .AppendPolylinePoints(scale, arc_points);
134
135 VS::PerVertexData vtx;
136 for (const auto& point : arc_points) {
137 vtx.position = position + point * dir;
138 vtx_builder.AppendVertex(vtx);
139 vtx.position = position + (-point * dir).Reflect(middle_normal);
140 vtx_builder.AppendVertex(vtx);
141 }
142 };
143 break;
144 }
145 return join_proc;
146 }
147
148 // static
149 StrokePathGeometry::CapProc StrokePathGeometry::GetCapProc(Cap stroke_cap) {
150 using VS = SolidFillVertexShader;
151 StrokePathGeometry::CapProc cap_proc;
152 switch (stroke_cap) {
153 case Cap::kButt:
154 cap_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
155 const Point& position, const Point& offset, Scalar scale,
156 bool reverse) {
157 Point orientation = offset * (reverse ? -1 : 1);
158 VS::PerVertexData vtx;
159 vtx.position = position + orientation;
160 vtx_builder.AppendVertex(vtx);
161 vtx.position = position - orientation;
162 vtx_builder.AppendVertex(vtx);
163 };
164 break;
165 case Cap::kRound:
166 cap_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
167 const Point& position, const Point& offset, Scalar scale,
168 bool reverse) {
169 Point orientation = offset * (reverse ? -1 : 1);
170
171 VS::PerVertexData vtx;
172
173 Point forward(offset.y, -offset.x);
174 Point forward_normal = forward.Normalize();
175
176 CubicPathComponent arc;
177 if (reverse) {
178 arc = CubicPathComponent(
179 forward,
180 forward + orientation * PathBuilder::kArcApproximationMagic,
181 orientation + forward * PathBuilder::kArcApproximationMagic,
182 orientation);
183 } else {
184 arc = CubicPathComponent(
185 orientation,
186 orientation + forward * PathBuilder::kArcApproximationMagic,
187 forward + orientation * PathBuilder::kArcApproximationMagic,
188 forward);
189 }
190
191 vtx.position = position + orientation;
192 vtx_builder.AppendVertex(vtx);
193 vtx.position = position - orientation;
194 vtx_builder.AppendVertex(vtx);
195 std::vector<Point> arc_points;
196 arc.AppendPolylinePoints(scale, arc_points);
197 for (const auto& point : arc_points) {
198 vtx.position = position + point;
199 vtx_builder.AppendVertex(vtx);
200 vtx.position = position + (-point).Reflect(forward_normal);
201 vtx_builder.AppendVertex(vtx);
202 }
203 };
204 break;
205 case Cap::kSquare:
206 cap_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
207 const Point& position, const Point& offset, Scalar scale,
208 bool reverse) {
209 Point orientation = offset * (reverse ? -1 : 1);
210
211 VS::PerVertexData vtx;
212
213 Point forward(offset.y, -offset.x);
214
215 vtx.position = position + orientation;
216 vtx_builder.AppendVertex(vtx);
217 vtx.position = position - orientation;
218 vtx_builder.AppendVertex(vtx);
219 vtx.position = position + orientation + forward;
220 vtx_builder.AppendVertex(vtx);
221 vtx.position = position - orientation + forward;
222 vtx_builder.AppendVertex(vtx);
223 };
224 break;
225 }
226 return cap_proc;
227 }
228
229 // static
230 VertexBufferBuilder<SolidFillVertexShader::PerVertexData>
231 StrokePathGeometry::CreateSolidStrokeVertices(
232 const Path& path,
233 Scalar stroke_width,
234 Scalar scaled_miter_limit,
235 const StrokePathGeometry::JoinProc& join_proc,
236 const StrokePathGeometry::CapProc& cap_proc,
237 Scalar scale) {
238 VertexBufferBuilder<VS::PerVertexData> vtx_builder;
239 auto point_buffer = std::make_unique<std::vector<Point>>();
240 // 512 is an arbitrary choice that should be big enough for most paths without
241 // needing to reallocate. If we have motivating benchmarks we should raise or
242 // lower this number, cause dnfield just made it up!
243 point_buffer->reserve(512);
244 auto polyline = path.CreatePolyline(scale, std::move(point_buffer));
245
246 VS::PerVertexData vtx;
247
248 // Offset state.
249 Point offset;
250 Point previous_offset; // Used for computing joins.
251
252 // Computes offset by calculating the direction from point_i - 1 to point_i if
253 // point_i is within `contour_start_point_i` and `contour_end_point_i`;
254 // Otherwise, it uses direction from contour.
255 auto compute_offset = [&polyline, &offset, &previous_offset, &stroke_width](
256 const size_t point_i,
257 const size_t contour_start_point_i,
258 const size_t contour_end_point_i,
259 const Path::PolylineContour& contour) {
260 Point direction;
261 if (point_i >= contour_end_point_i) {
262 direction = contour.end_direction;
263 } else if (point_i <= contour_start_point_i) {
264 direction = -contour.start_direction;
265 } else {
266 direction = (polyline.GetPoint(point_i) - polyline.GetPoint(point_i - 1))
267 .Normalize();
268 }
269 previous_offset = offset;
270 offset = Vector2{-direction.y, direction.x} * stroke_width * 0.5;
271 };
272
273 auto add_vertices_for_linear_component =
274 [&vtx_builder, &offset, &previous_offset, &vtx, &polyline,
275 &compute_offset, scaled_miter_limit, scale, &join_proc](
276 const size_t component_start_index, const size_t component_end_index,
277 const size_t contour_start_point_i, const size_t contour_end_point_i,
278 const Path::PolylineContour& contour) {
279 auto is_last_component =
280 component_start_index ==
281 contour.components.back().component_start_index;
282
283 for (size_t point_i = component_start_index;
284 point_i < component_end_index; point_i++) {
285 auto is_end_of_component = point_i == component_end_index - 1;
286 vtx.position = polyline.GetPoint(point_i) + offset;
287 vtx_builder.AppendVertex(vtx);
288 vtx.position = polyline.GetPoint(point_i) - offset;
289 vtx_builder.AppendVertex(vtx);
290
291 // For line components, two additional points need to be appended
292 // prior to appending a join connecting the next component.
293 vtx.position = polyline.GetPoint(point_i + 1) + offset;
294 vtx_builder.AppendVertex(vtx);
295 vtx.position = polyline.GetPoint(point_i + 1) - offset;
296 vtx_builder.AppendVertex(vtx);
297
298 compute_offset(point_i + 2, contour_start_point_i,
299 contour_end_point_i, contour);
300 if (!is_last_component && is_end_of_component) {
301 // Generate join from the current line to the next line.
302 join_proc(vtx_builder, polyline.GetPoint(point_i + 1),
303 previous_offset, offset, scaled_miter_limit, scale);
304 }
305 }
306 };
307
308 auto add_vertices_for_curve_component =
309 [&vtx_builder, &offset, &previous_offset, &vtx, &polyline,
310 &compute_offset, scaled_miter_limit, scale, &join_proc](
311 const size_t component_start_index, const size_t component_end_index,
312 const size_t contour_start_point_i, const size_t contour_end_point_i,
313 const Path::PolylineContour& contour) {
314 auto is_last_component =
315 component_start_index ==
316 contour.components.back().component_start_index;
317
318 for (size_t point_i = component_start_index;
319 point_i < component_end_index; point_i++) {
320 auto is_end_of_component = point_i == component_end_index - 1;
321
322 vtx.position = polyline.GetPoint(point_i) + offset;
323 vtx_builder.AppendVertex(vtx);
324 vtx.position = polyline.GetPoint(point_i) - offset;
325 vtx_builder.AppendVertex(vtx);
326
327 compute_offset(point_i + 2, contour_start_point_i,
328 contour_end_point_i, contour);
329 // For curve components, the polyline is detailed enough such that
330 // it can avoid worrying about joins altogether.
331 if (is_end_of_component) {
332 vtx.position = polyline.GetPoint(point_i + 1) + offset;
333 vtx_builder.AppendVertex(vtx);
334 vtx.position = polyline.GetPoint(point_i + 1) - offset;
335 vtx_builder.AppendVertex(vtx);
336 // Generate join from the current line to the next line.
337 if (!is_last_component) {
338 join_proc(vtx_builder, polyline.GetPoint(point_i + 1),
339 previous_offset, offset, scaled_miter_limit, scale);
340 }
341 }
342 }
343 };
344
345 for (size_t contour_i = 0; contour_i < polyline.contours.size();
346 contour_i++) {
347 auto contour = polyline.contours[contour_i];
348 size_t contour_start_point_i, contour_end_point_i;
349 std::tie(contour_start_point_i, contour_end_point_i) =
350 polyline.GetContourPointBounds(contour_i);
351
352 switch (contour_end_point_i - contour_start_point_i) {
353 case 1: {
354 Point p = polyline.GetPoint(contour_start_point_i);
355 cap_proc(vtx_builder, p, {-stroke_width * 0.5f, 0}, scale, false);
356 cap_proc(vtx_builder, p, {stroke_width * 0.5f, 0}, scale, false);
357 continue;
358 }
359 case 0:
360 continue; // This contour has no renderable content.
361 default:
362 break;
363 }
364
365 compute_offset(contour_start_point_i, contour_start_point_i,
366 contour_end_point_i, contour);
367 const Point contour_first_offset = offset;
368
369 if (contour_i > 0) {
370 // This branch only executes when we've just finished drawing a contour
371 // and are switching to a new one.
372 // We're drawing a triangle strip, so we need to "pick up the pen" by
373 // appending two vertices at the end of the previous contour and two
374 // vertices at the start of the new contour (thus connecting the two
375 // contours with two zero volume triangles, which will be discarded by
376 // the rasterizer).
377 vtx.position = polyline.GetPoint(contour_start_point_i - 1);
378 // Append two vertices when "picking up" the pen so that the triangle
379 // drawn when moving to the beginning of the new contour will have zero
380 // volume.
381 vtx_builder.AppendVertex(vtx);
382 vtx_builder.AppendVertex(vtx);
383
384 vtx.position = polyline.GetPoint(contour_start_point_i);
385 // Append two vertices at the beginning of the new contour, which
386 // appends two triangles of zero area.
387 vtx_builder.AppendVertex(vtx);
388 vtx_builder.AppendVertex(vtx);
389 }
390
391 // Generate start cap.
392 if (!polyline.contours[contour_i].is_closed) {
393 auto cap_offset =
394 Vector2(-contour.start_direction.y, contour.start_direction.x) *
395 stroke_width * 0.5; // Counterclockwise normal
396 cap_proc(vtx_builder, polyline.GetPoint(contour_start_point_i),
397 cap_offset, scale, true);
398 }
399
400 for (size_t contour_component_i = 0;
401 contour_component_i < contour.components.size();
402 contour_component_i++) {
403 auto component = contour.components[contour_component_i];
404 auto is_last_component =
405 contour_component_i == contour.components.size() - 1;
406
407 auto component_start_index = component.component_start_index;
408 auto component_end_index =
409 is_last_component ? contour_end_point_i - 1
410 : contour.components[contour_component_i + 1]
411 .component_start_index;
412 if (component.is_curve) {
413 add_vertices_for_curve_component(
414 component_start_index, component_end_index, contour_start_point_i,
415 contour_end_point_i, contour);
416 } else {
417 add_vertices_for_linear_component(
418 component_start_index, component_end_index, contour_start_point_i,
419 contour_end_point_i, contour);
420 }
421 }
422
423 // Generate end cap or join.
424 if (!contour.is_closed) {
425 auto cap_offset =
426 Vector2(-contour.end_direction.y, contour.end_direction.x) *
427 stroke_width * 0.5; // Clockwise normal
428 cap_proc(vtx_builder, polyline.GetPoint(contour_end_point_i - 1),
429 cap_offset, scale, false);
430 } else {
431 join_proc(vtx_builder, polyline.GetPoint(contour_start_point_i), offset,
432 contour_first_offset, scaled_miter_limit, scale);
433 }
434 }
435
436 return vtx_builder;
437 }
438
439 GeometryResult StrokePathGeometry::GetPositionBuffer(
440 const ContentContext& renderer,
441 const Entity& entity,
442 RenderPass& pass) const {
443 if (stroke_width_ < 0.0) {
444 return {};
445 }
446 auto determinant = entity.GetTransform().GetDeterminant();
447 if (determinant == 0) {
448 return {};
449 }
450
451 Scalar min_size = 1.0f / sqrt(std::abs(determinant));
452 Scalar stroke_width = std::max(stroke_width_, min_size);
453
454 auto& host_buffer = pass.GetTransientsBuffer();
455 auto vertex_builder = CreateSolidStrokeVertices(
456 path_, stroke_width, miter_limit_ * stroke_width_ * 0.5,
457 GetJoinProc(stroke_join_), GetCapProc(stroke_cap_),
458 entity.GetTransform().GetMaxBasisLength());
459
460 return GeometryResult{
461 .type = PrimitiveType::kTriangleStrip,
462 .vertex_buffer = vertex_builder.CreateVertexBuffer(host_buffer),
463 .transform = pass.GetOrthographicTransform() * entity.GetTransform(),
464 .prevent_overdraw = true,
465 };
466 }
467
468 GeometryResult StrokePathGeometry::GetPositionUVBuffer(
469 Rect texture_coverage,
470 Matrix effect_transform,
471 const ContentContext& renderer,
472 const Entity& entity,
473 RenderPass& pass) const {
474 if (stroke_width_ < 0.0) {
475 return {};
476 }
477 auto determinant = entity.GetTransform().GetDeterminant();
478 if (determinant == 0) {
479 return {};
480 }
481
482 Scalar min_size = 1.0f / sqrt(std::abs(determinant));
483 Scalar stroke_width = std::max(stroke_width_, min_size);
484
485 auto& host_buffer = pass.GetTransientsBuffer();
486 auto stroke_builder = CreateSolidStrokeVertices(
487 path_, stroke_width, miter_limit_ * stroke_width_ * 0.5,
488 GetJoinProc(stroke_join_), GetCapProc(stroke_cap_),
489 entity.GetTransform().GetMaxBasisLength());
490 auto vertex_builder = ComputeUVGeometryCPU(
491 stroke_builder, {0, 0}, texture_coverage.GetSize(), effect_transform);
492
493 return GeometryResult{
494 .type = PrimitiveType::kTriangleStrip,
495 .vertex_buffer = vertex_builder.CreateVertexBuffer(host_buffer),
496 .transform = pass.GetOrthographicTransform() * entity.GetTransform(),
497 .prevent_overdraw = true,
498 };
499 }
500
501 GeometryVertexType StrokePathGeometry::GetVertexType() const {
502 return GeometryVertexType::kPosition;
503 }
504
505 std::optional<Rect> StrokePathGeometry::GetCoverage(
506 const Matrix& transform) const {
507 auto path_bounds = path_.GetBoundingBox();
508 if (!path_bounds.has_value()) {
509 return std::nullopt;
510 }
511 auto path_coverage = path_bounds->TransformBounds(transform);
512
513 Scalar max_radius = 0.5;
514 if (stroke_cap_ == Cap::kSquare) {
515 max_radius = max_radius * kSqrt2;
516 }
517 if (stroke_join_ == Join::kMiter) {
518 max_radius = std::max(max_radius, miter_limit_ * 0.5f);
519 }
520 Scalar determinant = transform.GetDeterminant();
521 if (determinant == 0) {
522 return std::nullopt;
523 }
524 Scalar min_size = 1.0f / sqrt(std::abs(determinant));
525 Vector2 max_radius_xy =
526 transform
527 .TransformDirection(Vector2(max_radius, max_radius) *
528 std::max(stroke_width_, min_size))
529 .Abs();
530 return path_coverage.Expand(max_radius_xy);
531 }
532
533 } // namespace impeller
impeller::Cap::kRound
@ kRound
impeller::Cap::kSquare
@ kSquare
impeller::TPoint::y
Type y
Definition: point.h:26
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::StrokePathGeometry::~StrokePathGeometry
~StrokePathGeometry()
impeller::kSqrt2
constexpr float kSqrt2
Definition: constants.h:47
stroke_path_geometry.h
impeller::Vector2
Point Vector2
Definition: point.h:312
impeller::Path::GetBoundingBox
std::optional< Rect > GetBoundingBox() const
Definition: path.cc:388
impeller::PathBuilder::kArcApproximationMagic
constexpr static const Scalar kArcApproximationMagic
Definition: path_builder.h:23
impeller::Cap::kButt
@ kButt
impeller::ComputeUVGeometryCPU
VertexBufferBuilder< TextureFillVertexShader::PerVertexData > ComputeUVGeometryCPU(VertexBufferBuilder< SolidFillVertexShader::PerVertexData > &input, Point texture_origin, Size texture_coverage, Matrix effect_transform)
Compute UV geometry for a VBB that contains only position geometry.
Definition: geometry.cc:91
impeller::GeometryVertexType
GeometryVertexType
Definition: geometry.h:34
impeller::Join::kMiter
@ kMiter
path_builder.h
impeller::PrimitiveType::kTriangleStrip
@ kTriangleStrip
impeller::Point
TPoint< Scalar > Point
Definition: point.h:308
impeller::Path
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition: path.h:55
impeller::VertexBufferBuilder
Definition: vertex_buffer_builder.h:24
impeller::StrokePathGeometry::GetMiterLimit
Scalar GetMiterLimit() const
Definition: stroke_path_geometry.cc:28
impeller::Rect
TRect< Scalar > Rect
Definition: rect.h:488
impeller::Join::kRound
@ kRound
impeller::TPoint::Cross
constexpr Type Cross(const TPoint &p) const
Definition: point.h:213
impeller::StrokePathGeometry::GetStrokeCap
Cap GetStrokeCap() const
Definition: stroke_path_geometry.cc:32
impeller::StrokePathGeometry::GetStrokeJoin
Join GetStrokeJoin() const
Definition: stroke_path_geometry.cc:36
impeller::Join::kBevel
@ kBevel
impeller::TPoint::Abs
constexpr TPoint Abs() const
Definition: point.h:211
impeller::Join
Join
Definition: path.h:24
impeller::StrokePathGeometry::StrokePathGeometry
StrokePathGeometry(Path path, Scalar stroke_width, Scalar miter_limit, Cap stroke_cap, Join stroke_join)
Definition: stroke_path_geometry.cc:11
impeller::TPoint::GetLength
constexpr Type GetLength() const
Definition: point.h:201
std
Definition: comparable.h:95
impeller::TPoint
Definition: point.h:22
impeller::ScalarNearlyEqual
constexpr bool ScalarNearlyEqual(Scalar x, Scalar y, Scalar tolerance=kEhCloseEnough)
Definition: scalar.h:30
impeller::VertexBufferBuilder::AppendVertex
VertexBufferBuilder & AppendVertex(VertexType_ vertex)
Definition: vertex_buffer_builder.h:65
impeller::TPoint::Normalize
constexpr TPoint Normalize() const
Definition: point.h:203
impeller
Definition: aiks_context.cc:10
impeller::kPosition
@ kPosition
Definition: geometry.h:35
impeller::StrokePathGeometry::GetStrokeWidth
Scalar GetStrokeWidth() const
Definition: stroke_path_geometry.cc:24
impeller::Cap
Cap
Definition: path.h:18