RecordingCanvasTests.cpp revision f77ca0872102116f58693d26703af8279573d014
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
31namespace android {
32namespace uirenderer {
33
34static 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
44static 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
56OPENGL_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
64OPENGL_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
80OPENGL_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
91OPENGL_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
112OPENGL_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
128OPENGL_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
145OPENGL_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
157OPENGL_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
174OPENGL_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
195OPENGL_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) flags |= SkPaint::kUnderlineText_ReserveFlag;
205                if (j != 0) flags |= SkPaint::kStrikeThruText_ReserveFlag;
206                paint.setFlags(flags);
207                TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
208            }
209        }
210    });
211
212    auto ops = dl->getOps();
213    ASSERT_EQ(8u, ops.size());
214
215    int index = 0;
216    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough
217
218    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
219    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only
220
221    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
222    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only
223
224    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
225    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline
226    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
227}
228
229OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) {
230    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
231        SkPaint paint;
232        paint.setAntiAlias(true);
233        paint.setTextSize(20);
234        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
235        paint.setTextAlign(SkPaint::kLeft_Align);
236        TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
237        paint.setTextAlign(SkPaint::kCenter_Align);
238        TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
239        paint.setTextAlign(SkPaint::kRight_Align);
240        TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
241    });
242
243    int count = 0;
244    float lastX = FLT_MAX;
245    playbackOps(*dl, [&count, &lastX](const RecordedOp& op) {
246        count++;
247        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
248        EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
249                << "recorded drawText commands must force kLeft_Align on their paint";
250
251        // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class)
252        EXPECT_GT(lastX, ((const TextOp&)op).x)
253                << "x coordinate should reduce across each of the draw commands, from alignment";
254        lastX = ((const TextOp&)op).x;
255    });
256    ASSERT_EQ(3, count);
257}
258
259OPENGL_PIPELINE_TEST(RecordingCanvas, drawColor) {
260    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
261        canvas.drawColor(Color::Black, SkBlendMode::kSrcOver);
262    });
263
264    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
265    auto op = *(dl->getOps()[0]);
266    EXPECT_EQ(RecordedOpId::ColorOp, op.opId);
267    EXPECT_EQ(nullptr, op.localClip);
268    EXPECT_TRUE(op.unmappedBounds.isEmpty()) << "Expect undefined recorded bounds";
269}
270
271OPENGL_PIPELINE_TEST(RecordingCanvas, backgroundAndImage) {
272    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
273        sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25));
274        SkPaint paint;
275        paint.setColor(SK_ColorBLUE);
276
277        canvas.save(SaveFlags::MatrixClip);
278        {
279            // a background!
280            canvas.save(SaveFlags::MatrixClip);
281            canvas.drawRect(0, 0, 100, 200, paint);
282            canvas.restore();
283        }
284        {
285            // an image!
286            canvas.save(SaveFlags::MatrixClip);
287            canvas.translate(25, 25);
288            canvas.scale(2, 2);
289            canvas.drawBitmap(*bitmap, 0, 0, nullptr);
290            canvas.restore();
291        }
292        canvas.restore();
293    });
294
295    int count = 0;
296    playbackOps(*dl, [&count](const RecordedOp& op) {
297        if (count == 0) {
298            ASSERT_EQ(RecordedOpId::RectOp, op.opId);
299            ASSERT_NE(nullptr, op.paint);
300            EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
301            EXPECT_EQ(Rect(100, 200), op.unmappedBounds);
302            EXPECT_EQ(nullptr, op.localClip);
303
304            Matrix4 expectedMatrix;
305            expectedMatrix.loadIdentity();
306            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
307        } else {
308            ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
309            EXPECT_EQ(nullptr, op.paint);
310            EXPECT_EQ(Rect(25, 25), op.unmappedBounds);
311            EXPECT_EQ(nullptr, op.localClip);
312
313            Matrix4 expectedMatrix;
314            expectedMatrix.loadTranslate(25, 25, 0);
315            expectedMatrix.scale(2, 2, 1);
316            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
317        }
318        count++;
319    });
320    ASSERT_EQ(2, count);
321}
322
323RENDERTHREAD_OPENGL_PIPELINE_TEST(RecordingCanvas, textureLayer) {
324    auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100,
325            SkMatrix::MakeTrans(5, 5));
326
327    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
328            [&layerUpdater](RecordingCanvas& canvas) {
329        canvas.drawLayer(layerUpdater.get());
330    });
331
332    validateSingleOp(dl, [] (const RecordedOp& op) {
333        ASSERT_EQ(RecordedOpId::TextureLayerOp, op.opId);
334        ASSERT_TRUE(op.localMatrix.isIdentity()) << "Op must not apply matrix at record time.";
335    });
336}
337
338OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_simple) {
339    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
340        canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer);
341        canvas.drawRect(10, 20, 190, 180, SkPaint());
342        canvas.restore();
343    });
344    int count = 0;
345    playbackOps(*dl, [&count](const RecordedOp& op) {
346        Matrix4 expectedMatrix;
347        switch(count++) {
348        case 0:
349            EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
350            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
351            EXPECT_EQ(nullptr, op.localClip);
352            EXPECT_TRUE(op.localMatrix.isIdentity());
353            break;
354        case 1:
355            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
356            EXPECT_CLIP_RECT(Rect(180, 160), op.localClip);
357            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
358            expectedMatrix.loadTranslate(-10, -20, 0);
359            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
360            break;
361        case 2:
362            EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
363            // Don't bother asserting recording state data - it's not used
364            break;
365        default:
366            ADD_FAILURE();
367        }
368    });
369    EXPECT_EQ(3, count);
370}
371
372OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rounding) {
373    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
374            canvas.saveLayerAlpha(10.25f, 10.75f, 89.25f, 89.75f, 128, SaveFlags::ClipToLayer);
375            canvas.drawRect(20, 20, 80, 80, SkPaint());
376            canvas.restore();
377        });
378        int count = 0;
379        playbackOps(*dl, [&count](const RecordedOp& op) {
380            Matrix4 expectedMatrix;
381            switch(count++) {
382            case 0:
383                EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
384                EXPECT_EQ(Rect(10, 10, 90, 90), op.unmappedBounds) << "Expect bounds rounded out";
385                break;
386            case 1:
387                EXPECT_EQ(RecordedOpId::RectOp, op.opId);
388                expectedMatrix.loadTranslate(-10, -10, 0);
389                EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix) << "Expect rounded offset";
390                break;
391            case 2:
392                EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
393                // Don't bother asserting recording state data - it's not used
394                break;
395            default:
396                ADD_FAILURE();
397            }
398        });
399        EXPECT_EQ(3, count);
400}
401
402OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_missingRestore) {
403    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
404        canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
405        canvas.drawRect(0, 0, 200, 200, SkPaint());
406        // Note: restore omitted, shouldn't result in unmatched save
407    });
408    int count = 0;
409    playbackOps(*dl, [&count](const RecordedOp& op) {
410        if (count++ == 2) {
411            EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
412        }
413    });
414    EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer";
415}
416
417OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_simpleUnclipped) {
418    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
419        canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
420        canvas.drawRect(10, 20, 190, 180, SkPaint());
421        canvas.restore();
422    });
423    int count = 0;
424    playbackOps(*dl, [&count](const RecordedOp& op) {
425        switch(count++) {
426        case 0:
427            EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId);
428            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
429            EXPECT_EQ(nullptr, op.localClip);
430            EXPECT_TRUE(op.localMatrix.isIdentity());
431            break;
432        case 1:
433            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
434            EXPECT_EQ(nullptr, op.localClip);
435            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
436            EXPECT_TRUE(op.localMatrix.isIdentity());
437            break;
438        case 2:
439            EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId);
440            // Don't bother asserting recording state data - it's not used
441            break;
442        default:
443            ADD_FAILURE();
444        }
445    });
446    EXPECT_EQ(3, count);
447}
448
449OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_addClipFlag) {
450    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
451        canvas.save(SaveFlags::MatrixClip);
452        canvas.clipRect(10, 20, 190, 180, SkClipOp::kIntersect);
453        canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
454        canvas.drawRect(10, 20, 190, 180, SkPaint());
455        canvas.restore();
456        canvas.restore();
457    });
458    int count = 0;
459    playbackOps(*dl, [&count](const RecordedOp& op) {
460        if (count++ == 0) {
461            EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId)
462                    << "Clip + unclipped saveLayer should result in a clipped layer";
463        }
464    });
465    EXPECT_EQ(3, count);
466}
467
468OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_viewportCrop) {
469    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
470        // shouldn't matter, since saveLayer will clip to its bounds
471        canvas.clipRect(-1000, -1000, 1000, 1000, SkClipOp::kReplace);
472
473        canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer);
474        canvas.drawRect(0, 0, 400, 400, SkPaint());
475        canvas.restore();
476    });
477    int count = 0;
478    playbackOps(*dl, [&count](const RecordedOp& op) {
479        if (count++ == 1) {
480            Matrix4 expectedMatrix;
481            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
482            EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be
483            // intersection of viewport and saveLayer bounds, in layer space;
484            EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
485            expectedMatrix.loadTranslate(-100, -100, 0);
486            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
487        }
488    });
489    EXPECT_EQ(3, count);
490}
491
492OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
493    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
494        canvas.save(SaveFlags::MatrixClip);
495        canvas.translate(100, 100);
496        canvas.rotate(45);
497        canvas.translate(-50, -50);
498
499        canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer);
500        canvas.drawRect(0, 0, 100, 100, SkPaint());
501        canvas.restore();
502
503        canvas.restore();
504    });
505    int count = 0;
506    playbackOps(*dl, [&count](const RecordedOp& op) {
507        if (count++ == 1) {
508            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
509            EXPECT_CLIP_RECT(Rect(100, 100), op.localClip);
510            EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
511            EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
512                    << "Recorded op shouldn't see any canvas transform before the saveLayer";
513        }
514    });
515    EXPECT_EQ(3, count);
516}
517
518OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rotateClipped) {
519    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
520        canvas.save(SaveFlags::MatrixClip);
521        canvas.translate(100, 100);
522        canvas.rotate(45);
523        canvas.translate(-200, -200);
524
525        // area of saveLayer will be clipped to parent viewport, so we ask for 400x400...
526        canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer);
527        canvas.drawRect(0, 0, 400, 400, SkPaint());
528        canvas.restore();
529
530        canvas.restore();
531    });
532    int count = 0;
533    playbackOps(*dl, [&count](const RecordedOp& op) {
534        if (count++ == 1) {
535            Matrix4 expectedMatrix;
536            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
537
538            // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
539            // the parent 200x200 viewport, but prior to rotation
540            ASSERT_NE(nullptr, op.localClip);
541            ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode);
542            // NOTE: this check relies on saveLayer altering the clip post-viewport init. This
543            // causes the clip to be recorded by contained draw commands, though it's not necessary
544            // since the same clip will be computed at draw time. If such a change is made, this
545            // check could be done at record time by querying the clip, or the clip could be altered
546            // slightly so that it is serialized.
547            EXPECT_EQ(Rect(59, 59, 341, 341), op.localClip->rect);
548            EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
549            expectedMatrix.loadIdentity();
550            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
551        }
552    });
553    EXPECT_EQ(3, count);
554}
555
556OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rejectBegin) {
557    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
558        canvas.save(SaveFlags::MatrixClip);
559        canvas.translate(0, -20); // avoid identity case
560        // empty clip rect should force layer + contents to be rejected
561        canvas.clipRect(0, -20, 200, -20, SkClipOp::kIntersect);
562        canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
563        canvas.drawRect(0, 0, 200, 200, SkPaint());
564        canvas.restore();
565        canvas.restore();
566    });
567
568    ASSERT_EQ(0u, dl->getOps().size()) << "Begin/Rect/End should all be rejected.";
569}
570
571OPENGL_PIPELINE_TEST(RecordingCanvas, drawRenderNode_rejection) {
572    auto child = TestUtils::createNode(50, 50, 150, 150,
573            [](RenderProperties& props, Canvas& canvas) {
574        SkPaint paint;
575        paint.setColor(SK_ColorWHITE);
576        canvas.drawRect(0, 0, 100, 100, paint);
577    });
578
579    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&child](RecordingCanvas& canvas) {
580        canvas.clipRect(0, 0, 0, 0, SkClipOp::kIntersect); // empty clip, reject node
581        canvas.drawRenderNode(child.get()); // shouldn't crash when rejecting node...
582    });
583    ASSERT_TRUE(dl->isEmpty());
584}
585
586OPENGL_PIPELINE_TEST(RecordingCanvas, drawRenderNode_projection) {
587    sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150,
588            [](RenderProperties& props, Canvas& canvas) {
589        SkPaint paint;
590        paint.setColor(SK_ColorWHITE);
591        canvas.drawRect(0, 0, 100, 100, paint);
592    });
593    {
594        background->mutateStagingProperties().setProjectionReceiver(false);
595
596        // NO RECEIVER PRESENT
597        auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
598                    [&background](RecordingCanvas& canvas) {
599            canvas.drawRect(0, 0, 100, 100, SkPaint());
600            canvas.drawRenderNode(background.get());
601            canvas.drawRect(0, 0, 100, 100, SkPaint());
602        });
603        EXPECT_EQ(-1, dl->projectionReceiveIndex)
604                << "no projection receiver should have been observed";
605    }
606    {
607        background->mutateStagingProperties().setProjectionReceiver(true);
608
609        // RECEIVER PRESENT
610        auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
611                    [&background](RecordingCanvas& canvas) {
612            canvas.drawRect(0, 0, 100, 100, SkPaint());
613            canvas.drawRenderNode(background.get());
614            canvas.drawRect(0, 0, 100, 100, SkPaint());
615        });
616
617        ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops";
618        auto op = dl->getOps()[1];
619        EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId);
620        EXPECT_EQ(1, dl->projectionReceiveIndex)
621                << "correct projection receiver not identified";
622
623        // verify the behavior works even though projection receiver hasn't been sync'd yet
624        EXPECT_TRUE(background->stagingProperties().isProjectionReceiver());
625        EXPECT_FALSE(background->properties().isProjectionReceiver());
626    }
627}
628
629OPENGL_PIPELINE_TEST(RecordingCanvas, firstClipWillReplace) {
630    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
631        canvas.save(SaveFlags::MatrixClip);
632        // since no explicit clip set on canvas, this should be the one observed on op:
633        canvas.clipRect(-100, -100, 300, 300, SkClipOp::kIntersect);
634
635        SkPaint paint;
636        paint.setColor(SK_ColorWHITE);
637        canvas.drawRect(0, 0, 100, 100, paint);
638
639        canvas.restore();
640    });
641    ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
642    // first clip must be preserved, even if it extends beyond canvas bounds
643    EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip);
644}
645
646OPENGL_PIPELINE_TEST(RecordingCanvas, replaceClipIntersectWithRoot) {
647    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
648        canvas.save(SaveFlags::MatrixClip);
649        canvas.clipRect(-10, -10, 110, 110, SkClipOp::kReplace);
650        canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
651        canvas.restore();
652    });
653    ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
654    // first clip must be preserved, even if it extends beyond canvas bounds
655    EXPECT_CLIP_RECT(Rect(-10, -10, 110, 110), dl->getOps()[0]->localClip);
656    EXPECT_TRUE(dl->getOps()[0]->localClip->intersectWithRoot);
657}
658
659OPENGL_PIPELINE_TEST(RecordingCanvas, insertReorderBarrier) {
660    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
661        canvas.drawRect(0, 0, 400, 400, SkPaint());
662        canvas.insertReorderBarrier(true);
663        canvas.insertReorderBarrier(false);
664        canvas.insertReorderBarrier(false);
665        canvas.insertReorderBarrier(true);
666        canvas.drawRect(0, 0, 400, 400, SkPaint());
667        canvas.insertReorderBarrier(false);
668    });
669
670    auto chunks = dl->getChunks();
671    EXPECT_EQ(0u, chunks[0].beginOpIndex);
672    EXPECT_EQ(1u, chunks[0].endOpIndex);
673    EXPECT_FALSE(chunks[0].reorderChildren);
674
675    EXPECT_EQ(1u, chunks[1].beginOpIndex);
676    EXPECT_EQ(2u, chunks[1].endOpIndex);
677    EXPECT_TRUE(chunks[1].reorderChildren);
678}
679
680OPENGL_PIPELINE_TEST(RecordingCanvas, insertReorderBarrier_clip) {
681    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
682        // first chunk: no recorded clip
683        canvas.insertReorderBarrier(true);
684        canvas.drawRect(0, 0, 400, 400, SkPaint());
685
686        // second chunk: no recorded clip, since inorder region
687        canvas.clipRect(0, 0, 200, 200, SkClipOp::kIntersect);
688        canvas.insertReorderBarrier(false);
689        canvas.drawRect(0, 0, 400, 400, SkPaint());
690
691        // third chunk: recorded clip
692        canvas.insertReorderBarrier(true);
693        canvas.drawRect(0, 0, 400, 400, SkPaint());
694    });
695
696    auto chunks = dl->getChunks();
697    ASSERT_EQ(3u, chunks.size());
698
699    EXPECT_TRUE(chunks[0].reorderChildren);
700    EXPECT_EQ(nullptr, chunks[0].reorderClip);
701
702    EXPECT_FALSE(chunks[1].reorderChildren);
703    EXPECT_EQ(nullptr, chunks[1].reorderClip);
704
705    EXPECT_TRUE(chunks[2].reorderChildren);
706    ASSERT_NE(nullptr, chunks[2].reorderClip);
707    EXPECT_EQ(Rect(200, 200), chunks[2].reorderClip->rect);
708}
709
710OPENGL_PIPELINE_TEST(RecordingCanvas, refPaint) {
711    SkPaint paint;
712
713    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) {
714        paint.setColor(SK_ColorBLUE);
715        // first two should use same paint
716        canvas.drawRect(0, 0, 200, 10, paint);
717        SkPaint paintCopy(paint);
718        canvas.drawRect(0, 10, 200, 20, paintCopy);
719
720        // only here do we use different paint ptr
721        paint.setColor(SK_ColorRED);
722        canvas.drawRect(0, 20, 200, 30, paint);
723    });
724    auto ops = dl->getOps();
725    ASSERT_EQ(3u, ops.size());
726
727    // first two are the same
728    EXPECT_NE(nullptr, ops[0]->paint);
729    EXPECT_NE(&paint, ops[0]->paint);
730    EXPECT_EQ(ops[0]->paint, ops[1]->paint);
731
732    // last is different, but still copied / non-null
733    EXPECT_NE(nullptr, ops[2]->paint);
734    EXPECT_NE(ops[0]->paint, ops[2]->paint);
735    EXPECT_NE(&paint, ops[2]->paint);
736}
737
738OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmap) {
739    sk_sp<Bitmap> bitmap(TestUtils::createBitmap(100, 100));
740    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
741        canvas.drawBitmap(*bitmap, 0, 0, nullptr);
742    });
743    auto& bitmaps = dl->getBitmapResources();
744    EXPECT_EQ(1u, bitmaps.size());
745}
746
747OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmapInShader_bitmapShader) {
748    sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100);
749    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
750        SkPaint paint;
751        SkBitmap skBitmap;
752        bitmap->getSkBitmap(&skBitmap);
753        sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
754        sk_sp<SkShader> shader = image->makeShader(
755                SkShader::TileMode::kClamp_TileMode,
756                SkShader::TileMode::kClamp_TileMode,
757                nullptr);
758        paint.setShader(std::move(shader));
759        canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
760    });
761    auto& bitmaps = dl->getBitmapResources();
762    EXPECT_EQ(1u, bitmaps.size());
763}
764
765OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmapInShader_composeShader) {
766    sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100);
767    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
768        SkPaint paint;
769        SkBitmap skBitmap;
770        bitmap->getSkBitmap(&skBitmap);
771        sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
772        sk_sp<SkShader> shader1 = image->makeShader(
773                SkShader::TileMode::kClamp_TileMode,
774                SkShader::TileMode::kClamp_TileMode,
775                nullptr);
776
777        SkPoint center;
778        center.set(50, 50);
779        SkColor colors[2];
780        colors[0] = Color::Black;
781        colors[1] = Color::White;
782        sk_sp<SkShader> shader2 = SkGradientShader::MakeRadial(center, 50, colors, nullptr, 2,
783                SkShader::TileMode::kRepeat_TileMode);
784
785        sk_sp<SkShader> composeShader = SkShader::MakeComposeShader(std::move(shader1), std::move(shader2),
786                SkBlendMode::kMultiply);
787        paint.setShader(std::move(composeShader));
788        canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
789    });
790    auto& bitmaps = dl->getBitmapResources();
791    EXPECT_EQ(1u, bitmaps.size());
792}
793
794OPENGL_PIPELINE_TEST(RecordingCanvas, drawText) {
795    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
796        Paint paint;
797        paint.setAntiAlias(true);
798        paint.setTextSize(20);
799        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
800        std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
801        canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::kBidi_Force_LTR, paint, NULL);
802    });
803
804    int count = 0;
805    playbackOps(*dl, [&count](const RecordedOp& op) {
806        count++;
807        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
808        EXPECT_EQ(nullptr, op.localClip);
809        EXPECT_TRUE(op.localMatrix.isIdentity());
810        EXPECT_TRUE(op.unmappedBounds.getHeight() >= 10);
811        EXPECT_TRUE(op.unmappedBounds.getWidth() >= 25);
812    });
813    ASSERT_EQ(1, count);
814}
815
816OPENGL_PIPELINE_TEST(RecordingCanvas, drawTextInHighContrast) {
817    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
818        canvas.setHighContrastText(true);
819        Paint paint;
820        paint.setColor(SK_ColorWHITE);
821        paint.setAntiAlias(true);
822        paint.setTextSize(20);
823        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
824        std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
825        canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::kBidi_Force_LTR, paint, NULL);
826    });
827
828    int count = 0;
829    playbackOps(*dl, [&count](const RecordedOp& op) {
830        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
831        if (count++ == 0) {
832            EXPECT_EQ(SK_ColorBLACK, op.paint->getColor());
833            EXPECT_EQ(SkPaint::kStrokeAndFill_Style, op.paint->getStyle());
834        } else {
835            EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
836            EXPECT_EQ(SkPaint::kFill_Style, op.paint->getStyle());
837        }
838
839    });
840    ASSERT_EQ(2, count);
841}
842
843} // namespace uirenderer
844} // namespace android
845