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