diff --git a/impeller/aiks/aiks_context.cc b/impeller/aiks/aiks_context.cc index e724ac597af9a..7b029dc7028b5 100644 --- a/impeller/aiks/aiks_context.cc +++ b/impeller/aiks/aiks_context.cc @@ -5,6 +5,7 @@ #include "impeller/aiks/aiks_context.h" #include "impeller/aiks/picture.h" +#include "impeller/entity/contents/tessellation_cache.h" namespace impeller { @@ -42,7 +43,11 @@ bool AiksContext::Render(const Picture& picture, RenderTarget& render_target) { } if (picture.pass) { - return picture.pass->Render(*content_context_, render_target); + auto res = picture.pass->Render(*content_context_, render_target); + // FIXME(knopp): This should be called for the last surface of the frame, + // but there's currently no way to do this. + content_context_->GetTessellationCache().FinishFrame(); + return res; } return true; diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 84cb7544fdcd7..8f22ae5141672 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -22,6 +22,11 @@ namespace impeller { +namespace { +const uint32_t kTagDlRRect = 'dlrr'; +const uint32_t kTagDlCircle = 'dlci'; +} // namespace + Canvas::Canvas() { Initialize(std::nullopt); } @@ -242,6 +247,11 @@ void Canvas::DrawRRect(Rect rect, Scalar corner_radius, const Paint& paint) { .SetConvexity(Convexity::kConvex) .AddRoundedRect(rect, corner_radius) .TakePath(); + struct { + Rect rect; + Scalar corner_radius; + } identifier = {.rect = rect, .corner_radius = corner_radius}; + path.SetPathIdentifier(PathIdentifier(kTagDlRRect, identifier)); if (paint.style == Paint::Style::kFill) { Entity entity; entity.SetTransformation(GetCurrentTransformation()); @@ -266,6 +276,11 @@ void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) { .AddCircle(center, radius) .SetConvexity(Convexity::kConvex) .TakePath(); + struct { + Point center; + Scalar radius; + } identifier = {.center = center, .radius = radius}; + circle_path.SetPathIdentifier(PathIdentifier(kTagDlCircle, identifier)); DrawPath(circle_path, paint); } diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index c2d65bc260811..e0f5206ecdd2e 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -889,10 +889,7 @@ void DlDispatcher::drawRRect(const SkRRect& rrect) { // |flutter::DlOpReceiver| void DlDispatcher::drawDRRect(const SkRRect& outer, const SkRRect& inner) { - PathBuilder builder; - builder.AddPath(skia_conversions::ToPath(outer)); - builder.AddPath(skia_conversions::ToPath(inner)); - canvas_.DrawPath(builder.TakePath(FillType::kOdd), paint_); + canvas_.DrawPath(skia_conversions::ToPath(outer, inner), paint_); } // |flutter::DlOpReceiver| diff --git a/impeller/display_list/skia_conversions.cc b/impeller/display_list/skia_conversions.cc index 0fdeeab6adbd3..95f5f7f3e0994 100644 --- a/impeller/display_list/skia_conversions.cc +++ b/impeller/display_list/skia_conversions.cc @@ -9,6 +9,12 @@ namespace impeller { namespace skia_conversions { +namespace { +const uint32_t kTagSkiaPath = 'skpa'; +const uint32_t kTagSkiaRRect = 'skrr'; +const uint32_t kTagSkiaDRRect = 'skdr'; +} // namespace + Rect ToRect(const SkRect& rect) { return Rect::MakeLTRB(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); } @@ -121,14 +127,47 @@ Path ToPath(const SkPath& path) { } builder.SetConvexity(path.isConvex() ? Convexity::kConvex : Convexity::kUnknown); - return builder.TakePath(fill_type); + auto result = builder.TakePath(fill_type); + struct { + uint32_t generation_id; + FillType fill_type; + } identifier = { + .generation_id = path.getGenerationID(), + .fill_type = fill_type, + }; + result.SetPathIdentifier(PathIdentifier(kTagSkiaPath, identifier)); + return result; } Path ToPath(const SkRRect& rrect) { - return PathBuilder{} - .AddRoundedRect(ToRect(rrect.getBounds()), ToRoundingRadii(rrect)) - .SetConvexity(Convexity::kConvex) - .TakePath(); + auto path = + PathBuilder{} + .AddRoundedRect(ToRect(rrect.getBounds()), ToRoundingRadii(rrect)) + .SetConvexity(Convexity::kConvex) + .TakePath(); + struct { + SkRRect rrect; + } identifier = { + .rrect = rrect, + }; + path.SetPathIdentifier(PathIdentifier(kTagSkiaRRect, identifier)); + return path; +} + +Path ToPath(const SkRRect& outer, const SkRRect& inner) { + PathBuilder builder; + builder.AddPath(ToPath(outer)); + builder.AddPath(ToPath(inner)); + auto path = builder.TakePath(FillType::kOdd); + struct { + SkRRect outer; + SkRRect inner; + } identifier = { + .outer = outer, + .inner = inner, + }; + path.SetPathIdentifier(PathIdentifier(kTagSkiaDRRect, identifier)); + return path; } Point ToPoint(const SkPoint& point) { diff --git a/impeller/display_list/skia_conversions.h b/impeller/display_list/skia_conversions.h index c56cf298a932f..d4345e0c75ff1 100644 --- a/impeller/display_list/skia_conversions.h +++ b/impeller/display_list/skia_conversions.h @@ -42,6 +42,8 @@ Path ToPath(const SkPath& path); Path ToPath(const SkRRect& rrect); +Path ToPath(const SkRRect& outer, const SkRRect& inner); + Path PathDataFromTextBlob(const sk_sp& blob); std::optional ToPixelFormat(SkColorType type); diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index a09b7f965d4f0..26760a820449d 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -197,6 +197,8 @@ impeller_component("entity") { "contents/gradient_generator.h", "contents/linear_gradient_contents.cc", "contents/linear_gradient_contents.h", + "contents/tessellation_cache.cc", + "contents/tessellation_cache.h", "contents/radial_gradient_contents.cc", "contents/radial_gradient_contents.h", "contents/runtime_effect_contents.cc", @@ -261,6 +263,10 @@ impeller_component("entity") { "../typographer", ] + cflags = [ + "-g", + ] + deps = [ "//flutter/fml" ] } diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index ac398a11c5a63..4fbe1ed6f8f71 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -9,6 +9,7 @@ #include "impeller/base/strings.h" #include "impeller/core/formats.h" +#include "impeller/entity/contents/tessellation_cache.h" #include "impeller/entity/entity.h" #include "impeller/renderer/command_buffer.h" #include "impeller/renderer/pipeline_library.h" @@ -162,6 +163,7 @@ ContentContext::ContentContext(std::shared_ptr context) : context_(std::move(context)), lazy_glyph_atlas_(std::make_shared()), tessellator_(std::make_shared()), + tessellation_cache_(std::make_unique()), scene_context_(std::make_shared(context_)) { if (!context_ || !context_->IsValid()) { return; diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index cab89c125333a..d2ceb4eb4f093 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -329,6 +329,7 @@ struct ContentContextOptions { }; class Tessellator; +class TessellationCache; class ContentContext { public: @@ -342,6 +343,10 @@ class ContentContext { std::shared_ptr GetTessellator() const; + TessellationCache& GetTessellationCache() const { + return *tessellation_cache_; + } + #ifdef IMPELLER_DEBUG std::shared_ptr> GetCheckerboardPipeline( ContentContextOptions opts) const { @@ -853,6 +858,7 @@ class ContentContext { bool is_valid_ = false; std::shared_ptr tessellator_; + std::unique_ptr tessellation_cache_; std::shared_ptr scene_context_; bool wireframe_ = false; diff --git a/impeller/entity/contents/tessellation_cache.cc b/impeller/entity/contents/tessellation_cache.cc new file mode 100644 index 0000000000000..405901b9fc454 --- /dev/null +++ b/impeller/entity/contents/tessellation_cache.cc @@ -0,0 +1,70 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/entity/contents/tessellation_cache.h" + +#define IMPELLER_ENABLE_TESSELLATION_CACHE 1 + +namespace impeller { + +Path::Polyline TessellationCache::GetOrCreatePolyline( + const impeller::Path& path, + Scalar scale) { +#if IMPELLER_ENABLE_TESSELLATION_CACHE == 1 + auto path_identifier = path.GetPathIdentifier(); + if (!path_identifier) { + // We can't match path without a valid identifier. + return path.CreatePolyline(scale); + } + PolylineKey key{*path_identifier, scale}; + auto result = polyline_cache_.Get(key); + if (result) { + return *result; + } + auto polyline = path.CreatePolyline(scale); + polyline_cache_.Set(key, polyline); + return polyline; +#else + return path.CreatePolyline(scale); +#endif +} + +Tessellator::Result TessellationCache::Tessellate( + const Tessellator& tesselator, + FillType fill_type, + const Path::Polyline& polyline, + const Tessellator::BuilderCallback& callback) { +#if IMPELLER_ENABLE_TESSELLATION_CACHE == 1 + auto path_identifier = polyline.original_path_identifier; + if (!path_identifier) { + return tesselator.Tessellate(fill_type, polyline, callback); + } + + TessellatorKey key{*path_identifier, fill_type}; + auto result = tessellator_cache_.Get(key); + if (result) { + if (callback(result->vertices.data(), result->vertices.size(), + result->indices.data(), result->indices.size())) { + return Tessellator::Result::kSuccess; + } else { + return Tessellator::Result::kInputError; + } + } + + return tesselator.Tessellate( + fill_type, polyline, + [&](const float* vertices, size_t vertices_size, const uint16_t* indices, + size_t indices_size) { + TessellatorData data; + data.vertices.assign(vertices, vertices + vertices_size); + data.indices.assign(indices, indices + indices_size); + tessellator_cache_.Set(key, data); + return callback(vertices, vertices_size, indices, indices_size); + }); +#else + return tesselator.Tessellate(fill_type, polyline, callback); +#endif +} + +} // namespace impeller diff --git a/impeller/entity/contents/tessellation_cache.h b/impeller/entity/contents/tessellation_cache.h new file mode 100644 index 0000000000000..6ba59b3053110 --- /dev/null +++ b/impeller/entity/contents/tessellation_cache.h @@ -0,0 +1,130 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "flutter/fml/logging.h" +#include "impeller/geometry/path.h" +#include "impeller/tessellator/tessellator.h" + +namespace impeller { + +class TessellationCache { + public: + Path::Polyline GetOrCreatePolyline(const impeller::Path& path, Scalar scale); + + Tessellator::Result Tessellate(const Tessellator& tesselator, + FillType fill_type, + const Path::Polyline& polyline, + const Tessellator::BuilderCallback& callback); + + void FinishFrame() { + int cache_hits, cache_misses; + polyline_cache_.FinishFrame(cache_hits, cache_misses); + FML_LOG(INFO) << "PolylineCache: " << cache_hits << " hits, " + << cache_misses << " misses"; + + tessellator_cache_.FinishFrame(cache_hits, cache_misses); + FML_LOG(INFO) << "TessellatorCache: " << cache_hits << " hits, " + << cache_misses << " misses"; + } + + private: + struct PolylineKey { + PathIdentifier path_identifier; + Scalar scale; + + bool operator==(const PolylineKey& other) const { + return path_identifier == other.path_identifier && scale == other.scale; + } + }; + + struct PolylineKeyHash { + size_t operator()(const PolylineKey& key) const { + return PathIdentifier::Hash()(key.path_identifier) + + 31 * std::hash()(key.scale); + } + }; + + struct TessellatorKey { + PathIdentifier path_identifier; + FillType fill_type; + + bool operator==(const TessellatorKey& other) const { + return path_identifier == other.path_identifier && + fill_type == other.fill_type; + } + }; + + struct TessellatorKeyHash { + size_t operator()(const TessellatorKey& key) const { + return PathIdentifier::Hash()(key.path_identifier) + + 31 * std::hash()(key.fill_type); + } + }; + + struct TessellatorData { + std::vector vertices; + std::vector indices; + }; + + template + class FrameAwareCache { + public: + std::optional Get(const K& key) { + auto it = used_.find(key); + if (it != used_.end()) { + ++cache_hit_count_; + return it->second; + } + it = maybe_remove_.find(key); + if (it != maybe_remove_.end()) { + ++cache_hit_count_; + // promote maybe_remove_ to persistent_ + auto insert_it = used_.emplace(it->first, std::move(it->second)); + maybe_remove_.erase(it); + return insert_it.first->second; + } + ++cache_miss_count_; + return std::nullopt; + } + + void Set(const K key, V value) { used_.emplace(key, std::move(value)); } + + /// Returns number of cache misses for this frame. + void FinishFrame(int& cache_hits, int& cache_misses) { + maybe_remove_ = std::move(used_); + used_.clear(); + cache_hits = cache_hit_count_; + cache_misses = cache_miss_count_; + cache_hit_count_ = 0; + cache_miss_count_ = 0; + } + + private: + // Items used during this frame. + std::unordered_map used_; + + // Items that will be removed at the end of the frame unless they are + // accessed. + std::unordered_map maybe_remove_; + + int cache_miss_count_ = 0; + int cache_hit_count_ = 0; + }; + + FrameAwareCache> + polyline_cache_; + + FrameAwareCache> + tessellator_cache_; +}; + +} // namespace impeller \ No newline at end of file diff --git a/impeller/entity/geometry/fill_path_geometry.cc b/impeller/entity/geometry/fill_path_geometry.cc index b608ed0428ae1..af9b38e03b0a0 100644 --- a/impeller/entity/geometry/fill_path_geometry.cc +++ b/impeller/entity/geometry/fill_path_geometry.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "impeller/entity/geometry/fill_path_geometry.h" +#include "impeller/entity/contents/tessellation_cache.h" namespace impeller { @@ -21,8 +22,9 @@ GeometryResult FillPathGeometry::GetPositionBuffer( if (path_.GetFillType() == FillType::kNonZero && // path_.IsConvex()) { - auto [points, indices] = TessellateConvex( - path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength())); + auto [points, indices] = + TessellateConvex(renderer.GetTessellationCache().GetOrCreatePolyline( + path_, entity.GetTransformation().GetMaxBasisLength())); vertex_buffer.vertex_buffer = host_buffer.Emplace( points.data(), points.size() * sizeof(Point), alignof(Point)); @@ -40,9 +42,10 @@ GeometryResult FillPathGeometry::GetPositionBuffer( }; } - auto tesselation_result = renderer.GetTessellator()->Tessellate( - path_.GetFillType(), - path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()), + auto tesselation_result = renderer.GetTessellationCache().Tessellate( + *renderer.GetTessellator(), path_.GetFillType(), + renderer.GetTessellationCache().GetOrCreatePolyline( + path_, entity.GetTransformation().GetMaxBasisLength()), [&vertex_buffer, &host_buffer]( const float* vertices, size_t vertices_count, const uint16_t* indices, size_t indices_count) { @@ -106,8 +109,8 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer( } VertexBufferBuilder vertex_builder; - auto tesselation_result = renderer.GetTessellator()->Tessellate( - path_.GetFillType(), + auto tesselation_result = renderer.GetTessellationCache().Tessellate( + *renderer.GetTessellator(), path_.GetFillType(), path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()), [&vertex_builder, &texture_coverage, &effect_transform]( const float* vertices, size_t vertices_count, const uint16_t* indices, diff --git a/impeller/geometry/BUILD.gn b/impeller/geometry/BUILD.gn index 2afb29631c8e1..a943e324aa37f 100644 --- a/impeller/geometry/BUILD.gn +++ b/impeller/geometry/BUILD.gn @@ -13,6 +13,7 @@ impeller_component("geometry") { "gradient.cc", "gradient.h", "half.h", + "local_array.h", "matrix.cc", "matrix.h", "matrix_decomposition.cc", diff --git a/impeller/geometry/local_array.h b/impeller/geometry/local_array.h new file mode 100644 index 0000000000000..325d6a23911fc --- /dev/null +++ b/impeller/geometry/local_array.h @@ -0,0 +1,52 @@ + +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "fml/logging.h" + +namespace impeller { + +/// Simple array with static capacity that does not allocate memory on heap. +template +class LocalArray { + static_assert(std::is_trivially_destructible_v, + "Type must be trivially destructible"); + + public: + T* data() { return data_.data(); } + const T* data() const { return data_.data(); } + + size_t size() const { return size_; } + size_t capacity() const { return Capacity; } + + T& operator[](size_t index) { + FML_DCHECK(index < size_); + return data_[index]; + } + + const T& operator[](size_t index) const { + FML_DCHECK(index < size_); + return data_[index]; + } + + void clear() { size_ = 0; } + + T* begin() { return data_.begin(); } + const T* begin() const { return data_.begin(); } + + T* end() { return data_.begin() + size_; } + const T* end() const { return data_.begin() + size_; } + + void push_back(const T& value) { + FML_DCHECK(size_ < Capacity); + data_[size_++] = value; + } + + private: + std::array data_; + size_t size_ = 0; +}; +} // namespace impeller diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc index 1f677294f89df..9bdc6b0f35fb1 100644 --- a/impeller/geometry/path.cc +++ b/impeller/geometry/path.cc @@ -366,6 +366,7 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const { } end_contour(); } + polyline.original_path_identifier = GetPathIdentifier(); return polyline; } diff --git a/impeller/geometry/path.h b/impeller/geometry/path.h index 78a6e5f6647e2..dcd1ff033c6a0 100644 --- a/impeller/geometry/path.h +++ b/impeller/geometry/path.h @@ -39,6 +39,29 @@ enum class Convexity { kConvex, }; +/// Unique identifier for a path. +class PathIdentifier { + public: + template + PathIdentifier(uint32_t tag, const T& value) + : tag_(tag), data_(reinterpret_cast(&value), sizeof(T)) { + hash_ = tag + 31 * std::hash{}(data_); + } + + bool operator==(const PathIdentifier& other) const { + return tag_ == other.tag_ && hash_ == other.hash_ && data_ == other.data_; + } + + struct Hash { + size_t operator()(const PathIdentifier& id) const { return id.hash_; } + }; + + private: + uint32_t tag_; + std::string data_; // std::string used because of SSO. + size_t hash_; +}; + //------------------------------------------------------------------------------ /// @brief Paths are lightweight objects that describe a collection of /// linear, quadratic, or cubic segments. These segments may be @@ -81,6 +104,9 @@ class Path { std::vector points; std::vector contours; + /// Generation ID of the path that this polyline was generated from. + std::optional original_path_identifier; + /// Convenience method to compute the start (inclusive) and end (exclusive) /// point of the given contour index. /// @@ -154,6 +180,15 @@ class Path { std::optional> GetMinMaxCoveragePoints() const; + /// The creator of path might have assigned it a unique identifier that can + /// be used to match identical paths across frames. This may come from + /// SkPath::getGenerationID(). + std::optional GetPathIdentifier() const { + return path_identifier_; + } + + void SetPathIdentifier(PathIdentifier id) { path_identifier_ = id; } + private: friend class PathBuilder; @@ -176,6 +211,7 @@ class Path { std::vector quads_; std::vector cubics_; std::vector contours_; + std::optional path_identifier_ = std::nullopt; }; } // namespace impeller diff --git a/impeller/geometry/path_component.cc b/impeller/geometry/path_component.cc index 14848ccbea9d4..4bfea30e64817 100644 --- a/impeller/geometry/path_component.cc +++ b/impeller/geometry/path_component.cc @@ -63,8 +63,11 @@ std::vector LinearPathComponent::CreatePolyline() const { return {p2}; } -std::vector LinearPathComponent::Extrema() const { - return {p1, p2}; +LinearPathComponent::ExtremaType LinearPathComponent::Extrema() const { + ExtremaType res; + res.push_back(p1); + res.push_back(p2); + return res; } std::optional LinearPathComponent::GetStartDirection() const { @@ -148,7 +151,7 @@ void QuadraticPathComponent::FillPointsForPolyline(std::vector& points, points.emplace_back(p2); } -std::vector QuadraticPathComponent::Extrema() const { +QuadraticPathComponent::ExtremaType QuadraticPathComponent::Extrema() const { CubicPathComponent elevated(*this); return elevated.Extrema(); } @@ -252,11 +255,13 @@ static inline bool NearZero(Scalar a) { return NearEqual(a, 0.0, 1e-12); } -static void CubicPathBoundingPopulateValues(std::vector& values, - Scalar p1, - Scalar p2, - Scalar p3, - Scalar p4) { +template +static void CubicPathBoundingPopulateValues( + LocalArray& values, + Scalar p1, + Scalar p2, + Scalar p3, + Scalar p4) { const Scalar a = 3.0 * (-p1 + 3.0 * p2 - 3.0 * p3 + p4); const Scalar b = 6.0 * (p1 - 2.0 * p2 + p3); const Scalar c = 3.0 * (p2 - p1); @@ -271,7 +276,7 @@ static void CubicPathBoundingPopulateValues(std::vector& values, Scalar t = -c / b; if (t >= 0.0 && t <= 1.0) { - values.emplace_back(t); + values.push_back(t); } return; } @@ -295,31 +300,33 @@ static void CubicPathBoundingPopulateValues(std::vector& values, { Scalar t = q / a; if (t >= 0.0 && t <= 1.0) { - values.emplace_back(t); + values.push_back(t); } } { Scalar t = c / q; if (t >= 0.0 && t <= 1.0) { - values.emplace_back(t); + values.push_back(t); } } } -std::vector CubicPathComponent::Extrema() const { +CubicPathComponent::ExtremaType CubicPathComponent::Extrema() const { /* * As described in: https://pomax.github.io/bezierinfo/#extremities */ - std::vector values; + LocalArray values; CubicPathBoundingPopulateValues(values, p1.x, cp1.x, cp2.x, p2.x); CubicPathBoundingPopulateValues(values, p1.y, cp1.y, cp2.y, p2.y); - std::vector points = {p1, p2}; + ExtremaType points; + points.push_back(p1); + points.push_back(p2); for (const auto& value : values) { - points.emplace_back(Solve(value)); + points.push_back(Solve(value)); } return points; diff --git a/impeller/geometry/path_component.h b/impeller/geometry/path_component.h index c2a808cf18d2a..fbb282b0b2614 100644 --- a/impeller/geometry/path_component.h +++ b/impeller/geometry/path_component.h @@ -8,6 +8,7 @@ #include #include +#include "impeller/geometry/local_array.h" #include "impeller/geometry/point.h" #include "impeller/geometry/rect.h" #include "impeller/geometry/scalar.h" @@ -36,7 +37,8 @@ struct LinearPathComponent { std::vector CreatePolyline() const; - std::vector Extrema() const; + typedef LocalArray ExtremaType; + ExtremaType Extrema() const; bool operator==(const LinearPathComponent& other) const { return p1 == other.p1 && p2 == other.p2; @@ -76,7 +78,8 @@ struct QuadraticPathComponent { void FillPointsForPolyline(std::vector& points, Scalar scale_factor) const; - std::vector Extrema() const; + typedef LocalArray ExtremaType; + ExtremaType Extrema() const; bool operator==(const QuadraticPathComponent& other) const { return p1 == other.p1 && cp == other.cp && p2 == other.p2; @@ -114,7 +117,8 @@ struct CubicPathComponent { // See the note on QuadraticPathComponent::CreatePolyline for references. std::vector CreatePolyline(Scalar scale) const; - std::vector Extrema() const; + typedef LocalArray ExtremaType; + ExtremaType Extrema() const; std::vector ToQuadraticPathComponents( Scalar accuracy) const;