1/*
2 * Copyright (C) 2016 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 <BakedOpDispatcher.h>
20#include <BakedOpRenderer.h>
21#include <FrameBuilder.h>
22#include <LayerUpdateQueue.h>
23#include <RecordedOp.h>
24#include <hwui/Paint.h>
25#include <tests/common/TestUtils.h>
26#include <utils/Color.h>
27
28#include <SkBlurDrawLooper.h>
29#include <SkDashPathEffect.h>
30#include <SkPath.h>
31
32using namespace android::uirenderer;
33
34static BakedOpRenderer::LightInfo sLightInfo;
35const FrameBuilder::LightGeometry sLightGeometry = {{100, 100, 100}, 50};
36
37class ValidatingBakedOpRenderer : public BakedOpRenderer {
38public:
39    ValidatingBakedOpRenderer(RenderState& renderState,
40                              std::function<void(const Glop& glop)> validator)
41            : BakedOpRenderer(Caches::getInstance(), renderState, true, false, sLightInfo)
42            , mValidator(validator) {
43        mGlopReceiver = ValidatingGlopReceiver;
44    }
45
46private:
47    static void ValidatingGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds,
48                                       const ClipBase* clip, const Glop& glop) {
49        auto vbor = reinterpret_cast<ValidatingBakedOpRenderer*>(&renderer);
50        vbor->mValidator(glop);
51    }
52    std::function<void(const Glop& glop)> mValidator;
53};
54
55typedef void (*TestBakedOpReceiver)(BakedOpRenderer&, const BakedOpState&);
56
57static void testUnmergedGlopDispatch(renderthread::RenderThread& renderThread, RecordedOp* op,
58                                     std::function<void(const Glop& glop)> glopVerifier,
59                                     int expectedGlopCount = 1) {
60    // Create op, and wrap with basic state.
61    LinearAllocator allocator;
62    auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 100));
63    auto state = BakedOpState::tryConstruct(allocator, *snapshot, *op);
64    ASSERT_NE(nullptr, state);
65
66    int glopCount = 0;
67    auto glopReceiver = [&glopVerifier, &glopCount, &expectedGlopCount](const Glop& glop) {
68        ASSERT_LE(glopCount++, expectedGlopCount) << expectedGlopCount << "glop(s) expected";
69        glopVerifier(glop);
70    };
71    ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver);
72
73// Dispatch based on op type created, similar to Frame/LayerBuilder dispatch behavior
74#define X(Type)                                                                              \
75    [](BakedOpRenderer& renderer, const BakedOpState& state) {                               \
76        BakedOpDispatcher::on##Type(renderer, static_cast<const Type&>(*(state.op)), state); \
77    },
78    static TestBakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X);
79#undef X
80    unmergedReceivers[op->opId](renderer, *state);
81    ASSERT_EQ(expectedGlopCount, glopCount) << "Exactly " << expectedGlopCount
82                                            << "Glop(s) expected";
83}
84
85RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, pathTexture_positionOvalArc) {
86    SkPaint strokePaint;
87    strokePaint.setStyle(SkPaint::kStroke_Style);
88    strokePaint.setStrokeWidth(4);
89
90    float intervals[] = {1.0f, 1.0f};
91    strokePaint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
92
93    auto textureGlopVerifier = [](const Glop& glop) {
94        // validate glop produced by renderPathTexture (so texture, unit quad)
95        auto texture = glop.fill.texture.texture;
96        ASSERT_NE(nullptr, texture);
97        float expectedOffset = floor(4 * 1.5f + 0.5f);
98        EXPECT_EQ(expectedOffset, reinterpret_cast<PathTexture*>(texture)->offset)
99                << "Should see conservative offset from PathCache::computeBounds";
100        Rect expectedBounds(10, 15, 20, 25);
101        expectedBounds.outset(expectedOffset);
102
103        Matrix4 expectedModelView;
104        expectedModelView.loadTranslate(10 - expectedOffset, 15 - expectedOffset, 0);
105        expectedModelView.scale(10 + 2 * expectedOffset, 10 + 2 * expectedOffset, 1);
106        EXPECT_EQ(expectedModelView, glop.transform.modelView)
107                << "X and Y offsets, and scale both applied to model view";
108    };
109
110    // Arc and Oval will render functionally the same glop, differing only in texture content
111    ArcOp arcOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint, 0, 270, true);
112    testUnmergedGlopDispatch(renderThread, &arcOp, textureGlopVerifier);
113
114    OvalOp ovalOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint);
115    testUnmergedGlopDispatch(renderThread, &ovalOp, textureGlopVerifier);
116}
117
118RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, onLayerOp_bufferless) {
119    SkPaint layerPaint;
120    layerPaint.setAlpha(128);
121    OffscreenBuffer* buffer = nullptr;  // no providing a buffer, should hit rect fallback case
122    LayerOp op(Rect(10, 10), Matrix4::identity(), nullptr, &layerPaint, &buffer);
123    testUnmergedGlopDispatch(renderThread, &op,
124                             [](const Glop& glop) { ADD_FAILURE() << "Nothing should happen"; }, 0);
125}
126
127static int getGlopTransformFlags(renderthread::RenderThread& renderThread, RecordedOp* op) {
128    int result = 0;
129    testUnmergedGlopDispatch(renderThread, op, [&result](const Glop& glop) {
130        result = glop.transform.transformFlags;
131    });
132    return result;
133}
134
135RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, offsetFlags) {
136    Rect bounds(10, 15, 20, 25);
137    SkPaint paint;
138    SkPaint aaPaint;
139    aaPaint.setAntiAlias(true);
140
141    RoundRectOp roundRectOp(bounds, Matrix4::identity(), nullptr, &paint, 0, 270);
142    EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &roundRectOp))
143            << "Expect no offset for round rect op.";
144
145    const float points[4] = {0.5, 0.5, 1.0, 1.0};
146    PointsOp antiAliasedPointsOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4);
147    EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedPointsOp))
148            << "Expect no offset for AA points.";
149    PointsOp pointsOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4);
150    EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &pointsOp))
151            << "Expect an offset for non-AA points.";
152
153    LinesOp antiAliasedLinesOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4);
154    EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedLinesOp))
155            << "Expect no offset for AA lines.";
156    LinesOp linesOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4);
157    EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &linesOp))
158            << "Expect an offset for non-AA lines.";
159}
160
161RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, renderTextWithShadow) {
162    auto node = TestUtils::createNode<RecordingCanvas>(
163            0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
164
165                android::Paint shadowPaint;
166                shadowPaint.setColor(SK_ColorRED);
167
168                SkScalar sigma = Blur::convertRadiusToSigma(5);
169                shadowPaint.setLooper(SkBlurDrawLooper::Make(SK_ColorWHITE, sigma, 3, 3));
170
171                TestUtils::drawUtf8ToCanvas(&canvas, "A", shadowPaint, 25, 25);
172                TestUtils::drawUtf8ToCanvas(&canvas, "B", shadowPaint, 50, 50);
173            });
174
175    int glopCount = 0;
176    auto glopReceiver = [&glopCount](const Glop& glop) {
177        if (glopCount < 2) {
178            // two white shadows
179            EXPECT_EQ(FloatColor({1, 1, 1, 1}), glop.fill.color);
180        } else {
181            // two text draws merged into one, drawn after both shadows
182            EXPECT_EQ(FloatColor({1, 0, 0, 1}), glop.fill.color);
183        }
184        glopCount++;
185    };
186
187    ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver);
188
189    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
190                              Caches::getInstance());
191    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
192
193    frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
194    ASSERT_EQ(3, glopCount) << "Exactly three glops expected";
195}
196
197static void validateLayerDraw(renderthread::RenderThread& renderThread,
198                              std::function<void(const Glop& glop)> validator) {
199    auto node = TestUtils::createNode<RecordingCanvas>(
200            0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
201                props.mutateLayerProperties().setType(LayerType::RenderLayer);
202
203                // provide different blend mode, so decoration draws contrast
204                props.mutateLayerProperties().setXferMode(SkBlendMode::kSrc);
205                canvas.drawColor(Color::Black, SkBlendMode::kSrcOver);
206            });
207    OffscreenBuffer** layerHandle = node->getLayerHandle();
208
209    auto syncedNode = TestUtils::getSyncedNode(node);
210
211    // create RenderNode's layer here in same way prepareTree would
212    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
213    *layerHandle = &layer;
214    {
215        LayerUpdateQueue layerUpdateQueue;  // Note: enqueue damage post-sync, so bounds are valid
216        layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(0, 0, 100, 100));
217
218        ValidatingBakedOpRenderer renderer(renderThread.renderState(), validator);
219        FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
220                                  Caches::getInstance());
221        frameBuilder.deferLayers(layerUpdateQueue);
222        frameBuilder.deferRenderNode(*syncedNode);
223        frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
224    }
225
226    // clean up layer pointer, so we can safely destruct RenderNode
227    *layerHandle = nullptr;
228}
229
230static FloatColor makeFloatColor(uint32_t color) {
231    FloatColor c;
232    c.set(color);
233    return c;
234}
235
236RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, layerUpdateProperties) {
237    for (bool debugOverdraw : {false, true}) {
238        for (bool debugLayersUpdates : {false, true}) {
239            ScopedProperty<bool> ovdProp(Properties::debugOverdraw, debugOverdraw);
240            ScopedProperty<bool> lupProp(Properties::debugLayersUpdates, debugLayersUpdates);
241
242            int glopCount = 0;
243            validateLayerDraw(renderThread, [&glopCount, &debugLayersUpdates](const Glop& glop) {
244                if (glopCount == 0) {
245                    // 0 - Black layer fill
246                    EXPECT_TRUE(glop.fill.colorEnabled);
247                    EXPECT_EQ(makeFloatColor(Color::Black), glop.fill.color);
248                } else if (glopCount == 1) {
249                    // 1 - Uncolored (textured) layer draw
250                    EXPECT_FALSE(glop.fill.colorEnabled);
251                } else if (glopCount == 2) {
252                    // 2 - layer overlay, if present
253                    EXPECT_TRUE(glop.fill.colorEnabled);
254                    // blend srcover, different from that of layer
255                    EXPECT_EQ(GLenum(GL_ONE), glop.blend.src);
256                    EXPECT_EQ(GLenum(GL_ONE_MINUS_SRC_ALPHA), glop.blend.dst);
257                    EXPECT_EQ(makeFloatColor(debugLayersUpdates ? 0x7f00ff00 : 0), glop.fill.color)
258                            << "Should be transparent green if debugLayersUpdates";
259                } else if (glopCount < 7) {
260                    // 3 - 6 - overdraw indicator overlays, if present
261                    EXPECT_TRUE(glop.fill.colorEnabled);
262                    uint32_t expectedColor = Caches::getInstance().getOverdrawColor(glopCount - 2);
263                    ASSERT_EQ(makeFloatColor(expectedColor), glop.fill.color);
264                } else {
265                    ADD_FAILURE() << "Too many glops observed";
266                }
267                glopCount++;
268            });
269            int expectedCount = 2;
270            if (debugLayersUpdates || debugOverdraw) expectedCount++;
271            if (debugOverdraw) expectedCount += 4;
272            EXPECT_EQ(expectedCount, glopCount);
273        }
274    }
275}
276
277RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, pathTextureSnapping) {
278    Rect bounds(10, 15, 20, 25);
279    SkPaint paint;
280    SkPath path;
281    path.addRect(SkRect::MakeXYWH(1.5, 3.8, 100, 90));
282    PathOp op(bounds, Matrix4::identity(), nullptr, &paint, &path);
283    testUnmergedGlopDispatch(renderThread, &op, [](const Glop& glop) {
284        auto texture = glop.fill.texture.texture;
285        ASSERT_NE(nullptr, texture);
286        EXPECT_EQ(1, reinterpret_cast<PathTexture*>(texture)->left);
287        EXPECT_EQ(3, reinterpret_cast<PathTexture*>(texture)->top);
288    });
289}
290