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 "GrGaussianConvolutionFragmentProcessor.h"
9
10#include "GrProxyMove.h"
11#include "GrTextureProxy.h"
12#include "../private/GrGLSL.h"
13#include "glsl/GrGLSLFragmentProcessor.h"
14#include "glsl/GrGLSLFragmentShaderBuilder.h"
15#include "glsl/GrGLSLProgramDataManager.h"
16#include "glsl/GrGLSLUniformHandler.h"
17
18// For brevity
19typedef GrGLSLProgramDataManager::UniformHandle UniformHandle;
20
21class GrGLConvolutionEffect : public GrGLSLFragmentProcessor {
22public:
23    void emitCode(EmitArgs&) override;
24
25    static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*);
26
27protected:
28    void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor&) override;
29
30private:
31    UniformHandle fKernelUni;
32    UniformHandle fImageIncrementUni;
33    UniformHandle fBoundsUni;
34
35    typedef GrGLSLFragmentProcessor INHERITED;
36};
37
38void GrGLConvolutionEffect::emitCode(EmitArgs& args) {
39    const GrGaussianConvolutionFragmentProcessor& ce =
40            args.fFp.cast<GrGaussianConvolutionFragmentProcessor>();
41
42    GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
43    fImageIncrementUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kVec2f_GrSLType,
44                                                    kDefault_GrSLPrecision, "ImageIncrement");
45    if (ce.useBounds()) {
46        fBoundsUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kVec2f_GrSLType,
47                                                kDefault_GrSLPrecision, "Bounds");
48    }
49
50    int width = Gr1DKernelEffect::WidthFromRadius(ce.radius());
51
52    int arrayCount = (width + 3) / 4;
53    SkASSERT(4 * arrayCount >= width);
54
55    fKernelUni = uniformHandler->addUniformArray(kFragment_GrShaderFlag, kVec4f_GrSLType,
56                                                 kDefault_GrSLPrecision, "Kernel", arrayCount);
57
58    GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
59    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
60
61    fragBuilder->codeAppendf("%s = vec4(0, 0, 0, 0);", args.fOutputColor);
62
63    const GrShaderVar& kernel = uniformHandler->getUniformVariable(fKernelUni);
64    const char* imgInc = uniformHandler->getUniformCStr(fImageIncrementUni);
65
66    fragBuilder->codeAppendf("vec2 coord = %s - %d.0 * %s;", coords2D.c_str(), ce.radius(), imgInc);
67
68    // Manually unroll loop because some drivers don't; yields 20-30% speedup.
69    const char* kVecSuffix[4] = {".x", ".y", ".z", ".w"};
70    for (int i = 0; i < width; i++) {
71        SkString index;
72        SkString kernelIndex;
73        index.appendS32(i / 4);
74        kernel.appendArrayAccess(index.c_str(), &kernelIndex);
75        kernelIndex.append(kVecSuffix[i & 0x3]);
76
77        if (ce.useBounds()) {
78            // We used to compute a bool indicating whether we're in bounds or not, cast it to a
79            // float, and then mul weight*texture_sample by the float. However, the Adreno 430 seems
80            // to have a bug that caused corruption.
81            const char* bounds = uniformHandler->getUniformCStr(fBoundsUni);
82            const char* component = ce.direction() == Gr1DKernelEffect::kY_Direction ? "y" : "x";
83            fragBuilder->codeAppendf("if (coord.%s >= %s.x && coord.%s <= %s.y) {", component,
84                                     bounds, component, bounds);
85        }
86        fragBuilder->codeAppendf("%s += ", args.fOutputColor);
87        fragBuilder->appendTextureLookup(args.fTexSamplers[0], "coord");
88        fragBuilder->codeAppendf(" * %s;\n", kernelIndex.c_str());
89        if (ce.useBounds()) {
90            fragBuilder->codeAppend("}");
91        }
92        fragBuilder->codeAppendf("coord += %s;\n", imgInc);
93    }
94
95    SkString modulate;
96    GrGLSLMulVarBy4f(&modulate, args.fOutputColor, args.fInputColor);
97    fragBuilder->codeAppend(modulate.c_str());
98}
99
100void GrGLConvolutionEffect::onSetData(const GrGLSLProgramDataManager& pdman,
101                                      const GrProcessor& processor) {
102    const GrGaussianConvolutionFragmentProcessor& conv =
103            processor.cast<GrGaussianConvolutionFragmentProcessor>();
104    GrTexture& texture = *conv.textureSampler(0).texture();
105
106    float imageIncrement[2] = {0};
107    float ySign = texture.origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f;
108    switch (conv.direction()) {
109        case Gr1DKernelEffect::kX_Direction:
110            imageIncrement[0] = 1.0f / texture.width();
111            break;
112        case Gr1DKernelEffect::kY_Direction:
113            imageIncrement[1] = ySign / texture.height();
114            break;
115        default:
116            SkFAIL("Unknown filter direction.");
117    }
118    pdman.set2fv(fImageIncrementUni, 1, imageIncrement);
119    if (conv.useBounds()) {
120        const int* bounds = conv.bounds();
121        if (Gr1DKernelEffect::kX_Direction == conv.direction()) {
122            SkScalar inv = SkScalarInvert(SkIntToScalar(texture.width()));
123            pdman.set2f(fBoundsUni, inv * bounds[0], inv * bounds[1]);
124        } else {
125            SkScalar inv = SkScalarInvert(SkIntToScalar(texture.height()));
126            if (texture.origin() != kTopLeft_GrSurfaceOrigin) {
127                pdman.set2f(fBoundsUni, 1.0f - (inv * bounds[1]), 1.0f - (inv * bounds[0]));
128            } else {
129                pdman.set2f(fBoundsUni, inv * bounds[1], inv * bounds[0]);
130            }
131        }
132    }
133    int width = Gr1DKernelEffect::WidthFromRadius(conv.radius());
134
135    int arrayCount = (width + 3) / 4;
136    SkASSERT(4 * arrayCount >= width);
137    pdman.set4fv(fKernelUni, arrayCount, conv.kernel());
138}
139
140void GrGLConvolutionEffect::GenKey(const GrProcessor& processor, const GrShaderCaps&,
141                                   GrProcessorKeyBuilder* b) {
142    const GrGaussianConvolutionFragmentProcessor& conv =
143            processor.cast<GrGaussianConvolutionFragmentProcessor>();
144    uint32_t key = conv.radius();
145    key <<= 2;
146    if (conv.useBounds()) {
147        key |= 0x2;
148        key |= GrGaussianConvolutionFragmentProcessor::kY_Direction == conv.direction() ? 0x1 : 0x0;
149    }
150    b->add32(key);
151}
152
153///////////////////////////////////////////////////////////////////////////////
154static void fill_in_1D_guassian_kernel(float* kernel, int width, float gaussianSigma, int radius) {
155    const float denom = 1.0f / (2.0f * gaussianSigma * gaussianSigma);
156
157    float sum = 0.0f;
158    for (int i = 0; i < width; ++i) {
159        float x = static_cast<float>(i - radius);
160        // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
161        // is dropped here, since we renormalize the kernel below.
162        kernel[i] = sk_float_exp(-x * x * denom);
163        sum += kernel[i];
164    }
165    // Normalize the kernel
166    float scale = 1.0f / sum;
167    for (int i = 0; i < width; ++i) {
168        kernel[i] *= scale;
169    }
170}
171
172GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor(
173                                                            GrResourceProvider* resourceProvider,
174                                                            sk_sp<GrTextureProxy> proxy,
175                                                            Direction direction,
176                                                            int radius,
177                                                            float gaussianSigma,
178                                                            bool useBounds,
179                                                            int bounds[2])
180        : INHERITED{resourceProvider,
181                    ModulationFlags(proxy->config()),
182                    GR_PROXY_MOVE(proxy),
183                    direction,
184                    radius}
185        , fUseBounds(useBounds) {
186    this->initClassID<GrGaussianConvolutionFragmentProcessor>();
187    SkASSERT(radius <= kMaxKernelRadius);
188
189    fill_in_1D_guassian_kernel(fKernel, this->width(), gaussianSigma, this->radius());
190
191    memcpy(fBounds, bounds, sizeof(fBounds));
192}
193
194GrGaussianConvolutionFragmentProcessor::~GrGaussianConvolutionFragmentProcessor() {}
195
196void GrGaussianConvolutionFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
197                                                                   GrProcessorKeyBuilder* b) const {
198    GrGLConvolutionEffect::GenKey(*this, caps, b);
199}
200
201GrGLSLFragmentProcessor* GrGaussianConvolutionFragmentProcessor::onCreateGLSLInstance() const {
202    return new GrGLConvolutionEffect;
203}
204
205bool GrGaussianConvolutionFragmentProcessor::onIsEqual(const GrFragmentProcessor& sBase) const {
206    const GrGaussianConvolutionFragmentProcessor& s =
207            sBase.cast<GrGaussianConvolutionFragmentProcessor>();
208    return (this->radius() == s.radius() && this->direction() == s.direction() &&
209            this->useBounds() == s.useBounds() &&
210            0 == memcmp(fBounds, s.fBounds, sizeof(fBounds)) &&
211            0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
212}
213
214///////////////////////////////////////////////////////////////////////////////
215
216GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrGaussianConvolutionFragmentProcessor);
217
218#if GR_TEST_UTILS
219sk_sp<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::TestCreate(
220        GrProcessorTestData* d) {
221    int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
222                                        : GrProcessorUnitTest::kAlphaTextureIdx;
223    sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
224
225    bool useBounds = d->fRandom->nextBool();
226    int bounds[2];
227
228    Direction dir;
229    if (d->fRandom->nextBool()) {
230        dir = kX_Direction;
231        bounds[0] = d->fRandom->nextRangeU(0, proxy->width()-1);
232        bounds[1] = d->fRandom->nextRangeU(bounds[0], proxy->width()-1);
233    } else {
234        dir = kY_Direction;
235        bounds[0] = d->fRandom->nextRangeU(0, proxy->height()-1);
236        bounds[1] = d->fRandom->nextRangeU(bounds[0], proxy->height()-1);
237    }
238
239    int radius = d->fRandom->nextRangeU(1, kMaxKernelRadius);
240    float sigma = radius / 3.f;
241
242    return GrGaussianConvolutionFragmentProcessor::Make(
243            d->resourceProvider(), d->textureProxy(texIdx),
244            dir, radius, sigma, useBounds, bounds);
245}
246#endif
247