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