-
Notifications
You must be signed in to change notification settings - Fork 6k
WIP: [impeller] Add tessellation cache #44324
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -121,7 +121,9 @@ Path ToPath(const SkPath& path) { | |
| } | ||
| builder.SetConvexity(path.isConvex() ? Convexity::kConvex | ||
| : Convexity::kUnknown); | ||
| return builder.TakePath(fill_type); | ||
| auto result = builder.TakePath(fill_type); | ||
| result.SetOriginalGenerationId(path.getGenerationID()); | ||
|
||
| return result; | ||
| } | ||
|
|
||
| Path ToPath(const SkRRect& rrect) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -329,6 +329,7 @@ struct ContentContextOptions { | |
| }; | ||
|
|
||
| class Tessellator; | ||
| class TessellationCache; | ||
|
|
||
| class ContentContext { | ||
| public: | ||
|
|
@@ -342,6 +343,10 @@ class ContentContext { | |
|
|
||
| std::shared_ptr<Tessellator> GetTessellator() const; | ||
|
|
||
| TessellationCache& GetTessellationCache() const { | ||
| return *tessellation_cache_; | ||
| } | ||
|
|
||
| #ifdef IMPELLER_DEBUG | ||
| std::shared_ptr<Pipeline<PipelineDescriptor>> GetCheckerboardPipeline( | ||
| ContentContextOptions opts) const { | ||
|
|
@@ -853,6 +858,7 @@ class ContentContext { | |
|
|
||
| bool is_valid_ = false; | ||
| std::shared_ptr<Tessellator> tessellator_; | ||
| std::unique_ptr<TessellationCache> tessellation_cache_; | ||
|
Comment on lines
860
to
+861
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make the tessellation cache own the tessellator? |
||
| std::shared_ptr<scene::SceneContext> scene_context_; | ||
| bool wireframe_ = false; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you take the exact scale as a cache key then animated transformations will thrash your cache each frame despite the vertices being almost unchanged. |
||
| #if IMPELLER_ENABLE_TESSELLATION_CACHE == 1 | ||
| auto generation_id = path.GetOriginalGenerationId(); | ||
| if (!generation_id) { | ||
| // This path did not originate from Skia path. | ||
| return path.CreatePolyline(scale); | ||
| } | ||
| PolylineKey key{*generation_id, 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 generation_id = polyline.original_generation_id; | ||
| if (!generation_id) { | ||
| return tesselator.Tessellate(fill_type, polyline, callback); | ||
| } | ||
|
|
||
| TessellatorKey key{*generation_id, 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 | ||
|
Comment on lines
+55
to
+67
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice if the tessellation cache was a bit more general and could also cache data that was generated by compute. For an example, look at the draw points geometry. For that to work, it would likely have to be able to vend device buffers instead. On the other hand, we'll likely batch compute workloads in ways that make these buffers difficult to use. So perhaps only using the tessellation cache for the CPU bound workloads is the way forward. |
||
| } | ||
|
|
||
| } // namespace impeller | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| // 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 { | ||
| std::uint32_t generation_id; | ||
| Scalar scale; | ||
|
|
||
| bool operator==(const PolylineKey& other) const { | ||
| return generation_id == other.generation_id && scale == other.scale; | ||
| } | ||
| }; | ||
|
|
||
| struct PolylineKeyHash { | ||
| size_t operator()(const PolylineKey& key) const { | ||
| return key.generation_id + 31 * std::hash<Scalar>()(key.scale); | ||
| } | ||
| }; | ||
|
|
||
| struct TessellatorKey { | ||
| std::uint32_t generation_id; | ||
| FillType fill_type; | ||
|
|
||
| bool operator==(const TessellatorKey& other) const { | ||
| return generation_id == other.generation_id && | ||
| fill_type == other.fill_type; | ||
| } | ||
| }; | ||
|
|
||
| struct TessellatorKeyHash { | ||
| size_t operator()(const TessellatorKey& key) const { | ||
| return key.generation_id + 31 * std::hash<FillType>()(key.fill_type); | ||
| } | ||
| }; | ||
|
|
||
| struct TessellatorData { | ||
| std::vector<float> vertices; | ||
| std::vector<uint16_t> indices; | ||
| }; | ||
|
|
||
| template <typename K, typename V, typename Hash, typename Compare> | ||
| class FrameAwareCache { | ||
| public: | ||
| std::optional<V> 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<K, V, Hash, Compare> used_; | ||
|
|
||
| // Items that will be removed at the end of the frame unless they are | ||
| // accessed. | ||
| std::unordered_map<K, V, Hash, Compare> maybe_remove_; | ||
|
|
||
| int cache_miss_count_ = 0; | ||
| int cache_hit_count_ = 0; | ||
| }; | ||
|
|
||
| FrameAwareCache<PolylineKey, | ||
| Path::Polyline, | ||
| PolylineKeyHash, | ||
| std::equal_to<PolylineKey>> | ||
| polyline_cache_; | ||
|
|
||
| FrameAwareCache<TessellatorKey, | ||
| TessellatorData, | ||
| TessellatorKeyHash, | ||
| std::equal_to<TessellatorKey>> | ||
| tessellator_cache_; | ||
| }; | ||
|
|
||
| } // namespace impeller |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no concept of a frame, but you could probably use the concept of a particular presentation. See context_vk, this tracks increments in the swapchain. We might need to bring a similar concept out of the platform specific backend.