| 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(const Path& path,
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
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 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{
368 .type = PrimitiveType::kTriangleStrip,
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{
402 .type = PrimitiveType::kTriangleStrip,
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 {
411 return GeometryVertexType::kPosition;
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