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