blob: 844f8c42d657953407f6528834ca4701f1b2324e [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/post_task.h"
#include "base/task/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) {
weak_ptr_ = weak_ptr_factory_.GetWeakPtr();
}
ImageController::~ImageController() {
StopWorkerTasks();
for (auto& request : orphaned_decode_requests_)
std::move(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();
weak_ptr_ = weak_ptr_factory_.GetWeakPtr();
// 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->OnTaskCompleted();
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->OnTaskCompleted();
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));
}
image_decode_queue_.clear();
}
void ImageController::SetPaintWorkletLayerPainter(
std::unique_ptr<PaintWorkletLayerPainter> painter) {
paint_worklet_image_cache_.SetPaintWorkletLayerPainter(std::move(painter));
}
void ImageController::SetImageDecodeCache(ImageDecodeCache* cache) {
DCHECK(!cache_ || !cache);
if (!cache) {
SetPredecodeImages(std::vector<DrawImage>(),
ImageDecodeCache::TracingInfo());
StopWorkerTasks();
image_cache_max_limit_bytes_ = 0u;
}
cache_ = cache;
if (cache_) {
image_cache_max_limit_bytes_ = cache_->GetMaximumMemoryLimitBytes();
GenerateTasksForOrphanedRequests();
}
}
void ImageController::ConvertPaintWorkletImagesToTask(
std::vector<DrawImage>* sync_decoded_images,
std::vector<scoped_refptr<TileTask>>* tasks) {
for (auto it = sync_decoded_images->begin();
it != sync_decoded_images->end();) {
if (!it->paint_image().IsPaintWorklet()) {
++it;
continue;
}
scoped_refptr<TileTask> result =
paint_worklet_image_cache_.GetTaskForPaintWorkletImage(*it);
DCHECK(result);
tasks->push_back(std::move(result));
// Remove it so that there is no need to check whether an image is
// PaintWorklet generated or not in TileManager's
// work_to_schedule->extra_prepaint_images.insert.
it = sync_decoded_images->erase(it);
}
}
void ImageController::ConvertDataImagesToTasks(
std::vector<DrawImage>* sync_decoded_images,
std::vector<scoped_refptr<TileTask>>* tasks,
bool* has_at_raster_images,
const ImageDecodeCache::TracingInfo& tracing_info) {
DCHECK(cache_);
*has_at_raster_images = false;
for (auto it = sync_decoded_images->begin();
it != sync_decoded_images->end();) {
if (it->paint_image().IsPaintWorklet()) {
++it;
continue;
}
ImageDecodeCache::TaskResult result =
cache_->GetTaskForImageAndRef(*it, tracing_info);
*has_at_raster_images |= result.IsAtRaster();
if (result.task)
tasks->push_back(std::move(result.task));
if (result.need_unref)
++it;
else
it = sync_decoded_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;
bool has_at_raster_images = false;
ConvertDataImagesToTasks(&images, &new_tasks, &has_at_raster_images,
tracing_info);
UnrefImages(predecode_locked_images_);
predecode_locked_images_ = std::move(images);
return new_tasks;
}
ImageController::ImageDecodeRequestId ImageController::QueueImageDecode(
const DrawImage& draw_image,
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_++;
DCHECK(draw_image.paint_image());
bool is_image_lazy = draw_image.paint_image().IsLazyGenerated();
// Get the tasks for this decode.
ImageDecodeCache::TaskResult result(false);
if (is_image_lazy)
result = cache_->GetOutOfRasterDecodeTaskForImageAndRef(draw_image);
// If we don't need to unref this, we don't actually have a task.
DCHECK(result.need_unref || !result.task);
// Schedule the task and signal that there is more work.
base::AutoLock hold(lock_);
image_decode_queue_[id] =
ImageDecodeRequest(id, draw_image, std::move(callback),
std::move(result.task), result.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({std::move(it->second)});
requested_locked_images_.erase(it);
}
void ImageController::ProcessNextImageDecodeOnWorkerThread() {
TRACE_EVENT0("cc", "ImageController::ProcessNextImageDecodeOnWorkerThread");
scoped_refptr<TileTask> decode_task;
ImageDecodeRequestId decode_id;
{
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_task = decode_it->second.task;
decode_id = decode_it->second.id;
// 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] = std::move(decode_it->second);
image_decode_queue_.erase(decode_it);
}
// 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_, 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.paint_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.
std::move(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.paint_image().IsLazyGenerated()) {
// Get the task for this decode.
ImageDecodeCache::TaskResult result =
cache_->GetOutOfRasterDecodeTaskForImageAndRef(request.draw_image);
request.need_unref = result.need_unref;
request.task = result.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,
ImageDecodedCallback callback,
scoped_refptr<TileTask> task,
bool need_unref)
: id(id),
draw_image(draw_image),
callback(std::move(callback)),
task(std::move(task)),
need_unref(need_unref) {}
ImageController::ImageDecodeRequest::ImageDecodeRequest(
ImageDecodeRequest&& other) = default;
ImageController::ImageDecodeRequest::~ImageDecodeRequest() = default;
ImageController::ImageDecodeRequest& ImageController::ImageDecodeRequest::
operator=(ImageDecodeRequest&& other) = default;
} // namespace cc