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