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 "gl/builders/GrGLProgramBuilder.h" 9#include "GrConvolutionEffect.h" 10#include "gl/GrGLProcessor.h" 11#include "gl/GrGLSL.h" 12#include "gl/GrGLTexture.h" 13#include "GrTBackendProcessorFactory.h" 14 15// For brevity 16typedef GrGLProgramDataManager::UniformHandle UniformHandle; 17 18class GrGLConvolutionEffect : public GrGLFragmentProcessor { 19public: 20 GrGLConvolutionEffect(const GrBackendProcessorFactory&, const GrProcessor&); 21 22 virtual void emitCode(GrGLProgramBuilder*, 23 const GrFragmentProcessor&, 24 const GrProcessorKey&, 25 const char* outputColor, 26 const char* inputColor, 27 const TransformedCoordsArray&, 28 const TextureSamplerArray&) SK_OVERRIDE; 29 30 virtual void setData(const GrGLProgramDataManager& pdman, const GrProcessor&) SK_OVERRIDE; 31 32 static inline void GenKey(const GrProcessor&, const GrGLCaps&, GrProcessorKeyBuilder*); 33 34private: 35 int width() const { return Gr1DKernelEffect::WidthFromRadius(fRadius); } 36 bool useBounds() const { return fUseBounds; } 37 Gr1DKernelEffect::Direction direction() const { return fDirection; } 38 39 int fRadius; 40 bool fUseBounds; 41 Gr1DKernelEffect::Direction fDirection; 42 UniformHandle fKernelUni; 43 UniformHandle fImageIncrementUni; 44 UniformHandle fBoundsUni; 45 46 typedef GrGLFragmentProcessor INHERITED; 47}; 48 49GrGLConvolutionEffect::GrGLConvolutionEffect(const GrBackendProcessorFactory& factory, 50 const GrProcessor& processor) 51 : INHERITED(factory) { 52 const GrConvolutionEffect& c = processor.cast<GrConvolutionEffect>(); 53 fRadius = c.radius(); 54 fUseBounds = c.useBounds(); 55 fDirection = c.direction(); 56} 57 58void GrGLConvolutionEffect::emitCode(GrGLProgramBuilder* builder, 59 const GrFragmentProcessor&, 60 const GrProcessorKey& key, 61 const char* outputColor, 62 const char* inputColor, 63 const TransformedCoordsArray& coords, 64 const TextureSamplerArray& samplers) { 65 fImageIncrementUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility, 66 kVec2f_GrSLType, "ImageIncrement"); 67 if (this->useBounds()) { 68 fBoundsUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility, 69 kVec2f_GrSLType, "Bounds"); 70 } 71 fKernelUni = builder->addUniformArray(GrGLProgramBuilder::kFragment_Visibility, 72 kFloat_GrSLType, "Kernel", this->width()); 73 74 GrGLFragmentShaderBuilder* fsBuilder = builder->getFragmentShaderBuilder(); 75 SkString coords2D = fsBuilder->ensureFSCoords2D(coords, 0); 76 77 fsBuilder->codeAppendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor); 78 79 int width = this->width(); 80 const GrGLShaderVar& kernel = builder->getUniformVariable(fKernelUni); 81 const char* imgInc = builder->getUniformCStr(fImageIncrementUni); 82 83 fsBuilder->codeAppendf("\t\tvec2 coord = %s - %d.0 * %s;\n", coords2D.c_str(), fRadius, imgInc); 84 85 // Manually unroll loop because some drivers don't; yields 20-30% speedup. 86 for (int i = 0; i < width; i++) { 87 SkString index; 88 SkString kernelIndex; 89 index.appendS32(i); 90 kernel.appendArrayAccess(index.c_str(), &kernelIndex); 91 fsBuilder->codeAppendf("\t\t%s += ", outputColor); 92 fsBuilder->appendTextureLookup(samplers[0], "coord"); 93 if (this->useBounds()) { 94 const char* bounds = builder->getUniformCStr(fBoundsUni); 95 const char* component = this->direction() == Gr1DKernelEffect::kY_Direction ? "y" : "x"; 96 fsBuilder->codeAppendf(" * float(coord.%s >= %s.x && coord.%s <= %s.y)", 97 component, bounds, component, bounds); 98 } 99 fsBuilder->codeAppendf(" * %s;\n", kernelIndex.c_str()); 100 fsBuilder->codeAppendf("\t\tcoord += %s;\n", imgInc); 101 } 102 103 SkString modulate; 104 GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor); 105 fsBuilder->codeAppend(modulate.c_str()); 106} 107 108void GrGLConvolutionEffect::setData(const GrGLProgramDataManager& pdman, 109 const GrProcessor& processor) { 110 const GrConvolutionEffect& conv = processor.cast<GrConvolutionEffect>(); 111 GrTexture& texture = *conv.texture(0); 112 // the code we generated was for a specific kernel radius 113 SkASSERT(conv.radius() == fRadius); 114 float imageIncrement[2] = { 0 }; 115 float ySign = texture.origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f; 116 switch (conv.direction()) { 117 case Gr1DKernelEffect::kX_Direction: 118 imageIncrement[0] = 1.0f / texture.width(); 119 break; 120 case Gr1DKernelEffect::kY_Direction: 121 imageIncrement[1] = ySign / texture.height(); 122 break; 123 default: 124 SkFAIL("Unknown filter direction."); 125 } 126 pdman.set2fv(fImageIncrementUni, 1, imageIncrement); 127 if (conv.useBounds()) { 128 const float* bounds = conv.bounds(); 129 if (Gr1DKernelEffect::kY_Direction == conv.direction() && 130 texture.origin() != kTopLeft_GrSurfaceOrigin) { 131 pdman.set2f(fBoundsUni, 1.0f - bounds[1], 1.0f - bounds[0]); 132 } else { 133 pdman.set2f(fBoundsUni, bounds[0], bounds[1]); 134 } 135 } 136 pdman.set1fv(fKernelUni, this->width(), conv.kernel()); 137} 138 139void GrGLConvolutionEffect::GenKey(const GrProcessor& processor, const GrGLCaps&, 140 GrProcessorKeyBuilder* b) { 141 const GrConvolutionEffect& conv = processor.cast<GrConvolutionEffect>(); 142 uint32_t key = conv.radius(); 143 key <<= 2; 144 if (conv.useBounds()) { 145 key |= 0x2; 146 key |= GrConvolutionEffect::kY_Direction == conv.direction() ? 0x1 : 0x0; 147 } 148 b->add32(key); 149} 150 151/////////////////////////////////////////////////////////////////////////////// 152 153GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture, 154 Direction direction, 155 int radius, 156 const float* kernel, 157 bool useBounds, 158 float bounds[2]) 159 : Gr1DKernelEffect(texture, direction, radius), fUseBounds(useBounds) { 160 SkASSERT(radius <= kMaxKernelRadius); 161 SkASSERT(kernel); 162 int width = this->width(); 163 for (int i = 0; i < width; i++) { 164 fKernel[i] = kernel[i]; 165 } 166 memcpy(fBounds, bounds, sizeof(fBounds)); 167} 168 169GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture, 170 Direction direction, 171 int radius, 172 float gaussianSigma, 173 bool useBounds, 174 float bounds[2]) 175 : Gr1DKernelEffect(texture, direction, radius), fUseBounds(useBounds) { 176 SkASSERT(radius <= kMaxKernelRadius); 177 int width = this->width(); 178 179 float sum = 0.0f; 180 float denom = 1.0f / (2.0f * gaussianSigma * gaussianSigma); 181 for (int i = 0; i < width; ++i) { 182 float x = static_cast<float>(i - this->radius()); 183 // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian 184 // is dropped here, since we renormalize the kernel below. 185 fKernel[i] = sk_float_exp(- x * x * denom); 186 sum += fKernel[i]; 187 } 188 // Normalize the kernel 189 float scale = 1.0f / sum; 190 for (int i = 0; i < width; ++i) { 191 fKernel[i] *= scale; 192 } 193 memcpy(fBounds, bounds, sizeof(fBounds)); 194} 195 196GrConvolutionEffect::~GrConvolutionEffect() { 197} 198 199const GrBackendFragmentProcessorFactory& GrConvolutionEffect::getFactory() const { 200 return GrTBackendFragmentProcessorFactory<GrConvolutionEffect>::getInstance(); 201} 202 203bool GrConvolutionEffect::onIsEqual(const GrProcessor& sBase) const { 204 const GrConvolutionEffect& s = sBase.cast<GrConvolutionEffect>(); 205 return (this->texture(0) == s.texture(0) && 206 this->radius() == s.radius() && 207 this->direction() == s.direction() && 208 this->useBounds() == s.useBounds() && 209 0 == memcmp(fBounds, s.fBounds, sizeof(fBounds)) && 210 0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float))); 211} 212 213/////////////////////////////////////////////////////////////////////////////// 214 215GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrConvolutionEffect); 216 217GrFragmentProcessor* GrConvolutionEffect::TestCreate(SkRandom* random, 218 GrContext*, 219 const GrDrawTargetCaps&, 220 GrTexture* textures[]) { 221 int texIdx = random->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx : 222 GrProcessorUnitTest::kAlphaTextureIdx; 223 Direction dir = random->nextBool() ? kX_Direction : kY_Direction; 224 int radius = random->nextRangeU(1, kMaxKernelRadius); 225 float kernel[kMaxKernelWidth]; 226 for (size_t i = 0; i < SK_ARRAY_COUNT(kernel); ++i) { 227 kernel[i] = random->nextSScalar1(); 228 } 229 float bounds[2]; 230 for (size_t i = 0; i < SK_ARRAY_COUNT(bounds); ++i) { 231 bounds[i] = random->nextF(); 232 } 233 234 bool useBounds = random->nextBool(); 235 return GrConvolutionEffect::Create(textures[texIdx], 236 dir, 237 radius, 238 kernel, 239 useBounds, 240 bounds); 241} 242