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