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