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