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