blob: f37ae734f4b45942dd590e1a99186ec6342a2998 [file] [log] [blame]
Chris Daltona6fcb762019-05-13 08:57:53 -06001/*
2 * Copyright 2019 Google Inc.
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/utils/SkRandom.h"
9#include "src/gpu/ccpr/GrOctoBounds.h"
10#include "tests/Test.h"
11
12using namespace skiatest;
13
14constexpr static float kEpsilon = 1e-3f;
15
16static int numClipsOut = 0;
17static int numIntersectClips = 0;
18
19// Ensures devBounds and devBounds45 are valid. Namely, that they are both tight bounding boxes
20// around a valid octagon.
21static void validate_octo_bounds(
22 Reporter* reporter, const SkIRect& clipRect, const GrOctoBounds& octoBounds) {
23 // Verify dev bounds are inside the clip rect.
24 REPORTER_ASSERT(reporter, octoBounds.left() >= (float)clipRect.left() - kEpsilon);
25 REPORTER_ASSERT(reporter, octoBounds.top() >= (float)clipRect.top() - kEpsilon);
26 REPORTER_ASSERT(reporter, octoBounds.right() <= (float)clipRect.right() + kEpsilon);
27 REPORTER_ASSERT(reporter, octoBounds.bottom() <= (float)clipRect.bottom() + kEpsilon);
28
29 octoBounds.validateBoundsAreTight([reporter](
30 bool cond, const char* file, int line, const char* code) {
31 if (!cond) {
32 reporter->reportFailedWithContext(skiatest::Failure(file, line, code, SkString()));
33 }
34 });
35}
36
37// This is a variant of SkRandom::nextRangeU that can handle negative numbers. As currently written,
38// and assuming two's compliment, it would probably work to just call the existing nextRangeU
39// implementation with negative value(s), but we go through this method as an extra precaution.
40static int next_range_i(SkRandom* rand, int min, int max) {
41 int u = rand->nextRangeU(0, max - min);
42 return u + min;
43}
44
45static void test_octagon(Reporter* reporter, SkRandom* rand, float l, float t, float r, float b) {
46 for (int i = 0; i < 20; ++i) {
47 float minL45 = GrOctoBounds::Get_x45(l,b);
48 float maxL45 = std::min(GrOctoBounds::Get_x45(r,b), GrOctoBounds::Get_x45(l,t));
49 float minT45 = GrOctoBounds::Get_y45(l,t);
50 float maxT45 = std::min(GrOctoBounds::Get_y45(l,b), GrOctoBounds::Get_y45(r,t));
51 float minR45 = std::max(GrOctoBounds::Get_x45(l,t), GrOctoBounds::Get_x45(r,b));
52 float maxR45 = GrOctoBounds::Get_x45(r,t);
53 float minB45 = std::max(GrOctoBounds::Get_y45(r,t), GrOctoBounds::Get_y45(l,b));
54 float maxB45 = GrOctoBounds::Get_y45(r,b);
55
56 // Pick somewhat valid 45 degree bounds.
57 float l45 = rand->nextRangeF(minL45, maxL45);
58 float t45 = rand->nextRangeF(minT45, maxT45);
59 float r45 = rand->nextRangeF(minR45, maxR45);
60 float b45 = rand->nextRangeF(minB45, maxB45);
61
62 // Grow out diagonal corners if too tight, making 45 bounds valid.
63 std::function<void()> growOutDiagonals[4] = {
64 [&]() { // Push top-left diagonal corner outside left edge.
65 float miss = GrOctoBounds::Get_x(l45,t45) - l;
66 if (miss > 0) {
67 // x = (x45 + y45)/2
68 l45 -= miss;
69 if (l45 < minL45) {
70 t45 -= minL45 - l45;
71 l45 = minL45;
72 }
73 t45 -= miss;
74 if (t45 < minT45) {
75 l45 -= minT45 - t45;
76 t45 = minT45;
77 }
78 }
79 },
80 [&]() { // Push top-right diagonal corner outside top edge.
81 float miss = GrOctoBounds::Get_y(r45,t45) - t;
82 if (miss > 0) {
83 // y = (y45 - x45)/2
84 r45 += miss;
85 if (r45 > maxR45) {
86 t45 -= r45 - maxR45;
87 r45 = maxR45;
88 }
89 t45 -= miss;
90 if (t45 < minT45) {
91 r45 += minT45 - t45;
92 t45 = minT45;
93 }
94 }
95 },
96 [&]() { // Push bottom-right diagonal corner outside right edge.
97 float miss = r - GrOctoBounds::Get_x(r45,b45);
98 if (miss > 0) {
99 // x = (x45 + y45)/2
100 r45 += miss;
101 if (r45 > maxR45) {
102 b45 += r45 - maxR45;
103 r45 = maxR45;
104 }
105 b45 += miss;
106 if (b45 > maxB45) {
107 r45 += b45 - maxB45;
108 b45 = maxB45;
109 }
110 }
111 },
112 [&]() { // Push bottom-left diagonal corner outside bottom edge.
113 float miss = b - GrOctoBounds::Get_y(l45,b45);
114 if (miss > 0) {
115 // y = (y45 - x45)/2
116 l45 -= miss;
117 if (l45 < minL45) {
118 b45 += minL45 - l45;
119 l45 = minL45;
120 }
121 b45 += miss;
122 if (b45 > maxB45) {
123 l45 -= b45 - maxB45;
124 b45 = maxB45;
125 }
126 }
127 },
128 };
129 // Shuffle.
130 for (int i = 0; i < 10; ++i) {
131 std::swap(growOutDiagonals[rand->nextRangeU(0, 3)],
132 growOutDiagonals[rand->nextRangeU(0, 3)]);
133 }
134 for (const auto& f : growOutDiagonals) {
135 f();
136 }
137
138 GrOctoBounds octoBounds(SkRect::MakeLTRB(l,t,r,b), SkRect::MakeLTRB(l45,t45,r45,b45));
139
140 SkIRect devIBounds;
141 octoBounds.roundOut(&devIBounds);
142
143 // Test a clip rect that completely encloses the octagon.
144 bool clipSuccess = octoBounds.clip(devIBounds);
145 REPORTER_ASSERT(reporter, clipSuccess);
146 // Should not have clipped anything.
147 REPORTER_ASSERT(reporter, octoBounds == GrOctoBounds({l,t,r,b}, {l45,t45,r45,b45}));
148 validate_octo_bounds(reporter, devIBounds, octoBounds);
149
150 // Test a bunch of random clip rects.
151 for (int j = 0; j < 20; ++j) {
152 SkIRect clipRect;
153 do {
154 clipRect.fLeft = next_range_i(rand, devIBounds.left(), devIBounds.right() - 1);
155 clipRect.fTop = next_range_i(rand, devIBounds.top(), devIBounds.bottom() - 1);
156 clipRect.fRight = next_range_i(rand, clipRect.left() + 1, devIBounds.right());
157 clipRect.fBottom = next_range_i(rand, clipRect.top() + 1, devIBounds.bottom());
158 } while (clipRect == devIBounds);
159
160 GrOctoBounds octoBoundsClipped = octoBounds;
161 if (!octoBoundsClipped.clip(clipRect)) {
162 // Ensure clipRect is completely outside one of the diagonals.
163 float il = (float)clipRect.left();
164 float it = (float)clipRect.top();
165 float ir = (float)clipRect.right();
166 float ib = (float)clipRect.bottom();
167 REPORTER_ASSERT(reporter,
168 GrOctoBounds::Get_x45(ir,it) <= l45 + kEpsilon ||
169 GrOctoBounds::Get_y45(ir,ib) <= t45 + kEpsilon ||
170 GrOctoBounds::Get_x45(il,ib) >= r45 - kEpsilon ||
171 GrOctoBounds::Get_y45(il,it) >= b45 - kEpsilon);
172 ++numClipsOut;
173 } else {
174 validate_octo_bounds(reporter, clipRect, octoBoundsClipped);
175 ++numIntersectClips;
176 }
177 }
178 }
179
180}
181
182DEF_TEST(OctoBounds, reporter) {
183 numClipsOut = 0;
184 numIntersectClips = 0;
185
186 SkRandom rand;
187 test_octagon(reporter, &rand, 0, 0, 100, 100);
188 test_octagon(reporter, &rand, -2, 0, 2, 100);
189 test_octagon(reporter, &rand, 0, -10, 100, 0);
190 // We can't test Infs or NaNs because they trigger internal asserts when setting GrOctoBounds.
191
192 // Sanity check on our random clip testing.. Just make we hit both types of clip.
193 REPORTER_ASSERT(reporter, numClipsOut > 0);
194 REPORTER_ASSERT(reporter, numIntersectClips > 0);
195}