blob: 3a89afb2b64961ce543d434e66f98ff2547cbf78 [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 <utility>
#include "base/bind.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_checker_impl.h"
#include "cc/test/skia_common.h"
#include "cc/tiles/image_decode_cache.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cc {
namespace {
class TestWorkerThread : public base::SimpleThread {
public:
TestWorkerThread()
: base::SimpleThread("test_worker_thread"), condition_(&lock_) {}
void Run() override {
for (;;) {
base::OnceClosure task;
{
base::AutoLock hold(lock_);
if (shutdown_)
break;
if (queue_.empty()) {
condition_.Wait();
continue;
}
task = std::move(queue_.front());
queue_.erase(queue_.begin());
}
std::move(task).Run();
}
}
void Shutdown() {
base::AutoLock hold(lock_);
shutdown_ = true;
condition_.Signal();
}
void PostTask(base::OnceClosure task) {
base::AutoLock hold(lock_);
queue_.push_back(std::move(task));
condition_.Signal();
}
private:
base::Lock lock_;
base::ConditionVariable condition_;
std::vector<base::OnceClosure> queue_;
bool shutdown_ = false;
};
class WorkerTaskRunner : public base::SequencedTaskRunner {
public:
WorkerTaskRunner() { thread_.Start(); }
bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay) override {
return PostDelayedTask(from_here, std::move(task), delay);
}
bool PostDelayedTask(const tracked_objects::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay) override {
thread_.PostTask(std::move(task));
return true;
}
bool RunsTasksOnCurrentThread() const override { return false; }
protected:
~WorkerTaskRunner() override {
thread_.Shutdown();
thread_.Join();
}
TestWorkerThread thread_;
};
// Image decode cache with introspection!
class TestableCache : public ImageDecodeCache {
public:
~TestableCache() override { EXPECT_EQ(number_of_refs_, 0); }
bool GetTaskForImageAndRef(const DrawImage& image,
const TracingInfo& tracing_info,
scoped_refptr<TileTask>* task) override {
// Return false for large images to mimic "won't fit in memory"
// behavior.
if (image.image() &&
image.image()->width() * image.image()->height() >= 1000 * 1000) {
return false;
}
*task = task_to_use_;
++number_of_refs_;
return true;
}
bool GetOutOfRasterDecodeTaskForImageAndRef(
const DrawImage& image,
scoped_refptr<TileTask>* task) override {
return GetTaskForImageAndRef(image, TracingInfo(), task);
}
void UnrefImage(const DrawImage& image) override {
ASSERT_GT(number_of_refs_, 0);
--number_of_refs_;
}
DecodedDrawImage GetDecodedImageForDraw(const DrawImage& image) override {
return DecodedDrawImage(nullptr, kNone_SkFilterQuality);
}
void DrawWithImageFinished(const DrawImage& image,
const DecodedDrawImage& decoded_image) override {}
void ReduceCacheUsage() override {}
void SetShouldAggressivelyFreeResources(
bool aggressively_free_resources) override {}
void ClearCache() override {}
int number_of_refs() const { return number_of_refs_; }
void SetTaskToUse(scoped_refptr<TileTask> task) { task_to_use_ = task; }
private:
int number_of_refs_ = 0;
scoped_refptr<TileTask> task_to_use_;
};
// A simple class that can receive decode callbacks.
class DecodeClient {
public:
DecodeClient() {}
void Callback(base::OnceClosure quit_closure,
ImageController::ImageDecodeRequestId id,
ImageController::ImageDecodeResult result) {
id_ = id;
result_ = result;
std::move(quit_closure).Run();
}
ImageController::ImageDecodeRequestId id() { return id_; }
ImageController::ImageDecodeResult result() { return result_; }
private:
ImageController::ImageDecodeRequestId id_ = 0;
ImageController::ImageDecodeResult result_ =
ImageController::ImageDecodeResult::FAILURE;
};
// A dummy task that does nothing.
class SimpleTask : public TileTask {
public:
SimpleTask() : TileTask(true /* supports_concurrent_execution */) {
EXPECT_TRUE(thread_checker_.CalledOnValidThread());
}
void RunOnWorkerThread() override {
EXPECT_FALSE(HasCompleted());
has_run_ = true;
}
void OnTaskCompleted() override {
EXPECT_TRUE(thread_checker_.CalledOnValidThread());
}
bool has_run() { return has_run_; }
private:
~SimpleTask() override = default;
base::ThreadChecker thread_checker_;
bool has_run_ = false;
DISALLOW_COPY_AND_ASSIGN(SimpleTask);
};
// A task that blocks until instructed otherwise.
class BlockingTask : public TileTask {
public:
BlockingTask()
: TileTask(true /* supports_concurrent_execution */), run_cv_(&lock_) {
EXPECT_TRUE(thread_checker_.CalledOnValidThread());
}
void RunOnWorkerThread() override {
EXPECT_FALSE(HasCompleted());
EXPECT_FALSE(thread_checker_.CalledOnValidThread());
base::AutoLock hold(lock_);
if (!can_run_)
run_cv_.Wait();
has_run_ = true;
}
void OnTaskCompleted() override {
EXPECT_TRUE(thread_checker_.CalledOnValidThread());
}
void AllowToRun() {
base::AutoLock hold(lock_);
can_run_ = true;
run_cv_.Signal();
}
bool has_run() { return has_run_; }
private:
~BlockingTask() override = default;
// Use ThreadCheckerImpl, so that release builds also get correct behavior.
base::ThreadCheckerImpl thread_checker_;
bool has_run_ = false;
base::Lock lock_;
base::ConditionVariable run_cv_;
bool can_run_ = false;
DISALLOW_COPY_AND_ASSIGN(BlockingTask);
};
// For tests that exercise image controller's thread, this is the timeout value
// to allow the worker thread to do its work.
int kDefaultTimeoutSeconds = 10;
class ImageControllerTest : public testing::Test {
public:
ImageControllerTest() : task_runner_(base::SequencedTaskRunnerHandle::Get()) {
image_ = CreateDiscardableImage(gfx::Size(1, 1));
}
~ImageControllerTest() override = default;
void SetUp() override {
worker_task_runner_ = make_scoped_refptr(new WorkerTaskRunner);
controller_.reset(
new ImageController(task_runner_.get(), worker_task_runner_));
cache_ = TestableCache();
controller_->SetImageDecodeCache(&cache_);
}
void TearDown() override {
controller_.reset();
worker_task_runner_ = nullptr;
}
base::SequencedTaskRunner* task_runner() { return task_runner_.get(); }
ImageController* controller() { return controller_.get(); }
TestableCache* cache() { return &cache_; }
sk_sp<const SkImage> image() const { return image_; }
// Timeout callback, which errors and exits the runloop.
static void Timeout(base::RunLoop* run_loop) {
ADD_FAILURE() << "Timeout.";
run_loop->Quit();
}
// Convenience method to run the run loop with a timeout.
void RunOrTimeout(base::RunLoop* run_loop) {
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ImageControllerTest::Timeout,
base::Unretained(run_loop)),
base::TimeDelta::FromSeconds(kDefaultTimeoutSeconds));
run_loop->Run();
}
void ResetController() { controller_.reset(); }
private:
scoped_refptr<base::SequencedTaskRunner> task_runner_;
scoped_refptr<WorkerTaskRunner> worker_task_runner_;
TestableCache cache_;
std::unique_ptr<ImageController> controller_;
sk_sp<const SkImage> image_;
};
TEST_F(ImageControllerTest, NullControllerUnrefsImages) {
std::vector<DrawImage> images(10);
ImageDecodeCache::TracingInfo tracing_info;
ASSERT_EQ(10u, images.size());
auto tasks =
controller()->SetPredecodeImages(std::move(images), tracing_info);
EXPECT_EQ(0u, tasks.size());
EXPECT_EQ(10, cache()->number_of_refs());
controller()->SetImageDecodeCache(nullptr);
EXPECT_EQ(0, cache()->number_of_refs());
}
TEST_F(ImageControllerTest, QueueImageDecode) {
base::RunLoop run_loop;
DecodeClient decode_client;
EXPECT_EQ(image()->bounds().width(), 1);
ImageController::ImageDecodeRequestId expected_id =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client),
run_loop.QuitClosure()));
RunOrTimeout(&run_loop);
EXPECT_EQ(expected_id, decode_client.id());
EXPECT_EQ(ImageController::ImageDecodeResult::SUCCESS,
decode_client.result());
}
TEST_F(ImageControllerTest, QueueImageDecodeNonLazy) {
base::RunLoop run_loop;
DecodeClient decode_client;
SkBitmap bitmap;
bitmap.allocN32Pixels(1, 1);
sk_sp<const SkImage> image = SkImage::MakeFromBitmap(bitmap);
ImageController::ImageDecodeRequestId expected_id =
controller()->QueueImageDecode(
image,
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client),
run_loop.QuitClosure()));
RunOrTimeout(&run_loop);
EXPECT_EQ(expected_id, decode_client.id());
EXPECT_EQ(ImageController::ImageDecodeResult::DECODE_NOT_REQUIRED,
decode_client.result());
}
TEST_F(ImageControllerTest, QueueImageDecodeTooLarge) {
base::RunLoop run_loop;
DecodeClient decode_client;
sk_sp<const SkImage> image = CreateDiscardableImage(gfx::Size(2000, 2000));
ImageController::ImageDecodeRequestId expected_id =
controller()->QueueImageDecode(
image,
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client),
run_loop.QuitClosure()));
RunOrTimeout(&run_loop);
EXPECT_EQ(expected_id, decode_client.id());
EXPECT_EQ(ImageController::ImageDecodeResult::FAILURE,
decode_client.result());
}
TEST_F(ImageControllerTest, QueueImageDecodeMultipleImages) {
base::RunLoop run_loop;
DecodeClient decode_client1;
ImageController::ImageDecodeRequestId expected_id1 =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client1),
base::Bind([] {})));
DecodeClient decode_client2;
ImageController::ImageDecodeRequestId expected_id2 =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client2),
base::Bind([] {})));
DecodeClient decode_client3;
ImageController::ImageDecodeRequestId expected_id3 =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client3),
run_loop.QuitClosure()));
RunOrTimeout(&run_loop);
EXPECT_EQ(expected_id1, decode_client1.id());
EXPECT_EQ(ImageController::ImageDecodeResult::SUCCESS,
decode_client1.result());
EXPECT_EQ(expected_id2, decode_client2.id());
EXPECT_EQ(ImageController::ImageDecodeResult::SUCCESS,
decode_client2.result());
EXPECT_EQ(expected_id3, decode_client3.id());
EXPECT_EQ(ImageController::ImageDecodeResult::SUCCESS,
decode_client3.result());
}
TEST_F(ImageControllerTest, QueueImageDecodeWithTask) {
scoped_refptr<SimpleTask> task(new SimpleTask);
cache()->SetTaskToUse(task);
base::RunLoop run_loop;
DecodeClient decode_client;
ImageController::ImageDecodeRequestId expected_id =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client),
run_loop.QuitClosure()));
RunOrTimeout(&run_loop);
EXPECT_EQ(expected_id, decode_client.id());
EXPECT_TRUE(task->has_run());
EXPECT_TRUE(task->HasCompleted());
}
TEST_F(ImageControllerTest, QueueImageDecodeMultipleImagesSameTask) {
scoped_refptr<SimpleTask> task(new SimpleTask);
cache()->SetTaskToUse(task);
base::RunLoop run_loop;
DecodeClient decode_client1;
ImageController::ImageDecodeRequestId expected_id1 =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client1),
base::Bind([] {})));
DecodeClient decode_client2;
ImageController::ImageDecodeRequestId expected_id2 =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client2),
base::Bind([] {})));
DecodeClient decode_client3;
ImageController::ImageDecodeRequestId expected_id3 =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client3),
run_loop.QuitClosure()));
RunOrTimeout(&run_loop);
EXPECT_EQ(expected_id1, decode_client1.id());
EXPECT_EQ(ImageController::ImageDecodeResult::SUCCESS,
decode_client1.result());
EXPECT_EQ(expected_id2, decode_client2.id());
EXPECT_EQ(ImageController::ImageDecodeResult::SUCCESS,
decode_client2.result());
EXPECT_EQ(expected_id3, decode_client3.id());
EXPECT_EQ(ImageController::ImageDecodeResult::SUCCESS,
decode_client3.result());
EXPECT_TRUE(task->has_run());
EXPECT_TRUE(task->HasCompleted());
}
TEST_F(ImageControllerTest, QueueImageDecodeChangeControllerWithTaskQueued) {
scoped_refptr<BlockingTask> task_one(new BlockingTask);
cache()->SetTaskToUse(task_one);
DecodeClient decode_client1;
ImageController::ImageDecodeRequestId expected_id1 =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client1),
base::Bind([] {})));
scoped_refptr<BlockingTask> task_two(new BlockingTask);
cache()->SetTaskToUse(task_two);
base::RunLoop run_loop;
DecodeClient decode_client2;
ImageController::ImageDecodeRequestId expected_id2 =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client2),
run_loop.QuitClosure()));
task_one->AllowToRun();
task_two->AllowToRun();
controller()->SetImageDecodeCache(nullptr);
ResetController();
RunOrTimeout(&run_loop);
EXPECT_TRUE(task_one->state().IsCanceled() || task_one->HasCompleted());
EXPECT_TRUE(task_two->state().IsCanceled() || task_two->HasCompleted());
EXPECT_EQ(expected_id1, decode_client1.id());
EXPECT_EQ(expected_id2, decode_client2.id());
}
TEST_F(ImageControllerTest, QueueImageDecodeImageAlreadyLocked) {
scoped_refptr<SimpleTask> task(new SimpleTask);
cache()->SetTaskToUse(task);
base::RunLoop run_loop1;
DecodeClient decode_client1;
ImageController::ImageDecodeRequestId expected_id1 =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client1),
run_loop1.QuitClosure()));
RunOrTimeout(&run_loop1);
EXPECT_EQ(expected_id1, decode_client1.id());
EXPECT_TRUE(task->has_run());
cache()->SetTaskToUse(nullptr);
base::RunLoop run_loop2;
DecodeClient decode_client2;
ImageController::ImageDecodeRequestId expected_id2 =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client2),
run_loop2.QuitClosure()));
RunOrTimeout(&run_loop2);
EXPECT_EQ(expected_id2, decode_client2.id());
EXPECT_EQ(ImageController::ImageDecodeResult::SUCCESS,
decode_client2.result());
}
TEST_F(ImageControllerTest, QueueImageDecodeLockedImageControllerChange) {
scoped_refptr<SimpleTask> task(new SimpleTask);
cache()->SetTaskToUse(task);
base::RunLoop run_loop1;
DecodeClient decode_client1;
ImageController::ImageDecodeRequestId expected_id1 =
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client1),
run_loop1.QuitClosure()));
RunOrTimeout(&run_loop1);
EXPECT_EQ(expected_id1, decode_client1.id());
EXPECT_TRUE(task->has_run());
EXPECT_EQ(1, cache()->number_of_refs());
controller()->SetImageDecodeCache(nullptr);
EXPECT_EQ(0, cache()->number_of_refs());
}
TEST_F(ImageControllerTest, DispatchesDecodeCallbacksAfterCacheReset) {
scoped_refptr<SimpleTask> task(new SimpleTask);
cache()->SetTaskToUse(task);
base::RunLoop run_loop1;
DecodeClient decode_client1;
base::RunLoop run_loop2;
DecodeClient decode_client2;
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client1),
run_loop1.QuitClosure()));
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client2),
run_loop2.QuitClosure()));
// Now reset the image cache before decode completed callbacks are posted to
// the compositor thread. Ensure that the completion callbacks for the decode
// is still run.
controller()->SetImageDecodeCache(nullptr);
ResetController();
RunOrTimeout(&run_loop1);
RunOrTimeout(&run_loop2);
EXPECT_EQ(ImageController::ImageDecodeResult::FAILURE,
decode_client1.result());
EXPECT_EQ(ImageController::ImageDecodeResult::FAILURE,
decode_client2.result());
}
TEST_F(ImageControllerTest, DispatchesDecodeCallbacksAfterCacheChanged) {
scoped_refptr<SimpleTask> task(new SimpleTask);
cache()->SetTaskToUse(task);
base::RunLoop run_loop1;
DecodeClient decode_client1;
base::RunLoop run_loop2;
DecodeClient decode_client2;
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client1),
run_loop1.QuitClosure()));
controller()->QueueImageDecode(
image(),
base::Bind(&DecodeClient::Callback, base::Unretained(&decode_client2),
run_loop2.QuitClosure()));
// Now reset the image cache before decode completed callbacks are posted to
// the compositor thread. This should orphan the requests.
controller()->SetImageDecodeCache(nullptr);
EXPECT_EQ(0, cache()->number_of_refs());
TestableCache other_cache;
other_cache.SetTaskToUse(task);
controller()->SetImageDecodeCache(&other_cache);
RunOrTimeout(&run_loop1);
RunOrTimeout(&run_loop2);
EXPECT_EQ(2, other_cache.number_of_refs());
EXPECT_EQ(ImageController::ImageDecodeResult::SUCCESS,
decode_client1.result());
EXPECT_EQ(ImageController::ImageDecodeResult::SUCCESS,
decode_client2.result());
// Reset the controller since the order of destruction is wrong in this test
// (|other_cache| should outlive the controller. This is normally done via
// SetImageDecodeCache(nullptr) or it can be done in the dtor of the cache.)
ResetController();
}
} // namespace
} // namespace cc