diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index 647567b74dc72..f3d075594b05b 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -180,6 +180,7 @@ void RasterCache::Prepare(PrerollContext* context, Entry& entry = cache_[cache_key]; entry.access_count++; entry.used_this_frame = true; + entry.unused_count = 0; if (!entry.image) { entry.image = RasterizeLayer(context, layer, ctm, checkerboard_images_); } @@ -222,7 +223,8 @@ bool RasterCache::Prepare(PrerollContext* context, bool is_complex, bool will_change, const SkMatrix& untranslated_matrix, - const SkPoint& offset) { + const SkPoint& offset, + bool is_high_priority) { if (!GenerateNewCacheInThisFrame()) { return false; } @@ -245,7 +247,8 @@ bool RasterCache::Prepare(PrerollContext* context, // Creates an entry, if not present prior. Entry& entry = cache_[cache_key]; - if (entry.access_count < access_threshold_) { + entry.is_high_priority = is_high_priority; + if (!is_high_priority && entry.access_count < access_threshold_) { // Frame threshold has not yet been reached. return false; } @@ -270,7 +273,8 @@ bool RasterCache::Prepare(PrerollContext* context, bool is_complex, bool will_change, const SkMatrix& untranslated_matrix, - const SkPoint& offset) { + const SkPoint& offset, + bool is_high_priority) { if (!GenerateNewCacheInThisFrame()) { return false; } @@ -300,7 +304,8 @@ bool RasterCache::Prepare(PrerollContext* context, // Creates an entry, if not present prior. Entry& entry = cache_[cache_key]; - if (entry.access_count < access_threshold_) { + entry.is_high_priority = is_high_priority; + if (!is_high_priority && entry.access_count < access_threshold_) { // Frame threshold has not yet been reached. return false; } @@ -345,6 +350,7 @@ void RasterCache::Touch(const RasterCacheKey& cache_key) { if (it != cache_.end()) { it->second.used_this_frame = true; it->second.access_count++; + it->second.unused_count = 0; } } @@ -384,6 +390,7 @@ bool RasterCache::Draw(const RasterCacheKey& cache_key, Entry& entry = it->second; entry.access_count++; entry.used_this_frame = true; + entry.unused_count = 0; if (entry.image) { entry.image->draw(canvas, paint); @@ -398,47 +405,37 @@ void RasterCache::PrepareNewFrame() { display_list_cached_this_frame_ = 0; } -void RasterCache::SweepOneCacheAfterFrame(RasterCacheKey::Map& cache, - RasterCacheMetrics& picture_metrics, - RasterCacheMetrics& layer_metrics) { +void RasterCache::SweepCacheAfterFrame() { std::vector::iterator> dead; - for (auto it = cache.begin(); it != cache.end(); ++it) { + for (auto it = cache_.begin(); it != cache_.end(); ++it) { Entry& entry = it->second; - if (!entry.used_this_frame) { - dead.push_back(it); - } else if (entry.image) { - RasterCacheKeyKind kind = it->first.kind(); - switch (kind) { - case RasterCacheKeyKind::kPictureMetrics: - picture_metrics.in_use_count++; - picture_metrics.in_use_bytes += entry.image->image_bytes(); - break; - case RasterCacheKeyKind::kLayerMetrics: - layer_metrics.in_use_count++; - layer_metrics.in_use_bytes += entry.image->image_bytes(); - break; + if (entry.unused_count < entry.unused_threshold()) { + entry.unused_count++; + if (entry.image) { + RasterCacheMetrics& metrics = GetMetricsForKind(it->first.kind()); + metrics.unused_count++; + metrics.unused_bytes += entry.image->image_bytes(); + } + } else { + dead.push_back(it); } + } else if (entry.image) { + RasterCacheMetrics& metrics = GetMetricsForKind(it->first.kind()); + metrics.in_use_count++; + metrics.in_use_bytes += entry.image->image_bytes(); } entry.used_this_frame = false; } for (auto it : dead) { if (it->second.image) { - RasterCacheKeyKind kind = it->first.kind(); - switch (kind) { - case RasterCacheKeyKind::kPictureMetrics: - picture_metrics.eviction_count++; - picture_metrics.eviction_bytes += it->second.image->image_bytes(); - break; - case RasterCacheKeyKind::kLayerMetrics: - layer_metrics.eviction_count++; - layer_metrics.eviction_bytes += it->second.image->image_bytes(); - break; - } + RasterCacheMetrics& metrics = GetMetricsForKind(it->first.kind()); + metrics.eviction_count++; + metrics.eviction_bytes += it->second.image->image_bytes(); } - cache.erase(it); + cache_.erase(it); } } @@ -447,7 +444,7 @@ void RasterCache::CleanupAfterFrame() { layer_metrics_ = {}; { TRACE_EVENT0("flutter", "RasterCache::SweepCaches"); - SweepOneCacheAfterFrame(cache_, picture_metrics_, layer_metrics_); + SweepCacheAfterFrame(); } TraceStatsToTimeline(); } @@ -458,6 +455,15 @@ void RasterCache::Clear() { layer_metrics_ = {}; } +RasterCacheMetrics& RasterCache::GetMetricsForKind(RasterCacheKeyKind kind) { + switch (kind) { + case RasterCacheKeyKind::kPictureMetrics: + return picture_metrics_; + case RasterCacheKeyKind::kLayerMetrics: + return layer_metrics_; + } +} + size_t RasterCache::GetCachedEntriesCount() const { return cache_.size(); } diff --git a/flow/raster_cache.h b/flow/raster_cache.h index bf5b39e92a751..fb52d7c948ca3 100644 --- a/flow/raster_cache.h +++ b/flow/raster_cache.h @@ -67,19 +67,32 @@ struct RasterCacheMetrics { */ size_t in_use_bytes = 0; + /** + * The number of cache entries with images unused but kept in this frame. + */ + size_t unused_count = 0; + + /** + * The size of all of the images unused but kept in this frame. + */ + size_t unused_bytes = 0; /** * The total cache entries that had images during this frame whether * they were used in the frame or held memory during the frame and then * were evicted after it ended. */ - size_t total_count() const { return in_use_count + eviction_count; } + size_t total_count() const { + return in_use_count + unused_count + eviction_count; + } /** * The size of all of the cached images during this frame whether * they were used in the frame or held memory during the frame and then * were evicted after it ended. */ - size_t total_bytes() const { return in_use_bytes + eviction_bytes; } + size_t total_bytes() const { + return in_use_bytes + unused_bytes + eviction_bytes; + } }; class RasterCache { @@ -89,6 +102,9 @@ class RasterCache { // on that frame. This limit allows us to throttle the cache and distribute // the work across multiple frames. static constexpr int kDefaultPictureAndDispLayListCacheLimitPerFrame = 3; + // The default number of frames the high-priority entry which is high priority + // survives if it is not used. + static constexpr int kHighPriorityEvictionThreshold = 3; explicit RasterCache(size_t access_threshold = 3, size_t picture_and_display_list_cache_limit_per_frame = @@ -186,13 +202,15 @@ class RasterCache { bool is_complex, bool will_change, const SkMatrix& untranslated_matrix, - const SkPoint& offset = SkPoint()); + const SkPoint& offset = SkPoint(), + bool is_high_priority = false); bool Prepare(PrerollContext* context, DisplayList* display_list, bool is_complex, bool will_change, const SkMatrix& untranslated_matrix, - const SkPoint& offset = SkPoint()); + const SkPoint& offset = SkPoint(), + bool is_high_priority = false); // If there is cache entry for this picture, display list or layer, mark it as // used for this frame in order to not get evicted. This is needed during @@ -286,9 +304,18 @@ class RasterCache { private: struct Entry { + // If the entry is high priority, it will always cache on first usage and + // survive 3 frames without usage. + bool is_high_priority = false; bool used_this_frame = false; size_t access_count = 0; + size_t unused_count = 0; std::unique_ptr image; + // Return the number of frames the entry survives if it is not used. If the + // number is 0, then it will be evicted when not in use. + size_t unused_threshold() const { + return is_high_priority ? kHighPriorityEvictionThreshold : 0; + } }; void Touch(const RasterCacheKey& cache_key); @@ -297,9 +324,9 @@ class RasterCache { SkCanvas& canvas, const SkPaint* paint) const; - void SweepOneCacheAfterFrame(RasterCacheKey::Map& cache, - RasterCacheMetrics& picture_metrics, - RasterCacheMetrics& layer_metrics); + void SweepCacheAfterFrame(); + + RasterCacheMetrics& GetMetricsForKind(RasterCacheKeyKind kind); bool GenerateNewCacheInThisFrame() const { // Disabling caching when access_threshold is zero is historic behavior. diff --git a/flow/raster_cache_unittests.cc b/flow/raster_cache_unittests.cc index b62cfe5d08cd3..c078026e56f35 100644 --- a/flow/raster_cache_unittests.cc +++ b/flow/raster_cache_unittests.cc @@ -58,6 +58,26 @@ TEST(RasterCache, ThresholdIsRespectedForSkPicture) { ASSERT_TRUE(cache.Draw(*picture, dummy_canvas)); } +TEST(RasterCache, HighPriorityIsRespectedForSkPicture) { + flutter::RasterCache cache; + + SkMatrix matrix = SkMatrix::I(); + + auto picture = GetSamplePicture(); + + SkCanvas dummy_canvas; + + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(); + + cache.PrepareNewFrame(); + + // Prepare should cache it when 1st access. + ASSERT_TRUE(cache.Prepare(&preroll_context_holder.preroll_context, + picture.get(), true, false, matrix, SkPoint(), + /**is_high_priority=*/true)); + ASSERT_TRUE(cache.Draw(*picture, dummy_canvas)); +} + TEST(RasterCache, MetricsOmitUnpopulatedEntries) { size_t threshold = 2; flutter::RasterCache cache(threshold); @@ -141,6 +161,26 @@ TEST(RasterCache, ThresholdIsRespectedForDisplayList) { ASSERT_TRUE(cache.Draw(*display_list, dummy_canvas)); } +TEST(RasterCache, HighPriorityIsRespectedForDisplayList) { + flutter::RasterCache cache; + + SkMatrix matrix = SkMatrix::I(); + + auto display_list = GetSampleDisplayList(); + + SkCanvas dummy_canvas; + + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(); + + cache.PrepareNewFrame(); + + // Prepare should cache it when 1st access. + ASSERT_TRUE(cache.Prepare(&preroll_context_holder.preroll_context, + display_list.get(), true, false, matrix, SkPoint(), + /**is_high_priority=*/true)); + ASSERT_TRUE(cache.Draw(*display_list, dummy_canvas)); +} + TEST(RasterCache, AccessThresholdOfZeroDisablesCachingForSkPicture) { size_t threshold = 0; flutter::RasterCache cache(threshold); @@ -291,6 +331,99 @@ TEST(RasterCache, SweepsRemoveUnusedDisplayLists) { ASSERT_FALSE(cache.Draw(*display_list, dummy_canvas)); } +void PrepareAndCleanupEmptyFrame(flutter::RasterCache& cache, size_t times) { + for (size_t i = 0; i < times; i++) { + cache.PrepareNewFrame(); + cache.CleanupAfterFrame(); // Extra frame without a Get image access. + } +} + +TEST(RasterCache, KeepUnusedSkPicturesIfIsHighPriority) { + size_t threshold = 1; + flutter::RasterCache cache(threshold); + + SkMatrix matrix = SkMatrix::I(); + + auto picture = GetSamplePicture(); + + SkCanvas dummy_canvas; + + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(); + + cache.PrepareNewFrame(); + + ASSERT_TRUE(cache.Prepare(&preroll_context_holder.preroll_context, + picture.get(), true, false, matrix, SkPoint(), + /**is_high_priority=*/true)); + ASSERT_TRUE(cache.Draw(*picture, dummy_canvas)); + + cache.CleanupAfterFrame(); + + PrepareAndCleanupEmptyFrame(cache, 1); + cache.PrepareNewFrame(); + ASSERT_TRUE(cache.Draw(*picture, dummy_canvas)); + cache.CleanupAfterFrame(); + + PrepareAndCleanupEmptyFrame(cache, 2); + cache.PrepareNewFrame(); + ASSERT_TRUE(cache.Draw(*picture, dummy_canvas)); + cache.CleanupAfterFrame(); + + PrepareAndCleanupEmptyFrame(cache, 3); + cache.PrepareNewFrame(); + ASSERT_TRUE(cache.Draw(*picture, dummy_canvas)); + cache.CleanupAfterFrame(); + + // The entry will be evicted when it is not used 4 times in a row. + PrepareAndCleanupEmptyFrame(cache, 4); + cache.PrepareNewFrame(); + ASSERT_FALSE(cache.Draw(*picture, dummy_canvas)); + cache.CleanupAfterFrame(); +} + +TEST(RasterCache, KeepUnusedDisplayListsIfIsHighPriority) { + size_t threshold = 1; + flutter::RasterCache cache(threshold); + + SkMatrix matrix = SkMatrix::I(); + + auto display_list = GetSampleDisplayList(); + + SkCanvas dummy_canvas; + + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(); + + cache.PrepareNewFrame(); + + ASSERT_TRUE(cache.Prepare(&preroll_context_holder.preroll_context, + display_list.get(), true, false, matrix, SkPoint(), + /**is_high_priority=*/true)); + ASSERT_TRUE(cache.Draw(*display_list, dummy_canvas)); + + cache.CleanupAfterFrame(); + + PrepareAndCleanupEmptyFrame(cache, 1); + cache.PrepareNewFrame(); + ASSERT_TRUE(cache.Draw(*display_list, dummy_canvas)); + cache.CleanupAfterFrame(); + + PrepareAndCleanupEmptyFrame(cache, 2); + cache.PrepareNewFrame(); + ASSERT_TRUE(cache.Draw(*display_list, dummy_canvas)); + cache.CleanupAfterFrame(); + + PrepareAndCleanupEmptyFrame(cache, 3); + cache.PrepareNewFrame(); + ASSERT_TRUE(cache.Draw(*display_list, dummy_canvas)); + cache.CleanupAfterFrame(); + + // The entry will be evicted when it is not used 4 times in a row. + PrepareAndCleanupEmptyFrame(cache, 4); + cache.PrepareNewFrame(); + ASSERT_FALSE(cache.Draw(*display_list, dummy_canvas)); + cache.CleanupAfterFrame(); +} + // Construct a cache result whose device target rectangle rounds out to be one // pixel wider than the cached image. Verify that it can be drawn without // triggering any assertions.