Decal fallback for SkImageShader

Bug: skia:
Change-Id: Ib39f74886c0edc655ded8ba1075e5205361ae650
Reviewed-on: https://skia-review.googlesource.com/c/176225
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Michael Ludwig 2018-12-17 09:50:51 -05:00 committed by Skia Commit-Bot
parent 1be2d421ce
commit be315a2748
12 changed files with 346 additions and 181 deletions

View File

@ -15,6 +15,7 @@
#include "GrRenderTargetContextPriv.h"
#include "SkGradientShader.h"
#include "SkImage.h"
#include "SkImage_Base.h"
#include "SkSurface.h"
#include "effects/GrTextureDomain.h"
#include "ops/GrDrawOp.h"
@ -26,13 +27,20 @@ namespace skiagm {
*/
class TextureDomainEffect : public GM {
public:
TextureDomainEffect() {
TextureDomainEffect(GrSamplerState::Filter filter)
: fFilter(filter) {
this->setBGColor(0xFFFFFFFF);
}
protected:
SkString onShortName() override {
return SkString("texture_domain_effect");
SkString name("texture_domain_effect");
if (fFilter == GrSamplerState::Filter::kBilerp) {
name.append("_bilerp");
} else if (fFilter == GrSamplerState::Filter::kMipMap) {
name.append("_mipmap");
}
return name;
}
SkISize onISize() override {
@ -88,8 +96,18 @@ protected:
}
GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
sk_sp<GrTextureProxy> proxy = proxyProvider->createTextureProxy(
sk_sp<GrTextureProxy> proxy;
if (fFilter == GrSamplerState::Filter::kMipMap) {
SkBitmap copy;
SkImageInfo info = as_IB(fImage)->onImageInfo().makeColorType(kN32_SkColorType);
if (!copy.tryAllocPixels(info) || !fImage->readPixels(copy.pixmap(), 0, 0)) {
return;
}
proxy = proxyProvider->createMipMapProxyFromBitmap(copy);
} else {
proxy = proxyProvider->createTextureProxy(
fImage, kNone_GrSurfaceFlags, 1, SkBudgeted::kYes, SkBackingFit::kExact);
}
if (!proxy) {
return;
}
@ -100,11 +118,10 @@ protected:
textureMatrices.push_back();
textureMatrices.back().setRotate(45.f, proxy->width() / 2.f, proxy->height() / 2.f);
const SkIRect texelDomains[] = {
fImage->bounds(),
SkIRect::MakeXYWH(fImage->width() / 4, fImage->height() / 4,
fImage->width() / 2, fImage->height() / 2),
SkIRect::MakeXYWH(fImage->width() / 4 - 1, fImage->height() / 4 - 1,
fImage->width() / 2 + 2, fImage->height() / 2 + 2),
};
SkRect renderRect = SkRect::Make(fImage->bounds());
@ -116,12 +133,18 @@ protected:
SkScalar x = kDrawPad + kTestPad;
for (int m = 0; m < GrTextureDomain::kModeCount; ++m) {
GrTextureDomain::Mode mode = (GrTextureDomain::Mode) m;
if (fFilter != GrSamplerState::Filter::kNearest &&
mode == GrTextureDomain::kRepeat_Mode) {
// Repeat mode doesn't produce correct results with bilerp filtering
continue;
}
GrPaint grPaint;
grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
auto fp = GrTextureDomainEffect::Make(
proxy, textureMatrices[tm],
GrTextureDomain::MakeTexelDomainForMode(texelDomains[d], mode), mode,
GrSamplerState::Filter::kNearest);
GrTextureDomain::MakeTexelDomain(texelDomains[d], mode),
mode, fFilter);
if (!fp) {
continue;
@ -144,9 +167,13 @@ private:
static constexpr int kTargetWidth = 100;
static constexpr int kTargetHeight = 100;
sk_sp<SkImage> fImage;
GrSamplerState::Filter fFilter;
typedef GM INHERITED;
};
DEF_GM(return new TextureDomainEffect;)
DEF_GM(return new TextureDomainEffect(GrSamplerState::Filter::kNearest);)
DEF_GM(return new TextureDomainEffect(GrSamplerState::Filter::kBilerp);)
DEF_GM(return new TextureDomainEffect(GrSamplerState::Filter::kMipMap);)
}

View File

@ -265,17 +265,29 @@ DEF_GM( return new Tiling2GM(make_grad, "gradient"); )
#include "SkGradientShader.h"
DEF_SIMPLE_GM(tilemode_decal, canvas, 715, 560) {
DEF_SIMPLE_GM(tilemode_decal, canvas, 720, 1100) {
auto img = GetResourceAsImage("images/mandrill_128.png");
SkPaint bgpaint;
bgpaint.setColor(SK_ColorYELLOW);
SkRect r = { -20, -20, img->width() + 20.0f, img->height() + 20.0f };
canvas->translate(25, 25);
canvas->translate(45, 45);
std::function<void(SkPaint*, SkShader::TileMode, SkShader::TileMode)> shader_procs[] = {
[img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
// Test no filtering with decal mode
paint->setShader(img->makeShader(tx, ty));
paint->setFilterQuality(kNone_SkFilterQuality);
},
[img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
// Test bilerp approximation for decal mode (or clamp to border HW)
paint->setShader(img->makeShader(tx, ty));
paint->setFilterQuality(kLow_SkFilterQuality);
},
[img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
// Test bicubic filter with decal mode
paint->setShader(img->makeShader(tx, ty));
paint->setFilterQuality(kHigh_SkFilterQuality);
},
[img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
@ -306,9 +318,14 @@ DEF_SIMPLE_GM(tilemode_decal, canvas, 715, 560) {
SkPaint paint;
canvas->save();
for (const auto& proc : shader_procs) {
canvas->save();
// Apply a slight rotation to highlight the differences between filtered and unfiltered
// decal edges
canvas->rotate(4);
canvas->drawRect(r, bgpaint);
proc(&paint, p.fX, p.fY);
canvas->drawRect(r, paint);
canvas->restore();
canvas->translate(0, r.height() + 20);
}
canvas->restore();

View File

@ -314,7 +314,7 @@ sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::filterImageGPU(
SkIntToScalar(bgSubset.top() - backgroundOffset.fY));
bgFP = GrTextureDomainEffect::Make(
std::move(backgroundProxy), backgroundMatrix,
GrTextureDomain::MakeTexelDomain(bgSubset),
GrTextureDomain::MakeTexelDomain(bgSubset, GrTextureDomain::kDecal_Mode),
GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
background->alphaType(),
@ -331,7 +331,7 @@ sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::filterImageGPU(
SkIntToScalar(fgSubset.top() - foregroundOffset.fY));
auto foregroundFP = GrTextureDomainEffect::Make(
std::move(foregroundProxy), foregroundMatrix,
GrTextureDomain::MakeTexelDomain(fgSubset),
GrTextureDomain::MakeTexelDomain(fgSubset, GrTextureDomain::kDecal_Mode),
GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
foreground->getColorSpace(),

View File

@ -448,8 +448,10 @@ GrDisplacementMapEffect::GrDisplacementMapEffect(
, fDisplacementTransform(offsetMatrix, displacement.get())
, fDisplacementSampler(displacement)
, fColorTransform(color.get())
, fDomain(color.get(), GrTextureDomain::MakeTexelDomain(SkIRect::MakeSize(colorDimensions)),
GrTextureDomain::kDecal_Mode)
, fDomain(color.get(),
GrTextureDomain::MakeTexelDomain(SkIRect::MakeSize(colorDimensions),
GrTextureDomain::kDecal_Mode),
GrTextureDomain::kDecal_Mode, GrTextureDomain::kDecal_Mode)
, fColorSampler(color)
, fXChannelSelector(xChannelSelector)
, fYChannelSelector(yChannelSelector)
@ -605,7 +607,8 @@ void GrGLDisplacementMapEffect::onSetData(const GrGLSLProgramDataManager& pdman,
pdman.set2f(fScaleUni, SkScalarToFloat(scaleX),
proxy->origin() == kTopLeft_GrSurfaceOrigin ?
SkScalarToFloat(scaleY) : SkScalarToFloat(-scaleY));
fGLDomain.setData(pdman, displacementMap.domain(), proxy);
fGLDomain.setData(pdman, displacementMap.domain(), proxy,
displacementMap.textureSampler(1).samplerState());
}
void GrGLDisplacementMapEffect::GenKey(const GrProcessor& proc,

View File

@ -1676,8 +1676,8 @@ private:
static GrTextureDomain create_domain(GrTextureProxy* proxy, const SkIRect* srcBounds,
GrTextureDomain::Mode mode) {
if (srcBounds) {
SkRect texelDomain = GrTextureDomain::MakeTexelDomainForMode(*srcBounds, mode);
return GrTextureDomain(proxy, texelDomain, mode);
SkRect texelDomain = GrTextureDomain::MakeTexelDomain(*srcBounds, mode);
return GrTextureDomain(proxy, texelDomain, mode, mode);
} else {
return GrTextureDomain::IgnoredDomain();
}
@ -1926,7 +1926,7 @@ void GrGLLightingEffect::onSetData(const GrGLSLProgramDataManager& pdman,
pdman.set1f(fSurfaceScaleUni, lighting.surfaceScale());
sk_sp<SkImageFilterLight> transformedLight(
lighting.light()->transform(lighting.filterMatrix()));
fDomain.setData(pdman, lighting.domain(), proxy);
fDomain.setData(pdman, lighting.domain(), proxy, lighting.textureSampler(0).samplerState());
fLight->setData(pdman, transformedLight.get());
}

View File

@ -280,10 +280,10 @@ sk_sp<SkSpecialImage> SkXfermodeImageFilter_Base::filterImageGPU(
SkMatrix bgMatrix = SkMatrix::MakeTrans(
SkIntToScalar(bgSubset.left() - backgroundOffset.fX),
SkIntToScalar(bgSubset.top() - backgroundOffset.fY));
bgFP = GrTextureDomainEffect::Make(std::move(backgroundProxy), bgMatrix,
GrTextureDomain::MakeTexelDomain(bgSubset),
GrTextureDomain::kDecal_Mode,
GrSamplerState::Filter::kNearest);
bgFP = GrTextureDomainEffect::Make(
std::move(backgroundProxy), bgMatrix,
GrTextureDomain::MakeTexelDomain(bgSubset, GrTextureDomain::kDecal_Mode),
GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
background->alphaType(),
outputProperties.colorSpace());
@ -299,7 +299,7 @@ sk_sp<SkSpecialImage> SkXfermodeImageFilter_Base::filterImageGPU(
SkIntToScalar(fgSubset.top() - foregroundOffset.fY));
auto foregroundFP = GrTextureDomainEffect::Make(
std::move(foregroundProxy), fgMatrix,
GrTextureDomain::MakeTexelDomain(fgSubset),
GrTextureDomain::MakeTexelDomain(fgSubset, GrTextureDomain::kDecal_Mode),
GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
foreground->getColorSpace(),

View File

@ -113,15 +113,20 @@ void GrGLBicubicEffect::onSetData(const GrGLSLProgramDataManager& pdman,
imageIncrement[0] = 1.0f / texture->width();
imageIncrement[1] = 1.0f / texture->height();
pdman.set2fv(fImageIncrementUni, 1, imageIncrement);
fDomain.setData(pdman, bicubicEffect.domain(), proxy);
fDomain.setData(pdman, bicubicEffect.domain(), proxy,
processor.textureSampler(0).samplerState());
}
GrBicubicEffect::GrBicubicEffect(sk_sp<GrTextureProxy> proxy,
const SkMatrix& matrix,
const GrSamplerState::WrapMode wrapModes[2])
const GrSamplerState::WrapMode wrapModes[2],
GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY)
: INHERITED{kGrBicubicEffect_ClassID, ModulateByConfigOptimizationFlags(proxy->config())}
, fCoordTransform(matrix, proxy.get())
, fDomain(GrTextureDomain::IgnoredDomain())
, fDomain(proxy.get(),
GrTextureDomain::MakeTexelDomain(
SkIRect::MakeWH(proxy->width(), proxy->height()), modeX, modeY),
modeX, modeY)
, fTextureSampler(std::move(proxy),
GrSamplerState(wrapModes, GrSamplerState::Filter::kNearest)) {
this->addCoordTransform(&fCoordTransform);
@ -133,7 +138,7 @@ GrBicubicEffect::GrBicubicEffect(sk_sp<GrTextureProxy> proxy,
const SkRect& domain)
: INHERITED(kGrBicubicEffect_ClassID, ModulateByConfigOptimizationFlags(proxy->config()))
, fCoordTransform(matrix, proxy.get())
, fDomain(proxy.get(), domain, GrTextureDomain::kClamp_Mode)
, fDomain(proxy.get(), domain, GrTextureDomain::kClamp_Mode, GrTextureDomain::kClamp_Mode)
, fTextureSampler(std::move(proxy)) {
this->addCoordTransform(&fCoordTransform);
this->setTextureSamplerCnt(1);

View File

@ -34,8 +34,24 @@ public:
static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
const SkMatrix& matrix,
const GrSamplerState::WrapMode wrapModes[2]) {
// Ignore the domain on x and y, since this factory relies solely on the wrap mode of the
// sampler to constrain texture coordinates
return Make(std::move(proxy), matrix, wrapModes, GrTextureDomain::kIgnore_Mode,
GrTextureDomain::kIgnore_Mode);
}
/**
* Create a Mitchell filter effect with specified texture matrix and x/y tile modes. This
* supports providing modes for the texture domain explicitly, in the event that it should
* override the behavior of the sampler's tile mode (e.g. clamp to border unsupported).
*/
static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
const SkMatrix& matrix,
const GrSamplerState::WrapMode wrapModes[2],
GrTextureDomain::Mode modeX,
GrTextureDomain::Mode modeY) {
return std::unique_ptr<GrFragmentProcessor>(new GrBicubicEffect(std::move(proxy), matrix,
wrapModes));
wrapModes, modeX, modeY));
}
/**
@ -60,7 +76,8 @@ public:
private:
GrBicubicEffect(sk_sp<GrTextureProxy>, const SkMatrix& matrix,
const GrSamplerState::WrapMode wrapModes[2]);
const GrSamplerState::WrapMode wrapModes[2],
GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY);
GrBicubicEffect(sk_sp<GrTextureProxy>, const SkMatrix &matrix, const SkRect& domain);
explicit GrBicubicEffect(const GrBicubicEffect&);

View File

@ -140,7 +140,7 @@ void GrGLMatrixConvolutionEffect::onSetData(const GrGLSLProgramDataManager& pdma
pdman.set4fv(fKernelUni, arrayCount, conv.kernel());
pdman.set1f(fGainUni, conv.gain());
pdman.set1f(fBiasUni, conv.bias());
fDomain.setData(pdman, conv.domain(), proxy);
fDomain.setData(pdman, conv.domain(), proxy, conv.textureSampler(0).samplerState());
}
GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(sk_sp<GrTextureProxy> srcProxy,
@ -156,9 +156,8 @@ GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(sk_sp<GrTextureProxy> srcPr
// parameters.
: INHERITED(kGrMatrixConvolutionEffect_ClassID, kNone_OptimizationFlags)
, fCoordTransform(srcProxy.get())
, fDomain(srcProxy.get(),
GrTextureDomain::MakeTexelDomainForMode(srcBounds, tileMode),
tileMode)
, fDomain(srcProxy.get(), GrTextureDomain::MakeTexelDomain(srcBounds, tileMode),
tileMode, tileMode)
, fTextureSampler(std::move(srcProxy))
, fKernelSize(kernelSize)
, fGain(SkScalarToFloat(gain))

View File

@ -21,26 +21,14 @@
#include <utility>
static bool can_ignore_rect(GrTextureProxy* proxy, const SkRect& domain) {
if (GrProxyProvider::IsFunctionallyExact(proxy)) {
const SkIRect kFullRect = SkIRect::MakeWH(proxy->width(), proxy->height());
return domain.contains(kFullRect);
}
return false;
}
GrTextureDomain::GrTextureDomain(GrTextureProxy* proxy, const SkRect& domain, Mode mode, int index)
: fMode(mode)
GrTextureDomain::GrTextureDomain(GrTextureProxy* proxy, const SkRect& domain, Mode modeX,
Mode modeY, int index)
: fModeX(modeX)
, fModeY(modeY)
, fIndex(index) {
if (kIgnore_Mode == fMode) {
return;
}
if (kClamp_Mode == mode && can_ignore_rect(proxy, domain)) {
fMode = kIgnore_Mode;
if (!proxy) {
SkASSERT(modeX == kIgnore_Mode && modeY == kIgnore_Mode);
return;
}
@ -61,6 +49,33 @@ GrTextureDomain::GrTextureDomain(GrTextureProxy* proxy, const SkRect& domain, Mo
//////////////////////////////////////////////////////////////////////////////
static SkString clamp_expression(GrTextureDomain::Mode mode, const char* inCoord,
const char* coordSwizzle, const char* domain,
const char* minSwizzle, const char* maxSwizzle) {
SkString clampedExpr;
switch(mode) {
case GrTextureDomain::kIgnore_Mode:
clampedExpr.printf("%s.%s\n", inCoord, coordSwizzle);
break;
case GrTextureDomain::kDecal_Mode:
// The lookup coordinate to use for decal will be clamped just like kClamp_Mode,
// it's just that the post-processing will be different, so fall through
case GrTextureDomain::kClamp_Mode:
clampedExpr.printf("clamp(%s.%s, %s.%s, %s.%s)",
inCoord, coordSwizzle, domain, minSwizzle, domain, maxSwizzle);
break;
case GrTextureDomain::kRepeat_Mode:
clampedExpr.printf("mod(%s.%s - %s.%s, %s.%s - %s.%s) + %s.%s",
inCoord, coordSwizzle, domain, minSwizzle, domain, maxSwizzle,
domain, minSwizzle, domain, minSwizzle);
break;
default:
SkASSERTF(false, "Unknown texture domain mode: %u\n", (uint32_t) mode);
break;
}
return clampedExpr;
}
void GrTextureDomain::GLDomain::sampleTexture(GrGLSLShaderBuilder* builder,
GrGLSLUniformHandler* uniformHandler,
const GrShaderCaps* shaderCaps,
@ -69,11 +84,14 @@ void GrTextureDomain::GLDomain::sampleTexture(GrGLSLShaderBuilder* builder,
const SkString& inCoords,
GrGLSLFragmentProcessor::SamplerHandle sampler,
const char* inModulateColor) {
SkASSERT(!fHasMode || textureDomain.mode() == fMode);
SkDEBUGCODE(fMode = textureDomain.mode();)
SkASSERT(!fHasMode || (textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY));
SkDEBUGCODE(fModeX = textureDomain.modeX();)
SkDEBUGCODE(fModeY = textureDomain.modeY();)
SkDEBUGCODE(fHasMode = true;)
if (textureDomain.mode() != kIgnore_Mode && !fDomainUni.isValid()) {
if ((textureDomain.modeX() != kIgnore_Mode || textureDomain.modeY() != kIgnore_Mode) &&
!fDomainUni.isValid()) {
// Must include the domain uniform since at least one axis uses it
const char* name;
SkString uniName("TexDom");
if (textureDomain.fIndex >= 0) {
@ -84,95 +102,114 @@ void GrTextureDomain::GLDomain::sampleTexture(GrGLSLShaderBuilder* builder,
fDomainName = name;
}
switch (textureDomain.mode()) {
case kIgnore_Mode: {
builder->codeAppendf("%s = ", outColor);
builder->appendTextureLookupAndModulate(inModulateColor, sampler, inCoords.c_str(),
kFloat2_GrSLType);
builder->codeAppend(";");
break;
bool decalX = textureDomain.modeX() == kDecal_Mode;
bool decalY = textureDomain.modeY() == kDecal_Mode;
if ((decalX || decalY) && !fDecalUni.isValid()) {
const char* name;
SkString uniName("DecalParams");
if (textureDomain.fIndex >= 0) {
uniName.appendS32(textureDomain.fIndex);
}
case kClamp_Mode: {
SkString clampedCoords;
clampedCoords.appendf("clamp(%s, %s.xy, %s.zw)",
inCoords.c_str(), fDomainName.c_str(), fDomainName.c_str());
// Half3 since this will hold texture width, height, and then a step function control param
fDecalUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf3_GrSLType,
uniName.c_str(), &name);
fDecalName = name;
}
builder->codeAppendf("%s = ", outColor);
builder->appendTextureLookupAndModulate(inModulateColor, sampler, clampedCoords.c_str(),
kFloat2_GrSLType);
builder->codeAppend(";");
break;
// Add a block so that we can declare variables
GrGLSLShaderBuilder::ShaderBlock block(builder);
// Always use a local variable for the input coordinates; often callers pass in an expression
// and we want to cache it across all of its references in the code below
builder->codeAppendf("float2 origCoord = %s;", inCoords.c_str());
builder->codeAppend("float2 clampedCoord = ");
if (textureDomain.modeX() != textureDomain.modeY()) {
// The wrap modes differ on the two axes, so build up a coordinate that respects each axis'
// domain rule independently before sampling the texture.
SkString tcX = clamp_expression(textureDomain.modeX(), "origCoord", "x",
fDomainName.c_str(), "x", "z");
SkString tcY = clamp_expression(textureDomain.modeY(), "origCoord", "y",
fDomainName.c_str(), "y", "w");
builder->codeAppendf("float2(%s, %s)", tcX.c_str(), tcY.c_str());
} else {
// Since the x and y axis wrap modes are the same, they can be calculated together using
// more efficient vector operations
SkString tc = clamp_expression(textureDomain.modeX(), "origCoord", "xy",
fDomainName.c_str(), "xy", "zw");
builder->codeAppend(tc.c_str());
}
builder->codeAppend(";");
// Look up the texture sample at the clamped coordinate location
builder->codeAppend("half4 inside = ");
builder->appendTextureLookupAndModulate(inModulateColor, sampler, "clampedCoord",
kFloat2_GrSLType);
builder->codeAppend(";");
// Apply decal mode's transparency interpolation if needed
if (decalX || decalY) {
// The decal err is the max absoluate value between the clamped coordinate and the original
// pixel coordinate. This will then be clamped to 1.f if it's greater than the control
// parameter, which simulates kNearest and kBilerp behavior depending on if it's 0 or 1.
if (decalX && decalY) {
builder->codeAppendf("half err = max(abs(clampedCoord.x - origCoord.x) * %s.x, "
"abs(clampedCoord.y - origCoord.y) * %s.y);",
fDecalName.c_str(), fDecalName.c_str());
} else if (decalX) {
builder->codeAppendf("half err = abs(clampedCoord.x - origCoord.x) * %s.x;",
fDecalName.c_str());
} else {
SkASSERT(decalY);
builder->codeAppendf("half err = abs(clampedCoord.y - origCoord.y) * %s.y;",
fDecalName.c_str());
}
case kDecal_Mode: {
// Add a block since we're going to declare variables.
GrGLSLShaderBuilder::ShaderBlock block(builder);
const char* domain = fDomainName.c_str();
if (!shaderCaps->canUseAnyFunctionInShader()) {
// On the NexusS and GalaxyNexus, the other path (with the 'any'
// call) causes the compilation error "Calls to any function that
// may require a gradient calculation inside a conditional block
// may return undefined results". This appears to be an issue with
// the 'any' call since even the simple "result=black; if (any())
// result=white;" code fails to compile.
builder->codeAppend("half4 outside = half4(0.0, 0.0, 0.0, 0.0);");
builder->codeAppend("half4 inside = ");
builder->appendTextureLookupAndModulate(inModulateColor, sampler, inCoords.c_str(),
kFloat2_GrSLType);
builder->codeAppend(";");
builder->codeAppendf("float x = (%s).x;", inCoords.c_str());
builder->codeAppendf("float y = (%s).y;", inCoords.c_str());
builder->codeAppendf("x = abs(2.0*(x - %s.x)/(%s.z - %s.x) - 1.0);",
domain, domain, domain);
builder->codeAppendf("y = abs(2.0*(y - %s.y)/(%s.w - %s.y) - 1.0);",
domain, domain, domain);
builder->codeAppend("half blend = step(1.0, max(x, y));");
builder->codeAppendf("%s = mix(inside, outside, blend);", outColor);
} else {
builder->codeAppend("bool4 outside;\n");
builder->codeAppendf("outside.xy = lessThan(%s, %s.xy);", inCoords.c_str(),
domain);
builder->codeAppendf("outside.zw = greaterThan(%s, %s.zw);", inCoords.c_str(),
domain);
builder->codeAppendf("%s = any(outside) ? half4(0.0, 0.0, 0.0, 0.0) : ",
outColor);
builder->appendTextureLookupAndModulate(inModulateColor, sampler, inCoords.c_str(),
kFloat2_GrSLType);
builder->codeAppend(";");
}
break;
}
case kRepeat_Mode: {
SkString clampedCoords;
clampedCoords.printf("mod(%s - %s.xy, %s.zw - %s.xy) + %s.xy",
inCoords.c_str(), fDomainName.c_str(), fDomainName.c_str(),
fDomainName.c_str(), fDomainName.c_str());
builder->codeAppendf("%s = ", outColor);
builder->appendTextureLookupAndModulate(inModulateColor, sampler, clampedCoords.c_str(),
kFloat2_GrSLType);
builder->codeAppend(";");
break;
}
// Apply a transform to the error rate, which let's us simulate nearest or bilerp filtering
// in the same shader. When the texture is nearest filtered, fSizeName.z is set to 1/2 so
// this becomes a step function centered at .5 away from the clamped coordinate (but the
// domain for decal is inset by .5 so the edge lines up properly). When bilerp, fSizeName.z
// is set to 1 and it becomes a simple linear blend between texture and transparent.
builder->codeAppendf("if (err > %s.z) { err = 1.0; } else if (%s.z < 1) { err = 0.0; }",
fDecalName.c_str(), fDecalName.c_str());
builder->codeAppendf("%s = mix(inside, half4(0, 0, 0, 0), err);", outColor);
} else {
// A simple look up
builder->codeAppendf("%s = inside;", outColor);
}
}
void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
const GrTextureDomain& textureDomain,
GrTextureProxy* proxy) {
GrTextureProxy* proxy,
const GrSamplerState& sampler) {
GrTexture* tex = proxy->peekTexture();
SkASSERT(fHasMode && textureDomain.mode() == fMode);
if (kIgnore_Mode != textureDomain.mode()) {
SkASSERT(fHasMode && textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY);
if (kIgnore_Mode != textureDomain.modeX() || kIgnore_Mode != textureDomain.modeY()) {
bool sendDecalData = textureDomain.modeX() == kDecal_Mode ||
textureDomain.modeY() == kDecal_Mode;
// If the texture is using nearest filtering, then the decal filter weight should step from
// 0 (texture) to 1 (transparent) one half pixel away from the domain. When doing any other
// form of filtering, the weight should be 1.0 so that it smoothly interpolates between the
// texture and transparent.
SkScalar decalFilterWeight = sampler.filter() == GrSamplerState::Filter::kNearest ?
SK_ScalarHalf : 1.0f;
SkScalar wInv, hInv, h;
if (proxy->textureType() == GrTextureType::kRectangle) {
wInv = hInv = 1.f;
h = tex->height();
// Don't do any scaling by texture size for decal filter rate, it's already in pixels
if (sendDecalData) {
pdman.set3f(fDecalUni, 1.f, 1.f, decalFilterWeight);
}
} else {
wInv = SK_Scalar1 / tex->width();
hInv = SK_Scalar1 / tex->height();
h = 1.f;
if (sendDecalData) {
pdman.set3f(fDecalUni, tex->width(), tex->height(), decalFilterWeight);
}
}
float values[kPrevDomainCount] = {
@ -213,8 +250,9 @@ void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
///////////////////////////////////////////////////////////////////////////////
inline GrFragmentProcessor::OptimizationFlags GrTextureDomainEffect::OptFlags(
GrPixelConfig config, GrTextureDomain::Mode mode) {
if (mode == GrTextureDomain::kDecal_Mode || !GrPixelConfigIsOpaque(config)) {
GrPixelConfig config, GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY) {
if (modeX == GrTextureDomain::kDecal_Mode || modeY == GrTextureDomain::kDecal_Mode ||
!GrPixelConfigIsOpaque(config)) {
return GrFragmentProcessor::kCompatibleWithCoverageAsAlpha_OptimizationFlag;
} else {
return GrFragmentProcessor::kCompatibleWithCoverageAsAlpha_OptimizationFlag |
@ -228,26 +266,37 @@ std::unique_ptr<GrFragmentProcessor> GrTextureDomainEffect::Make(
const SkRect& domain,
GrTextureDomain::Mode mode,
GrSamplerState::Filter filterMode) {
if (GrTextureDomain::kIgnore_Mode == mode ||
(GrTextureDomain::kClamp_Mode == mode && can_ignore_rect(proxy.get(), domain))) {
return GrSimpleTextureEffect::Make(std::move(proxy), matrix, filterMode);
} else {
return std::unique_ptr<GrFragmentProcessor>(new GrTextureDomainEffect(
std::move(proxy), matrix, domain, mode, filterMode));
}
return Make(std::move(proxy), matrix, domain, mode, mode,
GrSamplerState(GrSamplerState::WrapMode::kClamp, filterMode));
}
std::unique_ptr<GrFragmentProcessor> GrTextureDomainEffect::Make(
sk_sp<GrTextureProxy> proxy,
const SkMatrix& matrix,
const SkRect& domain,
GrTextureDomain::Mode modeX,
GrTextureDomain::Mode modeY,
const GrSamplerState& sampler) {
// If both domain modes happen to be ignore, it would be faster to just drop the domain logic
// entirely Technically, we could also use the simple texture effect if the domain modes agree
// with the sampler modes and the proxy is the same size as the domain. It's a lot easier for
// calling code to detect these cases and handle it themselves.
return std::unique_ptr<GrFragmentProcessor>(new GrTextureDomainEffect(
std::move(proxy), matrix, domain, modeX, modeY, sampler));
}
GrTextureDomainEffect::GrTextureDomainEffect(sk_sp<GrTextureProxy> proxy,
const SkMatrix& matrix,
const SkRect& domain,
GrTextureDomain::Mode mode,
GrSamplerState::Filter filterMode)
: INHERITED(kGrTextureDomainEffect_ClassID, OptFlags(proxy->config(), mode))
GrTextureDomain::Mode modeX,
GrTextureDomain::Mode modeY,
const GrSamplerState& sampler)
: INHERITED(kGrTextureDomainEffect_ClassID, OptFlags(proxy->config(), modeX, modeY))
, fCoordTransform(matrix, proxy.get())
, fTextureDomain(proxy.get(), domain, mode)
, fTextureSampler(std::move(proxy), filterMode) {
SkASSERT(mode != GrTextureDomain::kRepeat_Mode ||
filterMode == GrSamplerState::Filter::kNearest);
, fTextureDomain(proxy.get(), domain, modeX, modeY)
, fTextureSampler(std::move(proxy), sampler) {
SkASSERT((modeX != GrTextureDomain::kRepeat_Mode && modeY != GrTextureDomain::kRepeat_Mode) ||
sampler.filter() == GrSamplerState::Filter::kNearest);
this->addCoordTransform(&fCoordTransform);
this->setTextureSamplerCnt(1);
}
@ -293,7 +342,7 @@ GrGLSLFragmentProcessor* GrTextureDomainEffect::onCreateGLSLInstance() const {
const GrTextureDomain& domain = tde.fTextureDomain;
GrTextureProxy* proxy = tde.textureSampler(0).proxy();
fGLDomain.setData(pdman, domain, proxy);
fGLDomain.setData(pdman, domain, proxy, tde.textureSampler(0).samplerState());
}
private:
@ -322,16 +371,21 @@ std::unique_ptr<GrFragmentProcessor> GrTextureDomainEffect::TestCreate(GrProcess
domain.fRight = d->fRandom->nextRangeScalar(domain.fLeft, proxy->width());
domain.fTop = d->fRandom->nextRangeScalar(0, proxy->height());
domain.fBottom = d->fRandom->nextRangeScalar(domain.fTop, proxy->height());
GrTextureDomain::Mode mode =
GrTextureDomain::Mode modeX =
(GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount);
GrTextureDomain::Mode modeY =
(GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount);
const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
bool bilerp = mode != GrTextureDomain::kRepeat_Mode ? d->fRandom->nextBool() : false;
bool bilerp = modeX != GrTextureDomain::kRepeat_Mode && modeY != GrTextureDomain::kRepeat_Mode ?
d->fRandom->nextBool() : false;
return GrTextureDomainEffect::Make(
std::move(proxy),
matrix,
domain,
mode,
bilerp ? GrSamplerState::Filter::kBilerp : GrSamplerState::Filter::kNearest);
modeX,
modeY,
GrSamplerState(GrSamplerState::WrapMode::kClamp, bilerp ?
GrSamplerState::Filter::kBilerp : GrSamplerState::Filter::kNearest));
}
#endif
@ -347,8 +401,9 @@ GrDeviceSpaceTextureDecalFragmentProcessor::GrDeviceSpaceTextureDecalFragmentPro
: INHERITED(kGrDeviceSpaceTextureDecalFragmentProcessor_ClassID,
kCompatibleWithCoverageAsAlpha_OptimizationFlag)
, fTextureSampler(proxy, GrSamplerState::ClampNearest())
, fTextureDomain(proxy.get(), GrTextureDomain::MakeTexelDomain(subset),
GrTextureDomain::kDecal_Mode) {
, fTextureDomain(proxy.get(),
GrTextureDomain::MakeTexelDomain(subset, GrTextureDomain::kDecal_Mode),
GrTextureDomain::kDecal_Mode, GrTextureDomain::kDecal_Mode) {
this->setTextureSamplerCnt(1);
fDeviceSpaceOffset.fX = deviceSpaceOffset.fX - subset.fLeft;
fDeviceSpaceOffset.fY = deviceSpaceOffset.fY - subset.fTop;
@ -400,7 +455,8 @@ GrGLSLFragmentProcessor* GrDeviceSpaceTextureDecalFragmentProcessor::onCreateGLS
GrTextureProxy* proxy = dstdfp.textureSampler(0).proxy();
GrTexture* texture = proxy->peekTexture();
fGLDomain.setData(pdman, dstdfp.fTextureDomain, proxy);
fGLDomain.setData(pdman, dstdfp.fTextureDomain, proxy,
dstdfp.textureSampler(0).samplerState());
float iw = 1.f / texture->width();
float ih = 1.f / texture->height();
float scaleAndTransData[4] = {

View File

@ -45,7 +45,7 @@ public:
static const GrTextureDomain& IgnoredDomain() {
static const GrTextureDomain gDomain((GrTextureProxy*)nullptr,
SkRect::MakeEmpty(), kIgnore_Mode);
SkRect::MakeEmpty(), kIgnore_Mode, kIgnore_Mode);
return gDomain;
}
@ -53,28 +53,38 @@ public:
* @param index Pass a value >= 0 if using multiple texture domains in the same effect.
* It is used to keep inserted variables from causing name collisions.
*/
GrTextureDomain(GrTextureProxy*, const SkRect& domain, Mode, int index = -1);
GrTextureDomain(GrTextureProxy*, const SkRect& domain, Mode modeX, Mode modeY, int index = -1);
GrTextureDomain(const GrTextureDomain&) = default;
const SkRect& domain() const { return fDomain; }
Mode mode() const { return fMode; }
Mode modeX() const { return fModeX; }
Mode modeY() const { return fModeY; }
/* Computes a domain that bounds all the texels in texelRect. Note that with bilerp enabled
texels neighboring the domain may be read. */
static const SkRect MakeTexelDomain(const SkIRect& texelRect) {
return SkRect::Make(texelRect);
/*
* Computes a domain that bounds all the texels in texelRect, possibly insetting by half a pixel
* depending on the mode. The mode is used for both axes.
*/
static const SkRect MakeTexelDomain(const SkIRect& texelRect, Mode mode) {
return MakeTexelDomain(texelRect, mode, mode);
}
static const SkRect MakeTexelDomainForMode(const SkIRect& texelRect, Mode mode) {
// For Clamp mode, inset by half a texel.
SkScalar inset = (mode == kClamp_Mode && !texelRect.isEmpty()) ? SK_ScalarHalf : 0;
return SkRect::MakeLTRB(texelRect.fLeft + inset, texelRect.fTop + inset,
texelRect.fRight - inset, texelRect.fBottom - inset);
static const SkRect MakeTexelDomain(const SkIRect& texelRect, Mode modeX, Mode modeY) {
// For Clamp and decal modes, inset by half a texel
SkScalar insetX = ((modeX == kClamp_Mode || modeX == kDecal_Mode) && texelRect.width() > 0)
? SK_ScalarHalf : 0;
SkScalar insetY = ((modeY == kClamp_Mode || modeY == kDecal_Mode) && texelRect.height() > 0)
? SK_ScalarHalf : 0;
return SkRect::MakeLTRB(texelRect.fLeft + insetX, texelRect.fTop + insetY,
texelRect.fRight - insetX, texelRect.fBottom - insetY);
}
bool operator==(const GrTextureDomain& that) const {
return fMode == that.fMode && (kIgnore_Mode == fMode || fDomain == that.fDomain);
return fModeX == that.fModeX && fModeY == that.fModeY &&
(kIgnore_Mode == fModeX || (fDomain.fLeft == that.fDomain.fLeft &&
fDomain.fRight == that.fDomain.fRight)) &&
(kIgnore_Mode == fModeY || (fDomain.fTop == that.fDomain.fTop &&
fDomain.fBottom == that.fDomain.fBottom));
}
/**
@ -115,10 +125,12 @@ public:
* texture domain. The rectangle is automatically adjusted to account for the texture's
* origin.
*/
void setData(const GrGLSLProgramDataManager&, const GrTextureDomain&, GrTextureProxy*);
void setData(const GrGLSLProgramDataManager&, const GrTextureDomain&, GrTextureProxy*,
const GrSamplerState& sampler);
enum {
kDomainKeyBits = 2, // See DomainKey().
kModeBits = 2, // See DomainKey().
kDomainKeyBits = 4
};
/**
@ -126,21 +138,28 @@ public:
* computed key. The returned will be limited to the lower kDomainKeyBits bits.
*/
static uint32_t DomainKey(const GrTextureDomain& domain) {
GR_STATIC_ASSERT(kModeCount <= (1 << kDomainKeyBits));
return domain.mode();
GR_STATIC_ASSERT(kModeCount <= (1 << kModeBits));
return domain.modeX() | (domain.modeY() << kModeBits);
}
private:
static const int kPrevDomainCount = 4;
SkDEBUGCODE(Mode fMode;)
SkDEBUGCODE(Mode fModeX;)
SkDEBUGCODE(Mode fModeY;)
SkDEBUGCODE(bool fHasMode = false;)
GrGLSLProgramDataManager::UniformHandle fDomainUni;
SkString fDomainName;
// Only initialized if the domain has at least one decal axis
GrGLSLProgramDataManager::UniformHandle fDecalUni;
SkString fDecalName;
float fPrevDomain[kPrevDomainCount];
};
protected:
Mode fMode;
Mode fModeX;
Mode fModeY;
SkRect fDomain;
int fIndex;
};
@ -153,9 +172,16 @@ public:
static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy>,
const SkMatrix&,
const SkRect& domain,
GrTextureDomain::Mode,
GrTextureDomain::Mode mode,
GrSamplerState::Filter filterMode);
static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy>,
const SkMatrix&,
const SkRect& domain,
GrTextureDomain::Mode modeX,
GrTextureDomain::Mode modeY,
const GrSamplerState& sampler);
const char* name() const override { return "TextureDomain"; }
std::unique_ptr<GrFragmentProcessor> clone() const override {
@ -181,12 +207,14 @@ private:
GrTextureDomainEffect(sk_sp<GrTextureProxy>,
const SkMatrix&,
const SkRect& domain,
GrTextureDomain::Mode,
GrSamplerState::Filter);
GrTextureDomain::Mode modeX,
GrTextureDomain::Mode modeY,
const GrSamplerState&);
explicit GrTextureDomainEffect(const GrTextureDomainEffect&);
static OptimizationFlags OptFlags(GrPixelConfig config, GrTextureDomain::Mode mode);
static OptimizationFlags OptFlags(GrPixelConfig config, GrTextureDomain::Mode modeX,
GrTextureDomain::Mode modeY);
GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;

View File

@ -187,16 +187,19 @@ std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor(
GrSamplerState::WrapMode wrapModes[] = {tile_mode_to_wrap_mode(fTileModeX),
tile_mode_to_wrap_mode(fTileModeY)};
if ((wrapModes[0] == GrSamplerState::WrapMode::kClampToBorder ||
wrapModes[1] == GrSamplerState::WrapMode::kClampToBorder) &&
!args.fContext->contextPriv().caps()->clampToBorderSupport()) {
// HW clamp to border is unavailable, so fall back to clamp for now
// TODO(michaelludwig): If clamp-to-border is selected but is unsupported, the texture
// domain effect could be used to emulate the decal effect.
// If either domainX or domainY are un-ignored, a texture domain effect has to be used to
// implement the decal mode (while leaving non-decal axes alone). The wrap mode originally
// clamp-to-border is reset to clamp since the hw cannot implement it directly.
GrTextureDomain::Mode domainX = GrTextureDomain::kIgnore_Mode;
GrTextureDomain::Mode domainY = GrTextureDomain::kIgnore_Mode;
if (!args.fContext->contextPriv().caps()->clampToBorderSupport()) {
if (wrapModes[0] == GrSamplerState::WrapMode::kClampToBorder) {
domainX = GrTextureDomain::kDecal_Mode;
wrapModes[0] = GrSamplerState::WrapMode::kClamp;
}
if (wrapModes[1] == GrSamplerState::WrapMode::kClampToBorder) {
domainY = GrTextureDomain::kDecal_Mode;
wrapModes[1] = GrSamplerState::WrapMode::kClamp;
}
}
@ -224,9 +227,19 @@ std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor(
std::unique_ptr<GrFragmentProcessor> inner;
if (doBicubic) {
inner = GrBicubicEffect::Make(std::move(proxy), lmInverse, wrapModes);
// domainX and domainY will properly apply the decal effect with the texture domain used in
// the bicubic filter if clamp to border was unsupported in hardware
inner = GrBicubicEffect::Make(std::move(proxy), lmInverse, wrapModes, domainX, domainY);
} else {
inner = GrSimpleTextureEffect::Make(std::move(proxy), lmInverse, samplerState);
if (domainX != GrTextureDomain::kIgnore_Mode || domainY != GrTextureDomain::kIgnore_Mode) {
SkRect domain = GrTextureDomain::MakeTexelDomain(
SkIRect::MakeWH(proxy->width(), proxy->height()),
domainX, domainY);
inner = GrTextureDomainEffect::Make(std::move(proxy), lmInverse, domain,
domainX, domainY, samplerState);
} else {
inner = GrSimpleTextureEffect::Make(std::move(proxy), lmInverse, samplerState);
}
}
inner = GrColorSpaceXformEffect::Make(std::move(inner), fImage->colorSpace(),
fImage->alphaType(),