Support bilerp filtering for all ShaderModes in GrTextureEffect.
Support MIP map for all but kRepeat.
Update texel_subset_ GMs to exercise newly supported modes.
Also makes GrTextureEffect do shader wrap modes for rectangle/
external textures even when not subsetted.
Change-Id: Ie2d299106120b5c6cc40365e8f300cc426dd5489
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/268163
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/gm/texelsubset.cpp b/gm/texelsubset.cpp
index c7fc64e..95dc059 100644
--- a/gm/texelsubset.cpp
+++ b/gm/texelsubset.cpp
@@ -134,9 +134,11 @@
GrSamplerState sampler(wmx, wmy, fFilter);
- // kRepeat and kMirrorRepeat don't currently work with filtering.
+ // kRepeat doesn't work with MIP map filtering yet.
bool shouldWork =
- fFilter == GrSamplerState::Filter::kNearest || !sampler.isRepeated();
+ fFilter != GrSamplerState::Filter::kMipMap ||
+ (sampler.wrapModeX() != GrSamplerState::WrapMode::kRepeat &&
+ sampler.wrapModeY() != GrSamplerState::WrapMode::kRepeat);
drawRect = localRect.makeOffset(x, y);
diff --git a/src/gpu/effects/GrTextureEffect.cpp b/src/gpu/effects/GrTextureEffect.cpp
index 7a16cc4..5f3ff77 100644
--- a/src/gpu/effects/GrTextureEffect.cpp
+++ b/src/gpu/effects/GrTextureEffect.cpp
@@ -15,125 +15,76 @@
#include "src/sksl/SkSLCPP.h"
#include "src/sksl/SkSLUtil.h"
-namespace {
-struct Span {
- float fA = 0.f, fB = 0.f;
-
- Span makeInset(float o) const {
- Span r = {fA + o, fB - o};
- if (r.fA > r.fB) {
- r.fA = r.fB = (r.fA + r.fB) / 2;
- }
- return r;
- }
-
- bool contains(Span r) const { return fA <= r.fA && fB >= r.fB; }
-};
-} // anonymous namespace
-
-GrTextureEffect::Sampling::Sampling(GrSamplerState sampler, SkISize size, const GrCaps& caps)
- : fHWSampler(sampler) {
- if (!caps.clampToBorderSupport()) {
- if (fHWSampler.wrapModeX() == GrSamplerState::WrapMode::kClampToBorder) {
- fHWSampler.setWrapModeX(GrSamplerState::WrapMode::kClamp);
- fShaderModes[0] = ShaderMode::kDecal;
- Span span{0, (float)size.width()};
- if (sampler.filter() != GrSamplerState::Filter::kNearest) {
- span = span.makeInset(0.5f);
- }
- fShaderSubset.fLeft = span.fA;
- fShaderSubset.fRight = span.fB;
- }
- if (fHWSampler.wrapModeY() == GrSamplerState::WrapMode::kClampToBorder) {
- fHWSampler.setWrapModeY(GrSamplerState::WrapMode::kClamp);
- fShaderModes[1] = ShaderMode::kDecal;
- Span span{0, (float)size.height()};
- if (sampler.filter() != GrSamplerState::Filter::kNearest) {
- span = span.makeInset(0.5f);
- }
- fShaderSubset.fTop = span.fA;
- fShaderSubset.fBottom = span.fB;
- }
- }
- if (!caps.npotTextureTileSupport()) {
- if (fHWSampler.wrapModeX() != GrSamplerState::WrapMode::kClamp && !SkIsPow2(size.width())) {
- fShaderModes[0] = static_cast<ShaderMode>(fHWSampler.wrapModeX());
- fHWSampler.setWrapModeX(GrSamplerState::WrapMode::kClamp);
- // We don't yet support shader based Mirror or Repeat with filtering.
- fHWSampler.setFilterMode(GrSamplerState::Filter::kNearest);
- fShaderSubset.fLeft = 0;
- fShaderSubset.fRight = size.width();
- }
- if (fHWSampler.wrapModeY() != GrSamplerState::WrapMode::kClamp &&
- !SkIsPow2(size.height())) {
- fShaderModes[1] = static_cast<ShaderMode>(fHWSampler.wrapModeY());
- fHWSampler.setWrapModeY(GrSamplerState::WrapMode::kClamp);
- fHWSampler.setFilterMode(GrSamplerState::Filter::kNearest);
- fShaderSubset.fTop = 0;
- fShaderSubset.fBottom = size.height();
- }
- }
-}
+using Mode = GrSamplerState::WrapMode;
+using Filter = GrSamplerState::Filter;
GrTextureEffect::Sampling::Sampling(const GrSurfaceProxy& proxy,
GrSamplerState sampler,
const SkRect& subset,
const SkRect* domain,
const GrCaps& caps) {
- using Mode = GrSamplerState::WrapMode;
- using Filter = GrSamplerState::Filter;
+ struct Span {
+ float fA = 0.f, fB = 0.f;
- struct Result1D {
- ShaderMode fShaderMode;
- Span fShaderSubset;
- Mode fHWMode;
- Filter fFilter;
- };
-
- auto resolve = [filter = sampler.filter(), &caps](int size, Mode mode, Span subset,
- Span domain) {
- Result1D r;
- r.fFilter = filter;
- bool canDoHW = (mode != Mode::kClampToBorder || caps.clampToBorderSupport()) &&
- (mode == Mode::kClamp || caps.npotTextureTileSupport() || SkIsPow2(size));
- if (canDoHW && size > 0 && subset.fA <= 0 && subset.fB >= size) {
- r.fShaderMode = ShaderMode::kNone;
- r.fHWMode = mode;
+ Span makeInset(float o) const {
+ Span r = {fA + o, fB - o};
+ if (r.fA > r.fB) {
+ r.fA = r.fB = (r.fA + r.fB) / 2;
+ }
return r;
}
+ bool contains(Span r) const { return fA <= r.fA && fB >= r.fB; }
+ };
+ struct Result1D {
+ ShaderMode fShaderMode;
+ Span fShaderSubset;
+ Span fShaderClamp;
+ Mode fHWMode;
+ };
+
+ auto type = proxy.asTextureProxy()->textureType();
+ auto filter = sampler.filter();
+
+ auto resolve = [type, &caps, filter](int size, Mode mode, Span subset, Span domain) {
+ Result1D r;
+ bool canDoHW = (mode != Mode::kClampToBorder || caps.clampToBorderSupport()) &&
+ (mode == Mode::kClamp || caps.npotTextureTileSupport() || SkIsPow2(size)) &&
+ (mode == Mode::kClamp || mode == Mode::kClampToBorder ||
+ type == GrTextureType::k2D);
+ if (canDoHW && size > 0 && subset.fA <= 0 && subset.fB >= size) {
+ r.fShaderMode = ShaderMode::kNone;
+ r.fHWMode = mode;
+ r.fShaderSubset = r.fShaderClamp = {0, 0};
+ return r;
+ }
+
+ r.fShaderSubset = subset;
bool domainIsSafe = false;
- Span insetSubset;
if (filter == Filter::kNearest) {
Span isubset{sk_float_floor(subset.fA), sk_float_ceil(subset.fB)};
if (domain.fA > isubset.fA && domain.fB < isubset.fB) {
domainIsSafe = true;
}
- if (mode == Mode::kClamp) {
- // This inset prevents sampling neighboring texels that could occur when
- // texture coords fall exactly at texel boundaries (depending on precision
- // and GPU-specific snapping at the boundary).
- insetSubset = isubset.makeInset(0.5f);
- } else {
- // TODO: Handle other modes properly in this case.
- insetSubset = subset;
+ // This inset prevents sampling neighboring texels that could occur when
+ // texture coords fall exactly at texel boundaries (depending on precision
+ // and GPU-specific snapping at the boundary).
+ r.fShaderClamp = isubset.makeInset(0.5f);
+ } else {
+ r.fShaderClamp = subset.makeInset(0.5f);
+ if (r.fShaderClamp.contains(domain)) {
+ domainIsSafe = true;
}
- } else {
- insetSubset = subset.makeInset(0.5f);
- domainIsSafe = insetSubset.contains(domain);
}
- if (canDoHW && domainIsSafe) {
+ if (domainIsSafe) {
+ // The domain of coords that will be used won't access texels outside of the subset.
+ // So the wrap mode effectively doesn't matter. We use kClamp since it is always
+ // supported.
r.fShaderMode = ShaderMode::kNone;
- r.fHWMode = mode;
+ r.fHWMode = Mode::kClamp;
+ r.fShaderSubset = r.fShaderClamp = {0, 0};
return r;
}
-
- if (mode == Mode::kRepeat || mode == Mode::kMirrorRepeat) {
- r.fFilter = Filter::kNearest;
- r.fShaderSubset = subset;
- } else {
- r.fShaderSubset = insetSubset;
- }
r.fShaderMode = static_cast<ShaderMode>(mode);
r.fHWMode = Mode::kClamp;
return r;
@@ -151,11 +102,20 @@
: Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
auto y = resolve(dim.height(), sampler.wrapModeY(), subsetY, domainY);
- fHWSampler = {x.fHWMode, y.fHWMode, std::min(x.fFilter, y.fFilter)};
+ if (filter == Filter::kMipMap && (x.fShaderMode == ShaderMode::kRepeat ||
+ y.fShaderMode == ShaderMode::kRepeat)) {
+ // Have not yet implemented necessary shader filter code to get LOD selection
+ // correct with kRepeat.
+ filter = Filter::kBilerp;
+ }
+
+ fHWSampler = {x.fHWMode, y.fHWMode, filter};
fShaderModes[0] = x.fShaderMode;
fShaderModes[1] = y.fShaderMode;
fShaderSubset = {x.fShaderSubset.fA, y.fShaderSubset.fA,
x.fShaderSubset.fB, y.fShaderSubset.fB};
+ fShaderClamp = {x.fShaderClamp.fA, y.fShaderClamp.fA,
+ x.fShaderClamp.fB, y.fShaderClamp.fB};
}
bool GrTextureEffect::Sampling::usesDecal() const {
@@ -167,7 +127,7 @@
std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(GrSurfaceProxyView view,
SkAlphaType alphaType,
const SkMatrix& matrix,
- GrSamplerState::Filter filter) {
+ Filter filter) {
return std::unique_ptr<GrFragmentProcessor>(
new GrTextureEffect(std::move(view), alphaType, matrix, Sampling(filter)));
}
@@ -177,7 +137,8 @@
const SkMatrix& matrix,
GrSamplerState sampler,
const GrCaps& caps) {
- Sampling sampling(sampler, view.proxy()->dimensions(), caps);
+ Sampling sampling(*view.proxy(), sampler, SkRect::Make(view.proxy()->dimensions()), nullptr,
+ caps);
return std::unique_ptr<GrFragmentProcessor>(
new GrTextureEffect(std::move(view), alphaType, matrix, sampling));
}
@@ -228,10 +189,36 @@
new GrTextureEffect(std::move(view), alphaType, matrix, sampling));
}
+GrTextureEffect::FilterLogic GrTextureEffect::GetFilterLogic(ShaderMode mode,
+ GrSamplerState::Filter filter) {
+ switch (mode) {
+ case ShaderMode::kMirrorRepeat:
+ case ShaderMode::kNone:
+ case ShaderMode::kClamp:
+ return FilterLogic::kNone;
+ case ShaderMode::kRepeat:
+ switch (filter) {
+ case GrSamplerState::Filter::kNearest:
+ return FilterLogic::kNone;
+ case GrSamplerState::Filter::kBilerp:
+ return FilterLogic::kRepeatBilerp;
+ case GrSamplerState::Filter::kMipMap:
+ // return FilterLogic::kRepeatMipMap;
+ SkUNREACHABLE;
+ }
+ SkUNREACHABLE;
+ case ShaderMode::kDecal:
+ return filter > GrSamplerState::Filter::kNearest ? FilterLogic::kDecalFilter
+ : FilterLogic::kDecalNearest;
+ }
+ SkUNREACHABLE;
+}
+
GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const {
class Impl : public GrGLSLFragmentProcessor {
UniformHandle fSubsetUni;
- UniformHandle fDecalUni;
+ UniformHandle fClampUni;
+ UniformHandle fNormUni;
public:
void emitCode(EmitArgs& args) override {
@@ -250,38 +237,106 @@
args.fTexSamplers[0], coords);
fb->codeAppendf(";");
} else {
- const char* subsetName;
- SkString uniName("TexDom");
- fSubsetUni = args.fUniformHandler->addUniform(
- kFragment_GrShaderFlag, kHalf4_GrSLType, "subset", &subsetName);
+ // Here is the basic flow of the various ShaderModes are implemented in a series of
+ // steps. Not all the steps apply to all the modes. We try to emit only the steps
+ // that are necessary for the given x/y shader modes.
+ //
+ // 0) Start with interpolated coordinates (unnormalize if doing anything
+ // complicated).
+ // 1) Map the coordinates into the subset range [Repeat and MirrorRepeat], or pass
+ // through output of 0).
+ // 2) Clamp the coordinates to a 0.5 inset of the subset rect [Clamp, Repeat, and
+ // MirrorRepeat always or Decal only when filtering] or pass through output of
+ // 1). The clamp rect collapses to a line or point it if the subset rect is less
+ // than one pixel wide/tall.
+ // 3) Look up texture with output of 2) [All]
+ // 3) Use the difference between 1) and 2) to apply filtering at edge [Repeat or
+ // Decal]. In the Repeat case this requires extra texture lookups on the other
+ // side of the subset (up to 3 more reads). Or if Decal and not filtering
+ // do a hard less than/greater than test with the subset rect.
- // 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
+ // Convert possible projective texture coordinates into non-homogeneous half2.
fb->codeAppendf(
"float2 inCoord = %s;",
fb->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint).c_str());
- fb->codeAppend("float2 clampedCoord;");
- auto subsetCoord = [fb, subsetName](ShaderMode mode, const char* coordSwizzle,
+ const auto& m = te.fShaderModes;
+ const auto* texture = te.fSampler.proxy()->peekTexture();
+ bool normCoords = texture->texturePriv().textureType() != GrTextureType::kRectangle;
+ auto filter = te.fSampler.samplerState().filter();
+ FilterLogic filterLogic[2] = {GetFilterLogic(m[0], filter),
+ GetFilterLogic(m[1], filter)};
+
+ auto modeUsesSubset = [](ShaderMode m) {
+ return m == ShaderMode::kRepeat || m == ShaderMode::kMirrorRepeat ||
+ m == ShaderMode::kDecal;
+ };
+
+ auto modeUsesClamp = [filter](ShaderMode m) {
+ return m != ShaderMode::kNone &&
+ (m != ShaderMode::kDecal || filter > Filter::kNearest);
+ };
+
+ bool useSubset[2] = {modeUsesSubset(m[0]), modeUsesSubset(m[1])};
+ bool useClamp [2] = {modeUsesClamp (m[0]), modeUsesClamp (m[1])};
+
+ const char* subsetName = nullptr;
+ if (useSubset[0] || useSubset[1]) {
+ fSubsetUni = args.fUniformHandler->addUniform(
+ kFragment_GrShaderFlag, kFloat4_GrSLType, "subset", &subsetName);
+ }
+
+ const char* clampName = nullptr;
+ if (useClamp[0] || useClamp[1]) {
+ fClampUni = args.fUniformHandler->addUniform(
+ kFragment_GrShaderFlag, kFloat4_GrSLType, "clamp", &clampName);
+ }
+
+ // To keep things a little simpler, when we have filtering logic in the shader we
+ // operate on unnormalized texture coordinates. We add a uniform that stores
+ // {w, h, 1/w, 1/h} in a float4.
+ const char* norm = nullptr;
+ if (normCoords && (filterLogic[0] != FilterLogic::kNone ||
+ filterLogic[1] != FilterLogic::kNone)) {
+ // TODO: Detect support for textureSize() or polyfill textureSize() in SkSL and
+ // always use?
+ fNormUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+ kFloat4_GrSLType, "norm", &norm);
+ // TODO: Remove the normalization from the CoordTransform to skip unnormalizing
+ // step here.
+ fb->codeAppendf("inCoord *= %s.xy;", norm);
+ }
+
+ // Generates a string to read at a coordinate, normalizing coords if necessary.
+ auto read = [fb, norm, &sampler = args.fTexSamplers[0]](const char* coord) {
+ SkString result;
+ SkString normCoord;
+ if (norm) {
+ normCoord.printf("(%s) * %s.zw", coord, norm);
+ } else {
+ normCoord = coord;
+ }
+ fb->appendTextureLookup(&result, sampler, normCoord.c_str());
+ return result;
+ };
+
+ // Implements coord wrapping for kRepeat and kMirrorRepeat
+ auto subsetCoord = [fb, subsetName](ShaderMode mode,
+ const char* coordSwizzle,
const char* subsetStartSwizzle,
const char* subsetStopSwizzle) {
switch (mode) {
+ // These modes either don't use the subset rect or don't need to map the
+ // coords to be within the subset.
case ShaderMode::kNone:
- fb->codeAppendf("clampedCoord.%s = inCoord.%s;\n", coordSwizzle,
- coordSwizzle);
- break;
case ShaderMode::kDecal:
- // 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 ShaderMode::kClamp:
- fb->codeAppendf("clampedCoord.%s = clamp(inCoord.%s, %s.%s, %s.%s);",
- coordSwizzle, coordSwizzle, subsetName,
- subsetStartSwizzle, subsetName, subsetStopSwizzle);
+ fb->codeAppendf("subsetCoord.%s = inCoord.%s;", coordSwizzle,
+ coordSwizzle);
break;
case ShaderMode::kRepeat:
fb->codeAppendf(
- "clampedCoord.%s = mod(inCoord.%s - %s.%s, %s.%s - %s.%s) + "
+ "subsetCoord.%s = mod(inCoord.%s - %s.%s, %s.%s - %s.%s) + "
"%s.%s;",
coordSwizzle, coordSwizzle, subsetName, subsetStartSwizzle,
subsetName, subsetStopSwizzle, subsetName, subsetStartSwizzle,
@@ -294,7 +349,7 @@
fb->codeAppendf("float w2 = 2 * w;");
fb->codeAppendf("float m = mod(inCoord.%s - %s.%s, w2);", coordSwizzle,
subsetName, subsetStartSwizzle);
- fb->codeAppendf("clampedCoord.%s = mix(m, w2 - m, step(w, m)) + %s.%s;",
+ fb->codeAppendf("subsetCoord.%s = mix(m, w2 - m, step(w, m)) + %s.%s;",
coordSwizzle, subsetName, subsetStartSwizzle);
fb->codeAppend("}");
break;
@@ -302,50 +357,112 @@
}
};
+ auto clampCoord = [fb, clampName](bool clamp,
+ const char* coordSwizzle,
+ const char* clampStartSwizzle,
+ const char* clampStopSwizzle) {
+ if (clamp) {
+ fb->codeAppendf("clampedCoord.%s = clamp(subsetCoord.%s, %s.%s, %s.%s);",
+ coordSwizzle, coordSwizzle, clampName, clampStartSwizzle,
+ clampName, clampStopSwizzle);
+ } else {
+ fb->codeAppendf("clampedCoord.%s = subsetCoord.%s;", coordSwizzle,
+ coordSwizzle);
+ }
+ };
+
+ fb->codeAppend("float2 subsetCoord;");
subsetCoord(te.fShaderModes[0], "x", "x", "z");
subsetCoord(te.fShaderModes[1], "y", "y", "w");
- SkString textureLookup;
- fb->appendTextureLookup(&textureLookup, args.fTexSamplers[0], "clampedCoord");
- fb->codeAppendf("half4 textureColor = %s;", textureLookup.c_str());
+ fb->codeAppend("float2 clampedCoord;");
+ clampCoord(useClamp[0], "x", "x", "z");
+ clampCoord(useClamp[1], "y", "y", "w");
- // Apply decal mode's transparency interpolation if needed
- bool decalX = te.fShaderModes[0] == ShaderMode::kDecal;
- bool decalY = te.fShaderModes[1] == ShaderMode::kDecal;
- if (decalX || decalY) {
- const char* decalName;
- // Half3 since this will hold texture width, height, and then a step function
- // control param
- fDecalUni = args.fUniformHandler->addUniform(
- kFragment_GrShaderFlag, kHalf3_GrSLType, uniName.c_str(), &decalName);
- // The decal err is the max absolute 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) {
- fb->codeAppendf(
- "half err = max(half(abs(clampedCoord.x - inCoord.x) * %s.x), "
- " half(abs(clampedCoord.y - inCoord.y) * %s.y));",
- decalName, decalName);
- } else if (decalX) {
- fb->codeAppendf("half err = half(abs(clampedCoord.x - inCoord.x) * %s.x);",
- decalName);
- } else {
- SkASSERT(decalY);
- fb->codeAppendf("half err = half(abs(clampedCoord.y - inCoord.y) * %s.y);",
- decalName);
- }
+ fb->codeAppendf("half4 textureColor = %s;", read("clampedCoord").c_str());
- // 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 0 so this becomes a step function centered at the
- // clamped coordinate. When bilerp, fSizeName.z is set to 1 and it becomes
- // a simple linear blend between texture and transparent.
- fb->codeAppendf(
- "if (err > %s.z) { err = 1.0; } else if (%s.z < 1) { err = 0.0; }",
- decalName, decalName);
- fb->codeAppend("textureColor = mix(textureColor, half4(0), err);");
+ // Strings for extra texture reads used only in kRepeatBilerp
+ SkString repeatBilerpReadX;
+ SkString repeatBilerpReadY;
+
+ // Calculate the amount the coord moved for clamping. This will be used
+ // to implement shader-based filtering for kDecal and kRepeat.
+
+ if (filterLogic[0] == FilterLogic::kRepeatBilerp ||
+ filterLogic[0] == FilterLogic::kDecalFilter) {
+ fb->codeAppend("half errX = half(subsetCoord.x - clampedCoord.x);");
+ fb->codeAppendf("float repeatCoordX = errX > 0 ? %s.x : %s.z;", clampName,
+ clampName);
+ repeatBilerpReadX = read("float2(repeatCoordX, clampedCoord.y)");
}
- fb->codeAppendf("%s = textureColor * %s;", args.fOutputColor, args.fInputColor);
+ if (filterLogic[1] == FilterLogic::kRepeatBilerp ||
+ filterLogic[1] == FilterLogic::kDecalFilter) {
+ fb->codeAppend("half errY = half(subsetCoord.y - clampedCoord.y);");
+ fb->codeAppendf("float repeatCoordY = errY > 0 ? %s.y : %s.w;", clampName,
+ clampName);
+ repeatBilerpReadY = read("float2(clampedCoord.x, repeatCoordY)");
+ }
+
+ // Add logic for kRepeatBilerp. Do 1 or 3 more texture reads depending
+ // on whether both modes are kRepeat and whether we're near a single subset edge
+ // or a corner. Then blend the multiple reads using the err values calculated
+ // above.
+ const char* ifStr = "if";
+ if (filterLogic[0] == FilterLogic::kRepeatBilerp &&
+ filterLogic[1] == FilterLogic::kRepeatBilerp) {
+ auto repeatBilerpReadXY = read("float2(repeatCoordX, repeatCoordY)");
+ fb->codeAppendf(
+ "if (errX != 0 && errY != 0) {"
+ " textureColor = mix(mix(textureColor, %s, errX),"
+ " mix(%s, %s, errX),"
+ " errY);"
+ "}",
+ repeatBilerpReadX.c_str(), repeatBilerpReadY.c_str(),
+ repeatBilerpReadXY.c_str());
+ ifStr = "else if";
+ }
+ if (filterLogic[0] == FilterLogic::kRepeatBilerp) {
+ fb->codeAppendf(
+ "%s (errX != 0) {"
+ " textureColor = mix(textureColor, %s, abs(errX));"
+ "}",
+ ifStr, repeatBilerpReadX.c_str());
+ }
+ if (filterLogic[1] == FilterLogic::kRepeatBilerp) {
+ fb->codeAppendf(
+ "%s (errY != 0) {"
+ " textureColor = mix(textureColor, %s, abs(errY));"
+ "}",
+ ifStr, repeatBilerpReadY.c_str());
+ }
+
+ // Do soft edge shader filtering against transparent black for kDecalFilter using
+ // the err values calculated above.
+ if (filterLogic[0] == FilterLogic::kDecalFilter) {
+ fb->codeAppendf(
+ "textureColor = mix(textureColor, half4(0), min(abs(errX), 1));");
+ }
+ if (filterLogic[1] == FilterLogic::kDecalFilter) {
+ fb->codeAppendf(
+ "textureColor = mix(textureColor, half4(0), min(abs(errY), 1));");
+ }
+
+ // Do hard-edge shader transition to transparent black for kDecalNearest at the
+ // subset boundaries.
+ if (filterLogic[0] == FilterLogic::kDecalNearest) {
+ fb->codeAppendf(
+ "if (inCoord.x < %s.x || inCoord.x > %s.z) {"
+ " textureColor = half4(0);"
+ "}",
+ subsetName, subsetName);
+ }
+ if (filterLogic[1] == FilterLogic::kDecalNearest) {
+ fb->codeAppendf(
+ "if (inCoord.y < %s.y || inCoord.y > %s.w) {"
+ " textureColor = half4(0);"
+ "}",
+ subsetName, subsetName);
+ }
+ fb->codeAppendf("%s = %s * textureColor;", args.fOutputColor, args.fInputColor);
}
}
@@ -353,42 +470,43 @@
void onSetData(const GrGLSLProgramDataManager& pdm,
const GrFragmentProcessor& fp) override {
const auto& te = fp.cast<GrTextureEffect>();
- if (fSubsetUni.isValid()) {
- const float w = te.fSampler.peekTexture()->width();
- const float h = te.fSampler.peekTexture()->height();
- const auto& s = te.fSubset;
- float rect[] = {s.fLeft, s.fTop, s.fRight, s.fBottom};
- float decalW[3];
+ const float w = te.fSampler.peekTexture()->width();
+ const float h = te.fSampler.peekTexture()->height();
+ const auto& s = te.fSubset;
+ const auto& c = te.fClamp;
+ auto type = te.fSampler.peekTexture()->texturePriv().textureType();
+
+ float norm[4] = {w, h, 1.f/w, 1.f/h};
+
+ if (fNormUni.isValid()) {
+ pdm.set4fv(fNormUni, 1, norm);
+ SkASSERT(type != GrTextureType::kRectangle);
+ }
+
+ auto pushRect = [&](float rect[4], UniformHandle uni) {
if (te.fSampler.view().origin() == kBottomLeft_GrSurfaceOrigin) {
rect[1] = h - rect[1];
rect[3] = h - rect[3];
std::swap(rect[1], rect[3]);
}
-
- if (te.fSampler.peekTexture()->texturePriv().textureType() !=
- GrTextureType::kRectangle) {
- float iw = 1.f / w;
- float ih = 1.f / h;
- rect[0] *= iw;
- rect[2] *= iw;
- rect[1] *= ih;
- rect[3] *= ih;
- decalW[0] = w;
- decalW[1] = h;
- } else {
- decalW[0] = 1;
- decalW[1] = 1;
+ if (!fNormUni.isValid() && type != GrTextureType::kRectangle) {
+ rect[0] *= norm[2];
+ rect[2] *= norm[2];
+ rect[1] *= norm[3];
+ rect[3] *= norm[3];
}
- pdm.set4fv(fSubsetUni, 1, rect);
+ pdm.set4fv(uni, 1, rect);
+ };
- if (fDecalUni.isValid()) {
- bool filter = te.textureSampler(0).samplerState().filter() !=
- GrSamplerState::Filter::kNearest;
- decalW[2] = filter ? 1.f : 0.f;
- pdm.set3fv(fDecalUni, 1, decalW);
- }
+ if (fSubsetUni.isValid()) {
+ float subset[] = {s.fLeft, s.fTop, s.fRight, s.fBottom};
+ pushRect(subset, fSubsetUni);
+ }
+ if (fClampUni.isValid()) {
+ float subset[] = {c.fLeft, c.fTop, c.fRight, c.fBottom};
+ pushRect(subset, fClampUni);
}
}
};
@@ -396,12 +514,12 @@
}
void GrTextureEffect::onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const {
- bool shaderFilter = (fShaderModes[0] == ShaderMode::kDecal ||
- fShaderModes[1] == ShaderMode::kDecal) &&
- fSampler.samplerState().filter() != GrSamplerState::Filter::kNearest;
auto m0 = static_cast<uint32_t>(fShaderModes[0]);
auto m1 = static_cast<uint32_t>(fShaderModes[1]);
- b->add32(shaderFilter << 31 | (m0 << 16) | m1);
+ auto filter = fSampler.samplerState().filter();
+ auto l0 = static_cast<uint32_t>(GetFilterLogic(fShaderModes[0], filter));
+ auto l1 = static_cast<uint32_t>(GetFilterLogic(fShaderModes[1], filter));
+ b->add32((l0 << 24) | (l1 << 16) | (m0 << 8) | m1);
}
bool GrTextureEffect::onIsEqual(const GrFragmentProcessor& other) const {
@@ -417,6 +535,7 @@
, fCoordTransform(matrix, view.proxy(), view.origin())
, fSampler(std::move(view), sampling.fHWSampler)
, fSubset(sampling.fShaderSubset)
+ , fClamp(sampling.fShaderClamp)
, fShaderModes{sampling.fShaderModes[0], sampling.fShaderModes[1]} {
// We always compare the range even when it isn't used so assert we have canonical don't care
// values.
@@ -448,18 +567,26 @@
#if GR_TEST_UTILS
std::unique_ptr<GrFragmentProcessor> GrTextureEffect::TestCreate(GrProcessorTestData* testData) {
auto [view, ct, at] = testData->randomView();
- GrSamplerState::WrapMode wrapModes[2];
+ Mode wrapModes[2];
GrTest::TestWrapModes(testData->fRandom, wrapModes);
- if (!testData->caps()->npotTextureTileSupport()) {
- // Performing repeat sampling on npot textures will cause asserts on HW
- // that lacks support.
- wrapModes[0] = GrSamplerState::WrapMode::kClamp;
- wrapModes[1] = GrSamplerState::WrapMode::kClamp;
- }
- GrSamplerState params(wrapModes, testData->fRandom->nextBool()
- ? GrSamplerState::Filter::kBilerp
- : GrSamplerState::Filter::kNearest);
+ Filter filter;
+ if (view.asTextureProxy()->mipMapped() == GrMipMapped::kYes) {
+ switch (testData->fRandom->nextULessThan(3)) {
+ case 0:
+ filter = Filter::kNearest;
+ break;
+ case 1:
+ filter = Filter::kBilerp;
+ break;
+ default:
+ filter = Filter::kMipMap;
+ break;
+ }
+ } else {
+ filter = testData->fRandom->nextBool() ? Filter::kBilerp : Filter::kNearest;
+ }
+ GrSamplerState params(wrapModes, filter);
const SkMatrix& matrix = GrTest::TestMatrix(testData->fRandom);
return GrTextureEffect::Make(std::move(view), at, matrix, params, *testData->caps());
diff --git a/src/gpu/effects/GrTextureEffect.h b/src/gpu/effects/GrTextureEffect.h
index 85bad15..3f8dd18 100644
--- a/src/gpu/effects/GrTextureEffect.h
+++ b/src/gpu/effects/GrTextureEffect.h
@@ -100,8 +100,8 @@
GrSamplerState fHWSampler;
ShaderMode fShaderModes[2] = {ShaderMode::kNone, ShaderMode::kNone};
SkRect fShaderSubset = {0, 0, 0, 0};
+ SkRect fShaderClamp = {0, 0, 0, 0};
Sampling(GrSamplerState::Filter filter) : fHWSampler(filter) {}
- Sampling(GrSamplerState, SkISize, const GrCaps&);
Sampling(const GrSurfaceProxy& proxy,
GrSamplerState sampler,
const SkRect&,
@@ -110,9 +110,23 @@
inline bool usesDecal() const;
};
+ /**
+ * Sometimes the implementation of a ShaderMode depends on which GrSamplerState::Filter is
+ * used.
+ */
+ enum class FilterLogic {
+ kNone, // The shader isn't specialized for the filter.
+ kRepeatBilerp, // Filter across the subset boundary for kRepeat mode
+ // kRepeatMipMap, // Logic for LOD selection with kRepeat mode. Not yet implemented.
+ kDecalFilter, // Logic for fading to transparent black when filtering with kDecal.
+ kDecalNearest, // Logic for hard transition to transparent black when not filtering.
+ };
+ static FilterLogic GetFilterLogic(ShaderMode mode, GrSamplerState::Filter filter);
+
GrCoordTransform fCoordTransform;
TextureSampler fSampler;
SkRect fSubset;
+ SkRect fClamp;
ShaderMode fShaderModes[2];
inline GrTextureEffect(GrSurfaceProxyView, SkAlphaType, const SkMatrix&, const Sampling&);