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