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