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