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 "GrTexture.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 19 using UniformHandle = GrGLSLProgramDataManager::UniformHandle; 20 using Direction = GrGaussianConvolutionFragmentProcessor::Direction; 21 22 class GrGLConvolutionEffect : public GrGLSLFragmentProcessor { 23 public: 24 void emitCode(EmitArgs&) override; 25 26 static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); 27 28 protected: 29 void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; 30 31 private: 32 UniformHandle fKernelUni; 33 UniformHandle fImageIncrementUni; 34 UniformHandle fBoundsUni; 35 36 typedef GrGLSLFragmentProcessor INHERITED; 37 }; 38 39 void GrGLConvolutionEffect::emitCode(EmitArgs& args) { 40 const GrGaussianConvolutionFragmentProcessor& ce = 41 args.fFp.cast<GrGaussianConvolutionFragmentProcessor>(); 42 43 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 44 fImageIncrementUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, 45 "ImageIncrement"); 46 if (ce.useBounds()) { 47 fBoundsUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, 48 "Bounds"); 49 } 50 51 int width = ce.width(); 52 53 int arrayCount = (width + 3) / 4; 54 SkASSERT(4 * arrayCount >= width); 55 56 fKernelUni = uniformHandler->addUniformArray(kFragment_GrShaderFlag, kHalf4_GrSLType, 57 "Kernel", arrayCount); 58 59 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; 60 SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); 61 62 fragBuilder->codeAppendf("%s = half4(0, 0, 0, 0);", args.fOutputColor); 63 64 const GrShaderVar& kernel = uniformHandler->getUniformVariable(fKernelUni); 65 const char* imgInc = uniformHandler->getUniformCStr(fImageIncrementUni); 66 67 fragBuilder->codeAppendf("float2 coord = %s - %d.0 * %s;", coords2D.c_str(), ce.radius(), imgInc); 68 fragBuilder->codeAppend("float2 coordSampled = half2(0, 0);"); 69 70 // Manually unroll loop because some drivers don't; yields 20-30% speedup. 71 const char* kVecSuffix[4] = {".x", ".y", ".z", ".w"}; 72 for (int i = 0; i < width; i++) { 73 SkString index; 74 SkString kernelIndex; 75 index.appendS32(i / 4); 76 kernel.appendArrayAccess(index.c_str(), &kernelIndex); 77 kernelIndex.append(kVecSuffix[i & 0x3]); 78 79 fragBuilder->codeAppend("coordSampled = coord;"); 80 if (ce.useBounds()) { 81 // We used to compute a bool indicating whether we're in bounds or not, cast it to a 82 // float, and then mul weight*texture_sample by the float. However, the Adreno 430 seems 83 // to have a bug that caused corruption. 84 const char* bounds = uniformHandler->getUniformCStr(fBoundsUni); 85 const char* component = ce.direction() == Direction::kY ? "y" : "x"; 86 87 switch (ce.mode()) { 88 case GrTextureDomain::kClamp_Mode: { 89 fragBuilder->codeAppendf("coordSampled.%s = clamp(coord.%s, %s.x, %s.y);\n", 90 component, component, bounds, bounds); 91 break; 92 } 93 case GrTextureDomain::kRepeat_Mode: { 94 fragBuilder->codeAppendf("coordSampled.%s = " 95 "mod(coord.%s - %s.x, %s.y - %s.x) + %s.x;\n", 96 component, component, bounds, bounds, bounds, bounds); 97 break; 98 } 99 case GrTextureDomain::kDecal_Mode: { 100 fragBuilder->codeAppendf("if (coord.%s >= %s.x && coord.%s <= %s.y) {", 101 component, bounds, component, bounds); 102 break; 103 } 104 default: { 105 SK_ABORT("Unsupported operation."); 106 } 107 } 108 } 109 fragBuilder->codeAppendf("%s += ", args.fOutputColor); 110 fragBuilder->appendTextureLookup(args.fTexSamplers[0], "coordSampled"); 111 fragBuilder->codeAppendf(" * %s;\n", kernelIndex.c_str()); 112 if (GrTextureDomain::kDecal_Mode == ce.mode()) { 113 fragBuilder->codeAppend("}"); 114 } 115 fragBuilder->codeAppendf("coord += %s;\n", imgInc); 116 } 117 fragBuilder->codeAppendf("%s *= %s;\n", args.fOutputColor, args.fInputColor); 118 } 119 120 void GrGLConvolutionEffect::onSetData(const GrGLSLProgramDataManager& pdman, 121 const GrFragmentProcessor& processor) { 122 const GrGaussianConvolutionFragmentProcessor& conv = 123 processor.cast<GrGaussianConvolutionFragmentProcessor>(); 124 GrSurfaceProxy* proxy = conv.textureSampler(0).proxy(); 125 GrTexture& texture = *proxy->priv().peekTexture(); 126 127 float imageIncrement[2] = {0}; 128 float ySign = proxy->origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f; 129 switch (conv.direction()) { 130 case Direction::kX: 131 imageIncrement[0] = 1.0f / texture.width(); 132 break; 133 case Direction::kY: 134 imageIncrement[1] = ySign / texture.height(); 135 break; 136 default: 137 SK_ABORT("Unknown filter direction."); 138 } 139 pdman.set2fv(fImageIncrementUni, 1, imageIncrement); 140 if (conv.useBounds()) { 141 float bounds[2] = {0}; 142 bounds[0] = conv.bounds()[0]; 143 bounds[1] = conv.bounds()[1]; 144 if (GrTextureDomain::kClamp_Mode == conv.mode()) { 145 bounds[0] += SK_ScalarHalf; 146 bounds[1] -= SK_ScalarHalf; 147 } 148 if (Direction::kX == conv.direction()) { 149 SkScalar inv = SkScalarInvert(SkIntToScalar(texture.width())); 150 pdman.set2f(fBoundsUni, inv * bounds[0], inv * bounds[1]); 151 } else { 152 SkScalar inv = SkScalarInvert(SkIntToScalar(texture.height())); 153 if (proxy->origin() != kTopLeft_GrSurfaceOrigin) { 154 pdman.set2f(fBoundsUni, 1.0f - (inv * bounds[1]), 1.0f - (inv * bounds[0])); 155 } else { 156 pdman.set2f(fBoundsUni, inv * bounds[1], inv * bounds[0]); 157 } 158 } 159 } 160 int width = conv.width(); 161 162 int arrayCount = (width + 3) / 4; 163 SkASSERT(4 * arrayCount >= width); 164 pdman.set4fv(fKernelUni, arrayCount, conv.kernel()); 165 } 166 167 void GrGLConvolutionEffect::GenKey(const GrProcessor& processor, const GrShaderCaps&, 168 GrProcessorKeyBuilder* b) { 169 const GrGaussianConvolutionFragmentProcessor& conv = 170 processor.cast<GrGaussianConvolutionFragmentProcessor>(); 171 uint32_t key = conv.radius(); 172 key <<= 3; 173 key |= Direction::kY == conv.direction() ? 0x4 : 0x0; 174 key |= static_cast<uint32_t>(conv.mode()); 175 b->add32(key); 176 } 177 178 /////////////////////////////////////////////////////////////////////////////// 179 static void fill_in_1D_gaussian_kernel(float* kernel, int width, float gaussianSigma, int radius) { 180 const float denom = 1.0f / (2.0f * gaussianSigma * gaussianSigma); 181 182 float sum = 0.0f; 183 for (int i = 0; i < width; ++i) { 184 float x = static_cast<float>(i - radius); 185 // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian 186 // is dropped here, since we renormalize the kernel below. 187 kernel[i] = sk_float_exp(-x * x * denom); 188 sum += kernel[i]; 189 } 190 // Normalize the kernel 191 float scale = 1.0f / sum; 192 for (int i = 0; i < width; ++i) { 193 kernel[i] *= scale; 194 } 195 } 196 197 GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor( 198 sk_sp<GrTextureProxy> proxy, 199 Direction direction, 200 int radius, 201 float gaussianSigma, 202 GrTextureDomain::Mode mode, 203 int bounds[2]) 204 : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID, 205 ModulateByConfigOptimizationFlags(proxy->config())) 206 , fCoordTransform(proxy.get()) 207 , fTextureSampler(std::move(proxy)) 208 , fRadius(radius) 209 , fDirection(direction) 210 , fMode(mode) { 211 this->addCoordTransform(&fCoordTransform); 212 this->addTextureSampler(&fTextureSampler); 213 SkASSERT(radius <= kMaxKernelRadius); 214 215 fill_in_1D_gaussian_kernel(fKernel, this->width(), gaussianSigma, this->radius()); 216 217 memcpy(fBounds, bounds, sizeof(fBounds)); 218 } 219 220 GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor( 221 const GrGaussianConvolutionFragmentProcessor& that) 222 : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID, that.optimizationFlags()) 223 , fCoordTransform(that.fCoordTransform) 224 , fTextureSampler(that.fTextureSampler) 225 , fRadius(that.fRadius) 226 , fDirection(that.fDirection) 227 , fMode(that.fMode) { 228 this->addCoordTransform(&fCoordTransform); 229 this->addTextureSampler(&fTextureSampler); 230 memcpy(fKernel, that.fKernel, that.width() * sizeof(float)); 231 memcpy(fBounds, that.fBounds, sizeof(fBounds)); 232 } 233 234 void GrGaussianConvolutionFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, 235 GrProcessorKeyBuilder* b) const { 236 GrGLConvolutionEffect::GenKey(*this, caps, b); 237 } 238 239 GrGLSLFragmentProcessor* GrGaussianConvolutionFragmentProcessor::onCreateGLSLInstance() const { 240 return new GrGLConvolutionEffect; 241 } 242 243 bool GrGaussianConvolutionFragmentProcessor::onIsEqual(const GrFragmentProcessor& sBase) const { 244 const GrGaussianConvolutionFragmentProcessor& s = 245 sBase.cast<GrGaussianConvolutionFragmentProcessor>(); 246 return (this->radius() == s.radius() && this->direction() == s.direction() && 247 this->mode() == s.mode() && 248 0 == memcmp(fBounds, s.fBounds, sizeof(fBounds)) && 249 0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float))); 250 } 251 252 /////////////////////////////////////////////////////////////////////////////// 253 254 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrGaussianConvolutionFragmentProcessor); 255 256 #if GR_TEST_UTILS 257 std::unique_ptr<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::TestCreate( 258 GrProcessorTestData* d) { 259 int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx 260 : GrProcessorUnitTest::kAlphaTextureIdx; 261 sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx); 262 263 int bounds[2]; 264 int modeIdx = d->fRandom->nextRangeU(0, GrTextureDomain::kModeCount-1); 265 266 Direction dir; 267 if (d->fRandom->nextBool()) { 268 dir = Direction::kX; 269 bounds[0] = d->fRandom->nextRangeU(0, proxy->width()-1); 270 bounds[1] = d->fRandom->nextRangeU(bounds[0], proxy->width()-1); 271 } else { 272 dir = Direction::kY; 273 bounds[0] = d->fRandom->nextRangeU(0, proxy->height()-1); 274 bounds[1] = d->fRandom->nextRangeU(bounds[0], proxy->height()-1); 275 } 276 277 int radius = d->fRandom->nextRangeU(1, kMaxKernelRadius); 278 float sigma = radius / 3.f; 279 280 return GrGaussianConvolutionFragmentProcessor::Make( 281 d->textureProxy(texIdx), 282 dir, radius, sigma, static_cast<GrTextureDomain::Mode>(modeIdx), bounds); 283 } 284 #endif 285