1 /*
2 * Copyright 2017 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 "Test.h"
9
10 #if SK_SUPPORT_GPU
11
12 #include "GrContextPriv.h"
13 #include "GrProxyProvider.h"
14 #include "GrSurfaceProxy.h"
15 #include "GrTextureProducer.h"
16 #include "GrTextureProxy.h"
17
18 // For DetermineDomainMode (in the MDB world) we have 3 rects:
19 // 1) the final instantiated backing storage (i.e., the actual GrTexture's extent)
20 // 2) the proxy's extent, which may or may not match the GrTexture's extent
21 // 3) the constraint rect, which can optionally be hard or soft
22 // This test "fuzzes" all the combinations of these rects.
23 class GrTextureProducer_TestAccess {
24 public:
25 using DomainMode = GrTextureProducer::DomainMode;
26
DetermineDomainMode(const SkRect & constraintRect,GrTextureProducer::FilterConstraint filterConstraint,bool coordsLimitedToConstraintRect,GrTextureProxy * proxy,const GrSamplerState::Filter * filterModeOrNullForBicubic,SkRect * domainRect)27 static DomainMode DetermineDomainMode(const SkRect& constraintRect,
28 GrTextureProducer::FilterConstraint filterConstraint,
29 bool coordsLimitedToConstraintRect,
30 GrTextureProxy* proxy,
31 const GrSamplerState::Filter* filterModeOrNullForBicubic,
32 SkRect* domainRect) {
33 return GrTextureProducer::DetermineDomainMode(constraintRect,
34 filterConstraint,
35 coordsLimitedToConstraintRect,
36 proxy,
37 filterModeOrNullForBicubic,
38 domainRect);
39 }
40 };
41
42 using DomainMode = GrTextureProducer_TestAccess::DomainMode;
43
44 class RectInfo {
45 public:
46 enum Side { kLeft = 0, kTop = 1, kRight = 2, kBot = 3 };
47
48 enum EdgeType {
49 kSoft = 0, // there is data on the other side of this edge that we are allowed to sample
50 kHard = 1, // the backing resource ends at this edge
51 kBad = 2 // we can't sample across this edge
52 };
53
set(const SkRect & rect,EdgeType left,EdgeType top,EdgeType right,EdgeType bot,const char * name)54 void set(const SkRect& rect, EdgeType left, EdgeType top, EdgeType right, EdgeType bot,
55 const char* name) {
56 fRect = rect;
57 fTypes[kLeft] = left;
58 fTypes[kTop] = top;
59 fTypes[kRight] = right;
60 fTypes[kBot] = bot;
61 fName = name;
62 }
63
rect() const64 const SkRect& rect() const { return fRect; }
edgeType(Side side) const65 EdgeType edgeType(Side side) const { return fTypes[side]; }
name() const66 const char* name() const { return fName; }
67
68 #ifdef SK_DEBUG
isHardOrBadAllAround() const69 bool isHardOrBadAllAround() const {
70 for (int i = 0; i < 4; ++i) {
71 if (kHard != fTypes[i] && kBad != fTypes[i]) {
72 return false;
73 }
74 }
75 return true;
76 }
77 #endif
78
hasABad() const79 bool hasABad() const {
80 for (int i = 0; i < 4; ++i) {
81 if (kBad == fTypes[i]) {
82 return true;
83 }
84 }
85 return false;
86 }
87
88 #ifdef SK_DEBUG
print(const char * label) const89 void print(const char* label) const {
90 SkDebugf("%s: %s (%.1f, %.1f, %.1f, %.1f), L: %s T: %s R: %s B: %s\n",
91 label, fName,
92 fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom,
93 ToStr(fTypes[kLeft]), ToStr(fTypes[kTop]),
94 ToStr(fTypes[kRight]), ToStr(fTypes[kBot]));
95 }
96 #endif
97
98 private:
99 #ifdef SK_DEBUG
ToStr(EdgeType type)100 static const char* ToStr(EdgeType type) {
101 static const char* names[] = { "soft", "hard", "bad" };
102 return names[type];
103 }
104 #endif
105
106 RectInfo operator=(const RectInfo& other); // disallow
107
108 SkRect fRect;
109 EdgeType fTypes[4];
110 const char* fName;
111
112 };
113
create_proxy(GrProxyProvider * proxyProvider,bool isPowerOfTwo,bool isExact,RectInfo * rect)114 static sk_sp<GrTextureProxy> create_proxy(GrProxyProvider* proxyProvider,
115 bool isPowerOfTwo,
116 bool isExact,
117 RectInfo* rect) {
118 int size = isPowerOfTwo ? 128 : 100;
119 SkBackingFit fit = isExact ? SkBackingFit::kExact : SkBackingFit::kApprox;
120
121 GrSurfaceDesc desc;
122 desc.fOrigin = kTopLeft_GrSurfaceOrigin;
123 desc.fWidth = size;
124 desc.fHeight = size;
125 desc.fConfig = kRGBA_8888_GrPixelConfig;
126
127 static const char* name = "proxy";
128
129 // Proxies are always hard on the left and top but can be bad on the right and bottom
130 rect->set(SkRect::MakeWH(size, size),
131 RectInfo::kHard,
132 RectInfo::kHard,
133 (isPowerOfTwo || isExact) ? RectInfo::kHard : RectInfo::kBad,
134 (isPowerOfTwo || isExact) ? RectInfo::kHard : RectInfo::kBad,
135 name);
136
137 return proxyProvider->createProxy(desc, fit, SkBudgeted::kYes);
138 }
139
compute_inset_edgetype(RectInfo::EdgeType previous,bool isInsetHard,bool coordsAreLimitedToRect,float insetAmount,float halfFilterWidth)140 static RectInfo::EdgeType compute_inset_edgetype(RectInfo::EdgeType previous,
141 bool isInsetHard, bool coordsAreLimitedToRect,
142 float insetAmount, float halfFilterWidth) {
143 if (isInsetHard) {
144 if (coordsAreLimitedToRect) {
145 SkASSERT(halfFilterWidth >= 0.0f);
146 if (0.0f == halfFilterWidth) {
147 return RectInfo::kSoft;
148 }
149 }
150
151 if (0.0f == insetAmount && RectInfo::kHard == previous) {
152 return RectInfo::kHard;
153 }
154
155 return RectInfo::kBad;
156 }
157
158 if (RectInfo::kHard == previous) {
159 return RectInfo::kHard;
160 }
161
162 if (coordsAreLimitedToRect) {
163 SkASSERT(halfFilterWidth >= 0.0f);
164 if (0.0 == halfFilterWidth || insetAmount > halfFilterWidth) {
165 return RectInfo::kSoft;
166 }
167 }
168
169 return previous;
170 }
171
172 static const int kInsetLeft_Flag = 0x1;
173 static const int kInsetTop_Flag = 0x2;
174 static const int kInsetRight_Flag = 0x4;
175 static const int kInsetBot_Flag = 0x8;
176
177 // If 'isInsetHard' is true we can't sample across the inset boundary.
178 // If 'areCoordsLimitedToRect' is true the client promises to never sample outside the inset.
generic_inset(const RectInfo & enclosing,RectInfo * result,bool isInsetHard,bool areCoordsLimitedToRect,float insetAmount,float halfFilterWidth,uint32_t flags,const char * name)179 static const SkRect* generic_inset(const RectInfo& enclosing,
180 RectInfo* result,
181 bool isInsetHard,
182 bool areCoordsLimitedToRect,
183 float insetAmount,
184 float halfFilterWidth,
185 uint32_t flags,
186 const char* name) {
187 SkRect newR = enclosing.rect();
188
189 RectInfo::EdgeType left = enclosing.edgeType(RectInfo::kLeft);
190 if (flags & kInsetLeft_Flag) {
191 newR.fLeft += insetAmount;
192 left = compute_inset_edgetype(left, isInsetHard, areCoordsLimitedToRect,
193 insetAmount, halfFilterWidth);
194 } else {
195 left = compute_inset_edgetype(left, isInsetHard, areCoordsLimitedToRect,
196 0.0f, halfFilterWidth);
197 }
198
199 RectInfo::EdgeType top = enclosing.edgeType(RectInfo::kTop);
200 if (flags & kInsetTop_Flag) {
201 newR.fTop += insetAmount;
202 top = compute_inset_edgetype(top, isInsetHard, areCoordsLimitedToRect,
203 insetAmount, halfFilterWidth);
204 } else {
205 top = compute_inset_edgetype(top, isInsetHard, areCoordsLimitedToRect,
206 0.0f, halfFilterWidth);
207 }
208
209 RectInfo::EdgeType right = enclosing.edgeType(RectInfo::kRight);
210 if (flags & kInsetRight_Flag) {
211 newR.fRight -= insetAmount;
212 right = compute_inset_edgetype(right, isInsetHard, areCoordsLimitedToRect,
213 insetAmount, halfFilterWidth);
214 } else {
215 right = compute_inset_edgetype(right, isInsetHard, areCoordsLimitedToRect,
216 0.0f, halfFilterWidth);
217 }
218
219 RectInfo::EdgeType bot = enclosing.edgeType(RectInfo::kBot);
220 if (flags & kInsetBot_Flag) {
221 newR.fBottom -= insetAmount;
222 bot = compute_inset_edgetype(bot, isInsetHard, areCoordsLimitedToRect,
223 insetAmount, halfFilterWidth);
224 } else {
225 bot = compute_inset_edgetype(bot, isInsetHard, areCoordsLimitedToRect,
226 0.0f, halfFilterWidth);
227 }
228
229 result->set(newR, left, top, right, bot, name);
230 return &result->rect();
231 }
232
233 // Make a rect that only touches the enclosing rect on the left.
left_only(const RectInfo & enclosing,RectInfo * result,bool isInsetHard,bool areCoordsLimitedToRect,float insetAmount,float halfFilterWidth)234 static const SkRect* left_only(const RectInfo& enclosing,
235 RectInfo* result,
236 bool isInsetHard,
237 bool areCoordsLimitedToRect,
238 float insetAmount,
239 float halfFilterWidth) {
240 static const char* name = "left";
241 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
242 insetAmount, halfFilterWidth,
243 kInsetTop_Flag|kInsetRight_Flag|kInsetBot_Flag, name);
244 }
245
246 // Make a rect that only touches the enclosing rect on the top.
top_only(const RectInfo & enclosing,RectInfo * result,bool isInsetHard,bool areCoordsLimitedToRect,float insetAmount,float halfFilterWidth)247 static const SkRect* top_only(const RectInfo& enclosing,
248 RectInfo* result,
249 bool isInsetHard,
250 bool areCoordsLimitedToRect,
251 float insetAmount,
252 float halfFilterWidth) {
253 static const char* name = "top";
254 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
255 insetAmount, halfFilterWidth,
256 kInsetLeft_Flag|kInsetRight_Flag|kInsetBot_Flag, name);
257 }
258
259 // Make a rect that only touches the enclosing rect on the right.
right_only(const RectInfo & enclosing,RectInfo * result,bool isInsetHard,bool areCoordsLimitedToRect,float insetAmount,float halfFilterWidth)260 static const SkRect* right_only(const RectInfo& enclosing,
261 RectInfo* result,
262 bool isInsetHard,
263 bool areCoordsLimitedToRect,
264 float insetAmount,
265 float halfFilterWidth) {
266 static const char* name = "right";
267 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
268 insetAmount, halfFilterWidth,
269 kInsetLeft_Flag|kInsetTop_Flag|kInsetBot_Flag, name);
270 }
271
272 // Make a rect that only touches the enclosing rect on the bottom.
bot_only(const RectInfo & enclosing,RectInfo * result,bool isInsetHard,bool areCoordsLimitedToRect,float insetAmount,float halfFilterWidth)273 static const SkRect* bot_only(const RectInfo& enclosing,
274 RectInfo* result,
275 bool isInsetHard,
276 bool areCoordsLimitedToRect,
277 float insetAmount,
278 float halfFilterWidth) {
279 static const char* name = "bot";
280 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
281 insetAmount, halfFilterWidth,
282 kInsetLeft_Flag|kInsetTop_Flag|kInsetRight_Flag, name);
283 }
284
285 // Make a rect that is inset all around.
full_inset(const RectInfo & enclosing,RectInfo * result,bool isInsetHard,bool areCoordsLimitedToRect,float insetAmount,float halfFilterWidth)286 static const SkRect* full_inset(const RectInfo& enclosing,
287 RectInfo* result,
288 bool isInsetHard,
289 bool areCoordsLimitedToRect,
290 float insetAmount,
291 float halfFilterWidth) {
292 static const char* name = "all";
293 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
294 insetAmount, halfFilterWidth,
295 kInsetLeft_Flag|kInsetTop_Flag|kInsetRight_Flag|kInsetBot_Flag, name);
296 }
297
298 // Make a rect with no inset. This is only used for constraint rect creation.
no_inset(const RectInfo & enclosing,RectInfo * result,bool isInsetHard,bool areCoordsLimitedToRect,float insetAmount,float halfFilterWidth)299 static const SkRect* no_inset(const RectInfo& enclosing,
300 RectInfo* result,
301 bool isInsetHard,
302 bool areCoordsLimitedToRect,
303 float insetAmount,
304 float halfFilterWidth) {
305 static const char* name = "none";
306 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
307 insetAmount, halfFilterWidth, 0, name);
308 }
309
proxy_test(skiatest::Reporter * reporter,GrProxyProvider * proxyProvider)310 static void proxy_test(skiatest::Reporter* reporter, GrProxyProvider* proxyProvider) {
311 GrTextureProducer_TestAccess::DomainMode actualMode, expectedMode;
312 SkRect actualDomainRect;
313
314 static const GrSamplerState::Filter gModes[] = {
315 GrSamplerState::Filter::kNearest,
316 GrSamplerState::Filter::kBilerp,
317 GrSamplerState::Filter::kMipMap,
318 };
319
320 static const GrSamplerState::Filter* gModePtrs[] = {&gModes[0], &gModes[1], nullptr,
321 &gModes[2]};
322
323 static const float gHalfFilterWidth[] = { 0.0f, 0.5f, 1.5f, 10000.0f };
324
325 for (auto isPowerOfTwoSized : { true, false }) {
326 for (auto isExact : { true, false }) {
327 RectInfo outermost;
328
329 sk_sp<GrTextureProxy> proxy = create_proxy(proxyProvider, isPowerOfTwoSized,
330 isExact, &outermost);
331 SkASSERT(outermost.isHardOrBadAllAround());
332
333 for (auto isConstraintRectHard : { true, false }) {
334 for (auto areCoordsLimitedToConstraintRect : { true, false }) {
335 for (int filterMode = 0; filterMode < 4; ++filterMode) {
336 for (auto constraintRectMaker : { left_only, top_only, right_only,
337 bot_only, full_inset, no_inset }) {
338 for (auto insetAmt : { 0.25f, 0.75f, 1.25f, 1.75f, 5.0f }) {
339 RectInfo constraintRectStorage;
340 const SkRect* constraintRect = (*constraintRectMaker)(
341 outermost,
342 &constraintRectStorage,
343 isConstraintRectHard,
344 areCoordsLimitedToConstraintRect,
345 insetAmt,
346 gHalfFilterWidth[filterMode]);
347 SkASSERT(constraintRect); // always need one of these
348 SkASSERT(outermost.rect().contains(*constraintRect));
349
350 actualMode = GrTextureProducer_TestAccess::DetermineDomainMode(
351 *constraintRect,
352 isConstraintRectHard
353 ? GrTextureProducer::kYes_FilterConstraint
354 : GrTextureProducer::kNo_FilterConstraint,
355 areCoordsLimitedToConstraintRect,
356 proxy.get(),
357 gModePtrs[filterMode],
358 &actualDomainRect);
359
360 expectedMode = DomainMode::kNoDomain_DomainMode;
361 if (constraintRectStorage.hasABad()) {
362 if (3 == filterMode) {
363 expectedMode = DomainMode::kTightCopy_DomainMode;
364 } else {
365 expectedMode = DomainMode::kDomain_DomainMode;
366 }
367 }
368
369 REPORTER_ASSERT(reporter, expectedMode == actualMode);
370 // TODO: add a check that the returned domain rect is correct
371 }
372 }
373 }
374 }
375 }
376 }
377 }
378 }
379
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DetermineDomainModeTest,reporter,ctxInfo)380 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DetermineDomainModeTest, reporter, ctxInfo) {
381 GrContext* context = ctxInfo.grContext();
382
383 proxy_test(reporter, context->contextPriv().proxyProvider());
384 }
385
386 #endif
387