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