blob: ee9a694f196dfbdc18e28cc0fe4f3989c18ad8b5 [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
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.
22class GrTextureProducer_TestAccess {
23public:
24 using DomainMode = GrTextureProducer::DomainMode;
25
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
44using DomainMode = GrTextureProducer_TestAccess::DomainMode;
45
46#ifdef SK_DEBUG
47static 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
53static 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
62class RectInfo {
63public:
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
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
82 const SkRect& rect() const { return fRect; }
83 EdgeType edgeType(Side side) const { return fTypes[side]; }
84 const char* name() const { return fName; }
85
86#ifdef SK_DEBUG
87 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
97 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
107 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
116private:
117#ifdef SK_DEBUG
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
132static 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;
Robert Phillips16d8ec62017-07-27 16:16:25 -0400140 desc.fOrigin = kTopLeft_GrSurfaceOrigin;
Robert Phillips51e7ca32017-03-27 10:14:08 -0400141 desc.fWidth = size;
142 desc.fHeight = size;
Robert Phillips16d8ec62017-07-27 16:16:25 -0400143 desc.fConfig = kRGBA_8888_GrPixelConfig;
Robert Phillips51e7ca32017-03-27 10:14:08 -0400144
145 static const char* name = "proxy";
146
147 // Proxies are always hard on the left and top but can be bad on the right and bottom
148 rect->set(SkRect::MakeWH(size, size),
149 RectInfo::kHard,
150 RectInfo::kHard,
151 (isPowerOfTwo || isExact) ? RectInfo::kHard : RectInfo::kBad,
152 (isPowerOfTwo || isExact) ? RectInfo::kHard : RectInfo::kBad,
153 name);
154
155 sk_sp<GrTextureProxy> proxy = GrSurfaceProxy::MakeDeferred(resourceProvider,
156 desc, fit,
157 SkBudgeted::kYes);
158 return proxy;
159}
160
161static RectInfo::EdgeType compute_inset_edgetype(RectInfo::EdgeType previous,
162 bool isInsetHard, bool coordsAreLimitedToRect,
163 float insetAmount, float halfFilterWidth) {
164 if (isInsetHard) {
165 if (coordsAreLimitedToRect) {
166 SkASSERT(halfFilterWidth >= 0.0f);
167 if (0.0f == halfFilterWidth) {
168 return RectInfo::kSoft;
169 }
170 }
171
172 if (0.0f == insetAmount && RectInfo::kHard == previous) {
173 return RectInfo::kHard;
174 }
175
176 return RectInfo::kBad;
177 }
178
179 if (RectInfo::kHard == previous) {
180 return RectInfo::kHard;
181 }
182
183 if (coordsAreLimitedToRect) {
184 SkASSERT(halfFilterWidth >= 0.0f);
185 if (0.0 == halfFilterWidth || insetAmount > halfFilterWidth) {
186 return RectInfo::kSoft;
187 }
188 }
189
190 return previous;
191}
192
193static const int kInsetLeft_Flag = 0x1;
194static const int kInsetTop_Flag = 0x2;
195static const int kInsetRight_Flag = 0x4;
196static const int kInsetBot_Flag = 0x8;
197
198// If 'isInsetHard' is true we can't sample across the inset boundary.
199// If 'areCoordsLimitedToRect' is true the client promises to never sample outside the inset.
200static const SkRect* generic_inset(const RectInfo& enclosing,
201 RectInfo* result,
202 bool isInsetHard,
203 bool areCoordsLimitedToRect,
204 float insetAmount,
205 float halfFilterWidth,
206 uint32_t flags,
207 const char* name) {
208 SkRect newR = enclosing.rect();
209
210 RectInfo::EdgeType left = enclosing.edgeType(RectInfo::kLeft);
211 if (flags & kInsetLeft_Flag) {
212 newR.fLeft += insetAmount;
213 left = compute_inset_edgetype(left, isInsetHard, areCoordsLimitedToRect,
214 insetAmount, halfFilterWidth);
215 } else {
216 left = compute_inset_edgetype(left, isInsetHard, areCoordsLimitedToRect,
217 0.0f, halfFilterWidth);
218 }
219
220 RectInfo::EdgeType top = enclosing.edgeType(RectInfo::kTop);
221 if (flags & kInsetTop_Flag) {
222 newR.fTop += insetAmount;
223 top = compute_inset_edgetype(top, isInsetHard, areCoordsLimitedToRect,
224 insetAmount, halfFilterWidth);
225 } else {
226 top = compute_inset_edgetype(top, isInsetHard, areCoordsLimitedToRect,
227 0.0f, halfFilterWidth);
228 }
229
230 RectInfo::EdgeType right = enclosing.edgeType(RectInfo::kRight);
231 if (flags & kInsetRight_Flag) {
232 newR.fRight -= insetAmount;
233 right = compute_inset_edgetype(right, isInsetHard, areCoordsLimitedToRect,
234 insetAmount, halfFilterWidth);
235 } else {
236 right = compute_inset_edgetype(right, isInsetHard, areCoordsLimitedToRect,
237 0.0f, halfFilterWidth);
238 }
239
240 RectInfo::EdgeType bot = enclosing.edgeType(RectInfo::kBot);
241 if (flags & kInsetBot_Flag) {
242 newR.fBottom -= insetAmount;
243 bot = compute_inset_edgetype(bot, isInsetHard, areCoordsLimitedToRect,
244 insetAmount, halfFilterWidth);
245 } else {
246 bot = compute_inset_edgetype(bot, isInsetHard, areCoordsLimitedToRect,
247 0.0f, halfFilterWidth);
248 }
249
250 result->set(newR, left, top, right, bot, name);
251 return &result->rect();
252}
253
254// Make a rect that only touches the enclosing rect on the left.
255static const SkRect* left_only(const RectInfo& enclosing,
256 RectInfo* result,
257 bool isInsetHard,
258 bool areCoordsLimitedToRect,
259 float insetAmount,
260 float halfFilterWidth) {
261 static const char* name = "left";
262 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
263 insetAmount, halfFilterWidth,
264 kInsetTop_Flag|kInsetRight_Flag|kInsetBot_Flag, name);
265}
266
267// Make a rect that only touches the enclosing rect on the top.
268static const SkRect* top_only(const RectInfo& enclosing,
269 RectInfo* result,
270 bool isInsetHard,
271 bool areCoordsLimitedToRect,
272 float insetAmount,
273 float halfFilterWidth) {
274 static const char* name = "top";
275 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
276 insetAmount, halfFilterWidth,
277 kInsetLeft_Flag|kInsetRight_Flag|kInsetBot_Flag, name);
278}
279
280// Make a rect that only touches the enclosing rect on the right.
281static const SkRect* right_only(const RectInfo& enclosing,
282 RectInfo* result,
283 bool isInsetHard,
284 bool areCoordsLimitedToRect,
285 float insetAmount,
286 float halfFilterWidth) {
287 static const char* name = "right";
288 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
289 insetAmount, halfFilterWidth,
290 kInsetLeft_Flag|kInsetTop_Flag|kInsetBot_Flag, name);
291}
292
293// Make a rect that only touches the enclosing rect on the bottom.
294static const SkRect* bot_only(const RectInfo& enclosing,
295 RectInfo* result,
296 bool isInsetHard,
297 bool areCoordsLimitedToRect,
298 float insetAmount,
299 float halfFilterWidth) {
300 static const char* name = "bot";
301 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
302 insetAmount, halfFilterWidth,
303 kInsetLeft_Flag|kInsetTop_Flag|kInsetRight_Flag, name);
304}
305
306// Make a rect that is inset all around.
307static const SkRect* full_inset(const RectInfo& enclosing,
308 RectInfo* result,
309 bool isInsetHard,
310 bool areCoordsLimitedToRect,
311 float insetAmount,
312 float halfFilterWidth) {
313 static const char* name = "all";
314 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
315 insetAmount, halfFilterWidth,
316 kInsetLeft_Flag|kInsetTop_Flag|kInsetRight_Flag|kInsetBot_Flag, name);
317}
318
319// This is only used for content rect creation. We ensure 'result' is correct but
320// return null to indicate no content area (other than what the proxy specifies).
321static const SkRect* null_rect(const RectInfo& enclosing,
322 RectInfo* result,
323 bool isInsetHard,
324 bool areCoordsLimitedToRect,
325 float insetAmount,
326 float halfFilterWidth) {
327 static const char* name = "null";
328 generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
329 insetAmount, halfFilterWidth, 0, name);
330 return nullptr;
331}
332
333// Make a rect with no inset. This is only used for constraint rect creation.
334static const SkRect* no_inset(const RectInfo& enclosing,
335 RectInfo* result,
336 bool isInsetHard,
337 bool areCoordsLimitedToRect,
338 float insetAmount,
339 float halfFilterWidth) {
340 static const char* name = "none";
341 return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
342 insetAmount, halfFilterWidth, 0, name);
343}
344
345static void proxy_test(skiatest::Reporter* reporter, GrResourceProvider* resourceProvider) {
346 GrTextureProducer_TestAccess::DomainMode actualMode, expectedMode;
347 SkRect actualDomainRect;
348
349 static const GrSamplerParams::FilterMode gModes[] = {
350 GrSamplerParams::kNone_FilterMode,
351 GrSamplerParams::kBilerp_FilterMode,
352 GrSamplerParams::kMipMap_FilterMode,
353 };
354
355 static const GrSamplerParams::FilterMode* gModePtrs[] = {
356 &gModes[0], &gModes[1], nullptr, &gModes[2]
357 };
358
359 static const float gHalfFilterWidth[] = { 0.0f, 0.5f, 1.5f, 10000.0f };
360
361 for (auto isPowerOfTwoSized : { true, false }) {
362 for (auto isExact : { true, false }) {
363 RectInfo outermost;
364
365 sk_sp<GrTextureProxy> proxy = create_proxy(resourceProvider, isPowerOfTwoSized,
366 isExact, &outermost);
367 SkASSERT(outermost.isHardOrBadAllAround());
368
369 for (auto contentRectMaker : { left_only, top_only, right_only,
370 bot_only, full_inset, null_rect}) {
371 RectInfo contentRectStorage;
372 const SkRect* contentRect = (*contentRectMaker)(outermost,
373 &contentRectStorage,
374 true, false, 5.0f, -1.0f);
375 if (contentRect) {
376 // We only have content rects if they actually reduce the extent of the content
377 SkASSERT(!contentRect->contains(outermost.rect()));
378 SkASSERT(outermost.rect().contains(*contentRect));
379 SkASSERT(is_irect(*contentRect));
380 }
381 SkASSERT(contentRectStorage.isHardOrBadAllAround());
382
383 for (auto isConstraintRectHard : { true, false }) {
384 for (auto areCoordsLimitedToConstraintRect : { true, false }) {
385 for (int filterMode = 0; filterMode < 4; ++filterMode) {
386 for (auto constraintRectMaker : { left_only, top_only, right_only,
387 bot_only, full_inset, no_inset }) {
388 for (auto insetAmt : { 0.25f, 0.75f, 1.25f, 1.75f, 5.0f }) {
389 RectInfo constraintRectStorage;
390 const SkRect* constraintRect = (*constraintRectMaker)(
391 contentRect ? contentRectStorage : outermost,
392 &constraintRectStorage,
393 isConstraintRectHard,
394 areCoordsLimitedToConstraintRect,
395 insetAmt,
396 gHalfFilterWidth[filterMode]);
397 SkASSERT(constraintRect); // always need one of these
398 if (contentRect) {
399 SkASSERT(contentRect->contains(*constraintRect));
400 } else {
401 SkASSERT(outermost.rect().contains(*constraintRect));
402 }
403
404 SkIRect contentIRect;
405 if (contentRect) {
406 contentIRect = to_irect(*contentRect);
407 }
408
409 actualMode = GrTextureProducer_TestAccess::DetermineDomainMode(
410 *constraintRect,
411 isConstraintRectHard
412 ? GrTextureProducer::kYes_FilterConstraint
413 : GrTextureProducer::kNo_FilterConstraint,
414 areCoordsLimitedToConstraintRect,
415 proxy.get(),
416 contentRect ? &contentIRect : nullptr,
417 gModePtrs[filterMode],
418 &actualDomainRect);
419
420 expectedMode = DomainMode::kNoDomain_DomainMode;
421 if (constraintRectStorage.hasABad()) {
422 if (3 == filterMode) {
423 expectedMode = DomainMode::kTightCopy_DomainMode;
424 } else {
425 expectedMode = DomainMode::kDomain_DomainMode;
426 }
427 }
428
429 REPORTER_ASSERT(reporter, expectedMode == actualMode);
430 // TODO: add a check that the returned domain rect is correct
431 }
432 }
433 }
434 }
435 }
436 }
437 }
438 }
439}
440
441DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DetermineDomainModeTest, reporter, ctxInfo) {
442 GrContext* context = ctxInfo.grContext();
443
444 proxy_test(reporter, context->resourceProvider());
445}
446
447#endif