GrConvolutionEffect.cpp revision f0a104e6f16dc095286d32f1e104894ae0b2b19f
1/*
2 * Copyright 2012 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 "GrConvolutionEffect.h"
9#include "gl/GrGLProgramStage.h"
10#include "gl/GrGLSL.h"
11#include "gl/GrGLTexture.h"
12#include "GrProgramStageFactory.h"
13
14class GrGLConvolutionEffect : public GrGLProgramStage {
15public:
16    GrGLConvolutionEffect(const GrProgramStageFactory& factory,
17                          const GrCustomStage& stage);
18
19    virtual void setupVariables(GrGLShaderBuilder* state,
20                                int stage) SK_OVERRIDE;
21    virtual void emitVS(GrGLShaderBuilder* state,
22                        const char* vertexCoords) SK_OVERRIDE;
23    virtual void emitFS(GrGLShaderBuilder* state,
24                        const char* outputColor,
25                        const char* inputColor,
26                        const char* samplerName) SK_OVERRIDE;
27
28    virtual void initUniforms(const GrGLInterface*, int programID) SK_OVERRIDE;
29
30    virtual void setData(const GrGLInterface*,
31                         const GrGLTexture&,
32                         const GrCustomStage&,
33                         int stageNum) SK_OVERRIDE;
34
35    static inline StageKey GenKey(const GrCustomStage&);
36
37private:
38    int width() const { return Gr1DKernelEffect::WidthFromRadius(fRadius); }
39
40    int                   fRadius;
41    const GrGLShaderVar*  fKernelVar;
42    GrGLint               fKernelLocation;
43    const GrGLShaderVar*  fImageIncrementVar;
44    GrGLint               fImageIncrementLocation;
45
46    typedef GrGLProgramStage INHERITED;
47};
48
49GrGLConvolutionEffect::GrGLConvolutionEffect(const GrProgramStageFactory& factory,
50                                             const GrCustomStage& stage)
51    : GrGLProgramStage(factory)
52    , fKernelVar(NULL)
53    , fKernelLocation(0)
54    , fImageIncrementVar(NULL)
55    , fImageIncrementLocation(0) {
56    const GrConvolutionEffect& c =
57        static_cast<const GrConvolutionEffect&>(stage);
58    fRadius = c.radius();
59}
60
61void GrGLConvolutionEffect::setupVariables(GrGLShaderBuilder* state,
62                                           int stage) {
63    fImageIncrementVar = &state->addUniform(
64        GrGLShaderBuilder::kBoth_VariableLifetime,
65        kVec2f_GrSLType, "uImageIncrement", stage);
66    fKernelVar = &state->addUniform(
67        GrGLShaderBuilder::kFragment_VariableLifetime,
68        kFloat_GrSLType, "uKernel", stage, this->width());
69
70    fImageIncrementLocation = kUseUniform;
71    fKernelLocation = kUseUniform;
72}
73
74void GrGLConvolutionEffect::emitVS(GrGLShaderBuilder* state,
75                                   const char* vertexCoords) {
76    SkString* code = &state->fVSCode;
77    code->appendf("\t\t%s -= vec2(%d, %d) * %s;\n",
78                  vertexCoords, fRadius, fRadius,
79                  fImageIncrementVar->getName().c_str());
80}
81
82void GrGLConvolutionEffect::emitFS(GrGLShaderBuilder* state,
83                                   const char* outputColor,
84                                   const char* inputColor,
85                                   const char* samplerName) {
86    SkString* code = &state->fFSCode;
87
88    code->appendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor);
89
90    code->appendf("\t\tvec2 coord = %s;\n", state->fSampleCoords.c_str());
91
92    int width = this ->width();
93    // Manually unroll loop because some drivers don't; yields 20-30% speedup.
94    for (int i = 0; i < width; i++) {
95        SkString index;
96        SkString kernelIndex;
97        index.appendS32(i);
98        fKernelVar->appendArrayAccess(index.c_str(), &kernelIndex);
99        code->appendf("\t\t%s += ", outputColor);
100        state->emitTextureLookup(samplerName, "coord");
101        code->appendf(" * %s;\n", kernelIndex.c_str());
102        code->appendf("\t\tcoord += %s;\n",
103                      fImageIncrementVar->getName().c_str());
104    }
105
106    if (state->fModulate.size()) {
107        code->appendf("\t\t%s = %s%s;\n", outputColor, outputColor,
108                      state->fModulate.c_str());
109    }
110}
111
112void GrGLConvolutionEffect::initUniforms(const GrGLInterface* gl,
113                                         int programID) {
114    GR_GL_CALL_RET(gl, fImageIncrementLocation,
115        GetUniformLocation(programID,
116            fImageIncrementVar->getName().c_str()));
117    GR_GL_CALL_RET(gl, fKernelLocation,
118        GetUniformLocation(programID, fKernelVar->getName().c_str()));
119}
120
121void GrGLConvolutionEffect::setData(const GrGLInterface* gl,
122                                    const GrGLTexture& texture,
123                                    const GrCustomStage& data,
124                                    int stageNum) {
125    const GrConvolutionEffect& conv =
126        static_cast<const GrConvolutionEffect&>(data);
127    // the code we generated was for a specific kernel radius
128    GrAssert(conv.radius() == fRadius);
129    float imageIncrement[2] = { 0 };
130    switch (conv.direction()) {
131        case Gr1DKernelEffect::kX_Direction:
132            imageIncrement[0] = 1.0f / texture.width();
133            break;
134        case Gr1DKernelEffect::kY_Direction:
135            imageIncrement[1] = 1.0f / texture.height();
136            break;
137        default:
138            GrCrash("Unknown filter direction.");
139    }
140    GR_GL_CALL(gl, Uniform2fv(fImageIncrementLocation, 1, imageIncrement));
141
142    GR_GL_CALL(gl, Uniform1fv(fKernelLocation, this->width(), conv.kernel()));
143}
144
145GrGLProgramStage::StageKey GrGLConvolutionEffect::GenKey(
146                                                    const GrCustomStage& s) {
147    return static_cast<const GrConvolutionEffect&>(s).radius();
148}
149
150///////////////////////////////////////////////////////////////////////////////
151
152GrConvolutionEffect::GrConvolutionEffect(Direction direction,
153                                         int radius,
154                                         const float* kernel)
155    : Gr1DKernelEffect(direction, radius) {
156    GrAssert(radius <= kMaxKernelRadius);
157    int width = this->width();
158    if (NULL != kernel) {
159        for (int i = 0; i < width; i++) {
160            fKernel[i] = kernel[i];
161        }
162    }
163}
164
165GrConvolutionEffect::~GrConvolutionEffect() {
166}
167
168const GrProgramStageFactory& GrConvolutionEffect::getFactory() const {
169    return GrTProgramStageFactory<GrConvolutionEffect>::getInstance();
170}
171
172bool GrConvolutionEffect::isEqual(const GrCustomStage& sBase) const {
173     const GrConvolutionEffect& s =
174        static_cast<const GrConvolutionEffect&>(sBase);
175    return (this->radius() == s.radius() &&
176             this->direction() == s.direction() &&
177             0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
178}
179
180void GrConvolutionEffect::setGaussianKernel(float sigma) {
181    int width = this->width();
182    float sum = 0.0f;
183    float denom = 1.0f / (2.0f * sigma * sigma);
184    for (int i = 0; i < width; ++i) {
185        float x = static_cast<float>(i - this->radius());
186        // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
187        // is dropped here, since we renormalize the kernel below.
188        fKernel[i] = sk_float_exp(- x * x * denom);
189        sum += fKernel[i];
190    }
191    // Normalize the kernel
192    float scale = 1.0f / sum;
193    for (int i = 0; i < width; ++i) {
194        fKernel[i] *= scale;
195    }
196}
197