1#include "GrBicubicEffect.h"
2
3#define DS(x) SkDoubleToScalar(x)
4
5const SkScalar GrBicubicEffect::gMitchellCoefficients[16] = {
6    DS( 1.0 / 18.0), DS(-9.0 / 18.0), DS( 15.0 / 18.0), DS( -7.0 / 18.0),
7    DS(16.0 / 18.0), DS( 0.0 / 18.0), DS(-36.0 / 18.0), DS( 21.0 / 18.0),
8    DS( 1.0 / 18.0), DS( 9.0 / 18.0), DS( 27.0 / 18.0), DS(-21.0 / 18.0),
9    DS( 0.0 / 18.0), DS( 0.0 / 18.0), DS( -6.0 / 18.0), DS(  7.0 / 18.0),
10};
11
12
13class GrGLBicubicEffect : public GrGLEffect {
14public:
15    GrGLBicubicEffect(const GrBackendEffectFactory& factory,
16                      const GrDrawEffect&);
17
18    virtual void emitCode(GrGLShaderBuilder*,
19                          const GrDrawEffect&,
20                          EffectKey,
21                          const char* outputColor,
22                          const char* inputColor,
23                          const TransformedCoordsArray&,
24                          const TextureSamplerArray&) SK_OVERRIDE;
25
26    virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
27
28    static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
29        const GrTextureDomain& domain = drawEffect.castEffect<GrBicubicEffect>().domain();
30        return GrTextureDomain::GLDomain::DomainKey(domain);
31    }
32
33private:
34    typedef GrGLUniformManager::UniformHandle        UniformHandle;
35
36    UniformHandle               fCoefficientsUni;
37    UniformHandle               fImageIncrementUni;
38    GrTextureDomain::GLDomain   fDomain;
39
40    typedef GrGLEffect INHERITED;
41};
42
43GrGLBicubicEffect::GrGLBicubicEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
44    : INHERITED(factory) {
45}
46
47void GrGLBicubicEffect::emitCode(GrGLShaderBuilder* builder,
48                                 const GrDrawEffect& drawEffect,
49                                 EffectKey key,
50                                 const char* outputColor,
51                                 const char* inputColor,
52                                 const TransformedCoordsArray& coords,
53                                 const TextureSamplerArray& samplers) {
54    const GrTextureDomain& domain = drawEffect.castEffect<GrBicubicEffect>().domain();
55
56    SkString coords2D = builder->ensureFSCoords2D(coords, 0);
57    fCoefficientsUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
58                                           kMat44f_GrSLType, "Coefficients");
59    fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
60                                             kVec2f_GrSLType, "ImageIncrement");
61
62    const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
63    const char* coeff = builder->getUniformCStr(fCoefficientsUni);
64
65    SkString cubicBlendName;
66
67    static const GrGLShaderVar gCubicBlendArgs[] = {
68        GrGLShaderVar("coefficients",  kMat44f_GrSLType),
69        GrGLShaderVar("t",             kFloat_GrSLType),
70        GrGLShaderVar("c0",            kVec4f_GrSLType),
71        GrGLShaderVar("c1",            kVec4f_GrSLType),
72        GrGLShaderVar("c2",            kVec4f_GrSLType),
73        GrGLShaderVar("c3",            kVec4f_GrSLType),
74    };
75    builder->fsEmitFunction(kVec4f_GrSLType,
76                            "cubicBlend",
77                            SK_ARRAY_COUNT(gCubicBlendArgs),
78                            gCubicBlendArgs,
79                            "\tvec4 ts = vec4(1.0, t, t * t, t * t * t);\n"
80                            "\tvec4 c = coefficients * ts;\n"
81                            "\treturn c.x * c0 + c.y * c1 + c.z * c2 + c.w * c3;\n",
82                            &cubicBlendName);
83    builder->fsCodeAppendf("\tvec2 coord = %s - %s * vec2(0.5);\n", coords2D.c_str(), imgInc);
84    // We unnormalize the coord in order to determine our fractional offset (f) within the texel
85    // We then snap coord to a texel center and renormalize. The snap prevents cases where the
86    // starting coords are near a texel boundary and accumulations of imgInc would cause us to skip/
87    // double hit a texel.
88    builder->fsCodeAppendf("\tcoord /= %s;\n", imgInc);
89    builder->fsCodeAppend("\tvec2 f = fract(coord);\n");
90    builder->fsCodeAppendf("\tcoord = (coord - f + vec2(0.5)) * %s;\n", imgInc);
91    builder->fsCodeAppend("\tvec4 rowColors[4];\n");
92    for (int y = 0; y < 4; ++y) {
93        for (int x = 0; x < 4; ++x) {
94            SkString coord;
95            coord.printf("coord + %s * vec2(%d, %d)", imgInc, x - 1, y - 1);
96            SkString sampleVar;
97            sampleVar.printf("rowColors[%d]", x);
98            fDomain.sampleTexture(builder, domain, sampleVar.c_str(), coord, samplers[0]);
99        }
100        builder->fsCodeAppendf("\tvec4 s%d = %s(%s, f.x, rowColors[0], rowColors[1], rowColors[2], rowColors[3]);\n", y, cubicBlendName.c_str(), coeff);
101    }
102    SkString bicubicColor;
103    bicubicColor.printf("%s(%s, f.y, s0, s1, s2, s3)", cubicBlendName.c_str(), coeff);
104    builder->fsCodeAppendf("\t%s = %s;\n", outputColor, (GrGLSLExpr4(bicubicColor.c_str()) * GrGLSLExpr4(inputColor)).c_str());
105}
106
107void GrGLBicubicEffect::setData(const GrGLUniformManager& uman,
108                                const GrDrawEffect& drawEffect) {
109    const GrBicubicEffect& effect = drawEffect.castEffect<GrBicubicEffect>();
110    const GrTexture& texture = *effect.texture(0);
111    float imageIncrement[2];
112    imageIncrement[0] = 1.0f / texture.width();
113    imageIncrement[1] = 1.0f / texture.height();
114    uman.set2fv(fImageIncrementUni, 1, imageIncrement);
115    uman.setMatrix4f(fCoefficientsUni, effect.coefficients());
116    fDomain.setData(uman, effect.domain(), texture.origin());
117}
118
119static inline void convert_row_major_scalar_coeffs_to_column_major_floats(float dst[16],
120                                                                          const SkScalar src[16]) {
121    for (int y = 0; y < 4; y++) {
122        for (int x = 0; x < 4; x++) {
123            dst[x * 4 + y] = SkScalarToFloat(src[y * 4 + x]);
124        }
125    }
126}
127
128GrBicubicEffect::GrBicubicEffect(GrTexture* texture,
129                                 const SkScalar coefficients[16],
130                                 const SkMatrix &matrix,
131                                 const SkShader::TileMode tileModes[2])
132  : INHERITED(texture, matrix, GrTextureParams(tileModes, GrTextureParams::kNone_FilterMode))
133  , fDomain(GrTextureDomain::IgnoredDomain()) {
134    convert_row_major_scalar_coeffs_to_column_major_floats(fCoefficients, coefficients);
135}
136
137GrBicubicEffect::GrBicubicEffect(GrTexture* texture,
138                                 const SkScalar coefficients[16],
139                                 const SkMatrix &matrix,
140                                 const SkRect& domain)
141  : INHERITED(texture, matrix, GrTextureParams(SkShader::kClamp_TileMode,
142                                               GrTextureParams::kNone_FilterMode))
143  , fDomain(domain, GrTextureDomain::kClamp_Mode) {
144    convert_row_major_scalar_coeffs_to_column_major_floats(fCoefficients, coefficients);
145}
146
147GrBicubicEffect::~GrBicubicEffect() {
148}
149
150const GrBackendEffectFactory& GrBicubicEffect::getFactory() const {
151    return GrTBackendEffectFactory<GrBicubicEffect>::getInstance();
152}
153
154bool GrBicubicEffect::onIsEqual(const GrEffect& sBase) const {
155    const GrBicubicEffect& s = CastEffect<GrBicubicEffect>(sBase);
156    return this->textureAccess(0) == s.textureAccess(0) &&
157           !memcmp(fCoefficients, s.coefficients(), 16);
158}
159
160void GrBicubicEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
161    // FIXME: Perhaps we can do better.
162    *validFlags = 0;
163    return;
164}
165
166GR_DEFINE_EFFECT_TEST(GrBicubicEffect);
167
168GrEffectRef* GrBicubicEffect::TestCreate(SkRandom* random,
169                                         GrContext* context,
170                                         const GrDrawTargetCaps&,
171                                         GrTexture* textures[]) {
172    int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
173                                      GrEffectUnitTest::kAlphaTextureIdx;
174    SkScalar coefficients[16];
175    for (int i = 0; i < 16; i++) {
176        coefficients[i] = random->nextSScalar1();
177    }
178    return GrBicubicEffect::Create(textures[texIdx], coefficients);
179}
180
181//////////////////////////////////////////////////////////////////////////////
182
183bool GrBicubicEffect::ShouldUseBicubic(const SkMatrix& matrix,
184                                       GrTextureParams::FilterMode* filterMode) {
185    if (matrix.isIdentity()) {
186        *filterMode = GrTextureParams::kNone_FilterMode;
187        return false;
188    }
189
190    SkScalar scales[2];
191    if (!matrix.getMinMaxScales(scales) || scales[0] < SK_Scalar1) {
192        // Bicubic doesn't handle arbitrary minimization well, as src texels can be skipped
193        // entirely,
194        *filterMode = GrTextureParams::kMipMap_FilterMode;
195        return false;
196    }
197    // At this point if scales[1] == SK_Scalar1 then the matrix doesn't do any scaling.
198    if (scales[1] == SK_Scalar1) {
199        if (matrix.rectStaysRect() && SkScalarIsInt(matrix.getTranslateX()) &&
200            SkScalarIsInt(matrix.getTranslateY())) {
201            *filterMode = GrTextureParams::kNone_FilterMode;
202        } else {
203            // Use bilerp to handle rotation or fractional translation.
204            *filterMode = GrTextureParams::kBilerp_FilterMode;
205        }
206        return false;
207    }
208    // When we use the bicubic filtering effect each sample is read from the texture using
209    // nearest neighbor sampling.
210    *filterMode = GrTextureParams::kNone_FilterMode;
211    return true;
212}
213