1 /*
2 * Copyright (C) 2015 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 <BakedOpState.h>
20 #include <ClipArea.h>
21 #include <RecordedOp.h>
22 #include <tests/common/TestUtils.h>
23
24 namespace android {
25 namespace uirenderer {
26
TEST(ResolvedRenderState,construct)27 TEST(ResolvedRenderState, construct) {
28 LinearAllocator allocator;
29 Matrix4 translate10x20;
30 translate10x20.loadTranslate(10, 20, 0);
31
32 SkPaint paint;
33 ClipRect clip(Rect(100, 200));
34 RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, &clip, &paint);
35 {
36 // recorded with transform, no parent transform
37 auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
38 ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false);
39 EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20);
40 EXPECT_EQ(Rect(100, 200), state.clipRect());
41 EXPECT_EQ(Rect(40, 60, 100, 200), state.clippedBounds); // translated and also clipped
42 EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
43 }
44 {
45 // recorded with transform and parent transform
46 auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
47 ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false);
48
49 Matrix4 expectedTranslate;
50 expectedTranslate.loadTranslate(20, 40, 0);
51 EXPECT_MATRIX_APPROX_EQ(expectedTranslate, state.transform);
52
53 // intersection of parent & transformed child clip
54 EXPECT_EQ(Rect(10, 20, 100, 200), state.clipRect());
55
56 // translated and also clipped
57 EXPECT_EQ(Rect(50, 80, 100, 200), state.clippedBounds);
58 EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
59 }
60 }
61
TEST(ResolvedRenderState,computeLocalSpaceClip)62 TEST(ResolvedRenderState, computeLocalSpaceClip) {
63 LinearAllocator allocator;
64 Matrix4 translate10x20;
65 translate10x20.loadTranslate(10, 20, 0);
66
67 SkPaint paint;
68 ClipRect clip(Rect(100, 200));
69 RectOp recordedOp(Rect(1000, 1000), translate10x20, &clip, &paint);
70 {
71 // recorded with transform, no parent transform
72 auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
73 ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false);
74 EXPECT_EQ(Rect(-10, -20, 90, 180), state.computeLocalSpaceClip())
75 << "Local clip rect should be 100x200, offset by -10,-20";
76 }
77 {
78 // recorded with transform + parent transform
79 auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
80 ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false);
81 EXPECT_EQ(Rect(-10, -20, 80, 160), state.computeLocalSpaceClip())
82 << "Local clip rect should be 90x190, offset by -10,-20";
83 }
84 }
85
86 const float HAIRLINE = 0.0f;
87
88 // Note: bounds will be conservative, but not precise for non-hairline
89 // - use approx bounds checks for these
90 const float SEMI_HAIRLINE = 0.3f;
91
92 struct StrokeTestCase {
93 float scale;
94 float strokeWidth;
95 const std::function<void(const ResolvedRenderState&)> validator;
96 };
97
98 const static StrokeTestCase sStrokeTestCases[] = {
99 {1, HAIRLINE,
__anon3ac3d9670102() 100 [](const ResolvedRenderState& state) {
101 EXPECT_EQ(Rect(49.5f, 49.5f, 150.5f, 150.5f), state.clippedBounds);
102 }},
103 {1, SEMI_HAIRLINE,
__anon3ac3d9670202() 104 [](const ResolvedRenderState& state) {
105 EXPECT_TRUE(state.clippedBounds.contains(49.5f, 49.5f, 150.5f, 150.5f));
106 EXPECT_TRUE(Rect(49, 49, 151, 151).contains(state.clippedBounds));
107 }},
108 {1, 20,
__anon3ac3d9670302() 109 [](const ResolvedRenderState& state) {
110 EXPECT_EQ(Rect(40, 40, 160, 160), state.clippedBounds);
111 }},
112
113 // 3x3 scale:
114 {3, HAIRLINE,
__anon3ac3d9670402() 115 [](const ResolvedRenderState& state) {
116 EXPECT_EQ(Rect(149.5f, 149.5f, 200, 200), state.clippedBounds);
117 EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
118 }},
119 {3, SEMI_HAIRLINE,
__anon3ac3d9670502() 120 [](const ResolvedRenderState& state) {
121 EXPECT_TRUE(state.clippedBounds.contains(149.5f, 149.5f, 200, 200));
122 EXPECT_TRUE(Rect(149, 149, 200, 200).contains(state.clippedBounds));
123 }},
124 {3, 20,
__anon3ac3d9670602() 125 [](const ResolvedRenderState& state) {
126 EXPECT_TRUE(state.clippedBounds.contains(120, 120, 200, 200));
127 EXPECT_TRUE(Rect(119, 119, 200, 200).contains(state.clippedBounds));
128 }},
129
130 // 0.5f x 0.5f scale
131 {0.5f, HAIRLINE,
__anon3ac3d9670702() 132 [](const ResolvedRenderState& state) {
133 EXPECT_EQ(Rect(24.5f, 24.5f, 75.5f, 75.5f), state.clippedBounds);
134 }},
135 {0.5f, SEMI_HAIRLINE,
__anon3ac3d9670802() 136 [](const ResolvedRenderState& state) {
137 EXPECT_TRUE(state.clippedBounds.contains(24.5f, 24.5f, 75.5f, 75.5f));
138 EXPECT_TRUE(Rect(24, 24, 76, 76).contains(state.clippedBounds));
139 }},
__anon3ac3d9670902() 140 {0.5f, 20, [](const ResolvedRenderState& state) {
141 EXPECT_TRUE(state.clippedBounds.contains(19.5f, 19.5f, 80.5f, 80.5f));
142 EXPECT_TRUE(Rect(19, 19, 81, 81).contains(state.clippedBounds));
143 }}};
144
TEST(ResolvedRenderState,construct_expandForStroke)145 TEST(ResolvedRenderState, construct_expandForStroke) {
146 LinearAllocator allocator;
147 // Loop over table of test cases and verify different combinations of stroke width and transform
148 for (auto&& testCase : sStrokeTestCases) {
149 SkPaint strokedPaint;
150 strokedPaint.setAntiAlias(true);
151 strokedPaint.setStyle(SkPaint::kStroke_Style);
152 strokedPaint.setStrokeWidth(testCase.strokeWidth);
153
154 ClipRect clip(Rect(200, 200));
155 RectOp recordedOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &strokedPaint);
156
157 Matrix4 snapshotMatrix;
158 snapshotMatrix.loadScale(testCase.scale, testCase.scale, 1);
159 auto parentSnapshot = TestUtils::makeSnapshot(snapshotMatrix, Rect(200, 200));
160
161 ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, true, false);
162 testCase.validator(state);
163 }
164 }
165
TEST(BakedOpState,tryConstruct)166 TEST(BakedOpState, tryConstruct) {
167 Matrix4 translate100x0;
168 translate100x0.loadTranslate(100, 0, 0);
169
170 SkPaint paint;
171 ClipRect clip(Rect(100, 200));
172
173 LinearAllocator allocator;
174 RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), &clip, &paint);
175 auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
176 EXPECT_NE(nullptr, BakedOpState::tryConstruct(allocator, *snapshot, successOp))
177 << "successOp NOT rejected by clip, so should be constructed";
178 size_t successAllocSize = allocator.usedSize();
179 EXPECT_LE(64u, successAllocSize) << "relatively large alloc for non-rejected op";
180
181 RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, &clip, &paint);
182 EXPECT_EQ(nullptr, BakedOpState::tryConstruct(allocator, *snapshot, rejectOp))
183 << "rejectOp rejected by clip, so should not be constructed";
184
185 // NOTE: this relies on the clip having already been serialized by the op above
186 EXPECT_EQ(successAllocSize, allocator.usedSize()) << "no extra allocation used for rejected op";
187 }
188
TEST(BakedOpState,tryShadowOpConstruct)189 TEST(BakedOpState, tryShadowOpConstruct) {
190 Matrix4 translate10x20;
191 translate10x20.loadTranslate(10, 20, 0);
192
193 LinearAllocator allocator;
194 {
195 auto snapshot = TestUtils::makeSnapshot(translate10x20, Rect()); // Note: empty clip
196 BakedOpState* bakedState =
197 BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
198
199 EXPECT_EQ(nullptr, bakedState) << "op should be rejected by clip, so not constructed";
200 EXPECT_EQ(0u, allocator.usedSize()) << "no serialization, even for clip,"
201 "since op is quick rejected based on snapshot clip";
202 }
203 {
204 auto snapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
205 BakedOpState* bakedState =
206 BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
207
208 ASSERT_NE(nullptr, bakedState) << "NOT rejected by clip, so op should be constructed";
209 EXPECT_LE(64u, allocator.usedSize()) << "relatively large alloc for non-rejected op";
210
211 EXPECT_MATRIX_APPROX_EQ(translate10x20, bakedState->computedState.transform);
212 EXPECT_EQ(Rect(100, 200), bakedState->computedState.clippedBounds);
213 }
214 }
215
TEST(BakedOpState,tryStrokeableOpConstruct)216 TEST(BakedOpState, tryStrokeableOpConstruct) {
217 LinearAllocator allocator;
218 {
219 // check regular rejection
220 SkPaint paint;
221 paint.setStyle(SkPaint::kStrokeAndFill_Style);
222 paint.setStrokeWidth(0.0f);
223 ClipRect clip(Rect(100, 200));
224 RectOp rejectOp(Rect(100, 200), Matrix4::identity(), &clip, &paint);
225 auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect()); // Note: empty clip
226 auto bakedState = BakedOpState::tryStrokeableOpConstruct(
227 allocator, *snapshot, rejectOp, BakedOpState::StrokeBehavior::StyleDefined, false);
228
229 EXPECT_EQ(nullptr, bakedState);
230 EXPECT_GT(8u,
231 allocator.usedSize()); // no significant allocation space used for rejected op
232 }
233 {
234 // check simple unscaled expansion
235 SkPaint paint;
236 paint.setStyle(SkPaint::kStrokeAndFill_Style);
237 paint.setStrokeWidth(10.0f);
238 ClipRect clip(Rect(200, 200));
239 RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
240 auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
241 auto bakedState = BakedOpState::tryStrokeableOpConstruct(
242 allocator, *snapshot, rejectOp, BakedOpState::StrokeBehavior::StyleDefined, false);
243
244 ASSERT_NE(nullptr, bakedState);
245 EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds);
246 EXPECT_EQ(0, bakedState->computedState.clipSideFlags);
247 }
248 {
249 // check simple unscaled expansion, and fill style with stroke forced
250 SkPaint paint;
251 paint.setStyle(SkPaint::kFill_Style);
252 paint.setStrokeWidth(10.0f);
253 ClipRect clip(Rect(200, 200));
254 RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
255 auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
256 auto bakedState = BakedOpState::tryStrokeableOpConstruct(
257 allocator, *snapshot, rejectOp, BakedOpState::StrokeBehavior::Forced, false);
258
259 ASSERT_NE(nullptr, bakedState);
260 EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds);
261 EXPECT_EQ(0, bakedState->computedState.clipSideFlags);
262 }
263 }
264
265 } // namespace uirenderer
266 } // namespace android
267