blob: 3ae9c73c1efc858e34a79ded7274bdcb16e77258 [file] [log] [blame]
/*
* 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 "src/gpu/effects/GrTextureEffect.h"
#include "include/gpu/GrTexture.h"
#include "src/gpu/GrTexturePriv.h"
#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
#include "src/sksl/SkSLCPP.h"
#include "src/sksl/SkSLUtil.h"
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) {
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; }
};
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;
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;
}
// 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;
}
}
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::kClamp;
r.fShaderSubset = r.fShaderClamp = {0, 0};
return r;
}
r.fShaderMode = static_cast<ShaderMode>(mode);
r.fHWMode = Mode::kClamp;
return r;
};
SkISize dim = proxy.isFullyLazy() ? SkISize{-1, -1} : proxy.backingStoreDimensions();
Span subsetX{subset.fLeft, subset.fRight};
auto domainX = domain ? Span{domain->fLeft, domain->fRight}
: Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
auto x = resolve(dim.width(), sampler.wrapModeX(), subsetX, domainX);
Span subsetY{subset.fTop, subset.fBottom};
auto domainY = domain ? Span{domain->fTop, domain->fBottom}
: Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
auto y = resolve(dim.height(), sampler.wrapModeY(), subsetY, domainY);
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 {
return fShaderModes[0] == ShaderMode::kDecal || fShaderModes[1] == ShaderMode::kDecal ||
fHWSampler.wrapModeX() == GrSamplerState::WrapMode::kClampToBorder ||
fHWSampler.wrapModeY() == GrSamplerState::WrapMode::kClampToBorder;
}
std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(GrSurfaceProxyView view,
SkAlphaType alphaType,
const SkMatrix& matrix,
Filter filter) {
return std::unique_ptr<GrFragmentProcessor>(
new GrTextureEffect(std::move(view), alphaType, matrix, Sampling(filter)));
}
std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(GrSurfaceProxyView view,
SkAlphaType alphaType,
const SkMatrix& matrix,
GrSamplerState sampler,
const GrCaps& 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));
}
std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeSubset(GrSurfaceProxyView view,
SkAlphaType alphaType,
const SkMatrix& matrix,
GrSamplerState sampler,
const SkRect& subset,
const GrCaps& caps) {
Sampling sampling(*view.proxy(), sampler, subset, nullptr, caps);
return std::unique_ptr<GrFragmentProcessor>(
new GrTextureEffect(std::move(view), alphaType, matrix, sampling));
}
std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeSubset(GrSurfaceProxyView view,
SkAlphaType alphaType,
const SkMatrix& matrix,
GrSamplerState sampler,
const SkRect& subset,
const SkRect& domain,
const GrCaps& caps) {
Sampling sampling(*view.proxy(), sampler, subset, &domain, caps);
return std::unique_ptr<GrFragmentProcessor>(
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;
case ShaderMode::kDecal:
return filter > GrSamplerState::Filter::kNearest ? FilterLogic::kDecalFilter
: FilterLogic::kDecalNearest;
}
SkUNREACHABLE;
}
GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const {
class Impl : public GrGLSLFragmentProcessor {
UniformHandle fSubsetUni;
UniformHandle fClampUni;
UniformHandle fNormUni;
public:
void emitCode(EmitArgs& args) override {
auto te = args.fFp.cast<GrTextureEffect>();
const char* coords;
if (args.fFp.coordTransformsApplyToLocalCoords()) {
coords = args.fTransformedCoords[0].fVaryingPoint.c_str();
} else {
coords = "_coords";
}
auto* fb = args.fFragBuilder;
if (te.fShaderModes[0] == ShaderMode::kNone &&
te.fShaderModes[1] == ShaderMode::kNone) {
fb->codeAppendf("%s = ", args.fOutputColor);
fb->appendTextureLookupAndBlend(args.fInputColor, SkBlendMode::kModulate,
args.fTexSamplers[0], coords);
fb->codeAppendf(";");
} else {
// 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.
// Convert possible projective texture coordinates into non-homogeneous half2.
fb->codeAppendf(
"float2 inCoord = %s;",
fb->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint).c_str());
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 = [&](const char* coord) {
SkString result;
SkString normCoord;
if (norm) {
normCoord.printf("(%s) * %s.zw", coord, norm);
} else {
normCoord = coord;
}
fb->appendTextureLookup(&result, args.fTexSamplers[0], normCoord.c_str());
return result;
};
// Implements coord wrapping for kRepeat and kMirrorRepeat
auto subsetCoord = [&](ShaderMode mode,
const char* coordSwizzle,
const char* subsetStartSwizzle,
const char* subsetStopSwizzle,
const char* extraCoord,
const char* coordWeight) {
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:
case ShaderMode::kDecal:
case ShaderMode::kClamp:
fb->codeAppendf("subsetCoord.%s = inCoord.%s;", coordSwizzle,
coordSwizzle);
break;
case ShaderMode::kRepeat:
if (filter == Filter::kMipMap) {
// The approach here is to generate two sets of texture coords that
// are both "moving" at the same speed (if not direction) as
// inCoords. We accomplish that by using two out of phase mirror
// repeat coords. We will always sample using both coords but the
// read from the upward sloping one is selected using a weight
// that transitions from one set to the other near the reflection
// point. Like the coords, the weight is a saw-tooth function,
// phase-shifted, vertically translated, and then clamped to 0..1.
// TODO: Skip this and use textureGrad() when available.
SkASSERT(extraCoord);
SkASSERT(coordWeight);
fb->codeAppend("{");
fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName,
subsetStopSwizzle, subsetName, subsetStartSwizzle);
fb->codeAppendf("float w2 = 2 * w;");
fb->codeAppendf("float d = inCoord.%s - %s.%s;", coordSwizzle,
subsetName, subsetStartSwizzle);
fb->codeAppend("float m = mod(d, w2);");
fb->codeAppend("float o = mix(m, w2 - m, step(w, m));");
fb->codeAppendf("subsetCoord.%s = o + %s.%s;", coordSwizzle,
subsetName, subsetStartSwizzle);
fb->codeAppendf("%s = w - o + %s.%s;", extraCoord, subsetName,
subsetStartSwizzle);
// coordWeight is used as the third param of mix() to blend between a
// sample taken using subsetCoord and a sample at extraCoord.
fb->codeAppend("float hw = w/2;");
fb->codeAppend("float n = mod(d - hw, w2);");
fb->codeAppendf(
"%s = saturate(half(mix(n, w2 - n, step(w, n)) - hw + "
"0.5));",
coordWeight);
fb->codeAppend("}");
} else {
fb->codeAppendf(
"subsetCoord.%s = mod(inCoord.%s - %s.%s, %s.%s - %s.%s) + "
"%s.%s;",
coordSwizzle, coordSwizzle, subsetName, subsetStartSwizzle,
subsetName, subsetStopSwizzle, subsetName,
subsetStartSwizzle, subsetName, subsetStartSwizzle);
}
break;
case ShaderMode::kMirrorRepeat: {
fb->codeAppend("{");
fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName,
subsetStopSwizzle, subsetName, subsetStartSwizzle);
fb->codeAppendf("float w2 = 2 * w;");
fb->codeAppendf("float m = mod(inCoord.%s - %s.%s, w2);", coordSwizzle,
subsetName, subsetStartSwizzle);
fb->codeAppendf("subsetCoord.%s = mix(m, w2 - m, step(w, m)) + %s.%s;",
coordSwizzle, subsetName, subsetStartSwizzle);
fb->codeAppend("}");
break;
}
}
};
auto clampCoord = [&](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);
}
};
// Insert vars for extra coords and blending weights for kRepeatMipMap.
const char* extraRepeatCoordX = nullptr;
const char* repeatCoordWeightX = nullptr;
const char* extraRepeatCoordY = nullptr;
const char* repeatCoordWeightY = nullptr;
if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
fb->codeAppend("float extraRepeatCoordX; half repeatCoordWeightX;");
extraRepeatCoordX = "extraRepeatCoordX";
repeatCoordWeightX = "repeatCoordWeightX";
}
if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
fb->codeAppend("float extraRepeatCoordY; half repeatCoordWeightY;");
extraRepeatCoordY = "extraRepeatCoordY";
repeatCoordWeightY = "repeatCoordWeightY";
}
// Apply subset rect and clamp rect to coords.
fb->codeAppend("float2 subsetCoord;");
subsetCoord(te.fShaderModes[0], "x", "x", "z", extraRepeatCoordX,
repeatCoordWeightX);
subsetCoord(te.fShaderModes[1], "y", "y", "w", extraRepeatCoordY,
repeatCoordWeightY);
fb->codeAppend("float2 clampedCoord;");
clampCoord(useClamp[0], "x", "x", "z");
clampCoord(useClamp[1], "y", "y", "w");
// Additional clamping for the extra coords for kRepeatMipMap.
if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
fb->codeAppendf("extraRepeatCoordX = clamp(extraRepeatCoordX, %s.x, %s.z);",
clampName, clampName);
}
if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
fb->codeAppendf("extraRepeatCoordY = clamp(extraRepeatCoordY, %s.y, %s.w);",
clampName, clampName);
}
// Do the 2 or 4 texture reads for kRepeatMipMap and then apply the weight(s)
// to blend between them. If neither direction is kRepeatMipMap do a single
// read at clampedCoord.
if (filterLogic[0] == FilterLogic::kRepeatMipMap &&
filterLogic[1] == FilterLogic::kRepeatMipMap) {
fb->codeAppendf(
"half4 textureColor ="
" mix(mix(%s, %s, repeatCoordWeightX),"
" mix(%s, %s, repeatCoordWeightX),"
" repeatCoordWeightY);",
read("clampedCoord").c_str(),
read("float2(extraRepeatCoordX, clampedCoord.y)").c_str(),
read("float2(clampedCoord.x, extraRepeatCoordY)").c_str(),
read("float2(extraRepeatCoordX, extraRepeatCoordY)").c_str());
} else if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightX);",
read("clampedCoord").c_str(),
read("float2(extraRepeatCoordX, clampedCoord.y)").c_str());
} else if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightY);",
read("clampedCoord").c_str(),
read("float2(clampedCoord.x, extraRepeatCoordY)").c_str());
} else {
fb->codeAppendf("half4 textureColor = %s;", read("clampedCoord").c_str());
}
// 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)");
}
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);
}
}
protected:
void onSetData(const GrGLSLProgramDataManager& pdm,
const GrFragmentProcessor& fp) override {
const auto& te = fp.cast<GrTextureEffect>();
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 (!fNormUni.isValid() && type != GrTextureType::kRectangle) {
rect[0] *= norm[2];
rect[2] *= norm[2];
rect[1] *= norm[3];
rect[3] *= norm[3];
}
pdm.set4fv(uni, 1, rect);
};
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);
}
}
};
return new Impl;
}
void GrTextureEffect::onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const {
auto m0 = static_cast<uint32_t>(fShaderModes[0]);
auto m1 = static_cast<uint32_t>(fShaderModes[1]);
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 {
auto that = other.cast<GrTextureEffect>();
return fShaderModes[0] == that.fShaderModes[1] && fShaderModes[1] == that.fShaderModes[1] &&
fSubset == that.fSubset;
}
GrTextureEffect::GrTextureEffect(GrSurfaceProxyView view, SkAlphaType alphaType,
const SkMatrix& matrix, const Sampling& sampling)
: GrFragmentProcessor(kGrTextureEffect_ClassID,
ModulateForSamplerOptFlags(alphaType, sampling.usesDecal()))
, 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.
SkASSERT(fShaderModes[0] != ShaderMode::kNone || (fSubset.fLeft == 0 && fSubset.fRight == 0));
SkASSERT(fShaderModes[1] != ShaderMode::kNone || (fSubset.fTop == 0 && fSubset.fBottom == 0));
this->setTextureSamplerCnt(1);
this->addCoordTransform(&fCoordTransform);
}
GrTextureEffect::GrTextureEffect(const GrTextureEffect& src)
: INHERITED(kGrTextureEffect_ClassID, src.optimizationFlags())
, fCoordTransform(src.fCoordTransform)
, fSampler(src.fSampler)
, fSubset(src.fSubset)
, fShaderModes{src.fShaderModes[0], src.fShaderModes[1]} {
this->setTextureSamplerCnt(1);
this->addCoordTransform(&fCoordTransform);
}
std::unique_ptr<GrFragmentProcessor> GrTextureEffect::clone() const {
return std::unique_ptr<GrFragmentProcessor>(new GrTextureEffect(*this));
}
const GrFragmentProcessor::TextureSampler& GrTextureEffect::onTextureSampler(int) const {
return fSampler;
}
GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTextureEffect);
#if GR_TEST_UTILS
std::unique_ptr<GrFragmentProcessor> GrTextureEffect::TestCreate(GrProcessorTestData* testData) {
auto [view, ct, at] = testData->randomView();
Mode wrapModes[2];
GrTest::TestWrapModes(testData->fRandom, wrapModes);
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());
}
#endif