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