• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
make_unnormalized_half_kernel(float * halfKernel,int halfKernelSize,float sigma)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.
make_half_kernel_and_summed_table(float * halfKernel,float * summedHalfKernel,int halfKernelSize,float sigma)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.
apply_kernel_in_y(float * results,int numSteps,float firstX,float circleR,int halfKernelSize,const float * summedHalfKernelTable)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.
eval_at(float evalX,float circleR,const float * halfKernel,int halfKernelSize,const float * 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.
create_circle_profile(uint8_t * weights,float sigma,float circleR,int profileTextureWidth)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 
create_half_plane_profile(uint8_t * profile,int profileWidth)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 
create_profile_texture(GrProxyProvider * proxyProvider,const SkRect & circle,float sigma,float * solidRadius,float * textureRadius)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 
Make(GrProxyProvider * proxyProvider,const SkRect & circle,float sigma)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:
GrGLSLCircleBlurFragmentProcessor()261     GrGLSLCircleBlurFragmentProcessor() {}
emitCode(EmitArgs & args)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:
onSetData(const GrGLSLProgramDataManager & data,const GrFragmentProcessor & _proc)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 };
onCreateGLSLInstance() const311 GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() const {
312     return new GrGLSLCircleBlurFragmentProcessor();
313 }
onGetGLSLProcessorKey(const GrShaderCaps & caps,GrProcessorKeyBuilder * b) const314 void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
315                                                           GrProcessorKeyBuilder* b) const {}
onIsEqual(const GrFragmentProcessor & other) const316 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 }
GrCircleBlurFragmentProcessor(const GrCircleBlurFragmentProcessor & src)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 }
clone() const334 std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::clone() const {
335     return std::unique_ptr<GrFragmentProcessor>(new GrCircleBlurFragmentProcessor(*this));
336 }
onTextureSampler(int index) const337 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
TestCreate(GrProcessorTestData * testData)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