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