xref: /aosp_15_r20/frameworks/base/libs/hwui/tests/unit/SkiaDisplayListTests.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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