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