RecordingCanvasTests.cpp revision a7f6bba1a3565c19715e878dfe7f0e01022944ff
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 <RecordedOp.h> 20#include <RecordingCanvas.h> 21#include <hwui/Paint.h> 22#include <minikin/Layout.h> 23#include <tests/common/TestUtils.h> 24#include <utils/Color.h> 25 26namespace android { 27namespace uirenderer { 28 29static void playbackOps(const DisplayList& displayList, 30 std::function<void(const RecordedOp&)> opReceiver) { 31 for (auto& chunk : displayList.getChunks()) { 32 for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { 33 RecordedOp* op = displayList.getOps()[opIndex]; 34 opReceiver(*op); 35 } 36 } 37} 38 39TEST(RecordingCanvas, emptyPlayback) { 40 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 41 canvas.save(SaveFlags::MatrixClip); 42 canvas.restore(); 43 }); 44 playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); }); 45} 46 47TEST(RecordingCanvas, clipRect) { 48 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { 49 canvas.save(SaveFlags::MatrixClip); 50 canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op); 51 canvas.drawRect(0, 0, 50, 50, SkPaint()); 52 canvas.drawRect(50, 50, 100, 100, SkPaint()); 53 canvas.restore(); 54 }); 55 56 ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops"; 57 EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip); 58 EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip); 59 EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip) 60 << "Clip should be serialized once"; 61} 62 63TEST(RecordingCanvas, emptyClipRect) { 64 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 65 canvas.save(SaveFlags::MatrixClip); 66 canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op); 67 canvas.clipRect(100, 100, 200, 200, SkRegion::kIntersect_Op); 68 canvas.drawRect(0, 0, 50, 50, SkPaint()); // rejected at record time 69 canvas.restore(); 70 }); 71 ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected."; 72} 73 74TEST(RecordingCanvas, drawArc) { 75 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 76 canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint()); 77 canvas.drawArc(0, 0, 100, 100, 0, 360, true, SkPaint()); 78 }); 79 80 auto&& ops = dl->getOps(); 81 ASSERT_EQ(2u, ops.size()) << "Must be exactly two ops"; 82 EXPECT_EQ(RecordedOpId::ArcOp, ops[0]->opId); 83 EXPECT_EQ(Rect(200, 200), ops[0]->unmappedBounds); 84 85 EXPECT_EQ(RecordedOpId::OvalOp, ops[1]->opId) 86 << "Circular arcs should be converted to ovals"; 87 EXPECT_EQ(Rect(100, 100), ops[1]->unmappedBounds); 88} 89 90TEST(RecordingCanvas, drawLines) { 91 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 92 SkPaint paint; 93 paint.setStrokeWidth(20); // doesn't affect recorded bounds - would be resolved at bake time 94 float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line 95 canvas.drawLines(&points[0], 7, paint); 96 }); 97 98 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 99 auto op = dl->getOps()[0]; 100 ASSERT_EQ(RecordedOpId::LinesOp, op->opId); 101 EXPECT_EQ(4, ((LinesOp*)op)->floatCount) 102 << "float count must be rounded down to closest multiple of 4"; 103 EXPECT_EQ(Rect(20, 10), op->unmappedBounds) 104 << "unmapped bounds must be size of line, and not outset for stroke width"; 105} 106 107TEST(RecordingCanvas, drawRect) { 108 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 109 canvas.drawRect(10, 20, 90, 180, SkPaint()); 110 }); 111 112 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 113 auto op = *(dl->getOps()[0]); 114 ASSERT_EQ(RecordedOpId::RectOp, op.opId); 115 EXPECT_EQ(nullptr, op.localClip); 116 EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds); 117} 118 119TEST(RecordingCanvas, drawRoundRect) { 120 // Round case - stays rounded 121 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 122 canvas.drawRoundRect(0, 0, 100, 100, 10, 10, SkPaint()); 123 }); 124 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 125 ASSERT_EQ(RecordedOpId::RoundRectOp, dl->getOps()[0]->opId); 126 127 // Non-rounded case - turned into drawRect 128 dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 129 canvas.drawRoundRect(0, 0, 100, 100, 0, -1, SkPaint()); 130 }); 131 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 132 ASSERT_EQ(RecordedOpId::RectOp, dl->getOps()[0]->opId) 133 << "Non-rounded rects should be converted"; 134} 135 136TEST(RecordingCanvas, drawGlyphs) { 137 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 138 SkPaint paint; 139 paint.setAntiAlias(true); 140 paint.setTextSize(20); 141 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 142 TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); 143 }); 144 145 int count = 0; 146 playbackOps(*dl, [&count](const RecordedOp& op) { 147 count++; 148 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 149 EXPECT_EQ(nullptr, op.localClip); 150 EXPECT_TRUE(op.localMatrix.isIdentity()); 151 EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25)) 152 << "Op expected to be 25+ pixels wide, 10+ pixels tall"; 153 }); 154 ASSERT_EQ(1, count); 155} 156 157TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) { 158 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 159 SkPaint paint; 160 paint.setAntiAlias(true); 161 paint.setTextSize(20); 162 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 163 for (int i = 0; i < 2; i++) { 164 for (int j = 0; j < 2; j++) { 165 paint.setUnderlineText(i != 0); 166 paint.setStrikeThruText(j != 0); 167 TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); 168 } 169 } 170 }); 171 172 auto ops = dl->getOps(); 173 ASSERT_EQ(8u, ops.size()); 174 175 int index = 0; 176 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough 177 178 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 179 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only 180 181 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 182 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only 183 184 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 185 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline 186 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough 187} 188 189TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) { 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 paint.setTextAlign(SkPaint::kLeft_Align); 196 TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); 197 paint.setTextAlign(SkPaint::kCenter_Align); 198 TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); 199 paint.setTextAlign(SkPaint::kRight_Align); 200 TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); 201 }); 202 203 int count = 0; 204 float lastX = FLT_MAX; 205 playbackOps(*dl, [&count, &lastX](const RecordedOp& op) { 206 count++; 207 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 208 EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign()) 209 << "recorded drawText commands must force kLeft_Align on their paint"; 210 211 // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class) 212 EXPECT_GT(lastX, ((const TextOp&)op).x) 213 << "x coordinate should reduce across each of the draw commands, from alignment"; 214 lastX = ((const TextOp&)op).x; 215 }); 216 ASSERT_EQ(3, count); 217} 218 219TEST(RecordingCanvas, drawColor) { 220 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 221 canvas.drawColor(Color::Black, SkXfermode::kSrcOver_Mode); 222 }); 223 224 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 225 auto op = *(dl->getOps()[0]); 226 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 227 EXPECT_EQ(nullptr, op.localClip); 228 EXPECT_TRUE(op.unmappedBounds.contains(Rect(-1000, -1000, 1000, 1000))) 229 << "no clip, unmappedBounds should resolve to be much larger than DL bounds"; 230} 231 232TEST(RecordingCanvas, backgroundAndImage) { 233 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 234 SkBitmap bitmap; 235 bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25)); 236 SkPaint paint; 237 paint.setColor(SK_ColorBLUE); 238 239 canvas.save(SaveFlags::MatrixClip); 240 { 241 // a background! 242 canvas.save(SaveFlags::MatrixClip); 243 canvas.drawRect(0, 0, 100, 200, paint); 244 canvas.restore(); 245 } 246 { 247 // an image! 248 canvas.save(SaveFlags::MatrixClip); 249 canvas.translate(25, 25); 250 canvas.scale(2, 2); 251 canvas.drawBitmap(bitmap, 0, 0, nullptr); 252 canvas.restore(); 253 } 254 canvas.restore(); 255 }); 256 257 int count = 0; 258 playbackOps(*dl, [&count](const RecordedOp& op) { 259 if (count == 0) { 260 ASSERT_EQ(RecordedOpId::RectOp, op.opId); 261 ASSERT_NE(nullptr, op.paint); 262 EXPECT_EQ(SK_ColorBLUE, op.paint->getColor()); 263 EXPECT_EQ(Rect(100, 200), op.unmappedBounds); 264 EXPECT_EQ(nullptr, op.localClip); 265 266 Matrix4 expectedMatrix; 267 expectedMatrix.loadIdentity(); 268 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 269 } else { 270 ASSERT_EQ(RecordedOpId::BitmapOp, op.opId); 271 EXPECT_EQ(nullptr, op.paint); 272 EXPECT_EQ(Rect(25, 25), op.unmappedBounds); 273 EXPECT_EQ(nullptr, op.localClip); 274 275 Matrix4 expectedMatrix; 276 expectedMatrix.loadTranslate(25, 25, 0); 277 expectedMatrix.scale(2, 2, 1); 278 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 279 } 280 count++; 281 }); 282 ASSERT_EQ(2, count); 283} 284 285TEST(RecordingCanvas, saveLayer_simple) { 286 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 287 canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer); 288 canvas.drawRect(10, 20, 190, 180, SkPaint()); 289 canvas.restore(); 290 }); 291 int count = 0; 292 playbackOps(*dl, [&count](const RecordedOp& op) { 293 Matrix4 expectedMatrix; 294 switch(count++) { 295 case 0: 296 EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId); 297 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 298 EXPECT_EQ(nullptr, op.localClip); 299 EXPECT_TRUE(op.localMatrix.isIdentity()); 300 break; 301 case 1: 302 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 303 EXPECT_CLIP_RECT(Rect(180, 160), op.localClip); 304 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 305 expectedMatrix.loadTranslate(-10, -20, 0); 306 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 307 break; 308 case 2: 309 EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); 310 // Don't bother asserting recording state data - it's not used 311 break; 312 default: 313 ADD_FAILURE(); 314 } 315 }); 316 EXPECT_EQ(3, count); 317} 318 319TEST(RecordingCanvas, saveLayer_missingRestore) { 320 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 321 canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer); 322 canvas.drawRect(0, 0, 200, 200, SkPaint()); 323 // Note: restore omitted, shouldn't result in unmatched save 324 }); 325 int count = 0; 326 playbackOps(*dl, [&count](const RecordedOp& op) { 327 if (count++ == 2) { 328 EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); 329 } 330 }); 331 EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer"; 332} 333 334TEST(RecordingCanvas, saveLayer_simpleUnclipped) { 335 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 336 canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped 337 canvas.drawRect(10, 20, 190, 180, SkPaint()); 338 canvas.restore(); 339 }); 340 int count = 0; 341 playbackOps(*dl, [&count](const RecordedOp& op) { 342 switch(count++) { 343 case 0: 344 EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId); 345 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 346 EXPECT_EQ(nullptr, op.localClip); 347 EXPECT_TRUE(op.localMatrix.isIdentity()); 348 break; 349 case 1: 350 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 351 EXPECT_EQ(nullptr, op.localClip); 352 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 353 EXPECT_TRUE(op.localMatrix.isIdentity()); 354 break; 355 case 2: 356 EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId); 357 // Don't bother asserting recording state data - it's not used 358 break; 359 default: 360 ADD_FAILURE(); 361 } 362 }); 363 EXPECT_EQ(3, count); 364} 365 366TEST(RecordingCanvas, saveLayer_addClipFlag) { 367 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 368 canvas.save(SaveFlags::MatrixClip); 369 canvas.clipRect(10, 20, 190, 180, SkRegion::kIntersect_Op); 370 canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped 371 canvas.drawRect(10, 20, 190, 180, SkPaint()); 372 canvas.restore(); 373 canvas.restore(); 374 }); 375 int count = 0; 376 playbackOps(*dl, [&count](const RecordedOp& op) { 377 if (count++ == 0) { 378 EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId) 379 << "Clip + unclipped saveLayer should result in a clipped layer"; 380 } 381 }); 382 EXPECT_EQ(3, count); 383} 384 385TEST(RecordingCanvas, saveLayer_viewportCrop) { 386 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 387 // shouldn't matter, since saveLayer will clip to its bounds 388 canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op); 389 390 canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer); 391 canvas.drawRect(0, 0, 400, 400, SkPaint()); 392 canvas.restore(); 393 }); 394 int count = 0; 395 playbackOps(*dl, [&count](const RecordedOp& op) { 396 if (count++ == 1) { 397 Matrix4 expectedMatrix; 398 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 399 EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be 400 // intersection of viewport and saveLayer bounds, in layer space; 401 EXPECT_EQ(Rect(400, 400), op.unmappedBounds); 402 expectedMatrix.loadTranslate(-100, -100, 0); 403 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 404 } 405 }); 406 EXPECT_EQ(3, count); 407} 408 409TEST(RecordingCanvas, saveLayer_rotateUnclipped) { 410 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 411 canvas.save(SaveFlags::MatrixClip); 412 canvas.translate(100, 100); 413 canvas.rotate(45); 414 canvas.translate(-50, -50); 415 416 canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer); 417 canvas.drawRect(0, 0, 100, 100, SkPaint()); 418 canvas.restore(); 419 420 canvas.restore(); 421 }); 422 int count = 0; 423 playbackOps(*dl, [&count](const RecordedOp& op) { 424 if (count++ == 1) { 425 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 426 EXPECT_CLIP_RECT(Rect(100, 100), op.localClip); 427 EXPECT_EQ(Rect(100, 100), op.unmappedBounds); 428 EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix) 429 << "Recorded op shouldn't see any canvas transform before the saveLayer"; 430 } 431 }); 432 EXPECT_EQ(3, count); 433} 434 435TEST(RecordingCanvas, saveLayer_rotateClipped) { 436 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 437 canvas.save(SaveFlags::MatrixClip); 438 canvas.translate(100, 100); 439 canvas.rotate(45); 440 canvas.translate(-200, -200); 441 442 // area of saveLayer will be clipped to parent viewport, so we ask for 400x400... 443 canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer); 444 canvas.drawRect(0, 0, 400, 400, SkPaint()); 445 canvas.restore(); 446 447 canvas.restore(); 448 }); 449 int count = 0; 450 playbackOps(*dl, [&count](const RecordedOp& op) { 451 if (count++ == 1) { 452 Matrix4 expectedMatrix; 453 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 454 455 // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by 456 // the parent 200x200 viewport, but prior to rotation 457 ASSERT_NE(nullptr, op.localClip); 458 ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode); 459 // NOTE: this check relies on saveLayer altering the clip post-viewport init. This 460 // causes the clip to be recorded by contained draw commands, though it's not necessary 461 // since the same clip will be computed at draw time. If such a change is made, this 462 // check could be done at record time by querying the clip, or the clip could be altered 463 // slightly so that it is serialized. 464 EXPECT_EQ(Rect(59, 59, 341, 341), op.localClip->rect); 465 EXPECT_EQ(Rect(400, 400), op.unmappedBounds); 466 expectedMatrix.loadIdentity(); 467 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 468 } 469 }); 470 EXPECT_EQ(3, count); 471} 472 473TEST(RecordingCanvas, drawRenderNode_rejection) { 474 auto child = TestUtils::createNode(50, 50, 150, 150, 475 [](RenderProperties& props, RecordingCanvas& canvas) { 476 SkPaint paint; 477 paint.setColor(SK_ColorWHITE); 478 canvas.drawRect(0, 0, 100, 100, paint); 479 }); 480 481 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&child](RecordingCanvas& canvas) { 482 canvas.clipRect(0, 0, 0, 0, SkRegion::kIntersect_Op); // empty clip, reject node 483 canvas.drawRenderNode(child.get()); // shouldn't crash when rejecting node... 484 }); 485 ASSERT_TRUE(dl->isEmpty()); 486} 487 488TEST(RecordingCanvas, drawRenderNode_projection) { 489 sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150, 490 [](RenderProperties& props, RecordingCanvas& canvas) { 491 SkPaint paint; 492 paint.setColor(SK_ColorWHITE); 493 canvas.drawRect(0, 0, 100, 100, paint); 494 }); 495 { 496 background->mutateStagingProperties().setProjectionReceiver(false); 497 498 // NO RECEIVER PRESENT 499 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, 500 [&background](RecordingCanvas& canvas) { 501 canvas.drawRect(0, 0, 100, 100, SkPaint()); 502 canvas.drawRenderNode(background.get()); 503 canvas.drawRect(0, 0, 100, 100, SkPaint()); 504 }); 505 EXPECT_EQ(-1, dl->projectionReceiveIndex) 506 << "no projection receiver should have been observed"; 507 } 508 { 509 background->mutateStagingProperties().setProjectionReceiver(true); 510 511 // RECEIVER PRESENT 512 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, 513 [&background](RecordingCanvas& canvas) { 514 canvas.drawRect(0, 0, 100, 100, SkPaint()); 515 canvas.drawRenderNode(background.get()); 516 canvas.drawRect(0, 0, 100, 100, SkPaint()); 517 }); 518 519 ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops"; 520 auto op = dl->getOps()[1]; 521 EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId); 522 EXPECT_EQ(1, dl->projectionReceiveIndex) 523 << "correct projection receiver not identified"; 524 525 // verify the behavior works even though projection receiver hasn't been sync'd yet 526 EXPECT_TRUE(background->stagingProperties().isProjectionReceiver()); 527 EXPECT_FALSE(background->properties().isProjectionReceiver()); 528 } 529} 530 531TEST(RecordingCanvas, firstClipWillReplace) { 532 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 533 canvas.save(SaveFlags::MatrixClip); 534 // since no explicit clip set on canvas, this should be the one observed on op: 535 canvas.clipRect(-100, -100, 300, 300, SkRegion::kIntersect_Op); 536 537 SkPaint paint; 538 paint.setColor(SK_ColorWHITE); 539 canvas.drawRect(0, 0, 100, 100, paint); 540 541 canvas.restore(); 542 }); 543 ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op"; 544 // first clip must be preserved, even if it extends beyond canvas bounds 545 EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip); 546} 547 548TEST(RecordingCanvas, insertReorderBarrier) { 549 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 550 canvas.drawRect(0, 0, 400, 400, SkPaint()); 551 canvas.insertReorderBarrier(true); 552 canvas.insertReorderBarrier(false); 553 canvas.insertReorderBarrier(false); 554 canvas.insertReorderBarrier(true); 555 canvas.drawRect(0, 0, 400, 400, SkPaint()); 556 canvas.insertReorderBarrier(false); 557 }); 558 559 auto chunks = dl->getChunks(); 560 EXPECT_EQ(0u, chunks[0].beginOpIndex); 561 EXPECT_EQ(1u, chunks[0].endOpIndex); 562 EXPECT_FALSE(chunks[0].reorderChildren); 563 564 EXPECT_EQ(1u, chunks[1].beginOpIndex); 565 EXPECT_EQ(2u, chunks[1].endOpIndex); 566 EXPECT_TRUE(chunks[1].reorderChildren); 567} 568 569TEST(RecordingCanvas, refPaint) { 570 SkPaint paint; 571 paint.setAntiAlias(true); 572 paint.setTextSize(20); 573 paint.setTextAlign(SkPaint::kLeft_Align); 574 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 575 576 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) { 577 paint.setColor(SK_ColorBLUE); 578 // first three should use same paint 579 canvas.drawRect(0, 0, 200, 10, paint); 580 SkPaint paintCopy(paint); 581 canvas.drawRect(0, 10, 200, 20, paintCopy); 582 TestUtils::drawUtf8ToCanvas(&canvas, "helloworld", paint, 50, 25); 583 584 // only here do we use different paint ptr 585 paint.setColor(SK_ColorRED); 586 canvas.drawRect(0, 20, 200, 30, paint); 587 }); 588 auto ops = dl->getOps(); 589 ASSERT_EQ(4u, ops.size()); 590 591 // first three are the same 592 EXPECT_NE(nullptr, ops[0]->paint); 593 EXPECT_NE(&paint, ops[0]->paint); 594 EXPECT_EQ(ops[0]->paint, ops[1]->paint); 595 EXPECT_EQ(ops[0]->paint, ops[2]->paint); 596 597 // last is different, but still copied / non-null 598 EXPECT_NE(nullptr, ops[3]->paint); 599 EXPECT_NE(ops[0]->paint, ops[3]->paint); 600 EXPECT_NE(&paint, ops[3]->paint); 601} 602 603TEST(RecordingCanvas, drawText) { 604 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 605 Paint paint; 606 paint.setAntiAlias(true); 607 paint.setTextSize(20); 608 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 609 std::unique_ptr<uint16_t[]> dst = TestUtils::utf8ToUtf16("HELLO"); 610 canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL); 611 }); 612 613 int count = 0; 614 playbackOps(*dl, [&count](const RecordedOp& op) { 615 count++; 616 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 617 EXPECT_EQ(nullptr, op.localClip); 618 EXPECT_TRUE(op.localMatrix.isIdentity()); 619 EXPECT_TRUE(op.unmappedBounds.getHeight() >= 10); 620 EXPECT_TRUE(op.unmappedBounds.getWidth() >= 25); 621 }); 622 ASSERT_EQ(1, count); 623} 624 625TEST(RecordingCanvas, drawTextInHighContrast) { 626 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 627 canvas.setHighContrastText(true); 628 Paint paint; 629 paint.setColor(SK_ColorWHITE); 630 paint.setAntiAlias(true); 631 paint.setTextSize(20); 632 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 633 std::unique_ptr<uint16_t[]> dst = TestUtils::utf8ToUtf16("HELLO"); 634 canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL); 635 }); 636 637 int count = 0; 638 playbackOps(*dl, [&count](const RecordedOp& op) { 639 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 640 if (count++ == 0) { 641 EXPECT_EQ(SK_ColorBLACK, op.paint->getColor()); 642 EXPECT_EQ(SkPaint::kStrokeAndFill_Style, op.paint->getStyle()); 643 } else { 644 EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); 645 EXPECT_EQ(SkPaint::kFill_Style, op.paint->getStyle()); 646 } 647 648 }); 649 ASSERT_EQ(2, count); 650} 651 652} // namespace uirenderer 653} // namespace android 654