1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <VectorDrawable.h>
18 #include <gtest/gtest.h>
19
20 #include "AnimationContext.h"
21 #include "DamageAccumulator.h"
22 #include "IContextFactory.h"
23 #include "pipeline/skia/GLFunctorDrawable.h"
24 #include "pipeline/skia/SkiaDisplayList.h"
25 #include "renderthread/CanvasContext.h"
26 #include "tests/common/TestContext.h"
27 #include "tests/common/TestUtils.h"
28
29 using namespace android;
30 using namespace android::uirenderer;
31 using namespace android::uirenderer::renderthread;
32 using namespace android::uirenderer::skiapipeline;
33
TEST(SkiaDisplayList,create)34 TEST(SkiaDisplayList, create) {
35 SkiaDisplayList skiaDL;
36 ASSERT_TRUE(skiaDL.isEmpty());
37 ASSERT_FALSE(skiaDL.mProjectionReceiver);
38 }
39
TEST(SkiaDisplayList,reset)40 TEST(SkiaDisplayList, reset) {
41 std::unique_ptr<SkiaDisplayList> skiaDL;
42 {
43 SkiaRecordingCanvas canvas{nullptr, 1, 1};
44 canvas.drawColor(0, SkBlendMode::kSrc);
45 skiaDL.reset(canvas.finishRecording());
46 }
47
48 SkCanvas dummyCanvas;
49 RenderNodeDrawable drawable(nullptr, &dummyCanvas);
50 skiaDL->mChildNodes.emplace_back(nullptr, &dummyCanvas);
51 GLFunctorDrawable functorDrawable(nullptr, nullptr, &dummyCanvas);
52 skiaDL->mChildFunctors.push_back(&functorDrawable);
53 skiaDL->mMutableImages.push_back(nullptr);
54 skiaDL->appendVD(nullptr);
55 skiaDL->mProjectionReceiver = &drawable;
56
57 ASSERT_FALSE(skiaDL->mChildNodes.empty());
58 ASSERT_FALSE(skiaDL->mChildFunctors.empty());
59 ASSERT_FALSE(skiaDL->mMutableImages.empty());
60 ASSERT_TRUE(skiaDL->hasVectorDrawables());
61 ASSERT_FALSE(skiaDL->isEmpty());
62 ASSERT_TRUE(skiaDL->mProjectionReceiver);
63
64 skiaDL->reset();
65
66 ASSERT_TRUE(skiaDL->mChildNodes.empty());
67 ASSERT_TRUE(skiaDL->mChildFunctors.empty());
68 ASSERT_TRUE(skiaDL->mMutableImages.empty());
69 ASSERT_FALSE(skiaDL->hasVectorDrawables());
70 ASSERT_TRUE(skiaDL->isEmpty());
71 ASSERT_FALSE(skiaDL->mProjectionReceiver);
72 }
73
TEST(SkiaDisplayList,reuseDisplayList)74 TEST(SkiaDisplayList, reuseDisplayList) {
75 sp<RenderNode> renderNode = new RenderNode();
76 std::unique_ptr<SkiaDisplayList> availableList;
77
78 // no list has been attached so it should return a nullptr
79 availableList = renderNode->detachAvailableList();
80 ASSERT_EQ(availableList.get(), nullptr);
81
82 // attach a displayList for reuse
83 SkiaDisplayList skiaDL;
84 ASSERT_TRUE(skiaDL.reuseDisplayList(renderNode.get(), nullptr));
85
86 // detach the list that you just attempted to reuse
87 availableList = renderNode->detachAvailableList();
88 ASSERT_EQ(availableList.get(), &skiaDL);
89 availableList.release(); // prevents an invalid free since our DL is stack allocated
90
91 // after detaching there should return no available list
92 availableList = renderNode->detachAvailableList();
93 ASSERT_EQ(availableList.get(), nullptr);
94 }
95
TEST(SkiaDisplayList,syncContexts)96 TEST(SkiaDisplayList, syncContexts) {
97 SkiaDisplayList skiaDL;
98
99 SkCanvas dummyCanvas;
100 TestUtils::MockFunctor functor;
101 GLFunctorDrawable functorDrawable(&functor, nullptr, &dummyCanvas);
102 skiaDL.mChildFunctors.push_back(&functorDrawable);
103
104 int functor2 = WebViewFunctor_create(
105 nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
106 auto& counts = TestUtils::countsForFunctor(functor2);
107 skiaDL.mChildFunctors.push_back(
108 skiaDL.allocateDrawable<GLFunctorDrawable>(functor2, &dummyCanvas));
109 WebViewFunctor_release(functor2);
110
111 SkRect bounds = SkRect::MakeWH(200, 200);
112 VectorDrawableRoot vectorDrawable(new VectorDrawable::Group());
113 vectorDrawable.mutateStagingProperties()->setBounds(bounds);
114 skiaDL.appendVD(&vectorDrawable);
115
116 // ensure that the functor and vectorDrawable are properly synced
117 TestUtils::runOnRenderThread([&](auto&) {
118 skiaDL.syncContents(WebViewSyncData{
119 .applyForceDark = false,
120 });
121 });
122
123 EXPECT_EQ(functor.getLastMode(), DrawGlInfo::kModeSync);
124 EXPECT_EQ(counts.sync, 1);
125 EXPECT_EQ(counts.destroyed, 0);
126 EXPECT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds);
127
128 skiaDL.reset();
129 TestUtils::runOnRenderThread([](auto&) {
130 // Fence
131 });
132 EXPECT_EQ(counts.destroyed, 1);
133 }
134
135 class ContextFactory : public IContextFactory {
136 public:
createAnimationContext(renderthread::TimeLord & clock)137 virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
138 return new AnimationContext(clock);
139 }
140 };
141
RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList,prepareListAndChildren)142 RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
143 auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
144 ContextFactory contextFactory;
145 std::unique_ptr<CanvasContext> canvasContext(
146 CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
147 TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
148 DamageAccumulator damageAccumulator;
149 info.damageAccumulator = &damageAccumulator;
150
151 SkiaDisplayList skiaDL;
152
153 // The VectorDrawableRoot needs to have bounds on screen (and therefore not
154 // empty) in order to have PropertyChangeWillBeConsumed set.
155 const auto bounds = SkRect::MakeIWH(100, 100);
156
157 // prepare with a clean VD
158 VectorDrawableRoot cleanVD(new VectorDrawable::Group());
159 cleanVD.mutateProperties()->setBounds(bounds);
160 skiaDL.appendVD(&cleanVD);
161 cleanVD.getBitmapUpdateIfDirty(); // this clears the dirty bit
162
163 ASSERT_FALSE(cleanVD.isDirty());
164 ASSERT_FALSE(cleanVD.getPropertyChangeWillBeConsumed());
165 TestUtils::MockTreeObserver observer;
166 ASSERT_FALSE(skiaDL.prepareListAndChildren(observer, info, false,
167 [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
168 ASSERT_FALSE(cleanVD.getPropertyChangeWillBeConsumed());
169
170 // prepare again this time adding a dirty VD
171 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
172 dirtyVD.mutateProperties()->setBounds(bounds);
173 skiaDL.appendVD(&dirtyVD);
174
175 ASSERT_TRUE(dirtyVD.isDirty());
176 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
177 ASSERT_TRUE(skiaDL.prepareListAndChildren(observer, info, false,
178 [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
179 ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
180
181 // prepare again this time adding a RenderNode and a callback
182 sp<RenderNode> renderNode = new RenderNode();
183 TreeInfo* infoPtr = &info;
184 SkCanvas dummyCanvas;
185 skiaDL.mChildNodes.emplace_back(renderNode.get(), &dummyCanvas);
186 bool hasRun = false;
187 ASSERT_TRUE(skiaDL.prepareListAndChildren(
188 observer, info, false,
189 [&hasRun, renderNode, infoPtr](RenderNode* n, TreeObserver& observer, TreeInfo& i,
190 bool r) {
191 hasRun = true;
192 ASSERT_EQ(renderNode.get(), n);
193 ASSERT_EQ(infoPtr, &i);
194 ASSERT_FALSE(r);
195 }));
196 ASSERT_TRUE(hasRun);
197
198 canvasContext->destroy();
199 }
200
RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList,prepareListAndChildren_vdOffscreen)201 RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
202 auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
203 ContextFactory contextFactory;
204 std::unique_ptr<CanvasContext> canvasContext(
205 CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
206
207 // Set up a Surface so that we can position the VectorDrawable offscreen.
208 test::TestContext testContext;
209 testContext.setRenderOffscreen(true);
210 auto surface = testContext.surface();
211 int width, height;
212 surface->query(NATIVE_WINDOW_WIDTH, &width);
213 surface->query(NATIVE_WINDOW_HEIGHT, &height);
214 canvasContext->setSurface(std::move(surface));
215
216 TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
217 DamageAccumulator damageAccumulator;
218 info.damageAccumulator = &damageAccumulator;
219
220 // The VectorDrawableRoot needs to have bounds on screen (and therefore not
221 // empty) in order to have PropertyChangeWillBeConsumed set.
222 const auto bounds = SkRect::MakeIWH(100, 100);
223
224 for (const SkRect b : {bounds.makeOffset(width, 0),
225 bounds.makeOffset(0, height),
226 bounds.makeOffset(-bounds.width(), 0),
227 bounds.makeOffset(0, -bounds.height())}) {
228 SkiaDisplayList skiaDL;
229 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
230 dirtyVD.mutateProperties()->setBounds(b);
231 skiaDL.appendVD(&dirtyVD);
232
233 ASSERT_TRUE(dirtyVD.isDirty());
234 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
235
236 TestUtils::MockTreeObserver observer;
237 ASSERT_FALSE(skiaDL.prepareListAndChildren(
238 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
239 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
240 }
241
242 // The DamageAccumulator's transform can also result in the
243 // VectorDrawableRoot being offscreen.
244 for (const SkISize translate : { SkISize{width, 0},
245 SkISize{0, height},
246 SkISize{-width, 0},
247 SkISize{0, -height}}) {
248 Matrix4 mat4;
249 mat4.translate(translate.fWidth, translate.fHeight);
250 damageAccumulator.pushTransform(&mat4);
251
252 SkiaDisplayList skiaDL;
253 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
254 dirtyVD.mutateProperties()->setBounds(bounds);
255 skiaDL.appendVD(&dirtyVD);
256
257 ASSERT_TRUE(dirtyVD.isDirty());
258 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
259
260 TestUtils::MockTreeObserver observer;
261 ASSERT_FALSE(skiaDL.prepareListAndChildren(
262 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
263 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
264 damageAccumulator.popTransform();
265 }
266
267 // Another way to be offscreen: a matrix from the draw call.
268 for (const SkMatrix translate : { SkMatrix::MakeTrans(width, 0),
269 SkMatrix::MakeTrans(0, height),
270 SkMatrix::MakeTrans(-width, 0),
271 SkMatrix::MakeTrans(0, -height)}) {
272 SkiaDisplayList skiaDL;
273 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
274 dirtyVD.mutateProperties()->setBounds(bounds);
275 skiaDL.appendVD(&dirtyVD, translate);
276
277 ASSERT_TRUE(dirtyVD.isDirty());
278 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
279
280 TestUtils::MockTreeObserver observer;
281 ASSERT_FALSE(skiaDL.prepareListAndChildren(
282 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
283 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
284 }
285
286 // Verify that the matrices are combined in the right order.
287 {
288 // Rotate and then translate, so the VD is offscreen.
289 Matrix4 mat4;
290 mat4.loadRotate(180);
291 damageAccumulator.pushTransform(&mat4);
292
293 SkiaDisplayList skiaDL;
294 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
295 dirtyVD.mutateProperties()->setBounds(bounds);
296 SkMatrix translate = SkMatrix::MakeTrans(50, 50);
297 skiaDL.appendVD(&dirtyVD, translate);
298
299 ASSERT_TRUE(dirtyVD.isDirty());
300 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
301
302 TestUtils::MockTreeObserver observer;
303 ASSERT_FALSE(skiaDL.prepareListAndChildren(
304 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
305 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
306 damageAccumulator.popTransform();
307 }
308 {
309 // Switch the order of rotate and translate, so it is on screen.
310 Matrix4 mat4;
311 mat4.translate(50, 50);
312 damageAccumulator.pushTransform(&mat4);
313
314 SkiaDisplayList skiaDL;
315 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
316 dirtyVD.mutateProperties()->setBounds(bounds);
317 SkMatrix rotate;
318 rotate.setRotate(180);
319 skiaDL.appendVD(&dirtyVD, rotate);
320
321 ASSERT_TRUE(dirtyVD.isDirty());
322 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
323
324 TestUtils::MockTreeObserver observer;
325 ASSERT_TRUE(skiaDL.prepareListAndChildren(
326 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
327 ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
328 damageAccumulator.popTransform();
329 }
330 {
331 // An AVD that is larger than the screen.
332 SkiaDisplayList skiaDL;
333 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
334 dirtyVD.mutateProperties()->setBounds(SkRect::MakeLTRB(-1, -1, width + 1, height + 1));
335 skiaDL.appendVD(&dirtyVD);
336
337 ASSERT_TRUE(dirtyVD.isDirty());
338 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
339
340 TestUtils::MockTreeObserver observer;
341 ASSERT_TRUE(skiaDL.prepareListAndChildren(
342 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
343 ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
344 }
345 {
346 // An AVD whose bounds are not a rectangle after applying a matrix.
347 SkiaDisplayList skiaDL;
348 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
349 dirtyVD.mutateProperties()->setBounds(bounds);
350 SkMatrix mat;
351 mat.setRotate(45, 50, 50);
352 skiaDL.appendVD(&dirtyVD, mat);
353
354 ASSERT_TRUE(dirtyVD.isDirty());
355 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
356
357 TestUtils::MockTreeObserver observer;
358 ASSERT_TRUE(skiaDL.prepareListAndChildren(
359 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
360 ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
361 }
362 }
363
TEST(SkiaDisplayList,updateChildren)364 TEST(SkiaDisplayList, updateChildren) {
365 SkiaDisplayList skiaDL;
366
367 sp<RenderNode> renderNode = new RenderNode();
368 SkCanvas dummyCanvas;
369 skiaDL.mChildNodes.emplace_back(renderNode.get(), &dummyCanvas);
370 skiaDL.updateChildren([renderNode](RenderNode* n) { ASSERT_EQ(renderNode.get(), n); });
371 }
372