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