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 
6 
8 
9 namespace impeller {
10 
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 
23 
25  return stroke_width_;
26 }
27 
29  return miter_limit_;
30 }
31 
33  return stroke_cap_;
34 }
35 
37  return stroke_join_;
38 }
39 
40 // static
41 Scalar StrokePathGeometry::CreateBevelAndGetDirection(
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) *
125  alignment * dir;
126  Point start_handle =
127  start_offset + Point(start_offset.y, -start_offset.x) *
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{
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{
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 {
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