1 /*
2 * Copyright 2019 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/core/SkMatrix.h"
9 #include "include/core/SkPoint.h"
10 #include "include/core/SkRect.h"
11 #include "include/core/SkScalar.h"
12 #include "include/core/SkTypes.h"
13 #include "include/private/gpu/ganesh/GrTypesPriv.h"
14 #include "src/gpu/ganesh/geometry/GrQuad.h"
15 #include "src/gpu/ganesh/geometry/GrQuadUtils.h"
16 #include "tests/Test.h"
17
18 #define ASSERT(cond) REPORTER_ASSERT(r, cond)
19 #define ASSERTF(cond, ...) REPORTER_ASSERT(r, cond, __VA_ARGS__)
20 #define TEST(name) DEF_TEST(GrQuadCrop##name, r)
21 #define ASSERT_NEARLY_EQUAL(expected, actual) \
22 ASSERTF(SkScalarNearlyEqual(expected, actual), "expected: %f, actual: %f", \
23 expected, actual)
24
25 // Make the base rect contain the origin and have unique edge values so that each transform
26 // produces a different axis-aligned rectangle.
27 static const SkRect kDrawRect = SkRect::MakeLTRB(-5.f, -6.f, 10.f, 11.f);
28
run_crop_axis_aligned_test(skiatest::Reporter * r,const SkRect & clipRect,GrAA clipAA,const SkMatrix & viewMatrix,const SkMatrix * localMatrix)29 static void run_crop_axis_aligned_test(skiatest::Reporter* r, const SkRect& clipRect, GrAA clipAA,
30 const SkMatrix& viewMatrix, const SkMatrix* localMatrix) {
31 // Should use run_crop_fully_covers_test for non-rect matrices
32 SkASSERT(viewMatrix.rectStaysRect());
33
34 DrawQuad quad = {GrQuad::MakeFromRect(kDrawRect, viewMatrix),
35 GrQuad::MakeFromRect(kDrawRect, localMatrix ? *localMatrix : SkMatrix::I()),
36 clipAA == GrAA::kYes ? GrQuadAAFlags::kNone : GrQuadAAFlags::kAll};
37
38 bool exact = GrQuadUtils::CropToRect(clipRect, clipAA, &quad, /* calc. locals */ !!localMatrix);
39 ASSERTF(exact, "Expected exact crop");
40 ASSERTF(quad.fDevice.quadType() == GrQuad::Type::kAxisAligned,
41 "Expected quad to remain axis-aligned");
42
43 // Since we remained a rectangle, the bounds will exactly match the coordinates
44 SkRect expectedBounds = viewMatrix.mapRect(kDrawRect);
45 SkAssertResult(expectedBounds.intersect(clipRect));
46
47 SkRect actualBounds = quad.fDevice.bounds();
48 ASSERT_NEARLY_EQUAL(expectedBounds.fLeft, actualBounds.fLeft);
49 ASSERT_NEARLY_EQUAL(expectedBounds.fTop, actualBounds.fTop);
50 ASSERT_NEARLY_EQUAL(expectedBounds.fRight, actualBounds.fRight);
51 ASSERT_NEARLY_EQUAL(expectedBounds.fBottom, actualBounds.fBottom);
52
53 // Confirm that local coordinates match up with clipped edges and the transform
54 SkMatrix invViewMatrix;
55 SkAssertResult(viewMatrix.invert(&invViewMatrix));
56
57 if (localMatrix) {
58 SkMatrix toLocal = SkMatrix::Concat(*localMatrix, invViewMatrix);
59
60 for (int p = 0; p < 4; ++p) {
61 SkPoint expectedPoint = quad.fDevice.point(p);
62 toLocal.mapPoints(&expectedPoint, 1);
63 SkPoint actualPoint = quad.fLocal.point(p);
64
65 ASSERT_NEARLY_EQUAL(expectedPoint.fX, actualPoint.fX);
66 ASSERT_NEARLY_EQUAL(expectedPoint.fY, actualPoint.fY);
67 }
68 }
69
70 // Confirm that the edge flags match, by mapping clip rect to drawRect space and
71 // comparing to the original draw rect edges
72 SkRect drawClip = invViewMatrix.mapRect(clipRect);
73 if (drawClip.fLeft > kDrawRect.fLeft) {
74 if (clipAA == GrAA::kYes) {
75 ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kLeft, "Expected left edge AA set");
76 } else {
77 ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kLeft), "Expected left edge AA unset");
78 }
79 }
80 if (drawClip.fRight < kDrawRect.fRight) {
81 if (clipAA == GrAA::kYes) {
82 ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kRight, "Expected right edge AA set");
83 } else {
84 ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kRight), "Expected right edge AA unset");
85 }
86 }
87 if (drawClip.fTop > kDrawRect.fTop) {
88 if (clipAA == GrAA::kYes) {
89 ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kTop, "Expected top edge AA set");
90 } else {
91 ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kTop), "Expected top edge AA unset");
92 }
93 }
94 if (drawClip.fBottom < kDrawRect.fBottom) {
95 if (clipAA == GrAA::kYes) {
96 ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kBottom, "Expected bottom edge AA set");
97 } else {
98 ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kBottom), "Expected bottom edge AA unset");
99 }
100 }
101 }
102
run_crop_fully_covered_test(skiatest::Reporter * r,GrAA clipAA,const SkMatrix & viewMatrix,const SkMatrix * localMatrix)103 static void run_crop_fully_covered_test(skiatest::Reporter* r, GrAA clipAA,
104 const SkMatrix& viewMatrix, const SkMatrix* localMatrix) {
105 // Should use run_crop_axis_aligned for rect transforms since that verifies more behavior
106 SkASSERT(!viewMatrix.rectStaysRect());
107
108 // Test what happens when the geometry fully covers the crop rect. Given a fixed crop,
109 // use the provided view matrix to derive the "input" geometry that we know covers the crop.
110 SkMatrix invViewMatrix;
111 SkAssertResult(viewMatrix.invert(&invViewMatrix));
112
113 SkRect containsCrop = kDrawRect; // Use kDrawRect as the crop rect for this test
114 containsCrop.outset(10.f, 10.f);
115 SkRect drawRect = invViewMatrix.mapRect(containsCrop);
116
117 DrawQuad quad = {GrQuad::MakeFromRect(drawRect, viewMatrix),
118 GrQuad::MakeFromRect(drawRect, localMatrix ? *localMatrix : SkMatrix::I()),
119 clipAA == GrAA::kYes ? GrQuadAAFlags::kNone : GrQuadAAFlags::kAll};
120
121 if (localMatrix) {
122 DrawQuad originalQuad = quad;
123
124 bool exact = GrQuadUtils::CropToRect(kDrawRect, clipAA, &quad);
125 // Currently non-rect matrices don't know how to update local coordinates, so the crop
126 // doesn't know how to restrict itself and should leave the inputs unmodified
127 ASSERTF(!exact, "Expected crop to be not exact");
128 ASSERTF(quad.fEdgeFlags == originalQuad.fEdgeFlags,
129 "Expected edge flags not to be modified");
130
131 for (int i = 0; i < 4; ++i) {
132 ASSERT_NEARLY_EQUAL(originalQuad.fDevice.x(i), quad.fDevice.x(i));
133 ASSERT_NEARLY_EQUAL(originalQuad.fDevice.y(i), quad.fDevice.y(i));
134 ASSERT_NEARLY_EQUAL(originalQuad.fDevice.w(i), quad.fDevice.w(i));
135
136 ASSERT_NEARLY_EQUAL(originalQuad.fLocal.x(i), quad.fLocal.x(i));
137 ASSERT_NEARLY_EQUAL(originalQuad.fLocal.y(i), quad.fLocal.y(i));
138 ASSERT_NEARLY_EQUAL(originalQuad.fLocal.w(i), quad.fLocal.w(i));
139 }
140 } else {
141 // Since no local coordinates were provided, and the input draw geometry is known to
142 // fully cover the crop rect, the quad should be updated to match cropRect exactly,
143 // unless it's perspective in which case we don't do anything since the code isn't
144 // numerically robust enough.
145 DrawQuad originalQuad = quad;
146 bool exact = GrQuadUtils::CropToRect(kDrawRect, clipAA, &quad, /* calc. local */ false);
147 if (originalQuad.fDevice.quadType() == GrQuad::Type::kPerspective) {
148 ASSERTF(!exact, "Expected no change for perspective");
149 for (int i = 0; i < 4; ++i) {
150 ASSERTF(originalQuad.fDevice.x(i) == quad.fDevice.x(i));
151 ASSERTF(originalQuad.fDevice.y(i) == quad.fDevice.y(i));
152 ASSERTF(originalQuad.fDevice.w(i) == quad.fDevice.w(i));
153 }
154 return;
155 }
156
157 ASSERTF(exact, "Expected crop to be exact");
158 GrQuadAAFlags expectedFlags = clipAA == GrAA::kYes ? GrQuadAAFlags::kAll
159 : GrQuadAAFlags::kNone;
160 ASSERTF(expectedFlags == quad.fEdgeFlags,
161 "Expected edge flags do not match clip AA setting");
162 ASSERTF(quad.fDevice.quadType() == GrQuad::Type::kAxisAligned, "Unexpected quad type");
163
164 ASSERT_NEARLY_EQUAL(kDrawRect.fLeft, quad.fDevice.x(0));
165 ASSERT_NEARLY_EQUAL(kDrawRect.fTop, quad.fDevice.y(0));
166 ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(0));
167
168 ASSERT_NEARLY_EQUAL(kDrawRect.fLeft, quad.fDevice.x(1));
169 ASSERT_NEARLY_EQUAL(kDrawRect.fBottom, quad.fDevice.y(1));
170 ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(1));
171
172 ASSERT_NEARLY_EQUAL(kDrawRect.fRight, quad.fDevice.x(2));
173 ASSERT_NEARLY_EQUAL(kDrawRect.fTop, quad.fDevice.y(2));
174 ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(2));
175
176 ASSERT_NEARLY_EQUAL(kDrawRect.fRight, quad.fDevice.x(3));
177 ASSERT_NEARLY_EQUAL(kDrawRect.fBottom, quad.fDevice.y(3));
178 ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(3));
179 }
180 }
181
test_axis_aligned_all_clips(skiatest::Reporter * r,const SkMatrix & viewMatrix,const SkMatrix * localMatrix)182 static void test_axis_aligned_all_clips(skiatest::Reporter* r, const SkMatrix& viewMatrix,
183 const SkMatrix* localMatrix) {
184 static const float kInsideEdge = SkScalarAbs(kDrawRect.fLeft) - 1.f;
185 static const float kOutsideEdge = SkScalarAbs(kDrawRect.fBottom) + 1.f;
186 static const float kIntersectEdge = SkScalarAbs(kDrawRect.fTop) + 1.f;
187
188 static const SkRect kInsideClipRect = SkRect::MakeLTRB(-kInsideEdge, -kInsideEdge,
189 kInsideEdge, kInsideEdge);
190 static const SkRect kContainsClipRect = SkRect::MakeLTRB(-kOutsideEdge, -kOutsideEdge,
191 kOutsideEdge, kOutsideEdge);
192 static const SkRect kXYAxesClipRect = SkRect::MakeLTRB(-kIntersectEdge, -kIntersectEdge,
193 kIntersectEdge, kIntersectEdge);
194 static const SkRect kXAxisClipRect = SkRect::MakeLTRB(-kIntersectEdge, -kOutsideEdge,
195 kIntersectEdge, kOutsideEdge);
196 static const SkRect kYAxisClipRect = SkRect::MakeLTRB(-kOutsideEdge, -kIntersectEdge,
197 kOutsideEdge, kIntersectEdge);
198
199 run_crop_axis_aligned_test(r, kInsideClipRect, GrAA::kNo, viewMatrix, localMatrix);
200 run_crop_axis_aligned_test(r, kContainsClipRect, GrAA::kNo, viewMatrix, localMatrix);
201 run_crop_axis_aligned_test(r, kXYAxesClipRect, GrAA::kNo, viewMatrix, localMatrix);
202 run_crop_axis_aligned_test(r, kXAxisClipRect, GrAA::kNo, viewMatrix, localMatrix);
203 run_crop_axis_aligned_test(r, kYAxisClipRect, GrAA::kNo, viewMatrix, localMatrix);
204
205 run_crop_axis_aligned_test(r, kInsideClipRect, GrAA::kYes, viewMatrix, localMatrix);
206 run_crop_axis_aligned_test(r, kContainsClipRect, GrAA::kYes, viewMatrix, localMatrix);
207 run_crop_axis_aligned_test(r, kXYAxesClipRect, GrAA::kYes, viewMatrix, localMatrix);
208 run_crop_axis_aligned_test(r, kXAxisClipRect, GrAA::kYes, viewMatrix, localMatrix);
209 run_crop_axis_aligned_test(r, kYAxisClipRect, GrAA::kYes, viewMatrix, localMatrix);
210 }
211
test_axis_aligned(skiatest::Reporter * r,const SkMatrix & viewMatrix)212 static void test_axis_aligned(skiatest::Reporter* r, const SkMatrix& viewMatrix) {
213 test_axis_aligned_all_clips(r, viewMatrix, nullptr);
214
215 SkMatrix normalized = SkMatrix::RectToRect(kDrawRect, SkRect::MakeWH(1.f, 1.f));
216 test_axis_aligned_all_clips(r, viewMatrix, &normalized);
217
218 SkMatrix rotated;
219 rotated.setRotate(45.f);
220 test_axis_aligned_all_clips(r, viewMatrix, &rotated);
221
222 SkMatrix perspective;
223 perspective.setPerspY(0.001f);
224 perspective.setSkewX(8.f / 25.f);
225 test_axis_aligned_all_clips(r, viewMatrix, &perspective);
226 }
227
test_crop_fully_covered(skiatest::Reporter * r,const SkMatrix & viewMatrix)228 static void test_crop_fully_covered(skiatest::Reporter* r, const SkMatrix& viewMatrix) {
229 run_crop_fully_covered_test(r, GrAA::kNo, viewMatrix, nullptr);
230 run_crop_fully_covered_test(r, GrAA::kYes, viewMatrix, nullptr);
231
232 SkMatrix normalized = SkMatrix::RectToRect(kDrawRect, SkRect::MakeWH(1.f, 1.f));
233 run_crop_fully_covered_test(r, GrAA::kNo, viewMatrix, &normalized);
234 run_crop_fully_covered_test(r, GrAA::kYes, viewMatrix, &normalized);
235
236 SkMatrix rotated;
237 rotated.setRotate(45.f);
238 run_crop_fully_covered_test(r, GrAA::kNo, viewMatrix, &rotated);
239 run_crop_fully_covered_test(r, GrAA::kYes, viewMatrix, &rotated);
240
241 SkMatrix perspective;
242 perspective.setPerspY(0.001f);
243 perspective.setSkewX(8.f / 25.f);
244 run_crop_fully_covered_test(r, GrAA::kNo, viewMatrix, &perspective);
245 run_crop_fully_covered_test(r, GrAA::kYes, viewMatrix, &perspective);
246 }
247
TEST(AxisAligned)248 TEST(AxisAligned) {
249 test_axis_aligned(r, SkMatrix::I());
250 test_axis_aligned(r, SkMatrix::Scale(-1.f, 1.f));
251 test_axis_aligned(r, SkMatrix::Scale(1.f, -1.f));
252
253 SkMatrix rotation;
254 rotation.setRotate(90.f);
255 test_axis_aligned(r, rotation);
256 rotation.setRotate(180.f);
257 test_axis_aligned(r, rotation);
258 rotation.setRotate(270.f);
259 test_axis_aligned(r, rotation);
260 }
261
TEST(FullyCovered)262 TEST(FullyCovered) {
263 SkMatrix rotation;
264 rotation.setRotate(34.f);
265 test_crop_fully_covered(r, rotation);
266
267 SkMatrix skew;
268 skew.setSkewX(0.3f);
269 skew.setSkewY(0.04f);
270 test_crop_fully_covered(r, skew);
271
272 SkMatrix perspective;
273 perspective.setPerspX(0.001f);
274 perspective.setSkewY(8.f / 25.f);
275 test_crop_fully_covered(r, perspective);
276 }
277