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