[go: nahoru, domu]

blob: f415afbcdfc23b013352109827e9f4be37072389 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// 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 <utility>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.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(
scoped_refptr<base::SequencedTaskRunner> origin_task_runner,
scoped_refptr<base::SequencedTaskRunner> worker_task_runner)
: worker_task_runner_(std::move(worker_task_runner)) {
worker_state_ = std::make_unique<WorkerState>(std::move(origin_task_runner),
weak_ptr_factory_.GetWeakPtr());
}
ImageController::~ImageController() {
StopWorkerTasks();
for (auto& request : orphaned_decode_requests_)
std::move(request.callback).Run(request.id, ImageDecodeResult::FAILURE);
if (worker_task_runner_) {
// Delete `worker_state_` on `worker_task_runner_` (or elsewhere via the
// callback's destructor if `worker_task_runner_` stopped accepting tasks).
worker_task_runner_->PostTask(
FROM_HERE, base::DoNothingWithBoundArgs(std::move(worker_state_)));
}
}
ImageController::WorkerState::WorkerState(
scoped_refptr<base::SequencedTaskRunner> origin_task_runner,
base::WeakPtr<ImageController> weak_ptr)
: origin_task_runner(std::move(origin_task_runner)), weak_ptr(weak_ptr) {}
ImageController::WorkerState::~WorkerState() = default;
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;
base::AutoLock hold(worker_state_->lock);
// If a worker task is running, post a task and wait for its completion to
// "flush" the queue.
if (worker_state_->task_state == WorkerTaskState::kRunningTask) {
base::AutoUnlock release(worker_state_->lock);
CompletionEvent completion_event;
worker_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CompletionEvent::Signal,
base::Unretained(&completion_event)));
completion_event.Wait();
}
// 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 : worker_state_->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));
}
worker_state_->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 : worker_state_->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));
}
worker_state_->image_decode_queue.clear();
}
void ImageController::SetImageDecodeCache(ImageDecodeCache* cache) {
DCHECK(!cache_ || !cache);
if (!cache) {
SetPredecodeImages(std::vector<DrawImage>(),
ImageDecodeCache::TracingInfo());
StopWorkerTasks();
image_cache_max_limit_bytes_ = 0u;
image_cache_client_id_ = 0u;
}
cache_ = cache;
if (cache_) {
DCHECK_EQ(image_cache_client_id_, 0u);
image_cache_client_id_ = cache_->GenerateClientId();
image_cache_max_limit_bytes_ = cache_->GetMaximumMemoryLimitBytes();
GenerateTasksForOrphanedRequests();
}
}
void ImageController::ConvertImagesToTasks(
std::vector<DrawImage>* sync_decoded_images,
std::vector<scoped_refptr<TileTask>>* tasks,
bool* has_at_raster_images,
bool* has_hardware_accelerated_jpeg_candidates,
bool* has_hardware_accelerated_webp_candidates,
const ImageDecodeCache::TracingInfo& tracing_info) {
DCHECK(cache_);
*has_at_raster_images = false;
*has_hardware_accelerated_jpeg_candidates = false;
*has_hardware_accelerated_webp_candidates = false;
for (auto it = sync_decoded_images->begin();
it != sync_decoded_images->end();) {
// PaintWorklet images should not be included in this set; they have already
// been painted before raster and so do not need raster-time work.
DCHECK(!it->paint_image().IsPaintWorklet());
ImageDecodeCache::TaskResult result = cache_->GetTaskForImageAndRef(
image_cache_client_id_, *it, tracing_info);
*has_at_raster_images |= result.is_at_raster_decode;
ImageType image_type =
it->paint_image().GetImageHeaderMetadata()
? it->paint_image().GetImageHeaderMetadata()->image_type
: ImageType::kInvalid;
*has_hardware_accelerated_jpeg_candidates |=
(result.can_do_hardware_accelerated_decode &&
image_type == ImageType::kJPEG);
*has_hardware_accelerated_webp_candidates |=
(result.can_do_hardware_accelerated_decode &&
image_type == ImageType::kWEBP);
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;
// The images here are in a pre-decode area: we decode them in advance, but
// they're not dependencies for raster tasks. If these images do end up
// getting rasterized, we will still have a chance to record the raster
// scheduling delay UMAs when we create and run the raster task.
bool has_at_raster_images = false;
bool has_hardware_accelerated_jpeg_candidates = false;
bool has_hardware_accelerated_webp_candidates = false;
ConvertImagesToTasks(&images, &new_tasks, &has_at_raster_images,
&has_hardware_accelerated_jpeg_candidates,
&has_hardware_accelerated_webp_candidates, 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(
/*need_unref=*/false,
/*is_at_raster_decode=*/false,
/*can_do_hardware_accelerated_decode=*/false);
if (is_image_lazy)
result = cache_->GetOutOfRasterDecodeTaskForImageAndRef(
image_cache_client_id_, 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(worker_state_->lock);
worker_state_->image_decode_queue[id] =
ImageDecodeRequest(id, draw_image, std::move(callback),
std::move(result.task), result.need_unref);
ScheduleImageDecodeOnWorkerIfNeeded();
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);
}
// static
void ImageController::ProcessNextImageDecodeOnWorkerThread(
WorkerState* worker_state) {
TRACE_EVENT0("cc", "ImageController::ProcessNextImageDecodeOnWorkerThread");
base::AutoLock hold(worker_state->lock);
DCHECK_EQ(worker_state->task_state, WorkerTaskState::kQueuedTask);
worker_state->task_state = WorkerTaskState::kRunningTask;
// If we don't have any work, abort.
if (worker_state->image_decode_queue.empty()) {
worker_state->task_state = WorkerTaskState::kNoTask;
return;
}
// Take the next request from the queue.
auto decode_it = worker_state->image_decode_queue.begin();
DCHECK(decode_it != worker_state->image_decode_queue.end());
scoped_refptr<TileTask> decode_task = decode_it->second.task;
ImageDecodeRequestId 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.
worker_state->requests_needing_completion[decode_id] =
std::move(decode_it->second);
worker_state->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()) {
base::AutoUnlock release(worker_state->lock);
decode_task->state().DidSchedule();
decode_task->state().DidStart();
decode_task->RunOnWorkerThread();
decode_task->state().DidFinish();
}
worker_state->origin_task_runner->PostTask(
FROM_HERE, base::BindOnce(&ImageController::ImageDecodeCompleted,
worker_state->weak_ptr, decode_id));
DCHECK_EQ(worker_state->task_state, WorkerTaskState::kRunningTask);
worker_state->task_state = WorkerTaskState::kNoTask;
}
void ImageController::ImageDecodeCompleted(ImageDecodeRequestId id) {
ImageDecodedCallback callback;
ImageDecodeResult result = ImageDecodeResult::SUCCESS;
{
base::AutoLock hold(worker_state_->lock);
auto request_it = worker_state_->requests_needing_completion.find(id);
// The request may have been completed by StopWorkerTasks().
if (request_it == worker_state_->requests_needing_completion.end())
return;
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);
worker_state_->requests_needing_completion.erase(request_it);
ScheduleImageDecodeOnWorkerIfNeeded();
}
// Finally run the requested callback.
std::move(callback).Run(id, result);
}
void ImageController::GenerateTasksForOrphanedRequests() {
base::AutoLock hold(worker_state_->lock);
DCHECK_EQ(0u, worker_state_->image_decode_queue.size());
DCHECK_EQ(0u, worker_state_->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(image_cache_client_id_,
request.draw_image);
request.need_unref = result.need_unref;
request.task = result.task;
}
worker_state_->image_decode_queue[request.id] = std::move(request);
}
orphaned_decode_requests_.clear();
ScheduleImageDecodeOnWorkerIfNeeded();
}
void ImageController::ScheduleImageDecodeOnWorkerIfNeeded() {
if (worker_state_->task_state == WorkerTaskState::kNoTask &&
!worker_state_->image_decode_queue.empty()) {
worker_state_->task_state = WorkerTaskState::kQueuedTask;
// base::Unretained is safe because `worker_state_` is guaranteed to be
// deleted from a task posted to `worker_task_runner_` after this (see
// ~ImageController).
worker_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ImageController::ProcessNextImageDecodeOnWorkerThread,
base::Unretained(worker_state_.get())));
}
}
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