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