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