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>
This commit is contained in:
Brian Salomon 2020-02-13 13:54:24 -05:00 committed by Skia Commit-Bot
parent f9734c39b8
commit 53c909e05f
3 changed files with 347 additions and 204 deletions

View File

@ -134,9 +134,11 @@ protected:
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);

View File

@ -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 @@ GrTextureEffect::Sampling::Sampling(const GrSurfaceProxy& proxy,
: 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 @@ bool GrTextureEffect::Sampling::usesDecal() const {
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 @@ std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(GrSurfaceProxyView vi
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 @@ std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeSubset(GrSurfaceProxyV
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 @@ GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const {
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 @@ GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const {
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 @@ GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const {
}
};
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 @@ GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const {
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 @@ GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const {
}
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 @@ GrTextureEffect::GrTextureEffect(GrSurfaceProxyView view, SkAlphaType alphaType,
, 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 @@ GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTextureEffect);
#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());

View File

@ -100,8 +100,8 @@ private:
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 @@ private:
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&);