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_(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  auto arc_points = CubicPathComponent(start_offset, start_handle,
132  middle_handle, middle)
133  .CreatePolyline(scale);
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  for (const auto& point : arc.CreatePolyline(scale)) {
196  vtx.position = position + point;
197  vtx_builder.AppendVertex(vtx);
198  vtx.position = position + (-point).Reflect(forward_normal);
199  vtx_builder.AppendVertex(vtx);
200  }
201  };
202  break;
203  case Cap::kSquare:
204  cap_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
205  const Point& position, const Point& offset, Scalar scale,
206  bool reverse) {
207  Point orientation = offset * (reverse ? -1 : 1);
208 
209  VS::PerVertexData vtx;
210 
211  Point forward(offset.y, -offset.x);
212 
213  vtx.position = position + orientation;
214  vtx_builder.AppendVertex(vtx);
215  vtx.position = position - orientation;
216  vtx_builder.AppendVertex(vtx);
217  vtx.position = position + orientation + forward;
218  vtx_builder.AppendVertex(vtx);
219  vtx.position = position - orientation + forward;
220  vtx_builder.AppendVertex(vtx);
221  };
222  break;
223  }
224  return cap_proc;
225 }
226 
227 // static
228 VertexBufferBuilder<SolidFillVertexShader::PerVertexData>
229 StrokePathGeometry::CreateSolidStrokeVertices(
230  const Path& path,
231  Scalar stroke_width,
232  Scalar scaled_miter_limit,
233  const StrokePathGeometry::JoinProc& join_proc,
234  const StrokePathGeometry::CapProc& cap_proc,
235  Scalar scale) {
236  VertexBufferBuilder<VS::PerVertexData> vtx_builder;
237  auto polyline = path.CreatePolyline(scale);
238 
239  VS::PerVertexData vtx;
240 
241  // Offset state.
242  Point offset;
243  Point previous_offset; // Used for computing joins.
244 
245  auto compute_offset = [&polyline, &offset, &previous_offset,
246  &stroke_width](size_t point_i) {
247  previous_offset = offset;
248  Point direction =
249  (polyline.points[point_i] - polyline.points[point_i - 1]).Normalize();
250  offset = Vector2{-direction.y, direction.x} * stroke_width * 0.5;
251  };
252 
253  for (size_t contour_i = 0; contour_i < polyline.contours.size();
254  contour_i++) {
255  auto contour = polyline.contours[contour_i];
256  size_t contour_start_point_i, contour_end_point_i;
257  std::tie(contour_start_point_i, contour_end_point_i) =
258  polyline.GetContourPointBounds(contour_i);
259 
260  switch (contour_end_point_i - contour_start_point_i) {
261  case 1: {
262  Point p = polyline.points[contour_start_point_i];
263  cap_proc(vtx_builder, p, {-stroke_width * 0.5f, 0}, scale, false);
264  cap_proc(vtx_builder, p, {stroke_width * 0.5f, 0}, scale, false);
265  continue;
266  }
267  case 0:
268  continue; // This contour has no renderable content.
269  default:
270  break;
271  }
272 
273  // The first point's offset is always the same as the second point.
274  compute_offset(contour_start_point_i + 1);
275  const Point contour_first_offset = offset;
276 
277  if (contour_i > 0) {
278  // This branch only executes when we've just finished drawing a contour
279  // and are switching to a new one.
280  // We're drawing a triangle strip, so we need to "pick up the pen" by
281  // appending two vertices at the end of the previous contour and two
282  // vertices at the start of the new contour (thus connecting the two
283  // contours with two zero volume triangles, which will be discarded by
284  // the rasterizer).
285  vtx.position = polyline.points[contour_start_point_i - 1];
286  // Append two vertices when "picking up" the pen so that the triangle
287  // drawn when moving to the beginning of the new contour will have zero
288  // volume.
289  vtx_builder.AppendVertex(vtx);
290  vtx_builder.AppendVertex(vtx);
291 
292  vtx.position = polyline.points[contour_start_point_i];
293  // Append two vertices at the beginning of the new contour, which
294  // appends two triangles of zero area.
295  vtx_builder.AppendVertex(vtx);
296  vtx_builder.AppendVertex(vtx);
297  }
298 
299  // Generate start cap.
300  if (!polyline.contours[contour_i].is_closed) {
301  auto cap_offset =
302  Vector2(-contour.start_direction.y, contour.start_direction.x) *
303  stroke_width * 0.5; // Counterclockwise normal
304  cap_proc(vtx_builder, polyline.points[contour_start_point_i], cap_offset,
305  scale, true);
306  }
307 
308  // Generate contour geometry.
309  for (size_t point_i = contour_start_point_i + 1;
310  point_i < contour_end_point_i; point_i++) {
311  // Generate line rect.
312  vtx.position = polyline.points[point_i - 1] + offset;
313  vtx_builder.AppendVertex(vtx);
314  vtx.position = polyline.points[point_i - 1] - offset;
315  vtx_builder.AppendVertex(vtx);
316  vtx.position = polyline.points[point_i] + offset;
317  vtx_builder.AppendVertex(vtx);
318  vtx.position = polyline.points[point_i] - offset;
319  vtx_builder.AppendVertex(vtx);
320 
321  if (point_i < contour_end_point_i - 1) {
322  compute_offset(point_i + 1);
323 
324  // Generate join from the current line to the next line.
325  join_proc(vtx_builder, polyline.points[point_i], previous_offset,
326  offset, scaled_miter_limit, scale);
327  }
328  }
329 
330  // Generate end cap or join.
331  if (!polyline.contours[contour_i].is_closed) {
332  auto cap_offset =
333  Vector2(-contour.end_direction.y, contour.end_direction.x) *
334  stroke_width * 0.5; // Clockwise normal
335  cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1],
336  cap_offset, scale, false);
337  } else {
338  join_proc(vtx_builder, polyline.points[contour_start_point_i], offset,
339  contour_first_offset, scaled_miter_limit, scale);
340  }
341  }
342 
343  return vtx_builder;
344 }
345 
346 GeometryResult StrokePathGeometry::GetPositionBuffer(
347  const ContentContext& renderer,
348  const Entity& entity,
349  RenderPass& pass) {
350  if (stroke_width_ < 0.0) {
351  return {};
352  }
353  auto determinant = entity.GetTransformation().GetDeterminant();
354  if (determinant == 0) {
355  return {};
356  }
357 
358  Scalar min_size = 1.0f / sqrt(std::abs(determinant));
359  Scalar stroke_width = std::max(stroke_width_, min_size);
360 
361  auto& host_buffer = pass.GetTransientsBuffer();
362  auto vertex_builder = CreateSolidStrokeVertices(
363  path_, stroke_width, miter_limit_ * stroke_width_ * 0.5,
364  GetJoinProc(stroke_join_), GetCapProc(stroke_cap_),
365  entity.GetTransformation().GetMaxBasisLength());
366 
367  return GeometryResult{
369  .vertex_buffer = vertex_builder.CreateVertexBuffer(host_buffer),
370  .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
371  entity.GetTransformation(),
372  .prevent_overdraw = true,
373  };
374 }
375 
376 GeometryResult StrokePathGeometry::GetPositionUVBuffer(
377  Rect texture_coverage,
378  Matrix effect_transform,
379  const ContentContext& renderer,
380  const Entity& entity,
381  RenderPass& pass) {
382  if (stroke_width_ < 0.0) {
383  return {};
384  }
385  auto determinant = entity.GetTransformation().GetDeterminant();
386  if (determinant == 0) {
387  return {};
388  }
389 
390  Scalar min_size = 1.0f / sqrt(std::abs(determinant));
391  Scalar stroke_width = std::max(stroke_width_, min_size);
392 
393  auto& host_buffer = pass.GetTransientsBuffer();
394  auto stroke_builder = CreateSolidStrokeVertices(
395  path_, stroke_width, miter_limit_ * stroke_width_ * 0.5,
396  GetJoinProc(stroke_join_), GetCapProc(stroke_cap_),
397  entity.GetTransformation().GetMaxBasisLength());
398  auto vertex_builder = ComputeUVGeometryCPU(
399  stroke_builder, {0, 0}, texture_coverage.size, effect_transform);
400 
401  return GeometryResult{
403  .vertex_buffer = vertex_builder.CreateVertexBuffer(host_buffer),
404  .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
405  entity.GetTransformation(),
406  .prevent_overdraw = true,
407  };
408 }
409 
410 GeometryVertexType StrokePathGeometry::GetVertexType() const {
412 }
413 
414 std::optional<Rect> StrokePathGeometry::GetCoverage(
415  const Matrix& transform) const {
416  auto path_bounds = path_.GetBoundingBox();
417  if (!path_bounds.has_value()) {
418  return std::nullopt;
419  }
420  auto path_coverage = path_bounds->TransformBounds(transform);
421 
422  Scalar max_radius = 0.5;
423  if (stroke_cap_ == Cap::kSquare) {
424  max_radius = max_radius * kSqrt2;
425  }
426  if (stroke_join_ == Join::kMiter) {
427  max_radius = std::max(max_radius, miter_limit_ * 0.5f);
428  }
429  Scalar determinant = transform.GetDeterminant();
430  if (determinant == 0) {
431  return std::nullopt;
432  }
433  Scalar min_size = 1.0f / sqrt(std::abs(determinant));
434  Vector2 max_radius_xy =
435  transform
436  .TransformDirection(Vector2(max_radius, max_radius) *
437  std::max(stroke_width_, min_size))
438  .Abs();
439  return Rect(path_coverage.origin - max_radius_xy,
440  Size(path_coverage.size.width + max_radius_xy.x * 2,
441  path_coverage.size.height + max_radius_xy.y * 2));
442 }
443 
444 } // namespace impeller
impeller::Cap::kRound
@ kRound
impeller::Cap::kSquare
@ kSquare
impeller::TPoint::y
Type y
Definition: point.h:24
impeller::Scalar
float Scalar
Definition: scalar.h:15
impeller::StrokePathGeometry::~StrokePathGeometry
~StrokePathGeometry()
impeller::kSqrt2
constexpr float kSqrt2
Definition: constants.h:46
stroke_path_geometry.h
impeller::Vector2
Point Vector2
Definition: point.h:310
impeller::Path::GetBoundingBox
std::optional< Rect > GetBoundingBox() const
Definition: path.cc:399
impeller::Matrix::GetDeterminant
Scalar GetDeterminant() const
Definition: matrix.cc:162
impeller::Size
TSize< Scalar > Size
Definition: size.h:135
impeller::PathBuilder::kArcApproximationMagic
constexpr static const Scalar kArcApproximationMagic
Definition: path_builder.h:22
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:50
impeller::GeometryVertexType
GeometryVertexType
Definition: geometry.h:25
impeller::PrimitiveType::kTriangleStrip
@ kTriangleStrip
impeller::Join::kMiter
@ kMiter
impeller::StrokePathGeometry::StrokePathGeometry
StrokePathGeometry(const Path &path, Scalar stroke_width, Scalar miter_limit, Cap stroke_cap, Join stroke_join)
Definition: stroke_path_geometry.cc:11
path_builder.h
impeller::Point
TPoint< Scalar > Point
Definition: point.h:306
impeller::Path
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition: path.h:54
impeller::VertexBufferBuilder
Definition: vertex_buffer_builder.h:23
impeller::StrokePathGeometry::GetMiterLimit
Scalar GetMiterLimit() const
Definition: stroke_path_geometry.cc:28
impeller::Rect
TRect< Scalar > Rect
Definition: rect.h:306
impeller::Join::kRound
@ kRound
impeller::TPoint::Cross
constexpr Type Cross(const TPoint &p) const
Definition: point.h:211
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:209
impeller::Join
Join
Definition: path.h:23
impeller::TPoint::GetLength
constexpr Type GetLength() const
Definition: point.h:199
impeller::TPoint< Scalar >
impeller::ScalarNearlyEqual
constexpr bool ScalarNearlyEqual(Scalar x, Scalar y, Scalar tolerance=kEhCloseEnough)
Definition: scalar.h:27
impeller::Matrix::MakeOrthographic
static constexpr Matrix MakeOrthographic(TSize< T > size)
Definition: matrix.h:448
impeller::VertexBufferBuilder::AppendVertex
VertexBufferBuilder & AppendVertex(VertexType_ vertex)
Definition: vertex_buffer_builder.h:59
impeller::TPoint::Normalize
constexpr TPoint Normalize() const
Definition: point.h:201
impeller
Definition: aiks_context.cc:10
impeller::kPosition
@ kPosition
Definition: geometry.h:26
impeller::StrokePathGeometry::GetStrokeWidth
Scalar GetStrokeWidth() const
Definition: stroke_path_geometry.cc:24
impeller::Cap
Cap
Definition: path.h:17