// 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 "flutter/flow/raster_cache.h" #include #include "flutter/common/constants.h" #include "flutter/flow/layers/layer.h" #include "flutter/flow/paint_utils.h" #include "flutter/fml/logging.h" #include "flutter/fml/trace_event.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkPicture.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/GrDirectContext.h" namespace flutter { RasterCacheResult::RasterCacheResult(sk_sp image, const SkRect& logical_rect) : image_(std::move(image)), logical_rect_(logical_rect) {} void RasterCacheResult::draw(SkCanvas& canvas, const SkPaint* paint) const { TRACE_EVENT0("flutter", "RasterCacheResult::draw"); SkAutoCanvasRestore auto_restore(&canvas, true); SkIRect bounds = RasterCache::GetDeviceBounds(logical_rect_, canvas.getTotalMatrix()); FML_DCHECK( std::abs(bounds.size().width() - image_->dimensions().width()) <= 1 && std::abs(bounds.size().height() - image_->dimensions().height()) <= 1); canvas.resetMatrix(); canvas.drawImage(image_, bounds.fLeft, bounds.fTop, SkSamplingOptions(), paint); } RasterCache::RasterCache(size_t access_threshold, size_t picture_cache_limit_per_frame) : access_threshold_(access_threshold), picture_cache_limit_per_frame_(picture_cache_limit_per_frame), checkerboard_images_(false) {} static bool CanRasterizePicture(SkPicture* picture) { if (picture == nullptr) { return false; } const SkRect cull_rect = picture->cullRect(); if (cull_rect.isEmpty()) { // No point in ever rasterizing an empty picture. return false; } if (!cull_rect.isFinite()) { // Cannot attempt to rasterize into an infinitely large surface. return false; } return true; } static bool IsPictureWorthRasterizing(SkPicture* picture, bool will_change, bool is_complex) { if (will_change) { // If the picture is going to change in the future, there is no point in // doing to extra work to rasterize. return false; } if (!CanRasterizePicture(picture)) { // No point in deciding whether the picture is worth rasterizing if it // cannot be rasterized at all. return false; } if (is_complex) { // The caller seems to have extra information about the picture and thinks // the picture is always worth rasterizing. return true; } // TODO(abarth): We should find a better heuristic here that lets us avoid // wasting memory on trivial layers that are easy to re-rasterize every frame. return picture->approximateOpCount() > 5; } /// @note Procedure doesn't copy all closures. static std::unique_ptr Rasterize( GrDirectContext* context, const SkMatrix& ctm, SkColorSpace* dst_color_space, bool checkerboard, const SkRect& logical_rect, const std::function& draw_function) { TRACE_EVENT0("flutter", "RasterCachePopulate"); SkIRect cache_rect = RasterCache::GetDeviceBounds(logical_rect, ctm); const SkImageInfo image_info = SkImageInfo::MakeN32Premul( cache_rect.width(), cache_rect.height(), sk_ref_sp(dst_color_space)); sk_sp surface = context ? SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, image_info) : SkSurface::MakeRaster(image_info); if (!surface) { return nullptr; } SkCanvas* canvas = surface->getCanvas(); canvas->clear(SK_ColorTRANSPARENT); canvas->translate(-cache_rect.left(), -cache_rect.top()); canvas->concat(ctm); draw_function(canvas); if (checkerboard) { DrawCheckerboard(canvas, logical_rect); } return std::make_unique(surface->makeImageSnapshot(), logical_rect); } std::unique_ptr RasterCache::RasterizePicture( SkPicture* picture, GrDirectContext* context, const SkMatrix& ctm, SkColorSpace* dst_color_space, bool checkerboard) const { return Rasterize(context, ctm, dst_color_space, checkerboard, picture->cullRect(), [=](SkCanvas* canvas) { canvas->drawPicture(picture); }); } void RasterCache::Prepare(PrerollContext* context, Layer* layer, const SkMatrix& ctm) { LayerRasterCacheKey cache_key(layer->unique_id(), ctm); Entry& entry = layer_cache_[cache_key]; entry.access_count++; entry.used_this_frame = true; if (!entry.image) { entry.image = RasterizeLayer(context, layer, ctm, checkerboard_images_); } } std::unique_ptr RasterCache::RasterizeLayer( PrerollContext* context, Layer* layer, const SkMatrix& ctm, bool checkerboard) const { return Rasterize( context->gr_context, ctm, context->dst_color_space, checkerboard, layer->paint_bounds(), [layer, context](SkCanvas* canvas) { SkISize canvas_size = canvas->getBaseLayerSize(); SkNWayCanvas internal_nodes_canvas(canvas_size.width(), canvas_size.height()); internal_nodes_canvas.setMatrix(canvas->getTotalMatrix()); internal_nodes_canvas.addCanvas(canvas); Layer::PaintContext paintContext = { /* internal_nodes_canvas= */ static_cast( &internal_nodes_canvas), /* leaf_nodes_canvas= */ canvas, /* gr_context= */ context->gr_context, /* view_embedder= */ nullptr, context->raster_time, context->ui_time, context->texture_registry, context->has_platform_view ? nullptr : context->raster_cache, context->checkerboard_offscreen_layers, context->frame_device_pixel_ratio}; if (layer->needs_painting(paintContext)) { layer->Paint(paintContext); } }); } bool RasterCache::Prepare(GrDirectContext* context, SkPicture* picture, const SkMatrix& transformation_matrix, SkColorSpace* dst_color_space, bool is_complex, bool will_change) { // Disabling caching when access_threshold is zero is historic behavior. if (access_threshold_ == 0) { return false; } if (picture_cached_this_frame_ >= picture_cache_limit_per_frame_) { return false; } if (!IsPictureWorthRasterizing(picture, will_change, is_complex)) { // We only deal with pictures that are worthy of rasterization. return false; } // Decompose the matrix (once) for all subsequent operations. We want to make // sure to avoid volumetric distortions while accounting for scaling. const MatrixDecomposition matrix(transformation_matrix); if (!matrix.IsValid()) { // The matrix was singular. No point in going further. return false; } PictureRasterCacheKey cache_key(picture->uniqueID(), transformation_matrix); // Creates an entry, if not present prior. Entry& entry = picture_cache_[cache_key]; if (entry.access_count < access_threshold_) { // Frame threshold has not yet been reached. return false; } if (!entry.image) { entry.image = RasterizePicture(picture, context, transformation_matrix, dst_color_space, checkerboard_images_); picture_cached_this_frame_++; } return true; } bool RasterCache::Draw(const SkPicture& picture, SkCanvas& canvas) const { PictureRasterCacheKey cache_key(picture.uniqueID(), canvas.getTotalMatrix()); auto it = picture_cache_.find(cache_key); if (it == picture_cache_.end()) { return false; } Entry& entry = it->second; entry.access_count++; entry.used_this_frame = true; if (entry.image) { entry.image->draw(canvas, nullptr); return true; } return false; } bool RasterCache::Draw(const Layer* layer, SkCanvas& canvas, SkPaint* paint) const { LayerRasterCacheKey cache_key(layer->unique_id(), canvas.getTotalMatrix()); auto it = layer_cache_.find(cache_key); if (it == layer_cache_.end()) { return false; } Entry& entry = it->second; entry.access_count++; entry.used_this_frame = true; if (entry.image) { entry.image->draw(canvas, paint); return true; } return false; } void RasterCache::SweepAfterFrame() { SweepOneCacheAfterFrame(picture_cache_); SweepOneCacheAfterFrame(layer_cache_); picture_cached_this_frame_ = 0; TraceStatsToTimeline(); } void RasterCache::Clear() { picture_cache_.clear(); layer_cache_.clear(); } size_t RasterCache::GetCachedEntriesCount() const { return layer_cache_.size() + picture_cache_.size(); } size_t RasterCache::GetLayerCachedEntriesCount() const { return layer_cache_.size(); } size_t RasterCache::GetPictureCachedEntriesCount() const { return picture_cache_.size(); } void RasterCache::SetCheckboardCacheImages(bool checkerboard) { if (checkerboard_images_ == checkerboard) { return; } checkerboard_images_ = checkerboard; // Clear all existing entries so previously rasterized items (with or without // a checkerboard) will be refreshed in subsequent passes. Clear(); } void RasterCache::TraceStatsToTimeline() const { #if !FLUTTER_RELEASE FML_TRACE_COUNTER("flutter", "RasterCache", reinterpret_cast(this), "LayerCount", layer_cache_.size(), "LayerMBytes", EstimateLayerCacheByteSize() / kMegaByteSizeInBytes, "PictureCount", picture_cache_.size(), "PictureMBytes", EstimatePictureCacheByteSize() / kMegaByteSizeInBytes); #endif // !FLUTTER_RELEASE } size_t RasterCache::EstimateLayerCacheByteSize() const { size_t layer_cache_bytes = 0; for (const auto& item : layer_cache_) { if (item.second.image) { layer_cache_bytes += item.second.image->image_bytes(); } } return layer_cache_bytes; } size_t RasterCache::EstimatePictureCacheByteSize() const { size_t picture_cache_bytes = 0; for (const auto& item : picture_cache_) { if (item.second.image) { picture_cache_bytes += item.second.image->image_bytes(); } } return picture_cache_bytes; } } // namespace flutter