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 "src/gpu/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 = proxyProvider->findOrCreateProxyByUniqueKey(
207 key, GrColorType::kAlpha_8, 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), 1, SkBudgeted::kYes,
229 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 "include/gpu/GrTexture.h"
254 #include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
255 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
256 #include "src/gpu/glsl/GrGLSLProgramBuilder.h"
257 #include "src/sksl/SkSLCPP.h"
258 #include "src/sksl/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 circleDataVar = 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 * sample(%s, float2(half2(dist, 0.5))).%s.w;\n",
279 args.fUniformHandler->getUniformCStr(circleDataVar),
280 args.fUniformHandler->getUniformCStr(circleDataVar),
281 args.fUniformHandler->getUniformCStr(circleDataVar),
282 args.fUniformHandler->getUniformCStr(circleDataVar),
283 args.fUniformHandler->getUniformCStr(circleDataVar),
284 args.fUniformHandler->getUniformCStr(circleDataVar), args.fOutputColor,
285 args.fInputColor,
286 fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]),
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 = circleDataVar;
304 (void)circleData;
305
306 data.set4f(circleData, circleRect.centerX(), circleRect.centerY(), solidRadius,
307 1.f / textureRadius);
308 }
309 UniformHandle circleDataVar;
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 (circleRect != that.circleRect) return false;
320 if (textureRadius != that.textureRadius) return false;
321 if (solidRadius != that.solidRadius) return false;
322 if (blurProfileSampler != that.blurProfileSampler) return false;
323 return true;
324 }
GrCircleBlurFragmentProcessor(const GrCircleBlurFragmentProcessor & src)325 GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(
326 const GrCircleBlurFragmentProcessor& src)
327 : INHERITED(kGrCircleBlurFragmentProcessor_ClassID, src.optimizationFlags())
328 , circleRect(src.circleRect)
329 , textureRadius(src.textureRadius)
330 , solidRadius(src.solidRadius)
331 , blurProfileSampler(src.blurProfileSampler) {
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, blurProfileSampler);
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