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