| /* | 
 |  * Copyright 2017 Google Inc. | 
 |  * | 
 |  * Use of this source code is governed by a BSD-style license that can be | 
 |  * found in the LICENSE file. | 
 |  */ | 
 |  | 
 | #include "SkTypes.h" | 
 |  | 
 | #include "GrContext.h" | 
 | #include "GrContextFactory.h" | 
 | #include "GrContextPriv.h" | 
 | #include "GrProxyProvider.h" | 
 | #include "GrSamplerState.h" | 
 | #include "GrTextureProducer.h" | 
 | #include "GrTextureProxy.h" | 
 | #include "GrTypes.h" | 
 | #include "GrTypesPriv.h" | 
 | #include "SkRect.h" | 
 | #include "SkRefCnt.h" | 
 | #include "Test.h" | 
 |  | 
 | #include <initializer_list> | 
 |  | 
 | // For DetermineDomainMode (in the MDB world) we have 3 rects: | 
 | //      1) the final instantiated backing storage (i.e., the actual GrTexture's extent) | 
 | //      2) the proxy's extent, which may or may not match the GrTexture's extent | 
 | //      3) the constraint rect, which can optionally be hard or soft | 
 | // This test "fuzzes" all the combinations of these rects. | 
 | class GrTextureProducer_TestAccess { | 
 | public: | 
 |     using DomainMode = GrTextureProducer::DomainMode; | 
 |  | 
 |     static DomainMode DetermineDomainMode(const SkRect& constraintRect, | 
 |                                           GrTextureProducer::FilterConstraint filterConstraint, | 
 |                                           bool coordsLimitedToConstraintRect, | 
 |                                           GrTextureProxy* proxy, | 
 |                                           const GrSamplerState::Filter* filterModeOrNullForBicubic, | 
 |                                           SkRect* domainRect) { | 
 |         return GrTextureProducer::DetermineDomainMode(constraintRect, | 
 |                                                       filterConstraint, | 
 |                                                       coordsLimitedToConstraintRect, | 
 |                                                       proxy, | 
 |                                                       filterModeOrNullForBicubic, | 
 |                                                       domainRect); | 
 |     } | 
 | }; | 
 |  | 
 | using DomainMode = GrTextureProducer_TestAccess::DomainMode; | 
 |  | 
 | class RectInfo { | 
 | public: | 
 |     enum Side { kLeft = 0, kTop = 1, kRight = 2, kBot = 3 }; | 
 |  | 
 |     enum EdgeType { | 
 |         kSoft = 0,   // there is data on the other side of this edge that we are allowed to sample | 
 |         kHard = 1,   // the backing resource ends at this edge | 
 |         kBad  = 2    // we can't sample across this edge | 
 |     }; | 
 |  | 
 |     void set(const SkRect& rect, EdgeType left, EdgeType top, EdgeType right, EdgeType bot, | 
 |              const char* name) { | 
 |         fRect = rect; | 
 |         fTypes[kLeft]  = left; | 
 |         fTypes[kTop]   = top; | 
 |         fTypes[kRight] = right; | 
 |         fTypes[kBot]   = bot; | 
 |         fName = name; | 
 |     } | 
 |  | 
 |     const SkRect& rect() const { return fRect; } | 
 |     EdgeType edgeType(Side side) const { return fTypes[side]; } | 
 |     const char* name() const { return fName; } | 
 |  | 
 | #ifdef SK_DEBUG | 
 |     bool isHardOrBadAllAround() const { | 
 |         for (int i = 0; i < 4; ++i) { | 
 |             if (kHard != fTypes[i] && kBad != fTypes[i]) { | 
 |                 return false; | 
 |             } | 
 |         } | 
 |         return true; | 
 |     } | 
 | #endif | 
 |  | 
 |     bool hasABad() const { | 
 |         for (int i = 0; i < 4; ++i) { | 
 |             if (kBad == fTypes[i]) { | 
 |                 return true; | 
 |             } | 
 |         } | 
 |         return false; | 
 |     } | 
 |  | 
 | #ifdef SK_DEBUG | 
 |     void print(const char* label) const { | 
 |         SkDebugf("%s: %s (%.1f, %.1f, %.1f, %.1f), L: %s T: %s R: %s B: %s\n", | 
 |                  label, fName, | 
 |                  fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom, | 
 |                  ToStr(fTypes[kLeft]), ToStr(fTypes[kTop]), | 
 |                  ToStr(fTypes[kRight]), ToStr(fTypes[kBot])); | 
 |     } | 
 | #endif | 
 |  | 
 | private: | 
 | #ifdef SK_DEBUG | 
 |     static const char* ToStr(EdgeType type) { | 
 |         static const char* names[] = { "soft", "hard", "bad" }; | 
 |         return names[type]; | 
 |     } | 
 | #endif | 
 |  | 
 |     RectInfo operator=(const RectInfo& other); // disallow | 
 |  | 
 |     SkRect      fRect; | 
 |     EdgeType    fTypes[4]; | 
 |     const char* fName; | 
 |  | 
 | }; | 
 |  | 
 | static sk_sp<GrTextureProxy> create_proxy(GrContext* ctx, | 
 |                                           bool isPowerOfTwo, | 
 |                                           bool isExact, | 
 |                                           RectInfo* rect) { | 
 |     GrProxyProvider* proxyProvider = ctx->contextPriv().proxyProvider(); | 
 |     int size = isPowerOfTwo ? 128 : 100; | 
 |     SkBackingFit fit = isExact ? SkBackingFit::kExact : SkBackingFit::kApprox; | 
 |  | 
 |     GrSurfaceDesc desc; | 
 |     desc.fWidth = size; | 
 |     desc.fHeight = size; | 
 |     desc.fConfig = kRGBA_8888_GrPixelConfig; | 
 |  | 
 |     GrBackendFormat format = | 
 |             ctx->contextPriv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType); | 
 |  | 
 |     static const char* name = "proxy"; | 
 |  | 
 |     // Proxies are always hard on the left and top but can be bad on the right and bottom | 
 |     rect->set(SkRect::MakeWH(size, size), | 
 |               RectInfo::kHard, | 
 |               RectInfo::kHard, | 
 |               (isPowerOfTwo || isExact) ? RectInfo::kHard : RectInfo::kBad, | 
 |               (isPowerOfTwo || isExact) ? RectInfo::kHard : RectInfo::kBad, | 
 |               name); | 
 |  | 
 |     return proxyProvider->createProxy(format, desc, kTopLeft_GrSurfaceOrigin, fit, | 
 |                                       SkBudgeted::kYes); | 
 | } | 
 |  | 
 | static RectInfo::EdgeType compute_inset_edgetype(RectInfo::EdgeType previous, | 
 |                                                  bool isInsetHard, bool coordsAreLimitedToRect, | 
 |                                                  float insetAmount, float halfFilterWidth) { | 
 |     if (isInsetHard) { | 
 |         if (coordsAreLimitedToRect) { | 
 |             SkASSERT(halfFilterWidth >= 0.0f); | 
 |             if (0.0f == halfFilterWidth) { | 
 |                 return RectInfo::kSoft; | 
 |             } | 
 |         } | 
 |  | 
 |         if (0.0f == insetAmount && RectInfo::kHard == previous) { | 
 |             return RectInfo::kHard; | 
 |         } | 
 |  | 
 |         return RectInfo::kBad; | 
 |     } | 
 |  | 
 |     if (RectInfo::kHard == previous) { | 
 |         return RectInfo::kHard; | 
 |     } | 
 |  | 
 |     if (coordsAreLimitedToRect) { | 
 |         SkASSERT(halfFilterWidth >= 0.0f); | 
 |         if (0.0 == halfFilterWidth || insetAmount > halfFilterWidth) { | 
 |             return RectInfo::kSoft; | 
 |         } | 
 |     } | 
 |  | 
 |     return previous; | 
 | } | 
 |  | 
 | static const int kInsetLeft_Flag  = 0x1; | 
 | static const int kInsetTop_Flag   = 0x2; | 
 | static const int kInsetRight_Flag = 0x4; | 
 | static const int kInsetBot_Flag   = 0x8; | 
 |  | 
 | // If 'isInsetHard' is true we can't sample across the inset boundary. | 
 | // If 'areCoordsLimitedToRect' is true the client promises to never sample outside the inset. | 
 | static const SkRect* generic_inset(const RectInfo& enclosing, | 
 |                                    RectInfo* result, | 
 |                                    bool isInsetHard, | 
 |                                    bool areCoordsLimitedToRect, | 
 |                                    float insetAmount, | 
 |                                    float halfFilterWidth, | 
 |                                    uint32_t flags, | 
 |                                    const char* name) { | 
 |     SkRect newR = enclosing.rect(); | 
 |  | 
 |     RectInfo::EdgeType left = enclosing.edgeType(RectInfo::kLeft); | 
 |     if (flags & kInsetLeft_Flag) { | 
 |         newR.fLeft += insetAmount; | 
 |         left = compute_inset_edgetype(left, isInsetHard, areCoordsLimitedToRect, | 
 |                                       insetAmount, halfFilterWidth); | 
 |     } else { | 
 |         left = compute_inset_edgetype(left, isInsetHard, areCoordsLimitedToRect, | 
 |                                       0.0f, halfFilterWidth); | 
 |     } | 
 |  | 
 |     RectInfo::EdgeType top = enclosing.edgeType(RectInfo::kTop); | 
 |     if (flags & kInsetTop_Flag) { | 
 |         newR.fTop += insetAmount; | 
 |         top = compute_inset_edgetype(top, isInsetHard, areCoordsLimitedToRect, | 
 |                                      insetAmount, halfFilterWidth); | 
 |     } else { | 
 |         top = compute_inset_edgetype(top, isInsetHard, areCoordsLimitedToRect, | 
 |                                      0.0f, halfFilterWidth); | 
 |     } | 
 |  | 
 |     RectInfo::EdgeType right = enclosing.edgeType(RectInfo::kRight); | 
 |     if (flags & kInsetRight_Flag) { | 
 |         newR.fRight -= insetAmount; | 
 |         right = compute_inset_edgetype(right, isInsetHard, areCoordsLimitedToRect, | 
 |                                        insetAmount, halfFilterWidth); | 
 |     } else { | 
 |         right = compute_inset_edgetype(right, isInsetHard, areCoordsLimitedToRect, | 
 |                                        0.0f, halfFilterWidth); | 
 |     } | 
 |  | 
 |     RectInfo::EdgeType bot = enclosing.edgeType(RectInfo::kBot); | 
 |     if (flags & kInsetBot_Flag) { | 
 |         newR.fBottom -= insetAmount; | 
 |         bot = compute_inset_edgetype(bot, isInsetHard, areCoordsLimitedToRect, | 
 |                                      insetAmount, halfFilterWidth); | 
 |     } else { | 
 |         bot = compute_inset_edgetype(bot, isInsetHard, areCoordsLimitedToRect, | 
 |                                      0.0f, halfFilterWidth); | 
 |     } | 
 |  | 
 |     result->set(newR, left, top, right, bot, name); | 
 |     return &result->rect(); | 
 | } | 
 |  | 
 | // Make a rect that only touches the enclosing rect on the left. | 
 | static const SkRect* left_only(const RectInfo& enclosing, | 
 |                                RectInfo* result, | 
 |                                bool isInsetHard, | 
 |                                bool areCoordsLimitedToRect, | 
 |                                float insetAmount, | 
 |                                float halfFilterWidth) { | 
 |     static const char* name = "left"; | 
 |     return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect, | 
 |                          insetAmount, halfFilterWidth, | 
 |                          kInsetTop_Flag|kInsetRight_Flag|kInsetBot_Flag, name); | 
 | } | 
 |  | 
 | // Make a rect that only touches the enclosing rect on the top. | 
 | static const SkRect* top_only(const RectInfo& enclosing, | 
 |                                RectInfo* result, | 
 |                                bool isInsetHard, | 
 |                                bool areCoordsLimitedToRect, | 
 |                                float insetAmount, | 
 |                                float halfFilterWidth) { | 
 |     static const char* name = "top"; | 
 |     return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect, | 
 |                          insetAmount, halfFilterWidth, | 
 |                          kInsetLeft_Flag|kInsetRight_Flag|kInsetBot_Flag, name); | 
 | } | 
 |  | 
 | // Make a rect that only touches the enclosing rect on the right. | 
 | static const SkRect* right_only(const RectInfo& enclosing, | 
 |                                 RectInfo* result, | 
 |                                 bool isInsetHard, | 
 |                                 bool areCoordsLimitedToRect, | 
 |                                 float insetAmount, | 
 |                                 float halfFilterWidth) { | 
 |     static const char* name = "right"; | 
 |     return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect, | 
 |                          insetAmount, halfFilterWidth, | 
 |                          kInsetLeft_Flag|kInsetTop_Flag|kInsetBot_Flag, name); | 
 | } | 
 |  | 
 | // Make a rect that only touches the enclosing rect on the bottom. | 
 | static const SkRect* bot_only(const RectInfo& enclosing, | 
 |                               RectInfo* result, | 
 |                               bool isInsetHard, | 
 |                               bool areCoordsLimitedToRect, | 
 |                               float insetAmount, | 
 |                               float halfFilterWidth) { | 
 |     static const char* name = "bot"; | 
 |     return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect, | 
 |                          insetAmount, halfFilterWidth, | 
 |                          kInsetLeft_Flag|kInsetTop_Flag|kInsetRight_Flag, name); | 
 | } | 
 |  | 
 | // Make a rect that is inset all around. | 
 | static const SkRect* full_inset(const RectInfo& enclosing, | 
 |                                 RectInfo* result, | 
 |                                 bool isInsetHard, | 
 |                                 bool areCoordsLimitedToRect, | 
 |                                 float insetAmount, | 
 |                                 float halfFilterWidth) { | 
 |     static const char* name = "all"; | 
 |     return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect, | 
 |                          insetAmount, halfFilterWidth, | 
 |                          kInsetLeft_Flag|kInsetTop_Flag|kInsetRight_Flag|kInsetBot_Flag, name); | 
 | } | 
 |  | 
 | // Make a rect with no inset. This is only used for constraint rect creation. | 
 | static const SkRect* no_inset(const RectInfo& enclosing, | 
 |                               RectInfo* result, | 
 |                               bool isInsetHard, | 
 |                               bool areCoordsLimitedToRect, | 
 |                               float insetAmount, | 
 |                               float halfFilterWidth) { | 
 |     static const char* name = "none"; | 
 |     return generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect, | 
 |                          insetAmount, halfFilterWidth, 0, name); | 
 | } | 
 |  | 
 | static void proxy_test(skiatest::Reporter* reporter, GrContext* context) { | 
 |     GrTextureProducer_TestAccess::DomainMode actualMode, expectedMode; | 
 |     SkRect actualDomainRect; | 
 |  | 
 |     static const GrSamplerState::Filter gModes[] = { | 
 |             GrSamplerState::Filter::kNearest, | 
 |             GrSamplerState::Filter::kBilerp, | 
 |             GrSamplerState::Filter::kMipMap, | 
 |     }; | 
 |  | 
 |     static const GrSamplerState::Filter* gModePtrs[] = {&gModes[0], &gModes[1], nullptr, | 
 |                                                         &gModes[2]}; | 
 |  | 
 |     static const float gHalfFilterWidth[] = { 0.0f, 0.5f, 1.5f, 10000.0f }; | 
 |  | 
 |     for (auto isPowerOfTwoSized : { true, false }) { | 
 |         for (auto isExact : { true, false }) { | 
 |             RectInfo outermost; | 
 |  | 
 |             sk_sp<GrTextureProxy> proxy = create_proxy(context, isPowerOfTwoSized, | 
 |                                                        isExact, &outermost); | 
 |             SkASSERT(outermost.isHardOrBadAllAround()); | 
 |  | 
 |             for (auto isConstraintRectHard : { true, false }) { | 
 |                 for (auto areCoordsLimitedToConstraintRect : { true, false }) { | 
 |                     for (int filterMode = 0; filterMode < 4; ++filterMode) { | 
 |                         for (auto constraintRectMaker : { left_only, top_only, right_only, | 
 |                             bot_only, full_inset, no_inset }) { | 
 |                             for (auto insetAmt : { 0.25f, 0.75f, 1.25f, 1.75f, 5.0f }) { | 
 |                                 RectInfo constraintRectStorage; | 
 |                                 const SkRect* constraintRect = (*constraintRectMaker)( | 
 |                                         outermost, | 
 |                                         &constraintRectStorage, | 
 |                                         isConstraintRectHard, | 
 |                                         areCoordsLimitedToConstraintRect, | 
 |                                         insetAmt, | 
 |                                         gHalfFilterWidth[filterMode]); | 
 |                                 SkASSERT(constraintRect); // always need one of these | 
 |                                 SkASSERT(outermost.rect().contains(*constraintRect)); | 
 |  | 
 |                                 actualMode = GrTextureProducer_TestAccess::DetermineDomainMode( | 
 |                                         *constraintRect, | 
 |                                         isConstraintRectHard | 
 |                                             ? GrTextureProducer::kYes_FilterConstraint | 
 |                                             : GrTextureProducer::kNo_FilterConstraint, | 
 |                                         areCoordsLimitedToConstraintRect, | 
 |                                         proxy.get(), | 
 |                                         gModePtrs[filterMode], | 
 |                                         &actualDomainRect); | 
 |  | 
 |                                 expectedMode = DomainMode::kNoDomain_DomainMode; | 
 |                                 if (constraintRectStorage.hasABad()) { | 
 |                                     if (3 == filterMode) { | 
 |                                         expectedMode = DomainMode::kTightCopy_DomainMode; | 
 |                                     } else { | 
 |                                         expectedMode = DomainMode::kDomain_DomainMode; | 
 |                                     } | 
 |                                 } | 
 |  | 
 |                                 REPORTER_ASSERT(reporter, expectedMode == actualMode); | 
 |                                 // TODO: add a check that the returned domain rect is correct | 
 |                             } | 
 |                         } | 
 |                     } | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DetermineDomainModeTest, reporter, ctxInfo) { | 
 |     GrContext* context = ctxInfo.grContext(); | 
 |  | 
 |     proxy_test(reporter, context); | 
 | } |