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
playbackOps(const DisplayList & displayList,std::function<void (const RecordedOp &)> opReceiver)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
validateSingleOp(std::unique_ptr<DisplayList> & dl,std::function<void (const RecordedOp & op)> opValidator)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
OPENGL_PIPELINE_TEST(RecordingCanvas,emptyPlayback)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
OPENGL_PIPELINE_TEST(RecordingCanvas,clipRect)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
OPENGL_PIPELINE_TEST(RecordingCanvas,emptyClipRect)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
OPENGL_PIPELINE_TEST(RecordingCanvas,emptyPaintRejection)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawArc)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawLines)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawRect)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawRoundRect)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawGlyphs)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawGlyphs_strikeThruAndUnderline)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawGlyphs_forceAlignLeft)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawColor)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
OPENGL_PIPELINE_TEST(RecordingCanvas,backgroundAndImage)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
RENDERTHREAD_OPENGL_PIPELINE_TEST(RecordingCanvas,textureLayer)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
OPENGL_PIPELINE_TEST(RecordingCanvas,saveLayer_simple)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
OPENGL_PIPELINE_TEST(RecordingCanvas,saveLayer_rounding)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
OPENGL_PIPELINE_TEST(RecordingCanvas,saveLayer_missingRestore)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
OPENGL_PIPELINE_TEST(RecordingCanvas,saveLayer_simpleUnclipped)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
OPENGL_PIPELINE_TEST(RecordingCanvas,saveLayer_addClipFlag)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
OPENGL_PIPELINE_TEST(RecordingCanvas,saveLayer_viewportCrop)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
OPENGL_PIPELINE_TEST(RecordingCanvas,saveLayer_rotateUnclipped)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
OPENGL_PIPELINE_TEST(RecordingCanvas,saveLayer_rotateClipped)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
OPENGL_PIPELINE_TEST(RecordingCanvas,saveLayer_rejectBegin)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawRenderNode_rejection)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawRenderNode_projection)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
OPENGL_PIPELINE_TEST(RecordingCanvas,firstClipWillReplace)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
OPENGL_PIPELINE_TEST(RecordingCanvas,replaceClipIntersectWithRoot)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
OPENGL_PIPELINE_TEST(RecordingCanvas,insertReorderBarrier)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
OPENGL_PIPELINE_TEST(RecordingCanvas,insertReorderBarrier_clip)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
OPENGL_PIPELINE_TEST(RecordingCanvas,refPaint)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
OPENGL_PIPELINE_TEST(RecordingCanvas,refBitmap)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
OPENGL_PIPELINE_TEST(RecordingCanvas,refBitmapInShader_bitmapShader)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
OPENGL_PIPELINE_TEST(RecordingCanvas,refBitmapInShader_composeShader)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawText)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
OPENGL_PIPELINE_TEST(RecordingCanvas,drawTextInHighContrast)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