blob: 09f0b06ded396a28c3e6f6007299f540bce10d84 [file] [log] [blame]
Chris Craik419a1e72016-03-08 16:24:12 -08001/*
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
Chris Craik419a1e72016-03-08 16:24:12 -080019#include <BakedOpDispatcher.h>
20#include <BakedOpRenderer.h>
sergeyv284b7652016-05-10 13:58:12 -070021#include <FrameBuilder.h>
Chris Craik37413282016-05-12 17:48:51 -070022#include <LayerUpdateQueue.h>
Chris Craik37413282016-05-12 17:48:51 -070023#include <RecordedOp.h>
John Reck1bcacfd2017-11-03 10:12:19 -070024#include <hwui/Paint.h>
Chris Craik419a1e72016-03-08 16:24:12 -080025#include <tests/common/TestUtils.h>
Chris Craik37413282016-05-12 17:48:51 -070026#include <utils/Color.h>
Chris Craik419a1e72016-03-08 16:24:12 -080027
Chris Craik37413282016-05-12 17:48:51 -070028#include <SkBlurDrawLooper.h>
Chris Craike98a0462016-04-26 15:35:20 -070029#include <SkDashPathEffect.h>
sergeyv89561e62016-08-04 16:21:07 -070030#include <SkPath.h>
Chris Craike98a0462016-04-26 15:35:20 -070031
Chris Craik419a1e72016-03-08 16:24:12 -080032using namespace android::uirenderer;
33
34static BakedOpRenderer::LightInfo sLightInfo;
John Reck1bcacfd2017-11-03 10:12:19 -070035const FrameBuilder::LightGeometry sLightGeometry = {{100, 100, 100}, 50};
Chris Craik419a1e72016-03-08 16:24:12 -080036
37class ValidatingBakedOpRenderer : public BakedOpRenderer {
38public:
John Reck1bcacfd2017-11-03 10:12:19 -070039 ValidatingBakedOpRenderer(RenderState& renderState,
40 std::function<void(const Glop& glop)> validator)
Romain Guy07ae5052017-06-13 18:25:32 -070041 : BakedOpRenderer(Caches::getInstance(), renderState, true, false, sLightInfo)
Chris Craik419a1e72016-03-08 16:24:12 -080042 , mValidator(validator) {
43 mGlopReceiver = ValidatingGlopReceiver;
44 }
John Reck1bcacfd2017-11-03 10:12:19 -070045
Chris Craik419a1e72016-03-08 16:24:12 -080046private:
47 static void ValidatingGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds,
John Reck1bcacfd2017-11-03 10:12:19 -070048 const ClipBase* clip, const Glop& glop) {
Chris Craik419a1e72016-03-08 16:24:12 -080049 auto vbor = reinterpret_cast<ValidatingBakedOpRenderer*>(&renderer);
50 vbor->mValidator(glop);
51 }
52 std::function<void(const Glop& glop)> mValidator;
53};
54
sergeyv284b7652016-05-10 13:58:12 -070055typedef void (*TestBakedOpReceiver)(BakedOpRenderer&, const BakedOpState&);
Chris Craik419a1e72016-03-08 16:24:12 -080056
57static void testUnmergedGlopDispatch(renderthread::RenderThread& renderThread, RecordedOp* op,
John Reck1bcacfd2017-11-03 10:12:19 -070058 std::function<void(const Glop& glop)> glopVerifier,
59 int expectedGlopCount = 1) {
Chris Craik419a1e72016-03-08 16:24:12 -080060 // Create op, and wrap with basic state.
61 LinearAllocator allocator;
Chris Craik37413282016-05-12 17:48:51 -070062 auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 100));
Chris Craik419a1e72016-03-08 16:24:12 -080063 auto state = BakedOpState::tryConstruct(allocator, *snapshot, *op);
64 ASSERT_NE(nullptr, state);
65
66 int glopCount = 0;
John Reck1bcacfd2017-11-03 10:12:19 -070067 auto glopReceiver = [&glopVerifier, &glopCount, &expectedGlopCount](const Glop& glop) {
sergeyvaebbbef2016-05-31 14:18:02 -070068 ASSERT_LE(glopCount++, expectedGlopCount) << expectedGlopCount << "glop(s) expected";
Chris Craik419a1e72016-03-08 16:24:12 -080069 glopVerifier(glop);
70 };
71 ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver);
72
John Reck1bcacfd2017-11-03 10:12:19 -070073// 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 },
sergeyv284b7652016-05-10 13:58:12 -070078 static TestBakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X);
Chris Craik419a1e72016-03-08 16:24:12 -080079#undef X
80 unmergedReceivers[op->opId](renderer, *state);
sergeyvaebbbef2016-05-31 14:18:02 -070081 ASSERT_EQ(expectedGlopCount, glopCount) << "Exactly " << expectedGlopCount
John Reck1bcacfd2017-11-03 10:12:19 -070082 << "Glop(s) expected";
Chris Craik419a1e72016-03-08 16:24:12 -080083}
84
Greg Daniel98c78dad2017-01-04 14:45:56 -050085RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, pathTexture_positionOvalArc) {
Chris Craik419a1e72016-03-08 16:24:12 -080086 SkPaint strokePaint;
87 strokePaint.setStyle(SkPaint::kStroke_Style);
88 strokePaint.setStrokeWidth(4);
Chris Craike98a0462016-04-26 15:35:20 -070089
90 float intervals[] = {1.0f, 1.0f};
Mike Reed260ab722016-10-07 15:59:20 -040091 strokePaint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
Chris Craike98a0462016-04-26 15:35:20 -070092
John Reck1bcacfd2017-11-03 10:12:19 -070093 auto textureGlopVerifier = [](const Glop& glop) {
Chris Craik419a1e72016-03-08 16:24:12 -080094 // 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);
Chris Craike98a0462016-04-26 15:35:20 -0700102
Chris Craik419a1e72016-03-08 16:24:12 -0800103 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";
Chris Craike98a0462016-04-26 15:35:20 -0700108 };
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);
Chris Craik419a1e72016-03-08 16:24:12 -0800116}
117
Greg Daniel98c78dad2017-01-04 14:45:56 -0500118RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, onLayerOp_bufferless) {
Chris Craik419a1e72016-03-08 16:24:12 -0800119 SkPaint layerPaint;
120 layerPaint.setAlpha(128);
John Reck1bcacfd2017-11-03 10:12:19 -0700121 OffscreenBuffer* buffer = nullptr; // no providing a buffer, should hit rect fallback case
Chris Craik419a1e72016-03-08 16:24:12 -0800122 LayerOp op(Rect(10, 10), Matrix4::identity(), nullptr, &layerPaint, &buffer);
John Reck1bcacfd2017-11-03 10:12:19 -0700123 testUnmergedGlopDispatch(renderThread, &op,
124 [](const Glop& glop) { ADD_FAILURE() << "Nothing should happen"; }, 0);
Chris Craik419a1e72016-03-08 16:24:12 -0800125}
sergeyv92a5d4b2016-04-20 14:20:18 -0700126
127static int getGlopTransformFlags(renderthread::RenderThread& renderThread, RecordedOp* op) {
128 int result = 0;
John Reck1bcacfd2017-11-03 10:12:19 -0700129 testUnmergedGlopDispatch(renderThread, op, [&result](const Glop& glop) {
sergeyv92a5d4b2016-04-20 14:20:18 -0700130 result = glop.transform.transformFlags;
131 });
132 return result;
133}
134
Greg Daniel98c78dad2017-01-04 14:45:56 -0500135RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, offsetFlags) {
sergeyv92a5d4b2016-04-20 14:20:18 -0700136 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))
John Reck1bcacfd2017-11-03 10:12:19 -0700148 << "Expect no offset for AA points.";
sergeyv92a5d4b2016-04-20 14:20:18 -0700149 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.";
sergeyv284b7652016-05-10 13:58:12 -0700159}
160
Greg Daniel98c78dad2017-01-04 14:45:56 -0500161RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, renderTextWithShadow) {
John Reck1bcacfd2017-11-03 10:12:19 -0700162 auto node = TestUtils::createNode<RecordingCanvas>(
163 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
sergeyv284b7652016-05-10 13:58:12 -0700164
John Reck1bcacfd2017-11-03 10:12:19 -0700165 android::Paint shadowPaint;
166 shadowPaint.setColor(SK_ColorRED);
sergeyv284b7652016-05-10 13:58:12 -0700167
John Reck1bcacfd2017-11-03 10:12:19 -0700168 SkScalar sigma = Blur::convertRadiusToSigma(5);
169 shadowPaint.setLooper(SkBlurDrawLooper::Make(SK_ColorWHITE, sigma, 3, 3));
sergeyv284b7652016-05-10 13:58:12 -0700170
John Reck1bcacfd2017-11-03 10:12:19 -0700171 TestUtils::drawUtf8ToCanvas(&canvas, "A", shadowPaint, 25, 25);
172 TestUtils::drawUtf8ToCanvas(&canvas, "B", shadowPaint, 50, 50);
173 });
sergeyv284b7652016-05-10 13:58:12 -0700174
John Reck1bcacfd2017-11-03 10:12:19 -0700175 int glopCount = 0;
176 auto glopReceiver = [&glopCount](const Glop& glop) {
sergeyv284b7652016-05-10 13:58:12 -0700177 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
John Reck1bcacfd2017-11-03 10:12:19 -0700189 FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
190 Caches::getInstance());
sergeyv284b7652016-05-10 13:58:12 -0700191 frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
192
193 frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
194 ASSERT_EQ(3, glopCount) << "Exactly three glops expected";
Chris Craik37413282016-05-12 17:48:51 -0700195}
196
197static void validateLayerDraw(renderthread::RenderThread& renderThread,
John Reck1bcacfd2017-11-03 10:12:19 -0700198 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);
Chris Craik37413282016-05-12 17:48:51 -0700202
John Reck1bcacfd2017-11-03 10:12:19 -0700203 // provide different blend mode, so decoration draws contrast
204 props.mutateLayerProperties().setXferMode(SkBlendMode::kSrc);
205 canvas.drawColor(Color::Black, SkBlendMode::kSrcOver);
206 });
Chris Craik37413282016-05-12 17:48:51 -0700207 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 {
John Reck1bcacfd2017-11-03 10:12:19 -0700215 LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
Chris Craik37413282016-05-12 17:48:51 -0700216 layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(0, 0, 100, 100));
217
218 ValidatingBakedOpRenderer renderer(renderThread.renderState(), validator);
John Reck1bcacfd2017-11-03 10:12:19 -0700219 FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
220 Caches::getInstance());
Chris Craik37413282016-05-12 17:48:51 -0700221 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
Greg Daniel98c78dad2017-01-04 14:45:56 -0500236RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, layerUpdateProperties) {
John Reck1bcacfd2017-11-03 10:12:19 -0700237 for (bool debugOverdraw : {false, true}) {
238 for (bool debugLayersUpdates : {false, true}) {
Chris Craik37413282016-05-12 17:48:51 -0700239 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);
John Reck1bcacfd2017-11-03 10:12:19 -0700257 EXPECT_EQ(makeFloatColor(debugLayersUpdates ? 0x7f00ff00 : 0), glop.fill.color)
258 << "Should be transparent green if debugLayersUpdates";
Chris Craik37413282016-05-12 17:48:51 -0700259 } 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}
sergeyv89561e62016-08-04 16:21:07 -0700276
Greg Daniel98c78dad2017-01-04 14:45:56 -0500277RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, pathTextureSnapping) {
sergeyv89561e62016-08-04 16:21:07 -0700278 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);
John Reck1bcacfd2017-11-03 10:12:19 -0700283 testUnmergedGlopDispatch(renderThread, &op, [](const Glop& glop) {
sergeyv89561e62016-08-04 16:21:07 -0700284 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 });
Mike Reed260ab722016-10-07 15:59:20 -0400289}