GrConvolutionEffect.cpp revision 28a15fb8d603847949a61657ef5cb73ed9915021
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/GrGLSL.h"
11#include "gl/GrGLTexture.h"
12#include "GrBackendEffectFactory.h"
13
14// For brevity
15typedef GrGLUniformManager::UniformHandle UniformHandle;
16static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
17
18class GrGLConvolutionEffect : public GrGLLegacyEffect {
19public:
20    GrGLConvolutionEffect(const GrBackendEffectFactory&, const GrEffect&);
21
22    virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
23    virtual void emitVS(GrGLShaderBuilder* builder,
24                        const char* vertexCoords) SK_OVERRIDE {};
25    virtual void emitFS(GrGLShaderBuilder* builder,
26                        const char* outputColor,
27                        const char* inputColor,
28                        const TextureSamplerArray&) SK_OVERRIDE;
29
30    virtual void setData(const GrGLUniformManager& uman, const GrEffectStage&) SK_OVERRIDE;
31
32    static inline EffectKey GenKey(const GrEffect&, const GrGLCaps&);
33
34private:
35    int width() const { return Gr1DKernelEffect::WidthFromRadius(fRadius); }
36
37    int             fRadius;
38    UniformHandle   fKernelUni;
39    UniformHandle   fImageIncrementUni;
40
41    typedef GrGLLegacyEffect INHERITED;
42};
43
44GrGLConvolutionEffect::GrGLConvolutionEffect(const GrBackendEffectFactory& factory,
45                                             const GrEffect& effect)
46    : INHERITED(factory)
47    , fKernelUni(kInvalidUniformHandle)
48    , fImageIncrementUni(kInvalidUniformHandle) {
49    const GrConvolutionEffect& c =
50        static_cast<const GrConvolutionEffect&>(effect);
51    fRadius = c.radius();
52}
53
54void GrGLConvolutionEffect::setupVariables(GrGLShaderBuilder* builder) {
55    fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
56                                             kVec2f_GrSLType, "ImageIncrement");
57    fKernelUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
58                                          kFloat_GrSLType, "Kernel", this->width());
59}
60
61void GrGLConvolutionEffect::emitFS(GrGLShaderBuilder* builder,
62                                   const char* outputColor,
63                                   const char* inputColor,
64                                   const TextureSamplerArray& samplers) {
65    SkString* code = &builder->fFSCode;
66
67    code->appendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor);
68
69    int width = this ->width();
70    const GrGLShaderVar& kernel = builder->getUniformVariable(fKernelUni);
71    const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
72
73    code->appendf("\t\tvec2 coord = %s - %d.0 * %s;\n",
74                  builder->defaultTexCoordsName(), fRadius, imgInc);
75
76    // Manually unroll loop because some drivers don't; yields 20-30% speedup.
77    for (int i = 0; i < width; i++) {
78        SkString index;
79        SkString kernelIndex;
80        index.appendS32(i);
81        kernel.appendArrayAccess(index.c_str(), &kernelIndex);
82        code->appendf("\t\t%s += ", outputColor);
83        builder->appendTextureLookup(&builder->fFSCode, samplers[0], "coord");
84        code->appendf(" * %s;\n", kernelIndex.c_str());
85        code->appendf("\t\tcoord += %s;\n", imgInc);
86    }
87    GrGLSLMulVarBy4f(&builder->fFSCode, 2, outputColor, inputColor);
88}
89
90void GrGLConvolutionEffect::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
91    const GrConvolutionEffect& conv = static_cast<const GrConvolutionEffect&>(*stage.getEffect());
92    GrTexture& texture = *conv.texture(0);
93    // the code we generated was for a specific kernel radius
94    GrAssert(conv.radius() == fRadius);
95    float imageIncrement[2] = { 0 };
96    switch (conv.direction()) {
97        case Gr1DKernelEffect::kX_Direction:
98            imageIncrement[0] = 1.0f / texture.width();
99            break;
100        case Gr1DKernelEffect::kY_Direction:
101            imageIncrement[1] = 1.0f / texture.height();
102            break;
103        default:
104            GrCrash("Unknown filter direction.");
105    }
106    uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
107    uman.set1fv(fKernelUni, 0, this->width(), conv.kernel());
108}
109
110GrGLEffect::EffectKey GrGLConvolutionEffect::GenKey(const GrEffect& s,
111                                                         const GrGLCaps& caps) {
112    return static_cast<const GrConvolutionEffect&>(s).radius();
113}
114
115///////////////////////////////////////////////////////////////////////////////
116
117GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
118                                         Direction direction,
119                                         int radius,
120                                         const float* kernel)
121    : Gr1DKernelEffect(texture, direction, radius) {
122    GrAssert(radius <= kMaxKernelRadius);
123    int width = this->width();
124    if (NULL != kernel) {
125        for (int i = 0; i < width; i++) {
126            fKernel[i] = kernel[i];
127        }
128    }
129}
130
131GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
132                                         Direction direction,
133                                         int radius,
134                                         float gaussianSigma)
135    : Gr1DKernelEffect(texture, direction, radius) {
136    GrAssert(radius <= kMaxKernelRadius);
137    int width = this->width();
138
139    float sum = 0.0f;
140    float denom = 1.0f / (2.0f * gaussianSigma * gaussianSigma);
141    for (int i = 0; i < width; ++i) {
142        float x = static_cast<float>(i - this->radius());
143        // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
144        // is dropped here, since we renormalize the kernel below.
145        fKernel[i] = sk_float_exp(- x * x * denom);
146        sum += fKernel[i];
147    }
148    // Normalize the kernel
149    float scale = 1.0f / sum;
150    for (int i = 0; i < width; ++i) {
151        fKernel[i] *= scale;
152    }
153}
154
155GrConvolutionEffect::~GrConvolutionEffect() {
156}
157
158const GrBackendEffectFactory& GrConvolutionEffect::getFactory() const {
159    return GrTBackendEffectFactory<GrConvolutionEffect>::getInstance();
160}
161
162bool GrConvolutionEffect::isEqual(const GrEffect& sBase) const {
163     const GrConvolutionEffect& s =
164        static_cast<const GrConvolutionEffect&>(sBase);
165    return (INHERITED::isEqual(sBase) &&
166            this->radius() == s.radius() &&
167            this->direction() == s.direction() &&
168            0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
169}
170
171///////////////////////////////////////////////////////////////////////////////
172
173GR_DEFINE_EFFECT_TEST(GrConvolutionEffect);
174
175GrEffect* GrConvolutionEffect::TestCreate(SkRandom* random,
176                                          GrContext* context,
177                                          GrTexture* textures[]) {
178    int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
179                                      GrEffectUnitTest::kAlphaTextureIdx;
180    Direction dir = random->nextBool() ? kX_Direction : kY_Direction;
181    int radius = random->nextRangeU(1, kMaxKernelRadius);
182    float kernel[kMaxKernelRadius];
183    for (int i = 0; i < kMaxKernelRadius; ++i) {
184        kernel[i] = random->nextSScalar1();
185    }
186
187    return SkNEW_ARGS(GrConvolutionEffect, (textures[texIdx], dir, radius, kernel));
188}
189
190