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 34 TEST(SkiaDisplayList, create) { 35 SkiaDisplayList skiaDL; 36 ASSERT_TRUE(skiaDL.isEmpty()); 37 ASSERT_FALSE(skiaDL.mProjectionReceiver); 38 } 39 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 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 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: 137 virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { 138 return new AnimationContext(clock); 139 } 140 }; 141 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 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 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