GrCircleBlurFragmentProcessor.cpp revision 64c4728c70001ed074fecf5c4e083781987b12e9
1 2/* 3 * Copyright 2015 Google Inc. 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9#include "GrCircleBlurFragmentProcessor.h" 10 11#if SK_SUPPORT_GPU 12 13#include "GrContext.h" 14#include "GrTextureProvider.h" 15 16#include "glsl/GrGLSLFragmentProcessor.h" 17#include "glsl/GrGLSLFragmentShaderBuilder.h" 18#include "glsl/GrGLSLProgramBuilder.h" 19#include "glsl/GrGLSLProgramDataManager.h" 20 21class GrGLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor { 22public: 23 GrGLCircleBlurFragmentProcessor(const GrProcessor&) {} 24 void emitCode(EmitArgs&) override; 25 26protected: 27 void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override; 28 29private: 30 GrGLSLProgramDataManager::UniformHandle fDataUniform; 31 32 typedef GrGLSLFragmentProcessor INHERITED; 33}; 34 35void GrGLCircleBlurFragmentProcessor::emitCode(EmitArgs& args) { 36 37 const char *dataName; 38 39 // The data is formatted as: 40 // x,y - the center of the circle 41 // z - the distance at which the intensity starts falling off (e.g., the start of the table) 42 // w - the size of the profile texture 43 fDataUniform = args.fBuilder->addUniform(GrGLSLProgramBuilder::kFragment_Visibility, 44 kVec4f_GrSLType, 45 kDefault_GrSLPrecision, 46 "data", 47 &dataName); 48 49 GrGLSLFragmentBuilder* fsBuilder = args.fBuilder->getFragmentShaderBuilder(); 50 const char *fragmentPos = fsBuilder->fragmentPosition(); 51 52 if (args.fInputColor) { 53 fsBuilder->codeAppendf("vec4 src=%s;", args.fInputColor); 54 } else { 55 fsBuilder->codeAppendf("vec4 src=vec4(1);"); 56 } 57 58 fsBuilder->codeAppendf("vec2 vec = %s.xy - %s.xy;", fragmentPos, dataName); 59 fsBuilder->codeAppendf("float dist = (length(vec) - %s.z + 0.5) / %s.w;", dataName, dataName); 60 61 fsBuilder->codeAppendf("float intensity = "); 62 fsBuilder->appendTextureLookup(args.fSamplers[0], "vec2(dist, 0.5)"); 63 fsBuilder->codeAppend(".a;"); 64 65 fsBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor ); 66} 67 68void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLSLProgramDataManager& pdman, 69 const GrProcessor& proc) { 70 const GrCircleBlurFragmentProcessor& cbfp = proc.cast<GrCircleBlurFragmentProcessor>(); 71 const SkRect& circle = cbfp.circle(); 72 73 // The data is formatted as: 74 // x,y - the center of the circle 75 // z - the distance at which the intensity starts falling off (e.g., the start of the table) 76 // w - the size of the profile texture 77 pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(), 78 SkIntToScalar(cbfp.profileSize())); 79} 80 81/////////////////////////////////////////////////////////////////////////////// 82 83GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circle, 84 float sigma, 85 float offset, 86 GrTexture* blurProfile) 87 : fCircle(circle) 88 , fSigma(sigma) 89 , fOffset(offset) 90 , fBlurProfileAccess(blurProfile, GrTextureParams::kBilerp_FilterMode) { 91 this->initClassID<GrCircleBlurFragmentProcessor>(); 92 this->addTextureAccess(&fBlurProfileAccess); 93 this->setWillReadFragmentPosition(); 94} 95 96GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLInstance() const { 97 return new GrGLCircleBlurFragmentProcessor(*this); 98} 99 100void GrCircleBlurFragmentProcessor::onGetGLProcessorKey(const GrGLSLCaps& caps, 101 GrProcessorKeyBuilder* b) const { 102 GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b); 103} 104 105void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const { 106 inout->mulByUnknownSingleComponent(); 107} 108 109// Evaluate an AA circle function centered at the origin with 'radius' at (x,y) 110static inline float disk(float x, float y, float radius) { 111 float distSq = x*x + y*y; 112 if (distSq <= (radius-0.5f)*(radius-0.5f)) { 113 return 1.0f; 114 } else if (distSq >= (radius+0.5f)*(radius+0.5f)) { 115 return 0.0f; 116 } else { 117 float ramp = radius + 0.5f - sqrtf(distSq); 118 SkASSERT(ramp >= 0.0f && ramp <= 1.0f); 119 return ramp; 120 } 121} 122 123// Create the top half of an even-sized Gaussian kernel 124static void make_half_kernel(float* kernel, int kernelWH, float sigma) { 125 SkASSERT(!(kernelWH & 1)); 126 127 const float kernelOff = (kernelWH-1)/2.0f; 128 129 float b = 1.0f / (2.0f * sigma * sigma); 130 // omit the scale term since we're just going to renormalize 131 132 float tot = 0.0f; 133 for (int y = 0; y < kernelWH/2; ++y) { 134 for (int x = 0; x < kernelWH/2; ++x) { 135 // TODO: use a cheap approximation of the 2D Guassian? 136 float x2 = (x-kernelOff) * (x-kernelOff); 137 float y2 = (y-kernelOff) * (y-kernelOff); 138 // The kernel is symmetric so only compute it once for both sides 139 kernel[y*kernelWH+(kernelWH-x-1)] = kernel[y*kernelWH+x] = expf(-(x2 + y2) * b); 140 tot += 2.0f * kernel[y*kernelWH+x]; 141 } 142 } 143 // Still normalize the half kernel to 1.0 (rather than 0.5) so we don't 144 // have to scale by 2.0 after convolution. 145 for (int y = 0; y < kernelWH/2; ++y) { 146 for (int x = 0; x < kernelWH; ++x) { 147 kernel[y*kernelWH+x] /= tot; 148 } 149 } 150} 151 152// Apply the half-kernel at 't' away from the center of the circle 153static uint8_t eval_at(float t, float halfWidth, float* halfKernel, int kernelWH) { 154 SkASSERT(!(kernelWH & 1)); 155 156 const float kernelOff = (kernelWH-1)/2.0f; 157 158 float acc = 0; 159 160 for (int y = 0; y < kernelWH/2; ++y) { 161 if (kernelOff-y > halfWidth+0.5f) { 162 // All disk() samples in this row will be 0.0f 163 continue; 164 } 165 166 for (int x = 0; x < kernelWH; ++x) { 167 float image = disk(t - kernelOff + x, -kernelOff + y, halfWidth); 168 float kernel = halfKernel[y*kernelWH+x]; 169 acc += kernel * image; 170 } 171 } 172 173 return SkUnitScalarClampToByte(acc); 174} 175 176static inline void compute_profile_offset_and_size(float halfWH, float sigma, 177 float* offset, int* size) { 178 179 if (3*sigma <= halfWH) { 180 // The circle is bigger than the Gaussian. In this case we know the interior of the 181 // blurred circle is solid. 182 *offset = halfWH - 3 * sigma; // This location maps to 0.5f in the weights texture. 183 // It should always be 255. 184 *size = SkScalarCeilToInt(6*sigma); 185 } else { 186 // The Gaussian is bigger than the circle. 187 *offset = 0.0f; 188 *size = SkScalarCeilToInt(halfWH + 3*sigma); 189 } 190} 191 192static uint8_t* create_profile(float halfWH, float sigma) { 193 194 int kernelWH = SkScalarCeilToInt(6.0f*sigma); 195 kernelWH = (kernelWH + 1) & ~1; // make it the next even number up 196 197 SkAutoTArray<float> halfKernel(kernelWH*kernelWH/2); 198 199 make_half_kernel(halfKernel.get(), kernelWH, sigma); 200 201 float offset; 202 int numSteps; 203 204 compute_profile_offset_and_size(halfWH, sigma, &offset, &numSteps); 205 206 uint8_t* weights = new uint8_t[numSteps]; 207 for (int i = 0; i < numSteps; ++i) { 208 weights[i] = eval_at(offset+i, halfWH, halfKernel.get(), kernelWH); 209 } 210 211 return weights; 212} 213 214GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( 215 GrTextureProvider* textureProvider, 216 const SkRect& circle, 217 float sigma, 218 float* offset) { 219 float halfWH = circle.width() / 2.0f; 220 221 int size; 222 compute_profile_offset_and_size(halfWH, sigma, offset, &size); 223 224 GrSurfaceDesc texDesc; 225 texDesc.fWidth = size; 226 texDesc.fHeight = 1; 227 texDesc.fConfig = kAlpha_8_GrPixelConfig; 228 229 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); 230 GrUniqueKey key; 231 GrUniqueKey::Builder builder(&key, kDomain, 2); 232 // The profile curve varies with both the sigma of the Gaussian and the size of the 233 // disk. Quantizing to 16.16 should be close enough though. 234 builder[0] = SkScalarToFixed(sigma); 235 builder[1] = SkScalarToFixed(halfWH); 236 builder.finish(); 237 238 GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); 239 240 if (!blurProfile) { 241 SkAutoTDeleteArray<uint8_t> profile(create_profile(halfWH, sigma)); 242 243 blurProfile = textureProvider->createTexture(texDesc, true, profile.get(), 0); 244 if (blurProfile) { 245 textureProvider->assignUniqueKeyToTexture(key, blurProfile); 246 } 247 } 248 249 return blurProfile; 250} 251 252GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); 253 254const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessorTestData* d) { 255 SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f); 256 SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); 257 SkRect circle = SkRect::MakeWH(wh, wh); 258 return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(), circle, sigma); 259} 260 261#endif 262