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 = canvas.finishRecording();
46 }
47
48 SkCanvas dummyCanvas;
49 RenderNodeDrawable drawable(nullptr, &dummyCanvas);
50 skiaDL->mChildNodes.emplace_back(nullptr, &dummyCanvas);
51 int functor1 = WebViewFunctor_create(
52 nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
53 GLFunctorDrawable functorDrawable{functor1, &dummyCanvas};
54 WebViewFunctor_release(functor1);
55 skiaDL->mChildFunctors.push_back(&functorDrawable);
56 skiaDL->mMutableImages.push_back(nullptr);
57 skiaDL->appendVD(nullptr);
58 skiaDL->mProjectionReceiver = &drawable;
59
60 ASSERT_FALSE(skiaDL->mChildNodes.empty());
61 ASSERT_FALSE(skiaDL->mChildFunctors.empty());
62 ASSERT_FALSE(skiaDL->mMutableImages.empty());
63 ASSERT_TRUE(skiaDL->hasVectorDrawables());
64 ASSERT_FALSE(skiaDL->isEmpty());
65 ASSERT_TRUE(skiaDL->mProjectionReceiver);
66
67 skiaDL->reset();
68
69 ASSERT_TRUE(skiaDL->mChildNodes.empty());
70 ASSERT_TRUE(skiaDL->mChildFunctors.empty());
71 ASSERT_TRUE(skiaDL->mMutableImages.empty());
72 ASSERT_FALSE(skiaDL->hasVectorDrawables());
73 ASSERT_TRUE(skiaDL->isEmpty());
74 ASSERT_FALSE(skiaDL->mProjectionReceiver);
75 }
76
TEST(SkiaDisplayList,reuseDisplayList)77 TEST(SkiaDisplayList, reuseDisplayList) {
78 sp<RenderNode> renderNode = new RenderNode();
79 std::unique_ptr<SkiaDisplayList> availableList;
80
81 // no list has been attached so it should return a nullptr
82 availableList = renderNode->detachAvailableList();
83 ASSERT_EQ(availableList.get(), nullptr);
84
85 // attach a displayList for reuse
86 SkiaDisplayList skiaDL;
87 ASSERT_TRUE(skiaDL.reuseDisplayList(renderNode.get()));
88
89 // detach the list that you just attempted to reuse
90 availableList = renderNode->detachAvailableList();
91 ASSERT_EQ(availableList.get(), &skiaDL);
92 availableList.release(); // prevents an invalid free since our DL is stack allocated
93
94 // after detaching there should return no available list
95 availableList = renderNode->detachAvailableList();
96 ASSERT_EQ(availableList.get(), nullptr);
97 }
98
TEST(SkiaDisplayList,syncContexts)99 TEST(SkiaDisplayList, syncContexts) {
100 SkiaDisplayList skiaDL;
101
102 SkCanvas dummyCanvas;
103
104 int functor1 = WebViewFunctor_create(
105 nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
106 auto& counts = TestUtils::countsForFunctor(functor1);
107 skiaDL.mChildFunctors.push_back(
108 skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas));
109 WebViewFunctor_release(functor1);
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(counts.sync, 1);
124 EXPECT_EQ(counts.destroyed, 0);
125 EXPECT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds);
126
127 skiaDL.reset();
128 TestUtils::runOnRenderThread([](auto&) {
129 // Fence
130 });
131 EXPECT_EQ(counts.destroyed, 1);
132 }
133
134 class ContextFactory : public IContextFactory {
135 public:
createAnimationContext(renderthread::TimeLord & clock)136 virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
137 return new AnimationContext(clock);
138 }
139 };
140
RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList,prepareListAndChildren)141 RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
142 auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
143 ContextFactory contextFactory;
144 std::unique_ptr<CanvasContext> canvasContext(
145 CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
146 TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
147 DamageAccumulator damageAccumulator;
148 info.damageAccumulator = &damageAccumulator;
149
150 SkiaDisplayList skiaDL;
151
152 // The VectorDrawableRoot needs to have bounds on screen (and therefore not
153 // empty) in order to have PropertyChangeWillBeConsumed set.
154 const auto bounds = SkRect::MakeIWH(100, 100);
155
156 // prepare with a clean VD
157 VectorDrawableRoot cleanVD(new VectorDrawable::Group());
158 cleanVD.mutateProperties()->setBounds(bounds);
159 skiaDL.appendVD(&cleanVD);
160 cleanVD.getBitmapUpdateIfDirty(); // this clears the dirty bit
161
162 ASSERT_FALSE(cleanVD.isDirty());
163 ASSERT_FALSE(cleanVD.getPropertyChangeWillBeConsumed());
164 TestUtils::MockTreeObserver observer;
165 ASSERT_FALSE(skiaDL.prepareListAndChildren(observer, info, false,
166 [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
167 ASSERT_FALSE(cleanVD.getPropertyChangeWillBeConsumed());
168
169 // prepare again this time adding a dirty VD
170 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
171 dirtyVD.mutateProperties()->setBounds(bounds);
172 skiaDL.appendVD(&dirtyVD);
173
174 ASSERT_TRUE(dirtyVD.isDirty());
175 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
176 ASSERT_TRUE(skiaDL.prepareListAndChildren(observer, info, false,
177 [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
178 ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
179
180 // prepare again this time adding a RenderNode and a callback
181 sp<RenderNode> renderNode = new RenderNode();
182 TreeInfo* infoPtr = &info;
183 SkCanvas dummyCanvas;
184 skiaDL.mChildNodes.emplace_back(renderNode.get(), &dummyCanvas);
185 bool hasRun = false;
186 ASSERT_TRUE(skiaDL.prepareListAndChildren(
187 observer, info, false,
188 [&hasRun, renderNode, infoPtr](RenderNode* n, TreeObserver& observer, TreeInfo& i,
189 bool r) {
190 hasRun = true;
191 ASSERT_EQ(renderNode.get(), n);
192 ASSERT_EQ(infoPtr, &i);
193 ASSERT_FALSE(r);
194 }));
195 ASSERT_TRUE(hasRun);
196
197 canvasContext->destroy();
198 }
199
RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList,prepareListAndChildren_vdOffscreen)200 RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
201 auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
202 ContextFactory contextFactory;
203 std::unique_ptr<CanvasContext> canvasContext(
204 CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
205
206 // Set up a Surface so that we can position the VectorDrawable offscreen.
207 test::TestContext testContext;
208 testContext.setRenderOffscreen(true);
209 auto surface = testContext.surface();
210 int width = ANativeWindow_getWidth(surface.get());
211 int height = ANativeWindow_getHeight(surface.get());
212 canvasContext->setSurface(surface.get());
213
214 TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
215 DamageAccumulator damageAccumulator;
216 info.damageAccumulator = &damageAccumulator;
217
218 // The VectorDrawableRoot needs to have bounds on screen (and therefore not
219 // empty) in order to have PropertyChangeWillBeConsumed set.
220 const auto bounds = SkRect::MakeIWH(100, 100);
221
222 for (const SkRect b : {bounds.makeOffset(width, 0),
223 bounds.makeOffset(0, height),
224 bounds.makeOffset(-bounds.width(), 0),
225 bounds.makeOffset(0, -bounds.height())}) {
226 SkiaDisplayList skiaDL;
227 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
228 dirtyVD.mutateProperties()->setBounds(b);
229 skiaDL.appendVD(&dirtyVD);
230
231 ASSERT_TRUE(dirtyVD.isDirty());
232 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
233
234 TestUtils::MockTreeObserver observer;
235 ASSERT_FALSE(skiaDL.prepareListAndChildren(
236 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
237 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
238 }
239
240 // The DamageAccumulator's transform can also result in the
241 // VectorDrawableRoot being offscreen.
242 for (const SkISize translate : { SkISize{width, 0},
243 SkISize{0, height},
244 SkISize{-width, 0},
245 SkISize{0, -height}}) {
246 Matrix4 mat4;
247 mat4.translate(translate.fWidth, translate.fHeight);
248 damageAccumulator.pushTransform(&mat4);
249
250 SkiaDisplayList skiaDL;
251 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
252 dirtyVD.mutateProperties()->setBounds(bounds);
253 skiaDL.appendVD(&dirtyVD);
254
255 ASSERT_TRUE(dirtyVD.isDirty());
256 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
257
258 TestUtils::MockTreeObserver observer;
259 ASSERT_FALSE(skiaDL.prepareListAndChildren(
260 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
261 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
262 damageAccumulator.popTransform();
263 }
264
265 // Another way to be offscreen: a matrix from the draw call.
266 for (const SkMatrix translate : { SkMatrix::Translate(width, 0),
267 SkMatrix::Translate(0, height),
268 SkMatrix::Translate(-width, 0),
269 SkMatrix::Translate(0, -height)}) {
270 SkiaDisplayList skiaDL;
271 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
272 dirtyVD.mutateProperties()->setBounds(bounds);
273 skiaDL.appendVD(&dirtyVD, translate);
274
275 ASSERT_TRUE(dirtyVD.isDirty());
276 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
277
278 TestUtils::MockTreeObserver observer;
279 ASSERT_FALSE(skiaDL.prepareListAndChildren(
280 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
281 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
282 }
283
284 // Verify that the matrices are combined in the right order.
285 {
286 // Rotate and then translate, so the VD is offscreen.
287 Matrix4 mat4;
288 mat4.loadRotate(180);
289 damageAccumulator.pushTransform(&mat4);
290
291 SkiaDisplayList skiaDL;
292 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
293 dirtyVD.mutateProperties()->setBounds(bounds);
294 SkMatrix translate = SkMatrix::Translate(50, 50);
295 skiaDL.appendVD(&dirtyVD, translate);
296
297 ASSERT_TRUE(dirtyVD.isDirty());
298 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
299
300 TestUtils::MockTreeObserver observer;
301 ASSERT_FALSE(skiaDL.prepareListAndChildren(
302 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
303 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
304 damageAccumulator.popTransform();
305 }
306 {
307 // Switch the order of rotate and translate, so it is on screen.
308 Matrix4 mat4;
309 mat4.translate(50, 50);
310 damageAccumulator.pushTransform(&mat4);
311
312 SkiaDisplayList skiaDL;
313 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
314 dirtyVD.mutateProperties()->setBounds(bounds);
315 SkMatrix rotate;
316 rotate.setRotate(180);
317 skiaDL.appendVD(&dirtyVD, rotate);
318
319 ASSERT_TRUE(dirtyVD.isDirty());
320 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
321
322 TestUtils::MockTreeObserver observer;
323 ASSERT_TRUE(skiaDL.prepareListAndChildren(
324 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
325 ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
326 damageAccumulator.popTransform();
327 }
328 {
329 // An AVD that is larger than the screen.
330 SkiaDisplayList skiaDL;
331 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
332 dirtyVD.mutateProperties()->setBounds(SkRect::MakeLTRB(-1, -1, width + 1, height + 1));
333 skiaDL.appendVD(&dirtyVD);
334
335 ASSERT_TRUE(dirtyVD.isDirty());
336 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
337
338 TestUtils::MockTreeObserver observer;
339 ASSERT_TRUE(skiaDL.prepareListAndChildren(
340 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
341 ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
342 }
343 {
344 // An AVD whose bounds are not a rectangle after applying a matrix.
345 SkiaDisplayList skiaDL;
346 VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
347 dirtyVD.mutateProperties()->setBounds(bounds);
348 SkMatrix mat;
349 mat.setRotate(45, 50, 50);
350 skiaDL.appendVD(&dirtyVD, mat);
351
352 ASSERT_TRUE(dirtyVD.isDirty());
353 ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
354
355 TestUtils::MockTreeObserver observer;
356 ASSERT_TRUE(skiaDL.prepareListAndChildren(
357 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
358 ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
359 }
360 }
361
TEST(SkiaDisplayList,updateChildren)362 TEST(SkiaDisplayList, updateChildren) {
363 SkiaDisplayList skiaDL;
364
365 sp<RenderNode> renderNode = new RenderNode();
366 SkCanvas dummyCanvas;
367 skiaDL.mChildNodes.emplace_back(renderNode.get(), &dummyCanvas);
368 skiaDL.updateChildren([renderNode](RenderNode* n) { ASSERT_EQ(renderNode.get(), n); });
369 }
370