GrConvolutionEffect.cpp revision e0e7cfe44bb9d66d76120a79e5275c294bacaa22
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/GrGLEffect.h"
10#include "gl/GrGLEffectMatrix.h"
11#include "gl/GrGLSL.h"
12#include "gl/GrGLTexture.h"
13#include "GrTBackendEffectFactory.h"
14
15// For brevity
16typedef GrGLUniformManager::UniformHandle UniformHandle;
17
18class GrGLConvolutionEffect : public GrGLEffect {
19public:
20    GrGLConvolutionEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
21
22    virtual void emitCode(GrGLShaderBuilder*,
23                          const GrDrawEffect&,
24                          EffectKey,
25                          const char* outputColor,
26                          const char* inputColor,
27                          const TextureSamplerArray&) SK_OVERRIDE;
28
29    virtual void setData(const GrGLUniformManager& uman, const GrDrawEffect&) SK_OVERRIDE;
30
31    static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
32
33private:
34    int width() const { return Gr1DKernelEffect::WidthFromRadius(fRadius); }
35    bool useBounds() const { return fUseBounds; }
36    Gr1DKernelEffect::Direction direction() const { return fDirection; }
37
38    int                 fRadius;
39    bool                fUseBounds;
40    Gr1DKernelEffect::Direction    fDirection;
41    UniformHandle       fKernelUni;
42    UniformHandle       fImageIncrementUni;
43    UniformHandle       fBoundsUni;
44    GrGLEffectMatrix    fEffectMatrix;
45
46    typedef GrGLEffect INHERITED;
47};
48
49GrGLConvolutionEffect::GrGLConvolutionEffect(const GrBackendEffectFactory& factory,
50                                             const GrDrawEffect& drawEffect)
51    : INHERITED(factory)
52    , fEffectMatrix(drawEffect.castEffect<GrConvolutionEffect>().coordsType()) {
53    const GrConvolutionEffect& c = drawEffect.castEffect<GrConvolutionEffect>();
54    fRadius = c.radius();
55    fUseBounds = c.useBounds();
56    fDirection = c.direction();
57}
58
59void GrGLConvolutionEffect::emitCode(GrGLShaderBuilder* builder,
60                                     const GrDrawEffect&,
61                                     EffectKey key,
62                                     const char* outputColor,
63                                     const char* inputColor,
64                                     const TextureSamplerArray& samplers) {
65    SkString coords;
66    fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
67    fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
68                                             kVec2f_GrSLType, "ImageIncrement");
69    if (this->useBounds()) {
70        fBoundsUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
71                                         kVec2f_GrSLType, "Bounds");
72    }
73    fKernelUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_Visibility,
74                                          kFloat_GrSLType, "Kernel", this->width());
75
76    builder->fsCodeAppendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor);
77
78    int width = this->width();
79    const GrGLShaderVar& kernel = builder->getUniformVariable(fKernelUni);
80    const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
81
82    builder->fsCodeAppendf("\t\tvec2 coord = %s - %d.0 * %s;\n", coords.c_str(), fRadius, imgInc);
83
84    // Manually unroll loop because some drivers don't; yields 20-30% speedup.
85    for (int i = 0; i < width; i++) {
86        SkString index;
87        SkString kernelIndex;
88        index.appendS32(i);
89        kernel.appendArrayAccess(index.c_str(), &kernelIndex);
90        builder->fsCodeAppendf("\t\t%s += ", outputColor);
91        builder->fsAppendTextureLookup(samplers[0], "coord");
92        if (this->useBounds()) {
93            const char* bounds = builder->getUniformCStr(fBoundsUni);
94            const char* component = this->direction() == Gr1DKernelEffect::kY_Direction ? "y" : "x";
95            builder->fsCodeAppendf(" * float(coord.%s >= %s.x && coord.%s <= %s.y)",
96                component, bounds, component, bounds);
97        }
98        builder->fsCodeAppendf(" * %s;\n", kernelIndex.c_str());
99        builder->fsCodeAppendf("\t\tcoord += %s;\n", imgInc);
100    }
101
102    SkString modulate;
103    GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
104    builder->fsCodeAppend(modulate.c_str());
105}
106
107void GrGLConvolutionEffect::setData(const GrGLUniformManager& uman,
108                                    const GrDrawEffect& drawEffect) {
109    const GrConvolutionEffect& conv = drawEffect.castEffect<GrConvolutionEffect>();
110    GrTexture& texture = *conv.texture(0);
111    // the code we generated was for a specific kernel radius
112    SkASSERT(conv.radius() == fRadius);
113    float imageIncrement[2] = { 0 };
114    float ySign = texture.origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f;
115    switch (conv.direction()) {
116        case Gr1DKernelEffect::kX_Direction:
117            imageIncrement[0] = 1.0f / texture.width();
118            break;
119        case Gr1DKernelEffect::kY_Direction:
120            imageIncrement[1] = ySign / texture.height();
121            break;
122        default:
123            GrCrash("Unknown filter direction.");
124    }
125    uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
126    if (conv.useBounds()) {
127        const float* bounds = conv.bounds();
128        if (Gr1DKernelEffect::kY_Direction == conv.direction() &&
129            texture.origin() != kTopLeft_GrSurfaceOrigin) {
130            uman.set2f(fBoundsUni, 1.0f - bounds[1], 1.0f - bounds[0]);
131        } else {
132            uman.set2f(fBoundsUni, bounds[0], bounds[1]);
133        }
134    }
135    uman.set1fv(fKernelUni, 0, this->width(), conv.kernel());
136    fEffectMatrix.setData(uman, conv.getMatrix(), drawEffect, conv.texture(0));
137}
138
139GrGLEffect::EffectKey GrGLConvolutionEffect::GenKey(const GrDrawEffect& drawEffect,
140                                                    const GrGLCaps&) {
141    const GrConvolutionEffect& conv = drawEffect.castEffect<GrConvolutionEffect>();
142    EffectKey key = conv.radius();
143    key <<= 2;
144    if (conv.useBounds()) {
145        key |= 0x2;
146        key |= GrConvolutionEffect::kY_Direction == conv.direction() ? 0x1 : 0x0;
147    }
148    key <<= GrGLEffectMatrix::kKeyBits;
149    EffectKey matrixKey = GrGLEffectMatrix::GenKey(conv.getMatrix(),
150                                                   drawEffect,
151                                                   conv.coordsType(),
152                                                   conv.texture(0));
153    return key | matrixKey;
154}
155
156///////////////////////////////////////////////////////////////////////////////
157
158GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
159                                         Direction direction,
160                                         int radius,
161                                         const float* kernel,
162                                         bool useBounds,
163                                         float bounds[2])
164    : Gr1DKernelEffect(texture, direction, radius), fUseBounds(useBounds) {
165    SkASSERT(radius <= kMaxKernelRadius);
166    SkASSERT(NULL != kernel);
167    int width = this->width();
168    for (int i = 0; i < width; i++) {
169        fKernel[i] = kernel[i];
170    }
171    memcpy(fBounds, bounds, sizeof(fBounds));
172}
173
174GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
175                                         Direction direction,
176                                         int radius,
177                                         float gaussianSigma,
178                                         bool useBounds,
179                                         float bounds[2])
180    : Gr1DKernelEffect(texture, direction, radius), fUseBounds(useBounds) {
181    SkASSERT(radius <= kMaxKernelRadius);
182    int width = this->width();
183
184    float sum = 0.0f;
185    float denom = 1.0f / (2.0f * gaussianSigma * gaussianSigma);
186    for (int i = 0; i < width; ++i) {
187        float x = static_cast<float>(i - this->radius());
188        // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
189        // is dropped here, since we renormalize the kernel below.
190        fKernel[i] = sk_float_exp(- x * x * denom);
191        sum += fKernel[i];
192    }
193    // Normalize the kernel
194    float scale = 1.0f / sum;
195    for (int i = 0; i < width; ++i) {
196        fKernel[i] *= scale;
197    }
198    memcpy(fBounds, bounds, sizeof(fBounds));
199}
200
201GrConvolutionEffect::~GrConvolutionEffect() {
202}
203
204const GrBackendEffectFactory& GrConvolutionEffect::getFactory() const {
205    return GrTBackendEffectFactory<GrConvolutionEffect>::getInstance();
206}
207
208bool GrConvolutionEffect::onIsEqual(const GrEffect& sBase) const {
209    const GrConvolutionEffect& s = CastEffect<GrConvolutionEffect>(sBase);
210    return (this->texture(0) == s.texture(0) &&
211            this->radius() == s.radius() &&
212            this->direction() == s.direction() &&
213            this->useBounds() == s.useBounds() &&
214            0 == memcmp(fBounds, s.fBounds, sizeof(fBounds)) &&
215            0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
216}
217
218///////////////////////////////////////////////////////////////////////////////
219
220GR_DEFINE_EFFECT_TEST(GrConvolutionEffect);
221
222GrEffectRef* GrConvolutionEffect::TestCreate(SkRandom* random,
223                                             GrContext*,
224                                             const GrDrawTargetCaps&,
225                                             GrTexture* textures[]) {
226    int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
227                                      GrEffectUnitTest::kAlphaTextureIdx;
228    Direction dir = random->nextBool() ? kX_Direction : kY_Direction;
229    int radius = random->nextRangeU(1, kMaxKernelRadius);
230    float kernel[kMaxKernelWidth];
231    for (size_t i = 0; i < SK_ARRAY_COUNT(kernel); ++i) {
232        kernel[i] = random->nextSScalar1();
233    }
234    float bounds[2];
235    for (size_t i = 0; i < SK_ARRAY_COUNT(bounds); ++i) {
236        bounds[i] = random->nextF();
237    }
238
239    bool useBounds = random->nextBool();
240    return GrConvolutionEffect::Create(textures[texIdx],
241                                       dir,
242                                       radius,
243                                       kernel,
244                                       useBounds,
245                                       bounds);
246}
247