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