Index: cc/tiles/image_controller.cc |
diff --git a/cc/tiles/image_controller.cc b/cc/tiles/image_controller.cc |
index b0eaa101ebf5f45f2f79d1049bf4735b92021e78..886a00e2983f8b587cfbbc33cce1c931bcf8dab0 100644 |
--- a/cc/tiles/image_controller.cc |
+++ b/cc/tiles/image_controller.cc |
@@ -4,10 +4,119 @@ |
#include "cc/tiles/image_controller.h" |
+#include "base/bind.h" |
+#include "cc/tiles/tile_task_manager.h" |
+ |
namespace cc { |
+namespace { |
+class WorkerThread : public base::SimpleThread { |
+ public: |
+ WorkerThread(const std::string& name_prefix, |
+ const Options& options, |
+ ImageController* image_manager) |
+ : base::SimpleThread(name_prefix, options), |
+ image_manager_(image_manager) {} |
+ |
+ void Run() override { image_manager_->ProcessImageDecodes(); } |
+ |
+ private: |
+ ImageController* image_manager_ = nullptr; |
+}; |
+ |
+const int kNumFramesToLock = 2; |
+} // namespace |
+ |
+ImageController::ImageDecodeRequestId |
+ ImageController::s_next_image_decode_queue_id_ = 1; |
+ |
+ImageController::ImageController(base::SequencedTaskRunner* task_runner) |
+ : task_runner_(task_runner), |
+ work_available_cv_(&lock_), |
+ weak_ptr_factory_(this) { |
+ // Worker thread will be started when we get a controller. |
+} |
-ImageController::ImageController() = default; |
-ImageController::~ImageController() = default; |
+ImageController::~ImageController() { |
+ StopWorkerThread(); |
+} |
+ |
+void ImageController::StartWorkerThread() { |
+ DCHECK(!worker_thread_); |
+ |
+ shutdown_ = false; |
+ worker_thread_.reset(new WorkerThread( |
+ "ImageDecodeWorker", |
+ base::SimpleThread::Options(base::ThreadPriority::BACKGROUND), this)); |
+ worker_thread_->Start(); |
+} |
+ |
+void ImageController::StopWorkerThread() { |
+ if (shutdown_) { |
+ DCHECK(!cache_); |
+ DCHECK(!worker_thread_); |
+ return; |
+ } |
+ |
+ // First thing, join the thread so we don't have any races in the rest of the |
+ // function and so we don't need to acquire the lock. |
+ shutdown_ = true; |
+ work_available_cv_.Signal(); |
+ worker_thread_->Join(); |
+ worker_thread_.reset(); |
+ |
+ // Invalidate the weak ptrs, so any tasks to complete requests won't run (we |
+ // will complete everything in this function). |
+ weak_ptr_factory_.InvalidateWeakPtrs(); |
+ |
+ // 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.first); |
+ 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_) { |
+ ImageDecodeRequestId id = request_to_complete.first; |
+ ImageDecodeRequest& request = request_to_complete.second; |
+ |
+ // The task (if one exists) would have run already, so we just need to |
+ // complete it. |
+ if (request.task) |
+ request.task->DidComplete(); |
+ |
+ // Issue the callback, and unref the image immediately. This is so that any |
+ // code waiting on the callback can proceed, although we're breaking the |
+ // promise of having this image decoded. This is unfortunate, but it seems |
+ // like the least complexity to process an image decode controller becoming |
+ // nullptr. |
+ request.callback.Run(id); |
+ if (request.need_unref) |
+ cache_->UnrefImage(request.draw_image); |
+ } |
+ 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_) { |
+ ImageDecodeRequestId id = request_pair.first; |
+ 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". |
+ if (request.task->state().IsNew()) |
+ request.task->state().DidCancel(); |
+ request.task->DidComplete(); |
+ } |
+ // Run the callback and unref the image. |
+ request.callback.Run(id); |
+ cache_->UnrefImage(request.draw_image); |
+ } |
+ image_decode_queue_.clear(); |
+} |
void ImageController::SetImageDecodeCache(ImageDecodeCache* cache) { |
// We can only switch from null to non-null and back. |
@@ -18,8 +127,13 @@ void ImageController::SetImageDecodeCache(ImageDecodeCache* cache) { |
if (!cache) { |
SetPredecodeImages(std::vector<DrawImage>(), |
ImageDecodeCache::TracingInfo()); |
+ StopWorkerThread(); |
} |
cache_ = cache; |
+ |
+ if (cache_) |
+ StartWorkerThread(); |
+ |
// Debugging information for crbug.com/650234. |
++num_times_cache_was_set_; |
} |
@@ -65,4 +179,145 @@ std::vector<scoped_refptr<TileTask>> ImageController::SetPredecodeImages( |
return new_tasks; |
} |
+ImageController::ImageDecodeRequestId ImageController::QueueImageDecode( |
+ sk_sp<const SkImage> image, |
+ const ImageDecodedCallback& callback) { |
+ // Generate the next id. |
+ ImageDecodeRequestId id = s_next_image_decode_queue_id_++; |
+ |
+ DCHECK(image); |
+ auto image_bounds = image->bounds(); |
+ DrawImage draw_image(std::move(image), image_bounds, kNone_SkFilterQuality, |
+ SkMatrix::I()); |
+ |
+ // Get the tasks for this decode. |
+ scoped_refptr<TileTask> task; |
+ bool 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); |
+ work_available_cv_.Signal(); |
+ return id; |
+} |
+ |
+void ImageController::NotifyFrameFinished() { |
+ // Reduce the locked frame count on all images and unlock them if the count |
+ // reaches 0. |
+ for (auto it = requested_locked_images_.begin(); |
+ it != requested_locked_images_.end();) { |
+ if (--it->second == 0) { |
+ UnrefImages({it->first}); |
+ it = requested_locked_images_.erase(it); |
+ } else { |
+ ++it; |
+ } |
+ } |
+} |
+ |
+void ImageController::ProcessImageDecodes() { |
+ base::AutoLock hold(lock_); |
+ for (;;) { |
+ // Exit on shutdown, so the thread can be joined. |
+ if (shutdown_) |
+ break; |
+ |
+ // If we don't have any work, wait until we get some. |
+ if (image_decode_queue_.empty()) { |
+ work_available_cv_.Wait(); |
+ continue; |
+ } |
+ |
+ // Take the next request from the queue. |
+ auto decode_it = image_decode_queue_.begin(); |
+ DCHECK(decode_it != image_decode_queue_.end()); |
+ ImageDecodeRequest 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 |
+ // StopWorkerThread(). 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; |
+ |
+ { |
+ base::AutoUnlock unlock(lock_); |
+ // 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(); |
+ } |
+ task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&ImageController::ImageDecodeCompleted, |
+ weak_ptr_factory_.GetWeakPtr(), decode.id)); |
+ } |
+ } |
+} |
+ |
+void ImageController::ImageDecodeCompleted(ImageDecodeRequestId id) { |
+ ImageDecodedCallback callback; |
+ { |
+ 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; |
+ |
+ // If we need to urnef this decode, then we have to put it into the locked |
+ // images vector. |
+ if (request.need_unref) { |
+ requested_locked_images_.push_back( |
+ std::make_pair(std::move(request.draw_image), kNumFramesToLock)); |
+ } |
+ // 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); |
+ } |
+ callback.Run(id); |
+} |
+ |
+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 |