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