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