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