1 /* 2 * Copyright (C) 2015 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 <gtest/gtest.h> 18 19 #include <DeferredLayerUpdater.h> 20 #include <RecordedOp.h> 21 #include <RecordingCanvas.h> 22 #include <hwui/Paint.h> 23 #include <minikin/Layout.h> 24 #include <tests/common/TestUtils.h> 25 #include <utils/Color.h> 26 27 #include <SkGradientShader.h> 28 #include <SkImagePriv.h> 29 #include <SkShader.h> 30 31 namespace android { 32 namespace uirenderer { 33 34 static void playbackOps(const DisplayList& displayList, 35 std::function<void(const RecordedOp&)> opReceiver) { 36 for (auto& chunk : displayList.getChunks()) { 37 for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { 38 RecordedOp* op = displayList.getOps()[opIndex]; 39 opReceiver(*op); 40 } 41 } 42 } 43 44 static void validateSingleOp(std::unique_ptr<DisplayList>& dl, 45 std::function<void(const RecordedOp& op)> opValidator) { 46 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 47 opValidator(*(dl->getOps()[0])); 48 } 49 50 // The RecordingCanvas is only ever used by the OpenGL RenderPipeline and never when Skia is in use. 51 // Thus, even though many of these test are not directly dependent on the current RenderPipeline, we 52 // set them all to be OPENGL_PIPELINE_TESTs in case the underlying code in RecordingCanvas ever 53 // changes to require the use of the OPENGL_PIPELINE. Currently the textureLayer test is the only 54 // test that requires being an OPENGL_PIPELINE_TEST. 55 56 OPENGL_PIPELINE_TEST(RecordingCanvas, emptyPlayback) { 57 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 58 canvas.save(SaveFlags::MatrixClip); 59 canvas.restore(); 60 }); 61 playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); }); 62 } 63 64 OPENGL_PIPELINE_TEST(RecordingCanvas, clipRect) { 65 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { 66 canvas.save(SaveFlags::MatrixClip); 67 canvas.clipRect(0, 0, 100, 100, SkClipOp::kIntersect); 68 canvas.drawRect(0, 0, 50, 50, SkPaint()); 69 canvas.drawRect(50, 50, 100, 100, SkPaint()); 70 canvas.restore(); 71 }); 72 73 ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops"; 74 EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip); 75 EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip); 76 EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip) 77 << "Clip should be serialized once"; 78 } 79 80 OPENGL_PIPELINE_TEST(RecordingCanvas, emptyClipRect) { 81 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 82 canvas.save(SaveFlags::MatrixClip); 83 canvas.clipRect(0, 0, 100, 100, SkClipOp::kIntersect); 84 canvas.clipRect(100, 100, 200, 200, SkClipOp::kIntersect); 85 canvas.drawRect(0, 0, 50, 50, SkPaint()); // rejected at record time 86 canvas.restore(); 87 }); 88 ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected."; 89 } 90 91 OPENGL_PIPELINE_TEST(RecordingCanvas, emptyPaintRejection) { 92 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 93 SkPaint emptyPaint; 94 emptyPaint.setColor(Color::Transparent); 95 96 float points[] = {0, 0, 200, 200}; 97 canvas.drawPoints(points, 4, emptyPaint); 98 canvas.drawLines(points, 4, emptyPaint); 99 canvas.drawRect(0, 0, 200, 200, emptyPaint); 100 canvas.drawRegion(SkRegion(SkIRect::MakeWH(200, 200)), emptyPaint); 101 canvas.drawRoundRect(0, 0, 200, 200, 10, 10, emptyPaint); 102 canvas.drawCircle(100, 100, 100, emptyPaint); 103 canvas.drawOval(0, 0, 200, 200, emptyPaint); 104 canvas.drawArc(0, 0, 200, 200, 0, 360, true, emptyPaint); 105 SkPath path; 106 path.addRect(0, 0, 200, 200); 107 canvas.drawPath(path, emptyPaint); 108 }); 109 EXPECT_EQ(0u, dl->getOps().size()) << "Op should be rejected"; 110 } 111 112 OPENGL_PIPELINE_TEST(RecordingCanvas, drawArc) { 113 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 114 canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint()); 115 canvas.drawArc(0, 0, 100, 100, 0, 360, true, SkPaint()); 116 }); 117 118 auto&& ops = dl->getOps(); 119 ASSERT_EQ(2u, ops.size()) << "Must be exactly two ops"; 120 EXPECT_EQ(RecordedOpId::ArcOp, ops[0]->opId); 121 EXPECT_EQ(Rect(200, 200), ops[0]->unmappedBounds); 122 123 EXPECT_EQ(RecordedOpId::OvalOp, ops[1]->opId) << "Circular arcs should be converted to ovals"; 124 EXPECT_EQ(Rect(100, 100), ops[1]->unmappedBounds); 125 } 126 127 OPENGL_PIPELINE_TEST(RecordingCanvas, drawLines) { 128 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 129 SkPaint paint; 130 paint.setStrokeWidth( 131 20); // doesn't affect recorded bounds - would be resolved at bake time 132 float points[] = {0, 0, 20, 10, 30, 40, 90}; // NB: only 1 valid line 133 canvas.drawLines(&points[0], 7, paint); 134 }); 135 136 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 137 auto op = dl->getOps()[0]; 138 ASSERT_EQ(RecordedOpId::LinesOp, op->opId); 139 EXPECT_EQ(4, ((LinesOp*)op)->floatCount) 140 << "float count must be rounded down to closest multiple of 4"; 141 EXPECT_EQ(Rect(20, 10), op->unmappedBounds) 142 << "unmapped bounds must be size of line, and not outset for stroke width"; 143 } 144 145 OPENGL_PIPELINE_TEST(RecordingCanvas, drawRect) { 146 auto dl = TestUtils::createDisplayList<RecordingCanvas>( 147 100, 200, [](RecordingCanvas& canvas) { canvas.drawRect(10, 20, 90, 180, SkPaint()); }); 148 149 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 150 auto op = *(dl->getOps()[0]); 151 ASSERT_EQ(RecordedOpId::RectOp, op.opId); 152 EXPECT_EQ(nullptr, op.localClip); 153 EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds); 154 } 155 156 OPENGL_PIPELINE_TEST(RecordingCanvas, drawRoundRect) { 157 // Round case - stays rounded 158 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 159 canvas.drawRoundRect(0, 0, 100, 100, 10, 10, SkPaint()); 160 }); 161 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 162 ASSERT_EQ(RecordedOpId::RoundRectOp, dl->getOps()[0]->opId); 163 164 // Non-rounded case - turned into drawRect 165 dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 166 canvas.drawRoundRect(0, 0, 100, 100, 0, -1, SkPaint()); 167 }); 168 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 169 ASSERT_EQ(RecordedOpId::RectOp, dl->getOps()[0]->opId) 170 << "Non-rounded rects should be converted"; 171 } 172 173 OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs) { 174 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 175 SkPaint paint; 176 paint.setAntiAlias(true); 177 paint.setTextSize(20); 178 TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); 179 }); 180 181 int count = 0; 182 playbackOps(*dl, [&count](const RecordedOp& op) { 183 count++; 184 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 185 EXPECT_EQ(nullptr, op.localClip); 186 EXPECT_TRUE(op.localMatrix.isIdentity()); 187 EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25)) 188 << "Op expected to be 25+ pixels wide, 10+ pixels tall"; 189 }); 190 ASSERT_EQ(1, count); 191 } 192 193 OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) { 194 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 195 SkPaint paint; 196 paint.setAntiAlias(true); 197 paint.setTextSize(20); 198 for (int i = 0; i < 2; i++) { 199 for (int j = 0; j < 2; j++) { 200 uint32_t flags = paint.getFlags(); 201 if (i != 0) { 202 flags |= SkPaint::kUnderlineText_ReserveFlag; 203 } else { 204 flags &= ~SkPaint::kUnderlineText_ReserveFlag; 205 } 206 if (j != 0) { 207 flags |= SkPaint::kStrikeThruText_ReserveFlag; 208 } else { 209 flags &= ~SkPaint::kStrikeThruText_ReserveFlag; 210 } 211 paint.setFlags(flags); 212 TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); 213 } 214 } 215 }); 216 217 auto ops = dl->getOps(); 218 ASSERT_EQ(8u, ops.size()); 219 220 int index = 0; 221 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough 222 223 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 224 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only 225 226 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 227 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only 228 229 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 230 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline 231 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough 232 } 233 234 OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) { 235 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 236 SkPaint paint; 237 paint.setAntiAlias(true); 238 paint.setTextSize(20); 239 paint.setTextAlign(SkPaint::kLeft_Align); 240 TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); 241 paint.setTextAlign(SkPaint::kCenter_Align); 242 TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); 243 paint.setTextAlign(SkPaint::kRight_Align); 244 TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); 245 }); 246 247 int count = 0; 248 float lastX = FLT_MAX; 249 playbackOps(*dl, [&count, &lastX](const RecordedOp& op) { 250 count++; 251 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 252 EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign()) 253 << "recorded drawText commands must force kLeft_Align on their paint"; 254 255 // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class) 256 EXPECT_GT(lastX, ((const TextOp&)op).x) 257 << "x coordinate should reduce across each of the draw commands, from alignment"; 258 lastX = ((const TextOp&)op).x; 259 }); 260 ASSERT_EQ(3, count); 261 } 262 263 OPENGL_PIPELINE_TEST(RecordingCanvas, drawColor) { 264 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 265 canvas.drawColor(Color::Black, SkBlendMode::kSrcOver); 266 }); 267 268 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 269 auto op = *(dl->getOps()[0]); 270 EXPECT_EQ(RecordedOpId::ColorOp, op.opId); 271 EXPECT_EQ(nullptr, op.localClip); 272 EXPECT_TRUE(op.unmappedBounds.isEmpty()) << "Expect undefined recorded bounds"; 273 } 274 275 OPENGL_PIPELINE_TEST(RecordingCanvas, backgroundAndImage) { 276 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 277 sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25)); 278 SkPaint paint; 279 paint.setColor(SK_ColorBLUE); 280 281 canvas.save(SaveFlags::MatrixClip); 282 { 283 // a background! 284 canvas.save(SaveFlags::MatrixClip); 285 canvas.drawRect(0, 0, 100, 200, paint); 286 canvas.restore(); 287 } 288 { 289 // an image! 290 canvas.save(SaveFlags::MatrixClip); 291 canvas.translate(25, 25); 292 canvas.scale(2, 2); 293 canvas.drawBitmap(*bitmap, 0, 0, nullptr); 294 canvas.restore(); 295 } 296 canvas.restore(); 297 }); 298 299 int count = 0; 300 playbackOps(*dl, [&count](const RecordedOp& op) { 301 if (count == 0) { 302 ASSERT_EQ(RecordedOpId::RectOp, op.opId); 303 ASSERT_NE(nullptr, op.paint); 304 EXPECT_EQ(SK_ColorBLUE, op.paint->getColor()); 305 EXPECT_EQ(Rect(100, 200), op.unmappedBounds); 306 EXPECT_EQ(nullptr, op.localClip); 307 308 Matrix4 expectedMatrix; 309 expectedMatrix.loadIdentity(); 310 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 311 } else { 312 ASSERT_EQ(RecordedOpId::BitmapOp, op.opId); 313 EXPECT_EQ(nullptr, op.paint); 314 EXPECT_EQ(Rect(25, 25), op.unmappedBounds); 315 EXPECT_EQ(nullptr, op.localClip); 316 317 Matrix4 expectedMatrix; 318 expectedMatrix.loadTranslate(25, 25, 0); 319 expectedMatrix.scale(2, 2, 1); 320 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 321 } 322 count++; 323 }); 324 ASSERT_EQ(2, count); 325 } 326 327 RENDERTHREAD_OPENGL_PIPELINE_TEST(RecordingCanvas, textureLayer) { 328 auto layerUpdater = 329 TestUtils::createTextureLayerUpdater(renderThread, 100, 100, SkMatrix::MakeTrans(5, 5)); 330 331 auto dl = TestUtils::createDisplayList<RecordingCanvas>( 332 200, 200, 333 [&layerUpdater](RecordingCanvas& canvas) { canvas.drawLayer(layerUpdater.get()); }); 334 335 validateSingleOp(dl, [](const RecordedOp& op) { 336 ASSERT_EQ(RecordedOpId::TextureLayerOp, op.opId); 337 ASSERT_TRUE(op.localMatrix.isIdentity()) << "Op must not apply matrix at record time."; 338 }); 339 } 340 341 OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_simple) { 342 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 343 canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer); 344 canvas.drawRect(10, 20, 190, 180, SkPaint()); 345 canvas.restore(); 346 }); 347 int count = 0; 348 playbackOps(*dl, [&count](const RecordedOp& op) { 349 Matrix4 expectedMatrix; 350 switch (count++) { 351 case 0: 352 EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId); 353 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 354 EXPECT_EQ(nullptr, op.localClip); 355 EXPECT_TRUE(op.localMatrix.isIdentity()); 356 break; 357 case 1: 358 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 359 EXPECT_CLIP_RECT(Rect(180, 160), op.localClip); 360 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 361 expectedMatrix.loadTranslate(-10, -20, 0); 362 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 363 break; 364 case 2: 365 EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); 366 // Don't bother asserting recording state data - it's not used 367 break; 368 default: 369 ADD_FAILURE(); 370 } 371 }); 372 EXPECT_EQ(3, count); 373 } 374 375 OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rounding) { 376 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { 377 canvas.saveLayerAlpha(10.25f, 10.75f, 89.25f, 89.75f, 128, SaveFlags::ClipToLayer); 378 canvas.drawRect(20, 20, 80, 80, SkPaint()); 379 canvas.restore(); 380 }); 381 int count = 0; 382 playbackOps(*dl, [&count](const RecordedOp& op) { 383 Matrix4 expectedMatrix; 384 switch (count++) { 385 case 0: 386 EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId); 387 EXPECT_EQ(Rect(10, 10, 90, 90), op.unmappedBounds) << "Expect bounds rounded out"; 388 break; 389 case 1: 390 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 391 expectedMatrix.loadTranslate(-10, -10, 0); 392 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix) << "Expect rounded offset"; 393 break; 394 case 2: 395 EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); 396 // Don't bother asserting recording state data - it's not used 397 break; 398 default: 399 ADD_FAILURE(); 400 } 401 }); 402 EXPECT_EQ(3, count); 403 } 404 405 OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_missingRestore) { 406 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 407 canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer); 408 canvas.drawRect(0, 0, 200, 200, SkPaint()); 409 // Note: restore omitted, shouldn't result in unmatched save 410 }); 411 int count = 0; 412 playbackOps(*dl, [&count](const RecordedOp& op) { 413 if (count++ == 2) { 414 EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); 415 } 416 }); 417 EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer"; 418 } 419 420 OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_simpleUnclipped) { 421 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 422 canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped 423 canvas.drawRect(10, 20, 190, 180, SkPaint()); 424 canvas.restore(); 425 }); 426 int count = 0; 427 playbackOps(*dl, [&count](const RecordedOp& op) { 428 switch (count++) { 429 case 0: 430 EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId); 431 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 432 EXPECT_EQ(nullptr, op.localClip); 433 EXPECT_TRUE(op.localMatrix.isIdentity()); 434 break; 435 case 1: 436 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 437 EXPECT_EQ(nullptr, op.localClip); 438 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 439 EXPECT_TRUE(op.localMatrix.isIdentity()); 440 break; 441 case 2: 442 EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId); 443 // Don't bother asserting recording state data - it's not used 444 break; 445 default: 446 ADD_FAILURE(); 447 } 448 }); 449 EXPECT_EQ(3, count); 450 } 451 452 OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_addClipFlag) { 453 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 454 canvas.save(SaveFlags::MatrixClip); 455 canvas.clipRect(10, 20, 190, 180, SkClipOp::kIntersect); 456 canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped 457 canvas.drawRect(10, 20, 190, 180, SkPaint()); 458 canvas.restore(); 459 canvas.restore(); 460 }); 461 int count = 0; 462 playbackOps(*dl, [&count](const RecordedOp& op) { 463 if (count++ == 0) { 464 EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId) 465 << "Clip + unclipped saveLayer should result in a clipped layer"; 466 } 467 }); 468 EXPECT_EQ(3, count); 469 } 470 471 OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_viewportCrop) { 472 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 473 // shouldn't matter, since saveLayer will clip to its bounds 474 canvas.clipRect(-1000, -1000, 1000, 1000, SkClipOp::kReplace_deprecated); 475 476 canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer); 477 canvas.drawRect(0, 0, 400, 400, SkPaint()); 478 canvas.restore(); 479 }); 480 int count = 0; 481 playbackOps(*dl, [&count](const RecordedOp& op) { 482 if (count++ == 1) { 483 Matrix4 expectedMatrix; 484 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 485 EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be 486 // intersection of viewport and saveLayer bounds, in layer space; 487 EXPECT_EQ(Rect(400, 400), op.unmappedBounds); 488 expectedMatrix.loadTranslate(-100, -100, 0); 489 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 490 } 491 }); 492 EXPECT_EQ(3, count); 493 } 494 495 OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rotateUnclipped) { 496 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 497 canvas.save(SaveFlags::MatrixClip); 498 canvas.translate(100, 100); 499 canvas.rotate(45); 500 canvas.translate(-50, -50); 501 502 canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer); 503 canvas.drawRect(0, 0, 100, 100, SkPaint()); 504 canvas.restore(); 505 506 canvas.restore(); 507 }); 508 int count = 0; 509 playbackOps(*dl, [&count](const RecordedOp& op) { 510 if (count++ == 1) { 511 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 512 EXPECT_CLIP_RECT(Rect(100, 100), op.localClip); 513 EXPECT_EQ(Rect(100, 100), op.unmappedBounds); 514 EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix) 515 << "Recorded op shouldn't see any canvas transform before the saveLayer"; 516 } 517 }); 518 EXPECT_EQ(3, count); 519 } 520 521 OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rotateClipped) { 522 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 523 canvas.save(SaveFlags::MatrixClip); 524 canvas.translate(100, 100); 525 canvas.rotate(45); 526 canvas.translate(-200, -200); 527 528 // area of saveLayer will be clipped to parent viewport, so we ask for 400x400... 529 canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer); 530 canvas.drawRect(0, 0, 400, 400, SkPaint()); 531 canvas.restore(); 532 533 canvas.restore(); 534 }); 535 int count = 0; 536 playbackOps(*dl, [&count](const RecordedOp& op) { 537 if (count++ == 1) { 538 Matrix4 expectedMatrix; 539 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 540 541 // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by 542 // the parent 200x200 viewport, but prior to rotation 543 ASSERT_NE(nullptr, op.localClip); 544 ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode); 545 // NOTE: this check relies on saveLayer altering the clip post-viewport init. This 546 // causes the clip to be recorded by contained draw commands, though it's not necessary 547 // since the same clip will be computed at draw time. If such a change is made, this 548 // check could be done at record time by querying the clip, or the clip could be altered 549 // slightly so that it is serialized. 550 EXPECT_EQ(Rect(59, 59, 341, 341), op.localClip->rect); 551 EXPECT_EQ(Rect(400, 400), op.unmappedBounds); 552 expectedMatrix.loadIdentity(); 553 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 554 } 555 }); 556 EXPECT_EQ(3, count); 557 } 558 559 OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rejectBegin) { 560 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 561 canvas.save(SaveFlags::MatrixClip); 562 canvas.translate(0, -20); // avoid identity case 563 // empty clip rect should force layer + contents to be rejected 564 canvas.clipRect(0, -20, 200, -20, SkClipOp::kIntersect); 565 canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer); 566 canvas.drawRect(0, 0, 200, 200, SkPaint()); 567 canvas.restore(); 568 canvas.restore(); 569 }); 570 571 ASSERT_EQ(0u, dl->getOps().size()) << "Begin/Rect/End should all be rejected."; 572 } 573 574 OPENGL_PIPELINE_TEST(RecordingCanvas, drawRenderNode_rejection) { 575 auto child = 576 TestUtils::createNode(50, 50, 150, 150, [](RenderProperties& props, Canvas& canvas) { 577 SkPaint paint; 578 paint.setColor(SK_ColorWHITE); 579 canvas.drawRect(0, 0, 100, 100, paint); 580 }); 581 582 auto dl = TestUtils::createDisplayList<RecordingCanvas>( 583 200, 200, [&child](RecordingCanvas& canvas) { 584 canvas.clipRect(0, 0, 0, 0, SkClipOp::kIntersect); // empty clip, reject node 585 canvas.drawRenderNode(child.get()); // shouldn't crash when rejecting node... 586 }); 587 ASSERT_TRUE(dl->isEmpty()); 588 } 589 590 OPENGL_PIPELINE_TEST(RecordingCanvas, drawRenderNode_projection) { 591 sp<RenderNode> background = 592 TestUtils::createNode(50, 50, 150, 150, [](RenderProperties& props, Canvas& canvas) { 593 SkPaint paint; 594 paint.setColor(SK_ColorWHITE); 595 canvas.drawRect(0, 0, 100, 100, paint); 596 }); 597 { 598 background->mutateStagingProperties().setProjectionReceiver(false); 599 600 // NO RECEIVER PRESENT 601 auto dl = TestUtils::createDisplayList<RecordingCanvas>( 602 200, 200, [&background](RecordingCanvas& canvas) { 603 canvas.drawRect(0, 0, 100, 100, SkPaint()); 604 canvas.drawRenderNode(background.get()); 605 canvas.drawRect(0, 0, 100, 100, SkPaint()); 606 }); 607 EXPECT_EQ(-1, dl->projectionReceiveIndex) 608 << "no projection receiver should have been observed"; 609 } 610 { 611 background->mutateStagingProperties().setProjectionReceiver(true); 612 613 // RECEIVER PRESENT 614 auto dl = TestUtils::createDisplayList<RecordingCanvas>( 615 200, 200, [&background](RecordingCanvas& canvas) { 616 canvas.drawRect(0, 0, 100, 100, SkPaint()); 617 canvas.drawRenderNode(background.get()); 618 canvas.drawRect(0, 0, 100, 100, SkPaint()); 619 }); 620 621 ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops"; 622 auto op = dl->getOps()[1]; 623 EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId); 624 EXPECT_EQ(1, dl->projectionReceiveIndex) << "correct projection receiver not identified"; 625 626 // verify the behavior works even though projection receiver hasn't been sync'd yet 627 EXPECT_TRUE(background->stagingProperties().isProjectionReceiver()); 628 EXPECT_FALSE(background->properties().isProjectionReceiver()); 629 } 630 } 631 632 OPENGL_PIPELINE_TEST(RecordingCanvas, firstClipWillReplace) { 633 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 634 canvas.save(SaveFlags::MatrixClip); 635 // since no explicit clip set on canvas, this should be the one observed on op: 636 canvas.clipRect(-100, -100, 300, 300, SkClipOp::kIntersect); 637 638 SkPaint paint; 639 paint.setColor(SK_ColorWHITE); 640 canvas.drawRect(0, 0, 100, 100, paint); 641 642 canvas.restore(); 643 }); 644 ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op"; 645 // first clip must be preserved, even if it extends beyond canvas bounds 646 EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip); 647 } 648 649 OPENGL_PIPELINE_TEST(RecordingCanvas, replaceClipIntersectWithRoot) { 650 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { 651 canvas.save(SaveFlags::MatrixClip); 652 canvas.clipRect(-10, -10, 110, 110, SkClipOp::kReplace_deprecated); 653 canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); 654 canvas.restore(); 655 }); 656 ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op"; 657 // first clip must be preserved, even if it extends beyond canvas bounds 658 EXPECT_CLIP_RECT(Rect(-10, -10, 110, 110), dl->getOps()[0]->localClip); 659 EXPECT_TRUE(dl->getOps()[0]->localClip->intersectWithRoot); 660 } 661 662 OPENGL_PIPELINE_TEST(RecordingCanvas, insertReorderBarrier) { 663 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 664 canvas.drawRect(0, 0, 400, 400, SkPaint()); 665 canvas.insertReorderBarrier(true); 666 canvas.insertReorderBarrier(false); 667 canvas.insertReorderBarrier(false); 668 canvas.insertReorderBarrier(true); 669 canvas.drawRect(0, 0, 400, 400, SkPaint()); 670 canvas.insertReorderBarrier(false); 671 }); 672 673 auto chunks = dl->getChunks(); 674 EXPECT_EQ(0u, chunks[0].beginOpIndex); 675 EXPECT_EQ(1u, chunks[0].endOpIndex); 676 EXPECT_FALSE(chunks[0].reorderChildren); 677 678 EXPECT_EQ(1u, chunks[1].beginOpIndex); 679 EXPECT_EQ(2u, chunks[1].endOpIndex); 680 EXPECT_TRUE(chunks[1].reorderChildren); 681 } 682 683 OPENGL_PIPELINE_TEST(RecordingCanvas, insertReorderBarrier_clip) { 684 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 685 // first chunk: no recorded clip 686 canvas.insertReorderBarrier(true); 687 canvas.drawRect(0, 0, 400, 400, SkPaint()); 688 689 // second chunk: no recorded clip, since inorder region 690 canvas.clipRect(0, 0, 200, 200, SkClipOp::kIntersect); 691 canvas.insertReorderBarrier(false); 692 canvas.drawRect(0, 0, 400, 400, SkPaint()); 693 694 // third chunk: recorded clip 695 canvas.insertReorderBarrier(true); 696 canvas.drawRect(0, 0, 400, 400, SkPaint()); 697 }); 698 699 auto chunks = dl->getChunks(); 700 ASSERT_EQ(3u, chunks.size()); 701 702 EXPECT_TRUE(chunks[0].reorderChildren); 703 EXPECT_EQ(nullptr, chunks[0].reorderClip); 704 705 EXPECT_FALSE(chunks[1].reorderChildren); 706 EXPECT_EQ(nullptr, chunks[1].reorderClip); 707 708 EXPECT_TRUE(chunks[2].reorderChildren); 709 ASSERT_NE(nullptr, chunks[2].reorderClip); 710 EXPECT_EQ(Rect(200, 200), chunks[2].reorderClip->rect); 711 } 712 713 OPENGL_PIPELINE_TEST(RecordingCanvas, refPaint) { 714 SkPaint paint; 715 716 auto dl = TestUtils::createDisplayList<RecordingCanvas>( 717 200, 200, [&paint](RecordingCanvas& canvas) { 718 paint.setColor(SK_ColorBLUE); 719 // first two should use same paint 720 canvas.drawRect(0, 0, 200, 10, paint); 721 SkPaint paintCopy(paint); 722 canvas.drawRect(0, 10, 200, 20, paintCopy); 723 724 // only here do we use different paint ptr 725 paint.setColor(SK_ColorRED); 726 canvas.drawRect(0, 20, 200, 30, paint); 727 }); 728 auto ops = dl->getOps(); 729 ASSERT_EQ(3u, ops.size()); 730 731 // first two are the same 732 EXPECT_NE(nullptr, ops[0]->paint); 733 EXPECT_NE(&paint, ops[0]->paint); 734 EXPECT_EQ(ops[0]->paint, ops[1]->paint); 735 736 // last is different, but still copied / non-null 737 EXPECT_NE(nullptr, ops[2]->paint); 738 EXPECT_NE(ops[0]->paint, ops[2]->paint); 739 EXPECT_NE(&paint, ops[2]->paint); 740 } 741 742 OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmap) { 743 sk_sp<Bitmap> bitmap(TestUtils::createBitmap(100, 100)); 744 auto dl = TestUtils::createDisplayList<RecordingCanvas>( 745 100, 100, 746 [&bitmap](RecordingCanvas& canvas) { canvas.drawBitmap(*bitmap, 0, 0, nullptr); }); 747 auto& bitmaps = dl->getBitmapResources(); 748 EXPECT_EQ(1u, bitmaps.size()); 749 } 750 751 OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmapInShader_bitmapShader) { 752 sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100); 753 auto dl = TestUtils::createDisplayList<RecordingCanvas>( 754 100, 100, [&bitmap](RecordingCanvas& canvas) { 755 SkPaint paint; 756 SkBitmap skBitmap; 757 bitmap->getSkBitmap(&skBitmap); 758 sk_sp<SkImage> image = 759 SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode); 760 sk_sp<SkShader> shader = 761 image->makeShader(SkShader::TileMode::kClamp_TileMode, 762 SkShader::TileMode::kClamp_TileMode, nullptr); 763 paint.setShader(std::move(shader)); 764 canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint); 765 }); 766 auto& bitmaps = dl->getBitmapResources(); 767 EXPECT_EQ(1u, bitmaps.size()); 768 } 769 770 OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmapInShader_composeShader) { 771 sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100); 772 auto dl = TestUtils::createDisplayList<RecordingCanvas>( 773 100, 100, [&bitmap](RecordingCanvas& canvas) { 774 SkPaint paint; 775 SkBitmap skBitmap; 776 bitmap->getSkBitmap(&skBitmap); 777 sk_sp<SkImage> image = 778 SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode); 779 sk_sp<SkShader> shader1 = 780 image->makeShader(SkShader::TileMode::kClamp_TileMode, 781 SkShader::TileMode::kClamp_TileMode, nullptr); 782 783 SkPoint center; 784 center.set(50, 50); 785 SkColor colors[2]; 786 colors[0] = Color::Black; 787 colors[1] = Color::White; 788 sk_sp<SkShader> shader2 = SkGradientShader::MakeRadial( 789 center, 50, colors, nullptr, 2, SkShader::TileMode::kRepeat_TileMode); 790 791 sk_sp<SkShader> composeShader = SkShader::MakeComposeShader( 792 std::move(shader1), std::move(shader2), SkBlendMode::kMultiply); 793 paint.setShader(std::move(composeShader)); 794 canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint); 795 }); 796 auto& bitmaps = dl->getBitmapResources(); 797 EXPECT_EQ(1u, bitmaps.size()); 798 } 799 800 OPENGL_PIPELINE_TEST(RecordingCanvas, drawText) { 801 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 802 Paint paint; 803 paint.setAntiAlias(true); 804 paint.setTextSize(20); 805 TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25); 806 }); 807 808 int count = 0; 809 playbackOps(*dl, [&count](const RecordedOp& op) { 810 count++; 811 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 812 EXPECT_EQ(nullptr, op.localClip); 813 EXPECT_TRUE(op.localMatrix.isIdentity()); 814 EXPECT_TRUE(op.unmappedBounds.getHeight() >= 10); 815 EXPECT_TRUE(op.unmappedBounds.getWidth() >= 25); 816 }); 817 ASSERT_EQ(1, count); 818 } 819 820 OPENGL_PIPELINE_TEST(RecordingCanvas, drawTextInHighContrast) { 821 Properties::enableHighContrastText = true; 822 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 823 Paint paint; 824 paint.setColor(SK_ColorWHITE); 825 paint.setAntiAlias(true); 826 paint.setTextSize(20); 827 TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25); 828 }); 829 Properties::enableHighContrastText = false; 830 831 int count = 0; 832 playbackOps(*dl, [&count](const RecordedOp& op) { 833 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 834 if (count++ == 0) { 835 EXPECT_EQ(SK_ColorBLACK, op.paint->getColor()); 836 EXPECT_EQ(SkPaint::kStrokeAndFill_Style, op.paint->getStyle()); 837 } else { 838 EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); 839 EXPECT_EQ(SkPaint::kFill_Style, op.paint->getStyle()); 840 } 841 842 }); 843 ASSERT_EQ(2, count); 844 } 845 846 } // namespace uirenderer 847 } // namespace android 848