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