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#include "gl/builders/GrGLProgramBuilder.h"
8#include "GrMatrixConvolutionEffect.h"
9#include "gl/GrGLProcessor.h"
10#include "gl/GrGLSL.h"
11#include "gl/GrGLTexture.h"
12#include "GrTBackendProcessorFactory.h"
13
14class GrGLMatrixConvolutionEffect : public GrGLFragmentProcessor {
15public:
16    GrGLMatrixConvolutionEffect(const GrBackendProcessorFactory& factory,
17                                const GrProcessor&);
18    virtual void emitCode(GrGLProgramBuilder*,
19                          const GrFragmentProcessor&,
20                          const GrProcessorKey&,
21                          const char* outputColor,
22                          const char* inputColor,
23                          const TransformedCoordsArray&,
24                          const TextureSamplerArray&) SK_OVERRIDE;
25
26    static inline void GenKey(const GrProcessor&, const GrGLCaps&, GrProcessorKeyBuilder*);
27
28    virtual void setData(const GrGLProgramDataManager&, const GrProcessor&) SK_OVERRIDE;
29
30private:
31    typedef GrGLProgramDataManager::UniformHandle UniformHandle;
32    SkISize                     fKernelSize;
33    bool                        fConvolveAlpha;
34
35    UniformHandle               fBoundsUni;
36    UniformHandle               fKernelUni;
37    UniformHandle               fImageIncrementUni;
38    UniformHandle               fKernelOffsetUni;
39    UniformHandle               fGainUni;
40    UniformHandle               fBiasUni;
41    GrTextureDomain::GLDomain   fDomain;
42
43    typedef GrGLFragmentProcessor INHERITED;
44};
45
46GrGLMatrixConvolutionEffect::GrGLMatrixConvolutionEffect(const GrBackendProcessorFactory& factory,
47                                                         const GrProcessor& processor)
48    : INHERITED(factory) {
49    const GrMatrixConvolutionEffect& m = processor.cast<GrMatrixConvolutionEffect>();
50    fKernelSize = m.kernelSize();
51    fConvolveAlpha = m.convolveAlpha();
52}
53
54void GrGLMatrixConvolutionEffect::emitCode(GrGLProgramBuilder* builder,
55                                           const GrFragmentProcessor& fp,
56                                           const GrProcessorKey& key,
57                                           const char* outputColor,
58                                           const char* inputColor,
59                                           const TransformedCoordsArray& coords,
60                                           const TextureSamplerArray& samplers) {
61    sk_ignore_unused_variable(inputColor);
62    const GrTextureDomain& domain = fp.cast<GrMatrixConvolutionEffect>().domain();
63
64    fBoundsUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
65                                     kVec4f_GrSLType, "Bounds");
66    fImageIncrementUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
67                                             kVec2f_GrSLType, "ImageIncrement");
68    fKernelUni = builder->addUniformArray(GrGLProgramBuilder::kFragment_Visibility,
69                                          kFloat_GrSLType,
70                                          "Kernel",
71                                          fKernelSize.width() * fKernelSize.height());
72    fKernelOffsetUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
73                                           kVec2f_GrSLType, "KernelOffset");
74    fGainUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
75                                   kFloat_GrSLType, "Gain");
76    fBiasUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
77                                   kFloat_GrSLType, "Bias");
78
79    const char* kernelOffset = builder->getUniformCStr(fKernelOffsetUni);
80    const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
81    const char* kernel = builder->getUniformCStr(fKernelUni);
82    const char* gain = builder->getUniformCStr(fGainUni);
83    const char* bias = builder->getUniformCStr(fBiasUni);
84    int kWidth = fKernelSize.width();
85    int kHeight = fKernelSize.height();
86
87    GrGLFragmentShaderBuilder* fsBuilder = builder->getFragmentShaderBuilder();
88    SkString coords2D = fsBuilder->ensureFSCoords2D(coords, 0);
89    fsBuilder->codeAppend("vec4 sum = vec4(0, 0, 0, 0);");
90    fsBuilder->codeAppendf("vec2 coord = %s - %s * %s;", coords2D.c_str(), kernelOffset,
91                           imgInc);
92    fsBuilder->codeAppend("vec4 c;");
93
94    for (int y = 0; y < kHeight; y++) {
95        for (int x = 0; x < kWidth; x++) {
96            GrGLShaderBuilder::ShaderBlock block(fsBuilder);
97            fsBuilder->codeAppendf("float k = %s[%d * %d + %d];", kernel, y, kWidth, x);
98            SkString coord;
99            coord.printf("coord + vec2(%d, %d) * %s", x, y, imgInc);
100            fDomain.sampleTexture(fsBuilder, domain, "c", coord, samplers[0]);
101            if (!fConvolveAlpha) {
102                fsBuilder->codeAppend("c.rgb /= c.a;");
103            }
104            fsBuilder->codeAppend("sum += c * k;");
105        }
106    }
107    if (fConvolveAlpha) {
108        fsBuilder->codeAppendf("%s = sum * %s + %s;", outputColor, gain, bias);
109        fsBuilder->codeAppendf("%s.rgb = clamp(%s.rgb, 0.0, %s.a);",
110                               outputColor, outputColor, outputColor);
111    } else {
112        fDomain.sampleTexture(fsBuilder, domain, "c", coords2D, samplers[0]);
113        fsBuilder->codeAppendf("%s.a = c.a;", outputColor);
114        fsBuilder->codeAppendf("%s.rgb = sum.rgb * %s + %s;", outputColor, gain, bias);
115        fsBuilder->codeAppendf("%s.rgb *= %s.a;", outputColor, outputColor);
116    }
117
118    SkString modulate;
119    GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
120    fsBuilder->codeAppend(modulate.c_str());
121}
122
123void GrGLMatrixConvolutionEffect::GenKey(const GrProcessor& processor,
124                                         const GrGLCaps&, GrProcessorKeyBuilder* b) {
125    const GrMatrixConvolutionEffect& m = processor.cast<GrMatrixConvolutionEffect>();
126    SkASSERT(m.kernelSize().width() <= 0x7FFF && m.kernelSize().height() <= 0xFFFF);
127    uint32_t key = m.kernelSize().width() << 16 | m.kernelSize().height();
128    key |= m.convolveAlpha() ? 1 << 31 : 0;
129    b->add32(key);
130    b->add32(GrTextureDomain::GLDomain::DomainKey(m.domain()));
131}
132
133void GrGLMatrixConvolutionEffect::setData(const GrGLProgramDataManager& pdman,
134                                          const GrProcessor& processor) {
135    const GrMatrixConvolutionEffect& conv = processor.cast<GrMatrixConvolutionEffect>();
136    GrTexture& texture = *conv.texture(0);
137    // the code we generated was for a specific kernel size
138    SkASSERT(conv.kernelSize() == fKernelSize);
139    float imageIncrement[2];
140    float ySign = texture.origin() == kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f;
141    imageIncrement[0] = 1.0f / texture.width();
142    imageIncrement[1] = ySign / texture.height();
143    pdman.set2fv(fImageIncrementUni, 1, imageIncrement);
144    pdman.set2fv(fKernelOffsetUni, 1, conv.kernelOffset());
145    pdman.set1fv(fKernelUni, fKernelSize.width() * fKernelSize.height(), conv.kernel());
146    pdman.set1f(fGainUni, conv.gain());
147    pdman.set1f(fBiasUni, conv.bias());
148    fDomain.setData(pdman, conv.domain(), texture.origin());
149}
150
151GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(GrTexture* texture,
152                                                     const SkIRect& bounds,
153                                                     const SkISize& kernelSize,
154                                                     const SkScalar* kernel,
155                                                     SkScalar gain,
156                                                     SkScalar bias,
157                                                     const SkIPoint& kernelOffset,
158                                                     GrTextureDomain::Mode tileMode,
159                                                     bool convolveAlpha)
160  : INHERITED(texture, GrCoordTransform::MakeDivByTextureWHMatrix(texture)),
161    fKernelSize(kernelSize),
162    fGain(SkScalarToFloat(gain)),
163    fBias(SkScalarToFloat(bias) / 255.0f),
164    fConvolveAlpha(convolveAlpha),
165    fDomain(GrTextureDomain::MakeTexelDomain(texture, bounds), tileMode) {
166    for (int i = 0; i < kernelSize.width() * kernelSize.height(); i++) {
167        fKernel[i] = SkScalarToFloat(kernel[i]);
168    }
169    fKernelOffset[0] = static_cast<float>(kernelOffset.x());
170    fKernelOffset[1] = static_cast<float>(kernelOffset.y());
171}
172
173GrMatrixConvolutionEffect::~GrMatrixConvolutionEffect() {
174}
175
176const GrBackendFragmentProcessorFactory& GrMatrixConvolutionEffect::getFactory() const {
177    return GrTBackendFragmentProcessorFactory<GrMatrixConvolutionEffect>::getInstance();
178}
179
180bool GrMatrixConvolutionEffect::onIsEqual(const GrProcessor& sBase) const {
181    const GrMatrixConvolutionEffect& s = sBase.cast<GrMatrixConvolutionEffect>();
182    return this->texture(0) == s.texture(0) &&
183           fKernelSize == s.kernelSize() &&
184           !memcmp(fKernel, s.kernel(),
185                   fKernelSize.width() * fKernelSize.height() * sizeof(float)) &&
186           fGain == s.gain() &&
187           fBias == s.bias() &&
188           fKernelOffset == s.kernelOffset() &&
189           fConvolveAlpha == s.convolveAlpha() &&
190           fDomain == s.domain();
191}
192
193// Static function to create a 2D convolution
194GrFragmentProcessor*
195GrMatrixConvolutionEffect::CreateGaussian(GrTexture* texture,
196                                          const SkIRect& bounds,
197                                          const SkISize& kernelSize,
198                                          SkScalar gain,
199                                          SkScalar bias,
200                                          const SkIPoint& kernelOffset,
201                                          GrTextureDomain::Mode tileMode,
202                                          bool convolveAlpha,
203                                          SkScalar sigmaX,
204                                          SkScalar sigmaY) {
205    float kernel[MAX_KERNEL_SIZE];
206    int width = kernelSize.width();
207    int height = kernelSize.height();
208    SkASSERT(width * height <= MAX_KERNEL_SIZE);
209    float sum = 0.0f;
210    float sigmaXDenom = 1.0f / (2.0f * SkScalarToFloat(SkScalarSquare(sigmaX)));
211    float sigmaYDenom = 1.0f / (2.0f * SkScalarToFloat(SkScalarSquare(sigmaY)));
212    int xRadius = width / 2;
213    int yRadius = height / 2;
214    for (int x = 0; x < width; x++) {
215      float xTerm = static_cast<float>(x - xRadius);
216      xTerm = xTerm * xTerm * sigmaXDenom;
217      for (int y = 0; y < height; y++) {
218        float yTerm = static_cast<float>(y - yRadius);
219        float xyTerm = sk_float_exp(-(xTerm + yTerm * yTerm * sigmaYDenom));
220        // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
221       // is dropped here, since we renormalize the kernel below.
222        kernel[y * width + x] = xyTerm;
223        sum += xyTerm;
224      }
225    }
226    // Normalize the kernel
227    float scale = 1.0f / sum;
228    for (int i = 0; i < width * height; ++i) {
229        kernel[i] *= scale;
230    }
231    return SkNEW_ARGS(GrMatrixConvolutionEffect, (texture,
232                                                  bounds,
233                                                  kernelSize,
234                                                  kernel,
235                                                  gain,
236                                                  bias,
237                                                  kernelOffset,
238                                                  tileMode,
239                                                  convolveAlpha));
240}
241
242GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrMatrixConvolutionEffect);
243
244GrFragmentProcessor* GrMatrixConvolutionEffect::TestCreate(SkRandom* random,
245                                                           GrContext* context,
246                                                           const GrDrawTargetCaps&,
247                                                           GrTexture* textures[]) {
248    int texIdx = random->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx :
249                                      GrProcessorUnitTest::kAlphaTextureIdx;
250    int width = random->nextRangeU(1, MAX_KERNEL_SIZE);
251    int height = random->nextRangeU(1, MAX_KERNEL_SIZE / width);
252    SkISize kernelSize = SkISize::Make(width, height);
253    SkAutoTDeleteArray<SkScalar> kernel(new SkScalar[width * height]);
254    for (int i = 0; i < width * height; i++) {
255        kernel.get()[i] = random->nextSScalar1();
256    }
257    SkScalar gain = random->nextSScalar1();
258    SkScalar bias = random->nextSScalar1();
259    SkIPoint kernelOffset = SkIPoint::Make(random->nextRangeU(0, kernelSize.width()),
260                                           random->nextRangeU(0, kernelSize.height()));
261    SkIRect bounds = SkIRect::MakeXYWH(random->nextRangeU(0, textures[texIdx]->width()),
262                                       random->nextRangeU(0, textures[texIdx]->height()),
263                                       random->nextRangeU(0, textures[texIdx]->width()),
264                                       random->nextRangeU(0, textures[texIdx]->height()));
265    GrTextureDomain::Mode tileMode = static_cast<GrTextureDomain::Mode>(random->nextRangeU(0, 2));
266    bool convolveAlpha = random->nextBool();
267    return GrMatrixConvolutionEffect::Create(textures[texIdx],
268                                             bounds,
269                                             kernelSize,
270                                             kernel.get(),
271                                             gain,
272                                             bias,
273                                             kernelOffset,
274                                             tileMode,
275                                             convolveAlpha);
276}
277