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