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