Add new proxy-based DetermineDomainMode w/ test
split out of: https://skia-review.googlesource.com/c/8823/ (Remove GrFragmentProcessor-derived class' GrTexture-based ctors)
Change-Id: Ibf54da589710382254b416e4801d83e9f5663f44
Reviewed-on: https://skia-review.googlesource.com/10180
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/tests/DetermineDomainModeTest.cpp b/tests/DetermineDomainModeTest.cpp
new file mode 100644
index 0000000..add746f
--- /dev/null
+++ b/tests/DetermineDomainModeTest.cpp
@@ -0,0 +1,446 @@
+/*
+ * 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 "Test.h"
+
+#if SK_SUPPORT_GPU
+
+#include "GrSurfaceProxy.h"
+#include "GrTextureProducer.h"
+#include "GrTextureProxy.h"
+
+// For DetermineDomainMode (in the MDB world) we have 4 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 content rect, which can be a subset of the proxy's extent or null
+// 4) 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 SkIRect* textureContentArea,
+ const GrSamplerParams::FilterMode* filterModeOrNullForBicubic,
+ SkRect* domainRect) {
+ return GrTextureProducer::DetermineDomainMode(constraintRect,
+ filterConstraint,
+ coordsLimitedToConstraintRect,
+ proxy,
+ textureContentArea,
+ filterModeOrNullForBicubic,
+ domainRect);
+ }
+};
+
+using DomainMode = GrTextureProducer_TestAccess::DomainMode;
+
+#ifdef SK_DEBUG
+static bool is_irect(const SkRect& r) {
+ return SkScalarIsInt(r.fLeft) && SkScalarIsInt(r.fTop) &&
+ SkScalarIsInt(r.fRight) && SkScalarIsInt(r.fBottom);
+}
+#endif
+
+static SkIRect to_irect(const SkRect& r) {
+ SkASSERT(is_irect(r));
+ return SkIRect::MakeLTRB(SkScalarRoundToInt(r.fLeft),
+ SkScalarRoundToInt(r.fTop),
+ SkScalarRoundToInt(r.fRight),
+ SkScalarRoundToInt(r.fBottom));
+}
+
+
+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(GrResourceProvider* resourceProvider,
+ bool isPowerOfTwo,
+ bool isExact,
+ RectInfo* rect) {
+ int size = isPowerOfTwo ? 128 : 100;
+ SkBackingFit fit = isExact ? SkBackingFit::kExact : SkBackingFit::kApprox;
+
+ GrSurfaceDesc desc;
+ desc.fConfig = kRGBA_8888_GrPixelConfig;
+ desc.fWidth = size;
+ desc.fHeight = size;
+
+ 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);
+
+ sk_sp<GrTextureProxy> proxy = GrSurfaceProxy::MakeDeferred(resourceProvider,
+ desc, fit,
+ SkBudgeted::kYes);
+ return proxy;
+}
+
+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);
+}
+
+// This is only used for content rect creation. We ensure 'result' is correct but
+// return null to indicate no content area (other than what the proxy specifies).
+static const SkRect* null_rect(const RectInfo& enclosing,
+ RectInfo* result,
+ bool isInsetHard,
+ bool areCoordsLimitedToRect,
+ float insetAmount,
+ float halfFilterWidth) {
+ static const char* name = "null";
+ generic_inset(enclosing, result, isInsetHard, areCoordsLimitedToRect,
+ insetAmount, halfFilterWidth, 0, name);
+ return nullptr;
+}
+
+// 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, GrResourceProvider* resourceProvider) {
+ GrTextureProducer_TestAccess::DomainMode actualMode, expectedMode;
+ SkRect actualDomainRect;
+
+ static const GrSamplerParams::FilterMode gModes[] = {
+ GrSamplerParams::kNone_FilterMode,
+ GrSamplerParams::kBilerp_FilterMode,
+ GrSamplerParams::kMipMap_FilterMode,
+ };
+
+ static const GrSamplerParams::FilterMode* 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(resourceProvider, isPowerOfTwoSized,
+ isExact, &outermost);
+ SkASSERT(outermost.isHardOrBadAllAround());
+
+ for (auto contentRectMaker : { left_only, top_only, right_only,
+ bot_only, full_inset, null_rect}) {
+ RectInfo contentRectStorage;
+ const SkRect* contentRect = (*contentRectMaker)(outermost,
+ &contentRectStorage,
+ true, false, 5.0f, -1.0f);
+ if (contentRect) {
+ // We only have content rects if they actually reduce the extent of the content
+ SkASSERT(!contentRect->contains(outermost.rect()));
+ SkASSERT(outermost.rect().contains(*contentRect));
+ SkASSERT(is_irect(*contentRect));
+ }
+ SkASSERT(contentRectStorage.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)(
+ contentRect ? contentRectStorage : outermost,
+ &constraintRectStorage,
+ isConstraintRectHard,
+ areCoordsLimitedToConstraintRect,
+ insetAmt,
+ gHalfFilterWidth[filterMode]);
+ SkASSERT(constraintRect); // always need one of these
+ if (contentRect) {
+ SkASSERT(contentRect->contains(*constraintRect));
+ } else {
+ SkASSERT(outermost.rect().contains(*constraintRect));
+ }
+
+ SkIRect contentIRect;
+ if (contentRect) {
+ contentIRect = to_irect(*contentRect);
+ }
+
+ actualMode = GrTextureProducer_TestAccess::DetermineDomainMode(
+ *constraintRect,
+ isConstraintRectHard
+ ? GrTextureProducer::kYes_FilterConstraint
+ : GrTextureProducer::kNo_FilterConstraint,
+ areCoordsLimitedToConstraintRect,
+ proxy.get(),
+ contentRect ? &contentIRect : nullptr,
+ 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->resourceProvider());
+}
+
+#endif