BakedOpDispatcherTests.cpp revision 284b765e3c1647859d4dac772744e8859c033216
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 <RecordedOp.h>
20#include <BakedOpDispatcher.h>
21#include <BakedOpRenderer.h>
22#include <FrameBuilder.h>
23#include <SkBlurDrawLooper.h>
24#include <hwui/Paint.h>
25#include <tests/common/TestUtils.h>
26
27#include <SkDashPathEffect.h>
28
29using namespace android::uirenderer;
30
31static BakedOpRenderer::LightInfo sLightInfo;
32const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50};
33static Rect sBaseClip(100, 100);
34
35class ValidatingBakedOpRenderer : public BakedOpRenderer {
36public:
37    ValidatingBakedOpRenderer(RenderState& renderState, std::function<void(const Glop& glop)> validator)
38            : BakedOpRenderer(Caches::getInstance(), renderState, true, sLightInfo)
39            , mValidator(validator) {
40        mGlopReceiver = ValidatingGlopReceiver;
41    }
42private:
43    static void ValidatingGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds,
44            const ClipBase* clip, const Glop& glop) {
45
46        auto vbor = reinterpret_cast<ValidatingBakedOpRenderer*>(&renderer);
47        vbor->mValidator(glop);
48    }
49    std::function<void(const Glop& glop)> mValidator;
50};
51
52typedef void (*TestBakedOpReceiver)(BakedOpRenderer&, const BakedOpState&);
53
54static void testUnmergedGlopDispatch(renderthread::RenderThread& renderThread, RecordedOp* op,
55        std::function<void(const Glop& glop)> glopVerifier) {
56    // Create op, and wrap with basic state.
57    LinearAllocator allocator;
58    auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), sBaseClip);
59    auto state = BakedOpState::tryConstruct(allocator, *snapshot, *op);
60    ASSERT_NE(nullptr, state);
61
62    int glopCount = 0;
63    auto glopReceiver = [&glopVerifier, &glopCount] (const Glop& glop) {
64        ASSERT_EQ(glopCount++, 0) << "Only one Glop expected";
65        glopVerifier(glop);
66    };
67    ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver);
68
69    // Dispatch based on op type created, similar to Frame/LayerBuilder dispatch behavior
70#define X(Type) \
71        [](BakedOpRenderer& renderer, const BakedOpState& state) { \
72            BakedOpDispatcher::on##Type(renderer, static_cast<const Type&>(*(state.op)), state); \
73        },
74    static TestBakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X);
75#undef X
76    unmergedReceivers[op->opId](renderer, *state);
77    ASSERT_EQ(1, glopCount) << "Exactly one Glop expected";
78}
79
80RENDERTHREAD_TEST(BakedOpDispatcher, pathTexture_positionOvalArc) {
81    SkPaint strokePaint;
82    strokePaint.setStyle(SkPaint::kStroke_Style);
83    strokePaint.setStrokeWidth(4);
84
85    float intervals[] = {1.0f, 1.0f};
86    auto dashEffect = SkDashPathEffect::Create(intervals, 2, 0);
87    strokePaint.setPathEffect(dashEffect);
88    dashEffect->unref();
89
90    auto textureGlopVerifier = [] (const Glop& glop) {
91        // validate glop produced by renderPathTexture (so texture, unit quad)
92        auto texture = glop.fill.texture.texture;
93        ASSERT_NE(nullptr, texture);
94        float expectedOffset = floor(4 * 1.5f + 0.5f);
95        EXPECT_EQ(expectedOffset, reinterpret_cast<PathTexture*>(texture)->offset)
96                << "Should see conservative offset from PathCache::computeBounds";
97        Rect expectedBounds(10, 15, 20, 25);
98        expectedBounds.outset(expectedOffset);
99
100        Matrix4 expectedModelView;
101        expectedModelView.loadTranslate(10 - expectedOffset, 15 - expectedOffset, 0);
102        expectedModelView.scale(10 + 2 * expectedOffset, 10 + 2 * expectedOffset, 1);
103        EXPECT_EQ(expectedModelView, glop.transform.modelView)
104                << "X and Y offsets, and scale both applied to model view";
105    };
106
107    // Arc and Oval will render functionally the same glop, differing only in texture content
108    ArcOp arcOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint, 0, 270, true);
109    testUnmergedGlopDispatch(renderThread, &arcOp, textureGlopVerifier);
110
111    OvalOp ovalOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint);
112    testUnmergedGlopDispatch(renderThread, &ovalOp, textureGlopVerifier);
113}
114
115RENDERTHREAD_TEST(BakedOpDispatcher, onLayerOp_bufferless) {
116    SkPaint layerPaint;
117    layerPaint.setAlpha(128);
118    OffscreenBuffer* buffer = nullptr; // no providing a buffer, should hit rect fallback case
119    LayerOp op(Rect(10, 10), Matrix4::identity(), nullptr, &layerPaint, &buffer);
120    testUnmergedGlopDispatch(renderThread, &op, [&renderThread] (const Glop& glop) {
121        // rect glop is dispatched with paint props applied
122        EXPECT_EQ(renderThread.renderState().meshState().getUnitQuadVBO(),
123                glop.mesh.vertices.bufferObject) << "Unit quad should be drawn";
124        EXPECT_EQ(nullptr, glop.fill.texture.texture) << "Should be no texture when layer is null";
125        EXPECT_FLOAT_EQ(128 / 255.0f, glop.fill.color.a) << "Rect quad should use op alpha";
126    });
127}
128
129static int getGlopTransformFlags(renderthread::RenderThread& renderThread, RecordedOp* op) {
130    int result = 0;
131    testUnmergedGlopDispatch(renderThread, op, [&result] (const Glop& glop) {
132        result = glop.transform.transformFlags;
133    });
134    return result;
135}
136
137RENDERTHREAD_TEST(BakedOpDispatcher, offsetFlags) {
138    Rect bounds(10, 15, 20, 25);
139    SkPaint paint;
140    SkPaint aaPaint;
141    aaPaint.setAntiAlias(true);
142
143    RoundRectOp roundRectOp(bounds, Matrix4::identity(), nullptr, &paint, 0, 270);
144    EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &roundRectOp))
145            << "Expect no offset for round rect op.";
146
147    const float points[4] = {0.5, 0.5, 1.0, 1.0};
148    PointsOp antiAliasedPointsOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4);
149    EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedPointsOp))
150                << "Expect no offset for AA points.";
151    PointsOp pointsOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4);
152    EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &pointsOp))
153            << "Expect an offset for non-AA points.";
154
155    LinesOp antiAliasedLinesOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4);
156    EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedLinesOp))
157            << "Expect no offset for AA lines.";
158    LinesOp linesOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4);
159    EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &linesOp))
160            << "Expect an offset for non-AA lines.";
161}
162
163RENDERTHREAD_TEST(BakedOpDispatcher, renderTextWithShadow) {
164    auto node = TestUtils::createNode(0, 0, 100, 100,
165            [](RenderProperties& props, TestCanvas& canvas) {
166
167        android::Paint shadowPaint;
168        shadowPaint.setColor(SK_ColorRED);
169
170        SkScalar sigma = Blur::convertRadiusToSigma(5);
171        shadowPaint.setLooper(SkBlurDrawLooper::Create(SK_ColorWHITE, sigma, 3, 3))->unref();
172
173        TestUtils::drawUtf8ToCanvas(&canvas, "A", shadowPaint, 25, 25);
174        TestUtils::drawUtf8ToCanvas(&canvas, "B", shadowPaint, 50, 50);
175    });
176
177    int  glopCount = 0;
178    auto glopReceiver = [&glopCount] (const Glop& glop) {
179        if (glopCount < 2) {
180            // two white shadows
181            EXPECT_EQ(FloatColor({1, 1, 1, 1}), glop.fill.color);
182        } else {
183            // two text draws merged into one, drawn after both shadows
184            EXPECT_EQ(FloatColor({1, 0, 0, 1}), glop.fill.color);
185        }
186        glopCount++;
187    };
188
189    ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver);
190
191    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
192            sLightGeometry, Caches::getInstance());
193    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
194
195    frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
196    ASSERT_EQ(3, glopCount) << "Exactly three glops expected";
197}