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