blob: c2d2b8a1f0900a36ae1cbce2c4cefc80f9230056 [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/renderer/platform/graphics/paint_worklet_paint_dispatcher.h"
#include "base/bind.h"
#include "base/run_loop.h"
#include "cc/paint/paint_worklet_job.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_type.h"
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
namespace blink {
namespace {
// We need a thread (or multiple threads) for the (mock) worklets to run on.
std::unique_ptr<Thread> CreateTestThread(const char* name) {
return Platform::Current()->CreateThread(
ThreadCreationParams(ThreadType::kTestThread).SetThreadNameForTest(name));
}
class PaintWorkletPaintDispatcherAsyncTest : public ::testing::Test {
public:
PlatformPaintWorkletLayerPainter::DoneCallback CreateTestCompleteCallback() {
return base::BindOnce(
&PaintWorkletPaintDispatcherAsyncTest::VerifyResultAndFinish,
base::Unretained(this));
}
// Allows a test to block on |VerifyResultAndFinish| being called. If a
// PaintWorkletPaintDispatcherAsyncTest test times out, it likely means the
// callback created by |CreateTestCompleteCallback| was never posted by the
// worklet thread.
void WaitForTestCompletion() { run_loop_.Run(); }
private:
void VerifyResultAndFinish(cc::PaintWorkletJobMap results) {
run_loop_.Quit();
}
base::RunLoop run_loop_;
};
class MockPaintWorkletPainter
: public GarbageCollected<MockPaintWorkletPainter>,
public PaintWorkletPainter {
public:
MockPaintWorkletPainter(int worklet_id) {
ON_CALL(*this, GetWorkletId).WillByDefault(Return(worklet_id));
}
~MockPaintWorkletPainter() = default;
MOCK_CONST_METHOD0(GetWorkletId, int());
MOCK_METHOD2(
Paint,
sk_sp<PaintRecord>(const cc::PaintWorkletInput*,
const cc::PaintWorkletJob::AnimatedPropertyValues&));
};
class MockPaintWorkletInput : public cc::PaintWorkletInput {
public:
explicit MockPaintWorkletInput(int worklet_id) {
ON_CALL(*this, WorkletId).WillByDefault(Return(worklet_id));
}
~MockPaintWorkletInput() = default;
MOCK_CONST_METHOD0(GetSize, gfx::SizeF());
MOCK_CONST_METHOD0(WorkletId, int());
MOCK_CONST_METHOD0(GetPropertyKeys,
const std::vector<PaintWorkletInput::PropertyKey>&());
};
cc::PaintWorkletInput* AddPaintWorkletInputToMap(cc::PaintWorkletJobMap& map,
int worklet_id) {
if (!map.contains(worklet_id))
map[worklet_id] = base::MakeRefCounted<cc::PaintWorkletJobVector>();
auto input = base::MakeRefCounted<MockPaintWorkletInput>(worklet_id);
MockPaintWorkletInput* input_ptr = input.get();
cc::PaintWorkletJob::AnimatedPropertyValues animated_property_values;
map[worklet_id]->data.emplace_back(/*layer_id=*/1, std::move(input),
animated_property_values);
return input_ptr;
}
} // namespace
TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchedWorkletIsPainted) {
auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
const int worklet_id = 4;
MockPaintWorkletPainter* mock_painter =
MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(worklet_id);
std::unique_ptr<Thread> worklet_thread = CreateTestThread("WorkletThread");
dispatcher->RegisterPaintWorkletPainter(mock_painter,
worklet_thread->GetTaskRunner());
cc::PaintWorkletJobMap job_map;
Vector<cc::PaintWorkletInput*> inputs = {
AddPaintWorkletInputToMap(job_map, worklet_id),
AddPaintWorkletInputToMap(job_map, worklet_id),
AddPaintWorkletInputToMap(job_map, worklet_id),
};
// The input jobs match the registered painter, so we should see a series of
// calls to Paint() with the appropriate PaintWorkletInputs.
for (cc::PaintWorkletInput* input : inputs)
EXPECT_CALL(*mock_painter, Paint(input, _)).Times(1);
dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
WaitForTestCompletion();
}
TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchCompletesWithNoPainters) {
auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
cc::PaintWorkletJobMap job_map;
AddPaintWorkletInputToMap(job_map, /*worklet_id=*/2);
AddPaintWorkletInputToMap(job_map, /*worklet_id=*/2);
AddPaintWorkletInputToMap(job_map, /*worklet_id=*/5);
// There are no painters to dispatch to, matching or otherwise, but the
// callback should still be called so this test passes if it doesn't hang on
// WaitForTestCompletion.
dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
WaitForTestCompletion();
}
TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchHandlesEmptyInput) {
auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
const int worklet_id = 4;
auto* mock_painter =
MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(worklet_id);
std::unique_ptr<Thread> worklet_thread = CreateTestThread("WorkletThread");
dispatcher->RegisterPaintWorkletPainter(mock_painter,
worklet_thread->GetTaskRunner());
cc::PaintWorkletJobMap job_map;
// The input job map is empty, so we should see no calls to Paint but the
// callback should still be called.
EXPECT_CALL(*mock_painter, Paint(_, _)).Times(0);
dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
WaitForTestCompletion();
}
TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchSelectsCorrectPainter) {
auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
const int first_worklet_id = 2;
auto* first_mock_painter =
MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(first_worklet_id);
std::unique_ptr<Thread> first_thread = CreateTestThread("WorkletThread1");
dispatcher->RegisterPaintWorkletPainter(first_mock_painter,
first_thread->GetTaskRunner());
const int second_worklet_id = 3;
auto* second_mock_painter =
MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(
second_worklet_id);
std::unique_ptr<Thread> second_thread = CreateTestThread("WorkletThread2");
dispatcher->RegisterPaintWorkletPainter(second_mock_painter,
second_thread->GetTaskRunner());
cc::PaintWorkletJobMap job_map;
Vector<cc::PaintWorkletInput*> inputs{
AddPaintWorkletInputToMap(job_map, second_worklet_id),
AddPaintWorkletInputToMap(job_map, second_worklet_id),
};
// Paint should only be called on the correct painter, with our input.
EXPECT_CALL(*first_mock_painter, Paint(_, _)).Times(0);
for (cc::PaintWorkletInput* input : inputs) {
EXPECT_CALL(*second_mock_painter, Paint(input, _)).Times(1);
}
dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
WaitForTestCompletion();
}
TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchIgnoresNonMatchingInput) {
auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
const int worklet_id = 2;
auto* mock_painter =
MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(worklet_id);
std::unique_ptr<Thread> worklet_thread = CreateTestThread("WorkletThread");
dispatcher->RegisterPaintWorkletPainter(mock_painter,
worklet_thread->GetTaskRunner());
cc::PaintWorkletJobMap job_map;
const int non_registered_worklet_id = 3;
cc::PaintWorkletInput* matching_input =
AddPaintWorkletInputToMap(job_map, worklet_id);
AddPaintWorkletInputToMap(job_map, non_registered_worklet_id);
// Only one job matches, so our painter should only be called once, and the
// callback should still be called.
EXPECT_CALL(*mock_painter, Paint(matching_input, _)).Times(1);
dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
WaitForTestCompletion();
}
TEST_F(PaintWorkletPaintDispatcherAsyncTest,
DispatchCorrectlyAssignsInputsToMultiplePainters) {
auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
const int first_worklet_id = 5;
auto* first_mock_painter =
MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(first_worklet_id);
std::unique_ptr<Thread> first_thread = CreateTestThread("WorkletThread1");
dispatcher->RegisterPaintWorkletPainter(first_mock_painter,
first_thread->GetTaskRunner());
const int second_worklet_id = 1;
auto* second_mock_painter =
MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(
second_worklet_id);
std::unique_ptr<Thread> second_thread = CreateTestThread("WorkletThread2");
dispatcher->RegisterPaintWorkletPainter(second_mock_painter,
second_thread->GetTaskRunner());
cc::PaintWorkletJobMap job_map;
cc::PaintWorkletInput* first_input =
AddPaintWorkletInputToMap(job_map, first_worklet_id);
cc::PaintWorkletInput* second_input =
AddPaintWorkletInputToMap(job_map, second_worklet_id);
// Both painters should be called with the correct inputs.
EXPECT_CALL(*first_mock_painter, Paint(first_input, _)).Times(1);
EXPECT_CALL(*second_mock_painter, Paint(second_input, _)).Times(1);
dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
WaitForTestCompletion();
}
TEST_F(PaintWorkletPaintDispatcherAsyncTest,
HasOngoingDispatchIsTrackedCorrectly) {
auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
const int first_worklet_id = 2;
auto* first_mock_painter =
MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(first_worklet_id);
std::unique_ptr<Thread> first_thread = CreateTestThread("WorkletThread1");
dispatcher->RegisterPaintWorkletPainter(first_mock_painter,
first_thread->GetTaskRunner());
// Nothing going on; no dispatch.
EXPECT_FALSE(dispatcher->HasOngoingDispatch());
cc::PaintWorkletJobMap job_map;
AddPaintWorkletInputToMap(job_map, first_worklet_id);
dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
EXPECT_TRUE(dispatcher->HasOngoingDispatch());
WaitForTestCompletion();
EXPECT_FALSE(dispatcher->HasOngoingDispatch());
}
} // namespace blink