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