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