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