1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/platform/graphics/paint_worklet_paint_dispatcher.h"
6
7 #include "base/bind.h"
8 #include "base/run_loop.h"
9 #include "cc/paint/paint_worklet_job.h"
10 #include "testing/gmock/include/gmock/gmock.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12 #include "third_party/blink/public/platform/platform.h"
13 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
14 #include "third_party/blink/renderer/platform/scheduler/public/thread_type.h"
15
16 using ::testing::_;
17 using ::testing::NiceMock;
18 using ::testing::Return;
19
20 namespace blink {
21 namespace {
22 // We need a thread (or multiple threads) for the (mock) worklets to run on.
CreateTestThread(const char * name)23 std::unique_ptr<Thread> CreateTestThread(const char* name) {
24 return Platform::Current()->CreateThread(
25 ThreadCreationParams(ThreadType::kTestThread).SetThreadNameForTest(name));
26 }
27
28 class PaintWorkletPaintDispatcherAsyncTest : public ::testing::Test {
29 public:
CreateTestCompleteCallback()30 PlatformPaintWorkletLayerPainter::DoneCallback CreateTestCompleteCallback() {
31 return base::BindOnce(
32 &PaintWorkletPaintDispatcherAsyncTest::VerifyResultAndFinish,
33 base::Unretained(this));
34 }
35
36 // Allows a test to block on |VerifyResultAndFinish| being called. If a
37 // PaintWorkletPaintDispatcherAsyncTest test times out, it likely means the
38 // callback created by |CreateTestCompleteCallback| was never posted by the
39 // worklet thread.
WaitForTestCompletion()40 void WaitForTestCompletion() { run_loop_.Run(); }
41
42 private:
VerifyResultAndFinish(cc::PaintWorkletJobMap results)43 void VerifyResultAndFinish(cc::PaintWorkletJobMap results) {
44 run_loop_.Quit();
45 }
46
47 base::RunLoop run_loop_;
48 };
49
50 class MockPaintWorkletPainter
51 : public GarbageCollected<MockPaintWorkletPainter>,
52 public PaintWorkletPainter {
53 USING_GARBAGE_COLLECTED_MIXIN(MockPaintWorkletPainter);
54
55 public:
MockPaintWorkletPainter(int worklet_id)56 MockPaintWorkletPainter(int worklet_id) {
57 ON_CALL(*this, GetWorkletId).WillByDefault(Return(worklet_id));
58 }
59 ~MockPaintWorkletPainter() = default;
60
61 MOCK_CONST_METHOD0(GetWorkletId, int());
62 MOCK_METHOD2(
63 Paint,
64 sk_sp<PaintRecord>(const cc::PaintWorkletInput*,
65 const cc::PaintWorkletJob::AnimatedPropertyValues&));
66 };
67
68 class MockPaintWorkletInput : public cc::PaintWorkletInput {
69 public:
MockPaintWorkletInput(int worklet_id)70 explicit MockPaintWorkletInput(int worklet_id) {
71 ON_CALL(*this, WorkletId).WillByDefault(Return(worklet_id));
72 }
73 ~MockPaintWorkletInput() = default;
74
75 MOCK_CONST_METHOD0(GetSize, gfx::SizeF());
76 MOCK_CONST_METHOD0(WorkletId, int());
77 MOCK_CONST_METHOD0(GetPropertyKeys,
78 const std::vector<PaintWorkletInput::PropertyKey>&());
79 };
80
AddPaintWorkletInputToMap(cc::PaintWorkletJobMap & map,int worklet_id)81 cc::PaintWorkletInput* AddPaintWorkletInputToMap(cc::PaintWorkletJobMap& map,
82 int worklet_id) {
83 if (!map.contains(worklet_id))
84 map[worklet_id] = base::MakeRefCounted<cc::PaintWorkletJobVector>();
85 auto input = base::MakeRefCounted<MockPaintWorkletInput>(worklet_id);
86 MockPaintWorkletInput* input_ptr = input.get();
87 cc::PaintWorkletJob::AnimatedPropertyValues animated_property_values;
88 map[worklet_id]->data.emplace_back(/*layer_id=*/1, std::move(input),
89 animated_property_values);
90 return input_ptr;
91 }
92 } // namespace
93
TEST_F(PaintWorkletPaintDispatcherAsyncTest,DispatchedWorkletIsPainted)94 TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchedWorkletIsPainted) {
95 auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
96
97 const int worklet_id = 4;
98 MockPaintWorkletPainter* mock_painter =
99 MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(worklet_id);
100 std::unique_ptr<Thread> worklet_thread = CreateTestThread("WorkletThread");
101 dispatcher->RegisterPaintWorkletPainter(mock_painter,
102 worklet_thread->GetTaskRunner());
103
104 cc::PaintWorkletJobMap job_map;
105 Vector<cc::PaintWorkletInput*> inputs = {
106 AddPaintWorkletInputToMap(job_map, worklet_id),
107 AddPaintWorkletInputToMap(job_map, worklet_id),
108 AddPaintWorkletInputToMap(job_map, worklet_id),
109 };
110
111 // The input jobs match the registered painter, so we should see a series of
112 // calls to Paint() with the appropriate PaintWorkletInputs.
113 for (cc::PaintWorkletInput* input : inputs)
114 EXPECT_CALL(*mock_painter, Paint(input, _)).Times(1);
115 dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
116
117 WaitForTestCompletion();
118 }
119
TEST_F(PaintWorkletPaintDispatcherAsyncTest,DispatchCompletesWithNoPainters)120 TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchCompletesWithNoPainters) {
121 auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
122
123 cc::PaintWorkletJobMap job_map;
124 AddPaintWorkletInputToMap(job_map, /*worklet_id=*/2);
125 AddPaintWorkletInputToMap(job_map, /*worklet_id=*/2);
126 AddPaintWorkletInputToMap(job_map, /*worklet_id=*/5);
127
128 // There are no painters to dispatch to, matching or otherwise, but the
129 // callback should still be called so this test passes if it doesn't hang on
130 // WaitForTestCompletion.
131 dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
132
133 WaitForTestCompletion();
134 }
135
TEST_F(PaintWorkletPaintDispatcherAsyncTest,DispatchHandlesEmptyInput)136 TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchHandlesEmptyInput) {
137 auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
138
139 const int worklet_id = 4;
140 auto* mock_painter =
141 MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(worklet_id);
142 std::unique_ptr<Thread> worklet_thread = CreateTestThread("WorkletThread");
143 dispatcher->RegisterPaintWorkletPainter(mock_painter,
144 worklet_thread->GetTaskRunner());
145
146 cc::PaintWorkletJobMap job_map;
147
148 // The input job map is empty, so we should see no calls to Paint but the
149 // callback should still be called.
150 EXPECT_CALL(*mock_painter, Paint(_, _)).Times(0);
151 dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
152
153 WaitForTestCompletion();
154 }
155
TEST_F(PaintWorkletPaintDispatcherAsyncTest,DispatchSelectsCorrectPainter)156 TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchSelectsCorrectPainter) {
157 auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
158
159 const int first_worklet_id = 2;
160 auto* first_mock_painter =
161 MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(first_worklet_id);
162 std::unique_ptr<Thread> first_thread = CreateTestThread("WorkletThread1");
163 dispatcher->RegisterPaintWorkletPainter(first_mock_painter,
164 first_thread->GetTaskRunner());
165
166 const int second_worklet_id = 3;
167 auto* second_mock_painter =
168 MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(
169 second_worklet_id);
170 std::unique_ptr<Thread> second_thread = CreateTestThread("WorkletThread2");
171 dispatcher->RegisterPaintWorkletPainter(second_mock_painter,
172 second_thread->GetTaskRunner());
173
174 cc::PaintWorkletJobMap job_map;
175 Vector<cc::PaintWorkletInput*> inputs{
176 AddPaintWorkletInputToMap(job_map, second_worklet_id),
177 AddPaintWorkletInputToMap(job_map, second_worklet_id),
178 };
179
180 // Paint should only be called on the correct painter, with our input.
181 EXPECT_CALL(*first_mock_painter, Paint(_, _)).Times(0);
182 for (cc::PaintWorkletInput* input : inputs) {
183 EXPECT_CALL(*second_mock_painter, Paint(input, _)).Times(1);
184 }
185 dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
186
187 WaitForTestCompletion();
188 }
189
TEST_F(PaintWorkletPaintDispatcherAsyncTest,DispatchIgnoresNonMatchingInput)190 TEST_F(PaintWorkletPaintDispatcherAsyncTest, DispatchIgnoresNonMatchingInput) {
191 auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
192
193 const int worklet_id = 2;
194 auto* mock_painter =
195 MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(worklet_id);
196 std::unique_ptr<Thread> worklet_thread = CreateTestThread("WorkletThread");
197 dispatcher->RegisterPaintWorkletPainter(mock_painter,
198 worklet_thread->GetTaskRunner());
199
200 cc::PaintWorkletJobMap job_map;
201 const int non_registered_worklet_id = 3;
202 cc::PaintWorkletInput* matching_input =
203 AddPaintWorkletInputToMap(job_map, worklet_id);
204 AddPaintWorkletInputToMap(job_map, non_registered_worklet_id);
205
206 // Only one job matches, so our painter should only be called once, and the
207 // callback should still be called.
208 EXPECT_CALL(*mock_painter, Paint(matching_input, _)).Times(1);
209 dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
210
211 WaitForTestCompletion();
212 }
213
TEST_F(PaintWorkletPaintDispatcherAsyncTest,DispatchCorrectlyAssignsInputsToMultiplePainters)214 TEST_F(PaintWorkletPaintDispatcherAsyncTest,
215 DispatchCorrectlyAssignsInputsToMultiplePainters) {
216 auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
217
218 const int first_worklet_id = 5;
219 auto* first_mock_painter =
220 MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(first_worklet_id);
221 std::unique_ptr<Thread> first_thread = CreateTestThread("WorkletThread1");
222 dispatcher->RegisterPaintWorkletPainter(first_mock_painter,
223 first_thread->GetTaskRunner());
224
225 const int second_worklet_id = 1;
226 auto* second_mock_painter =
227 MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(
228 second_worklet_id);
229 std::unique_ptr<Thread> second_thread = CreateTestThread("WorkletThread2");
230 dispatcher->RegisterPaintWorkletPainter(second_mock_painter,
231 second_thread->GetTaskRunner());
232
233 cc::PaintWorkletJobMap job_map;
234 cc::PaintWorkletInput* first_input =
235 AddPaintWorkletInputToMap(job_map, first_worklet_id);
236 cc::PaintWorkletInput* second_input =
237 AddPaintWorkletInputToMap(job_map, second_worklet_id);
238
239 // Both painters should be called with the correct inputs.
240 EXPECT_CALL(*first_mock_painter, Paint(first_input, _)).Times(1);
241 EXPECT_CALL(*second_mock_painter, Paint(second_input, _)).Times(1);
242 dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
243
244 WaitForTestCompletion();
245 }
246
TEST_F(PaintWorkletPaintDispatcherAsyncTest,HasOngoingDispatchIsTrackedCorrectly)247 TEST_F(PaintWorkletPaintDispatcherAsyncTest,
248 HasOngoingDispatchIsTrackedCorrectly) {
249 auto dispatcher = std::make_unique<PaintWorkletPaintDispatcher>();
250
251 const int first_worklet_id = 2;
252 auto* first_mock_painter =
253 MakeGarbageCollected<NiceMock<MockPaintWorkletPainter>>(first_worklet_id);
254 std::unique_ptr<Thread> first_thread = CreateTestThread("WorkletThread1");
255 dispatcher->RegisterPaintWorkletPainter(first_mock_painter,
256 first_thread->GetTaskRunner());
257
258 // Nothing going on; no dispatch.
259 EXPECT_FALSE(dispatcher->HasOngoingDispatch());
260
261 cc::PaintWorkletJobMap job_map;
262 AddPaintWorkletInputToMap(job_map, first_worklet_id);
263
264 dispatcher->DispatchWorklets(job_map, CreateTestCompleteCallback());
265 EXPECT_TRUE(dispatcher->HasOngoingDispatch());
266
267 WaitForTestCompletion();
268 EXPECT_FALSE(dispatcher->HasOngoingDispatch());
269 }
270
271 } // namespace blink
272