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 | |
19 | namespace flutter { |
20 | |
21 | RasterCacheResult::RasterCacheResult(sk_sp<SkImage> image, |
22 | const SkRect& logical_rect) |
23 | : image_(std::move(image)), logical_rect_(logical_rect) {} |
24 | |
25 | void 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 | |
37 | RasterCache::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 | |
43 | static 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 | |
63 | static 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. |
90 | static 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 | |
126 | std::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 | |
137 | void 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 | |
149 | std::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 | |
179 | bool 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 | |
223 | bool 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 | |
242 | bool 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 | |
263 | void RasterCache::SweepAfterFrame() { |
264 | SweepOneCacheAfterFrame(picture_cache_); |
265 | SweepOneCacheAfterFrame(layer_cache_); |
266 | picture_cached_this_frame_ = 0; |
267 | TraceStatsToTimeline(); |
268 | } |
269 | |
270 | void RasterCache::Clear() { |
271 | picture_cache_.clear(); |
272 | layer_cache_.clear(); |
273 | } |
274 | |
275 | size_t RasterCache::GetCachedEntriesCount() const { |
276 | return layer_cache_.size() + picture_cache_.size(); |
277 | } |
278 | |
279 | size_t RasterCache::GetLayerCachedEntriesCount() const { |
280 | return layer_cache_.size(); |
281 | } |
282 | |
283 | size_t RasterCache::GetPictureCachedEntriesCount() const { |
284 | return picture_cache_.size(); |
285 | } |
286 | |
287 | void 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 | |
299 | void 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 | |
311 | size_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 | |
321 | size_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 | |