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 14 class GrGLMatrixConvolutionEffect : public GrGLFragmentProcessor { 15 public: 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 30 private: 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 46 GrGLMatrixConvolutionEffect::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 54 void 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 123 void 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 133 void 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 151 GrMatrixConvolutionEffect::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 173 GrMatrixConvolutionEffect::~GrMatrixConvolutionEffect() { 174 } 175 176 const GrBackendFragmentProcessorFactory& GrMatrixConvolutionEffect::getFactory() const { 177 return GrTBackendFragmentProcessorFactory<GrMatrixConvolutionEffect>::getInstance(); 178 } 179 180 bool 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 194 GrFragmentProcessor* 195 GrMatrixConvolutionEffect::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 242 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrMatrixConvolutionEffect); 243 244 GrFragmentProcessor* 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