| /* | |
| * 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->priv().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->priv().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); | |
| } |