#include "cc/tiles/image_controller.h"
#include <utility>
#include "base/auto_reset.h"
#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,
base::RepeatingCallback<void(scoped_refptr<TileTask>)>
notify_external_dependent)
: worker_task_runner_(std::move(worker_task_runner)),
notify_external_dependent_(std::move(notify_external_dependent)) {
worker_state_ = std::make_unique<WorkerState>(std::move(origin_task_runner),
weak_ptr_factory_.GetWeakPtr());
worker_task_ = base::BindRepeating(
&ImageController::ProcessNextImageDecodeOnWorkerThread,
base::Unretained(worker_state_.get()));
}
ImageController::~ImageController() {
StopWorkerTasks();
for (auto& request : orphaned_decode_requests_)
std::move(request.callback).Run(request.id, ImageDecodeResult::FAILURE);
if (worker_task_runner_) {
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::ForEachDecodeRequest(
base::FunctionRef<void(ImageDecodeRequest&)> func) {
worker_state_->lock.AssertAcquired();
std::ranges::for_each(
worker_state_->image_decode_queue.begin(),
worker_state_->image_decode_queue.end(), func,
&std::pair<const ImageDecodeRequestId, ImageDecodeRequest>::second);
std::ranges::for_each(orphaned_decode_requests_.begin(),
orphaned_decode_requests_.end(), func);
}
void ImageController::StopWorkerTasks() {
if (!cache_ || !worker_task_runner_)
return;
TileTask::Vector external_dependents;
{
base::AutoLock hold(worker_state_->lock);
while (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();
}
for (auto& image_pair : requested_locked_images_) {
cache_->UnrefImage(image_pair.second);
}
requested_locked_images_.clear();
for (auto& request_to_complete :
worker_state_->requests_needing_completion) {
ImageDecodeRequest& request = request_to_complete.second;
if (request.task && !request.task->HasCompleted()) {
request.task->OnTaskCompleted();
request.task->DidComplete();
if (auto& dependent = request.task->external_dependent()) {
external_dependents.push_back(std::move(dependent));
}
}
if (request.need_unref) {
cache_->UnrefImage(request.draw_image);
}
request.task = nullptr;
request.need_unref = false;
orphaned_decode_requests_.push_back(std::move(request));
}
worker_state_->requests_needing_completion.clear();
for (auto& request_pair : worker_state_->image_decode_queue) {
ImageDecodeRequest& request = request_pair.second;
if (request.task) {
if (request.task->state().IsNew()) {
request.task->state().DidCancel();
}
if (!request.task->HasCompleted()) {
request.task->OnTaskCompleted();
request.task->DidComplete();
if (auto& dependent = request.task->external_dependent()) {
external_dependents.push_back(std::move(dependent));
}
}
}
if (request.need_unref) {
cache_->UnrefImage(request.draw_image);
}
request.task = nullptr;
request.need_unref = false;
orphaned_decode_requests_.push_back(std::move(request));
}
worker_state_->image_decode_queue.clear();
}
for (auto& dependent : external_dependents) {
dependent->ExternalDependencyCompleted();
notify_external_dependent_.Run(dependent);
}
}
bool ImageController::HasReadyToRunTask() const {
worker_state_->lock.AssertAcquired();
return std::ranges::any_of(
worker_state_->image_decode_queue.begin(),
worker_state_->image_decode_queue.end(),
[](const ImageDecodeRequest& request) -> bool {
return !request.has_external_dependency;
},
&std::pair<const ImageDecodeRequestId, ImageDecodeRequest>::second);
}
bool ImageController::HasReadyToRunTaskForTesting() const {
base::AutoLock hold(worker_state_->lock);
return HasReadyToRunTask();
}
void ImageController::FlushDecodeTasksForTesting() {
TileTask::Vector external_dependents;
std::vector<base::OnceClosure> callbacks;
{
base::AutoLock hold(worker_state_->lock);
while (worker_state_->task_state != WorkerTaskState::kNoTask) {
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();
}
while (HasReadyToRunTask()) {
ImageController::ProcessNextImageDecodeWithLock(worker_state_.get());
}
for (auto& request_to_complete :
worker_state_->requests_needing_completion) {
ImageDecodeRequest& request = request_to_complete.second;
ImageDecodeResult result = CompleteTaskForRequest(request);
if (request.task && request.task->external_dependent()) {
external_dependents.emplace_back(
std::move(request.task->external_dependent()));
}
callbacks.emplace_back(
base::BindOnce(std::move(request.callback), request.id, result));
}
worker_state_->requests_needing_completion.clear();
}
for (auto& dependent : external_dependents) {
dependent->ExternalDependencyCompleted();
notify_external_dependent_.Run(dependent);
}
for (auto& callback : callbacks) {
std::move(callback).Run();
}
}
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,
const ImageDecodeCache::TracingInfo& tracing_info) {
DCHECK(cache_);
*has_at_raster_images = false;
base::AutoLock hold(worker_state_->lock);
for (auto it = sync_decoded_images->begin();
it != sync_decoded_images->end();) {
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;
if (result.task) {
if (scoped_refptr<TileTask>& dependent =
result.task->external_dependent()) {
ForEachDecodeRequest([&dependent](ImageDecodeRequest& request) -> void {
if (request.task == dependent) {
request.has_external_dependency = true;
}
});
}
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;
ConvertImagesToTasks(&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,
bool speculative) {
CHECK(worker_task_runner_);
ImageDecodeRequestId id = s_next_image_decode_queue_id_++;
DCHECK(draw_image.paint_image());
bool is_image_lazy = draw_image.paint_image().IsLazyGenerated();
ImageDecodeCache::TaskResult result(
false,
false);
if (is_image_lazy) {
if (!cache_) {
orphaned_decode_requests_.emplace_back(
id, draw_image, std::move(callback), nullptr,
false, false);
return id;
}
result = cache_->GetOutOfRasterDecodeTaskForImageAndRef(
image_cache_client_id_, draw_image, speculative);
}
DCHECK(result.need_unref || !result.task);
base::AutoLock hold(worker_state_->lock);
bool has_external_dependency =
result.task && !result.task->dependencies().empty();
CHECK(!has_external_dependency ||
result.task->dependencies()[0]->IsRasterTask());
worker_state_->image_decode_queue[id] = ImageDecodeRequest(
id, draw_image, std::move(callback), std::move(result.task),
result.need_unref, has_external_dependency);
ScheduleImageDecodeOnWorkerIfNeeded();
return id;
}
void ImageController::ExternalDependencyCompletedForTask(
scoped_refptr<TileTask> task) {
base::AutoLock hold(worker_state_->lock);
ForEachDecodeRequest([&task](ImageDecodeRequest& request) -> void {
if (request.task == task) {
request.has_external_dependency = false;
}
});
ScheduleImageDecodeOnWorkerIfNeeded();
}
void ImageController::UnlockImageDecode(ImageDecodeRequestId id) {
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(
WorkerState* worker_state) {
TRACE_EVENT0("cc", "ImageController::ProcessNextImageDecodeOnWorkerThread");
base::AutoLock hold(worker_state->lock);
DCHECK_EQ(worker_state->task_state, WorkerTaskState::kQueuedTask);
ImageController::ProcessNextImageDecodeWithLock(worker_state);
worker_state->task_state = WorkerTaskState::kNoTask;
}
void ImageController::ProcessNextImageDecodeWithLock(
WorkerState* worker_state) {
worker_state->lock.AssertAcquired();
if (worker_state->image_decode_queue.empty()) {
return;
}
auto decode_it = worker_state->image_decode_queue.begin();
CHECK(decode_it != worker_state->image_decode_queue.end());
while (decode_it != worker_state->image_decode_queue.end() &&
decode_it->second.has_external_dependency) {
decode_it++;
}
if (decode_it == worker_state->image_decode_queue.end()) {
return;
}
scoped_refptr<TileTask> decode_task = decode_it->second.task;
ImageDecodeRequestId decode_id = decode_it->second.id;
worker_state->requests_needing_completion[decode_id] =
std::move(decode_it->second);
worker_state->image_decode_queue.erase(decode_it);
if (decode_task && decode_task->state().IsNew()) {
decode_task->state().DidSchedule();
decode_task->state().DidStart();
{
base::AutoReset<WorkerTaskState> reset_state(
&worker_state->task_state, WorkerTaskState::kRunningTask);
base::AutoUnlock release(worker_state->lock);
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));
}
void ImageController::ImageDecodeCompleted(ImageDecodeRequestId id) {
ImageDecodedCallback callback;
ImageDecodeResult result;
scoped_refptr<TileTask> external_dependent;
{
base::AutoLock hold(worker_state_->lock);
auto request_it = worker_state_->requests_needing_completion.find(id);
if (request_it == worker_state_->requests_needing_completion.end())
return;
id = request_it->first;
ImageDecodeRequest& request = request_it->second;
result = CompleteTaskForRequest(request);
if (request.task) {
external_dependent = std::move(request.task->external_dependent());
}
callback = std::move(request.callback);
worker_state_->requests_needing_completion.erase(request_it);
ScheduleImageDecodeOnWorkerIfNeeded();
}
if (external_dependent) {
external_dependent->ExternalDependencyCompleted();
notify_external_dependent_.Run(std::move(external_dependent));
}
std::move(callback).Run(id, result);
}
ImageController::ImageDecodeResult ImageController::CompleteTaskForRequest(
ImageDecodeRequest& request) {
worker_state_->lock.AssertAcquired();
ImageDecodeResult result;
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 (request.need_unref) {
requested_locked_images_[request.id] = std::move(request.draw_image);
}
if (request.task) {
if (!request.task->HasCompleted()) {
request.task->OnTaskCompleted();
request.task->DidComplete();
}
}
return 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()) {
ImageDecodeCache::TaskResult result =
cache_->GetOutOfRasterDecodeTaskForImageAndRef(image_cache_client_id_,
request.draw_image);
request.need_unref = result.need_unref;
request.task = result.task;
request.has_external_dependency =
result.task && !result.task->dependencies().empty();
}
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 &&
HasReadyToRunTask()) {
worker_state_->task_state = WorkerTaskState::kQueuedTask;
worker_task_runner_->PostTask(FROM_HERE, worker_task_);
}
}
ImageController::ImageDecodeRequest::ImageDecodeRequest() = default;
ImageController::ImageDecodeRequest::ImageDecodeRequest(
ImageDecodeRequestId id,
const DrawImage& draw_image,
ImageDecodedCallback callback,
scoped_refptr<TileTask> task,
bool need_unref,
bool has_external_dependency)
: id(id),
draw_image(draw_image),
callback(std::move(callback)),
task(std::move(task)),
need_unref(need_unref),
has_external_dependency(has_external_dependency) {}
ImageController::ImageDecodeRequest::ImageDecodeRequest(
ImageDecodeRequest&& other) = default;
ImageController::ImageDecodeRequest::~ImageDecodeRequest() = default;
ImageController::ImageDecodeRequest& ImageController::ImageDecodeRequest::
operator=(ImageDecodeRequest&& other) = default;
}