blob: ae81e64f96d06211f771c146df9d4263a4436a14 [file] [log] [blame]
// 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<base::SequencedTaskRunner> 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<DrawImage>(),
ImageDecodeCache::TracingInfo());
StopWorkerTasks();
}
cache_ = cache;
if (cache_)
GenerateTasksForOrphanedRequests();
}
void ImageController::GetTasksForImagesAndRef(
std::vector<DrawImage>* images,
std::vector<scoped_refptr<TileTask>>* tasks,
const ImageDecodeCache::TracingInfo& tracing_info) {
DCHECK(cache_);
for (auto it = images->begin(); it != images->end();) {
scoped_refptr<TileTask> 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<DrawImage>& images) {
for (auto image : images)
cache_->UnrefImage(image);
}
void ImageController::ReduceMemoryUsage() {
DCHECK(cache_);
cache_->ReduceCacheUsage();
}
std::vector<scoped_refptr<TileTask>> ImageController::SetPredecodeImages(
std::vector<DrawImage> images,
const ImageDecodeCache::TracingInfo& tracing_info) {
std::vector<scoped_refptr<TileTask>> 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<const SkImage> 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();
DrawImage draw_image(std::move(image), image_bounds, kNone_SkFilterQuality,
SkMatrix::I(), target_color_space);
// Get the tasks for this decode.
scoped_refptr<TileTask> 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<TileTask> 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