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