Flutter Impeller
tessellator.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 #include <cstdint>
7 #include <cstring>
8 
11 
12 namespace {
13 static constexpr int kPrecomputedDivisionCount = 1024;
14 
15 static int kPrecomputedDivisions[kPrecomputedDivisionCount] = {
16  // clang-format off
17  1, 2, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7,
18  8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10,
19  10, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 13,
20  13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14,
21  15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16,
22  16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18,
23  18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19,
24  19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
25  20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
26  22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23,
27  23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24,
28  24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25,
29  25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26,
30  26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27,
31  27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28,
32  28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29,
33  29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
34  29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
35  30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
36  31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32,
37  32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33,
38  33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33,
39  33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
40  34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 35, 35, 35,
41  35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 36, 36,
42  36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
43  36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
44  37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38,
45  38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
46  38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
47  39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 40, 40,
48  40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
49  40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 41, 41, 41,
50  41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
51  41, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
52  42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 43, 43, 43,
53  43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
54  43, 43, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 44, 44,
55  44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
56  44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
57  45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
58  45, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
59  46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 47,
60  47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
61  47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 48, 48, 48,
62  48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
63  48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49,
64  49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
65  49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50,
66  50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
67  50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51,
68  51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
69  51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52,
70  52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
71  52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 53,
72  53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
73  53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 54,
74  54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
75  54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
76  54, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
77  55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
78  55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
79  56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
80  56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57,
81  // clang-format on
82 };
83 
84 static size_t ComputeQuadrantDivisions(impeller::Scalar pixel_radius) {
85  if (pixel_radius <= 0.0) {
86  return 1;
87  }
88  int radius_index = ceil(pixel_radius);
89  if (radius_index < kPrecomputedDivisionCount) {
90  return kPrecomputedDivisions[radius_index];
91  }
92 
93  // For a circle with N divisions per quadrant, the maximum deviation of
94  // the polgyon approximation from the true circle will be at the center
95  // of the base of each triangular pie slice. We can compute that distance
96  // by finding the midpoint of the line of the first slice and compare
97  // its distance from the center of the circle to the radius. We will aim
98  // to have the length of that bisector to be within |kCircleTolerance|
99  // from the radius in pixels.
100  //
101  // Each vertex will appear at an angle of:
102  // theta(i) = (kPi / 2) * (i / N) // for i in [0..N]
103  // with each point falling at:
104  // point(i) = r * (cos(theta), sin(theta))
105  // If we consider the unit circle to simplify the calculations below then
106  // we need to scale the tolerance from its absolute quantity into a unit
107  // circle fraction:
108  // k = tolerance / radius
109  // Using this scaled tolerance below to avoid multiplying by the radius
110  // throughout all of the math, we have:
111  // first point = (1, 0) // theta(0) == 0
112  // theta = kPi / 2 / N // theta(1)
113  // second point = (cos(theta), sin(theta)) = (c, s)
114  // midpoint = (first + second) * 0.5 = ((1 + c)/2, s/2)
115  // |midpoint| = sqrt((1 + c)*(1 + c)/4 + s*s/4)
116  // = sqrt((1 + c + c + c*c + s*s) / 4)
117  // = sqrt((1 + 2c + 1) / 4)
118  // = sqrt((2 + 2c) / 4)
119  // = sqrt((1 + c) / 2)
120  // = cos(theta / 2) // using half-angle cosine formula
121  // error = 1 - |midpoint| = 1 - cos(theta / 2)
122  // cos(theta/2) = 1 - error
123  // theta/2 = acos(1 - error)
124  // kPi / 2 / N / 2 = acos(1 - error)
125  // kPi / 4 / acos(1 - error) = N
126  // Since we need error <= k, we want divisions >= N, so we use:
127  // N = ceil(kPi / 4 / acos(1 - k))
128  //
129  // Math is confirmed in https://math.stackexchange.com/a/4132095
130  // (keeping in mind that we are computing quarter circle divisions here)
131  // which also points out a performance optimization that is accurate
132  // to within an over-estimation of 1 division would be:
133  // N = ceil(kPi / 4 / sqrt(2 * k))
134  // Since we have precomputed the divisions for radii up to 1024, we can
135  // afford to be more accurate using the acos formula here for larger radii.
136  double k = impeller::Tessellator::kCircleTolerance / pixel_radius;
137  return ceil(impeller::kPiOver4 / std::acos(1 - k));
138 }
139 
140 template <typename IndexT>
141 impeller::IndexType IndexTypeFor();
142 template <>
143 impeller::IndexType IndexTypeFor<uint32_t>() {
145 }
146 template <>
147 impeller::IndexType IndexTypeFor<uint16_t>() {
149 }
150 
151 /// @brief A vertex writer that generates a triangle fan and requires primitive
152 /// restart.
153 template <typename IndexT>
154 class FanPathVertexWriter : public impeller::PathTessellator::VertexWriter {
155  public:
156  explicit FanPathVertexWriter(impeller::Point* point_buffer,
157  IndexT* index_buffer)
158  : point_buffer_(point_buffer), index_buffer_(index_buffer) {}
159 
160  ~FanPathVertexWriter() = default;
161 
162  size_t GetIndexCount() const { return index_count_; }
163  size_t GetPointCount() const { return count_; }
164 
165  void EndContour() override {
166  if (count_ == 0) {
167  return;
168  }
169  index_buffer_[index_count_++] = static_cast<IndexT>(-1);
170  }
171 
172  void Write(impeller::Point point) override {
173  index_buffer_[index_count_++] = count_;
174  point_buffer_[count_++] = point;
175  }
176 
177  private:
178  size_t count_ = 0;
179  size_t index_count_ = 0;
180  impeller::Point* point_buffer_ = nullptr;
181  IndexT* index_buffer_ = nullptr;
182 };
183 
184 /// @brief A vertex writer that generates a triangle strip and requires
185 /// primitive restart.
186 template <typename IndexT>
187 class StripPathVertexWriter : public impeller::PathTessellator::VertexWriter {
188  public:
189  explicit StripPathVertexWriter(impeller::Point* point_buffer,
190  IndexT* index_buffer)
191  : point_buffer_(point_buffer), index_buffer_(index_buffer) {}
192 
193  ~StripPathVertexWriter() = default;
194 
195  size_t GetIndexCount() const { return index_count_; }
196  size_t GetPointCount() const { return count_; }
197 
198  void EndContour() override {
199  if (count_ == 0u || contour_start_ == count_ - 1) {
200  // Empty or first contour.
201  return;
202  }
203 
204  size_t start = contour_start_;
205  size_t end = count_ - 1;
206 
207  index_buffer_[index_count_++] = start;
208 
209  size_t a = start + 1;
210  size_t b = end;
211  while (a < b) {
212  index_buffer_[index_count_++] = a;
213  index_buffer_[index_count_++] = b;
214  a++;
215  b--;
216  }
217  if (a == b) {
218  index_buffer_[index_count_++] = a;
219  }
220 
221  contour_start_ = count_;
222  index_buffer_[index_count_++] = static_cast<IndexT>(-1);
223  }
224 
225  void Write(impeller::Point point) override {
226  point_buffer_[count_++] = point;
227  }
228 
229  private:
230  size_t count_ = 0;
231  size_t index_count_ = 0;
232  size_t contour_start_ = 0;
233  impeller::Point* point_buffer_ = nullptr;
234  IndexT* index_buffer_ = nullptr;
235 };
236 
237 /// @brief A vertex writer that has no hardware requirements.
238 template <typename IndexT>
239 class GLESPathVertexWriter : public impeller::PathTessellator::VertexWriter {
240  public:
241  explicit GLESPathVertexWriter(std::vector<impeller::Point>& points,
242  std::vector<IndexT>& indices)
243  : points_(points), indices_(indices) {}
244 
245  ~GLESPathVertexWriter() = default;
246 
247  void EndContour() override {
248  if (points_.size() == 0u || contour_start_ == points_.size() - 1) {
249  // Empty or first contour.
250  return;
251  }
252 
253  auto start = contour_start_;
254  auto end = points_.size() - 1;
255  // All filled paths are drawn as if they are closed, but if
256  // there is an explicit close then a lineTo to the origin
257  // is inserted. This point isn't strictly necesary to
258  // correctly render the shape and can be dropped.
259  if (points_[end] == points_[start]) {
260  end--;
261  }
262 
263  // Triangle strip break for subsequent contours
264  if (contour_start_ != 0) {
265  auto back = indices_.back();
266  indices_.push_back(back);
267  indices_.push_back(start);
268  indices_.push_back(start);
269 
270  // If the contour has an odd number of points, insert an extra point when
271  // bridging to the next contour to preserve the correct triangle winding
272  // order.
273  if (previous_contour_odd_points_) {
274  indices_.push_back(start);
275  }
276  } else {
277  indices_.push_back(start);
278  }
279 
280  size_t a = start + 1;
281  size_t b = end;
282  while (a < b) {
283  indices_.push_back(a);
284  indices_.push_back(b);
285  a++;
286  b--;
287  }
288  if (a == b) {
289  indices_.push_back(a);
290  previous_contour_odd_points_ = false;
291  } else {
292  previous_contour_odd_points_ = true;
293  }
294  contour_start_ = points_.size();
295  }
296 
297  void Write(impeller::Point point) override { points_.push_back(point); }
298 
299  private:
300  bool previous_contour_odd_points_ = false;
301  size_t contour_start_ = 0u;
302  std::vector<impeller::Point>& points_;
303  std::vector<IndexT>& indices_;
304 };
305 
306 template <typename IndexT>
307 void DoTessellateConvexInternal(const impeller::PathSource& path,
308  std::vector<impeller::Point>& point_buffer,
309  std::vector<IndexT>& index_buffer,
310  impeller::Scalar tolerance) {
311  point_buffer.clear();
312  index_buffer.clear();
313 
314  GLESPathVertexWriter writer(point_buffer, index_buffer);
315 
316  impeller::PathTessellator::PathToFilledVertices(path, writer, tolerance);
317 }
318 
319 } // namespace
320 
321 namespace impeller {
322 
323 template <typename IndexT>
324 class ConvexTessellatorImpl : public Tessellator::ConvexTessellator {
325  public:
327  point_buffer_.reserve(2048);
328  index_buffer_.reserve(2048);
329  }
330 
332  HostBuffer& data_host_buffer,
333  HostBuffer& indexes_host_buffer,
334  Scalar tolerance,
335  bool supports_primitive_restart,
336  bool supports_triangle_fan) override {
337  if (supports_primitive_restart) {
338  // Primitive Restart.
339  const auto [point_count, contour_count] =
340  PathTessellator::CountFillStorage(path, tolerance);
341  BufferView point_buffer = data_host_buffer.Emplace(
342  nullptr, sizeof(Point) * point_count, alignof(Point));
343  BufferView index_buffer = indexes_host_buffer.Emplace(
344  nullptr, sizeof(IndexT) * (point_count + contour_count),
345  alignof(IndexT));
346 
347  auto* points_ptr =
348  reinterpret_cast<Point*>(point_buffer.GetBuffer()->OnGetContents() +
349  point_buffer.GetRange().offset);
350  auto* indices_ptr =
351  reinterpret_cast<IndexT*>(index_buffer.GetBuffer()->OnGetContents() +
352  index_buffer.GetRange().offset);
353 
354  auto tessellate_path = [&](auto& writer) {
355  PathTessellator::PathToFilledVertices(path, writer, tolerance);
356  FML_DCHECK(writer.GetPointCount() <= point_count);
357  FML_DCHECK(writer.GetIndexCount() <= (point_count + contour_count));
358  point_buffer.GetBuffer()->Flush(point_buffer.GetRange());
359  index_buffer.GetBuffer()->Flush(index_buffer.GetRange());
360 
361  return VertexBuffer{
362  .vertex_buffer = std::move(point_buffer),
363  .index_buffer = std::move(index_buffer),
364  .vertex_count = writer.GetIndexCount(),
365  .index_type = IndexTypeFor<IndexT>(),
366  };
367  };
368 
369  if (supports_triangle_fan) {
370  FanPathVertexWriter writer(points_ptr, indices_ptr);
371  return tessellate_path(writer);
372  } else {
373  StripPathVertexWriter writer(points_ptr, indices_ptr);
374  return tessellate_path(writer);
375  }
376  }
377 
378  DoTessellateConvexInternal(path, point_buffer_, index_buffer_, tolerance);
379 
380  if (point_buffer_.empty()) {
381  return VertexBuffer{
382  .vertex_buffer = {},
383  .index_buffer = {},
384  .vertex_count = 0u,
385  .index_type = IndexTypeFor<IndexT>(),
386  };
387  }
388 
389  BufferView vertex_buffer = data_host_buffer.Emplace(
390  point_buffer_.data(), sizeof(Point) * point_buffer_.size(),
391  alignof(Point));
392 
393  BufferView index_buffer = indexes_host_buffer.Emplace(
394  index_buffer_.data(), sizeof(IndexT) * index_buffer_.size(),
395  alignof(IndexT));
396 
397  return VertexBuffer{
398  .vertex_buffer = std::move(vertex_buffer),
399  .index_buffer = std::move(index_buffer),
400  .vertex_count = index_buffer_.size(),
401  .index_type = IndexTypeFor<IndexT>(),
402  };
403  }
404 
405  private:
406  std::vector<Point> point_buffer_;
407  std::vector<IndexT> index_buffer_;
408 };
409 
410 Tessellator::Tessellator(bool supports_32bit_primitive_indices)
411  : stroke_points_(kPointArenaSize) {
412  if (supports_32bit_primitive_indices) {
413  convex_tessellator_ = std::make_unique<ConvexTessellatorImpl<uint32_t>>();
414  } else {
415  convex_tessellator_ = std::make_unique<ConvexTessellatorImpl<uint16_t>>();
416  }
417 }
418 
419 Tessellator::~Tessellator() = default;
420 
421 std::vector<Point>& Tessellator::GetStrokePointCache() {
422  return stroke_points_;
423 }
424 
426  return GetTrigsForDivisions(ComputeQuadrantDivisions(pixel_radius));
427 }
428 
430  HostBuffer& data_host_buffer,
431  HostBuffer& indexes_host_buffer,
432  Scalar tolerance,
433  bool supports_primitive_restart,
434  bool supports_triangle_fan) {
435  return convex_tessellator_->TessellateConvex(
436  path, data_host_buffer, indexes_host_buffer, tolerance,
437  supports_primitive_restart, supports_triangle_fan);
438 }
439 
441  std::vector<Point>& point_buffer,
442  std::vector<uint16_t>& index_buffer,
443  Scalar tolerance) {
444  DoTessellateConvexInternal(path, point_buffer, index_buffer, tolerance);
445 }
446 
448  : Tessellator::Trigs(ComputeQuadrantDivisions(pixel_radius)) {}
449 
450 void Tessellator::Trigs::init(size_t divisions) {
451  if (!trigs_.empty()) {
452  return;
453  }
454 
455  // Either not cached yet, or we are using the temp storage...
456  trigs_.reserve(divisions + 1);
457 
458  double angle_scale = kPiOver2 / divisions;
459 
460  trigs_.emplace_back(1.0, 0.0);
461  for (size_t i = 1; i < divisions; i++) {
462  trigs_.emplace_back(Radians(i * angle_scale));
463  }
464  trigs_.emplace_back(0.0, 1.0);
465 }
466 
467 Tessellator::Trigs Tessellator::GetTrigsForDivisions(size_t divisions) {
468  return divisions < Tessellator::kCachedTrigCount
469  ? Trigs(precomputed_trigs_[divisions], divisions)
470  : Trigs(divisions);
471 }
472 
476 
477 EllipticalVertexGenerator::EllipticalVertexGenerator(
478  EllipticalVertexGenerator::GeneratorProc& generator,
479  Trigs&& trigs,
480  PrimitiveType triangle_type,
481  size_t vertices_per_trig,
482  Data&& data)
483  : impl_(generator),
484  trigs_(std::move(trigs)),
485  data_(data),
486  vertices_per_trig_(vertices_per_trig) {}
487 
488 EllipticalVertexGenerator Tessellator::FilledCircle(
489  const Matrix& view_transform,
490  const Point& center,
491  Scalar radius) {
492  size_t divisions =
493  ComputeQuadrantDivisions(view_transform.GetMaxBasisLengthXY() * radius);
494  return EllipticalVertexGenerator(Tessellator::GenerateFilledCircle,
495  GetTrigsForDivisions(divisions),
496  PrimitiveType::kTriangleStrip, 4,
497  {
498  .reference_centers = {center, center},
499  .radii = {radius, radius},
500  .half_width = -1.0f,
501  });
502 }
503 
504 EllipticalVertexGenerator Tessellator::StrokedCircle(
505  const Matrix& view_transform,
506  const Point& center,
507  Scalar radius,
508  Scalar half_width) {
509  if (half_width > 0) {
510  auto divisions = ComputeQuadrantDivisions(
511  view_transform.GetMaxBasisLengthXY() * radius + half_width);
512  return EllipticalVertexGenerator(Tessellator::GenerateStrokedCircle,
513  GetTrigsForDivisions(divisions),
514  PrimitiveType::kTriangleStrip, 8,
515  {
516  .reference_centers = {center, center},
517  .radii = {radius, radius},
518  .half_width = half_width,
519  });
520  } else {
521  return FilledCircle(view_transform, center, radius);
522  }
523 }
524 
525 ArcVertexGenerator ArcVertexGenerator::MakeFilled(
526  const Arc::Iteration& iteration,
527  Trigs&& trigs,
528  const Rect& oval_bounds,
529  bool use_center,
530  bool supports_triangle_fans) {
531  return ArcVertexGenerator(iteration, std::move(trigs), oval_bounds,
532  use_center, supports_triangle_fans, -1.0f,
533  Cap::kSquare, nullptr);
534 }
535 
536 ArcVertexGenerator ArcVertexGenerator::MakeStroked(
537  const Arc::Iteration& iteration,
538  Trigs&& trigs,
539  const Rect& oval_bounds,
540  Scalar half_width,
541  Cap cap,
542  std::unique_ptr<Trigs> round_cap_trigs) {
543  return ArcVertexGenerator(iteration, std::move(trigs), oval_bounds, false,
544  false, half_width, cap, std::move(round_cap_trigs));
545 }
546 
548  Trigs&& trigs,
549  const Rect& oval_bounds,
550  bool use_center,
551  bool supports_triangle_fans,
552  Scalar half_width,
553  Cap cap,
554  std::unique_ptr<Trigs> round_cap_trigs)
555  : iteration_(iteration),
556  trigs_(std::move(trigs)),
557  oval_bounds_(oval_bounds),
558  use_center_(use_center),
559  supports_triangle_fans_(supports_triangle_fans),
560  half_width_(half_width),
561  cap_(cap),
562  round_cap_trigs_(std::move(round_cap_trigs)) {}
563 
565  return (half_width_ < 0 && supports_triangle_fans_)
568 }
569 
571  size_t count = iteration_.GetPointCount();
572  if (half_width_ > 0) {
573  FML_DCHECK(!use_center_);
574  count *= 2;
575  if (cap_ == Cap::kSquare) {
576  count += 4;
577  } else if (cap_ == Cap::kRound) {
578  FML_DCHECK(round_cap_trigs_);
579  // 4 vertices for each Trig in round_cap_trigs_: 2 vertices for each in
580  // the start cap and 2 for each in the end cap. Subtract 4 because the cap
581  // vertices elide the beginning and end vertices of the arc.
582  count += round_cap_trigs_->size() * 4 - 4;
583  }
584  } else if (supports_triangle_fans_) {
585  if (use_center_) {
586  count++;
587  }
588  } else {
589  // corrugated triangle fan
590  count += (count + 1) / 2;
591  }
592  return count;
593 }
594 
596  const TessellatedVertexProc& proc) const {
597  if (half_width_ > 0) {
598  FML_DCHECK(!use_center_);
599  Tessellator::GenerateStrokedArc(trigs_, iteration_, oval_bounds_,
600  half_width_, cap_, round_cap_trigs_, proc);
601  } else if (supports_triangle_fans_) {
602  Tessellator::GenerateFilledArcFan(trigs_, iteration_, oval_bounds_,
603  use_center_, proc);
604  } else {
605  Tessellator::GenerateFilledArcStrip(trigs_, iteration_, oval_bounds_,
606  use_center_, proc);
607  }
608 }
609 
611  const Arc& arc,
612  bool supports_triangle_fans) {
613  size_t divisions = ComputeQuadrantDivisions(
614  view_transform.GetMaxBasisLengthXY() * arc.GetOvalSize().MaxDimension());
615 
617  arc.ComputeIterations(divisions), GetTrigsForDivisions(divisions),
618  arc.GetOvalBounds(), arc.IncludeCenter(), supports_triangle_fans);
619 };
620 
622  const Arc& arc,
623  Cap cap,
624  Scalar half_width) {
625  FML_DCHECK(half_width > 0);
626  FML_DCHECK(arc.IsPerfectCircle());
627  FML_DCHECK(!arc.IncludeCenter());
628  size_t divisions =
629  ComputeQuadrantDivisions(view_transform.GetMaxBasisLengthXY() *
630  (arc.GetOvalSize().MaxDimension() + half_width));
631 
632  std::unique_ptr<Trigs> round_cap_trigs;
633  if (cap == Cap::kRound) {
634  round_cap_trigs = std::make_unique<Trigs>(GetTrigsForDeviceRadius(
635  view_transform.GetMaxBasisLengthXY() * half_width));
636  }
637 
639  arc.ComputeIterations(divisions), GetTrigsForDivisions(divisions),
640  arc.GetOvalBounds(), half_width, cap, std::move(round_cap_trigs));
641 }
642 
644  const Matrix& view_transform,
645  const Point& p0,
646  const Point& p1,
647  Scalar radius) {
648  auto along = p1 - p0;
649  auto length = along.GetLength();
650  if (length > kEhCloseEnough) {
651  auto divisions =
652  ComputeQuadrantDivisions(view_transform.GetMaxBasisLengthXY() * radius);
653  return EllipticalVertexGenerator(Tessellator::GenerateRoundCapLine,
654  GetTrigsForDivisions(divisions),
656  {
657  .reference_centers = {p0, p1},
658  .radii = {radius, radius},
659  .half_width = -1.0f,
660  });
661  } else {
662  return FilledCircle(view_transform, p0, radius);
663  }
664 }
665 
667  const Matrix& view_transform,
668  const Rect& bounds) {
669  if (bounds.IsSquare()) {
670  return FilledCircle(view_transform, bounds.GetCenter(),
671  bounds.GetWidth() * 0.5f);
672  }
673  auto max_radius = bounds.GetSize().MaxDimension();
674  auto divisions = ComputeQuadrantDivisions(
675  view_transform.GetMaxBasisLengthXY() * max_radius);
676  auto center = bounds.GetCenter();
677  return EllipticalVertexGenerator(Tessellator::GenerateFilledEllipse,
678  GetTrigsForDivisions(divisions),
680  {
681  .reference_centers = {center, center},
682  .radii = bounds.GetSize() * 0.5f,
683  .half_width = -1.0f,
684  });
685 }
686 
688  const Matrix& view_transform,
689  const Rect& bounds,
690  const Size& radii) {
691  if (radii.width * 2 < bounds.GetWidth() ||
692  radii.height * 2 < bounds.GetHeight()) {
693  auto max_radius = radii.MaxDimension();
694  auto divisions = ComputeQuadrantDivisions(
695  view_transform.GetMaxBasisLengthXY() * max_radius);
696  auto upper_left = bounds.GetLeftTop() + radii;
697  auto lower_right = bounds.GetRightBottom() - radii;
698  return EllipticalVertexGenerator(Tessellator::GenerateFilledRoundRect,
699  GetTrigsForDivisions(divisions),
701  {
702  .reference_centers =
703  {
704  upper_left,
705  lower_right,
706  },
707  .radii = radii,
708  .half_width = -1.0f,
709  });
710  } else {
711  return FilledEllipse(view_transform, bounds);
712  }
713 }
714 
715 void Tessellator::GenerateFilledCircle(
716  const Trigs& trigs,
717  const EllipticalVertexGenerator::Data& data,
718  const TessellatedVertexProc& proc) {
719  auto center = data.reference_centers[0];
720  auto radius = data.radii.width;
721 
722  FML_DCHECK(center == data.reference_centers[1]);
723  FML_DCHECK(radius == data.radii.height);
724  FML_DCHECK(data.half_width < 0);
725 
726  // Quadrant 1 connecting with Quadrant 4:
727  for (auto& trig : trigs) {
728  auto offset = trig * radius;
729  proc({center.x - offset.x, center.y + offset.y});
730  proc({center.x - offset.x, center.y - offset.y});
731  }
732 
733  // The second half of the circle should be iterated in reverse, but
734  // we can instead iterate forward and swap the x/y values of the
735  // offset as the angles should be symmetric and thus should generate
736  // symmetrically reversed trig vectors.
737  // Quadrant 2 connecting with Quadrant 3:
738  for (auto& trig : trigs) {
739  auto offset = trig * radius;
740  proc({center.x + offset.y, center.y + offset.x});
741  proc({center.x + offset.y, center.y - offset.x});
742  }
743 }
744 
745 void Tessellator::GenerateStrokedCircle(
746  const Trigs& trigs,
747  const EllipticalVertexGenerator::Data& data,
748  const TessellatedVertexProc& proc) {
749  auto center = data.reference_centers[0];
750 
751  FML_DCHECK(center == data.reference_centers[1]);
752  FML_DCHECK(data.radii.IsSquare());
753  FML_DCHECK(data.half_width > 0 && data.half_width < data.radii.width);
754 
755  auto outer_radius = data.radii.width + data.half_width;
756  auto inner_radius = data.radii.width - data.half_width;
757 
758  // Zig-zag back and forth between points on the outer circle and the
759  // inner circle. Both circles are evaluated at the same number of
760  // quadrant divisions so the points for a given division should match
761  // 1 for 1 other than their applied radius.
762 
763  // Quadrant 1:
764  for (auto& trig : trigs) {
765  auto outer = trig * outer_radius;
766  auto inner = trig * inner_radius;
767  proc({center.x - outer.x, center.y - outer.y});
768  proc({center.x - inner.x, center.y - inner.y});
769  }
770 
771  // The even quadrants of the circle should be iterated in reverse, but
772  // we can instead iterate forward and swap the x/y values of the
773  // offset as the angles should be symmetric and thus should generate
774  // symmetrically reversed trig vectors.
775  // Quadrant 2:
776  for (auto& trig : trigs) {
777  auto outer = trig * outer_radius;
778  auto inner = trig * inner_radius;
779  proc({center.x + outer.y, center.y - outer.x});
780  proc({center.x + inner.y, center.y - inner.x});
781  }
782 
783  // Quadrant 3:
784  for (auto& trig : trigs) {
785  auto outer = trig * outer_radius;
786  auto inner = trig * inner_radius;
787  proc({center.x + outer.x, center.y + outer.y});
788  proc({center.x + inner.x, center.y + inner.y});
789  }
790 
791  // Quadrant 4:
792  for (auto& trig : trigs) {
793  auto outer = trig * outer_radius;
794  auto inner = trig * inner_radius;
795  proc({center.x - outer.y, center.y + outer.x});
796  proc({center.x - inner.y, center.y + inner.x});
797  }
798 }
799 
800 void Tessellator::GenerateFilledArcFan(const Trigs& trigs,
801  const Arc::Iteration& iteration,
802  const Rect& oval_bounds,
803  bool use_center,
804  const TessellatedVertexProc& proc) {
805  Point center = oval_bounds.GetCenter();
806  Size radii = oval_bounds.GetSize() * 0.5f;
807 
808  if (use_center) {
809  proc(center);
810  }
811  proc(center + iteration.start * radii);
812  for (size_t i = 0; i < iteration.quadrant_count; i++) {
813  auto quadrant = iteration.quadrants[i];
814  for (size_t j = quadrant.start_index; j < quadrant.end_index; j++) {
815  proc(center + trigs[j] * quadrant.axis * radii);
816  }
817  }
818  proc(center + iteration.end * radii);
819 }
820 
821 void Tessellator::GenerateFilledArcStrip(const Trigs& trigs,
822  const Arc::Iteration& iteration,
823  const Rect& oval_bounds,
824  bool use_center,
825  const TessellatedVertexProc& proc) {
826  Point center = oval_bounds.GetCenter();
827  Size radii = oval_bounds.GetSize() * 0.5f;
828 
829  Point origin;
830  if (use_center) {
831  origin = center;
832  } else {
833  Point midpoint = (iteration.start + iteration.end) * 0.5f;
834  origin = center + midpoint * radii;
835  }
836 
837  proc(origin);
838  proc(center + iteration.start * radii);
839  bool insert_origin = false;
840  for (size_t i = 0; i < iteration.quadrant_count; i++) {
841  auto quadrant = iteration.quadrants[i];
842  for (size_t j = quadrant.start_index; j < quadrant.end_index; j++) {
843  if (insert_origin) {
844  proc(origin);
845  }
846  insert_origin = !insert_origin;
847  proc(center + trigs[j] * quadrant.axis * radii);
848  }
849  }
850  if (insert_origin) {
851  proc(origin);
852  }
853  proc(center + iteration.end * radii);
854 }
855 
856 void Tessellator::GenerateStrokedArc(
857  const Trigs& trigs,
858  const Arc::Iteration& iteration,
859  const Rect& oval_bounds,
860  Scalar half_width,
861  Cap cap,
862  const std::unique_ptr<Trigs>& round_cap_trigs,
863  const TessellatedVertexProc& proc) {
864  Point center = oval_bounds.GetCenter();
865  Size base_radii = oval_bounds.GetSize() * 0.5f;
866  Size inner_radii = base_radii - Size(half_width, half_width);
867  Size outer_radii = base_radii + Size(half_width, half_width);
868 
869  // Starting cap
870  switch (cap) {
871  case Cap::kButt:
872  proc(center + iteration.start * inner_radii);
873  proc(center + iteration.start * outer_radii);
874  break;
875  case Cap::kRound:
876  FML_DCHECK(round_cap_trigs);
877  Tessellator::GenerateStartRoundCap(center + iteration.start * base_radii,
878  -iteration.start * half_width,
879  *round_cap_trigs, proc);
880  break;
881  case Cap::kSquare:
882  Vector2 offset =
883  Vector2{iteration.start.y, -iteration.start.x} * half_width;
884  proc(center + iteration.start * inner_radii + offset);
885  proc(center + iteration.start * outer_radii + offset);
886  proc(center + iteration.start * inner_radii);
887  proc(center + iteration.start * outer_radii);
888  break;
889  }
890 
891  for (size_t i = 0; i < iteration.quadrant_count; i++) {
892  auto quadrant = iteration.quadrants[i];
893  for (size_t j = quadrant.start_index; j < quadrant.end_index; j++) {
894  proc(center + trigs[j] * quadrant.axis * inner_radii);
895  proc(center + trigs[j] * quadrant.axis * outer_radii);
896  }
897  }
898 
899  // Ending cap
900  switch (cap) {
901  case Cap::kButt:
902  proc(center + iteration.end * inner_radii);
903  proc(center + iteration.end * outer_radii);
904  break;
905  case Cap::kRound:
906  FML_DCHECK(round_cap_trigs);
907  Tessellator::GenerateEndRoundCap(center + iteration.end * base_radii,
908  -iteration.end * half_width,
909  *round_cap_trigs, proc);
910  break;
911  case Cap::kSquare:
912  Vector2 offset = Vector2{-iteration.end.y, iteration.end.x} * half_width;
913  proc(center + iteration.end * inner_radii);
914  proc(center + iteration.end * outer_radii);
915  proc(center + iteration.end * inner_radii + offset);
916  proc(center + iteration.end * outer_radii + offset);
917  break;
918  }
919 }
920 
921 void Tessellator::GenerateRoundCapLine(
922  const Trigs& trigs,
923  const EllipticalVertexGenerator::Data& data,
924  const TessellatedVertexProc& proc) {
925  auto p0 = data.reference_centers[0];
926  auto p1 = data.reference_centers[1];
927  auto radius = data.radii.width;
928 
929  FML_DCHECK(radius == data.radii.height);
930  FML_DCHECK(data.half_width < 0);
931 
932  auto along = p1 - p0;
933  along *= radius / along.GetLength();
934  auto across = Point(-along.y, along.x);
935 
936  Tessellator::GenerateStartRoundCap(p0, across, trigs, proc);
937  Tessellator::GenerateEndRoundCap(p1, across, trigs, proc);
938 }
939 
940 void Tessellator::GenerateFilledEllipse(
941  const Trigs& trigs,
942  const EllipticalVertexGenerator::Data& data,
943  const TessellatedVertexProc& proc) {
944  auto center = data.reference_centers[0];
945  auto radii = data.radii;
946 
947  FML_DCHECK(center == data.reference_centers[1]);
948  FML_DCHECK(data.half_width < 0);
949 
950  // Quadrant 1 connecting with Quadrant 4:
951  for (auto& trig : trigs) {
952  auto offset = trig * radii;
953  proc({center.x - offset.x, center.y + offset.y});
954  proc({center.x - offset.x, center.y - offset.y});
955  }
956 
957  // The second half of the circle should be iterated in reverse, but
958  // we can instead iterate forward and swap the x/y values of the
959  // offset as the angles should be symmetric and thus should generate
960  // symmetrically reversed trig vectors.
961  // Quadrant 2 connecting with Quadrant 2:
962  for (auto& trig : trigs) {
963  auto offset = Point(trig.sin * radii.width, trig.cos * radii.height);
964  proc({center.x + offset.x, center.y + offset.y});
965  proc({center.x + offset.x, center.y - offset.y});
966  }
967 }
968 
969 void Tessellator::GenerateFilledRoundRect(
970  const Trigs& trigs,
971  const EllipticalVertexGenerator::Data& data,
972  const TessellatedVertexProc& proc) {
973  Scalar left = data.reference_centers[0].x;
974  Scalar top = data.reference_centers[0].y;
975  Scalar right = data.reference_centers[1].x;
976  Scalar bottom = data.reference_centers[1].y;
977  auto radii = data.radii;
978 
979  FML_DCHECK(data.half_width < 0);
980 
981  // Quadrant 1 connecting with Quadrant 4:
982  for (auto& trig : trigs) {
983  auto offset = trig * radii;
984  proc({left - offset.x, bottom + offset.y});
985  proc({left - offset.x, top - offset.y});
986  }
987 
988  // The second half of the round rect should be iterated in reverse, but
989  // we can instead iterate forward and swap the x/y values of the
990  // offset as the angles should be symmetric and thus should generate
991  // symmetrically reversed trig vectors.
992  // Quadrant 2 connecting with Quadrant 2:
993  for (auto& trig : trigs) {
994  auto offset = Point(trig.sin * radii.width, trig.cos * radii.height);
995  proc({right + offset.x, bottom + offset.y});
996  proc({right + offset.x, top - offset.y});
997  }
998 }
999 
1000 void Tessellator::GenerateStartRoundCap(const Point& p,
1001  const Vector2& perpendicular,
1002  const Trigs& trigs,
1003  const TessellatedVertexProc& proc) {
1004  Point along(perpendicular.y, -perpendicular.x);
1005 
1006  for (auto& trig : trigs) {
1007  auto relative_along = along * trig.cos;
1008  auto relative_across = perpendicular * trig.sin;
1009  proc(p - relative_along + relative_across);
1010  proc(p - relative_along - relative_across);
1011  }
1012 }
1013 
1014 void Tessellator::GenerateEndRoundCap(const Point& p,
1015  const Vector2& perpendicular,
1016  const Trigs& trigs,
1017  const TessellatedVertexProc& proc) {
1018  Point along(perpendicular.y, -perpendicular.x);
1019 
1020  // The ending round cap should be iterated in reverse, but
1021  // we can instead iterate forward and swap the sin/cos values as they
1022  // should be symmetric.
1023  for (auto& trig : trigs) {
1024  auto relative_along = along * trig.sin;
1025  auto relative_across = perpendicular * trig.cos;
1026  proc(p + relative_along + relative_across);
1027  proc(p + relative_along - relative_across);
1028  }
1029 }
1030 
1031 } // namespace impeller
bool use_center
VertexBuffer TessellateConvex(const PathSource &path, HostBuffer &data_host_buffer, HostBuffer &indexes_host_buffer, Scalar tolerance, bool supports_primitive_restart, bool supports_triangle_fan) override
Definition: tessellator.cc:331
virtual void Flush(std::optional< Range > range=std::nullopt) const
virtual uint8_t * OnGetContents() const =0
BufferView Emplace(const BufferType &buffer, size_t alignment=0)
Emplace non-uniform data (like contiguous vertices) onto the host buffer.
Definition: host_buffer.h:92
An interface for generating a multi contour polyline as a triangle strip.
virtual void Write(Point point)=0
static void PathToFilledVertices(const PathSource &source, VertexWriter &writer, Scalar scale)
static std::pair< size_t, size_t > CountFillStorage(const PathSource &source, Scalar scale)
The |VertexGenerator| implementation common to all shapes that are based on a polygonal representatio...
Definition: tessellator.h:191
static ArcVertexGenerator MakeStroked(const Arc::Iteration &iteration, Trigs &&trigs, const Rect &oval_bounds, Scalar half_width, Cap cap, std::unique_ptr< Trigs > round_cap_trigs)
Definition: tessellator.cc:536
size_t GetVertexCount() const override
|VertexGenerator|
Definition: tessellator.cc:570
PrimitiveType GetTriangleType() const override
|VertexGenerator|
Definition: tessellator.cc:564
static ArcVertexGenerator MakeFilled(const Arc::Iteration &iteration, Trigs &&trigs, const Rect &oval_bounds, bool use_center, bool supports_triangle_fans)
Definition: tessellator.cc:525
void GenerateVertices(const TessellatedVertexProc &proc) const override
|VertexGenerator|
Definition: tessellator.cc:595
The |VertexGenerator| implementation common to all shapes that are based on a polygonal representatio...
Definition: tessellator.h:140
Trigs(Scalar pixel_radius)
Definition: tessellator.cc:447
A utility that generates triangles of the specified fill type given a polyline. This happens on the C...
Definition: tessellator.h:37
Trigs GetTrigsForDeviceRadius(Scalar pixel_radius)
Definition: tessellator.cc:425
EllipticalVertexGenerator RoundCapLine(const Matrix &view_transform, const Point &p0, const Point &p1, Scalar radius)
Create a |VertexGenerator| that can produce vertices for a line with round end caps of the given radi...
Definition: tessellator.cc:643
std::vector< Point > & GetStrokePointCache()
Retrieve a pre-allocated arena of kPointArenaSize points.
Definition: tessellator.cc:421
EllipticalVertexGenerator FilledRoundRect(const Matrix &view_transform, const Rect &bounds, const Size &radii)
Create a |VertexGenerator| that can produce vertices for a filled round rect within the given bounds ...
Definition: tessellator.cc:687
static constexpr Scalar kCircleTolerance
The pixel tolerance used by the algorighm to determine how many divisions to create for a circle.
Definition: tessellator.h:280
VertexBuffer TessellateConvex(const PathSource &path, HostBuffer &data_host_buffer, HostBuffer &indexes_host_buffer, Scalar tolerance, bool supports_primitive_restart=false, bool supports_triangle_fan=false)
Given a convex path, create a triangle fan structure.
Definition: tessellator.cc:429
EllipticalVertexGenerator FilledCircle(const Matrix &view_transform, const Point &center, Scalar radius)
Create a |VertexGenerator| that can produce vertices for a filled circle of the given radius around t...
Definition: tessellator.cc:488
ArcVertexGenerator StrokedArc(const Matrix &view_transform, const Arc &arc, Cap cap, Scalar half_width)
Create a |VertexGenerator| that can produce vertices for a stroked arc inscribed within the given ova...
Definition: tessellator.cc:621
static void TessellateConvexInternal(const PathSource &path, std::vector< Point > &point_buffer, std::vector< uint16_t > &index_buffer, Scalar tolerance)
Definition: tessellator.cc:440
EllipticalVertexGenerator FilledEllipse(const Matrix &view_transform, const Rect &bounds)
Create a |VertexGenerator| that can produce vertices for a filled ellipse inscribed within the given ...
Definition: tessellator.cc:666
Tessellator(bool supports_32bit_primitive_indices=true)
Definition: tessellator.cc:410
std::function< void(const Point &p)> TessellatedVertexProc
A callback function for a |VertexGenerator| to deliver the vertices it computes as |Point| objects.
Definition: tessellator.h:97
ArcVertexGenerator FilledArc(const Matrix &view_transform, const Arc &arc, bool supports_triangle_fans)
Create a |VertexGenerator| that can produce vertices for a stroked arc inscribed within the given ova...
Definition: tessellator.cc:610
Tessellator::TessellatedVertexProc TessellatedVertexProc
Definition: tessellator.cc:473
PrimitiveType
Decides how backend draws pixels based on input vertices.
Definition: formats.h:355
Point Vector2
Definition: point.h:331
float Scalar
Definition: scalar.h:19
constexpr float kEhCloseEnough
Definition: constants.h:57
TRect< Scalar > Rect
Definition: rect.h:788
TPoint< Scalar > Point
Definition: point.h:327
Cap
An enum that describes ways to decorate the end of a path contour.
constexpr float kPiOver2
Definition: constants.h:32
Tessellator::EllipticalVertexGenerator EllipticalVertexGenerator
Definition: tessellator.cc:474
Tessellator::ArcVertexGenerator ArcVertexGenerator
Definition: tessellator.cc:475
constexpr float kPiOver4
Definition: constants.h:35
TSize< Scalar > Size
Definition: size.h:159
static constexpr size_t kPointArenaSize
The size of the point arena buffer stored on the tessellator.
Definition: tessellator.h:25
Definition: comparable.h:95
size_t GetPointCount() const
Definition: arc.cc:36
Iteration ComputeIterations(size_t step_count, bool simplify_360=true) const
Definition: arc.cc:102
constexpr bool IncludeCenter() const
Definition: arc.h:110
const Size GetOvalSize() const
Returns the size of the oval bounds.
Definition: arc.h:100
constexpr bool IsPerfectCircle() const
Definition: arc.h:112
const Rect & GetOvalBounds() const
Return the bounds of the oval in which this arc is inscribed.
Definition: arc.h:94
Range GetRange() const
Definition: buffer_view.h:27
const DeviceBuffer * GetBuffer() const
Definition: buffer_view.cc:17
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
Scalar GetMaxBasisLengthXY() const
Return the maximum scale applied specifically to either the X axis or Y axis unit vectors (the bases)...
Definition: matrix.h:328
size_t offset
Definition: range.h:14
constexpr Type GetLength() const
Definition: point.h:206
constexpr Type GetHeight() const
Returns the height of the rectangle, equivalent to |GetSize().height|.
Definition: rect.h:347
constexpr TSize< Type > GetSize() const
Returns the size of the rectangle which may be negative in either width or height and may have been c...
Definition: rect.h:327
constexpr bool IsSquare() const
Returns true if width and height are equal and neither is NaN.
Definition: rect.h:304
constexpr Type GetWidth() const
Returns the width of the rectangle, equivalent to |GetSize().width|.
Definition: rect.h:341
constexpr TPoint< T > GetRightBottom() const
Definition: rect.h:371
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition: rect.h:382
constexpr TPoint< T > GetLeftTop() const
Definition: rect.h:359
constexpr Type MaxDimension() const
Definition: size.h:106
Type height
Definition: size.h:29
Type width
Definition: size.h:28
const size_t start
const size_t end
std::vector< Point > points
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:69