GrBicubicEffect.cpp revision 3f6f76f98b6b37d17d1492791ff0feb1b7586bd6
1/*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "GrBicubicEffect.h"
9#include "GrInvariantOutput.h"
10#include "glsl/GrGLSLFragmentShaderBuilder.h"
11#include "glsl/GrGLSLProgramDataManager.h"
12#include "glsl/GrGLSLUniformHandler.h"
13
14#define DS(x) SkDoubleToScalar(x)
15
16const SkScalar GrBicubicEffect::gMitchellCoefficients[16] = {
17    DS( 1.0 / 18.0), DS(-9.0 / 18.0), DS( 15.0 / 18.0), DS( -7.0 / 18.0),
18    DS(16.0 / 18.0), DS( 0.0 / 18.0), DS(-36.0 / 18.0), DS( 21.0 / 18.0),
19    DS( 1.0 / 18.0), DS( 9.0 / 18.0), DS( 27.0 / 18.0), DS(-21.0 / 18.0),
20    DS( 0.0 / 18.0), DS( 0.0 / 18.0), DS( -6.0 / 18.0), DS(  7.0 / 18.0),
21};
22
23
24class GrGLBicubicEffect : public GrGLSLFragmentProcessor {
25public:
26    void emitCode(EmitArgs&) override;
27
28    static inline void GenKey(const GrProcessor& effect, const GrGLSLCaps&,
29                              GrProcessorKeyBuilder* b) {
30        const GrTextureDomain& domain = effect.cast<GrBicubicEffect>().domain();
31        b->add32(GrTextureDomain::GLDomain::DomainKey(domain));
32    }
33
34protected:
35    void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
36
37private:
38    typedef GrGLSLProgramDataManager::UniformHandle UniformHandle;
39
40    UniformHandle               fCoefficientsUni;
41    UniformHandle               fImageIncrementUni;
42    GrTextureDomain::GLDomain   fDomain;
43
44    typedef GrGLSLFragmentProcessor INHERITED;
45};
46
47void GrGLBicubicEffect::emitCode(EmitArgs& args) {
48    const GrTextureDomain& domain = args.fFp.cast<GrBicubicEffect>().domain();
49
50    GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
51    fCoefficientsUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
52                                                  kMat44f_GrSLType, kDefault_GrSLPrecision,
53                                                  "Coefficients");
54    fImageIncrementUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
55                                                    kVec2f_GrSLType, kDefault_GrSLPrecision,
56                                                    "ImageIncrement");
57
58    const char* imgInc = uniformHandler->getUniformCStr(fImageIncrementUni);
59    const char* coeff = uniformHandler->getUniformCStr(fCoefficientsUni);
60
61    SkString cubicBlendName;
62
63    static const GrGLSLShaderVar gCubicBlendArgs[] = {
64        GrGLSLShaderVar("coefficients",  kMat44f_GrSLType),
65        GrGLSLShaderVar("t",             kFloat_GrSLType),
66        GrGLSLShaderVar("c0",            kVec4f_GrSLType),
67        GrGLSLShaderVar("c1",            kVec4f_GrSLType),
68        GrGLSLShaderVar("c2",            kVec4f_GrSLType),
69        GrGLSLShaderVar("c3",            kVec4f_GrSLType),
70    };
71    GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
72    SkString coords2D = fragBuilder->ensureFSCoords2D(args.fCoords, 0);
73    fragBuilder->emitFunction(kVec4f_GrSLType,
74                              "cubicBlend",
75                              SK_ARRAY_COUNT(gCubicBlendArgs),
76                              gCubicBlendArgs,
77                              "\tvec4 ts = vec4(1.0, t, t * t, t * t * t);\n"
78                              "\tvec4 c = coefficients * ts;\n"
79                              "\treturn c.x * c0 + c.y * c1 + c.z * c2 + c.w * c3;\n",
80                              &cubicBlendName);
81    fragBuilder->codeAppendf("\tvec2 coord = %s - %s * vec2(0.5);\n", coords2D.c_str(), imgInc);
82    // We unnormalize the coord in order to determine our fractional offset (f) within the texel
83    // We then snap coord to a texel center and renormalize. The snap prevents cases where the
84    // starting coords are near a texel boundary and accumulations of imgInc would cause us to skip/
85    // double hit a texel.
86    fragBuilder->codeAppendf("\tcoord /= %s;\n", imgInc);
87    fragBuilder->codeAppend("\tvec2 f = fract(coord);\n");
88    fragBuilder->codeAppendf("\tcoord = (coord - f + vec2(0.5)) * %s;\n", imgInc);
89    fragBuilder->codeAppend("\tvec4 rowColors[4];\n");
90    for (int y = 0; y < 4; ++y) {
91        for (int x = 0; x < 4; ++x) {
92            SkString coord;
93            coord.printf("coord + %s * vec2(%d, %d)", imgInc, x - 1, y - 1);
94            SkString sampleVar;
95            sampleVar.printf("rowColors[%d]", x);
96            fDomain.sampleTexture(fragBuilder,
97                                  args.fUniformHandler,
98                                  args.fGLSLCaps,
99                                  domain,
100                                  sampleVar.c_str(),
101                                  coord,
102                                  args.fTexSamplers[0]);
103        }
104        fragBuilder->codeAppendf(
105            "\tvec4 s%d = %s(%s, f.x, rowColors[0], rowColors[1], rowColors[2], rowColors[3]);\n",
106            y, cubicBlendName.c_str(), coeff);
107    }
108    SkString bicubicColor;
109    bicubicColor.printf("%s(%s, f.y, s0, s1, s2, s3)", cubicBlendName.c_str(), coeff);
110    fragBuilder->codeAppendf("\t%s = %s;\n",
111                             args.fOutputColor, (GrGLSLExpr4(bicubicColor.c_str()) *
112                                                 GrGLSLExpr4(args.fInputColor)).c_str());
113}
114
115void GrGLBicubicEffect::onSetData(const GrGLSLProgramDataManager& pdman,
116                                  const GrProcessor& processor) {
117    const GrBicubicEffect& bicubicEffect = processor.cast<GrBicubicEffect>();
118    const GrTexture& texture = *processor.texture(0);
119    float imageIncrement[2];
120    imageIncrement[0] = 1.0f / texture.width();
121    imageIncrement[1] = 1.0f / texture.height();
122    pdman.set2fv(fImageIncrementUni, 1, imageIncrement);
123    pdman.setMatrix4f(fCoefficientsUni, bicubicEffect.coefficients());
124    fDomain.setData(pdman, bicubicEffect.domain(), texture.origin());
125}
126
127static inline void convert_row_major_scalar_coeffs_to_column_major_floats(float dst[16],
128                                                                          const SkScalar src[16]) {
129    for (int y = 0; y < 4; y++) {
130        for (int x = 0; x < 4; x++) {
131            dst[x * 4 + y] = SkScalarToFloat(src[y * 4 + x]);
132        }
133    }
134}
135
136GrBicubicEffect::GrBicubicEffect(GrTexture* texture,
137                                 const SkScalar coefficients[16],
138                                 const SkMatrix &matrix,
139                                 const SkShader::TileMode tileModes[2])
140  : INHERITED(texture, matrix, GrTextureParams(tileModes, GrTextureParams::kNone_FilterMode))
141  , fDomain(GrTextureDomain::IgnoredDomain()) {
142    this->initClassID<GrBicubicEffect>();
143    convert_row_major_scalar_coeffs_to_column_major_floats(fCoefficients, coefficients);
144}
145
146GrBicubicEffect::GrBicubicEffect(GrTexture* texture,
147                                 const SkScalar coefficients[16],
148                                 const SkMatrix &matrix,
149                                 const SkRect& domain)
150  : INHERITED(texture, matrix,
151              GrTextureParams(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode))
152  , fDomain(domain, GrTextureDomain::kClamp_Mode) {
153    this->initClassID<GrBicubicEffect>();
154    convert_row_major_scalar_coeffs_to_column_major_floats(fCoefficients, coefficients);
155}
156
157GrBicubicEffect::~GrBicubicEffect() {
158}
159
160void GrBicubicEffect::onGetGLSLProcessorKey(const GrGLSLCaps& caps,
161                                            GrProcessorKeyBuilder* b) const {
162    GrGLBicubicEffect::GenKey(*this, caps, b);
163}
164
165GrGLSLFragmentProcessor* GrBicubicEffect::onCreateGLSLInstance() const  {
166    return new GrGLBicubicEffect;
167}
168
169bool GrBicubicEffect::onIsEqual(const GrFragmentProcessor& sBase) const {
170    const GrBicubicEffect& s = sBase.cast<GrBicubicEffect>();
171    return !memcmp(fCoefficients, s.coefficients(), 16) &&
172           fDomain == s.fDomain;
173}
174
175void GrBicubicEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const {
176    // FIXME: Perhaps we can do better.
177    inout->mulByUnknownSingleComponent();
178}
179
180GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrBicubicEffect);
181
182const GrFragmentProcessor* GrBicubicEffect::TestCreate(GrProcessorTestData* d) {
183    int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx :
184                                          GrProcessorUnitTest::kAlphaTextureIdx;
185    SkScalar coefficients[16];
186    for (int i = 0; i < 16; i++) {
187        coefficients[i] = d->fRandom->nextSScalar1();
188    }
189    return GrBicubicEffect::Create(d->fTextures[texIdx], coefficients);
190}
191
192//////////////////////////////////////////////////////////////////////////////
193
194bool GrBicubicEffect::ShouldUseBicubic(const SkMatrix& matrix,
195                                       GrTextureParams::FilterMode* filterMode) {
196    if (matrix.isIdentity()) {
197        *filterMode = GrTextureParams::kNone_FilterMode;
198        return false;
199    }
200
201    SkScalar scales[2];
202    if (!matrix.getMinMaxScales(scales) || scales[0] < SK_Scalar1) {
203        // Bicubic doesn't handle arbitrary minimization well, as src texels can be skipped
204        // entirely,
205        *filterMode = GrTextureParams::kMipMap_FilterMode;
206        return false;
207    }
208    // At this point if scales[1] == SK_Scalar1 then the matrix doesn't do any scaling.
209    if (scales[1] == SK_Scalar1) {
210        if (matrix.rectStaysRect() && SkScalarIsInt(matrix.getTranslateX()) &&
211            SkScalarIsInt(matrix.getTranslateY())) {
212            *filterMode = GrTextureParams::kNone_FilterMode;
213        } else {
214            // Use bilerp to handle rotation or fractional translation.
215            *filterMode = GrTextureParams::kBilerp_FilterMode;
216        }
217        return false;
218    }
219    // When we use the bicubic filtering effect each sample is read from the texture using
220    // nearest neighbor sampling.
221    *filterMode = GrTextureParams::kNone_FilterMode;
222    return true;
223}
224