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