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