GrCircleBlurFragmentProcessor.cpp revision b525721907fa56fd20682116f7645b4d0a861b78
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 GrGLCircleBlurFragmentProcessor : 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 GrGLCircleBlurFragmentProcessor::emitCode(EmitArgs& args) { 37 38 const char *dataName; 39 40 // The data is formatted as: 41 // x,y - the center of the circle 42 // z - the distance at which the intensity starts falling off (e.g., the start of the table) 43 // w - the inverse of the profile texture size 44 fDataUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, 45 kVec4f_GrSLType, 46 kDefault_GrSLPrecision, 47 "data", 48 &dataName); 49 50 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; 51 const char *fragmentPos = fragBuilder->fragmentPosition(); 52 53 if (args.fInputColor) { 54 fragBuilder->codeAppendf("vec4 src=%s;", args.fInputColor); 55 } else { 56 fragBuilder->codeAppendf("vec4 src=vec4(1);"); 57 } 58 59 // We just want to compute "length(vec) - %s.z + 0.5) * %s.w" but need to rearrange 60 // for precision 61 fragBuilder->codeAppendf("vec2 vec = vec2( (%s.x - %s.x) * %s.w , (%s.y - %s.y) * %s.w );", 62 fragmentPos, dataName, dataName, 63 fragmentPos, dataName, dataName); 64 fragBuilder->codeAppendf("float dist = length(vec) + ( 0.5 - %s.z ) * %s.w;", 65 dataName, dataName); 66 67 fragBuilder->codeAppendf("float intensity = "); 68 fragBuilder->appendTextureLookup(args.fTexSamplers[0], "vec2(dist, 0.5)"); 69 fragBuilder->codeAppend(".a;"); 70 71 fragBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor ); 72} 73 74void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLSLProgramDataManager& pdman, 75 const GrProcessor& proc) { 76 const GrCircleBlurFragmentProcessor& cbfp = proc.cast<GrCircleBlurFragmentProcessor>(); 77 const SkRect& circle = cbfp.circle(); 78 79 // The data is formatted as: 80 // x,y - the center of the circle 81 // z - the distance at which the intensity starts falling off (e.g., the start of the table) 82 // w - the inverse of the profile texture size 83 pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(), 84 1.0f / cbfp.profileSize()); 85} 86 87/////////////////////////////////////////////////////////////////////////////// 88 89GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circle, 90 float sigma, 91 float offset, 92 GrTexture* blurProfile) 93 : fCircle(circle) 94 , fSigma(sigma) 95 , fOffset(offset) 96 , fBlurProfileAccess(blurProfile, GrTextureParams::kBilerp_FilterMode) { 97 this->initClassID<GrCircleBlurFragmentProcessor>(); 98 this->addTextureAccess(&fBlurProfileAccess); 99 this->setWillReadFragmentPosition(); 100} 101 102GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() const { 103 return new GrGLCircleBlurFragmentProcessor; 104} 105 106void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrGLSLCaps& caps, 107 GrProcessorKeyBuilder* b) const { 108 GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b); 109} 110 111void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const { 112 inout->mulByUnknownSingleComponent(); 113} 114 115// Create a Gaussian half-kernel and a summed area table given a sigma and number of discrete 116// steps. The half kernel is normalized to sum to 0.5. 117static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHalfKernel, 118 int halfKernelSize, float sigma) { 119 const float invSigma = 1.f / sigma; 120 const float b = -0.5f * invSigma * invSigma; 121 float tot = 0.0f; 122 // Compute half kernel values at half pixel steps out from the center. 123 float t = 0.5f; 124 for (int i = 0; i < halfKernelSize; ++i) { 125 float value = expf(t * t * b); 126 tot += value; 127 halfKernel[i] = value; 128 t += 1.f; 129 } 130 float sum = 0.f; 131 // The half kernel should sum to 0.5 not 1.0. 132 tot *= 2.f; 133 for (int i = 0; i < halfKernelSize; ++i) { 134 halfKernel[i] /= tot; 135 sum += halfKernel[i]; 136 summedHalfKernel[i] = sum; 137 } 138} 139 140// Applies the 1D half kernel vertically at a point (x, 0) to a circle centered at the origin with 141// radius circleR. 142static float eval_vertically(float x, float circleR, const float* summedHalfKernelTable, 143 int halfKernelSize) { 144 // Given x find the positive y that is on the edge of the circle. 145 float y = sqrtf(fabs(circleR * circleR - x * x)); 146 // In the column at x we exit the circle at +y and -y 147 // table entry j is actually the kernel evaluated at j + 0.5. 148 y -= 0.5f; 149 int yInt = SkScalarFloorToInt(y); 150 SkASSERT(yInt >= -1); 151 if (y < 0) { 152 return (y + 0.5f) * summedHalfKernelTable[0]; 153 } else if (yInt >= halfKernelSize - 1) { 154 return 0.5f; 155 } else { 156 float yFrac = y - yInt; 157 return (1.f - yFrac) * summedHalfKernelTable[yInt] + 158 yFrac * summedHalfKernelTable[yInt + 1]; 159 } 160} 161 162// Apply the kernel at point (t, 0) to a circle centered at the origin with radius circleR. 163static uint8_t eval_at(float t, float circleR, const float* halfKernel, 164 const float* summedHalfKernelTable, int halfKernelSize) { 165 float acc = 0; 166 167 for (int i = 0; i < halfKernelSize; ++i) { 168 float x = t - i - 0.5f; 169 if (x < -circleR || x > circleR) { 170 continue; 171 } 172 float verticalEval = eval_vertically(x, circleR, summedHalfKernelTable, halfKernelSize); 173 acc += verticalEval * halfKernel[i]; 174 } 175 for (int i = 0; i < halfKernelSize; ++i) { 176 float x = t + i + 0.5f; 177 if (x < -circleR || x > circleR) { 178 continue; 179 } 180 float verticalEval = eval_vertically(x, circleR, summedHalfKernelTable, halfKernelSize); 181 acc += verticalEval * halfKernel[i]; 182 } 183 // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about the 184 // x axis). 185 return SkUnitScalarClampToByte(2.f * acc); 186} 187 188static inline void compute_profile_offset_and_size(float circleR, float sigma, 189 float* offset, int* size) { 190 if (3*sigma <= circleR) { 191 // The circle is bigger than the Gaussian. In this case we know the interior of the 192 // blurred circle is solid. 193 *offset = circleR - 3 * sigma; // This location maps to 0.5f in the weights texture. 194 // It should always be 255. 195 *size = SkScalarCeilToInt(6*sigma); 196 } else { 197 // The Gaussian is bigger than the circle. 198 *offset = 0.0f; 199 *size = SkScalarCeilToInt(circleR + 3*sigma); 200 } 201} 202 203// This function creates a profile of a blurred circle. It does this by computing a kernel for 204// half the Gaussian and a matching summed area table. To compute a profile value at x = r it steps 205// outward in x from (r, 0) in both directions. There is a step for each direction for each entry 206// in the half kernel. The y contribution at each step is computed from the summed area table using 207// the height of the circle above the step point. Each y contribution is multiplied by the half 208// kernel value corresponding to the step in x. 209static uint8_t* create_profile(float circleR, float sigma) { 210 float offset; 211 int numSteps; 212 compute_profile_offset_and_size(circleR, sigma, &offset, &numSteps); 213 214 uint8_t* weights = new uint8_t[numSteps]; 215 216 // The full kernel is 6 sigmas wide. 217 int halfKernelSize = SkScalarCeilToInt(6.0f*sigma); 218 // round up to next multiple of 2 and then divide by 2 219 halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1; 220 SkAutoTArray<float> halfKernel(halfKernelSize); 221 SkAutoTArray<float> summedKernel(halfKernelSize); 222 make_half_kernel_and_summed_table(halfKernel.get(), summedKernel.get(), halfKernelSize, 223 sigma); 224 for (int i = 0; i < numSteps - 1; ++i) { 225 weights[i] = eval_at(offset+i, circleR, halfKernel.get(), summedKernel.get(), 226 halfKernelSize); 227 } 228 // Ensure the tail of the Gaussian goes to zero. 229 weights[numSteps - 1] = 0; 230 return weights; 231} 232 233GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( 234 GrTextureProvider* textureProvider, 235 const SkRect& circle, 236 float sigma, 237 float* offset) { 238 float circleR = circle.width() / 2.0f; 239 240 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); 241 GrUniqueKey key; 242 GrUniqueKey::Builder builder(&key, kDomain, 2); 243 // The profile curve varies with both the sigma of the Gaussian and the size of the 244 // disk. Quantizing to 16.16 should be close enough though. 245 builder[0] = SkScalarToFixed(sigma); 246 builder[1] = SkScalarToFixed(circleR); 247 builder.finish(); 248 249 GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); 250 251 int profileSize; 252 compute_profile_offset_and_size(circleR, sigma, offset, &profileSize); 253 254 if (!blurProfile) { 255 256 GrSurfaceDesc texDesc; 257 texDesc.fWidth = profileSize; 258 texDesc.fHeight = 1; 259 texDesc.fConfig = kAlpha_8_GrPixelConfig; 260 261 SkAutoTDeleteArray<uint8_t> profile(create_profile(circleR, sigma)); 262 263 blurProfile = textureProvider->createTexture(texDesc, SkBudgeted::kYes, profile.get(), 0); 264 if (blurProfile) { 265 textureProvider->assignUniqueKeyToTexture(key, blurProfile); 266 } 267 } 268 269 return blurProfile; 270} 271 272GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); 273 274const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessorTestData* d) { 275 SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f); 276 SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); 277 SkRect circle = SkRect::MakeWH(wh, wh); 278 return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(), circle, sigma); 279} 280 281#endif 282