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