1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "cc/tiles/checker_image_tracker.h"
13 #include "base/bind.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/notreached.h"
16 #include "base/trace_event/trace_event.h"
20 enum class CheckerImagingDecision {
23 // Animation State vetoes.
26 kVetoedMultipartImage,
29 kVetoedPartiallyLoadedImage,
31 // Size associated vetoes.
32 kVetoedSmallerThanCheckeringSize,
33 kVetoedLargerThanCacheSize,
35 // Vetoed because checkering of images has been disabled.
38 // Sync was requested by the embedder.
41 kCheckerImagingDecisionCount
44 std::string ToString(PaintImage::Id paint_image_id,
45 CheckerImagingDecision decision) {
46 std::ostringstream str;
47 str << "paint_image_id[" << paint_image_id << "] decision["
48 << static_cast<int>(decision) << "]";
52 CheckerImagingDecision GetAnimationDecision(const PaintImage& image) {
53 if (image.is_multipart())
54 return CheckerImagingDecision::kVetoedMultipartImage;
56 switch (image.animation_type()) {
57 case PaintImage::AnimationType::ANIMATED:
58 return CheckerImagingDecision::kVetoedAnimatedImage;
59 case PaintImage::AnimationType::VIDEO:
60 return CheckerImagingDecision::kVetoedVideoFrame;
61 case PaintImage::AnimationType::STATIC:
62 return CheckerImagingDecision::kCanChecker;
66 return CheckerImagingDecision::kCanChecker;
69 CheckerImagingDecision GetLoadDecision(const PaintImage& image) {
70 switch (image.completion_state()) {
71 case PaintImage::CompletionState::DONE:
72 return CheckerImagingDecision::kCanChecker;
73 case PaintImage::CompletionState::PARTIALLY_DONE:
74 return CheckerImagingDecision::kVetoedPartiallyLoadedImage;
78 return CheckerImagingDecision::kCanChecker;
81 CheckerImagingDecision GetSizeDecision(const SkIRect& src_rect,
84 // Ideally we would use the original image rect here to estimate the decode
85 // duration for this image. But in the case of sprites/atlases, where small
86 // subsets of this image are used across multiple tiles, re-invalidating for
87 // replacing these images can incur heavy raster cost. So we use the src_rect
89 // TODO(khushalsagar): May be we should look at the invalidation rect for an
90 // image here to detect these cases instead?
91 base::CheckedNumeric<size_t> checked_size = 4;
92 checked_size *= src_rect.width();
93 checked_size *= src_rect.height();
94 size_t size = checked_size.ValueOrDefault(std::numeric_limits<size_t>::max());
97 return CheckerImagingDecision::kVetoedSmallerThanCheckeringSize;
98 else if (size > max_bytes)
99 return CheckerImagingDecision::kVetoedLargerThanCacheSize;
101 return CheckerImagingDecision::kCanChecker;
104 CheckerImagingDecision GetCheckerImagingDecision(const PaintImage& image,
105 const SkIRect& src_rect,
108 CheckerImagingDecision decision = GetAnimationDecision(image);
109 if (decision != CheckerImagingDecision::kCanChecker)
112 decision = GetLoadDecision(image);
113 if (decision != CheckerImagingDecision::kCanChecker)
116 return GetSizeDecision(src_rect, min_bytes, max_bytes);
122 const int CheckerImageTracker::kNoDecodeAllowedPriority = -1;
124 CheckerImageTracker::ImageDecodeRequest::ImageDecodeRequest(
125 PaintImage paint_image,
127 : paint_image(std::move(paint_image)), type(type) {}
129 CheckerImageTracker::CheckerImageTracker(ImageController* image_controller,
130 CheckerImageTrackerClient* client,
131 bool enable_checker_imaging,
132 size_t min_image_bytes_to_checker)
133 : image_controller_(image_controller),
135 enable_checker_imaging_(enable_checker_imaging),
136 min_image_bytes_to_checker_(min_image_bytes_to_checker) {}
138 CheckerImageTracker::~CheckerImageTracker() = default;
140 void CheckerImageTracker::SetNoDecodesAllowed() {
141 decode_priority_allowed_ = kNoDecodeAllowedPriority;
144 void CheckerImageTracker::SetMaxDecodePriorityAllowed(DecodeType decode_type) {
145 DCHECK_GT(decode_type, kNoDecodeAllowedPriority);
146 DCHECK_GE(decode_type, decode_priority_allowed_);
147 DCHECK_LE(decode_type, DecodeType::kLast);
149 if (decode_priority_allowed_ == decode_type)
151 decode_priority_allowed_ = decode_type;
153 // This will start the next decode if applicable.
154 ScheduleNextImageDecode();
157 void CheckerImageTracker::ScheduleImageDecodeQueue(
158 ImageDecodeQueue image_decode_queue) {
159 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
160 "CheckerImageTracker::ScheduleImageDecodeQueue");
162 // The decodes in the queue should be prioritized correctly.
163 DecodeType type = DecodeType::kRaster;
164 for (const auto& image_request : image_decode_queue) {
165 DCHECK_GE(image_request.type, type);
166 type = image_request.type;
170 image_decode_queue_ = std::move(image_decode_queue);
171 ScheduleNextImageDecode();
174 const PaintImageIdFlatSet&
175 CheckerImageTracker::TakeImagesToInvalidateOnSyncTree() {
176 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
177 "CheckerImageTracker::TakeImagesToInvalidateOnSyncTree");
178 DCHECK_EQ(invalidated_images_on_current_sync_tree_.size(), 0u)
179 << "Sync tree can not be invalidated more than once";
181 invalidated_images_on_current_sync_tree_.swap(images_pending_invalidation_);
182 images_pending_invalidation_.clear();
183 return invalidated_images_on_current_sync_tree_;
186 void CheckerImageTracker::DidActivateSyncTree() {
187 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
188 "CheckerImageTracker::DidActivateSyncTree");
189 for (auto image_id : invalidated_images_on_current_sync_tree_)
190 image_id_to_decode_.erase(image_id);
191 invalidated_images_on_current_sync_tree_.clear();
194 void CheckerImageTracker::ClearTracker(bool can_clear_decode_policy_tracking) {
195 // Unlock all images and tracking for images pending invalidation. The
196 // |images_invalidated_on_current_sync_tree_| will be cleared when the sync
197 // tree is activated.
199 // Note that we assume that any images with DecodePolicy::ASYNC, which may be
200 // checkered, are safe to stop tracking here and will either be re-checkered
201 // and invalidated when the decode completes or be invalidated externally.
202 // This is because the policy decision for checkering an image is based on
203 // inputs received from a PaintImage in the DisplayItemList. The policy chosen
204 // for a PaintImage should remain unchanged.
205 // If the external inputs for deciding the decode policy for an image change,
206 // they should be accompanied with an invalidation during paint.
207 image_id_to_decode_.clear();
209 if (can_clear_decode_policy_tracking) {
210 decoding_mode_map_.clear();
211 image_async_decode_state_.clear();
213 // If we can't clear the decode policy, we need to make sure we still
214 // re-decode and checker images that were pending invalidation.
215 for (auto image_id : images_pending_invalidation_) {
216 auto it = image_async_decode_state_.find(image_id);
217 DCHECK(it != image_async_decode_state_.end());
218 DCHECK_EQ(it->second.policy, DecodePolicy::SYNC);
219 it->second.policy = DecodePolicy::ASYNC;
222 images_pending_invalidation_.clear();
225 void CheckerImageTracker::DisallowCheckeringForImage(const PaintImage& image) {
226 image_async_decode_state_.insert(
227 std::make_pair(image.stable_id(), DecodeState()));
230 void CheckerImageTracker::DidFinishImageDecode(
231 PaintImage::Id image_id,
232 ImageController::ImageDecodeRequestId request_id,
233 ImageController::ImageDecodeResult result) {
234 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
235 "CheckerImageTracker::DidFinishImageDecode");
236 TRACE_EVENT_NESTABLE_ASYNC_END0("cc", "CheckerImageTracker::DeferImageDecode",
237 TRACE_ID_LOCAL(image_id));
239 DCHECK_NE(ImageController::ImageDecodeResult::DECODE_NOT_REQUIRED, result);
240 DCHECK_EQ(outstanding_image_decode_.value().stable_id(), image_id);
241 outstanding_image_decode_.reset();
243 // The async decode state may have been cleared if the tracker was cleared
244 // before this decode could be finished.
245 auto it = image_async_decode_state_.find(image_id);
246 if (it == image_async_decode_state_.end()) {
247 DCHECK_EQ(image_id_to_decode_.count(image_id), 0u);
251 // We might have flipped this to sync while updating the hints. That function
252 // would have also requested an invalidation, so we can just schedule the next
254 if (it->second.policy == DecodePolicy::SYNC) {
255 DCHECK(decoding_mode_map_.find(image_id) != decoding_mode_map_.end());
256 DCHECK_EQ(decoding_mode_map_[image_id], PaintImage::DecodingMode::kSync);
258 ScheduleNextImageDecode();
262 it->second.policy = DecodePolicy::SYNC;
263 images_pending_invalidation_.insert(image_id);
264 ScheduleNextImageDecode();
265 client_->NeedsInvalidationForCheckerImagedTiles();
268 bool CheckerImageTracker::ShouldCheckerImage(const DrawImage& draw_image,
270 const PaintImage& image = draw_image.paint_image();
271 PaintImage::Id image_id = image.stable_id();
272 TRACE_EVENT1("cc.debug", "CheckerImageTracker::ShouldCheckerImage",
273 "image_id", image_id);
275 // Checkering of all images is disabled.
276 if (!enable_checker_imaging_)
279 if (!image.IsLazyGenerated())
282 // If the image was invalidated on the current sync tree and the tile is
283 // for the active tree, continue checkering it on the active tree to ensure
284 // the image update is atomic for the frame.
285 if (invalidated_images_on_current_sync_tree_.count(image_id) != 0 &&
286 tree == WhichTree::ACTIVE_TREE) {
290 // If the image is pending invalidation, continue checkering it. All tiles
291 // for these images will be invalidated on the next pending tree.
292 if (images_pending_invalidation_.find(image_id) !=
293 images_pending_invalidation_.end()) {
297 auto decoding_mode_it = decoding_mode_map_.find(image_id);
298 PaintImage::DecodingMode decoding_mode_hint =
299 decoding_mode_it == decoding_mode_map_.end()
300 ? PaintImage::DecodingMode::kUnspecified
301 : decoding_mode_it->second;
303 // We only checker images if the developer specifies async decoding mode.
304 if (decoding_mode_hint != PaintImage::DecodingMode::kAsync)
307 auto insert_result = image_async_decode_state_.insert(
308 std::pair<PaintImage::Id, DecodeState>(image_id, DecodeState()));
309 auto it = insert_result.first;
310 if (insert_result.second) {
311 // The following conditions must be true for an image to be checkerable:
313 // 1) Complete: The data for the image should have been completely loaded.
315 // 2) Static: Animated images/video frames can not be checkered.
317 // 3) Size constraints: Small images for which the decode is expected to
318 // be fast and large images which would breach the image cache budget and
319 // go through the at-raster decode path are not checkered.
321 // 4) Multipart images: Multipart images can be used to display mjpg video
322 // frames, checkering which would cause each video frame to flash and
323 // therefore should not be checkered.
325 // Note that we only need to do this check if we didn't veto above in this
327 CheckerImagingDecision decision = GetCheckerImagingDecision(
328 image, draw_image.src_rect(), min_image_bytes_to_checker_,
329 image_controller_->image_cache_max_limit_bytes());
331 if (decision == CheckerImagingDecision::kCanChecker && force_disabled_) {
332 // Get the decision for all the veto reasons first, so we can UMA the
333 // images that were not checkered only because checker-imaging was force
335 decision = CheckerImagingDecision::kVetoedForceDisable;
338 it->second.policy = decision == CheckerImagingDecision::kCanChecker
339 ? DecodePolicy::ASYNC
340 : DecodePolicy::SYNC;
342 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
343 "CheckerImageTracker::CheckerImagingDecision", "image_params",
344 ToString(image_id, decision));
347 // Update the decode state from the latest image we have seen. Note that it
348 // is not necessary to perform this in the early out cases above since in
349 // each of those cases the image has already been decoded.
350 UpdateDecodeState(draw_image, image_id, &it->second);
352 return it->second.policy == DecodePolicy::ASYNC;
355 void CheckerImageTracker::UpdateDecodeState(const DrawImage& draw_image,
356 PaintImage::Id paint_image_id,
357 DecodeState* decode_state) {
358 // If the policy is not async then either we decoded this image already or
359 // we decided not to ever checker it.
360 if (decode_state->policy != DecodePolicy::ASYNC)
363 // If the decode is already in flight, then we will have to live with what we
365 if (outstanding_image_decode_.has_value() &&
366 outstanding_image_decode_.value().stable_id() == paint_image_id) {
370 // Choose the max scale and filter quality. This keeps the memory usage to the
371 // minimum possible while still increasing the possibility of getting a cache
373 decode_state->scale = SkSize::Make(
374 std::max(decode_state->scale.fWidth, draw_image.scale().fWidth),
375 std::max(decode_state->scale.fHeight, draw_image.scale().fHeight));
376 decode_state->use_dark_mode = draw_image.use_dark_mode();
377 decode_state->filter_quality =
378 std::max(decode_state->filter_quality, draw_image.filter_quality());
379 decode_state->target_color_params = draw_image.target_color_params();
380 decode_state->frame_index = draw_image.frame_index();
383 void CheckerImageTracker::ScheduleNextImageDecode() {
384 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
385 "CheckerImageTracker::ScheduleNextImageDecode");
386 // We can have only one outstanding decode pending completion with the decode
387 // service. We'll come back here when it is completed.
388 if (outstanding_image_decode_.has_value())
391 if (image_decode_queue_.empty())
394 // If scheduling decodes for this priority is not allowed right now, don't
395 // schedule them. We will come back here when the allowed priority changes.
396 if (image_decode_queue_.front().type > decode_priority_allowed_)
399 DrawImage draw_image;
400 while (!image_decode_queue_.empty()) {
401 auto candidate = std::move(image_decode_queue_.front().paint_image);
402 image_decode_queue_.erase(image_decode_queue_.begin());
404 // Once an image has been decoded, it can still be present in the decode
405 // queue (duplicate entries), or while an image is still being skipped on
406 // the active tree. Check if the image is still ASYNC to see if a decode is
408 PaintImage::Id image_id = candidate.stable_id();
409 auto it = image_async_decode_state_.find(image_id);
410 DCHECK(it != image_async_decode_state_.end());
411 if (it->second.policy != DecodePolicy::ASYNC)
414 draw_image = DrawImage(
415 candidate, it->second.use_dark_mode,
416 SkIRect::MakeWH(candidate.width(), candidate.height()),
417 it->second.filter_quality,
418 SkM44::Scale(it->second.scale.width(), it->second.scale.height()),
419 it->second.frame_index, it->second.target_color_params);
420 outstanding_image_decode_.emplace(candidate);
424 // We either found an image to decode or we reached the end of the queue. If
425 // we couldn't find an image, we're done.
426 if (!outstanding_image_decode_.has_value()) {
427 DCHECK(image_decode_queue_.empty());
431 PaintImage::Id image_id = outstanding_image_decode_.value().stable_id();
432 DCHECK_EQ(image_id_to_decode_.count(image_id), 0u);
433 TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
434 "cc", "CheckerImageTracker::DeferImageDecode", TRACE_ID_LOCAL(image_id));
435 ImageController::ImageDecodeRequestId request_id =
436 image_controller_->QueueImageDecode(
437 draw_image, base::BindOnce(&CheckerImageTracker::DidFinishImageDecode,
438 weak_factory_.GetWeakPtr(), image_id));
440 image_id_to_decode_.emplace(image_id, std::make_unique<ScopedDecodeHolder>(
441 image_controller_, request_id));
444 void CheckerImageTracker::UpdateImageDecodingHints(
445 base::flat_map<PaintImage::Id, PaintImage::DecodingMode>
447 if (!enable_checker_imaging_)
450 // Merge the |decoding_mode_map| with our member map, keeping the more
451 // conservative values.
452 // TODO(vmpstr): Figure out if and how do we clear this value to ensure that
453 // if we no longer have any kSync images, for example, then we can loosen the
454 // requirement on the decoding mode for that image id.
455 for (auto pair : decoding_mode_map) {
456 PaintImage::Id id = pair.first;
457 PaintImage::DecodingMode decoding_mode = pair.second;
459 // In case we already have this image as async, it implies that we are
460 // currently displaying this content as checkered. We can flip the state to
461 // sync here and add the image to be invalidated. The invalidation should
462 // happen shortly after, since this function should be called in a commit.
463 auto state_it = image_async_decode_state_.find(id);
464 if (state_it != image_async_decode_state_.end()) {
465 auto& state = state_it->second;
466 if (state.policy == DecodePolicy::ASYNC &&
467 decoding_mode == PaintImage::DecodingMode::kSync) {
468 state.policy = DecodePolicy::SYNC;
469 images_pending_invalidation_.insert(id);
473 // Update the decoding hints map.
474 auto decoding_mode_it = decoding_mode_map_.find(id);
475 if (decoding_mode_it == decoding_mode_map_.end()) {
476 decoding_mode_map_[id] = decoding_mode;
478 decoding_mode_it->second =
479 PaintImage::GetConservative(decoding_mode_it->second, decoding_mode);