RecordingCanvasTests.cpp revision 8160f20b0aca8c6595d4b385d673f59b6bcd16a4
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 36TEST(RecordingCanvas, emptyPlayback) { 37 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 38 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 39 canvas.restore(); 40 }); 41 playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); }); 42} 43 44TEST(RecordingCanvas, drawLines) { 45 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 46 SkPaint paint; 47 paint.setStrokeWidth(20); 48 float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line 49 canvas.drawLines(&points[0], 7, paint); 50 }); 51 52 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 53 auto op = dl->getOps()[0]; 54 ASSERT_EQ(RecordedOpId::LinesOp, op->opId); 55 EXPECT_EQ(4, ((LinesOp*)op)->floatCount) 56 << "float count must be rounded down to closest multiple of 4"; 57 EXPECT_EQ(Rect(-10, -10, 30, 20), op->unmappedBounds) 58 << "unmapped bounds must be size of line, outset by 1/2 stroke width"; 59} 60 61TEST(RecordingCanvas, drawRect) { 62 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 63 canvas.drawRect(10, 20, 90, 180, SkPaint()); 64 }); 65 66 ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; 67 auto op = *(dl->getOps()[0]); 68 ASSERT_EQ(RecordedOpId::RectOp, op.opId); 69 EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect); 70 EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds); 71} 72 73TEST(RecordingCanvas, drawText) { 74 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 75 SkPaint paint; 76 paint.setAntiAlias(true); 77 paint.setTextSize(20); 78 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 79 TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); 80 }); 81 82 int count = 0; 83 playbackOps(*dl, [&count](const RecordedOp& op) { 84 count++; 85 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 86 EXPECT_EQ(Rect(0, 0, 200, 200), op.localClipRect); 87 EXPECT_TRUE(op.localMatrix.isIdentity()); 88 EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25)) 89 << "Op expected to be 25+ pixels wide, 10+ pixels tall"; 90 }); 91 ASSERT_EQ(1, count); 92} 93 94TEST(RecordingCanvas, drawText_strikeThruAndUnderline) { 95 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 96 SkPaint paint; 97 paint.setAntiAlias(true); 98 paint.setTextSize(20); 99 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 100 for (int i = 0; i < 2; i++) { 101 for (int j = 0; j < 2; j++) { 102 paint.setUnderlineText(i != 0); 103 paint.setStrikeThruText(j != 0); 104 TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); 105 } 106 } 107 }); 108 109 auto ops = dl->getOps(); 110 ASSERT_EQ(8u, ops.size()); 111 112 int index = 0; 113 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough 114 115 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 116 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only 117 118 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 119 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only 120 121 EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); 122 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline 123 EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough 124} 125 126TEST(RecordingCanvas, drawText_forceAlignLeft) { 127 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 128 SkPaint paint; 129 paint.setAntiAlias(true); 130 paint.setTextSize(20); 131 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 132 paint.setTextAlign(SkPaint::kLeft_Align); 133 TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); 134 paint.setTextAlign(SkPaint::kCenter_Align); 135 TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); 136 paint.setTextAlign(SkPaint::kRight_Align); 137 TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); 138 }); 139 140 int count = 0; 141 float lastX = FLT_MAX; 142 playbackOps(*dl, [&count, &lastX](const RecordedOp& op) { 143 count++; 144 ASSERT_EQ(RecordedOpId::TextOp, op.opId); 145 EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign()) 146 << "recorded drawText commands must force kLeft_Align on their paint"; 147 148 // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class) 149 EXPECT_GT(lastX, ((const TextOp&)op).x) 150 << "x coordinate should reduce across each of the draw commands, from alignment"; 151 lastX = ((const TextOp&)op).x; 152 }); 153 ASSERT_EQ(3, count); 154} 155 156TEST(RecordingCanvas, backgroundAndImage) { 157 auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { 158 SkBitmap bitmap; 159 bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25)); 160 SkPaint paint; 161 paint.setColor(SK_ColorBLUE); 162 163 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 164 { 165 // a background! 166 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 167 canvas.drawRect(0, 0, 100, 200, paint); 168 canvas.restore(); 169 } 170 { 171 // an image! 172 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 173 canvas.translate(25, 25); 174 canvas.scale(2, 2); 175 canvas.drawBitmap(bitmap, 0, 0, nullptr); 176 canvas.restore(); 177 } 178 canvas.restore(); 179 }); 180 181 int count = 0; 182 playbackOps(*dl, [&count](const RecordedOp& op) { 183 if (count == 0) { 184 ASSERT_EQ(RecordedOpId::RectOp, op.opId); 185 ASSERT_NE(nullptr, op.paint); 186 EXPECT_EQ(SK_ColorBLUE, op.paint->getColor()); 187 EXPECT_EQ(Rect(0, 0, 100, 200), op.unmappedBounds); 188 EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect); 189 190 Matrix4 expectedMatrix; 191 expectedMatrix.loadIdentity(); 192 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 193 } else { 194 ASSERT_EQ(RecordedOpId::BitmapOp, op.opId); 195 EXPECT_EQ(nullptr, op.paint); 196 EXPECT_EQ(Rect(0, 0, 25, 25), op.unmappedBounds); 197 EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect); 198 199 Matrix4 expectedMatrix; 200 expectedMatrix.loadTranslate(25, 25, 0); 201 expectedMatrix.scale(2, 2, 1); 202 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 203 } 204 count++; 205 }); 206 ASSERT_EQ(2, count); 207} 208 209TEST(RecordingCanvas, saveLayer_simple) { 210 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 211 canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kARGB_ClipLayer_SaveFlag); 212 canvas.drawRect(10, 20, 190, 180, SkPaint()); 213 canvas.restore(); 214 }); 215 int count = 0; 216 playbackOps(*dl, [&count](const RecordedOp& op) { 217 Matrix4 expectedMatrix; 218 switch(count++) { 219 case 0: 220 EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId); 221 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 222 EXPECT_EQ(Rect(0, 0, 200, 200), op.localClipRect); 223 EXPECT_TRUE(op.localMatrix.isIdentity()); 224 break; 225 case 1: 226 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 227 EXPECT_EQ(Rect(0, 0, 180, 160), op.localClipRect); 228 EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); 229 expectedMatrix.loadTranslate(-10, -20, 0); 230 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 231 break; 232 case 2: 233 EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); 234 // Don't bother asserting recording state data - it's not used 235 break; 236 default: 237 ADD_FAILURE(); 238 } 239 }); 240 EXPECT_EQ(3, count); 241} 242 243TEST(RecordingCanvas, saveLayer_viewportCrop) { 244 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 245 // shouldn't matter, since saveLayer will clip to its bounds 246 canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op); 247 248 canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::kARGB_ClipLayer_SaveFlag); 249 canvas.drawRect(0, 0, 400, 400, SkPaint()); 250 canvas.restore(); 251 }); 252 int count = 0; 253 playbackOps(*dl, [&count](const RecordedOp& op) { 254 if (count++ == 1) { 255 Matrix4 expectedMatrix; 256 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 257 EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect) << "Recorded clip rect should be" 258 " intersection of viewport and saveLayer bounds, in layer space"; 259 EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); 260 expectedMatrix.loadTranslate(-100, -100, 0); 261 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 262 } 263 }); 264 EXPECT_EQ(3, count); 265} 266 267TEST(RecordingCanvas, saveLayer_rotateUnclipped) { 268 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 269 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 270 canvas.translate(100, 100); 271 canvas.rotate(45); 272 canvas.translate(-50, -50); 273 274 canvas.saveLayerAlpha(0, 0, 100, 100, 128, SkCanvas::kARGB_ClipLayer_SaveFlag); 275 canvas.drawRect(0, 0, 100, 100, SkPaint()); 276 canvas.restore(); 277 278 canvas.restore(); 279 }); 280 int count = 0; 281 playbackOps(*dl, [&count](const RecordedOp& op) { 282 if (count++ == 1) { 283 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 284 EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect); 285 EXPECT_EQ(Rect(0, 0, 100, 100), op.unmappedBounds); 286 EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix) 287 << "Recorded op shouldn't see any canvas transform before the saveLayer"; 288 } 289 }); 290 EXPECT_EQ(3, count); 291} 292 293TEST(RecordingCanvas, saveLayer_rotateClipped) { 294 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 295 canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); 296 canvas.translate(100, 100); 297 canvas.rotate(45); 298 canvas.translate(-200, -200); 299 300 // area of saveLayer will be clipped to parent viewport, so we ask for 400x400... 301 canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kARGB_ClipLayer_SaveFlag); 302 canvas.drawRect(0, 0, 400, 400, SkPaint()); 303 canvas.restore(); 304 305 canvas.restore(); 306 }); 307 int count = 0; 308 playbackOps(*dl, [&count](const RecordedOp& op) { 309 if (count++ == 1) { 310 Matrix4 expectedMatrix; 311 EXPECT_EQ(RecordedOpId::RectOp, op.opId); 312 313 // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by 314 // the parent 200x200 viewport, but prior to rotation 315 EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136), op.localClipRect); 316 EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); 317 expectedMatrix.loadIdentity(); 318 EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); 319 } 320 }); 321 EXPECT_EQ(3, count); 322} 323 324TEST(RecordingCanvas, drawRenderNode_projection) { 325 sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150, 326 [](RenderProperties& props, RecordingCanvas& canvas) { 327 SkPaint paint; 328 paint.setColor(SK_ColorWHITE); 329 canvas.drawRect(0, 0, 100, 100, paint); 330 }); 331 { 332 background->mutateStagingProperties().setProjectionReceiver(false); 333 334 // NO RECEIVER PRESENT 335 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, 336 [&background](RecordingCanvas& canvas) { 337 canvas.drawRect(0, 0, 100, 100, SkPaint()); 338 canvas.drawRenderNode(background.get()); 339 canvas.drawRect(0, 0, 100, 100, SkPaint()); 340 }); 341 EXPECT_EQ(-1, dl->projectionReceiveIndex) 342 << "no projection receiver should have been observed"; 343 } 344 { 345 background->mutateStagingProperties().setProjectionReceiver(true); 346 347 // RECEIVER PRESENT 348 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, 349 [&background](RecordingCanvas& canvas) { 350 canvas.drawRect(0, 0, 100, 100, SkPaint()); 351 canvas.drawRenderNode(background.get()); 352 canvas.drawRect(0, 0, 100, 100, SkPaint()); 353 }); 354 355 ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops"; 356 auto op = dl->getOps()[1]; 357 EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId); 358 EXPECT_EQ(1, dl->projectionReceiveIndex) 359 << "correct projection receiver not identified"; 360 361 // verify the behavior works even though projection receiver hasn't been sync'd yet 362 EXPECT_TRUE(background->stagingProperties().isProjectionReceiver()); 363 EXPECT_FALSE(background->properties().isProjectionReceiver()); 364 } 365} 366 367TEST(RecordingCanvas, insertReorderBarrier) { 368 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { 369 canvas.drawRect(0, 0, 400, 400, SkPaint()); 370 canvas.insertReorderBarrier(true); 371 canvas.insertReorderBarrier(false); 372 canvas.insertReorderBarrier(false); 373 canvas.insertReorderBarrier(true); 374 canvas.drawRect(0, 0, 400, 400, SkPaint()); 375 canvas.insertReorderBarrier(false); 376 }); 377 378 auto chunks = dl->getChunks(); 379 EXPECT_EQ(0u, chunks[0].beginOpIndex); 380 EXPECT_EQ(1u, chunks[0].endOpIndex); 381 EXPECT_FALSE(chunks[0].reorderChildren); 382 383 EXPECT_EQ(1u, chunks[1].beginOpIndex); 384 EXPECT_EQ(2u, chunks[1].endOpIndex); 385 EXPECT_TRUE(chunks[1].reorderChildren); 386} 387 388TEST(RecordingCanvas, refPaint) { 389 SkPaint paint; 390 paint.setAntiAlias(true); 391 paint.setTextSize(20); 392 paint.setTextAlign(SkPaint::kLeft_Align); 393 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 394 395 auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) { 396 paint.setColor(SK_ColorBLUE); 397 // first three should use same paint 398 canvas.drawRect(0, 0, 200, 10, paint); 399 SkPaint paintCopy(paint); 400 canvas.drawRect(0, 10, 200, 20, paintCopy); 401 TestUtils::drawTextToCanvas(&canvas, "helloworld", paint, 50, 25); 402 403 // only here do we use different paint ptr 404 paint.setColor(SK_ColorRED); 405 canvas.drawRect(0, 20, 200, 30, paint); 406 }); 407 auto ops = dl->getOps(); 408 ASSERT_EQ(4u, ops.size()); 409 410 // first three are the same 411 EXPECT_NE(nullptr, ops[0]->paint); 412 EXPECT_NE(&paint, ops[0]->paint); 413 EXPECT_EQ(ops[0]->paint, ops[1]->paint); 414 EXPECT_EQ(ops[0]->paint, ops[2]->paint); 415 416 // last is different, but still copied / non-null 417 EXPECT_NE(nullptr, ops[3]->paint); 418 EXPECT_NE(ops[0]->paint, ops[3]->paint); 419 EXPECT_NE(&paint, ops[3]->paint); 420} 421 422} // namespace uirenderer 423} // namespace android 424