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