blob: 111194f4ad5dd599f1bb91fa476723fc086fc4f6 [file] [log] [blame]
Robert Phillips51e7ca32017-03-27 10:14:08 -04001/*
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
Brian Salomon4df00922017-09-07 16:34:11 +000016// For DetermineDomainMode (in the MDB world) we have 4 rects:
Robert Phillips51e7ca32017-03-27 10:14:08 -040017// 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
Brian Salomon4df00922017-09-07 16:34:11 +000019// 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
Robert Phillips51e7ca32017-03-27 10:14:08 -040021// This test "fuzzes" all the combinations of these rects.
22class GrTextureProducer_TestAccess {
23public:
24 using DomainMode = GrTextureProducer::DomainMode;
25
Brian Salomon2bbdcc42017-09-07 12:36:34 -040026 static DomainMode DetermineDomainMode(const SkRect& constraintRect,
27 GrTextureProducer::FilterConstraint filterConstraint,
28 bool coordsLimitedToConstraintRect,
29 GrTextureProxy* proxy,
30 const SkIRect* textureContentArea,
31 const GrSamplerState::Filter* filterModeOrNullForBicubic,
32 SkRect* domainRect) {
Robert Phillips51e7ca32017-03-27 10:14:08 -040033 return GrTextureProducer::DetermineDomainMode(constraintRect,
34 filterConstraint,
35 coordsLimitedToConstraintRect,
36 proxy,
Brian Salomon4df00922017-09-07 16:34:11 +000037 textureContentArea,
Robert Phillips51e7ca32017-03-27 10:14:08 -040038 filterModeOrNullForBicubic,
39 domainRect);
40 }
41};
42
43using DomainMode = GrTextureProducer_TestAccess::DomainMode;
44
Brian Salomon4df00922017-09-07 16:34:11 +000045#ifdef SK_DEBUG
46static bool is_irect(const SkRect& r) {
47 return SkScalarIsInt(r.fLeft) && SkScalarIsInt(r.fTop) &&
48 SkScalarIsInt(r.fRight) && SkScalarIsInt(r.fBottom);
49}
50#endif
51
52static SkIRect to_irect(const SkRect& r) {
53 SkASSERT(is_irect(r));
54 return SkIRect::MakeLTRB(SkScalarRoundToInt(r.fLeft),
55 SkScalarRoundToInt(r.fTop),
56 SkScalarRoundToInt(r.fRight),
57 SkScalarRoundToInt(r.fBottom));
58}
59
60
Robert Phillips51e7ca32017-03-27 10:14:08 -040061class RectInfo {
62public:
63 enum Side { kLeft = 0, kTop = 1, kRight = 2, kBot = 3 };
64
65 enum EdgeType {
66 kSoft = 0, // there is data on the other side of this edge that we are allowed to sample
67 kHard = 1, // the backing resource ends at this edge
68 kBad = 2 // we can't sample across this edge
69 };
70
71 void set(const SkRect& rect, EdgeType left, EdgeType top, EdgeType right, EdgeType bot,
72 const char* name) {
73 fRect = rect;
74 fTypes[kLeft] = left;
75 fTypes[kTop] = top;
76 fTypes[kRight] = right;
77 fTypes[kBot] = bot;
78 fName = name;
79 }
80
81 const SkRect& rect() const { return fRect; }
82 EdgeType edgeType(Side side) const { return fTypes[side]; }
83 const char* name() const { return fName; }
84
85#ifdef SK_DEBUG
86 bool isHardOrBadAllAround() const {
87 for (int i = 0; i < 4; ++i) {
88 if (kHard != fTypes[i] && kBad != fTypes[i]) {
89 return false;
90 }
91 }
92 return true;
93 }
94#endif
95
96 bool hasABad() const {
97 for (int i = 0; i < 4; ++i) {
98 if (kBad == fTypes[i]) {
99 return true;
100 }
101 }
102 return false;
103 }
104
105#ifdef SK_DEBUG
106 void print(const char* label) const {
107 SkDebugf("%s: %s (%.1f, %.1f, %.1f, %.1f), L: %s T: %s R: %s B: %s\n",
108 label, fName,
109 fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom,
110 ToStr(fTypes[kLeft]), ToStr(fTypes[kTop]),
111 ToStr(fTypes[kRight]), ToStr(fTypes[kBot]));
112 }
113#endif
114
115private:
116#ifdef SK_DEBUG
117 static const char* ToStr(EdgeType type) {
118 static const char* names[] = { "soft", "hard", "bad" };
119 return names[type];
120 }
121#endif
122
123 RectInfo operator=(const RectInfo& other); // disallow
124
125 SkRect fRect;
126 EdgeType fTypes[4];
127 const char* fName;
128
129};
130
131static sk_sp<GrTextureProxy> create_proxy(GrResourceProvider* resourceProvider,
132 bool isPowerOfTwo,
133 bool isExact,
134 RectInfo* rect) {
135 int size = isPowerOfTwo ? 128 : 100;
136 SkBackingFit fit = isExact ? SkBackingFit::kExact : SkBackingFit::kApprox;
137
138 GrSurfaceDesc desc;
Robert Phillips16d8ec62017-07-27 16:16:25 -0400139 desc.fOrigin = kTopLeft_GrSurfaceOrigin;
Robert Phillips51e7ca32017-03-27 10:14:08 -0400140 desc.fWidth = size;
141 desc.fHeight = size;
Robert Phillips16d8ec62017-07-27 16:16:25 -0400142 desc.fConfig = kRGBA_8888_GrPixelConfig;
Robert Phillips51e7ca32017-03-27 10:14:08 -0400143
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
160static 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
192static const int kInsetLeft_Flag = 0x1;
193static const int kInsetTop_Flag = 0x2;
194static const int kInsetRight_Flag = 0x4;
195static 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.
199static 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.
254static 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.
267static 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.
280static 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.
293static 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.
306static 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
Brian Salomon4df00922017-09-07 16:34:11 +0000318// 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).
320static 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
Robert Phillips51e7ca32017-03-27 10:14:08 -0400332// Make a rect with no inset. This is only used for constraint rect creation.
333static 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
344static void proxy_test(skiatest::Reporter* reporter, GrResourceProvider* resourceProvider) {
345 GrTextureProducer_TestAccess::DomainMode actualMode, expectedMode;
346 SkRect actualDomainRect;
347
Brian Salomon2bbdcc42017-09-07 12:36:34 -0400348 static const GrSamplerState::Filter gModes[] = {
349 GrSamplerState::Filter::kNearest,
350 GrSamplerState::Filter::kBilerp,
351 GrSamplerState::Filter::kMipMap,
Robert Phillips51e7ca32017-03-27 10:14:08 -0400352 };
353
Brian Salomon2bbdcc42017-09-07 12:36:34 -0400354 static const GrSamplerState::Filter* gModePtrs[] = {&gModes[0], &gModes[1], nullptr,
355 &gModes[2]};
Robert Phillips51e7ca32017-03-27 10:14:08 -0400356
357 static const float gHalfFilterWidth[] = { 0.0f, 0.5f, 1.5f, 10000.0f };
358
359 for (auto isPowerOfTwoSized : { true, false }) {
360 for (auto isExact : { true, false }) {
361 RectInfo outermost;
362
363 sk_sp<GrTextureProxy> proxy = create_proxy(resourceProvider, isPowerOfTwoSized,
364 isExact, &outermost);
365 SkASSERT(outermost.isHardOrBadAllAround());
Brian Salomon4df00922017-09-07 16:34:11 +0000366
367 for (auto contentRectMaker : { left_only, top_only, right_only,
368 bot_only, full_inset, null_rect}) {
369 RectInfo contentRectStorage;
370 const SkRect* contentRect = (*contentRectMaker)(outermost,
371 &contentRectStorage,
372 true, false, 5.0f, -1.0f);
373 if (contentRect) {
374 // We only have content rects if they actually reduce the extent of the content
375 SkASSERT(!contentRect->contains(outermost.rect()));
376 SkASSERT(outermost.rect().contains(*contentRect));
377 SkASSERT(is_irect(*contentRect));
Robert Phillips51e7ca32017-03-27 10:14:08 -0400378 }
Brian Salomon4df00922017-09-07 16:34:11 +0000379 SkASSERT(contentRectStorage.isHardOrBadAllAround());
Robert Phillips51e7ca32017-03-27 10:14:08 -0400380
Brian Salomon4df00922017-09-07 16:34:11 +0000381 for (auto isConstraintRectHard : { true, false }) {
382 for (auto areCoordsLimitedToConstraintRect : { true, false }) {
383 for (int filterMode = 0; filterMode < 4; ++filterMode) {
384 for (auto constraintRectMaker : { left_only, top_only, right_only,
385 bot_only, full_inset, no_inset }) {
386 for (auto insetAmt : { 0.25f, 0.75f, 1.25f, 1.75f, 5.0f }) {
387 RectInfo constraintRectStorage;
388 const SkRect* constraintRect = (*constraintRectMaker)(
389 contentRect ? contentRectStorage : outermost,
390 &constraintRectStorage,
391 isConstraintRectHard,
392 areCoordsLimitedToConstraintRect,
393 insetAmt,
394 gHalfFilterWidth[filterMode]);
395 SkASSERT(constraintRect); // always need one of these
396 if (contentRect) {
397 SkASSERT(contentRect->contains(*constraintRect));
Robert Phillips51e7ca32017-03-27 10:14:08 -0400398 } else {
Brian Salomon4df00922017-09-07 16:34:11 +0000399 SkASSERT(outermost.rect().contains(*constraintRect));
Robert Phillips51e7ca32017-03-27 10:14:08 -0400400 }
Brian Salomon6e4bbbe2017-09-07 11:41:25 -0400401
Brian Salomon4df00922017-09-07 16:34:11 +0000402 SkIRect contentIRect;
403 if (contentRect) {
404 contentIRect = to_irect(*contentRect);
405 }
406
407 actualMode = GrTextureProducer_TestAccess::DetermineDomainMode(
408 *constraintRect,
409 isConstraintRectHard
410 ? GrTextureProducer::kYes_FilterConstraint
411 : GrTextureProducer::kNo_FilterConstraint,
412 areCoordsLimitedToConstraintRect,
413 proxy.get(),
414 contentRect ? &contentIRect : nullptr,
415 gModePtrs[filterMode],
416 &actualDomainRect);
417
418 expectedMode = DomainMode::kNoDomain_DomainMode;
419 if (constraintRectStorage.hasABad()) {
420 if (3 == filterMode) {
421 expectedMode = DomainMode::kTightCopy_DomainMode;
422 } else {
423 expectedMode = DomainMode::kDomain_DomainMode;
424 }
425 }
426
427 REPORTER_ASSERT(reporter, expectedMode == actualMode);
428 // TODO: add a check that the returned domain rect is correct
429 }
Robert Phillips51e7ca32017-03-27 10:14:08 -0400430 }
431 }
432 }
433 }
434 }
435 }
436 }
437}
438
439DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DetermineDomainModeTest, reporter, ctxInfo) {
440 GrContext* context = ctxInfo.grContext();
441
442 proxy_test(reporter, context->resourceProvider());
443}
444
445#endif