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