RecordingCanvasTests.cpp revision b87eadda1818034ce03d85f30388384d1ac65916
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 <tests/common/TestUtils.h> 22 23namespace android { 24namespace uirenderer { 25 26static void playbackOps(const DisplayList& displayList, 27 std::function<void(const RecordedOp&)> opReceiver) { 28 for (auto& chunk : displayList.getChunks()) { 29 for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { 30 RecordedOp* op = displayList.getOps()[opIndex]; 31 opReceiver(*op); 32 } 33 } 34} 35 36#define EXPECT_CLIP_RECT(expRect, clipStatePtr) \ 37 EXPECT_NE(nullptr, (clipStatePtr)) << "Op is unclipped"; \ 38 if ((clipStatePtr)->mode == ClipMode::Rectangle) { \ 39 EXPECT_EQ((expRect), reinterpret_cast<const ClipRect*>(clipStatePtr)->rect); \ 40 } else { \ 41 ADD_FAILURE() << "ClipState not a rect"; \ 42 } 43 44TEST(RecordingCanvas, emptyPlayback) { 45 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 46 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 47 canvas.restore(); 48 }); 49 playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); }); 50} 51 52TEST(RecordingCanvas, clipRect) { 53 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { 54 canvas.save(SkCanvas::kMatrixClip_SaveFlag); 55 canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op); 56 canvas.drawRect(0, 0, 50, 50, SkPaint()); 57 canvas.drawRect(50, 50, 100, 100, SkPaint()); 58 canvas.restore(); 59 }); 60 61 ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops"; 62 EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip); 63 EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip); 64 EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip) 65 << "Clip should be serialized once"; 66} 67 68TEST(RecordingCanvas, drawLines) { 69 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 70 SkPaint paint; 71 paint.setStrokeWidth(20); // doesn't affect recorded bounds - would be resolved at bake time 72 float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line 73 canvas.drawLines(&points[0], 7, paint); 74 }); 75 76 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 77 auto op = dl->getOps()[0]; 78 ASSERT_EQ(RecordedOpId::LinesOp, op->opId); 79 EXPECT_EQ(4, ((LinesOp*)op)->floatCount) 80 << "float count must be rounded down to closest multiple of 4"; 81 EXPECT_EQ(Rect(20, 10), op->unmappedBounds) 82 << "unmapped bounds must be size of line, and not outset for stroke width"; 83} 84 85TEST(RecordingCanvas, drawRect) { 86 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 87 canvas.drawRect(10, 20, 90, 180, SkPaint()); 88 }); 89 90 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 91 auto op = *(dl->getOps()[0]); 92 ASSERT_EQ(RecordedOpId::RectOp, op.opId); 93 EXPECT_EQ(nullptr, op.localClip); 94 EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds); 95} 96 97TEST(RecordingCanvas, drawText) { 98 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 99 SkPaint paint; 100 paint.setAntiAlias(true); 101 paint.setTextSize(20); 102 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 103 TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); 104 }); 105 106 int count = 0; 107 playbackOps(*dl, [&count](const RecordedOp& op) { 108 count++; 109 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 110 EXPECT_EQ(nullptr, op.localClip); 111 EXPECT_TRUE(op.localMatrix.isIdentity()); 112 EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25)) 113 << "Op expected to be 25+ pixels wide, 10+ pixels tall"; 114 }); 115 ASSERT_EQ(1, count); 116} 117 118TEST(RecordingCanvas, drawText_strikeThruAndUnderline) { 119 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 120 SkPaint paint; 121 paint.setAntiAlias(true); 122 paint.setTextSize(20); 123 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 124 for (int i = 0; i < 2; i++) { 125 for (int j = 0; j < 2; j++) { 126 paint.setUnderlineText(i != 0); 127 paint.setStrikeThruText(j != 0); 128 TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); 129 } 130 } 131 }); 132 133 auto ops = dl->getOps(); 134 ASSERT_EQ(8u, ops.size()); 135 136 int index = 0; 137 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough 138 139 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 140 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only 141 142 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 143 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only 144 145 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 146 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline 147 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough 148} 149 150TEST(RecordingCanvas, drawText_forceAlignLeft) { 151 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 152 SkPaint paint; 153 paint.setAntiAlias(true); 154 paint.setTextSize(20); 155 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 156 paint.setTextAlign(SkPaint::kLeft_Align); 157 TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); 158 paint.setTextAlign(SkPaint::kCenter_Align); 159 TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); 160 paint.setTextAlign(SkPaint::kRight_Align); 161 TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); 162 }); 163 164 int count = 0; 165 float lastX = FLT_MAX; 166 playbackOps(*dl, [&count, &lastX](const RecordedOp& op) { 167 count++; 168 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 169 EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign()) 170 << "recorded drawText commands must force kLeft_Align on their paint"; 171 172 // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class) 173 EXPECT_GT(lastX, ((const TextOp&)op).x) 174 << "x coordinate should reduce across each of the draw commands, from alignment"; 175 lastX = ((const TextOp&)op).x; 176 }); 177 ASSERT_EQ(3, count); 178} 179 180TEST(RecordingCanvas, backgroundAndImage) { 181 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 182 SkBitmap bitmap; 183 bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25)); 184 SkPaint paint; 185 paint.setColor(SK_ColorBLUE); 186 187 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 188 { 189 // a background! 190 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 191 canvas.drawRect(0, 0, 100, 200, paint); 192 canvas.restore(); 193 } 194 { 195 // an image! 196 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 197 canvas.translate(25, 25); 198 canvas.scale(2, 2); 199 canvas.drawBitmap(bitmap, 0, 0, nullptr); 200 canvas.restore(); 201 } 202 canvas.restore(); 203 }); 204 205 int count = 0; 206 playbackOps(*dl, [&count](const RecordedOp& op) { 207 if (count == 0) { 208 ASSERT_EQ(RecordedOpId::RectOp, op.opId); 209 ASSERT_NE(nullptr, op.paint); 210 EXPECT_EQ(SK_ColorBLUE, op.paint->getColor()); 211 EXPECT_EQ(Rect(100, 200), op.unmappedBounds); 212 EXPECT_EQ(nullptr, op.localClip); 213 214 Matrix4 expectedMatrix; 215 expectedMatrix.loadIdentity(); 216 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 217 } else { 218 ASSERT_EQ(RecordedOpId::BitmapOp, op.opId); 219 EXPECT_EQ(nullptr, op.paint); 220 EXPECT_EQ(Rect(25, 25), op.unmappedBounds); 221 EXPECT_EQ(nullptr, op.localClip); 222 223 Matrix4 expectedMatrix; 224 expectedMatrix.loadTranslate(25, 25, 0); 225 expectedMatrix.scale(2, 2, 1); 226 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 227 } 228 count++; 229 }); 230 ASSERT_EQ(2, count); 231} 232 233TEST(RecordingCanvas, saveLayer_simple) { 234 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 235 canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kClipToLayer_SaveFlag); 236 canvas.drawRect(10, 20, 190, 180, SkPaint()); 237 canvas.restore(); 238 }); 239 int count = 0; 240 playbackOps(*dl, [&count](const RecordedOp& op) { 241 Matrix4 expectedMatrix; 242 switch(count++) { 243 case 0: 244 EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId); 245 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 246 EXPECT_EQ(nullptr, op.localClip); 247 EXPECT_TRUE(op.localMatrix.isIdentity()); 248 break; 249 case 1: 250 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 251 EXPECT_CLIP_RECT(Rect(180, 160), op.localClip); 252 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 253 expectedMatrix.loadTranslate(-10, -20, 0); 254 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 255 break; 256 case 2: 257 EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); 258 // Don't bother asserting recording state data - it's not used 259 break; 260 default: 261 ADD_FAILURE(); 262 } 263 }); 264 EXPECT_EQ(3, count); 265} 266 267TEST(RecordingCanvas, saveLayer_missingRestore) { 268 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 269 canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag); 270 canvas.drawRect(0, 0, 200, 200, SkPaint()); 271 // Note: restore omitted, shouldn't result in unmatched save 272 }); 273 int count = 0; 274 playbackOps(*dl, [&count](const RecordedOp& op) { 275 if (count++ == 2) { 276 EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); 277 } 278 }); 279 EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer"; 280} 281 282TEST(RecordingCanvas, saveLayer_simpleUnclipped) { 283 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 284 canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SkCanvas::SaveFlags)0); // unclipped 285 canvas.drawRect(10, 20, 190, 180, SkPaint()); 286 canvas.restore(); 287 }); 288 int count = 0; 289 playbackOps(*dl, [&count](const RecordedOp& op) { 290 switch(count++) { 291 case 0: 292 EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId); 293 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 294 EXPECT_EQ(nullptr, op.localClip); 295 EXPECT_TRUE(op.localMatrix.isIdentity()); 296 break; 297 case 1: 298 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 299 EXPECT_EQ(nullptr, op.localClip); 300 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 301 EXPECT_TRUE(op.localMatrix.isIdentity()); 302 break; 303 case 2: 304 EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId); 305 // Don't bother asserting recording state data - it's not used 306 break; 307 default: 308 ADD_FAILURE(); 309 } 310 }); 311 EXPECT_EQ(3, count); 312} 313 314TEST(RecordingCanvas, saveLayer_addClipFlag) { 315 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 316 canvas.save(SkCanvas::kMatrixClip_SaveFlag); 317 canvas.clipRect(10, 20, 190, 180, SkRegion::kIntersect_Op); 318 canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SkCanvas::SaveFlags)0); // unclipped 319 canvas.drawRect(10, 20, 190, 180, SkPaint()); 320 canvas.restore(); 321 canvas.restore(); 322 }); 323 int count = 0; 324 playbackOps(*dl, [&count](const RecordedOp& op) { 325 if (count++ == 0) { 326 EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId) 327 << "Clip + unclipped saveLayer should result in a clipped layer"; 328 } 329 }); 330 EXPECT_EQ(3, count); 331} 332 333TEST(RecordingCanvas, saveLayer_viewportCrop) { 334 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 335 // shouldn't matter, since saveLayer will clip to its bounds 336 canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op); 337 338 canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::kClipToLayer_SaveFlag); 339 canvas.drawRect(0, 0, 400, 400, SkPaint()); 340 canvas.restore(); 341 }); 342 int count = 0; 343 playbackOps(*dl, [&count](const RecordedOp& op) { 344 if (count++ == 1) { 345 Matrix4 expectedMatrix; 346 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 347 EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be 348 // intersection of viewport and saveLayer bounds, in layer space; 349 EXPECT_EQ(Rect(400, 400), op.unmappedBounds); 350 expectedMatrix.loadTranslate(-100, -100, 0); 351 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 352 } 353 }); 354 EXPECT_EQ(3, count); 355} 356 357TEST(RecordingCanvas, saveLayer_rotateUnclipped) { 358 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 359 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 360 canvas.translate(100, 100); 361 canvas.rotate(45); 362 canvas.translate(-50, -50); 363 364 canvas.saveLayerAlpha(0, 0, 100, 100, 128, SkCanvas::kClipToLayer_SaveFlag); 365 canvas.drawRect(0, 0, 100, 100, SkPaint()); 366 canvas.restore(); 367 368 canvas.restore(); 369 }); 370 int count = 0; 371 playbackOps(*dl, [&count](const RecordedOp& op) { 372 if (count++ == 1) { 373 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 374 EXPECT_CLIP_RECT(Rect(100, 100), op.localClip); 375 EXPECT_EQ(Rect(100, 100), op.unmappedBounds); 376 EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix) 377 << "Recorded op shouldn't see any canvas transform before the saveLayer"; 378 } 379 }); 380 EXPECT_EQ(3, count); 381} 382 383TEST(RecordingCanvas, saveLayer_rotateClipped) { 384 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 385 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 386 canvas.translate(100, 100); 387 canvas.rotate(45); 388 canvas.translate(-200, -200); 389 390 // area of saveLayer will be clipped to parent viewport, so we ask for 400x400... 391 canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag); 392 canvas.drawRect(0, 0, 400, 400, SkPaint()); 393 canvas.restore(); 394 395 canvas.restore(); 396 }); 397 int count = 0; 398 playbackOps(*dl, [&count](const RecordedOp& op) { 399 if (count++ == 1) { 400 Matrix4 expectedMatrix; 401 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 402 403 // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by 404 // the parent 200x200 viewport, but prior to rotation 405 ASSERT_NE(nullptr, op.localClip); 406 ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode); 407 // NOTE: this check relies on saveLayer altering the clip post-viewport init. This 408 // causes the clip to be recorded by contained draw commands, though it's not necessary 409 // since the same clip will be computed at draw time. If such a change is made, this 410 // check could be done at record time by querying the clip, or the clip could be altered 411 // slightly so that it is serialized. 412 EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136), 413 (reinterpret_cast<const ClipRect*>(op.localClip))->rect); 414 415 EXPECT_EQ(Rect(400, 400), op.unmappedBounds); 416 expectedMatrix.loadIdentity(); 417 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 418 } 419 }); 420 EXPECT_EQ(3, count); 421} 422 423TEST(RecordingCanvas, drawRenderNode_projection) { 424 sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150, 425 [](RenderProperties& props, RecordingCanvas& canvas) { 426 SkPaint paint; 427 paint.setColor(SK_ColorWHITE); 428 canvas.drawRect(0, 0, 100, 100, paint); 429 }); 430 { 431 background->mutateStagingProperties().setProjectionReceiver(false); 432 433 // NO RECEIVER PRESENT 434 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, 435 [&background](RecordingCanvas& canvas) { 436 canvas.drawRect(0, 0, 100, 100, SkPaint()); 437 canvas.drawRenderNode(background.get()); 438 canvas.drawRect(0, 0, 100, 100, SkPaint()); 439 }); 440 EXPECT_EQ(-1, dl->projectionReceiveIndex) 441 << "no projection receiver should have been observed"; 442 } 443 { 444 background->mutateStagingProperties().setProjectionReceiver(true); 445 446 // RECEIVER PRESENT 447 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, 448 [&background](RecordingCanvas& canvas) { 449 canvas.drawRect(0, 0, 100, 100, SkPaint()); 450 canvas.drawRenderNode(background.get()); 451 canvas.drawRect(0, 0, 100, 100, SkPaint()); 452 }); 453 454 ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops"; 455 auto op = dl->getOps()[1]; 456 EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId); 457 EXPECT_EQ(1, dl->projectionReceiveIndex) 458 << "correct projection receiver not identified"; 459 460 // verify the behavior works even though projection receiver hasn't been sync'd yet 461 EXPECT_TRUE(background->stagingProperties().isProjectionReceiver()); 462 EXPECT_FALSE(background->properties().isProjectionReceiver()); 463 } 464} 465 466TEST(RecordingCanvas, insertReorderBarrier) { 467 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 468 canvas.drawRect(0, 0, 400, 400, SkPaint()); 469 canvas.insertReorderBarrier(true); 470 canvas.insertReorderBarrier(false); 471 canvas.insertReorderBarrier(false); 472 canvas.insertReorderBarrier(true); 473 canvas.drawRect(0, 0, 400, 400, SkPaint()); 474 canvas.insertReorderBarrier(false); 475 }); 476 477 auto chunks = dl->getChunks(); 478 EXPECT_EQ(0u, chunks[0].beginOpIndex); 479 EXPECT_EQ(1u, chunks[0].endOpIndex); 480 EXPECT_FALSE(chunks[0].reorderChildren); 481 482 EXPECT_EQ(1u, chunks[1].beginOpIndex); 483 EXPECT_EQ(2u, chunks[1].endOpIndex); 484 EXPECT_TRUE(chunks[1].reorderChildren); 485} 486 487TEST(RecordingCanvas, refPaint) { 488 SkPaint paint; 489 paint.setAntiAlias(true); 490 paint.setTextSize(20); 491 paint.setTextAlign(SkPaint::kLeft_Align); 492 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 493 494 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) { 495 paint.setColor(SK_ColorBLUE); 496 // first three should use same paint 497 canvas.drawRect(0, 0, 200, 10, paint); 498 SkPaint paintCopy(paint); 499 canvas.drawRect(0, 10, 200, 20, paintCopy); 500 TestUtils::drawTextToCanvas(&canvas, "helloworld", paint, 50, 25); 501 502 // only here do we use different paint ptr 503 paint.setColor(SK_ColorRED); 504 canvas.drawRect(0, 20, 200, 30, paint); 505 }); 506 auto ops = dl->getOps(); 507 ASSERT_EQ(4u, ops.size()); 508 509 // first three are the same 510 EXPECT_NE(nullptr, ops[0]->paint); 511 EXPECT_NE(&paint, ops[0]->paint); 512 EXPECT_EQ(ops[0]->paint, ops[1]->paint); 513 EXPECT_EQ(ops[0]->paint, ops[2]->paint); 514 515 // last is different, but still copied / non-null 516 EXPECT_NE(nullptr, ops[3]->paint); 517 EXPECT_NE(ops[0]->paint, ops[3]->paint); 518 EXPECT_NE(&paint, ops[3]->paint); 519} 520 521} // namespace uirenderer 522} // namespace android 523