/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <gtest/gtest.h> #include <BakedOpDispatcher.h> #include <BakedOpRenderer.h> #include <FrameBuilder.h> #include <LayerUpdateQueue.h> #include <hwui/Paint.h> #include <RecordedOp.h> #include <tests/common/TestUtils.h> #include <utils/Color.h> #include <SkBlurDrawLooper.h> #include <SkDashPathEffect.h> using namespace android::uirenderer; static BakedOpRenderer::LightInfo sLightInfo; const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50}; class ValidatingBakedOpRenderer : public BakedOpRenderer { public: ValidatingBakedOpRenderer(RenderState& renderState, std::function<void(const Glop& glop)> validator) : BakedOpRenderer(Caches::getInstance(), renderState, true, sLightInfo) , mValidator(validator) { mGlopReceiver = ValidatingGlopReceiver; } private: static void ValidatingGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds, const ClipBase* clip, const Glop& glop) { auto vbor = reinterpret_cast<ValidatingBakedOpRenderer*>(&renderer); vbor->mValidator(glop); } std::function<void(const Glop& glop)> mValidator; }; typedef void (*TestBakedOpReceiver)(BakedOpRenderer&, const BakedOpState&); static void testUnmergedGlopDispatch(renderthread::RenderThread& renderThread, RecordedOp* op, std::function<void(const Glop& glop)> glopVerifier, int expectedGlopCount = 1) { // Create op, and wrap with basic state. LinearAllocator allocator; auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 100)); auto state = BakedOpState::tryConstruct(allocator, *snapshot, *op); ASSERT_NE(nullptr, state); int glopCount = 0; auto glopReceiver = [&glopVerifier, &glopCount, &expectedGlopCount] (const Glop& glop) { ASSERT_LE(glopCount++, expectedGlopCount) << expectedGlopCount << "glop(s) expected"; glopVerifier(glop); }; ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver); // Dispatch based on op type created, similar to Frame/LayerBuilder dispatch behavior #define X(Type) \ [](BakedOpRenderer& renderer, const BakedOpState& state) { \ BakedOpDispatcher::on##Type(renderer, static_cast<const Type&>(*(state.op)), state); \ }, static TestBakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X); #undef X unmergedReceivers[op->opId](renderer, *state); ASSERT_EQ(expectedGlopCount, glopCount) << "Exactly " << expectedGlopCount << "Glop(s) expected"; } RENDERTHREAD_TEST(BakedOpDispatcher, pathTexture_positionOvalArc) { SkPaint strokePaint; strokePaint.setStyle(SkPaint::kStroke_Style); strokePaint.setStrokeWidth(4); float intervals[] = {1.0f, 1.0f}; auto dashEffect = SkDashPathEffect::Create(intervals, 2, 0); strokePaint.setPathEffect(dashEffect); dashEffect->unref(); auto textureGlopVerifier = [] (const Glop& glop) { // validate glop produced by renderPathTexture (so texture, unit quad) auto texture = glop.fill.texture.texture; ASSERT_NE(nullptr, texture); float expectedOffset = floor(4 * 1.5f + 0.5f); EXPECT_EQ(expectedOffset, reinterpret_cast<PathTexture*>(texture)->offset) << "Should see conservative offset from PathCache::computeBounds"; Rect expectedBounds(10, 15, 20, 25); expectedBounds.outset(expectedOffset); Matrix4 expectedModelView; expectedModelView.loadTranslate(10 - expectedOffset, 15 - expectedOffset, 0); expectedModelView.scale(10 + 2 * expectedOffset, 10 + 2 * expectedOffset, 1); EXPECT_EQ(expectedModelView, glop.transform.modelView) << "X and Y offsets, and scale both applied to model view"; }; // Arc and Oval will render functionally the same glop, differing only in texture content ArcOp arcOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint, 0, 270, true); testUnmergedGlopDispatch(renderThread, &arcOp, textureGlopVerifier); OvalOp ovalOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint); testUnmergedGlopDispatch(renderThread, &ovalOp, textureGlopVerifier); } RENDERTHREAD_TEST(BakedOpDispatcher, onLayerOp_bufferless) { SkPaint layerPaint; layerPaint.setAlpha(128); OffscreenBuffer* buffer = nullptr; // no providing a buffer, should hit rect fallback case LayerOp op(Rect(10, 10), Matrix4::identity(), nullptr, &layerPaint, &buffer); testUnmergedGlopDispatch(renderThread, &op, [&renderThread] (const Glop& glop) { ADD_FAILURE() << "Nothing should happen"; }, 0); } static int getGlopTransformFlags(renderthread::RenderThread& renderThread, RecordedOp* op) { int result = 0; testUnmergedGlopDispatch(renderThread, op, [&result] (const Glop& glop) { result = glop.transform.transformFlags; }); return result; } RENDERTHREAD_TEST(BakedOpDispatcher, offsetFlags) { Rect bounds(10, 15, 20, 25); SkPaint paint; SkPaint aaPaint; aaPaint.setAntiAlias(true); RoundRectOp roundRectOp(bounds, Matrix4::identity(), nullptr, &paint, 0, 270); EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &roundRectOp)) << "Expect no offset for round rect op."; const float points[4] = {0.5, 0.5, 1.0, 1.0}; PointsOp antiAliasedPointsOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4); EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedPointsOp)) << "Expect no offset for AA points."; PointsOp pointsOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4); EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &pointsOp)) << "Expect an offset for non-AA points."; LinesOp antiAliasedLinesOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4); EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedLinesOp)) << "Expect no offset for AA lines."; LinesOp linesOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4); EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &linesOp)) << "Expect an offset for non-AA lines."; } RENDERTHREAD_TEST(BakedOpDispatcher, renderTextWithShadow) { auto node = TestUtils::createNode(0, 0, 100, 100, [](RenderProperties& props, TestCanvas& canvas) { android::Paint shadowPaint; shadowPaint.setColor(SK_ColorRED); SkScalar sigma = Blur::convertRadiusToSigma(5); shadowPaint.setLooper(SkBlurDrawLooper::Create(SK_ColorWHITE, sigma, 3, 3))->unref(); TestUtils::drawUtf8ToCanvas(&canvas, "A", shadowPaint, 25, 25); TestUtils::drawUtf8ToCanvas(&canvas, "B", shadowPaint, 50, 50); }); int glopCount = 0; auto glopReceiver = [&glopCount] (const Glop& glop) { if (glopCount < 2) { // two white shadows EXPECT_EQ(FloatColor({1, 1, 1, 1}), glop.fill.color); } else { // two text draws merged into one, drawn after both shadows EXPECT_EQ(FloatColor({1, 0, 0, 1}), glop.fill.color); } glopCount++; }; ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver); FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, Caches::getInstance()); frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); ASSERT_EQ(3, glopCount) << "Exactly three glops expected"; } static void validateLayerDraw(renderthread::RenderThread& renderThread, std::function<void(const Glop& glop)> validator) { auto node = TestUtils::createNode(0, 0, 100, 100, [](RenderProperties& props, TestCanvas& canvas) { props.mutateLayerProperties().setType(LayerType::RenderLayer); // provide different blend mode, so decoration draws contrast props.mutateLayerProperties().setXferMode(SkXfermode::Mode::kSrc_Mode); canvas.drawColor(Color::Black, SkXfermode::Mode::kSrcOver_Mode); }); OffscreenBuffer** layerHandle = node->getLayerHandle(); auto syncedNode = TestUtils::getSyncedNode(node); // create RenderNode's layer here in same way prepareTree would OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); *layerHandle = &layer; { LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(0, 0, 100, 100)); ValidatingBakedOpRenderer renderer(renderThread.renderState(), validator); FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, Caches::getInstance()); frameBuilder.deferLayers(layerUpdateQueue); frameBuilder.deferRenderNode(*syncedNode); frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); } // clean up layer pointer, so we can safely destruct RenderNode *layerHandle = nullptr; } static FloatColor makeFloatColor(uint32_t color) { FloatColor c; c.set(color); return c; } RENDERTHREAD_TEST(BakedOpDispatcher, layerUpdateProperties) { for (bool debugOverdraw : { false, true }) { for (bool debugLayersUpdates : { false, true }) { ScopedProperty<bool> ovdProp(Properties::debugOverdraw, debugOverdraw); ScopedProperty<bool> lupProp(Properties::debugLayersUpdates, debugLayersUpdates); int glopCount = 0; validateLayerDraw(renderThread, [&glopCount, &debugLayersUpdates](const Glop& glop) { if (glopCount == 0) { // 0 - Black layer fill EXPECT_TRUE(glop.fill.colorEnabled); EXPECT_EQ(makeFloatColor(Color::Black), glop.fill.color); } else if (glopCount == 1) { // 1 - Uncolored (textured) layer draw EXPECT_FALSE(glop.fill.colorEnabled); } else if (glopCount == 2) { // 2 - layer overlay, if present EXPECT_TRUE(glop.fill.colorEnabled); // blend srcover, different from that of layer EXPECT_EQ(GLenum(GL_ONE), glop.blend.src); EXPECT_EQ(GLenum(GL_ONE_MINUS_SRC_ALPHA), glop.blend.dst); EXPECT_EQ(makeFloatColor(debugLayersUpdates ? 0x7f00ff00 : 0), glop.fill.color) << "Should be transparent green if debugLayersUpdates"; } else if (glopCount < 7) { // 3 - 6 - overdraw indicator overlays, if present EXPECT_TRUE(glop.fill.colorEnabled); uint32_t expectedColor = Caches::getInstance().getOverdrawColor(glopCount - 2); ASSERT_EQ(makeFloatColor(expectedColor), glop.fill.color); } else { ADD_FAILURE() << "Too many glops observed"; } glopCount++; }); int expectedCount = 2; if (debugLayersUpdates || debugOverdraw) expectedCount++; if (debugOverdraw) expectedCount += 4; EXPECT_EQ(expectedCount, glopCount); } } }