// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "cc/tiles/image_controller.h" #include "base/bind.h" #include "base/task_scheduler/post_task.h" #include "base/task_scheduler/task_traits.h" #include "base/threading/thread_restrictions.h" #include "base/trace_event/trace_event.h" #include "cc/base/completion_event.h" #include "cc/tiles/tile_task_manager.h" namespace cc { ImageController::ImageDecodeRequestId ImageController::s_next_image_decode_queue_id_ = 1; ImageController::ImageController( base::SequencedTaskRunner* origin_task_runner, scoped_refptr worker_task_runner) : worker_task_runner_(std::move(worker_task_runner)), origin_task_runner_(origin_task_runner), weak_ptr_factory_(this) {} ImageController::~ImageController() { StopWorkerTasks(); for (auto& request : orphaned_decode_requests_) request.callback.Run(request.id, ImageDecodeResult::FAILURE); } void ImageController::StopWorkerTasks() { // We can't have worker threads without a cache_ or a worker_task_runner_, so // terminate early. if (!cache_ || !worker_task_runner_) return; // Abort all tasks that are currently scheduled to run (we'll wait for them to // finish next). { base::AutoLock hold(lock_); abort_tasks_ = true; } // Post a task that will simply signal a completion event to ensure that we // "flush" any scheduled tasks (they will abort). CompletionEvent completion_event; worker_task_runner_->PostTask( FROM_HERE, base::BindOnce([](CompletionEvent* event) { event->Signal(); }, base::Unretained(&completion_event))); completion_event.Wait(); // Reset the abort flag so that new tasks can be scheduled. { base::AutoLock hold(lock_); abort_tasks_ = false; } // Now that we flushed everything, if there was a task running and it // finished, it would have posted a completion callback back to the compositor // thread. We don't want that, so invalidate the weak ptrs again. Note that // nothing can start running between wait and this invalidate, since it would // only run on the current (compositor) thread. weak_ptr_factory_.InvalidateWeakPtrs(); // Now, begin cleanup. // Unlock all of the locked images (note that this vector would only be // populated if we actually need to unref the image. for (auto image_pair : requested_locked_images_) cache_->UnrefImage(image_pair.second); requested_locked_images_.clear(); // Now, complete the tasks that already ran but haven't completed. These would // be posted in the run loop, but since we invalidated the weak ptrs, we need // to run everything manually. for (auto& request_to_complete : requests_needing_completion_) { ImageDecodeRequest& request = request_to_complete.second; // The task (if one exists) would have run already, we just need to make // sure it was completed. Multiple requests for the same image use the same // task so it could have already been completed. if (request.task && !request.task->HasCompleted()) request.task->DidComplete(); if (request.need_unref) cache_->UnrefImage(request.draw_image); // Orphan the request so that we can still run it when a new cache is set. request.task = nullptr; request.need_unref = false; orphaned_decode_requests_.push_back(std::move(request)); } requests_needing_completion_.clear(); // Finally, complete all of the tasks that never started running. This is // similar to the |requests_needing_completion_|, but happens at a different // stage in the pipeline. for (auto& request_pair : image_decode_queue_) { ImageDecodeRequest& request = request_pair.second; if (request.task) { // This task may have run via a different request, so only cancel it if // it's "new". That is, the same task could have been referenced by // several different image deque requests for the same image. if (request.task->state().IsNew()) request.task->state().DidCancel(); if (!request.task->HasCompleted()) request.task->DidComplete(); } cache_->UnrefImage(request.draw_image); // Orphan the request so that we can still run it when a new cache is set. request.task = nullptr; request.need_unref = false; orphaned_decode_requests_.push_back(std::move(request)); } image_decode_queue_.clear(); } void ImageController::SetImageDecodeCache(ImageDecodeCache* cache) { DCHECK(!cache_ || !cache); if (!cache) { SetPredecodeImages(std::vector(), ImageDecodeCache::TracingInfo()); StopWorkerTasks(); image_cache_max_limit_bytes_ = 0u; } cache_ = cache; if (cache_) { image_cache_max_limit_bytes_ = cache_->GetMaximumMemoryLimitBytes(); GenerateTasksForOrphanedRequests(); } } void ImageController::GetTasksForImagesAndRef( std::vector* images, std::vector>* tasks, const ImageDecodeCache::TracingInfo& tracing_info) { DCHECK(cache_); for (auto it = images->begin(); it != images->end();) { scoped_refptr task; bool need_to_unref_when_finished = cache_->GetTaskForImageAndRef(*it, tracing_info, &task); if (task) tasks->push_back(std::move(task)); if (need_to_unref_when_finished) ++it; else it = images->erase(it); } } void ImageController::UnrefImages(const std::vector& images) { for (auto image : images) cache_->UnrefImage(image); } void ImageController::ReduceMemoryUsage() { DCHECK(cache_); cache_->ReduceCacheUsage(); } std::vector> ImageController::SetPredecodeImages( std::vector images, const ImageDecodeCache::TracingInfo& tracing_info) { std::vector> new_tasks; GetTasksForImagesAndRef(&images, &new_tasks, tracing_info); UnrefImages(predecode_locked_images_); predecode_locked_images_ = std::move(images); return new_tasks; } ImageController::ImageDecodeRequestId ImageController::QueueImageDecode( sk_sp image, const ImageDecodedCallback& callback) { // We must not receive any image requests if we have no worker. CHECK(worker_task_runner_); // Generate the next id. ImageDecodeRequestId id = s_next_image_decode_queue_id_++; // TODO(ccameron): The target color space specified here should match the // target color space that will be used at rasterization time. Leave this // unspecified now, since that will match the rasterization-time color // space while color correct rendering is disabled. gfx::ColorSpace target_color_space; DCHECK(image); bool is_image_lazy = image->isLazyGenerated(); auto image_bounds = image->bounds(); // TODO(khushalsagar): Eliminate the use of an incorrect id here and have all // call-sites provide PaintImage to the ImageController. DrawImage draw_image( PaintImage(PaintImage::kUnknownStableId, sk_sp(const_cast(image.release()))), image_bounds, kNone_SkFilterQuality, SkMatrix::I(), target_color_space); // Get the tasks for this decode. scoped_refptr task; bool need_unref = false; if (is_image_lazy) { need_unref = cache_->GetOutOfRasterDecodeTaskForImageAndRef(draw_image, &task); } // If we don't need to unref this, we don't actually have a task. DCHECK(need_unref || !task); // Schedule the task and signal that there is more work. base::AutoLock hold(lock_); image_decode_queue_[id] = ImageDecodeRequest(id, draw_image, callback, std::move(task), need_unref); // If this is the only image decode request, schedule a task to run. // Otherwise, the task will be scheduled in the previou task's completion. if (image_decode_queue_.size() == 1) { // Post a worker task. worker_task_runner_->PostTask( FROM_HERE, base::BindOnce(&ImageController::ProcessNextImageDecodeOnWorkerThread, base::Unretained(this))); } return id; } void ImageController::UnlockImageDecode(ImageDecodeRequestId id) { // If the image exists, ie we actually need to unlock it, then do so. auto it = requested_locked_images_.find(id); if (it == requested_locked_images_.end()) return; UnrefImages({it->second}); requested_locked_images_.erase(it); } void ImageController::ProcessNextImageDecodeOnWorkerThread() { TRACE_EVENT0("cc", "ImageController::ProcessNextImageDecodeOnWorkerThread"); ImageDecodeRequest decode; { base::AutoLock hold(lock_); // If we don't have any work, abort. if (image_decode_queue_.empty() || abort_tasks_) return; // Take the next request from the queue. auto decode_it = image_decode_queue_.begin(); DCHECK(decode_it != image_decode_queue_.end()); decode = std::move(decode_it->second); image_decode_queue_.erase(decode_it); // Notify that the task will need completion. Note that there are two cases // where we process this. First, we might complete this task as a response // to the posted task below. Second, we might complete it in // StopWorkerTasks(). In either case, the task would have already run // (either post task happens after running, or the thread was already joined // which means the task ran). This means that we can put the decode into // |requests_needing_completion_| here before actually running the task. requests_needing_completion_[decode.id] = decode; } // Run the task if we need to run it. If the task state isn't new, then // there is another task that is responsible for finishing it and cleaning // up (and it already ran); we just need to post a completion callback. // Note that the other tasks's completion will also run first, since the // requests are ordered. So, when we process this task's completion, we // won't actually do anything with the task and simply issue the callback. if (decode.task && decode.task->state().IsNew()) { decode.task->state().DidSchedule(); decode.task->state().DidStart(); decode.task->RunOnWorkerThread(); decode.task->state().DidFinish(); } origin_task_runner_->PostTask( FROM_HERE, base::BindOnce(&ImageController::ImageDecodeCompleted, weak_ptr_factory_.GetWeakPtr(), decode.id)); } void ImageController::ImageDecodeCompleted(ImageDecodeRequestId id) { ImageDecodedCallback callback; ImageDecodeResult result = ImageDecodeResult::SUCCESS; { base::AutoLock hold(lock_); auto request_it = requests_needing_completion_.find(id); DCHECK(request_it != requests_needing_completion_.end()); id = request_it->first; ImageDecodeRequest& request = request_it->second; // First, Determine the status of the decode. This has to happen here, since // we conditionally move from the draw image below. // Also note that if we don't need an unref for a lazy decoded images, it // implies that we never attempted the decode. Some of the reasons for this // would be that the image is of an empty size, or if the image doesn't fit // into memory. In all cases, this implies that the decode was a failure. if (!request.draw_image.image()->isLazyGenerated()) result = ImageDecodeResult::DECODE_NOT_REQUIRED; else if (!request.need_unref) result = ImageDecodeResult::FAILURE; else result = ImageDecodeResult::SUCCESS; // If we need to unref this decode, then we have to put it into the locked // images vector. if (request.need_unref) requested_locked_images_[id] = std::move(request.draw_image); // If we have a task that isn't completed yet, we need to complete it. if (request.task && !request.task->HasCompleted()) { request.task->OnTaskCompleted(); request.task->DidComplete(); } // Finally, save the callback so we can run it without the lock, and erase // the request from |requests_needing_completion_|. callback = std::move(request.callback); requests_needing_completion_.erase(request_it); } // Post another task to run. worker_task_runner_->PostTask( FROM_HERE, base::BindOnce(&ImageController::ProcessNextImageDecodeOnWorkerThread, base::Unretained(this))); // Finally run the requested callback. callback.Run(id, result); } void ImageController::GenerateTasksForOrphanedRequests() { base::AutoLock hold(lock_); DCHECK_EQ(0u, image_decode_queue_.size()); DCHECK_EQ(0u, requests_needing_completion_.size()); DCHECK(cache_); for (auto& request : orphaned_decode_requests_) { DCHECK(!request.task); DCHECK(!request.need_unref); if (request.draw_image.image()->isLazyGenerated()) { // Get the task for this decode. request.need_unref = cache_->GetOutOfRasterDecodeTaskForImageAndRef( request.draw_image, &request.task); } image_decode_queue_[request.id] = std::move(request); } orphaned_decode_requests_.clear(); if (!image_decode_queue_.empty()) { // Post a worker task. worker_task_runner_->PostTask( FROM_HERE, base::BindOnce(&ImageController::ProcessNextImageDecodeOnWorkerThread, base::Unretained(this))); } } ImageController::ImageDecodeRequest::ImageDecodeRequest() = default; ImageController::ImageDecodeRequest::ImageDecodeRequest( ImageDecodeRequestId id, const DrawImage& draw_image, const ImageDecodedCallback& callback, scoped_refptr task, bool need_unref) : id(id), draw_image(draw_image), callback(callback), task(std::move(task)), need_unref(need_unref) {} ImageController::ImageDecodeRequest::ImageDecodeRequest( ImageDecodeRequest&& other) = default; ImageController::ImageDecodeRequest::ImageDecodeRequest( const ImageDecodeRequest& other) = default; ImageController::ImageDecodeRequest::~ImageDecodeRequest() = default; ImageController::ImageDecodeRequest& ImageController::ImageDecodeRequest:: operator=(ImageDecodeRequest&& other) = default; ImageController::ImageDecodeRequest& ImageController::ImageDecodeRequest:: operator=(const ImageDecodeRequest& other) = default; } // namespace cc