GrCircleBlurFragmentProcessor.cpp revision 3865711259e25a90a1d72480f848863ada202067
1/* 2 * Copyright 2015 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 "GrCircleBlurFragmentProcessor.h" 9 10#if SK_SUPPORT_GPU 11 12#include "GrContext.h" 13#include "GrInvariantOutput.h" 14#include "GrTextureProvider.h" 15 16#include "glsl/GrGLSLFragmentProcessor.h" 17#include "glsl/GrGLSLFragmentShaderBuilder.h" 18#include "glsl/GrGLSLProgramDataManager.h" 19#include "glsl/GrGLSLUniformHandler.h" 20 21#include "SkFixed.h" 22 23class GrCircleBlurFragmentProcessor::GLSLProcessor : public GrGLSLFragmentProcessor { 24public: 25 void emitCode(EmitArgs&) override; 26 27protected: 28 void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override; 29 30private: 31 GrGLSLProgramDataManager::UniformHandle fDataUniform; 32 33 typedef GrGLSLFragmentProcessor INHERITED; 34}; 35 36void GrCircleBlurFragmentProcessor::GLSLProcessor::emitCode(EmitArgs& args) { 37 const char *dataName; 38 39 // The data is formatted as: 40 // x,y - the center of the circle 41 // z - inner radius that should map to 0th entry in the texture. 42 // w - the inverse of the distance over which the texture is stretched. 43 fDataUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, 44 kVec4f_GrSLType, 45 kDefault_GrSLPrecision, 46 "data", 47 &dataName); 48 49 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; 50 51 if (args.fInputColor) { 52 fragBuilder->codeAppendf("vec4 src=%s;", args.fInputColor); 53 } else { 54 fragBuilder->codeAppendf("vec4 src=vec4(1);"); 55 } 56 57 // We just want to compute "(length(vec) - %s.z + 0.5) * %s.w" but need to rearrange 58 // for precision. 59 fragBuilder->codeAppendf("vec2 vec = vec2( (sk_FragCoord.x - %s.x) * %s.w, " 60 "(sk_FragCoord.y - %s.y) * %s.w );", 61 dataName, dataName, dataName, dataName); 62 fragBuilder->codeAppendf("float dist = length(vec) + (0.5 - %s.z) * %s.w;", 63 dataName, dataName); 64 65 fragBuilder->codeAppendf("float intensity = "); 66 fragBuilder->appendTextureLookup(args.fTexSamplers[0], "vec2(dist, 0.5)"); 67 fragBuilder->codeAppend(".a;"); 68 69 fragBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor ); 70} 71 72void GrCircleBlurFragmentProcessor::GLSLProcessor::onSetData(const GrGLSLProgramDataManager& pdman, 73 const GrProcessor& proc) { 74 const GrCircleBlurFragmentProcessor& cbfp = proc.cast<GrCircleBlurFragmentProcessor>(); 75 const SkRect& circle = cbfp.fCircle; 76 77 // The data is formatted as: 78 // x,y - the center of the circle 79 // z - inner radius that should map to 0th entry in the texture. 80 // w - the inverse of the distance over which the profile texture is stretched. 81 pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.fSolidRadius, 82 1.f / cbfp.fTextureRadius); 83} 84 85/////////////////////////////////////////////////////////////////////////////// 86 87GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circle, 88 float textureRadius, 89 float solidRadius, 90 GrTexture* blurProfile) 91 : INHERITED(kModulatesInput_OptimizationFlag) 92 , fCircle(circle) 93 , fSolidRadius(solidRadius) 94 , fTextureRadius(textureRadius) 95 , fBlurProfileSampler(blurProfile, GrSamplerParams::kBilerp_FilterMode) { 96 this->initClassID<GrCircleBlurFragmentProcessor>(); 97 this->addTextureSampler(&fBlurProfileSampler); 98} 99 100GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() const { 101 return new GLSLProcessor; 102} 103 104void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, 105 GrProcessorKeyBuilder* b) const { 106 // The code for this processor is always the same so there is nothing to add to the key. 107 return; 108} 109 110void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const { 111 inout->mulByUnknownSingleComponent(); 112} 113 114// Computes an unnormalized half kernel (right side). Returns the summation of all the half kernel 115// values. 116static float make_unnormalized_half_kernel(float* halfKernel, int halfKernelSize, float sigma) { 117 const float invSigma = 1.f / sigma; 118 const float b = -0.5f * invSigma * invSigma; 119 float tot = 0.0f; 120 // Compute half kernel values at half pixel steps out from the center. 121 float t = 0.5f; 122 for (int i = 0; i < halfKernelSize; ++i) { 123 float value = expf(t * t * b); 124 tot += value; 125 halfKernel[i] = value; 126 t += 1.f; 127 } 128 return tot; 129} 130 131// Create a Gaussian half-kernel (right side) and a summed area table given a sigma and number of 132// discrete steps. The half kernel is normalized to sum to 0.5. 133static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHalfKernel, 134 int halfKernelSize, float sigma) { 135 // The half kernel should sum to 0.5 not 1.0. 136 const float tot = 2.f * make_unnormalized_half_kernel(halfKernel, halfKernelSize, sigma); 137 float sum = 0.f; 138 for (int i = 0; i < halfKernelSize; ++i) { 139 halfKernel[i] /= tot; 140 sum += halfKernel[i]; 141 summedHalfKernel[i] = sum; 142 } 143} 144 145// Applies the 1D half kernel vertically at points along the x axis to a circle centered at the 146// origin with radius circleR. 147void apply_kernel_in_y(float* results, int numSteps, float firstX, float circleR, 148 int halfKernelSize, const float* summedHalfKernelTable) { 149 float x = firstX; 150 for (int i = 0; i < numSteps; ++i, x += 1.f) { 151 if (x < -circleR || x > circleR) { 152 results[i] = 0; 153 continue; 154 } 155 float y = sqrtf(circleR * circleR - x * x); 156 // In the column at x we exit the circle at +y and -y 157 // The summed table entry j is actually reflects an offset of j + 0.5. 158 y -= 0.5f; 159 int yInt = SkScalarFloorToInt(y); 160 SkASSERT(yInt >= -1); 161 if (y < 0) { 162 results[i] = (y + 0.5f) * summedHalfKernelTable[0]; 163 } else if (yInt >= halfKernelSize - 1) { 164 results[i] = 0.5f; 165 } else { 166 float yFrac = y - yInt; 167 results[i] = (1.f - yFrac) * summedHalfKernelTable[yInt] + 168 yFrac * summedHalfKernelTable[yInt + 1]; 169 } 170 } 171} 172 173// Apply a Gaussian at point (evalX, 0) to a circle centered at the origin with radius circleR. 174// This relies on having a half kernel computed for the Gaussian and a table of applications of 175// the half kernel in y to columns at (evalX - halfKernel, evalX - halfKernel + 1, ..., evalX + 176// halfKernel) passed in as yKernelEvaluations. 177static uint8_t eval_at(float evalX, float circleR, const float* halfKernel, int halfKernelSize, 178 const float* yKernelEvaluations) { 179 float acc = 0; 180 181 float x = evalX - halfKernelSize; 182 for (int i = 0; i < halfKernelSize; ++i, x += 1.f) { 183 if (x < -circleR || x > circleR) { 184 continue; 185 } 186 float verticalEval = yKernelEvaluations[i]; 187 acc += verticalEval * halfKernel[halfKernelSize - i - 1]; 188 } 189 for (int i = 0; i < halfKernelSize; ++i, x += 1.f) { 190 if (x < -circleR || x > circleR) { 191 continue; 192 } 193 float verticalEval = yKernelEvaluations[i + halfKernelSize]; 194 acc += verticalEval * halfKernel[i]; 195 } 196 // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about the 197 // x axis). 198 return SkUnitScalarClampToByte(2.f * acc); 199} 200 201// This function creates a profile of a blurred circle. It does this by computing a kernel for 202// half the Gaussian and a matching summed area table. The summed area table is used to compute 203// an array of vertical applications of the half kernel to the circle along the x axis. The table 204// of y evaluations has 2 * k + n entries where k is the size of the half kernel and n is the size 205// of the profile being computed. Then for each of the n profile entries we walk out k steps in each 206// horizontal direction multiplying the corresponding y evaluation by the half kernel entry and 207// sum these values to compute the profile entry. 208static uint8_t* create_circle_profile(float sigma, float circleR, int profileTextureWidth) { 209 const int numSteps = profileTextureWidth; 210 uint8_t* weights = new uint8_t[numSteps]; 211 212 // The full kernel is 6 sigmas wide. 213 int halfKernelSize = SkScalarCeilToInt(6.0f*sigma); 214 // round up to next multiple of 2 and then divide by 2 215 halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1; 216 217 // Number of x steps at which to apply kernel in y to cover all the profile samples in x. 218 int numYSteps = numSteps + 2 * halfKernelSize; 219 220 SkAutoTArray<float> bulkAlloc(halfKernelSize + halfKernelSize + numYSteps); 221 float* halfKernel = bulkAlloc.get(); 222 float* summedKernel = bulkAlloc.get() + halfKernelSize; 223 float* yEvals = bulkAlloc.get() + 2 * halfKernelSize; 224 make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize, sigma); 225 226 float firstX = -halfKernelSize + 0.5f; 227 apply_kernel_in_y(yEvals, numYSteps, firstX, circleR, halfKernelSize, summedKernel); 228 229 for (int i = 0; i < numSteps - 1; ++i) { 230 float evalX = i + 0.5f; 231 weights[i] = eval_at(evalX, circleR, halfKernel, halfKernelSize, yEvals + i); 232 } 233 // Ensure the tail of the Gaussian goes to zero. 234 weights[numSteps - 1] = 0; 235 return weights; 236} 237 238static uint8_t* create_half_plane_profile(int profileWidth) { 239 SkASSERT(!(profileWidth & 0x1)); 240 // The full kernel is 6 sigmas wide. 241 float sigma = profileWidth / 6.f; 242 int halfKernelSize = profileWidth / 2; 243 244 SkAutoTArray<float> halfKernel(halfKernelSize); 245 uint8_t* profile = new uint8_t[profileWidth]; 246 247 // The half kernel should sum to 0.5. 248 const float tot = 2.f * make_unnormalized_half_kernel(halfKernel.get(), halfKernelSize, sigma); 249 float sum = 0.f; 250 // Populate the profile from the right edge to the middle. 251 for (int i = 0; i < halfKernelSize; ++i) { 252 halfKernel[halfKernelSize - i - 1] /= tot; 253 sum += halfKernel[halfKernelSize - i - 1]; 254 profile[profileWidth - i - 1] = SkUnitScalarClampToByte(sum); 255 } 256 // Populate the profile from the middle to the left edge (by flipping the half kernel and 257 // continuing the summation). 258 for (int i = 0; i < halfKernelSize; ++i) { 259 sum += halfKernel[i]; 260 profile[halfKernelSize - i - 1] = SkUnitScalarClampToByte(sum); 261 } 262 // Ensure tail goes to 0. 263 profile[profileWidth - 1] = 0; 264 return profile; 265} 266 267static GrTexture* create_profile_texture(GrTextureProvider* textureProvider, const SkRect& circle, 268 float sigma, float* solidRadius, float* textureRadius) { 269 float circleR = circle.width() / 2.0f; 270 // Profile textures are cached by the ratio of sigma to circle radius and by the size of the 271 // profile texture (binned by powers of 2). 272 SkScalar sigmaToCircleRRatio = sigma / circleR; 273 // When sigma is really small this becomes a equivalent to convolving a Gaussian with a half- 274 // plane. Similarly, in the extreme high ratio cases circle becomes a point WRT to the Guassian 275 // and the profile texture is a just a Gaussian evaluation. However, we haven't yet implemented 276 // this latter optimization. 277 sigmaToCircleRRatio = SkTMin(sigmaToCircleRRatio, 8.f); 278 SkFixed sigmaToCircleRRatioFixed; 279 static const SkScalar kHalfPlaneThreshold = 0.1f; 280 bool useHalfPlaneApprox = false; 281 if (sigmaToCircleRRatio <= kHalfPlaneThreshold) { 282 useHalfPlaneApprox = true; 283 sigmaToCircleRRatioFixed = 0; 284 *solidRadius = circleR - 3 * sigma; 285 *textureRadius = 6 * sigma; 286 } else { 287 // Convert to fixed point for the key. 288 sigmaToCircleRRatioFixed = SkScalarToFixed(sigmaToCircleRRatio); 289 // We shave off some bits to reduce the number of unique entries. We could probably shave 290 // off more than we do. 291 sigmaToCircleRRatioFixed &= ~0xff; 292 sigmaToCircleRRatio = SkFixedToScalar(sigmaToCircleRRatioFixed); 293 sigma = circleR * sigmaToCircleRRatio; 294 *solidRadius = 0; 295 *textureRadius = circleR + 3 * sigma; 296 } 297 298 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); 299 GrUniqueKey key; 300 GrUniqueKey::Builder builder(&key, kDomain, 1); 301 builder[0] = sigmaToCircleRRatioFixed; 302 builder.finish(); 303 304 GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); 305 if (!blurProfile) { 306 static constexpr int kProfileTextureWidth = 512; 307 GrSurfaceDesc texDesc; 308 texDesc.fWidth = kProfileTextureWidth; 309 texDesc.fHeight = 1; 310 texDesc.fConfig = kAlpha_8_GrPixelConfig; 311 312 std::unique_ptr<uint8_t[]> profile(nullptr); 313 if (useHalfPlaneApprox) { 314 profile.reset(create_half_plane_profile(kProfileTextureWidth)); 315 } else { 316 // Rescale params to the size of the texture we're creating. 317 SkScalar scale = kProfileTextureWidth / *textureRadius; 318 profile.reset(create_circle_profile(sigma * scale, circleR * scale, 319 kProfileTextureWidth)); 320 } 321 322 blurProfile = textureProvider->createTexture(texDesc, SkBudgeted::kYes, profile.get(), 0); 323 if (blurProfile) { 324 textureProvider->assignUniqueKeyToTexture(key, blurProfile); 325 } 326 } 327 328 return blurProfile; 329} 330 331////////////////////////////////////////////////////////////////////////////// 332 333sk_sp<GrFragmentProcessor> GrCircleBlurFragmentProcessor::Make(GrTextureProvider*textureProvider, 334 const SkRect& circle, float sigma) { 335 float solidRadius; 336 float textureRadius; 337 sk_sp<GrTexture> profile(create_profile_texture(textureProvider, circle, sigma, 338 &solidRadius, &textureRadius)); 339 if (!profile) { 340 return nullptr; 341 } 342 return sk_sp<GrFragmentProcessor>( 343 new GrCircleBlurFragmentProcessor(circle, textureRadius, solidRadius, profile.get())); 344} 345 346////////////////////////////////////////////////////////////////////////////// 347 348GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); 349 350#if GR_TEST_UTILS 351sk_sp<GrFragmentProcessor> GrCircleBlurFragmentProcessor::TestCreate(GrProcessorTestData* d) { 352 SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f); 353 SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); 354 SkRect circle = SkRect::MakeWH(wh, wh); 355 return GrCircleBlurFragmentProcessor::Make(d->fContext->textureProvider(), circle, sigma); 356} 357#endif 358 359#endif 360