Home | History | Annotate | Download | only in tests
      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 "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.
     22 class GrTextureProducer_TestAccess {
     23 public:
     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 
     44 using DomainMode = GrTextureProducer_TestAccess::DomainMode;
     45 
     46 #ifdef SK_DEBUG
     47 static 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 
     53 static 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 
     62 class RectInfo {
     63 public:
     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 
    116 private:
    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 
    132 static 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;
    140     desc.fConfig = kRGBA_8888_GrPixelConfig;
    141     desc.fWidth = size;
    142     desc.fHeight = size;
    143 
    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 
    160 static 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 
    192 static const int kInsetLeft_Flag  = 0x1;
    193 static const int kInsetTop_Flag   = 0x2;
    194 static const int kInsetRight_Flag = 0x4;
    195 static 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.
    199 static 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.
    254 static 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.
    267 static 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.
    280 static 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.
    293 static 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.
    306 static 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 
    318 // 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).
    320 static 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 
    332 // Make a rect with no inset. This is only used for constraint rect creation.
    333 static 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 
    344 static void proxy_test(skiatest::Reporter* reporter, GrResourceProvider* resourceProvider) {
    345     GrTextureProducer_TestAccess::DomainMode actualMode, expectedMode;
    346     SkRect actualDomainRect;
    347 
    348     static const GrSamplerParams::FilterMode gModes[] = {
    349         GrSamplerParams::kNone_FilterMode,
    350         GrSamplerParams::kBilerp_FilterMode,
    351         GrSamplerParams::kMipMap_FilterMode,
    352     };
    353 
    354     static const GrSamplerParams::FilterMode* gModePtrs[] = {
    355         &gModes[0], &gModes[1], nullptr, &gModes[2]
    356     };
    357 
    358     static const float gHalfFilterWidth[] = { 0.0f, 0.5f, 1.5f, 10000.0f };
    359 
    360     for (auto isPowerOfTwoSized : { true, false }) {
    361         for (auto isExact : { true, false }) {
    362             RectInfo outermost;
    363 
    364             sk_sp<GrTextureProxy> proxy = create_proxy(resourceProvider, isPowerOfTwoSized,
    365                                                        isExact, &outermost);
    366             SkASSERT(outermost.isHardOrBadAllAround());
    367 
    368             for (auto contentRectMaker : { left_only, top_only, right_only,
    369                                            bot_only, full_inset, null_rect}) {
    370                 RectInfo contentRectStorage;
    371                 const SkRect* contentRect = (*contentRectMaker)(outermost,
    372                                                                 &contentRectStorage,
    373                                                                 true, false, 5.0f, -1.0f);
    374                 if (contentRect) {
    375                     // We only have content rects if they actually reduce the extent of the content
    376                     SkASSERT(!contentRect->contains(outermost.rect()));
    377                     SkASSERT(outermost.rect().contains(*contentRect));
    378                     SkASSERT(is_irect(*contentRect));
    379                 }
    380                 SkASSERT(contentRectStorage.isHardOrBadAllAround());
    381 
    382                 for (auto isConstraintRectHard : { true, false }) {
    383                     for (auto areCoordsLimitedToConstraintRect : { true, false }) {
    384                         for (int filterMode = 0; filterMode < 4; ++filterMode) {
    385                             for (auto constraintRectMaker : { left_only, top_only, right_only,
    386                                                               bot_only, full_inset, no_inset }) {
    387                                 for (auto insetAmt : { 0.25f, 0.75f, 1.25f, 1.75f, 5.0f }) {
    388                                     RectInfo constraintRectStorage;
    389                                     const SkRect* constraintRect = (*constraintRectMaker)(
    390                                                     contentRect ? contentRectStorage : outermost,
    391                                                     &constraintRectStorage,
    392                                                     isConstraintRectHard,
    393                                                     areCoordsLimitedToConstraintRect,
    394                                                     insetAmt,
    395                                                     gHalfFilterWidth[filterMode]);
    396                                     SkASSERT(constraintRect); // always need one of these
    397                                     if (contentRect) {
    398                                         SkASSERT(contentRect->contains(*constraintRect));
    399                                     } else {
    400                                         SkASSERT(outermost.rect().contains(*constraintRect));
    401                                     }
    402 
    403                                     SkIRect contentIRect;
    404                                     if (contentRect) {
    405                                         contentIRect = to_irect(*contentRect);
    406                                     }
    407 
    408                                     actualMode = GrTextureProducer_TestAccess::DetermineDomainMode(
    409                                                     *constraintRect,
    410                                                     isConstraintRectHard
    411                                                         ? GrTextureProducer::kYes_FilterConstraint
    412                                                         : GrTextureProducer::kNo_FilterConstraint,
    413                                                     areCoordsLimitedToConstraintRect,
    414                                                     proxy.get(),
    415                                                     contentRect ? &contentIRect : nullptr,
    416                                                     gModePtrs[filterMode],
    417                                                     &actualDomainRect);
    418 
    419                                     expectedMode = DomainMode::kNoDomain_DomainMode;
    420                                     if (constraintRectStorage.hasABad()) {
    421                                         if (3 == filterMode) {
    422                                             expectedMode = DomainMode::kTightCopy_DomainMode;
    423                                         } else {
    424                                             expectedMode = DomainMode::kDomain_DomainMode;
    425                                         }
    426                                     }
    427 
    428                                     REPORTER_ASSERT(reporter, expectedMode == actualMode);
    429                                     // TODO: add a check that the returned domain rect is correct
    430                                 }
    431                             }
    432                         }
    433                     }
    434                 }
    435             }
    436         }
    437     }
    438 }
    439 
    440 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DetermineDomainModeTest, reporter, ctxInfo) {
    441     GrContext* context = ctxInfo.grContext();
    442 
    443     proxy_test(reporter, context->resourceProvider());
    444 }
    445 
    446 #endif
    447