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