• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 = ANativeWindow_getWidth(surface.get());
212     int height = ANativeWindow_getHeight(surface.get());
213     canvasContext->setSurface(surface.get());
214 
215     TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
216     DamageAccumulator damageAccumulator;
217     info.damageAccumulator = &damageAccumulator;
218 
219     // The VectorDrawableRoot needs to have bounds on screen (and therefore not
220     // empty) in order to have PropertyChangeWillBeConsumed set.
221     const auto bounds = SkRect::MakeIWH(100, 100);
222 
223     for (const SkRect b : {bounds.makeOffset(width, 0),
224                            bounds.makeOffset(0, height),
225                            bounds.makeOffset(-bounds.width(), 0),
226                            bounds.makeOffset(0, -bounds.height())}) {
227         SkiaDisplayList skiaDL;
228         VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
229         dirtyVD.mutateProperties()->setBounds(b);
230         skiaDL.appendVD(&dirtyVD);
231 
232         ASSERT_TRUE(dirtyVD.isDirty());
233         ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
234 
235         TestUtils::MockTreeObserver observer;
236         ASSERT_FALSE(skiaDL.prepareListAndChildren(
237                 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
238         ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
239     }
240 
241     // The DamageAccumulator's transform can also result in the
242     // VectorDrawableRoot being offscreen.
243     for (const SkISize translate : { SkISize{width, 0},
244                                      SkISize{0, height},
245                                      SkISize{-width, 0},
246                                      SkISize{0, -height}}) {
247         Matrix4 mat4;
248         mat4.translate(translate.fWidth, translate.fHeight);
249         damageAccumulator.pushTransform(&mat4);
250 
251         SkiaDisplayList skiaDL;
252         VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
253         dirtyVD.mutateProperties()->setBounds(bounds);
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         damageAccumulator.popTransform();
264     }
265 
266     // Another way to be offscreen: a matrix from the draw call.
267     for (const SkMatrix translate : { SkMatrix::MakeTrans(width, 0),
268                                       SkMatrix::MakeTrans(0, height),
269                                       SkMatrix::MakeTrans(-width, 0),
270                                       SkMatrix::MakeTrans(0, -height)}) {
271         SkiaDisplayList skiaDL;
272         VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
273         dirtyVD.mutateProperties()->setBounds(bounds);
274         skiaDL.appendVD(&dirtyVD, translate);
275 
276         ASSERT_TRUE(dirtyVD.isDirty());
277         ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
278 
279         TestUtils::MockTreeObserver observer;
280         ASSERT_FALSE(skiaDL.prepareListAndChildren(
281                 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
282         ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
283     }
284 
285     // Verify that the matrices are combined in the right order.
286     {
287         // Rotate and then translate, so the VD is offscreen.
288         Matrix4 mat4;
289         mat4.loadRotate(180);
290         damageAccumulator.pushTransform(&mat4);
291 
292         SkiaDisplayList skiaDL;
293         VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
294         dirtyVD.mutateProperties()->setBounds(bounds);
295         SkMatrix translate = SkMatrix::MakeTrans(50, 50);
296         skiaDL.appendVD(&dirtyVD, translate);
297 
298         ASSERT_TRUE(dirtyVD.isDirty());
299         ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
300 
301         TestUtils::MockTreeObserver observer;
302         ASSERT_FALSE(skiaDL.prepareListAndChildren(
303                 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
304         ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
305         damageAccumulator.popTransform();
306     }
307     {
308         // Switch the order of rotate and translate, so it is on screen.
309         Matrix4 mat4;
310         mat4.translate(50, 50);
311         damageAccumulator.pushTransform(&mat4);
312 
313         SkiaDisplayList skiaDL;
314         VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
315         dirtyVD.mutateProperties()->setBounds(bounds);
316         SkMatrix rotate;
317         rotate.setRotate(180);
318         skiaDL.appendVD(&dirtyVD, rotate);
319 
320         ASSERT_TRUE(dirtyVD.isDirty());
321         ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
322 
323         TestUtils::MockTreeObserver observer;
324         ASSERT_TRUE(skiaDL.prepareListAndChildren(
325                 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
326         ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
327         damageAccumulator.popTransform();
328     }
329     {
330         // An AVD that is larger than the screen.
331         SkiaDisplayList skiaDL;
332         VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
333         dirtyVD.mutateProperties()->setBounds(SkRect::MakeLTRB(-1, -1, width + 1, height + 1));
334         skiaDL.appendVD(&dirtyVD);
335 
336         ASSERT_TRUE(dirtyVD.isDirty());
337         ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
338 
339         TestUtils::MockTreeObserver observer;
340         ASSERT_TRUE(skiaDL.prepareListAndChildren(
341                 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
342         ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
343     }
344     {
345         // An AVD whose bounds are not a rectangle after applying a matrix.
346         SkiaDisplayList skiaDL;
347         VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
348         dirtyVD.mutateProperties()->setBounds(bounds);
349         SkMatrix mat;
350         mat.setRotate(45, 50, 50);
351         skiaDL.appendVD(&dirtyVD, mat);
352 
353         ASSERT_TRUE(dirtyVD.isDirty());
354         ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
355 
356         TestUtils::MockTreeObserver observer;
357         ASSERT_TRUE(skiaDL.prepareListAndChildren(
358                 observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
359         ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
360     }
361 }
362 
TEST(SkiaDisplayList,updateChildren)363 TEST(SkiaDisplayList, updateChildren) {
364     SkiaDisplayList skiaDL;
365 
366     sp<RenderNode> renderNode = new RenderNode();
367     SkCanvas dummyCanvas;
368     skiaDL.mChildNodes.emplace_back(renderNode.get(), &dummyCanvas);
369     skiaDL.updateChildren([renderNode](RenderNode* n) { ASSERT_EQ(renderNode.get(), n); });
370 }
371