Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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
12
13namespace impeller {
14using VS = SolidFillVertexShader;
15
16namespace {
17
18template <typename VertexWriter>
19using CapProc = std::function<void(VertexWriter& vtx_builder,
20 const Point& position,
21 const Point& offset,
23 bool reverse)>;
24
25template <typename VertexWriter>
26using JoinProc = std::function<void(VertexWriter& vtx_builder,
27 const Point& position,
28 const Point& start_offset,
29 const Point& end_offset,
30 Scalar miter_limit,
31 Scalar scale)>;
32
33class PositionWriter {
34 public:
35 void AppendVertex(const Point& point) {
36 data_.emplace_back(SolidFillVertexShader::PerVertexData{.position = point});
37 }
38
39 const std::vector<SolidFillVertexShader::PerVertexData>& GetData() const {
40 return data_;
41 }
42
43 private:
44 std::vector<SolidFillVertexShader::PerVertexData> data_ = {};
45};
46
47template <typename VertexWriter>
48class StrokeGenerator {
49 public:
50 StrokeGenerator(const Path::Polyline& p_polyline,
51 const Scalar p_stroke_width,
52 const Scalar p_scaled_miter_limit,
53 const JoinProc<VertexWriter>& p_join_proc,
54 const CapProc<VertexWriter>& p_cap_proc,
55 const Scalar p_scale)
56 : polyline(p_polyline),
57 stroke_width(p_stroke_width),
58 scaled_miter_limit(p_scaled_miter_limit),
59 join_proc(p_join_proc),
60 cap_proc(p_cap_proc),
61 scale(p_scale) {}
62
63 void Generate(VertexWriter& vtx_builder) {
64 for (size_t contour_i = 0; contour_i < polyline.contours.size();
65 contour_i++) {
66 const Path::PolylineContour& contour = polyline.contours[contour_i];
67 size_t contour_start_point_i, contour_end_point_i;
68 std::tie(contour_start_point_i, contour_end_point_i) =
69 polyline.GetContourPointBounds(contour_i);
70
71 auto contour_delta = contour_end_point_i - contour_start_point_i;
72 if (contour_delta == 1) {
73 Point p = polyline.GetPoint(contour_start_point_i);
74 cap_proc(vtx_builder, p, {-stroke_width * 0.5f, 0}, scale,
75 /*reverse=*/false);
76 cap_proc(vtx_builder, p, {stroke_width * 0.5f, 0}, scale,
77 /*reverse=*/false);
78 continue;
79 } else if (contour_delta == 0) {
80 continue; // This contour has no renderable content.
81 }
82
84 offset = ComputeOffset(contour_start_point_i, contour_start_point_i,
85 contour_end_point_i, contour);
86 const Point contour_first_offset = offset;
87
88 if (contour_i > 0) {
89 // This branch only executes when we've just finished drawing a contour
90 // and are switching to a new one.
91 // We're drawing a triangle strip, so we need to "pick up the pen" by
92 // appending two vertices at the end of the previous contour and two
93 // vertices at the start of the new contour (thus connecting the two
94 // contours with two zero volume triangles, which will be discarded by
95 // the rasterizer).
96 vtx.position = polyline.GetPoint(contour_start_point_i - 1);
97 // Append two vertices when "picking up" the pen so that the triangle
98 // drawn when moving to the beginning of the new contour will have zero
99 // volume.
100 vtx_builder.AppendVertex(vtx.position);
101 vtx_builder.AppendVertex(vtx.position);
102
103 vtx.position = polyline.GetPoint(contour_start_point_i);
104 // Append two vertices at the beginning of the new contour, which
105 // appends two triangles of zero area.
106 vtx_builder.AppendVertex(vtx.position);
107 vtx_builder.AppendVertex(vtx.position);
108 }
109
110 // Generate start cap.
111 if (!polyline.contours[contour_i].is_closed) {
112 Point cap_offset =
113 Vector2(-contour.start_direction.y, contour.start_direction.x) *
114 stroke_width * 0.5f; // Counterclockwise normal
115 cap_proc(vtx_builder, polyline.GetPoint(contour_start_point_i),
116 cap_offset, scale, /*reverse=*/true);
117 }
118
119 for (size_t contour_component_i = 0;
120 contour_component_i < contour.components.size();
121 contour_component_i++) {
122 const Path::PolylineContour::Component& component =
123 contour.components[contour_component_i];
124 bool is_last_component =
125 contour_component_i == contour.components.size() - 1;
126
127 size_t component_start_index = component.component_start_index;
128 size_t component_end_index =
129 is_last_component ? contour_end_point_i - 1
130 : contour.components[contour_component_i + 1]
131 .component_start_index;
132 if (component.is_curve) {
133 AddVerticesForCurveComponent(
134 vtx_builder, component_start_index, component_end_index,
135 contour_start_point_i, contour_end_point_i, contour);
136 } else {
137 AddVerticesForLinearComponent(
138 vtx_builder, component_start_index, component_end_index,
139 contour_start_point_i, contour_end_point_i, contour);
140 }
141 }
142
143 // Generate end cap or join.
144 if (!contour.is_closed) {
145 auto cap_offset =
146 Vector2(-contour.end_direction.y, contour.end_direction.x) *
147 stroke_width * 0.5f; // Clockwise normal
148 cap_proc(vtx_builder, polyline.GetPoint(contour_end_point_i - 1),
149 cap_offset, scale, /*reverse=*/false);
150 } else {
151 join_proc(vtx_builder, polyline.GetPoint(contour_start_point_i), offset,
152 contour_first_offset, scaled_miter_limit, scale);
153 }
154 }
155 }
156
157 /// Computes offset by calculating the direction from point_i - 1 to point_i
158 /// if point_i is within `contour_start_point_i` and `contour_end_point_i`;
159 /// Otherwise, it uses direction from contour.
160 Point ComputeOffset(const size_t point_i,
161 const size_t contour_start_point_i,
162 const size_t contour_end_point_i,
163 const Path::PolylineContour& contour) const {
164 Point direction;
165 if (point_i >= contour_end_point_i) {
166 direction = contour.end_direction;
167 } else if (point_i <= contour_start_point_i) {
168 direction = -contour.start_direction;
169 } else {
170 direction = (polyline.GetPoint(point_i) - polyline.GetPoint(point_i - 1))
171 .Normalize();
172 }
173 return Vector2{-direction.y, direction.x} * stroke_width * 0.5f;
174 }
175
176 void AddVerticesForLinearComponent(VertexWriter& vtx_builder,
177 const size_t component_start_index,
178 const size_t component_end_index,
179 const size_t contour_start_point_i,
180 const size_t contour_end_point_i,
182 bool is_last_component = component_start_index ==
183 contour.components.back().component_start_index;
184
185 for (size_t point_i = component_start_index; point_i < component_end_index;
186 point_i++) {
187 bool is_end_of_component = point_i == component_end_index - 1;
188 vtx.position = polyline.GetPoint(point_i) + offset;
189 vtx_builder.AppendVertex(vtx.position);
190 vtx.position = polyline.GetPoint(point_i) - offset;
191 vtx_builder.AppendVertex(vtx.position);
192
193 // For line components, two additional points need to be appended
194 // prior to appending a join connecting the next component.
195 vtx.position = polyline.GetPoint(point_i + 1) + offset;
196 vtx_builder.AppendVertex(vtx.position);
197 vtx.position = polyline.GetPoint(point_i + 1) - offset;
198 vtx_builder.AppendVertex(vtx.position);
199
201 offset = ComputeOffset(point_i + 2, contour_start_point_i,
202 contour_end_point_i, contour);
203 if (!is_last_component && is_end_of_component) {
204 // Generate join from the current line to the next line.
205 join_proc(vtx_builder, polyline.GetPoint(point_i + 1), previous_offset,
207 }
208 }
209 }
210
211 void AddVerticesForCurveComponent(VertexWriter& vtx_builder,
212 const size_t component_start_index,
213 const size_t component_end_index,
214 const size_t contour_start_point_i,
215 const size_t contour_end_point_i,
217 bool is_last_component = component_start_index ==
218 contour.components.back().component_start_index;
219
220 for (size_t point_i = component_start_index; point_i < component_end_index;
221 point_i++) {
222 bool is_end_of_component = point_i == component_end_index - 1;
223
224 vtx.position = polyline.GetPoint(point_i) + offset;
225 vtx_builder.AppendVertex(vtx.position);
226 vtx.position = polyline.GetPoint(point_i) - offset;
227 vtx_builder.AppendVertex(vtx.position);
228
230 offset = ComputeOffset(point_i + 2, contour_start_point_i,
231 contour_end_point_i, contour);
232 // For curve components, the polyline is detailed enough such that
233 // it can avoid worrying about joins altogether.
234 if (is_end_of_component) {
235 vtx.position = polyline.GetPoint(point_i + 1) + offset;
236 vtx_builder.AppendVertex(vtx.position);
237 vtx.position = polyline.GetPoint(point_i + 1) - offset;
238 vtx_builder.AppendVertex(vtx.position);
239 // Generate join from the current line to the next line.
240 if (!is_last_component) {
241 join_proc(vtx_builder, polyline.GetPoint(point_i + 1),
243 }
244 }
245 }
246 }
247
248 const Path::Polyline& polyline;
251 const JoinProc<VertexWriter>& join_proc;
252 const CapProc<VertexWriter>& cap_proc;
254
257 SolidFillVertexShader::PerVertexData vtx;
258};
259
260template <typename VertexWriter>
261void CreateButtCap(VertexWriter& vtx_builder,
262 const Point& position,
263 const Point& offset,
265 bool reverse) {
266 Point orientation = offset * (reverse ? -1 : 1);
267 VS::PerVertexData vtx;
268 vtx.position = position + orientation;
269 vtx_builder.AppendVertex(vtx.position);
270 vtx.position = position - orientation;
271 vtx_builder.AppendVertex(vtx.position);
272}
273
274template <typename VertexWriter>
275void CreateRoundCap(VertexWriter& vtx_builder,
276 const Point& position,
277 const Point& offset,
279 bool reverse) {
280 Point orientation = offset * (reverse ? -1 : 1);
281 Point forward(offset.y, -offset.x);
282 Point forward_normal = forward.Normalize();
283
284 CubicPathComponent arc;
285 if (reverse) {
286 arc = CubicPathComponent(
287 forward, forward + orientation * PathBuilder::kArcApproximationMagic,
288 orientation + forward * PathBuilder::kArcApproximationMagic,
289 orientation);
290 } else {
291 arc = CubicPathComponent(
292 orientation,
293 orientation + forward * PathBuilder::kArcApproximationMagic,
294 forward + orientation * PathBuilder::kArcApproximationMagic, forward);
295 }
296
297 Point vtx = position + orientation;
298 vtx_builder.AppendVertex(vtx);
299 vtx = position - orientation;
300 vtx_builder.AppendVertex(vtx);
301
302 arc.ToLinearPathComponents(scale, [&vtx_builder, &vtx, forward_normal,
303 position](const Point& point) {
304 vtx = position + point;
305 vtx_builder.AppendVertex(vtx);
306 vtx = position + (-point).Reflect(forward_normal);
307 vtx_builder.AppendVertex(vtx);
308 });
309}
310
311template <typename VertexWriter>
312void CreateSquareCap(VertexWriter& vtx_builder,
313 const Point& position,
314 const Point& offset,
316 bool reverse) {
317 Point orientation = offset * (reverse ? -1 : 1);
318 Point forward(offset.y, -offset.x);
319
320 Point vtx = position + orientation;
321 vtx_builder.AppendVertex(vtx);
322 vtx = position - orientation;
323 vtx_builder.AppendVertex(vtx);
324 vtx = position + orientation + forward;
325 vtx_builder.AppendVertex(vtx);
326 vtx = position - orientation + forward;
327 vtx_builder.AppendVertex(vtx);
328}
329
330template <typename VertexWriter>
331Scalar CreateBevelAndGetDirection(VertexWriter& vtx_builder,
332 const Point& position,
333 const Point& start_offset,
334 const Point& end_offset) {
335 Point vtx = position;
336 vtx_builder.AppendVertex(vtx);
337
338 Scalar dir = start_offset.Cross(end_offset) > 0 ? -1 : 1;
339 vtx = position + start_offset * dir;
340 vtx_builder.AppendVertex(vtx);
341 vtx = position + end_offset * dir;
342 vtx_builder.AppendVertex(vtx);
343
344 return dir;
345}
346
347template <typename VertexWriter>
348void CreateMiterJoin(VertexWriter& vtx_builder,
349 const Point& position,
350 const Point& start_offset,
351 const Point& end_offset,
352 Scalar miter_limit,
353 Scalar scale) {
354 Point start_normal = start_offset.Normalize();
355 Point end_normal = end_offset.Normalize();
356
357 // 1 for no joint (straight line), 0 for max joint (180 degrees).
358 Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2;
359 if (ScalarNearlyEqual(alignment, 1)) {
360 return;
361 }
362
363 Scalar direction = CreateBevelAndGetDirection(vtx_builder, position,
364 start_offset, end_offset);
365
366 Point miter_point = (((start_offset + end_offset) / 2) / alignment);
367 if (miter_point.GetDistanceSquared({0, 0}) > miter_limit * miter_limit) {
368 return; // Convert to bevel when we exceed the miter limit.
369 }
370
371 // Outer miter point.
372 VS::PerVertexData vtx;
373 vtx.position = position + miter_point * direction;
374 vtx_builder.AppendVertex(vtx.position);
375}
376
377template <typename VertexWriter>
378void CreateRoundJoin(VertexWriter& vtx_builder,
379 const Point& position,
380 const Point& start_offset,
381 const Point& end_offset,
382 Scalar miter_limit,
383 Scalar scale) {
384 Point start_normal = start_offset.Normalize();
385 Point end_normal = end_offset.Normalize();
386
387 // 0 for no joint (straight line), 1 for max joint (180 degrees).
388 Scalar alignment = 1 - (start_normal.Dot(end_normal) + 1) / 2;
389 if (ScalarNearlyEqual(alignment, 0)) {
390 return;
391 }
392
393 Scalar direction = CreateBevelAndGetDirection(vtx_builder, position,
394 start_offset, end_offset);
395
396 Point middle =
397 (start_offset + end_offset).Normalize() * start_offset.GetLength();
398 Point middle_normal = middle.Normalize();
399
400 Point middle_handle = middle + Point(-middle.y, middle.x) *
402 alignment * direction;
403 Point start_handle = start_offset + Point(start_offset.y, -start_offset.x) *
405 alignment * direction;
406
407 VS::PerVertexData vtx;
408 CubicPathComponent(start_offset, start_handle, middle_handle, middle)
409 .ToLinearPathComponents(scale, [&vtx_builder, direction, &vtx, position,
410 middle_normal](const Point& point) {
411 vtx.position = position + point * direction;
412 vtx_builder.AppendVertex(vtx.position);
413 vtx.position = position + (-point * direction).Reflect(middle_normal);
414 vtx_builder.AppendVertex(vtx.position);
415 });
416}
417
418template <typename VertexWriter>
419void CreateBevelJoin(VertexWriter& vtx_builder,
420 const Point& position,
421 const Point& start_offset,
422 const Point& end_offset,
423 Scalar miter_limit,
424 Scalar scale) {
425 CreateBevelAndGetDirection(vtx_builder, position, start_offset, end_offset);
426}
427
428template <typename VertexWriter>
429void CreateSolidStrokeVertices(VertexWriter& vtx_builder,
430 const Path::Polyline& polyline,
433 const JoinProc<VertexWriter>& join_proc,
434 const CapProc<VertexWriter>& cap_proc,
435 Scalar scale) {
436 StrokeGenerator stroke_generator(polyline, stroke_width, scaled_miter_limit,
438 stroke_generator.Generate(vtx_builder);
439}
440
441// static
442template <typename VertexWriter>
443JoinProc<VertexWriter> GetJoinProc(Join stroke_join) {
444 switch (stroke_join) {
445 case Join::kBevel:
446 return &CreateBevelJoin<VertexWriter>;
447 case Join::kMiter:
448 return &CreateMiterJoin<VertexWriter>;
449 case Join::kRound:
450 return &CreateRoundJoin<VertexWriter>;
451 }
452}
453
454template <typename VertexWriter>
455CapProc<VertexWriter> GetCapProc(Cap stroke_cap) {
456 switch (stroke_cap) {
457 case Cap::kButt:
458 return &CreateButtCap<VertexWriter>;
459 case Cap::kRound:
460 return &CreateRoundCap<VertexWriter>;
461 case Cap::kSquare:
462 return &CreateSquareCap<VertexWriter>;
463 }
464}
465} // namespace
466
467std::vector<SolidFillVertexShader::PerVertexData>
468StrokePathGeometry::GenerateSolidStrokeVertices(const Path::Polyline& polyline,
469 Scalar stroke_width,
470 Scalar miter_limit,
471 Join stroke_join,
472 Cap stroke_cap,
473 Scalar scale) {
474 auto scaled_miter_limit = stroke_width * miter_limit * 0.5f;
475 auto join_proc = GetJoinProc<PositionWriter>(stroke_join);
476 auto cap_proc = GetCapProc<PositionWriter>(stroke_cap);
477 StrokeGenerator stroke_generator(polyline, stroke_width, scaled_miter_limit,
479 PositionWriter vtx_builder;
480 stroke_generator.Generate(vtx_builder);
481 return vtx_builder.GetData();
482}
483
484StrokePathGeometry::StrokePathGeometry(const Path& path,
486 Scalar miter_limit,
487 Cap stroke_cap,
488 Join stroke_join)
489 : path_(path),
490 stroke_width_(stroke_width),
491 miter_limit_(miter_limit),
492 stroke_cap_(stroke_cap),
493 stroke_join_(stroke_join) {}
494
496
498 return stroke_width_;
499}
500
502 return miter_limit_;
503}
504
506 return stroke_cap_;
507}
508
510 return stroke_join_;
511}
512
514 const ContentContext& renderer,
515 const Entity& entity,
516 RenderPass& pass) const {
517 if (stroke_width_ < 0.0) {
518 return {};
519 }
520 auto determinant = entity.GetTransform().GetDeterminant();
521 if (determinant == 0) {
522 return {};
523 }
524
525 Scalar min_size = 1.0f / sqrt(std::abs(determinant));
526 Scalar stroke_width = std::max(stroke_width_, min_size);
527
528 auto& host_buffer = renderer.GetTransientsBuffer();
529 auto scale = entity.GetTransform().GetMaxBasisLength();
530
531 PositionWriter position_writer;
532 auto polyline = renderer.GetTessellator()->CreateTempPolyline(path_, scale);
533 CreateSolidStrokeVertices(position_writer, polyline, stroke_width,
534 miter_limit_ * stroke_width_ * 0.5f,
535 GetJoinProc<PositionWriter>(stroke_join_),
536 GetCapProc<PositionWriter>(stroke_cap_), scale);
537
538 BufferView buffer_view =
539 host_buffer.Emplace(position_writer.GetData().data(),
540 position_writer.GetData().size() *
541 sizeof(SolidFillVertexShader::PerVertexData),
542 alignof(SolidFillVertexShader::PerVertexData));
543
544 return GeometryResult{
546 .vertex_buffer =
547 {
548 .vertex_buffer = buffer_view,
549 .vertex_count = position_writer.GetData().size(),
550 .index_type = IndexType::kNone,
551 },
552 .transform = entity.GetShaderTransform(pass),
554 };
555}
556
560
562 const Matrix& transform) const {
563 auto path_bounds = path_.GetBoundingBox();
564 if (!path_bounds.has_value()) {
565 return std::nullopt;
566 }
567
568 Scalar max_radius = 0.5;
569 if (stroke_cap_ == Cap::kSquare) {
570 max_radius = max_radius * kSqrt2;
571 }
572 if (stroke_join_ == Join::kMiter) {
573 max_radius = std::max(max_radius, miter_limit_ * 0.5f);
574 }
575 Scalar determinant = transform.GetDeterminant();
576 if (determinant == 0) {
577 return std::nullopt;
578 }
579 Scalar min_size = 1.0f / sqrt(std::abs(determinant));
580 max_radius *= std::max(stroke_width_, min_size);
581 return path_bounds->Expand(max_radius).TransformBounds(transform);
582}
583
584} // namespace impeller
Matrix GetShaderTransform(const RenderPass &pass) const
Get the vertex shader transform used for drawing this Entity.
Definition entity.cc:50
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition entity.cc:46
static constexpr const Scalar kArcApproximationMagic
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition path.h:51
std::optional< Rect > GetBoundingBox() const
Definition path.cc:339
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition render_pass.h:33
GeometryResult::Mode GetResultMode() const override
std::optional< Rect > GetCoverage(const Matrix &transform) const override
GeometryResult GetPositionBuffer(const ContentContext &renderer, const Entity &entity, RenderPass &pass) const override
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets dir
Definition switches.h:145
@ kNone
Does not use the index buffer.
Point Vector2
Definition point.h:320
float Scalar
Definition scalar.h:18
SolidFillVertexShader VS
TPoint< Scalar > Point
Definition point.h:316
constexpr float kSqrt2
Definition constants.h:47
constexpr bool ScalarNearlyEqual(Scalar x, Scalar y, Scalar tolerance=kEhCloseEnough)
Definition scalar.h:30
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
Definition p3.cpp:47
SolidFillVertexShader::PerVertexData vtx
Point previous_offset
const CapProc< VertexWriter > & cap_proc
const Scalar scaled_miter_limit
const Scalar stroke_width
const Scalar scale
const JoinProc< VertexWriter > & join_proc
const Path::Polyline & polyline
Point offset
PrimitiveType type
Definition geometry.h:35
A 4x4 matrix using column-major storage.
Definition matrix.h:37
Scalar GetMaxBasisLength() const
Definition matrix.cc:196
Scalar GetDeterminant() const
Definition matrix.cc:162
constexpr Type GetLength() const
Definition point.h:206
constexpr TPoint Normalize() const
Definition point.h:208