Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1 | |
| 2 | /* |
| 3 | * Copyright 2020 Google LLC |
| 4 | * |
| 5 | * Use of this source code is governed by a BSD-style license that can be |
| 6 | * found in the LICENSE file. |
| 7 | */ |
| 8 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 9 | #include "src/gpu/v1/ClipStack.h" |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 10 | #include "tests/Test.h" |
| 11 | |
| 12 | #include "include/core/SkPath.h" |
| 13 | #include "include/core/SkRRect.h" |
| 14 | #include "include/core/SkRect.h" |
| 15 | #include "include/core/SkRegion.h" |
| 16 | #include "include/core/SkShader.h" |
| 17 | #include "include/gpu/GrDirectContext.h" |
| 18 | #include "src/core/SkMatrixProvider.h" |
| 19 | #include "src/core/SkRRectPriv.h" |
| 20 | #include "src/core/SkRectPriv.h" |
Adlai Holler | a069304 | 2020-10-14 11:23:11 -0400 | [diff] [blame] | 21 | #include "src/gpu/GrDirectContextPriv.h" |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 22 | #include "src/gpu/GrProxyProvider.h" |
Robert Phillips | 06273bc | 2021-08-11 15:43:50 -0400 | [diff] [blame] | 23 | #include "src/gpu/ops/GrDrawOp.h" |
Robert Phillips | 4dca831 | 2021-07-28 15:13:20 -0400 | [diff] [blame] | 24 | #include "src/gpu/v1/SurfaceDrawContext_v1.h" |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 25 | |
| 26 | namespace { |
| 27 | |
| 28 | class TestCaseBuilder; |
| 29 | class ElementsBuilder; |
| 30 | |
| 31 | enum class SavePolicy { |
| 32 | kNever, |
| 33 | kAtStart, |
| 34 | kAtEnd, |
| 35 | kBetweenEveryOp |
| 36 | }; |
| 37 | // TODO: We could add a RestorePolicy enum that tests different places to restore, but that would |
| 38 | // make defining the test expectations and order independence more cumbersome. |
| 39 | |
| 40 | class TestCase { |
| 41 | public: |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 42 | using ClipStack = skgpu::v1::ClipStack; |
| 43 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 44 | // Provides fluent API to describe actual clip commands and expected clip elements: |
| 45 | // TestCase test = TestCase::Build("example", deviceBounds) |
| 46 | // .actual().rect(r, GrAA::kYes, SkClipOp::kIntersect) |
| 47 | // .localToDevice(matrix) |
| 48 | // .nonAA() |
| 49 | // .difference() |
| 50 | // .path(p1) |
| 51 | // .path(p2) |
| 52 | // .finishElements() |
| 53 | // .expectedState(kDeviceRect) |
| 54 | // .expectedBounds(r.roundOut()) |
| 55 | // .expect().rect(r, GrAA::kYes, SkClipOp::kIntersect) |
| 56 | // .finishElements() |
| 57 | // .finishTest(); |
| 58 | static TestCaseBuilder Build(const char* name, const SkIRect& deviceBounds); |
| 59 | |
| 60 | void run(const std::vector<int>& order, SavePolicy policy, skiatest::Reporter* reporter) const; |
| 61 | |
| 62 | const SkIRect& deviceBounds() const { return fDeviceBounds; } |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 63 | ClipStack::ClipState expectedState() const { return fExpectedState; } |
| 64 | const std::vector<ClipStack::Element>& initialElements() const { return fElements; } |
| 65 | const std::vector<ClipStack::Element>& expectedElements() const { return fExpectedElements; } |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 66 | |
| 67 | private: |
| 68 | friend class TestCaseBuilder; |
| 69 | |
| 70 | TestCase(SkString name, |
| 71 | const SkIRect& deviceBounds, |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 72 | ClipStack::ClipState expectedState, |
| 73 | std::vector<ClipStack::Element> actual, |
| 74 | std::vector<ClipStack::Element> expected) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 75 | : fName(name) |
| 76 | , fElements(std::move(actual)) |
| 77 | , fDeviceBounds(deviceBounds) |
| 78 | , fExpectedElements(std::move(expected)) |
| 79 | , fExpectedState(expectedState) {} |
| 80 | |
| 81 | SkString getTestName(const std::vector<int>& order, SavePolicy policy) const; |
| 82 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 83 | // This may be tighter than ClipStack::getConservativeBounds() because this always accounts |
| 84 | // for difference ops, whereas ClipStack only sometimes can subtract the inner bounds for a |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 85 | // difference op. |
| 86 | std::pair<SkIRect, bool> getOptimalBounds() const; |
| 87 | |
| 88 | SkString fName; |
| 89 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 90 | // The input shapes+state to ClipStack |
| 91 | std::vector<ClipStack::Element> fElements; |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 92 | SkIRect fDeviceBounds; |
| 93 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 94 | // The expected output of iterating over the ClipStack after all fElements are added, although |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 95 | // order is not important |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 96 | std::vector<ClipStack::Element> fExpectedElements; |
| 97 | ClipStack::ClipState fExpectedState; |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 98 | }; |
| 99 | |
| 100 | class ElementsBuilder { |
| 101 | public: |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 102 | using ClipStack = skgpu::v1::ClipStack; |
| 103 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 104 | // Update the default matrix, aa, and op state for elements that are added. |
| 105 | ElementsBuilder& localToDevice(const SkMatrix& m) { fLocalToDevice = m; return *this; } |
| 106 | ElementsBuilder& aa() { fAA = GrAA::kYes; return *this; } |
| 107 | ElementsBuilder& nonAA() { fAA = GrAA::kNo; return *this; } |
| 108 | ElementsBuilder& intersect() { fOp = SkClipOp::kIntersect; return *this; } |
| 109 | ElementsBuilder& difference() { fOp = SkClipOp::kDifference; return *this; } |
| 110 | |
| 111 | // Add rect, rrect, or paths to the list of elements, possibly overriding the last set |
| 112 | // matrix, aa, and op state. |
| 113 | ElementsBuilder& rect(const SkRect& rect) { |
| 114 | return this->rect(rect, fLocalToDevice, fAA, fOp); |
| 115 | } |
| 116 | ElementsBuilder& rect(const SkRect& rect, GrAA aa, SkClipOp op) { |
| 117 | return this->rect(rect, fLocalToDevice, aa, op); |
| 118 | } |
| 119 | ElementsBuilder& rect(const SkRect& rect, const SkMatrix& m, GrAA aa, SkClipOp op) { |
| 120 | fElements->push_back({GrShape(rect), m, op, aa}); |
| 121 | return *this; |
| 122 | } |
| 123 | |
| 124 | ElementsBuilder& rrect(const SkRRect& rrect) { |
| 125 | return this->rrect(rrect, fLocalToDevice, fAA, fOp); |
| 126 | } |
| 127 | ElementsBuilder& rrect(const SkRRect& rrect, GrAA aa, SkClipOp op) { |
| 128 | return this->rrect(rrect, fLocalToDevice, aa, op); |
| 129 | } |
| 130 | ElementsBuilder& rrect(const SkRRect& rrect, const SkMatrix& m, GrAA aa, SkClipOp op) { |
| 131 | fElements->push_back({GrShape(rrect), m, op, aa}); |
| 132 | return *this; |
| 133 | } |
| 134 | |
| 135 | ElementsBuilder& path(const SkPath& path) { |
| 136 | return this->path(path, fLocalToDevice, fAA, fOp); |
| 137 | } |
| 138 | ElementsBuilder& path(const SkPath& path, GrAA aa, SkClipOp op) { |
| 139 | return this->path(path, fLocalToDevice, aa, op); |
| 140 | } |
| 141 | ElementsBuilder& path(const SkPath& path, const SkMatrix& m, GrAA aa, SkClipOp op) { |
| 142 | fElements->push_back({GrShape(path), m, op, aa}); |
| 143 | return *this; |
| 144 | } |
| 145 | |
| 146 | // Finish and return the original test case builder |
| 147 | TestCaseBuilder& finishElements() { |
| 148 | return *fBuilder; |
| 149 | } |
| 150 | |
| 151 | private: |
| 152 | friend class TestCaseBuilder; |
| 153 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 154 | ElementsBuilder(TestCaseBuilder* builder, std::vector<ClipStack::Element>* elements) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 155 | : fBuilder(builder) |
| 156 | , fElements(elements) {} |
| 157 | |
| 158 | SkMatrix fLocalToDevice = SkMatrix::I(); |
| 159 | GrAA fAA = GrAA::kNo; |
| 160 | SkClipOp fOp = SkClipOp::kIntersect; |
| 161 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 162 | TestCaseBuilder* fBuilder; |
| 163 | std::vector<ClipStack::Element>* fElements; |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 164 | }; |
| 165 | |
| 166 | class TestCaseBuilder { |
| 167 | public: |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 168 | using ClipStack = skgpu::v1::ClipStack; |
| 169 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 170 | ElementsBuilder actual() { return ElementsBuilder(this, &fActualElements); } |
| 171 | ElementsBuilder expect() { return ElementsBuilder(this, &fExpectedElements); } |
| 172 | |
| 173 | TestCaseBuilder& expectActual() { |
| 174 | fExpectedElements = fActualElements; |
| 175 | return *this; |
| 176 | } |
| 177 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 178 | TestCaseBuilder& state(ClipStack::ClipState state) { |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 179 | fExpectedState = state; |
| 180 | return *this; |
| 181 | } |
| 182 | |
| 183 | TestCase finishTest() { |
| 184 | TestCase test(fName, fDeviceBounds, fExpectedState, |
| 185 | std::move(fActualElements), std::move(fExpectedElements)); |
| 186 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 187 | fExpectedState = ClipStack::ClipState::kWideOpen; |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 188 | return test; |
| 189 | } |
| 190 | |
| 191 | private: |
| 192 | friend class TestCase; |
| 193 | |
| 194 | explicit TestCaseBuilder(const char* name, const SkIRect& deviceBounds) |
| 195 | : fName(name) |
| 196 | , fDeviceBounds(deviceBounds) |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 197 | , fExpectedState(ClipStack::ClipState::kWideOpen) {} |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 198 | |
| 199 | SkString fName; |
| 200 | SkIRect fDeviceBounds; |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 201 | ClipStack::ClipState fExpectedState; |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 202 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 203 | std::vector<ClipStack::Element> fActualElements; |
| 204 | std::vector<ClipStack::Element> fExpectedElements; |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 205 | }; |
| 206 | |
| 207 | TestCaseBuilder TestCase::Build(const char* name, const SkIRect& deviceBounds) { |
| 208 | return TestCaseBuilder(name, deviceBounds); |
| 209 | } |
| 210 | |
| 211 | SkString TestCase::getTestName(const std::vector<int>& order, SavePolicy policy) const { |
| 212 | SkString name = fName; |
| 213 | |
| 214 | SkString policyName; |
| 215 | switch(policy) { |
| 216 | case SavePolicy::kNever: |
| 217 | policyName = "never"; |
| 218 | break; |
| 219 | case SavePolicy::kAtStart: |
| 220 | policyName = "start"; |
| 221 | break; |
| 222 | case SavePolicy::kAtEnd: |
| 223 | policyName = "end"; |
| 224 | break; |
| 225 | case SavePolicy::kBetweenEveryOp: |
| 226 | policyName = "between"; |
| 227 | break; |
| 228 | } |
| 229 | |
| 230 | name.appendf("(save %s, order [", policyName.c_str()); |
| 231 | for (size_t i = 0; i < order.size(); ++i) { |
| 232 | if (i > 0) { |
| 233 | name.append(","); |
| 234 | } |
| 235 | name.appendf("%d", order[i]); |
| 236 | } |
| 237 | name.append("])"); |
| 238 | return name; |
| 239 | } |
| 240 | |
| 241 | std::pair<SkIRect, bool> TestCase::getOptimalBounds() const { |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 242 | if (fExpectedState == ClipStack::ClipState::kEmpty) { |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 243 | return {SkIRect::MakeEmpty(), true}; |
| 244 | } |
| 245 | |
| 246 | bool expectOptimal = true; |
| 247 | SkRegion region(fDeviceBounds); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 248 | for (const ClipStack::Element& e : fExpectedElements) { |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 249 | bool intersect = (e.fOp == SkClipOp::kIntersect && !e.fShape.inverted()) || |
| 250 | (e.fOp == SkClipOp::kDifference && e.fShape.inverted()); |
| 251 | |
| 252 | SkIRect elementBounds; |
| 253 | SkRegion::Op op; |
| 254 | if (intersect) { |
| 255 | op = SkRegion::kIntersect_Op; |
| 256 | expectOptimal &= e.fLocalToDevice.isIdentity(); |
| 257 | elementBounds = GrClip::GetPixelIBounds(e.fLocalToDevice.mapRect(e.fShape.bounds()), |
| 258 | e.fAA, GrClip::BoundsType::kExterior); |
| 259 | } else { |
| 260 | op = SkRegion::kDifference_Op; |
| 261 | expectOptimal = false; |
| 262 | if (e.fShape.isRect() && e.fLocalToDevice.isIdentity()) { |
| 263 | elementBounds = GrClip::GetPixelIBounds(e.fShape.rect(), e.fAA, |
| 264 | GrClip::BoundsType::kInterior); |
| 265 | } else if (e.fShape.isRRect() && e.fLocalToDevice.isIdentity()) { |
| 266 | elementBounds = GrClip::GetPixelIBounds(SkRRectPriv::InnerBounds(e.fShape.rrect()), |
| 267 | e.fAA, GrClip::BoundsType::kInterior); |
| 268 | } else { |
| 269 | elementBounds = SkIRect::MakeEmpty(); |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | region.op(SkRegion(elementBounds), op); |
| 274 | } |
| 275 | return {region.getBounds(), expectOptimal}; |
| 276 | } |
| 277 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 278 | static bool compare_elements(const skgpu::v1::ClipStack::Element& a, |
| 279 | const skgpu::v1::ClipStack::Element& b) { |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 280 | if (a.fAA != b.fAA || a.fOp != b.fOp || a.fLocalToDevice != b.fLocalToDevice || |
| 281 | a.fShape.type() != b.fShape.type()) { |
| 282 | return false; |
| 283 | } |
| 284 | switch(a.fShape.type()) { |
| 285 | case GrShape::Type::kRect: |
| 286 | return a.fShape.rect() == b.fShape.rect(); |
| 287 | case GrShape::Type::kRRect: |
| 288 | return a.fShape.rrect() == b.fShape.rrect(); |
| 289 | case GrShape::Type::kPath: |
| 290 | // A path's points are never transformed, the only modification is fill type which does |
| 291 | // not change the generation ID. For convex polygons, we check == so that more complex |
| 292 | // test cases can be evaluated. |
| 293 | return a.fShape.path().getGenerationID() == b.fShape.path().getGenerationID() || |
| 294 | (a.fShape.convex() && |
| 295 | a.fShape.segmentMask() == SkPathSegmentMask::kLine_SkPathSegmentMask && |
| 296 | a.fShape.path() == b.fShape.path()); |
| 297 | default: |
| 298 | SkDEBUGFAIL("Shape type not handled by test case yet."); |
| 299 | return false; |
| 300 | } |
| 301 | } |
| 302 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 303 | void TestCase::run(const std::vector<int>& order, |
| 304 | SavePolicy policy, |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 305 | skiatest::Reporter* reporter) const { |
| 306 | SkASSERT(fElements.size() == order.size()); |
| 307 | |
| 308 | SkSimpleMatrixProvider matrixProvider(SkMatrix::I()); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 309 | ClipStack cs(fDeviceBounds, &matrixProvider, false); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 310 | |
| 311 | if (policy == SavePolicy::kAtStart) { |
| 312 | cs.save(); |
| 313 | } |
| 314 | |
| 315 | for (int i : order) { |
| 316 | if (policy == SavePolicy::kBetweenEveryOp) { |
| 317 | cs.save(); |
| 318 | } |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 319 | const ClipStack::Element& e = fElements[i]; |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 320 | switch(e.fShape.type()) { |
| 321 | case GrShape::Type::kRect: |
| 322 | cs.clipRect(e.fLocalToDevice, e.fShape.rect(), e.fAA, e.fOp); |
| 323 | break; |
| 324 | case GrShape::Type::kRRect: |
| 325 | cs.clipRRect(e.fLocalToDevice, e.fShape.rrect(), e.fAA, e.fOp); |
| 326 | break; |
| 327 | case GrShape::Type::kPath: |
| 328 | cs.clipPath(e.fLocalToDevice, e.fShape.path(), e.fAA, e.fOp); |
| 329 | break; |
| 330 | default: |
| 331 | SkDEBUGFAIL("Shape type not handled by test case yet."); |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | if (policy == SavePolicy::kAtEnd) { |
| 336 | cs.save(); |
| 337 | } |
| 338 | |
| 339 | // Now validate |
| 340 | SkString name = this->getTestName(order, policy); |
| 341 | REPORTER_ASSERT(reporter, cs.clipState() == fExpectedState, |
| 342 | "%s, clip state expected %d, actual %d", |
| 343 | name.c_str(), (int) fExpectedState, (int) cs.clipState()); |
| 344 | SkIRect actualBounds = cs.getConservativeBounds(); |
| 345 | SkIRect optimalBounds; |
| 346 | bool expectOptimal; |
| 347 | std::tie(optimalBounds, expectOptimal) = this->getOptimalBounds(); |
| 348 | |
| 349 | if (expectOptimal) { |
| 350 | REPORTER_ASSERT(reporter, actualBounds == optimalBounds, |
| 351 | "%s, bounds expected [%d %d %d %d], actual [%d %d %d %d]", |
| 352 | name.c_str(), optimalBounds.fLeft, optimalBounds.fTop, |
| 353 | optimalBounds.fRight, optimalBounds.fBottom, |
| 354 | actualBounds.fLeft, actualBounds.fTop, |
| 355 | actualBounds.fRight, actualBounds.fBottom); |
| 356 | } else { |
| 357 | REPORTER_ASSERT(reporter, actualBounds.contains(optimalBounds), |
| 358 | "%s, bounds are not conservative, optimal [%d %d %d %d], actual [%d %d %d %d]", |
| 359 | name.c_str(), optimalBounds.fLeft, optimalBounds.fTop, |
| 360 | optimalBounds.fRight, optimalBounds.fBottom, |
| 361 | actualBounds.fLeft, actualBounds.fTop, |
| 362 | actualBounds.fRight, actualBounds.fBottom); |
| 363 | } |
| 364 | |
| 365 | size_t matchedElements = 0; |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 366 | for (const ClipStack::Element& a : cs) { |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 367 | bool found = false; |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 368 | for (const ClipStack::Element& e : fExpectedElements) { |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 369 | if (compare_elements(a, e)) { |
| 370 | // shouldn't match multiple expected elements or it's a bad test case |
| 371 | SkASSERT(!found); |
| 372 | found = true; |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | REPORTER_ASSERT(reporter, found, |
| 377 | "%s, unexpected clip element in stack: shape %d, aa %d, op %d", |
| 378 | name.c_str(), (int) a.fShape.type(), (int) a.fAA, (int) a.fOp); |
| 379 | matchedElements += found ? 1 : 0; |
| 380 | } |
| 381 | REPORTER_ASSERT(reporter, matchedElements == fExpectedElements.size(), |
| 382 | "%s, did not match all expected elements: expected %d but matched only %d", |
| 383 | name.c_str(), fExpectedElements.size(), matchedElements); |
| 384 | |
| 385 | // Validate restoration behavior |
| 386 | if (policy == SavePolicy::kAtEnd) { |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 387 | ClipStack::ClipState oldState = cs.clipState(); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 388 | cs.restore(); |
| 389 | REPORTER_ASSERT(reporter, cs.clipState() == oldState, |
| 390 | "%s, restoring an empty save record should not change clip state: " |
| 391 | "expected %d but got %d", (int) oldState, (int) cs.clipState()); |
| 392 | } else if (policy != SavePolicy::kNever) { |
| 393 | int restoreCount = policy == SavePolicy::kAtStart ? 1 : (int) order.size(); |
| 394 | for (int i = 0; i < restoreCount; ++i) { |
| 395 | cs.restore(); |
| 396 | } |
| 397 | // Should be wide open if everything is restored to base state |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 398 | REPORTER_ASSERT(reporter, cs.clipState() == ClipStack::ClipState::kWideOpen, |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 399 | "%s, restore should make stack become wide-open, not %d", |
| 400 | (int) cs.clipState()); |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | // All clip operations are commutative so applying actual elements in every possible order should |
| 405 | // always produce the same set of expected elements. |
| 406 | static void run_test_case(skiatest::Reporter* r, const TestCase& test) { |
| 407 | int n = (int) test.initialElements().size(); |
| 408 | std::vector<int> order(n); |
| 409 | std::vector<int> stack(n); |
| 410 | |
| 411 | // Initial order sequence and zeroed stack |
| 412 | for (int i = 0; i < n; ++i) { |
| 413 | order[i] = i; |
| 414 | stack[i] = 0; |
| 415 | } |
| 416 | |
| 417 | auto runTest = [&]() { |
| 418 | static const SavePolicy kPolicies[] = { SavePolicy::kNever, SavePolicy::kAtStart, |
| 419 | SavePolicy::kAtEnd, SavePolicy::kBetweenEveryOp }; |
| 420 | for (auto policy : kPolicies) { |
| 421 | test.run(order, policy, r); |
| 422 | } |
| 423 | }; |
| 424 | |
| 425 | // Heap's algorithm (non-recursive) to generate every permutation over the test case's elements |
| 426 | // https://en.wikipedia.org/wiki/Heap%27s_algorithm |
| 427 | runTest(); |
| 428 | |
| 429 | static constexpr int kMaxRuns = 720; // Don't run more than 6! configurations, even if n > 6 |
| 430 | int testRuns = 1; |
| 431 | |
| 432 | int i = 0; |
| 433 | while (i < n && testRuns < kMaxRuns) { |
| 434 | if (stack[i] < i) { |
| 435 | using std::swap; |
| 436 | if (i % 2 == 0) { |
| 437 | swap(order[0], order[i]); |
| 438 | } else { |
| 439 | swap(order[stack[i]], order[i]); |
| 440 | } |
| 441 | |
| 442 | runTest(); |
| 443 | stack[i]++; |
| 444 | i = 0; |
| 445 | testRuns++; |
| 446 | } else { |
| 447 | stack[i] = 0; |
| 448 | ++i; |
| 449 | } |
| 450 | } |
| 451 | } |
| 452 | |
| 453 | static SkPath make_octagon(const SkRect& r, SkScalar lr, SkScalar tb) { |
| 454 | SkPath p; |
| 455 | p.moveTo(r.fLeft + lr, r.fTop); |
| 456 | p.lineTo(r.fRight - lr, r.fTop); |
| 457 | p.lineTo(r.fRight, r.fTop + tb); |
| 458 | p.lineTo(r.fRight, r.fBottom - tb); |
| 459 | p.lineTo(r.fRight - lr, r.fBottom); |
| 460 | p.lineTo(r.fLeft + lr, r.fBottom); |
| 461 | p.lineTo(r.fLeft, r.fBottom - tb); |
| 462 | p.lineTo(r.fLeft, r.fTop + tb); |
| 463 | p.close(); |
| 464 | return p; |
| 465 | } |
| 466 | |
| 467 | static SkPath make_octagon(const SkRect& r) { |
| 468 | SkScalar lr = 0.3f * r.width(); |
| 469 | SkScalar tb = 0.3f * r.height(); |
| 470 | return make_octagon(r, lr, tb); |
| 471 | } |
| 472 | |
| 473 | static constexpr SkIRect kDeviceBounds = {0, 0, 100, 100}; |
| 474 | |
Chris Dalton | b1e8f85 | 2021-06-23 13:36:51 -0600 | [diff] [blame] | 475 | class NoOp : public GrDrawOp { |
| 476 | public: |
Chris Dalton | ea46ef3 | 2021-07-12 15:09:06 -0600 | [diff] [blame] | 477 | static NoOp* Get() { |
| 478 | static NoOp gNoOp; |
Chris Dalton | b1e8f85 | 2021-06-23 13:36:51 -0600 | [diff] [blame] | 479 | return &gNoOp; |
| 480 | } |
| 481 | private: |
| 482 | DEFINE_OP_CLASS_ID |
| 483 | NoOp() : GrDrawOp(ClassID()) {} |
| 484 | const char* name() const override { return "NoOp"; } |
| 485 | GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override { |
| 486 | return GrProcessorSet::EmptySetAnalysis(); |
| 487 | } |
| 488 | void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*, const |
| 489 | GrDstProxyView&, GrXferBarrierFlags, GrLoadOp) override {} |
| 490 | void onPrepare(GrOpFlushState*) override {} |
| 491 | void onExecute(GrOpFlushState*, const SkRect&) override {} |
| 492 | }; |
| 493 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 494 | } // anonymous namespace |
| 495 | |
| 496 | /////////////////////////////////////////////////////////////////////////////// |
| 497 | // These tests use the TestCase infrastructure to define clip stacks and |
| 498 | // associated expectations. |
| 499 | |
| 500 | // Tests that the initialized state of the clip stack is wide-open |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 501 | DEF_TEST(ClipStack_InitialState, r) { |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 502 | run_test_case(r, TestCase::Build("initial-state", SkIRect::MakeWH(100, 100)).finishTest()); |
| 503 | } |
| 504 | |
| 505 | // Tests that intersection of rects combine to a single element when they have the same AA type, |
| 506 | // or are pixel-aligned. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 507 | DEF_TEST(ClipStack_RectRectAACombine, r) { |
| 508 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 509 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 510 | SkRect pixelAligned = {0, 0, 10, 10}; |
| 511 | SkRect fracRect1 = pixelAligned.makeOffset(5.3f, 3.7f); |
| 512 | SkRect fracRect2 = {fracRect1.fLeft + 0.75f * fracRect1.width(), |
| 513 | fracRect1.fTop + 0.75f * fracRect1.height(), |
| 514 | fracRect1.fRight, fracRect1.fBottom}; |
| 515 | |
| 516 | SkRect fracIntersect; |
| 517 | SkAssertResult(fracIntersect.intersect(fracRect1, fracRect2)); |
| 518 | SkRect alignedIntersect; |
| 519 | SkAssertResult(alignedIntersect.intersect(pixelAligned, fracRect1)); |
| 520 | |
| 521 | // Both AA combine to one element |
| 522 | run_test_case(r, TestCase::Build("aa", kDeviceBounds) |
| 523 | .actual().aa().intersect() |
| 524 | .rect(fracRect1).rect(fracRect2) |
| 525 | .finishElements() |
| 526 | .expect().aa().intersect().rect(fracIntersect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 527 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 528 | .finishTest()); |
| 529 | |
| 530 | // Both non-AA combine to one element |
| 531 | run_test_case(r, TestCase::Build("nonaa", kDeviceBounds) |
| 532 | .actual().nonAA().intersect() |
| 533 | .rect(fracRect1).rect(fracRect2) |
| 534 | .finishElements() |
| 535 | .expect().nonAA().intersect().rect(fracIntersect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 536 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 537 | .finishTest()); |
| 538 | |
| 539 | // Pixel-aligned AA and non-AA combine |
| 540 | run_test_case(r, TestCase::Build("aligned-aa+nonaa", kDeviceBounds) |
| 541 | .actual().intersect() |
| 542 | .aa().rect(pixelAligned).nonAA().rect(fracRect1) |
| 543 | .finishElements() |
| 544 | .expect().nonAA().intersect().rect(alignedIntersect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 545 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 546 | .finishTest()); |
| 547 | |
| 548 | // AA and pixel-aligned non-AA combine |
| 549 | run_test_case(r, TestCase::Build("aa+aligned-nonaa", kDeviceBounds) |
| 550 | .actual().intersect() |
| 551 | .aa().rect(fracRect1).nonAA().rect(pixelAligned) |
| 552 | .finishElements() |
| 553 | .expect().aa().intersect().rect(alignedIntersect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 554 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 555 | .finishTest()); |
| 556 | |
| 557 | // Other mixed AA modes do not combine |
| 558 | run_test_case(r, TestCase::Build("aa+nonaa", kDeviceBounds) |
| 559 | .actual().intersect() |
| 560 | .aa().rect(fracRect1).nonAA().rect(fracRect2) |
| 561 | .finishElements() |
| 562 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 563 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 564 | .finishTest()); |
| 565 | } |
| 566 | |
| 567 | // Tests that an intersection and a difference op do not combine, even if they would have if both |
| 568 | // were intersection ops. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 569 | DEF_TEST(ClipStack_DifferenceNoCombine, r) { |
| 570 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 571 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 572 | SkRect r1 = {15.f, 14.f, 23.22f, 58.2f}; |
| 573 | SkRect r2 = r1.makeOffset(5.f, 8.f); |
| 574 | SkASSERT(r1.intersects(r2)); |
| 575 | |
| 576 | run_test_case(r, TestCase::Build("no-combine", kDeviceBounds) |
| 577 | .actual().aa().intersect().rect(r1) |
| 578 | .difference().rect(r2) |
| 579 | .finishElements() |
| 580 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 581 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 582 | .finishTest()); |
| 583 | } |
| 584 | |
| 585 | // Tests that intersection of rects in the same coordinate space can still be combined, but do not |
| 586 | // when the spaces differ. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 587 | DEF_TEST(ClipStack_RectRectNonAxisAligned, r) { |
| 588 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 589 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 590 | SkRect pixelAligned = {0, 0, 10, 10}; |
| 591 | SkRect fracRect1 = pixelAligned.makeOffset(5.3f, 3.7f); |
| 592 | SkRect fracRect2 = {fracRect1.fLeft + 0.75f * fracRect1.width(), |
| 593 | fracRect1.fTop + 0.75f * fracRect1.height(), |
| 594 | fracRect1.fRight, fracRect1.fBottom}; |
| 595 | |
| 596 | SkRect fracIntersect; |
| 597 | SkAssertResult(fracIntersect.intersect(fracRect1, fracRect2)); |
| 598 | |
| 599 | SkMatrix lm = SkMatrix::RotateDeg(45.f); |
| 600 | |
| 601 | // Both AA combine |
| 602 | run_test_case(r, TestCase::Build("aa", kDeviceBounds) |
| 603 | .actual().aa().intersect().localToDevice(lm) |
| 604 | .rect(fracRect1).rect(fracRect2) |
| 605 | .finishElements() |
| 606 | .expect().aa().intersect().localToDevice(lm) |
| 607 | .rect(fracIntersect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 608 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 609 | .finishTest()); |
| 610 | |
| 611 | // Both non-AA combine |
| 612 | run_test_case(r, TestCase::Build("nonaa", kDeviceBounds) |
| 613 | .actual().nonAA().intersect().localToDevice(lm) |
| 614 | .rect(fracRect1).rect(fracRect2) |
| 615 | .finishElements() |
| 616 | .expect().nonAA().intersect().localToDevice(lm) |
| 617 | .rect(fracIntersect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 618 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 619 | .finishTest()); |
| 620 | |
| 621 | // Integer-aligned coordinates under a local matrix with mixed AA don't combine, though |
| 622 | run_test_case(r, TestCase::Build("local-aa", kDeviceBounds) |
| 623 | .actual().intersect().localToDevice(lm) |
| 624 | .aa().rect(pixelAligned).nonAA().rect(fracRect1) |
| 625 | .finishElements() |
| 626 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 627 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 628 | .finishTest()); |
| 629 | } |
| 630 | |
| 631 | // Tests that intersection of two round rects can simplify to a single round rect when they have |
| 632 | // the same AA type. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 633 | DEF_TEST(ClipStack_RRectRRectAACombine, r) { |
| 634 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 635 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 636 | SkRRect r1 = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 2.f, 2.f); |
| 637 | SkRRect r2 = r1.makeOffset(6.f, 6.f); |
| 638 | |
| 639 | SkRRect intersect = SkRRectPriv::ConservativeIntersect(r1, r2); |
| 640 | SkASSERT(!intersect.isEmpty()); |
| 641 | |
| 642 | // Both AA combine |
| 643 | run_test_case(r, TestCase::Build("aa", kDeviceBounds) |
| 644 | .actual().aa().intersect() |
| 645 | .rrect(r1).rrect(r2) |
| 646 | .finishElements() |
| 647 | .expect().aa().intersect().rrect(intersect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 648 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 649 | .finishTest()); |
| 650 | |
| 651 | // Both non-AA combine |
| 652 | run_test_case(r, TestCase::Build("nonaa", kDeviceBounds) |
| 653 | .actual().nonAA().intersect() |
| 654 | .rrect(r1).rrect(r2) |
| 655 | .finishElements() |
| 656 | .expect().nonAA().intersect().rrect(intersect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 657 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 658 | .finishTest()); |
| 659 | |
| 660 | // Mixed do not combine |
| 661 | run_test_case(r, TestCase::Build("aa+nonaa", kDeviceBounds) |
| 662 | .actual().intersect() |
| 663 | .aa().rrect(r1).nonAA().rrect(r2) |
| 664 | .finishElements() |
| 665 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 666 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 667 | .finishTest()); |
| 668 | |
| 669 | // Same AA state can combine in the same local coordinate space |
| 670 | SkMatrix lm = SkMatrix::RotateDeg(45.f); |
| 671 | run_test_case(r, TestCase::Build("local-aa", kDeviceBounds) |
| 672 | .actual().aa().intersect().localToDevice(lm) |
| 673 | .rrect(r1).rrect(r2) |
| 674 | .finishElements() |
| 675 | .expect().aa().intersect().localToDevice(lm) |
| 676 | .rrect(intersect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 677 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 678 | .finishTest()); |
| 679 | run_test_case(r, TestCase::Build("local-nonaa", kDeviceBounds) |
| 680 | .actual().nonAA().intersect().localToDevice(lm) |
| 681 | .rrect(r1).rrect(r2) |
| 682 | .finishElements() |
| 683 | .expect().nonAA().intersect().localToDevice(lm) |
| 684 | .rrect(intersect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 685 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 686 | .finishTest()); |
| 687 | } |
| 688 | |
| 689 | // Tests that intersection of a round rect and rect can simplify to a new round rect or even a rect. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 690 | DEF_TEST(ClipStack_RectRRectCombine, r) { |
| 691 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 692 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 693 | SkRRect rrect = SkRRect::MakeRectXY({0, 0, 10, 10}, 2.f, 2.f); |
| 694 | SkRect cutTop = {-10, -10, 10, 4}; |
| 695 | SkRect cutMid = {-10, 3, 10, 7}; |
| 696 | |
| 697 | // Rect + RRect becomes a round rect with some square corners |
| 698 | SkVector cutCorners[4] = {{2.f, 2.f}, {2.f, 2.f}, {0, 0}, {0, 0}}; |
| 699 | SkRRect cutRRect; |
| 700 | cutRRect.setRectRadii({0, 0, 10, 4}, cutCorners); |
| 701 | run_test_case(r, TestCase::Build("still-rrect", kDeviceBounds) |
| 702 | .actual().intersect().aa().rrect(rrect).rect(cutTop).finishElements() |
| 703 | .expect().intersect().aa().rrect(cutRRect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 704 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 705 | .finishTest()); |
| 706 | |
| 707 | // Rect + RRect becomes a rect |
| 708 | SkRect cutRect = {0, 3, 10, 7}; |
| 709 | run_test_case(r, TestCase::Build("to-rect", kDeviceBounds) |
| 710 | .actual().intersect().aa().rrect(rrect).rect(cutMid).finishElements() |
| 711 | .expect().intersect().aa().rect(cutRect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 712 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 713 | .finishTest()); |
| 714 | |
| 715 | // But they can only combine when the intersecting shape is representable as a [r]rect. |
| 716 | cutRect = {0, 0, 1.5f, 5.f}; |
| 717 | run_test_case(r, TestCase::Build("no-combine", kDeviceBounds) |
| 718 | .actual().intersect().aa().rrect(rrect).rect(cutRect).finishElements() |
| 719 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 720 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 721 | .finishTest()); |
| 722 | } |
| 723 | |
| 724 | // Tests that a rect shape is actually pre-clipped to the device bounds |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 725 | DEF_TEST(ClipStack_RectDeviceClip, r) { |
| 726 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 727 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 728 | SkRect crossesDeviceEdge = {20.f, kDeviceBounds.fTop - 13.2f, |
| 729 | kDeviceBounds.fRight + 15.5f, 30.f}; |
| 730 | SkRect insideDevice = {20.f, kDeviceBounds.fTop, kDeviceBounds.fRight, 30.f}; |
| 731 | |
| 732 | run_test_case(r, TestCase::Build("device-aa-rect", kDeviceBounds) |
| 733 | .actual().intersect().aa().rect(crossesDeviceEdge).finishElements() |
| 734 | .expect().intersect().aa().rect(insideDevice).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 735 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 736 | .finishTest()); |
| 737 | |
| 738 | run_test_case(r, TestCase::Build("device-nonaa-rect", kDeviceBounds) |
| 739 | .actual().intersect().nonAA().rect(crossesDeviceEdge).finishElements() |
| 740 | .expect().intersect().nonAA().rect(insideDevice).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 741 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 742 | .finishTest()); |
| 743 | } |
| 744 | |
| 745 | // Tests that other shapes' bounds are contained by the device bounds, even if their shape is not. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 746 | DEF_TEST(ClipStack_ShapeDeviceBoundsClip, r) { |
| 747 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 748 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 749 | SkRect crossesDeviceEdge = {20.f, kDeviceBounds.fTop - 13.2f, |
| 750 | kDeviceBounds.fRight + 15.5f, 30.f}; |
| 751 | |
| 752 | // RRect |
| 753 | run_test_case(r, TestCase::Build("device-rrect", kDeviceBounds) |
| 754 | .actual().intersect().aa() |
| 755 | .rrect(SkRRect::MakeRectXY(crossesDeviceEdge, 4.f, 4.f)) |
| 756 | .finishElements() |
| 757 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 758 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 759 | .finishTest()); |
| 760 | |
| 761 | // Path |
| 762 | run_test_case(r, TestCase::Build("device-path", kDeviceBounds) |
| 763 | .actual().intersect().aa() |
| 764 | .path(make_octagon(crossesDeviceEdge)) |
| 765 | .finishElements() |
| 766 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 767 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 768 | .finishTest()); |
| 769 | } |
| 770 | |
| 771 | // Tests that a simplifiable path turns into a simpler element type |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 772 | DEF_TEST(ClipStack_PathSimplify, r) { |
| 773 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 774 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 775 | // Empty, point, and line paths -> empty |
| 776 | SkPath empty; |
| 777 | run_test_case(r, TestCase::Build("empty", kDeviceBounds) |
| 778 | .actual().path(empty).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 779 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 780 | .finishTest()); |
| 781 | SkPath point; |
| 782 | point.moveTo({0.f, 0.f}); |
| 783 | run_test_case(r, TestCase::Build("point", kDeviceBounds) |
| 784 | .actual().path(point).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 785 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 786 | .finishTest()); |
| 787 | |
| 788 | SkPath line; |
| 789 | line.moveTo({0.f, 0.f}); |
| 790 | line.lineTo({10.f, 5.f}); |
| 791 | run_test_case(r, TestCase::Build("line", kDeviceBounds) |
| 792 | .actual().path(line).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 793 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 794 | .finishTest()); |
| 795 | |
| 796 | // Rect path -> rect element |
| 797 | SkRect rect = {0.f, 2.f, 10.f, 15.4f}; |
| 798 | SkPath rectPath; |
| 799 | rectPath.addRect(rect); |
| 800 | run_test_case(r, TestCase::Build("rect", kDeviceBounds) |
| 801 | .actual().path(rectPath).finishElements() |
| 802 | .expect().rect(rect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 803 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 804 | .finishTest()); |
| 805 | |
| 806 | // Oval path -> rrect element |
| 807 | SkPath ovalPath; |
| 808 | ovalPath.addOval(rect); |
| 809 | run_test_case(r, TestCase::Build("oval", kDeviceBounds) |
| 810 | .actual().path(ovalPath).finishElements() |
| 811 | .expect().rrect(SkRRect::MakeOval(rect)).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 812 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 813 | .finishTest()); |
| 814 | |
| 815 | // RRect path -> rrect element |
| 816 | SkRRect rrect = SkRRect::MakeRectXY(rect, 2.f, 2.f); |
| 817 | SkPath rrectPath; |
| 818 | rrectPath.addRRect(rrect); |
| 819 | run_test_case(r, TestCase::Build("rrect", kDeviceBounds) |
| 820 | .actual().path(rrectPath).finishElements() |
| 821 | .expect().rrect(rrect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 822 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 823 | .finishTest()); |
| 824 | } |
| 825 | |
| 826 | // Tests that repeated identical clip operations are idempotent |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 827 | DEF_TEST(ClipStack_RepeatElement, r) { |
| 828 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 829 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 830 | // Same rect |
| 831 | SkRect rect = {5.3f, 62.f, 20.f, 85.f}; |
| 832 | run_test_case(r, TestCase::Build("same-rects", kDeviceBounds) |
| 833 | .actual().rect(rect).rect(rect).rect(rect).finishElements() |
| 834 | .expect().rect(rect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 835 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 836 | .finishTest()); |
| 837 | SkMatrix lm; |
| 838 | lm.setRotate(30.f, rect.centerX(), rect.centerY()); |
| 839 | run_test_case(r, TestCase::Build("same-local-rects", kDeviceBounds) |
| 840 | .actual().localToDevice(lm).rect(rect).rect(rect).rect(rect) |
| 841 | .finishElements() |
| 842 | .expect().localToDevice(lm).rect(rect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 843 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 844 | .finishTest()); |
| 845 | |
| 846 | // Same rrect |
| 847 | SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 2.5f); |
| 848 | run_test_case(r, TestCase::Build("same-rrects", kDeviceBounds) |
| 849 | .actual().rrect(rrect).rrect(rrect).rrect(rrect).finishElements() |
| 850 | .expect().rrect(rrect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 851 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 852 | .finishTest()); |
| 853 | run_test_case(r, TestCase::Build("same-local-rrects", kDeviceBounds) |
| 854 | .actual().localToDevice(lm).rrect(rrect).rrect(rrect).rrect(rrect) |
| 855 | .finishElements() |
| 856 | .expect().localToDevice(lm).rrect(rrect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 857 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 858 | .finishTest()); |
| 859 | |
| 860 | // Same convex path, by == |
| 861 | run_test_case(r, TestCase::Build("same-convex", kDeviceBounds) |
| 862 | .actual().path(make_octagon(rect)).path(make_octagon(rect)) |
| 863 | .finishElements() |
| 864 | .expect().path(make_octagon(rect)).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 865 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 866 | .finishTest()); |
| 867 | run_test_case(r, TestCase::Build("same-local-convex", kDeviceBounds) |
| 868 | .actual().localToDevice(lm) |
| 869 | .path(make_octagon(rect)).path(make_octagon(rect)) |
| 870 | .finishElements() |
| 871 | .expect().localToDevice(lm).path(make_octagon(rect)) |
| 872 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 873 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 874 | .finishTest()); |
| 875 | |
| 876 | // Same complicated path by gen-id but not == |
| 877 | SkPath path; // an hour glass |
| 878 | path.moveTo({0.f, 0.f}); |
| 879 | path.lineTo({20.f, 20.f}); |
| 880 | path.lineTo({0.f, 20.f}); |
| 881 | path.lineTo({20.f, 0.f}); |
| 882 | path.close(); |
| 883 | |
| 884 | run_test_case(r, TestCase::Build("same-path", kDeviceBounds) |
| 885 | .actual().path(path).path(path).path(path).finishElements() |
| 886 | .expect().path(path).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 887 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 888 | .finishTest()); |
| 889 | run_test_case(r, TestCase::Build("same-local-path", kDeviceBounds) |
| 890 | .actual().localToDevice(lm) |
| 891 | .path(path).path(path).path(path).finishElements() |
| 892 | .expect().localToDevice(lm).path(path) |
| 893 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 894 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 895 | .finishTest()); |
| 896 | } |
| 897 | |
| 898 | // Tests that inverse-filled paths are canonicalized to a regular fill and a swapped clip op |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 899 | DEF_TEST(ClipStack_InverseFilledPath, r) { |
| 900 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 901 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 902 | SkRect rect = {0.f, 0.f, 16.f, 17.f}; |
| 903 | SkPath rectPath; |
| 904 | rectPath.addRect(rect); |
| 905 | |
| 906 | SkPath inverseRectPath = rectPath; |
| 907 | inverseRectPath.toggleInverseFillType(); |
| 908 | |
| 909 | SkPath complexPath = make_octagon(rect); |
| 910 | SkPath inverseComplexPath = complexPath; |
| 911 | inverseComplexPath.toggleInverseFillType(); |
| 912 | |
| 913 | // Inverse filled rect + intersect -> diff rect |
| 914 | run_test_case(r, TestCase::Build("inverse-rect-intersect", kDeviceBounds) |
| 915 | .actual().aa().intersect().path(inverseRectPath).finishElements() |
| 916 | .expect().aa().difference().rect(rect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 917 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 918 | .finishTest()); |
| 919 | |
| 920 | // Inverse filled rect + difference -> int. rect |
| 921 | run_test_case(r, TestCase::Build("inverse-rect-difference", kDeviceBounds) |
| 922 | .actual().aa().difference().path(inverseRectPath).finishElements() |
| 923 | .expect().aa().intersect().rect(rect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 924 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 925 | .finishTest()); |
| 926 | |
| 927 | // Inverse filled path + intersect -> diff path |
| 928 | run_test_case(r, TestCase::Build("inverse-path-intersect", kDeviceBounds) |
| 929 | .actual().aa().intersect().path(inverseComplexPath).finishElements() |
| 930 | .expect().aa().difference().path(complexPath).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 931 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 932 | .finishTest()); |
| 933 | |
| 934 | // Inverse filled path + difference -> int. path |
| 935 | run_test_case(r, TestCase::Build("inverse-path-difference", kDeviceBounds) |
| 936 | .actual().aa().difference().path(inverseComplexPath).finishElements() |
| 937 | .expect().aa().intersect().path(complexPath).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 938 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 939 | .finishTest()); |
| 940 | } |
| 941 | |
| 942 | // Tests that clip operations that are offscreen either make the clip empty or stay wide open |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 943 | DEF_TEST(ClipStack_Offscreen, r) { |
| 944 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 945 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 946 | SkRect offscreenRect = {kDeviceBounds.fRight + 10.f, kDeviceBounds.fTop + 20.f, |
| 947 | kDeviceBounds.fRight + 40.f, kDeviceBounds.fTop + 60.f}; |
| 948 | SkASSERT(!offscreenRect.intersects(SkRect::Make(kDeviceBounds))); |
| 949 | |
| 950 | SkRRect offscreenRRect = SkRRect::MakeRectXY(offscreenRect, 5.f, 5.f); |
| 951 | SkPath offscreenPath = make_octagon(offscreenRect); |
| 952 | |
| 953 | // Intersect -> empty |
| 954 | run_test_case(r, TestCase::Build("intersect-combo", kDeviceBounds) |
| 955 | .actual().aa().intersect() |
| 956 | .rect(offscreenRect) |
| 957 | .rrect(offscreenRRect) |
| 958 | .path(offscreenPath) |
| 959 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 960 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 961 | .finishTest()); |
| 962 | run_test_case(r, TestCase::Build("intersect-rect", kDeviceBounds) |
| 963 | .actual().aa().intersect() |
| 964 | .rect(offscreenRect) |
| 965 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 966 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 967 | .finishTest()); |
| 968 | run_test_case(r, TestCase::Build("intersect-rrect", kDeviceBounds) |
| 969 | .actual().aa().intersect() |
| 970 | .rrect(offscreenRRect) |
| 971 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 972 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 973 | .finishTest()); |
| 974 | run_test_case(r, TestCase::Build("intersect-path", kDeviceBounds) |
| 975 | .actual().aa().intersect() |
| 976 | .path(offscreenPath) |
| 977 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 978 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 979 | .finishTest()); |
| 980 | |
| 981 | // Difference -> wide open |
| 982 | run_test_case(r, TestCase::Build("difference-combo", kDeviceBounds) |
| 983 | .actual().aa().difference() |
| 984 | .rect(offscreenRect) |
| 985 | .rrect(offscreenRRect) |
| 986 | .path(offscreenPath) |
| 987 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 988 | .state(ClipState::kWideOpen) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 989 | .finishTest()); |
| 990 | run_test_case(r, TestCase::Build("difference-rect", kDeviceBounds) |
| 991 | .actual().aa().difference() |
| 992 | .rect(offscreenRect) |
| 993 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 994 | .state(ClipState::kWideOpen) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 995 | .finishTest()); |
| 996 | run_test_case(r, TestCase::Build("difference-rrect", kDeviceBounds) |
| 997 | .actual().aa().difference() |
| 998 | .rrect(offscreenRRect) |
| 999 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1000 | .state(ClipState::kWideOpen) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1001 | .finishTest()); |
| 1002 | run_test_case(r, TestCase::Build("difference-path", kDeviceBounds) |
| 1003 | .actual().aa().difference() |
| 1004 | .path(offscreenPath) |
| 1005 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1006 | .state(ClipState::kWideOpen) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1007 | .finishTest()); |
| 1008 | } |
| 1009 | |
| 1010 | // Tests that an empty shape updates the clip state directly without needing an element |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1011 | DEF_TEST(ClipStack_EmptyShape, r) { |
| 1012 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1013 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1014 | // Intersect -> empty |
| 1015 | run_test_case(r, TestCase::Build("empty-intersect", kDeviceBounds) |
| 1016 | .actual().intersect().rect(SkRect::MakeEmpty()).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1017 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1018 | .finishTest()); |
| 1019 | |
| 1020 | // Difference -> no-op |
| 1021 | run_test_case(r, TestCase::Build("empty-difference", kDeviceBounds) |
| 1022 | .actual().difference().rect(SkRect::MakeEmpty()).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1023 | .state(ClipState::kWideOpen) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1024 | .finishTest()); |
| 1025 | |
| 1026 | SkRRect rrect = SkRRect::MakeRectXY({4.f, 10.f, 16.f, 32.f}, 2.f, 2.f); |
| 1027 | run_test_case(r, TestCase::Build("noop-difference", kDeviceBounds) |
| 1028 | .actual().difference().rrect(rrect).rect(SkRect::MakeEmpty()) |
| 1029 | .finishElements() |
| 1030 | .expect().difference().rrect(rrect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1031 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1032 | .finishTest()); |
| 1033 | } |
| 1034 | |
| 1035 | // Tests that sufficiently large difference operations can shrink the conservative bounds |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1036 | DEF_TEST(ClipStack_DifferenceBounds, r) { |
| 1037 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1038 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1039 | SkRect rightSide = {50.f, -10.f, 2.f * kDeviceBounds.fRight, kDeviceBounds.fBottom + 10.f}; |
| 1040 | SkRect clipped = rightSide; |
| 1041 | SkAssertResult(clipped.intersect(SkRect::Make(kDeviceBounds))); |
| 1042 | |
| 1043 | run_test_case(r, TestCase::Build("difference-cut", kDeviceBounds) |
| 1044 | .actual().nonAA().difference().rect(rightSide).finishElements() |
| 1045 | .expect().nonAA().difference().rect(clipped).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1046 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1047 | .finishTest()); |
| 1048 | } |
| 1049 | |
| 1050 | // Tests that intersections can combine even if there's a difference operation in the middle |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1051 | DEF_TEST(ClipStack_NoDifferenceInterference, r) { |
| 1052 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1053 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1054 | SkRect intR1 = {0.f, 0.f, 30.f, 30.f}; |
| 1055 | SkRect intR2 = {15.f, 15.f, 45.f, 45.f}; |
| 1056 | SkRect intCombo = {15.f, 15.f, 30.f, 30.f}; |
| 1057 | SkRect diff = {20.f, 6.f, 50.f, 50.f}; |
| 1058 | |
| 1059 | run_test_case(r, TestCase::Build("cross-diff-combine", kDeviceBounds) |
| 1060 | .actual().rect(intR1, GrAA::kYes, SkClipOp::kIntersect) |
| 1061 | .rect(diff, GrAA::kYes, SkClipOp::kDifference) |
| 1062 | .rect(intR2, GrAA::kYes, SkClipOp::kIntersect) |
| 1063 | .finishElements() |
| 1064 | .expect().rect(intCombo, GrAA::kYes, SkClipOp::kIntersect) |
| 1065 | .rect(diff, GrAA::kYes, SkClipOp::kDifference) |
| 1066 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1067 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1068 | .finishTest()); |
| 1069 | } |
| 1070 | |
| 1071 | // Tests that multiple path operations are all recorded, but not otherwise consolidated |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1072 | DEF_TEST(ClipStack_MultiplePaths, r) { |
| 1073 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1074 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1075 | // Chosen to be greater than the number of inline-allocated elements and save records of the |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1076 | // ClipStack so that we test heap allocation as well. |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1077 | static constexpr int kNumOps = 16; |
| 1078 | |
| 1079 | auto b = TestCase::Build("many-paths-difference", kDeviceBounds); |
| 1080 | SkRect d = {0.f, 0.f, 12.f, 12.f}; |
| 1081 | for (int i = 0; i < kNumOps; ++i) { |
| 1082 | b.actual().path(make_octagon(d), GrAA::kNo, SkClipOp::kDifference); |
| 1083 | |
| 1084 | d.offset(15.f, 0.f); |
| 1085 | if (d.fRight > kDeviceBounds.fRight) { |
| 1086 | d.fLeft = 0.f; |
| 1087 | d.fRight = 12.f; |
| 1088 | d.offset(0.f, 15.f); |
| 1089 | } |
| 1090 | } |
| 1091 | |
| 1092 | run_test_case(r, b.expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1093 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1094 | .finishTest()); |
| 1095 | |
| 1096 | b = TestCase::Build("many-paths-intersect", kDeviceBounds); |
| 1097 | d = {0.f, 0.f, 12.f, 12.f}; |
| 1098 | for (int i = 0; i < kNumOps; ++i) { |
| 1099 | b.actual().path(make_octagon(d), GrAA::kYes, SkClipOp::kIntersect); |
| 1100 | d.offset(0.01f, 0.01f); |
| 1101 | } |
| 1102 | |
| 1103 | run_test_case(r, b.expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1104 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1105 | .finishTest()); |
| 1106 | } |
| 1107 | |
| 1108 | // Tests that a single rect is treated as kDeviceRect state when it's axis-aligned and intersect. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1109 | DEF_TEST(ClipStack_DeviceRect, r) { |
| 1110 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1111 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1112 | // Axis-aligned + intersect -> kDeviceRect |
| 1113 | SkRect rect = {0, 0, 20, 20}; |
| 1114 | run_test_case(r, TestCase::Build("device-rect", kDeviceBounds) |
| 1115 | .actual().intersect().aa().rect(rect).finishElements() |
| 1116 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1117 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1118 | .finishTest()); |
| 1119 | |
| 1120 | // Not axis-aligned -> kComplex |
| 1121 | SkMatrix lm = SkMatrix::RotateDeg(15.f); |
| 1122 | run_test_case(r, TestCase::Build("unaligned-rect", kDeviceBounds) |
| 1123 | .actual().localToDevice(lm).intersect().aa().rect(rect) |
| 1124 | .finishElements() |
| 1125 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1126 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1127 | .finishTest()); |
| 1128 | |
| 1129 | // Not intersect -> kComplex |
| 1130 | run_test_case(r, TestCase::Build("diff-rect", kDeviceBounds) |
| 1131 | .actual().difference().aa().rect(rect).finishElements() |
| 1132 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1133 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1134 | .finishTest()); |
| 1135 | } |
| 1136 | |
| 1137 | // Tests that a single rrect is treated as kDeviceRRect state when it's axis-aligned and intersect. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1138 | DEF_TEST(ClipStack_DeviceRRect, r) { |
| 1139 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1140 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1141 | // Axis-aligned + intersect -> kDeviceRRect |
| 1142 | SkRect rect = {0, 0, 20, 20}; |
| 1143 | SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 5.f); |
| 1144 | run_test_case(r, TestCase::Build("device-rrect", kDeviceBounds) |
| 1145 | .actual().intersect().aa().rrect(rrect).finishElements() |
| 1146 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1147 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1148 | .finishTest()); |
| 1149 | |
| 1150 | // Not axis-aligned -> kComplex |
| 1151 | SkMatrix lm = SkMatrix::RotateDeg(15.f); |
| 1152 | run_test_case(r, TestCase::Build("unaligned-rrect", kDeviceBounds) |
| 1153 | .actual().localToDevice(lm).intersect().aa().rrect(rrect) |
| 1154 | .finishElements() |
| 1155 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1156 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1157 | .finishTest()); |
| 1158 | |
| 1159 | // Not intersect -> kComplex |
| 1160 | run_test_case(r, TestCase::Build("diff-rrect", kDeviceBounds) |
| 1161 | .actual().difference().aa().rrect(rrect).finishElements() |
| 1162 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1163 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1164 | .finishTest()); |
| 1165 | } |
| 1166 | |
| 1167 | // Tests that scale+translate matrices are pre-applied to rects and rrects, which also then allows |
| 1168 | // elements with different scale+translate matrices to be consolidated as if they were in the same |
| 1169 | // coordinate space. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1170 | DEF_TEST(ClipStack_ScaleTranslate, r) { |
| 1171 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1172 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1173 | SkMatrix lm = SkMatrix::Scale(2.f, 4.f); |
| 1174 | lm.postTranslate(15.5f, 14.3f); |
Michael Ludwig | d30e9ef | 2020-09-28 12:03:01 -0400 | [diff] [blame] | 1175 | SkASSERT(lm.preservesAxisAlignment() && lm.isScaleTranslate()); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1176 | |
| 1177 | // Rect -> matrix is applied up front |
| 1178 | SkRect rect = {0.f, 0.f, 10.f, 10.f}; |
| 1179 | run_test_case(r, TestCase::Build("st+rect", kDeviceBounds) |
| 1180 | .actual().rect(rect, lm, GrAA::kYes, SkClipOp::kIntersect) |
| 1181 | .finishElements() |
| 1182 | .expect().rect(lm.mapRect(rect), GrAA::kYes, SkClipOp::kIntersect) |
| 1183 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1184 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1185 | .finishTest()); |
| 1186 | |
| 1187 | // RRect -> matrix is applied up front |
| 1188 | SkRRect localRRect = SkRRect::MakeRectXY(rect, 2.f, 2.f); |
| 1189 | SkRRect deviceRRect; |
| 1190 | SkAssertResult(localRRect.transform(lm, &deviceRRect)); |
| 1191 | run_test_case(r, TestCase::Build("st+rrect", kDeviceBounds) |
| 1192 | .actual().rrect(localRRect, lm, GrAA::kYes, SkClipOp::kIntersect) |
| 1193 | .finishElements() |
| 1194 | .expect().rrect(deviceRRect, GrAA::kYes, SkClipOp::kIntersect) |
| 1195 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1196 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1197 | .finishTest()); |
| 1198 | |
| 1199 | // Path -> matrix is NOT applied |
| 1200 | run_test_case(r, TestCase::Build("st+path", kDeviceBounds) |
| 1201 | .actual().intersect().localToDevice(lm).path(make_octagon(rect)) |
| 1202 | .finishElements() |
| 1203 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1204 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1205 | .finishTest()); |
| 1206 | } |
| 1207 | |
Michael Ludwig | d30e9ef | 2020-09-28 12:03:01 -0400 | [diff] [blame] | 1208 | // Tests that rect-stays-rect matrices that are not scale+translate matrices are pre-applied. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1209 | DEF_TEST(ClipStack_PreserveAxisAlignment, r) { |
| 1210 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1211 | |
Michael Ludwig | d30e9ef | 2020-09-28 12:03:01 -0400 | [diff] [blame] | 1212 | SkMatrix lm = SkMatrix::RotateDeg(90.f); |
| 1213 | lm.postTranslate(15.5f, 14.3f); |
| 1214 | SkASSERT(lm.preservesAxisAlignment() && !lm.isScaleTranslate()); |
| 1215 | |
| 1216 | // Rect -> matrix is applied up front |
| 1217 | SkRect rect = {0.f, 0.f, 10.f, 10.f}; |
| 1218 | run_test_case(r, TestCase::Build("r90+rect", kDeviceBounds) |
| 1219 | .actual().rect(rect, lm, GrAA::kYes, SkClipOp::kIntersect) |
| 1220 | .finishElements() |
| 1221 | .expect().rect(lm.mapRect(rect), GrAA::kYes, SkClipOp::kIntersect) |
| 1222 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1223 | .state(ClipState::kDeviceRect) |
Michael Ludwig | d30e9ef | 2020-09-28 12:03:01 -0400 | [diff] [blame] | 1224 | .finishTest()); |
| 1225 | |
| 1226 | // RRect -> matrix is applied up front |
| 1227 | SkRRect localRRect = SkRRect::MakeRectXY(rect, 2.f, 2.f); |
| 1228 | SkRRect deviceRRect; |
| 1229 | SkAssertResult(localRRect.transform(lm, &deviceRRect)); |
| 1230 | run_test_case(r, TestCase::Build("r90+rrect", kDeviceBounds) |
| 1231 | .actual().rrect(localRRect, lm, GrAA::kYes, SkClipOp::kIntersect) |
| 1232 | .finishElements() |
| 1233 | .expect().rrect(deviceRRect, GrAA::kYes, SkClipOp::kIntersect) |
| 1234 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1235 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | d30e9ef | 2020-09-28 12:03:01 -0400 | [diff] [blame] | 1236 | .finishTest()); |
| 1237 | |
| 1238 | // Path -> matrix is NOT applied |
| 1239 | run_test_case(r, TestCase::Build("r90+path", kDeviceBounds) |
| 1240 | .actual().intersect().localToDevice(lm).path(make_octagon(rect)) |
| 1241 | .finishElements() |
| 1242 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1243 | .state(ClipState::kComplex) |
Michael Ludwig | d30e9ef | 2020-09-28 12:03:01 -0400 | [diff] [blame] | 1244 | .finishTest()); |
| 1245 | } |
| 1246 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1247 | // Tests that a convex path element can contain a rect or round rect, allowing the stack to be |
| 1248 | // simplified |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1249 | DEF_TEST(ClipStack_ConvexPathContains, r) { |
| 1250 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1251 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1252 | SkRect rect = {15.f, 15.f, 30.f, 30.f}; |
| 1253 | SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 5.f); |
| 1254 | SkPath bigPath = make_octagon(rect.makeOutset(10.f, 10.f), 5.f, 5.f); |
| 1255 | |
| 1256 | // Intersect -> path element isn't kept |
| 1257 | run_test_case(r, TestCase::Build("convex+rect-intersect", kDeviceBounds) |
| 1258 | .actual().aa().intersect().rect(rect).path(bigPath).finishElements() |
| 1259 | .expect().aa().intersect().rect(rect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1260 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1261 | .finishTest()); |
| 1262 | run_test_case(r, TestCase::Build("convex+rrect-intersect", kDeviceBounds) |
| 1263 | .actual().aa().intersect().rrect(rrect).path(bigPath).finishElements() |
| 1264 | .expect().aa().intersect().rrect(rrect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1265 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1266 | .finishTest()); |
| 1267 | |
| 1268 | // Difference -> path element is the only one left |
| 1269 | run_test_case(r, TestCase::Build("convex+rect-difference", kDeviceBounds) |
| 1270 | .actual().aa().difference().rect(rect).path(bigPath).finishElements() |
| 1271 | .expect().aa().difference().path(bigPath).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1272 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1273 | .finishTest()); |
| 1274 | run_test_case(r, TestCase::Build("convex+rrect-difference", kDeviceBounds) |
| 1275 | .actual().aa().difference().rrect(rrect).path(bigPath) |
| 1276 | .finishElements() |
| 1277 | .expect().aa().difference().path(bigPath).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1278 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1279 | .finishTest()); |
| 1280 | |
| 1281 | // Intersect small shape + difference big path -> empty |
| 1282 | run_test_case(r, TestCase::Build("convex-diff+rect-int", kDeviceBounds) |
| 1283 | .actual().aa().intersect().rect(rect) |
| 1284 | .difference().path(bigPath).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1285 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1286 | .finishTest()); |
| 1287 | run_test_case(r, TestCase::Build("convex-diff+rrect-int", kDeviceBounds) |
| 1288 | .actual().aa().intersect().rrect(rrect) |
| 1289 | .difference().path(bigPath).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1290 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1291 | .finishTest()); |
| 1292 | |
| 1293 | // Diff small shape + intersect big path -> both |
| 1294 | run_test_case(r, TestCase::Build("convex-int+rect-diff", kDeviceBounds) |
| 1295 | .actual().aa().intersect().path(bigPath).difference().rect(rect) |
| 1296 | .finishElements() |
| 1297 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1298 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1299 | .finishTest()); |
| 1300 | run_test_case(r, TestCase::Build("convex-int+rrect-diff", kDeviceBounds) |
| 1301 | .actual().aa().intersect().path(bigPath).difference().rrect(rrect) |
| 1302 | .finishElements() |
| 1303 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1304 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1305 | .finishTest()); |
| 1306 | } |
| 1307 | |
| 1308 | // Tests that rects/rrects in different coordinate spaces can be consolidated when one is fully |
| 1309 | // contained by the other. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1310 | DEF_TEST(ClipStack_NonAxisAlignedContains, r) { |
| 1311 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1312 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1313 | SkMatrix lm1 = SkMatrix::RotateDeg(45.f); |
| 1314 | SkRect bigR = {-20.f, -20.f, 20.f, 20.f}; |
| 1315 | SkRRect bigRR = SkRRect::MakeRectXY(bigR, 1.f, 1.f); |
| 1316 | |
| 1317 | SkMatrix lm2 = SkMatrix::RotateDeg(-45.f); |
| 1318 | SkRect smR = {-10.f, -10.f, 10.f, 10.f}; |
| 1319 | SkRRect smRR = SkRRect::MakeRectXY(smR, 1.f, 1.f); |
| 1320 | |
| 1321 | // I+I should select the smaller 2nd shape (r2 or rr2) |
| 1322 | run_test_case(r, TestCase::Build("rect-rect-ii", kDeviceBounds) |
| 1323 | .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect) |
| 1324 | .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1325 | .finishElements() |
| 1326 | .expect().rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1327 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1328 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1329 | .finishTest()); |
| 1330 | run_test_case(r, TestCase::Build("rrect-rrect-ii", kDeviceBounds) |
| 1331 | .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect) |
| 1332 | .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1333 | .finishElements() |
| 1334 | .expect().rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1335 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1336 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1337 | .finishTest()); |
| 1338 | run_test_case(r, TestCase::Build("rect-rrect-ii", kDeviceBounds) |
| 1339 | .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect) |
| 1340 | .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1341 | .finishElements() |
| 1342 | .expect().rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1343 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1344 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1345 | .finishTest()); |
| 1346 | run_test_case(r, TestCase::Build("rrect-rect-ii", kDeviceBounds) |
| 1347 | .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect) |
| 1348 | .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1349 | .finishElements() |
| 1350 | .expect().rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1351 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1352 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1353 | .finishTest()); |
| 1354 | |
| 1355 | // D+D should select the larger shape (r1 or rr1) |
| 1356 | run_test_case(r, TestCase::Build("rect-rect-dd", kDeviceBounds) |
| 1357 | .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1358 | .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference) |
| 1359 | .finishElements() |
| 1360 | .expect().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1361 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1362 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1363 | .finishTest()); |
| 1364 | run_test_case(r, TestCase::Build("rrect-rrect-dd", kDeviceBounds) |
| 1365 | .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1366 | .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference) |
| 1367 | .finishElements() |
| 1368 | .expect().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1369 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1370 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1371 | .finishTest()); |
| 1372 | run_test_case(r, TestCase::Build("rect-rrect-dd", kDeviceBounds) |
| 1373 | .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1374 | .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference) |
| 1375 | .finishElements() |
| 1376 | .expect().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1377 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1378 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1379 | .finishTest()); |
| 1380 | run_test_case(r, TestCase::Build("rrect-rect-dd", kDeviceBounds) |
| 1381 | .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1382 | .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference) |
| 1383 | .finishElements() |
| 1384 | .expect().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1385 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1386 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1387 | .finishTest()); |
| 1388 | |
| 1389 | // D(1)+I(2) should result in empty |
| 1390 | run_test_case(r, TestCase::Build("rectD-rectI", kDeviceBounds) |
| 1391 | .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1392 | .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1393 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1394 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1395 | .finishTest()); |
| 1396 | run_test_case(r, TestCase::Build("rrectD-rrectI", kDeviceBounds) |
| 1397 | .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1398 | .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1399 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1400 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1401 | .finishTest()); |
| 1402 | run_test_case(r, TestCase::Build("rectD-rrectI", kDeviceBounds) |
| 1403 | .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1404 | .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1405 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1406 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1407 | .finishTest()); |
| 1408 | run_test_case(r, TestCase::Build("rrectD-rectI", kDeviceBounds) |
| 1409 | .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference) |
| 1410 | .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1411 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1412 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1413 | .finishTest()); |
| 1414 | |
| 1415 | // I(1)+D(2) should result in both shapes |
| 1416 | run_test_case(r, TestCase::Build("rectI+rectD", kDeviceBounds) |
| 1417 | .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect) |
| 1418 | .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference) |
| 1419 | .finishElements() |
| 1420 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1421 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1422 | .finishTest()); |
| 1423 | run_test_case(r, TestCase::Build("rrectI+rrectD", kDeviceBounds) |
| 1424 | .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect) |
| 1425 | .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference) |
| 1426 | .finishElements() |
| 1427 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1428 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1429 | .finishTest()); |
| 1430 | run_test_case(r, TestCase::Build("rrectI+rectD", kDeviceBounds) |
| 1431 | .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect) |
| 1432 | .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference) |
| 1433 | .finishElements() |
| 1434 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1435 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1436 | .finishTest()); |
| 1437 | run_test_case(r, TestCase::Build("rectI+rrectD", kDeviceBounds) |
| 1438 | .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect) |
| 1439 | .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference) |
| 1440 | .finishElements() |
| 1441 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1442 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1443 | .finishTest()); |
| 1444 | } |
| 1445 | |
| 1446 | // Tests that shapes with mixed AA state that contain each other can still be consolidated, |
| 1447 | // unless they are too close to the edge and non-AA snapping can't be predicted |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1448 | DEF_TEST(ClipStack_MixedAAContains, r) { |
| 1449 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1450 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1451 | SkMatrix lm1 = SkMatrix::RotateDeg(45.f); |
| 1452 | SkRect r1 = {-20.f, -20.f, 20.f, 20.f}; |
| 1453 | |
| 1454 | SkMatrix lm2 = SkMatrix::RotateDeg(-45.f); |
| 1455 | SkRect r2Safe = {-10.f, -10.f, 10.f, 10.f}; |
| 1456 | SkRect r2Unsafe = {-19.5f, -19.5f, 19.5f, 19.5f}; |
| 1457 | |
| 1458 | // Non-AA sufficiently inside AA element can discard the outer AA element |
| 1459 | run_test_case(r, TestCase::Build("mixed-outeraa-combine", kDeviceBounds) |
| 1460 | .actual().rect(r1, lm1, GrAA::kYes, SkClipOp::kIntersect) |
| 1461 | .rect(r2Safe, lm2, GrAA::kNo, SkClipOp::kIntersect) |
| 1462 | .finishElements() |
| 1463 | .expect().rect(r2Safe, lm2, GrAA::kNo, SkClipOp::kIntersect) |
| 1464 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1465 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1466 | .finishTest()); |
| 1467 | // Vice versa |
| 1468 | run_test_case(r, TestCase::Build("mixed-inneraa-combine", kDeviceBounds) |
| 1469 | .actual().rect(r1, lm1, GrAA::kNo, SkClipOp::kIntersect) |
| 1470 | .rect(r2Safe, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1471 | .finishElements() |
| 1472 | .expect().rect(r2Safe, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1473 | .finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1474 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1475 | .finishTest()); |
| 1476 | |
| 1477 | // Non-AA too close to AA edges keeps both |
| 1478 | run_test_case(r, TestCase::Build("mixed-outeraa-nocombine", kDeviceBounds) |
| 1479 | .actual().rect(r1, lm1, GrAA::kYes, SkClipOp::kIntersect) |
| 1480 | .rect(r2Unsafe, lm2, GrAA::kNo, SkClipOp::kIntersect) |
| 1481 | .finishElements() |
| 1482 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1483 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1484 | .finishTest()); |
| 1485 | run_test_case(r, TestCase::Build("mixed-inneraa-nocombine", kDeviceBounds) |
| 1486 | .actual().rect(r1, lm1, GrAA::kNo, SkClipOp::kIntersect) |
| 1487 | .rect(r2Unsafe, lm2, GrAA::kYes, SkClipOp::kIntersect) |
| 1488 | .finishElements() |
| 1489 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1490 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1491 | .finishTest()); |
| 1492 | } |
| 1493 | |
| 1494 | // Tests that a shape that contains the device bounds updates the clip state directly |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1495 | DEF_TEST(ClipStack_ShapeContainsDevice, r) { |
| 1496 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1497 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1498 | SkRect rect = SkRect::Make(kDeviceBounds).makeOutset(10.f, 10.f); |
| 1499 | SkRRect rrect = SkRRect::MakeRectXY(rect, 10.f, 10.f); |
| 1500 | SkPath convex = make_octagon(rect, 10.f, 10.f); |
| 1501 | |
| 1502 | // Intersect -> no-op |
| 1503 | run_test_case(r, TestCase::Build("rect-intersect", kDeviceBounds) |
| 1504 | .actual().intersect().rect(rect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1505 | .state(ClipState::kWideOpen) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1506 | .finishTest()); |
| 1507 | run_test_case(r, TestCase::Build("rrect-intersect", kDeviceBounds) |
| 1508 | .actual().intersect().rrect(rrect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1509 | .state(ClipState::kWideOpen) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1510 | .finishTest()); |
| 1511 | run_test_case(r, TestCase::Build("convex-intersect", kDeviceBounds) |
| 1512 | .actual().intersect().path(convex).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1513 | .state(ClipState::kWideOpen) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1514 | .finishTest()); |
| 1515 | |
| 1516 | // Difference -> empty |
| 1517 | run_test_case(r, TestCase::Build("rect-difference", kDeviceBounds) |
| 1518 | .actual().difference().rect(rect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1519 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1520 | .finishTest()); |
| 1521 | run_test_case(r, TestCase::Build("rrect-difference", kDeviceBounds) |
| 1522 | .actual().difference().rrect(rrect).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1523 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1524 | .finishTest()); |
| 1525 | run_test_case(r, TestCase::Build("convex-difference", kDeviceBounds) |
| 1526 | .actual().difference().path(convex).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1527 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1528 | .finishTest()); |
| 1529 | } |
| 1530 | |
| 1531 | // Tests that shapes that do not overlap make for an empty clip (when intersecting), pick just the |
| 1532 | // intersecting op (when mixed), or are all kept (when diff'ing). |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1533 | DEF_TEST(ClipStack_DisjointShapes, r) { |
| 1534 | using ClipState = skgpu::v1::ClipStack::ClipState; |
| 1535 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1536 | SkRect rt = {10.f, 10.f, 20.f, 20.f}; |
| 1537 | SkRRect rr = SkRRect::MakeOval(rt.makeOffset({20.f, 0.f})); |
| 1538 | SkPath p = make_octagon(rt.makeOffset({0.f, 20.f})); |
| 1539 | |
| 1540 | // I+I |
| 1541 | run_test_case(r, TestCase::Build("iii", kDeviceBounds) |
| 1542 | .actual().aa().intersect().rect(rt).rrect(rr).path(p).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1543 | .state(ClipState::kEmpty) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1544 | .finishTest()); |
| 1545 | |
| 1546 | // D+D |
| 1547 | run_test_case(r, TestCase::Build("ddd", kDeviceBounds) |
| 1548 | .actual().nonAA().difference().rect(rt).rrect(rr).path(p) |
| 1549 | .finishElements() |
| 1550 | .expectActual() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1551 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1552 | .finishTest()); |
| 1553 | |
| 1554 | // I+D from rect |
| 1555 | run_test_case(r, TestCase::Build("idd", kDeviceBounds) |
| 1556 | .actual().aa().intersect().rect(rt) |
| 1557 | .nonAA().difference().rrect(rr).path(p) |
| 1558 | .finishElements() |
| 1559 | .expect().aa().intersect().rect(rt).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1560 | .state(ClipState::kDeviceRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1561 | .finishTest()); |
| 1562 | |
| 1563 | // I+D from rrect |
| 1564 | run_test_case(r, TestCase::Build("did", kDeviceBounds) |
| 1565 | .actual().aa().intersect().rrect(rr) |
| 1566 | .nonAA().difference().rect(rt).path(p) |
| 1567 | .finishElements() |
| 1568 | .expect().aa().intersect().rrect(rr).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1569 | .state(ClipState::kDeviceRRect) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1570 | .finishTest()); |
| 1571 | |
| 1572 | // I+D from path |
| 1573 | run_test_case(r, TestCase::Build("ddi", kDeviceBounds) |
| 1574 | .actual().aa().intersect().path(p) |
| 1575 | .nonAA().difference().rect(rt).rrect(rr) |
| 1576 | .finishElements() |
| 1577 | .expect().aa().intersect().path(p).finishElements() |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1578 | .state(ClipState::kComplex) |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1579 | .finishTest()); |
| 1580 | } |
| 1581 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1582 | DEF_TEST(ClipStack_ComplexClip, reporter) { |
| 1583 | using ClipStack = skgpu::v1::ClipStack; |
| 1584 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1585 | static constexpr float kN = 10.f; |
| 1586 | static constexpr float kR = kN / 3.f; |
| 1587 | |
| 1588 | // 4 rectangles that overlap by kN x 2kN (horiz), 2kN x kN (vert), or kN x kN (diagonal) |
| 1589 | static const SkRect kTL = {0.f, 0.f, 2.f * kN, 2.f * kN}; |
| 1590 | static const SkRect kTR = {kN, 0.f, 3.f * kN, 2.f * kN}; |
| 1591 | static const SkRect kBL = {0.f, kN, 2.f * kN, 3.f * kN}; |
| 1592 | static const SkRect kBR = {kN, kN, 3.f * kN, 3.f * kN}; |
| 1593 | |
| 1594 | enum ShapeType { kRect, kRRect, kConvex }; |
| 1595 | |
| 1596 | SkRect rects[] = { kTL, kTR, kBL, kBR }; |
| 1597 | for (ShapeType type : { kRect, kRRect, kConvex }) { |
| 1598 | for (int opBits = 6; opBits < 16; ++opBits) { |
| 1599 | SkString name; |
| 1600 | name.appendf("complex-%d-%d", (int) type, opBits); |
| 1601 | |
| 1602 | SkRect expectedRectIntersection = SkRect::Make(kDeviceBounds); |
| 1603 | SkRRect expectedRRectIntersection = SkRRect::MakeRect(expectedRectIntersection); |
| 1604 | |
| 1605 | auto b = TestCase::Build(name.c_str(), kDeviceBounds); |
| 1606 | for (int i = 0; i < 4; ++i) { |
| 1607 | SkClipOp op = (opBits & (1 << i)) ? SkClipOp::kIntersect : SkClipOp::kDifference; |
| 1608 | switch(type) { |
| 1609 | case kRect: { |
| 1610 | SkRect r = rects[i]; |
| 1611 | if (op == SkClipOp::kDifference) { |
| 1612 | // Shrink the rect for difference ops, otherwise in the rect testcase |
| 1613 | // any difference op would remove the intersection of the other ops |
| 1614 | // given how the rects are defined, and that's just not interesting. |
| 1615 | r.inset(kR, kR); |
| 1616 | } |
| 1617 | b.actual().rect(r, GrAA::kYes, op); |
| 1618 | if (op == SkClipOp::kIntersect) { |
| 1619 | SkAssertResult(expectedRectIntersection.intersect(r)); |
| 1620 | } else { |
| 1621 | b.expect().rect(r, GrAA::kYes, SkClipOp::kDifference); |
| 1622 | } |
| 1623 | break; } |
| 1624 | case kRRect: { |
| 1625 | SkRRect rrect = SkRRect::MakeRectXY(rects[i], kR, kR); |
| 1626 | b.actual().rrect(rrect, GrAA::kYes, op); |
| 1627 | if (op == SkClipOp::kIntersect) { |
| 1628 | expectedRRectIntersection = SkRRectPriv::ConservativeIntersect( |
| 1629 | expectedRRectIntersection, rrect); |
| 1630 | SkASSERT(!expectedRRectIntersection.isEmpty()); |
| 1631 | } else { |
| 1632 | b.expect().rrect(rrect, GrAA::kYes, SkClipOp::kDifference); |
| 1633 | } |
| 1634 | break; } |
| 1635 | case kConvex: |
| 1636 | b.actual().path(make_octagon(rects[i], kR, kR), GrAA::kYes, op); |
| 1637 | // NOTE: We don't set any expectations here, since convex just calls |
| 1638 | // expectActual() at the end. |
| 1639 | break; |
| 1640 | } |
| 1641 | } |
| 1642 | |
| 1643 | // The expectations differ depending on the shape type |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1644 | ClipStack::ClipState state = ClipStack::ClipState::kComplex; |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1645 | if (type == kConvex) { |
| 1646 | // The simplest case is when the paths cannot be combined together, so we expect |
| 1647 | // the actual elements to be unmodified (both intersect and difference). |
| 1648 | b.expectActual(); |
| 1649 | } else if (opBits) { |
| 1650 | // All intersection ops were pre-computed into expectedR[R]ectIntersection |
| 1651 | // - difference ops already added in the for loop |
| 1652 | if (type == kRect) { |
| 1653 | SkASSERT(expectedRectIntersection != SkRect::Make(kDeviceBounds) && |
| 1654 | !expectedRectIntersection.isEmpty()); |
| 1655 | b.expect().rect(expectedRectIntersection, GrAA::kYes, SkClipOp::kIntersect); |
| 1656 | if (opBits == 0xf) { |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1657 | state = ClipStack::ClipState::kDeviceRect; |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1658 | } |
| 1659 | } else { |
| 1660 | SkASSERT(expectedRRectIntersection != |
| 1661 | SkRRect::MakeRect(SkRect::Make(kDeviceBounds)) && |
| 1662 | !expectedRRectIntersection.isEmpty()); |
| 1663 | b.expect().rrect(expectedRRectIntersection, GrAA::kYes, SkClipOp::kIntersect); |
| 1664 | if (opBits == 0xf) { |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1665 | state = ClipStack::ClipState::kDeviceRRect; |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1666 | } |
| 1667 | } |
| 1668 | } |
| 1669 | |
John Stiles | fa088a6 | 2021-08-12 23:01:41 -0400 | [diff] [blame] | 1670 | run_test_case(reporter, b.state(state).finishTest()); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1671 | } |
| 1672 | } |
| 1673 | } |
| 1674 | |
| 1675 | // /////////////////////////////////////////////////////////////////////////////// |
| 1676 | // // These tests do not use the TestCase infrastructure and manipulate a |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1677 | // // ClipStack directly. |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1678 | |
| 1679 | // Tests that replaceClip() works as expected across save/restores |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1680 | DEF_TEST(ClipStack_ReplaceClip, r) { |
| 1681 | using ClipStack = skgpu::v1::ClipStack; |
| 1682 | |
| 1683 | ClipStack cs(kDeviceBounds, nullptr, false); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1684 | |
| 1685 | SkRRect rrect = SkRRect::MakeRectXY({15.f, 12.25f, 40.3f, 23.5f}, 4.f, 6.f); |
| 1686 | cs.clipRRect(SkMatrix::I(), rrect, GrAA::kYes, SkClipOp::kIntersect); |
| 1687 | |
| 1688 | SkIRect replace = {50, 25, 75, 40}; // Is disjoint from the rrect element |
| 1689 | cs.save(); |
| 1690 | cs.replaceClip(replace); |
| 1691 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1692 | REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kDeviceRect, |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1693 | "Clip did not become a device rect"); |
| 1694 | REPORTER_ASSERT(r, cs.getConservativeBounds() == replace, "Unexpected replaced clip bounds"); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1695 | const ClipStack::Element& replaceElement = *cs.begin(); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1696 | REPORTER_ASSERT(r, replaceElement.fShape.rect() == SkRect::Make(replace) && |
| 1697 | replaceElement.fAA == GrAA::kNo && |
| 1698 | replaceElement.fOp == SkClipOp::kIntersect && |
| 1699 | replaceElement.fLocalToDevice == SkMatrix::I(), |
| 1700 | "Unexpected replace element state"); |
| 1701 | |
| 1702 | // Restore should undo the replaced clip and bring back the rrect |
| 1703 | cs.restore(); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1704 | REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kDeviceRRect, |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1705 | "Unexpected state after restore, not kDeviceRRect"); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1706 | const ClipStack::Element& rrectElem = *cs.begin(); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1707 | REPORTER_ASSERT(r, rrectElem.fShape.rrect() == rrect && |
| 1708 | rrectElem.fAA == GrAA::kYes && |
| 1709 | rrectElem.fOp == SkClipOp::kIntersect && |
| 1710 | rrectElem.fLocalToDevice == SkMatrix::I(), |
| 1711 | "RRect element state not restored properly after replace clip undone"); |
| 1712 | } |
| 1713 | |
Robert Phillips | c4fbc8d | 2020-11-30 10:17:53 -0500 | [diff] [blame] | 1714 | // Try to overflow the number of allowed window rects (see skbug.com/10989) |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1715 | DEF_TEST(ClipStack_DiffRects, r) { |
| 1716 | using ClipStack = skgpu::v1::ClipStack; |
| 1717 | using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext; |
| 1718 | |
Robert Phillips | c4fbc8d | 2020-11-30 10:17:53 -0500 | [diff] [blame] | 1719 | GrMockOptions options; |
| 1720 | options.fMaxWindowRectangles = 8; |
| 1721 | |
| 1722 | SkSimpleMatrixProvider matrixProvider = SkMatrix::I(); |
| 1723 | sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(&options); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1724 | std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make( |
Robert Phillips | c4fbc8d | 2020-11-30 10:17:53 -0500 | [diff] [blame] | 1725 | context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(), |
Chris Dalton | f5b87f9 | 2021-04-19 17:27:09 -0600 | [diff] [blame] | 1726 | SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps()); |
Robert Phillips | c4fbc8d | 2020-11-30 10:17:53 -0500 | [diff] [blame] | 1727 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1728 | ClipStack cs(kDeviceBounds, &matrixProvider, false); |
Robert Phillips | c4fbc8d | 2020-11-30 10:17:53 -0500 | [diff] [blame] | 1729 | |
| 1730 | cs.save(); |
| 1731 | for (int y = 0; y < 10; ++y) { |
| 1732 | for (int x = 0; x < 10; ++x) { |
| 1733 | cs.clipRect(SkMatrix::I(), SkRect::MakeXYWH(10*x+1, 10*y+1, 8, 8), |
| 1734 | GrAA::kNo, SkClipOp::kDifference); |
| 1735 | } |
| 1736 | } |
| 1737 | |
| 1738 | GrAppliedClip out(kDeviceBounds.size()); |
| 1739 | SkRect drawBounds = SkRect::Make(kDeviceBounds); |
Robert Phillips | 4dca831 | 2021-07-28 15:13:20 -0400 | [diff] [blame] | 1740 | GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage, |
Chris Dalton | b1e8f85 | 2021-06-23 13:36:51 -0600 | [diff] [blame] | 1741 | &out, &drawBounds); |
Robert Phillips | c4fbc8d | 2020-11-30 10:17:53 -0500 | [diff] [blame] | 1742 | |
| 1743 | REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped); |
| 1744 | REPORTER_ASSERT(r, out.windowRectsState().numWindows() == 8); |
| 1745 | |
| 1746 | cs.restore(); |
| 1747 | } |
| 1748 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1749 | // Tests that when a stack is forced to always be AA, non-AA elements become AA |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1750 | DEF_TEST(ClipStack_ForceAA, r) { |
| 1751 | using ClipStack = skgpu::v1::ClipStack; |
| 1752 | |
| 1753 | ClipStack cs(kDeviceBounds, nullptr, true); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1754 | |
| 1755 | // AA will remain AA |
| 1756 | SkRect aaRect = {0.25f, 12.43f, 25.2f, 23.f}; |
| 1757 | cs.clipRect(SkMatrix::I(), aaRect, GrAA::kYes, SkClipOp::kIntersect); |
| 1758 | |
| 1759 | // Non-AA will become AA |
| 1760 | SkPath nonAAPath = make_octagon({2.f, 10.f, 16.f, 20.f}); |
| 1761 | cs.clipPath(SkMatrix::I(), nonAAPath, GrAA::kNo, SkClipOp::kIntersect); |
| 1762 | |
Michael Ludwig | 462bdfc | 2020-09-22 16:27:04 -0400 | [diff] [blame] | 1763 | // Non-AA rects remain non-AA so they can be applied as a scissor |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1764 | SkRect nonAARect = {4.5f, 5.f, 17.25f, 18.23f}; |
| 1765 | cs.clipRect(SkMatrix::I(), nonAARect, GrAA::kNo, SkClipOp::kIntersect); |
| 1766 | |
| 1767 | // The stack reports elements newest first, but the non-AA rect op was combined in place with |
| 1768 | // the first aa rect, so we should see nonAAPath as AA, and then the intersection of rects. |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1769 | auto elements = cs.begin(); |
| 1770 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1771 | const ClipStack::Element& nonAARectElement = *elements; |
Michael Ludwig | 462bdfc | 2020-09-22 16:27:04 -0400 | [diff] [blame] | 1772 | REPORTER_ASSERT(r, nonAARectElement.fShape.isRect(), "Expected rect element"); |
| 1773 | REPORTER_ASSERT(r, nonAARectElement.fAA == GrAA::kNo, |
| 1774 | "Axis-aligned non-AA rect ignores forceAA"); |
| 1775 | REPORTER_ASSERT(r, nonAARectElement.fShape.rect() == nonAARect, |
| 1776 | "Mixed AA rects should not combine"); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1777 | |
| 1778 | ++elements; |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1779 | const ClipStack::Element& aaPathElement = *elements; |
Michael Ludwig | 462bdfc | 2020-09-22 16:27:04 -0400 | [diff] [blame] | 1780 | REPORTER_ASSERT(r, aaPathElement.fShape.isPath(), "Expected path element"); |
| 1781 | REPORTER_ASSERT(r, aaPathElement.fShape.path() == nonAAPath, "Wrong path element"); |
| 1782 | REPORTER_ASSERT(r, aaPathElement.fAA == GrAA::kYes, "Path element not promoted to AA"); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1783 | |
| 1784 | ++elements; |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1785 | const ClipStack::Element& aaRectElement = *elements; |
Michael Ludwig | 462bdfc | 2020-09-22 16:27:04 -0400 | [diff] [blame] | 1786 | REPORTER_ASSERT(r, aaRectElement.fShape.isRect(), "Expected rect element"); |
| 1787 | REPORTER_ASSERT(r, aaRectElement.fShape.rect() == aaRect, |
| 1788 | "Mixed AA rects should not combine"); |
| 1789 | REPORTER_ASSERT(r, aaRectElement.fAA == GrAA::kYes, "Rect element stays AA"); |
| 1790 | |
| 1791 | ++elements; |
| 1792 | REPORTER_ASSERT(r, !(elements != cs.end()), "Expected only three clip elements"); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1793 | } |
| 1794 | |
| 1795 | // Tests preApply works as expected for device rects, rrects, and reports clipped-out, etc. as |
| 1796 | // expected. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1797 | DEF_TEST(ClipStack_PreApply, r) { |
| 1798 | using ClipStack = skgpu::v1::ClipStack; |
| 1799 | |
| 1800 | ClipStack cs(kDeviceBounds, nullptr, false); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1801 | |
| 1802 | // Offscreen is kClippedOut |
| 1803 | GrClip::PreClipResult result = cs.preApply({-10.f, -10.f, -1.f, -1.f}, GrAA::kYes); |
| 1804 | REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut, |
| 1805 | "Offscreen draw is kClippedOut"); |
| 1806 | |
| 1807 | // Intersecting screen with wide-open clip is kUnclipped |
| 1808 | result = cs.preApply({-10.f, -10.f, 10.f, 10.f}, GrAA::kYes); |
| 1809 | REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kUnclipped, |
| 1810 | "Wide open screen intersection is still kUnclipped"); |
| 1811 | |
| 1812 | // Empty clip is clipped out |
| 1813 | cs.save(); |
| 1814 | cs.clipRect(SkMatrix::I(), SkRect::MakeEmpty(), GrAA::kNo, SkClipOp::kIntersect); |
| 1815 | result = cs.preApply({0.f, 0.f, 20.f, 20.f}, GrAA::kYes); |
| 1816 | REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut, |
| 1817 | "Empty clip stack preApplies as kClippedOut"); |
| 1818 | cs.restore(); |
| 1819 | |
| 1820 | // Contained inside clip is kUnclipped (using rrect for the outer clip element since paths |
| 1821 | // don't support an inner bounds and anything complex is otherwise skipped in preApply). |
| 1822 | SkRect rect = {10.f, 10.f, 40.f, 40.f}; |
| 1823 | SkRRect bigRRect = SkRRect::MakeRectXY(rect.makeOutset(5.f, 5.f), 5.f, 5.f); |
| 1824 | cs.save(); |
| 1825 | cs.clipRRect(SkMatrix::I(), bigRRect, GrAA::kYes, SkClipOp::kIntersect); |
| 1826 | result = cs.preApply(rect, GrAA::kYes); |
| 1827 | REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kUnclipped, |
| 1828 | "Draw contained within clip is kUnclipped"); |
| 1829 | |
| 1830 | // Disjoint from clip (but still on screen) is kClippedOut |
| 1831 | result = cs.preApply({50.f, 50.f, 60.f, 60.f}, GrAA::kYes); |
| 1832 | REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut, |
| 1833 | "Draw not intersecting clip is kClippedOut"); |
| 1834 | cs.restore(); |
| 1835 | |
| 1836 | // Intersecting clip is kClipped for complex shape |
| 1837 | cs.save(); |
| 1838 | SkPath path = make_octagon(rect.makeOutset(5.f, 5.f), 5.f, 5.f); |
| 1839 | cs.clipPath(SkMatrix::I(), path, GrAA::kYes, SkClipOp::kIntersect); |
| 1840 | result = cs.preApply(path.getBounds(), GrAA::kNo); |
| 1841 | REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped && !result.fIsRRect, |
| 1842 | "Draw with complex clip is kClipped, but is not an rrect"); |
| 1843 | cs.restore(); |
| 1844 | |
| 1845 | // Intersecting clip is kDeviceRect for axis-aligned rect clip |
| 1846 | cs.save(); |
| 1847 | cs.clipRect(SkMatrix::I(), rect, GrAA::kYes, SkClipOp::kIntersect); |
| 1848 | result = cs.preApply(rect.makeOffset(2.f, 2.f), GrAA::kNo); |
| 1849 | REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped && |
| 1850 | result.fAA == GrAA::kYes && |
| 1851 | result.fIsRRect && |
| 1852 | result.fRRect == SkRRect::MakeRect(rect), |
| 1853 | "kDeviceRect clip stack should be reported by preApply"); |
| 1854 | cs.restore(); |
| 1855 | |
| 1856 | // Intersecting clip is kDeviceRRect for axis-aligned rrect clip |
| 1857 | cs.save(); |
| 1858 | SkRRect clipRRect = SkRRect::MakeRectXY(rect, 5.f, 5.f); |
| 1859 | cs.clipRRect(SkMatrix::I(), clipRRect, GrAA::kYes, SkClipOp::kIntersect); |
| 1860 | result = cs.preApply(rect.makeOffset(2.f, 2.f), GrAA::kNo); |
| 1861 | REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped && |
| 1862 | result.fAA == GrAA::kYes && |
| 1863 | result.fIsRRect && |
| 1864 | result.fRRect == clipRRect, |
| 1865 | "kDeviceRRect clip stack should be reported by preApply"); |
| 1866 | cs.restore(); |
| 1867 | } |
| 1868 | |
| 1869 | // Tests the clip shader entry point |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1870 | DEF_TEST(ClipStack_Shader, r) { |
| 1871 | using ClipStack = skgpu::v1::ClipStack; |
| 1872 | using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext; |
| 1873 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1874 | sk_sp<SkShader> shader = SkShaders::Color({0.f, 0.f, 0.f, 0.5f}, nullptr); |
| 1875 | |
| 1876 | SkSimpleMatrixProvider matrixProvider = SkMatrix::I(); |
| 1877 | sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(nullptr); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1878 | std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make( |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1879 | context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(), |
Chris Dalton | f5b87f9 | 2021-04-19 17:27:09 -0600 | [diff] [blame] | 1880 | SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps()); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1881 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1882 | ClipStack cs(kDeviceBounds, &matrixProvider, false); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1883 | cs.save(); |
| 1884 | cs.clipShader(shader); |
| 1885 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1886 | REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kComplex, |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1887 | "A clip shader should be reported as a complex clip"); |
| 1888 | |
| 1889 | GrAppliedClip out(kDeviceBounds.size()); |
| 1890 | SkRect drawBounds = {10.f, 11.f, 16.f, 32.f}; |
Robert Phillips | 4dca831 | 2021-07-28 15:13:20 -0400 | [diff] [blame] | 1891 | GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage, |
Chris Dalton | b1e8f85 | 2021-06-23 13:36:51 -0600 | [diff] [blame] | 1892 | &out, &drawBounds); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1893 | |
| 1894 | REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped, |
| 1895 | "apply() should return kClipped for a clip shader"); |
| 1896 | REPORTER_ASSERT(r, out.hasCoverageFragmentProcessor(), |
| 1897 | "apply() should have converted clip shader to a coverage FP"); |
| 1898 | |
| 1899 | GrAppliedClip out2(kDeviceBounds.size()); |
| 1900 | drawBounds = {-15.f, -10.f, -1.f, 10.f}; // offscreen |
Robert Phillips | 4dca831 | 2021-07-28 15:13:20 -0400 | [diff] [blame] | 1901 | effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage, &out2, |
Chris Dalton | b1e8f85 | 2021-06-23 13:36:51 -0600 | [diff] [blame] | 1902 | &drawBounds); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1903 | REPORTER_ASSERT(r, effect == GrClip::Effect::kClippedOut, |
| 1904 | "apply() should still discard offscreen draws with a clip shader"); |
| 1905 | |
| 1906 | cs.restore(); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1907 | REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kWideOpen, |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1908 | "restore() should get rid of the clip shader"); |
| 1909 | |
| 1910 | |
| 1911 | // Adding a clip shader on top of a device rect clip should prevent preApply from reporting |
| 1912 | // it as a device rect |
| 1913 | cs.clipRect(SkMatrix::I(), {10, 15, 30, 30}, GrAA::kNo, SkClipOp::kIntersect); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1914 | SkASSERT(cs.clipState() == ClipStack::ClipState::kDeviceRect); // test precondition |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1915 | cs.clipShader(shader); |
| 1916 | GrClip::PreClipResult result = cs.preApply(SkRect::Make(kDeviceBounds), GrAA::kYes); |
| 1917 | REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped && !result.fIsRRect, |
| 1918 | "A clip shader should not produce a device rect from preApply"); |
| 1919 | } |
| 1920 | |
| 1921 | // Tests apply() under simple circumstances, that don't require actual rendering of masks, or |
| 1922 | // atlases. This lets us define the test regularly instead of a GPU-only test. |
| 1923 | // - This is not exhaustive and is challenging to unit test, so apply() is predominantly tested by |
| 1924 | // the GMs instead. |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1925 | DEF_TEST(ClipStack_SimpleApply, r) { |
| 1926 | using ClipStack = skgpu::v1::ClipStack; |
| 1927 | using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext; |
| 1928 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1929 | SkSimpleMatrixProvider matrixProvider = SkMatrix::I(); |
| 1930 | sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(nullptr); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1931 | std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make( |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1932 | context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(), |
Chris Dalton | f5b87f9 | 2021-04-19 17:27:09 -0600 | [diff] [blame] | 1933 | SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps()); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1934 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 1935 | ClipStack cs(kDeviceBounds, &matrixProvider, false); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1936 | |
| 1937 | // Offscreen draw is kClippedOut |
| 1938 | { |
| 1939 | SkRect drawBounds = {-15.f, -15.f, -1.f, -1.f}; |
| 1940 | |
| 1941 | GrAppliedClip out(kDeviceBounds.size()); |
Robert Phillips | 4dca831 | 2021-07-28 15:13:20 -0400 | [diff] [blame] | 1942 | GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage, |
Chris Dalton | b1e8f85 | 2021-06-23 13:36:51 -0600 | [diff] [blame] | 1943 | &out, &drawBounds); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1944 | REPORTER_ASSERT(r, effect == GrClip::Effect::kClippedOut, "Offscreen draw is clipped out"); |
| 1945 | } |
| 1946 | |
| 1947 | // Draw contained in clip is kUnclipped |
| 1948 | { |
| 1949 | SkRect drawBounds = {15.4f, 16.3f, 26.f, 32.f}; |
| 1950 | cs.save(); |
| 1951 | cs.clipPath(SkMatrix::I(), make_octagon(drawBounds.makeOutset(5.f, 5.f), 5.f, 5.f), |
| 1952 | GrAA::kYes, SkClipOp::kIntersect); |
| 1953 | |
| 1954 | GrAppliedClip out(kDeviceBounds.size()); |
Robert Phillips | 4dca831 | 2021-07-28 15:13:20 -0400 | [diff] [blame] | 1955 | GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage, |
Chris Dalton | b1e8f85 | 2021-06-23 13:36:51 -0600 | [diff] [blame] | 1956 | &out, &drawBounds); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1957 | REPORTER_ASSERT(r, effect == GrClip::Effect::kUnclipped, "Draw inside clip is unclipped"); |
| 1958 | cs.restore(); |
| 1959 | } |
| 1960 | |
| 1961 | // Draw bounds are cropped to device space before checking contains |
| 1962 | { |
| 1963 | SkRect clipRect = {kDeviceBounds.fRight - 20.f, 10.f, kDeviceBounds.fRight, 20.f}; |
| 1964 | SkRect drawRect = clipRect.makeOffset(10.f, 0.f); |
| 1965 | |
| 1966 | cs.save(); |
| 1967 | cs.clipRect(SkMatrix::I(), clipRect, GrAA::kNo, SkClipOp::kIntersect); |
| 1968 | |
| 1969 | GrAppliedClip out(kDeviceBounds.size()); |
Robert Phillips | 4dca831 | 2021-07-28 15:13:20 -0400 | [diff] [blame] | 1970 | GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage, |
Chris Dalton | b1e8f85 | 2021-06-23 13:36:51 -0600 | [diff] [blame] | 1971 | &out, &drawRect); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1972 | REPORTER_ASSERT(r, SkRect::Make(kDeviceBounds).contains(drawRect), |
| 1973 | "Draw rect should be clipped to device rect"); |
| 1974 | REPORTER_ASSERT(r, effect == GrClip::Effect::kUnclipped, |
| 1975 | "After device clipping, this should be detected as contained within clip"); |
| 1976 | cs.restore(); |
| 1977 | } |
| 1978 | |
| 1979 | // Non-AA device rect intersect is just a scissor |
| 1980 | { |
| 1981 | SkRect clipRect = {15.3f, 17.23f, 30.2f, 50.8f}; |
| 1982 | SkRect drawRect = clipRect.makeOutset(10.f, 10.f); |
| 1983 | SkIRect expectedScissor = clipRect.round(); |
| 1984 | |
| 1985 | cs.save(); |
| 1986 | cs.clipRect(SkMatrix::I(), clipRect, GrAA::kNo, SkClipOp::kIntersect); |
| 1987 | |
| 1988 | GrAppliedClip out(kDeviceBounds.size()); |
Robert Phillips | 4dca831 | 2021-07-28 15:13:20 -0400 | [diff] [blame] | 1989 | GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage, |
Chris Dalton | b1e8f85 | 2021-06-23 13:36:51 -0600 | [diff] [blame] | 1990 | &out, &drawRect); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 1991 | REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped, "Draw should be clipped by rect"); |
| 1992 | REPORTER_ASSERT(r, !out.hasCoverageFragmentProcessor(), "Clip should not use coverage FPs"); |
| 1993 | REPORTER_ASSERT(r, !out.hardClip().hasStencilClip(), "Clip should not need stencil"); |
| 1994 | REPORTER_ASSERT(r, !out.hardClip().windowRectsState().enabled(), |
| 1995 | "Clip should not need window rects"); |
| 1996 | REPORTER_ASSERT(r, out.scissorState().enabled() && |
| 1997 | out.scissorState().rect() == expectedScissor, |
| 1998 | "Clip has unexpected scissor rectangle"); |
| 1999 | cs.restore(); |
| 2000 | } |
| 2001 | |
| 2002 | // Analytic coverage FPs |
| 2003 | auto testHasCoverageFP = [&](SkRect drawBounds) { |
| 2004 | GrAppliedClip out(kDeviceBounds.size()); |
Robert Phillips | 4dca831 | 2021-07-28 15:13:20 -0400 | [diff] [blame] | 2005 | GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage, |
Chris Dalton | b1e8f85 | 2021-06-23 13:36:51 -0600 | [diff] [blame] | 2006 | &out, &drawBounds); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 2007 | REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped, "Draw should be clipped"); |
| 2008 | REPORTER_ASSERT(r, out.scissorState().enabled(), "Coverage FPs should still set scissor"); |
| 2009 | REPORTER_ASSERT(r, out.hasCoverageFragmentProcessor(), "Clip should use coverage FP"); |
| 2010 | }; |
| 2011 | |
| 2012 | // Axis-aligned rect can be an analytic FP |
| 2013 | { |
| 2014 | cs.save(); |
| 2015 | cs.clipRect(SkMatrix::I(), {10.2f, 8.342f, 63.f, 23.3f}, GrAA::kYes, |
| 2016 | SkClipOp::kDifference); |
| 2017 | testHasCoverageFP({9.f, 10.f, 30.f, 18.f}); |
| 2018 | cs.restore(); |
| 2019 | } |
| 2020 | |
| 2021 | // Axis-aligned round rect can be an analytic FP |
| 2022 | { |
| 2023 | SkRect rect = {4.f, 8.f, 20.f, 20.f}; |
| 2024 | cs.save(); |
| 2025 | cs.clipRRect(SkMatrix::I(), SkRRect::MakeRectXY(rect, 3.f, 3.f), GrAA::kYes, |
| 2026 | SkClipOp::kIntersect); |
| 2027 | testHasCoverageFP(rect.makeOffset(2.f, 2.f)); |
| 2028 | cs.restore(); |
| 2029 | } |
| 2030 | |
| 2031 | // Transformed rect can be an analytic FP |
| 2032 | { |
| 2033 | SkRect rect = {14.f, 8.f, 30.f, 22.34f}; |
| 2034 | SkMatrix rot = SkMatrix::RotateDeg(34.f); |
| 2035 | cs.save(); |
| 2036 | cs.clipRect(rot, rect, GrAA::kNo, SkClipOp::kIntersect); |
| 2037 | testHasCoverageFP(rot.mapRect(rect)); |
| 2038 | cs.restore(); |
| 2039 | } |
| 2040 | |
| 2041 | // Convex polygons can be an analytic FP |
| 2042 | { |
| 2043 | SkRect rect = {15.f, 15.f, 45.f, 45.f}; |
| 2044 | cs.save(); |
| 2045 | cs.clipPath(SkMatrix::I(), make_octagon(rect), GrAA::kYes, SkClipOp::kIntersect); |
| 2046 | testHasCoverageFP(rect.makeOutset(2.f, 2.f)); |
| 2047 | cs.restore(); |
| 2048 | } |
| 2049 | } |
| 2050 | |
Chris Dalton | e8f6663 | 2021-06-16 11:59:22 -0600 | [diff] [blame] | 2051 | // Must disable tessellation in order to trigger SW mask generation when the clip stack is applied. |
| 2052 | static void disable_tessellation_atlas(GrContextOptions* options) { |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 2053 | options->fGpuPathRenderers = GpuPathRenderers::kNone; |
Chris Dalton | e8f6663 | 2021-06-16 11:59:22 -0600 | [diff] [blame] | 2054 | options->fAvoidStencilBuffers = true; |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 2055 | } |
| 2056 | |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 2057 | DEF_GPUTEST_FOR_CONTEXTS(ClipStack_SWMask, |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 2058 | sk_gpu_test::GrContextFactory::IsRenderingContext, |
Chris Dalton | e8f6663 | 2021-06-16 11:59:22 -0600 | [diff] [blame] | 2059 | r, ctxInfo, disable_tessellation_atlas) { |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 2060 | using ClipStack = skgpu::v1::ClipStack; |
| 2061 | using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext; |
| 2062 | |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 2063 | GrDirectContext* context = ctxInfo.directContext(); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 2064 | std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make( |
Chris Dalton | f5b87f9 | 2021-04-19 17:27:09 -0600 | [diff] [blame] | 2065 | context, GrColorType::kRGBA_8888, nullptr, SkBackingFit::kExact, kDeviceBounds.size(), |
| 2066 | SkSurfaceProps()); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 2067 | |
| 2068 | SkSimpleMatrixProvider matrixProvider = SkMatrix::I(); |
Robert Phillips | 74b9432 | 2021-08-17 11:50:20 -0400 | [diff] [blame] | 2069 | std::unique_ptr<ClipStack> cs(new ClipStack(kDeviceBounds, &matrixProvider, false)); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 2070 | |
| 2071 | auto addMaskRequiringClip = [&](SkScalar x, SkScalar y, SkScalar radius) { |
| 2072 | SkPath path; |
| 2073 | path.addCircle(x, y, radius); |
| 2074 | path.addCircle(x + radius / 2.f, y + radius / 2.f, radius); |
| 2075 | path.setFillType(SkPathFillType::kEvenOdd); |
| 2076 | |
| 2077 | // Use AA so that clip application does not route through the stencil buffer |
| 2078 | cs->clipPath(SkMatrix::I(), path, GrAA::kYes, SkClipOp::kIntersect); |
| 2079 | }; |
| 2080 | |
| 2081 | auto drawRect = [&](SkRect drawBounds) { |
| 2082 | GrPaint paint; |
| 2083 | paint.setColor4f({1.f, 1.f, 1.f, 1.f}); |
Robert Phillips | 4dca831 | 2021-07-28 15:13:20 -0400 | [diff] [blame] | 2084 | sdc->drawRect(cs.get(), std::move(paint), GrAA::kYes, SkMatrix::I(), drawBounds); |
Michael Ludwig | a195d10 | 2020-09-15 14:51:52 -0400 | [diff] [blame] | 2085 | }; |
| 2086 | |
| 2087 | auto generateMask = [&](SkRect drawBounds) { |
| 2088 | GrUniqueKey priorKey = cs->testingOnly_getLastSWMaskKey(); |
| 2089 | drawRect(drawBounds); |
| 2090 | GrUniqueKey newKey = cs->testingOnly_getLastSWMaskKey(); |
| 2091 | REPORTER_ASSERT(r, priorKey != newKey, "Did not generate a new SW mask key as expected"); |
| 2092 | return newKey; |
| 2093 | }; |
| 2094 | |
| 2095 | auto verifyKeys = [&](const std::vector<GrUniqueKey>& expectedKeys, |
| 2096 | const std::vector<GrUniqueKey>& releasedKeys) { |
| 2097 | context->flush(); |
| 2098 | GrProxyProvider* proxyProvider = context->priv().proxyProvider(); |
| 2099 | |
| 2100 | #ifdef SK_DEBUG |
| 2101 | // The proxy providers key count fluctuates based on proxy lifetime, but we want to |
| 2102 | // verify the resource count, and that requires using key tags that are debug-only. |
| 2103 | SkASSERT(expectedKeys.size() > 0 || releasedKeys.size() > 0); |
| 2104 | const char* tag = expectedKeys.size() > 0 ? expectedKeys[0].tag() : releasedKeys[0].tag(); |
| 2105 | GrResourceCache* cache = context->priv().getResourceCache(); |
| 2106 | int numProxies = cache->countUniqueKeysWithTag(tag); |
| 2107 | REPORTER_ASSERT(r, (int) expectedKeys.size() == numProxies, |
| 2108 | "Unexpected proxy count, got %d, not %d", |
| 2109 | numProxies, (int) expectedKeys.size()); |
| 2110 | #endif |
| 2111 | |
| 2112 | for (const auto& key : expectedKeys) { |
| 2113 | auto proxy = proxyProvider->findOrCreateProxyByUniqueKey(key); |
| 2114 | REPORTER_ASSERT(r, SkToBool(proxy), "Unable to find resource for expected mask key"); |
| 2115 | } |
| 2116 | for (const auto& key : releasedKeys) { |
| 2117 | auto proxy = proxyProvider->findOrCreateProxyByUniqueKey(key); |
| 2118 | REPORTER_ASSERT(r, !SkToBool(proxy), "SW mask not released as expected"); |
| 2119 | } |
| 2120 | }; |
| 2121 | |
| 2122 | // Creates a mask for a complex clip |
| 2123 | cs->save(); |
| 2124 | addMaskRequiringClip(5.f, 5.f, 20.f); |
| 2125 | GrUniqueKey keyADepth1 = generateMask({0.f, 0.f, 20.f, 20.f}); |
| 2126 | GrUniqueKey keyBDepth1 = generateMask({10.f, 10.f, 30.f, 30.f}); |
| 2127 | verifyKeys({keyADepth1, keyBDepth1}, {}); |
| 2128 | |
| 2129 | // Creates a new mask for a new save record, but doesn't delete the old records |
| 2130 | cs->save(); |
| 2131 | addMaskRequiringClip(6.f, 6.f, 15.f); |
| 2132 | GrUniqueKey keyADepth2 = generateMask({0.f, 0.f, 20.f, 20.f}); |
| 2133 | GrUniqueKey keyBDepth2 = generateMask({10.f, 10.f, 30.f, 30.f}); |
| 2134 | verifyKeys({keyADepth1, keyBDepth1, keyADepth2, keyBDepth2}, {}); |
| 2135 | |
| 2136 | // Release after modifying the current record (even if we don't draw anything) |
| 2137 | addMaskRequiringClip(4.f, 4.f, 15.f); |
| 2138 | GrUniqueKey keyCDepth2 = generateMask({4.f, 4.f, 16.f, 20.f}); |
| 2139 | verifyKeys({keyADepth1, keyBDepth1, keyCDepth2}, {keyADepth2, keyBDepth2}); |
| 2140 | |
| 2141 | // Release after restoring an older record |
| 2142 | cs->restore(); |
| 2143 | verifyKeys({keyADepth1, keyBDepth1}, {keyCDepth2}); |
| 2144 | |
| 2145 | // Drawing finds the old masks at depth 1 still w/o making new ones |
| 2146 | drawRect({0.f, 0.f, 20.f, 20.f}); |
| 2147 | drawRect({10.f, 10.f, 30.f, 30.f}); |
| 2148 | verifyKeys({keyADepth1, keyBDepth1}, {}); |
| 2149 | |
| 2150 | // Drawing something contained within a previous mask also does not make a new one |
| 2151 | drawRect({5.f, 5.f, 15.f, 15.f}); |
| 2152 | verifyKeys({keyADepth1, keyBDepth1}, {}); |
| 2153 | |
| 2154 | // Release on destruction |
| 2155 | cs = nullptr; |
| 2156 | verifyKeys({}, {keyADepth1, keyBDepth1}); |
| 2157 | } |