• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
5 #include "flutter/flow/raster_cache.h"
6 
7 #include <vector>
8 
9 #include "flutter/flow/layers/layer.h"
10 #include "flutter/flow/paint_utils.h"
11 #include "flutter/fml/logging.h"
12 #include "flutter/fml/trace_event.h"
13 #include "third_party/skia/include/core/SkCanvas.h"
14 #include "third_party/skia/include/core/SkImage.h"
15 #include "third_party/skia/include/core/SkPicture.h"
16 #include "third_party/skia/include/core/SkSurface.h"
17 
18 namespace flutter {
19 
RasterCacheResult()20 RasterCacheResult::RasterCacheResult() {}
21 
22 RasterCacheResult::RasterCacheResult(const RasterCacheResult& other) = default;
23 
24 RasterCacheResult::~RasterCacheResult() = default;
25 
RasterCacheResult(sk_sp<SkImage> image,const SkRect & logical_rect)26 RasterCacheResult::RasterCacheResult(sk_sp<SkImage> image,
27                                      const SkRect& logical_rect)
28     : image_(std::move(image)), logical_rect_(logical_rect) {}
29 
draw(SkCanvas & canvas,const SkPaint * paint) const30 void RasterCacheResult::draw(SkCanvas& canvas, const SkPaint* paint) const {
31   TRACE_EVENT0("flutter", "RasterCacheResult::draw");
32   SkAutoCanvasRestore auto_restore(&canvas, true);
33   SkIRect bounds =
34       RasterCache::GetDeviceBounds(logical_rect_, canvas.getTotalMatrix());
35   FML_DCHECK(bounds.size() == image_->dimensions());
36   canvas.resetMatrix();
37   canvas.drawImage(image_, bounds.fLeft, bounds.fTop, paint);
38 }
39 
RasterCache(size_t access_threshold,size_t picture_cache_limit_per_frame)40 RasterCache::RasterCache(size_t access_threshold,
41                          size_t picture_cache_limit_per_frame)
42     : access_threshold_(access_threshold),
43       picture_cache_limit_per_frame_(picture_cache_limit_per_frame),
44       checkerboard_images_(false),
45       weak_factory_(this) {}
46 
47 RasterCache::~RasterCache() = default;
48 
CanRasterizePicture(SkPicture * picture)49 static bool CanRasterizePicture(SkPicture* picture) {
50   if (picture == nullptr) {
51     return false;
52   }
53 
54   const SkRect cull_rect = picture->cullRect();
55 
56   if (cull_rect.isEmpty()) {
57     // No point in ever rasterizing an empty picture.
58     return false;
59   }
60 
61   if (!cull_rect.isFinite()) {
62     // Cannot attempt to rasterize into an infinitely large surface.
63     return false;
64   }
65 
66   return true;
67 }
68 
IsPictureWorthRasterizing(SkPicture * picture,bool will_change,bool is_complex)69 static bool IsPictureWorthRasterizing(SkPicture* picture,
70                                       bool will_change,
71                                       bool is_complex) {
72   if (will_change) {
73     // If the picture is going to change in the future, there is no point in
74     // doing to extra work to rasterize.
75     return false;
76   }
77 
78   if (!CanRasterizePicture(picture)) {
79     // No point in deciding whether the picture is worth rasterizing if it
80     // cannot be rasterized at all.
81     return false;
82   }
83 
84   if (is_complex) {
85     // The caller seems to have extra information about the picture and thinks
86     // the picture is always worth rasterizing.
87     return true;
88   }
89 
90   // TODO(abarth): We should find a better heuristic here that lets us avoid
91   // wasting memory on trivial layers that are easy to re-rasterize every frame.
92   return picture->approximateOpCount() > 5;
93 }
94 
Rasterize(GrContext * context,const SkMatrix & ctm,SkColorSpace * dst_color_space,bool checkerboard,const SkRect & logical_rect,std::function<void (SkCanvas *)> draw_function)95 static RasterCacheResult Rasterize(
96     GrContext* context,
97     const SkMatrix& ctm,
98     SkColorSpace* dst_color_space,
99     bool checkerboard,
100     const SkRect& logical_rect,
101     std::function<void(SkCanvas*)> draw_function) {
102   TRACE_EVENT0("flutter", "RasterCachePopulate");
103   SkIRect cache_rect = RasterCache::GetDeviceBounds(logical_rect, ctm);
104 
105   const SkImageInfo image_info = SkImageInfo::MakeN32Premul(
106       cache_rect.width(), cache_rect.height(), sk_ref_sp(dst_color_space));
107 
108   sk_sp<SkSurface> surface =
109       context
110           ? SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, image_info)
111           : SkSurface::MakeRaster(image_info);
112 
113   if (!surface) {
114     return {};
115   }
116 
117   SkCanvas* canvas = surface->getCanvas();
118   canvas->clear(SK_ColorTRANSPARENT);
119   canvas->translate(-cache_rect.left(), -cache_rect.top());
120   canvas->concat(ctm);
121   draw_function(canvas);
122 
123   if (checkerboard) {
124     DrawCheckerboard(canvas, logical_rect);
125   }
126 
127   return {surface->makeImageSnapshot(), logical_rect};
128 }
129 
RasterizePicture(SkPicture * picture,GrContext * context,const SkMatrix & ctm,SkColorSpace * dst_color_space,bool checkerboard)130 RasterCacheResult RasterizePicture(SkPicture* picture,
131                                    GrContext* context,
132                                    const SkMatrix& ctm,
133                                    SkColorSpace* dst_color_space,
134                                    bool checkerboard) {
135   return Rasterize(context, ctm, dst_color_space, checkerboard,
136                    picture->cullRect(),
137                    [=](SkCanvas* canvas) { canvas->drawPicture(picture); });
138 }
139 
ClampSize(size_t value,size_t min,size_t max)140 static inline size_t ClampSize(size_t value, size_t min, size_t max) {
141   if (value > max) {
142     return max;
143   }
144 
145   if (value < min) {
146     return min;
147   }
148 
149   return value;
150 }
151 
Prepare(PrerollContext * context,Layer * layer,const SkMatrix & ctm)152 void RasterCache::Prepare(PrerollContext* context,
153                           Layer* layer,
154                           const SkMatrix& ctm) {
155   LayerRasterCacheKey cache_key(layer->unique_id(), ctm);
156   Entry& entry = layer_cache_[cache_key];
157   entry.access_count = ClampSize(entry.access_count + 1, 0, access_threshold_);
158   entry.used_this_frame = true;
159   if (!entry.image.is_valid()) {
160     entry.image = Rasterize(context->gr_context, ctm, context->dst_color_space,
161                             checkerboard_images_, layer->paint_bounds(),
162                             [layer, context](SkCanvas* canvas) {
163                               SkISize canvas_size = canvas->getBaseLayerSize();
164                               SkNWayCanvas internal_nodes_canvas(
165                                   canvas_size.width(), canvas_size.height());
166                               internal_nodes_canvas.addCanvas(canvas);
167                               Layer::PaintContext paintContext = {
168                                   (SkCanvas*)&internal_nodes_canvas,
169                                   canvas,
170                                   context->gr_context,
171                                   nullptr,
172                                   context->raster_time,
173                                   context->ui_time,
174                                   context->texture_registry,
175                                   context->raster_cache,
176                                   context->checkerboard_offscreen_layers};
177                               if (layer->needs_painting()) {
178                                 layer->Paint(paintContext);
179                               }
180                             });
181   }
182 }
183 
Prepare(GrContext * context,SkPicture * picture,const SkMatrix & transformation_matrix,SkColorSpace * dst_color_space,bool is_complex,bool will_change)184 bool RasterCache::Prepare(GrContext* context,
185                           SkPicture* picture,
186                           const SkMatrix& transformation_matrix,
187                           SkColorSpace* dst_color_space,
188                           bool is_complex,
189                           bool will_change) {
190   if (picture_cached_this_frame_ >= picture_cache_limit_per_frame_) {
191     return false;
192   }
193   if (!IsPictureWorthRasterizing(picture, will_change, is_complex)) {
194     // We only deal with pictures that are worthy of rasterization.
195     return false;
196   }
197 
198   // Decompose the matrix (once) for all subsequent operations. We want to make
199   // sure to avoid volumetric distortions while accounting for scaling.
200   const MatrixDecomposition matrix(transformation_matrix);
201 
202   if (!matrix.IsValid()) {
203     // The matrix was singular. No point in going further.
204     return false;
205   }
206 
207   PictureRasterCacheKey cache_key(picture->uniqueID(), transformation_matrix);
208 
209   Entry& entry = picture_cache_[cache_key];
210   entry.access_count = ClampSize(entry.access_count + 1, 0, access_threshold_);
211   entry.used_this_frame = true;
212 
213   if (entry.access_count < access_threshold_ || access_threshold_ == 0) {
214     // Frame threshold has not yet been reached.
215     return false;
216   }
217 
218   if (!entry.image.is_valid()) {
219     entry.image = RasterizePicture(picture, context, transformation_matrix,
220                                    dst_color_space, checkerboard_images_);
221   }
222   picture_cached_this_frame_++;
223   return true;
224 }
225 
Get(const SkPicture & picture,const SkMatrix & ctm) const226 RasterCacheResult RasterCache::Get(const SkPicture& picture,
227                                    const SkMatrix& ctm) const {
228   PictureRasterCacheKey cache_key(picture.uniqueID(), ctm);
229   auto it = picture_cache_.find(cache_key);
230   return it == picture_cache_.end() ? RasterCacheResult() : it->second.image;
231 }
232 
Get(Layer * layer,const SkMatrix & ctm) const233 RasterCacheResult RasterCache::Get(Layer* layer, const SkMatrix& ctm) const {
234   LayerRasterCacheKey cache_key(layer->unique_id(), ctm);
235   auto it = layer_cache_.find(cache_key);
236   return it == layer_cache_.end() ? RasterCacheResult() : it->second.image;
237 }
238 
SweepAfterFrame()239 void RasterCache::SweepAfterFrame() {
240   using PictureCache = PictureRasterCacheKey::Map<Entry>;
241   using LayerCache = LayerRasterCacheKey::Map<Entry>;
242   SweepOneCacheAfterFrame<PictureCache, PictureCache::iterator>(picture_cache_);
243   SweepOneCacheAfterFrame<LayerCache, LayerCache::iterator>(layer_cache_);
244   picture_cached_this_frame_ = 0;
245   TraceStatsToTimeline();
246 }
247 
Clear()248 void RasterCache::Clear() {
249   picture_cache_.clear();
250   layer_cache_.clear();
251 }
252 
SetCheckboardCacheImages(bool checkerboard)253 void RasterCache::SetCheckboardCacheImages(bool checkerboard) {
254   if (checkerboard_images_ == checkerboard) {
255     return;
256   }
257 
258   checkerboard_images_ = checkerboard;
259 
260   // Clear all existing entries so previously rasterized items (with or without
261   // a checkerboard) will be refreshed in subsequent passes.
262   Clear();
263 }
264 
TraceStatsToTimeline() const265 void RasterCache::TraceStatsToTimeline() const {
266 #if FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_RELEASE
267 
268   size_t layer_cache_count = 0;
269   size_t layer_cache_bytes = 0;
270   size_t picture_cache_count = 0;
271   size_t picture_cache_bytes = 0;
272 
273   for (const auto& item : layer_cache_) {
274     const auto dimensions = item.second.image.image_dimensions();
275     layer_cache_count++;
276     layer_cache_bytes += dimensions.width() * dimensions.height() * 4;
277   }
278 
279   for (const auto& item : picture_cache_) {
280     const auto dimensions = item.second.image.image_dimensions();
281     picture_cache_count++;
282     picture_cache_bytes += dimensions.width() * dimensions.height() * 4;
283   }
284 
285   FML_TRACE_COUNTER("flutter", "RasterCache",
286                     reinterpret_cast<int64_t>(this),             //
287                     "LayerCount", layer_cache_count,             //
288                     "LayerMBytes", layer_cache_bytes * 1e-6,     //
289                     "PictureCount", picture_cache_count,         //
290                     "PictureMBytes", picture_cache_bytes * 1e-6  //
291   );
292 
293 #endif  // FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_RELEASE
294 }
295 
296 }  // namespace flutter
297