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. |
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. |
38 | static 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. |
54 | void 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. |
88 | static 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. |
122 | static 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 | |
153 | static 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 | |
180 | static 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 | |
257 | std::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" |
275 | class GrGLSLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor { |
276 | public: |
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 | |
309 | private: |
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 | }; |
330 | GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() const { |
331 | return new GrGLSLCircleBlurFragmentProcessor(); |
332 | } |
333 | void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps, |
334 | GrProcessorKeyBuilder* b) const {} |
335 | bool 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 | } |
344 | GrCircleBlurFragmentProcessor::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 | } |
353 | std::unique_ptr<GrFragmentProcessor> GrCircleBlurFragmentProcessor::clone() const { |
354 | return std::unique_ptr<GrFragmentProcessor>(new GrCircleBlurFragmentProcessor(*this)); |
355 | } |
356 | const GrFragmentProcessor::TextureSampler& GrCircleBlurFragmentProcessor::onTextureSampler( |
357 | int index) const { |
358 | return IthTextureSampler(index, blurProfileSampler); |
359 | } |
360 | GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); |
361 | #if GR_TEST_UTILS |
362 | std::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 | |