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, 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 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, 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 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 {1, HAIRLINE, 100 [](const ResolvedRenderState& state) { 101 EXPECT_EQ(Rect(49.5f, 49.5f, 150.5f, 150.5f), state.clippedBounds); 102 }}, 103 {1, SEMI_HAIRLINE, 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, 109 [](const ResolvedRenderState& state) { 110 EXPECT_EQ(Rect(40, 40, 160, 160), state.clippedBounds); 111 }}, 112 113 // 3x3 scale: 114 {3, HAIRLINE, 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, 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, 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, 132 [](const ResolvedRenderState& state) { 133 EXPECT_EQ(Rect(24.5f, 24.5f, 75.5f, 75.5f), state.clippedBounds); 134 }}, 135 {0.5f, SEMI_HAIRLINE, 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 }}, 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 145TEST(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 166TEST(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 189TEST(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 216TEST(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