• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 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 #include "src/core/SkGpuBlurUtils.h"
9 
10 #include "include/core/SkBitmap.h"
11 #include "include/core/SkRect.h"
12 #include "src/core/SkMathPriv.h"
13 
14 #if SK_SUPPORT_GPU
15 #include "include/gpu/GrRecordingContext.h"
16 #include "src/gpu/GrCaps.h"
17 #include "src/gpu/GrRecordingContextPriv.h"
18 #include "src/gpu/SkGr.h"
19 #include "src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h"
20 #include "src/gpu/effects/GrMatrixConvolutionEffect.h"
21 #include "src/gpu/effects/GrTextureEffect.h"
22 
23 #if SK_GPU_V1
24 #include "src/gpu/v1/SurfaceDrawContext_v1.h"
25 
26 using Direction = GrGaussianConvolutionFragmentProcessor::Direction;
27 
fill_in_2D_gaussian_kernel(float * kernel,int width,int height,SkScalar sigmaX,SkScalar sigmaY)28 static void fill_in_2D_gaussian_kernel(float* kernel, int width, int height,
29                                        SkScalar sigmaX, SkScalar sigmaY) {
30     const float twoSigmaSqrdX = 2.0f * SkScalarToFloat(SkScalarSquare(sigmaX));
31     const float twoSigmaSqrdY = 2.0f * SkScalarToFloat(SkScalarSquare(sigmaY));
32 
33     // SkGpuBlurUtils::GaussianBlur() should have detected the cases where a 2D blur
34     // degenerates to a 1D on X or Y, or to the identity.
35     SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(sigmaX) &&
36              !SkGpuBlurUtils::IsEffectivelyZeroSigma(sigmaY));
37     SkASSERT(!SkScalarNearlyZero(twoSigmaSqrdX) && !SkScalarNearlyZero(twoSigmaSqrdY));
38 
39     const float sigmaXDenom = 1.0f / twoSigmaSqrdX;
40     const float sigmaYDenom = 1.0f / twoSigmaSqrdY;
41     const int xRadius = width / 2;
42     const int yRadius = height / 2;
43 
44     float sum = 0.0f;
45     for (int x = 0; x < width; x++) {
46         float xTerm = static_cast<float>(x - xRadius);
47         xTerm = xTerm * xTerm * sigmaXDenom;
48         for (int y = 0; y < height; y++) {
49             float yTerm = static_cast<float>(y - yRadius);
50             float xyTerm = sk_float_exp(-(xTerm + yTerm * yTerm * sigmaYDenom));
51             // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
52             // is dropped here, since we renormalize the kernel below.
53             kernel[y * width + x] = xyTerm;
54             sum += xyTerm;
55         }
56     }
57     // Normalize the kernel
58     float scale = 1.0f / sum;
59     for (int i = 0; i < width * height; ++i) {
60         kernel[i] *= scale;
61     }
62 }
63 
64 /**
65  * Draws 'dstRect' into 'surfaceFillContext' evaluating a 1D Gaussian over 'srcView'. The src rect
66  * is 'dstRect' offset by 'dstToSrcOffset'. 'mode' and 'bounds' are applied to the src coords.
67  */
convolve_gaussian_1d(skgpu::SurfaceFillContext * sfc,GrSurfaceProxyView srcView,const SkIRect srcSubset,SkIVector dstToSrcOffset,const SkIRect & dstRect,SkAlphaType srcAlphaType,Direction direction,int radius,float sigma,SkTileMode mode)68 static void convolve_gaussian_1d(skgpu::SurfaceFillContext* sfc,
69                                  GrSurfaceProxyView srcView,
70                                  const SkIRect srcSubset,
71                                  SkIVector dstToSrcOffset,
72                                  const SkIRect& dstRect,
73                                  SkAlphaType srcAlphaType,
74                                  Direction direction,
75                                  int radius,
76                                  float sigma,
77                                  SkTileMode mode) {
78     SkASSERT(radius && !SkGpuBlurUtils::IsEffectivelyZeroSigma(sigma));
79     auto wm = SkTileModeToWrapMode(mode);
80     auto srcRect = dstRect.makeOffset(dstToSrcOffset);
81     // NOTE: This could just be GrMatrixConvolutionEffect with one of the dimensions set to 1
82     // and the appropriate kernel already computed, but there's value in keeping the shader simpler.
83     // TODO(michaelludwig): Is this true? If not, is the shader key simplicity worth it two have
84     // two convolution effects?
85     std::unique_ptr<GrFragmentProcessor> conv =
86             GrGaussianConvolutionFragmentProcessor::Make(std::move(srcView),
87                                                          srcAlphaType,
88                                                          direction,
89                                                          radius,
90                                                          sigma,
91                                                          wm,
92                                                          srcSubset,
93                                                          &srcRect,
94                                                          *sfc->caps());
95     sfc->fillRectToRectWithFP(srcRect, dstRect, std::move(conv));
96 }
97 
convolve_gaussian_2d(GrRecordingContext * rContext,GrSurfaceProxyView srcView,GrColorType srcColorType,const SkIRect & srcBounds,const SkIRect & dstBounds,int radiusX,int radiusY,SkScalar sigmaX,SkScalar sigmaY,SkTileMode mode,sk_sp<SkColorSpace> finalCS,SkBackingFit dstFit)98 static std::unique_ptr<skgpu::v1::SurfaceDrawContext> convolve_gaussian_2d(
99         GrRecordingContext* rContext,
100         GrSurfaceProxyView srcView,
101         GrColorType srcColorType,
102         const SkIRect& srcBounds,
103         const SkIRect& dstBounds,
104         int radiusX,
105         int radiusY,
106         SkScalar sigmaX,
107         SkScalar sigmaY,
108         SkTileMode mode,
109         sk_sp<SkColorSpace> finalCS,
110         SkBackingFit dstFit) {
111     SkASSERT(radiusX && radiusY);
112     SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(sigmaX) &&
113              !SkGpuBlurUtils::IsEffectivelyZeroSigma(sigmaY));
114     // Create the sdc with default SkSurfaceProps. Gaussian blurs will soon use a
115     // SurfaceFillContext, at which point the SkSurfaceProps won't exist anymore.
116     auto sdc = skgpu::v1::SurfaceDrawContext::Make(
117             rContext, srcColorType, std::move(finalCS), dstFit, dstBounds.size(), SkSurfaceProps(),
118             1, GrMipmapped::kNo, srcView.proxy()->isProtected(), srcView.origin());
119     if (!sdc) {
120         return nullptr;
121     }
122 
123     SkISize size = SkISize::Make(SkGpuBlurUtils::KernelWidth(radiusX),
124                                  SkGpuBlurUtils::KernelWidth(radiusY));
125     SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
126     GrPaint paint;
127     auto wm = SkTileModeToWrapMode(mode);
128 
129     // GaussianBlur() should have downsampled the request until we can handle the 2D blur with
130     // just a uniform array.
131     SkASSERT(size.area() <= GrMatrixConvolutionEffect::kMaxUniformSize);
132     float kernel[GrMatrixConvolutionEffect::kMaxUniformSize];
133     fill_in_2D_gaussian_kernel(kernel, size.width(), size.height(), sigmaX, sigmaY);
134     auto conv = GrMatrixConvolutionEffect::Make(rContext, std::move(srcView), srcBounds,
135                                                 size, kernel, 1.0f, 0.0f, kernelOffset, wm, true,
136                                                 *sdc->caps());
137 
138     paint.setColorFragmentProcessor(std::move(conv));
139     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
140 
141     // 'dstBounds' is actually in 'srcView' proxy space. It represents the blurred area from src
142     // space that we want to capture in the new RTC at {0, 0}. Hence, we use its size as the rect to
143     // draw and it directly as the local rect.
144     sdc->fillRectToRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
145                         SkRect::Make(dstBounds.size()), SkRect::Make(dstBounds));
146 
147     return sdc;
148 }
149 
convolve_gaussian(GrRecordingContext * rContext,GrSurfaceProxyView srcView,GrColorType srcColorType,SkAlphaType srcAlphaType,SkIRect srcBounds,SkIRect dstBounds,Direction direction,int radius,float sigma,SkTileMode mode,sk_sp<SkColorSpace> finalCS,SkBackingFit fit)150 static std::unique_ptr<skgpu::v1::SurfaceDrawContext> convolve_gaussian(
151         GrRecordingContext* rContext,
152         GrSurfaceProxyView srcView,
153         GrColorType srcColorType,
154         SkAlphaType srcAlphaType,
155         SkIRect srcBounds,
156         SkIRect dstBounds,
157         Direction direction,
158         int radius,
159         float sigma,
160         SkTileMode mode,
161         sk_sp<SkColorSpace> finalCS,
162         SkBackingFit fit) {
163     using namespace SkGpuBlurUtils;
164     SkASSERT(radius > 0 && !SkGpuBlurUtils::IsEffectivelyZeroSigma(sigma));
165     // Logically we're creating an infinite blur of 'srcBounds' of 'srcView' with 'mode' tiling
166     // and then capturing the 'dstBounds' portion in a new RTC where the top left of 'dstBounds' is
167     // at {0, 0} in the new RTC.
168     //
169     // Create the sdc with default SkSurfaceProps. Gaussian blurs will soon use a
170     // SurfaceFillContext, at which point the SkSurfaceProps won't exist anymore.
171     auto dstSDC = skgpu::v1::SurfaceDrawContext::Make(
172             rContext, srcColorType, std::move(finalCS), fit, dstBounds.size(), SkSurfaceProps(), 1,
173             GrMipmapped::kNo, srcView.proxy()->isProtected(), srcView.origin());
174     if (!dstSDC) {
175         return nullptr;
176     }
177     // This represents the translation from 'dstSurfaceDrawContext' coords to 'srcView' coords.
178     auto rtcToSrcOffset = dstBounds.topLeft();
179 
180     auto srcBackingBounds = SkIRect::MakeSize(srcView.proxy()->backingStoreDimensions());
181     // We've implemented splitting the dst bounds up into areas that do and do not need to
182     // use shader based tiling but only for some modes...
183     bool canSplit = mode == SkTileMode::kDecal || mode == SkTileMode::kClamp;
184     // ...but it's not worth doing the splitting if we'll get HW tiling instead of shader tiling.
185     bool canHWTile =
186             srcBounds.contains(srcBackingBounds)         &&
187             !rContext->priv().caps()->reducedShaderMode() && // this mode always uses shader tiling
188             !(mode == SkTileMode::kDecal && !rContext->priv().caps()->clampToBorderSupport());
189     if (!canSplit || canHWTile) {
190         auto dstRect = SkIRect::MakeSize(dstBounds.size());
191         convolve_gaussian_1d(dstSDC.get(), std::move(srcView), srcBounds,
192                              rtcToSrcOffset, dstRect, srcAlphaType, direction, radius, sigma, mode);
193         return dstSDC;
194     }
195 
196     // 'left' and 'right' are the sub rects of 'srcBounds' where 'mode' must be enforced.
197     // 'mid' is the area where we can ignore the mode because the kernel does not reach to the
198     // edge of 'srcBounds'.
199     SkIRect mid, left, right;
200     // 'top' and 'bottom' are areas of 'dstBounds' that are entirely above/below 'srcBounds'.
201     // These are areas that we can simply clear in the dst in kDecal mode. If 'srcBounds'
202     // straddles the top edge of 'dstBounds' then 'top' will be inverted and we will skip
203     // processing for the rect. Similar for 'bottom'. The positional/directional labels above refer
204     // to the Direction::kX case and one should think of these as 'left' and 'right' for
205     // Direction::kY.
206     SkIRect top, bottom;
207     if (Direction::kX == direction) {
208         top    = {dstBounds.left(), dstBounds.top()   , dstBounds.right(), srcBounds.top()   };
209         bottom = {dstBounds.left(), srcBounds.bottom(), dstBounds.right(), dstBounds.bottom()};
210 
211         // Inset for sub-rect of 'srcBounds' where the x-dir kernel doesn't reach the edges, clipped
212         // vertically to dstBounds.
213         int midA = std::max(srcBounds.top()   , dstBounds.top()   );
214         int midB = std::min(srcBounds.bottom(), dstBounds.bottom());
215         mid = {srcBounds.left() + radius, midA, srcBounds.right() - radius, midB};
216         if (mid.isEmpty()) {
217             // There is no middle where the bounds can be ignored. Make the left span the whole
218             // width of dst and we will not draw mid or right.
219             left = {dstBounds.left(), mid.top(), dstBounds.right(), mid.bottom()};
220         } else {
221             left  = {dstBounds.left(), mid.top(), mid.left()       , mid.bottom()};
222             right = {mid.right(),      mid.top(), dstBounds.right(), mid.bottom()};
223         }
224     } else {
225         // This is the same as the x direction code if you turn your head 90 degrees CCW. Swap x and
226         // y and swap top/bottom with left/right.
227         top    = {dstBounds.left(),  dstBounds.top(), srcBounds.left() , dstBounds.bottom()};
228         bottom = {srcBounds.right(), dstBounds.top(), dstBounds.right(), dstBounds.bottom()};
229 
230         int midA = std::max(srcBounds.left() , dstBounds.left() );
231         int midB = std::min(srcBounds.right(), dstBounds.right());
232         mid = {midA, srcBounds.top() + radius, midB, srcBounds.bottom() - radius};
233 
234         if (mid.isEmpty()) {
235             left = {mid.left(), dstBounds.top(), mid.right(), dstBounds.bottom()};
236         } else {
237             left  = {mid.left(), dstBounds.top(), mid.right(), mid.top()         };
238             right = {mid.left(), mid.bottom()   , mid.right(), dstBounds.bottom()};
239         }
240     }
241 
242     auto convolve = [&](SkIRect rect) {
243         // Transform rect into the render target's coord system.
244         rect.offset(-rtcToSrcOffset);
245         convolve_gaussian_1d(dstSDC.get(), srcView, srcBounds, rtcToSrcOffset, rect,
246                              srcAlphaType, direction, radius, sigma, mode);
247     };
248     auto clear = [&](SkIRect rect) {
249         // Transform rect into the render target's coord system.
250         rect.offset(-rtcToSrcOffset);
251         dstSDC->clearAtLeast(rect, SK_PMColor4fTRANSPARENT);
252     };
253 
254     // Doing mid separately will cause two draws to occur (left and right batch together). At
255     // small sizes of mid it is worse to issue more draws than to just execute the slightly
256     // more complicated shader that implements the tile mode across mid. This threshold is
257     // very arbitrary right now. It is believed that a 21x44 mid on a Moto G4 is a significant
258     // regression compared to doing one draw but it has not been locally evaluated or tuned.
259     // The optimal cutoff is likely to vary by GPU.
260     if (!mid.isEmpty() && mid.width()*mid.height() < 256*256) {
261         left.join(mid);
262         left.join(right);
263         mid = SkIRect::MakeEmpty();
264         right = SkIRect::MakeEmpty();
265         // It's unknown whether for kDecal it'd be better to expand the draw rather than a draw and
266         // up to two clears.
267         if (mode == SkTileMode::kClamp) {
268             left.join(top);
269             left.join(bottom);
270             top = SkIRect::MakeEmpty();
271             bottom = SkIRect::MakeEmpty();
272         }
273     }
274 
275     if (!top.isEmpty()) {
276         if (mode == SkTileMode::kDecal) {
277             clear(top);
278         } else {
279             convolve(top);
280         }
281     }
282 
283     if (!bottom.isEmpty()) {
284         if (mode == SkTileMode::kDecal) {
285             clear(bottom);
286         } else {
287             convolve(bottom);
288         }
289     }
290 
291     if (mid.isEmpty()) {
292         convolve(left);
293     } else {
294         convolve(left);
295         convolve(right);
296         convolve(mid);
297     }
298     return dstSDC;
299 }
300 
301 // Expand the contents of 'src' to fit in 'dstSize'. At this point, we are expanding an intermediate
302 // image, so there's no need to account for a proxy offset from the original input.
reexpand(GrRecordingContext * rContext,std::unique_ptr<skgpu::SurfaceContext> src,const SkRect & srcBounds,SkISize dstSize,sk_sp<SkColorSpace> colorSpace,SkBackingFit fit)303 static std::unique_ptr<skgpu::v1::SurfaceDrawContext> reexpand(
304         GrRecordingContext* rContext,
305         std::unique_ptr<skgpu::SurfaceContext> src,
306         const SkRect& srcBounds,
307         SkISize dstSize,
308         sk_sp<SkColorSpace> colorSpace,
309         SkBackingFit fit) {
310     GrSurfaceProxyView srcView = src->readSurfaceView();
311     if (!srcView.asTextureProxy()) {
312         return nullptr;
313     }
314 
315     GrColorType srcColorType = src->colorInfo().colorType();
316     SkAlphaType srcAlphaType = src->colorInfo().alphaType();
317 
318     src.reset(); // no longer needed
319 
320     // Create the sdc with default SkSurfaceProps. Gaussian blurs will soon use a
321     // SurfaceFillContext, at which point the SkSurfaceProps won't exist anymore.
322     auto dstSDC = skgpu::v1::SurfaceDrawContext::Make(
323             rContext, srcColorType, std::move(colorSpace), fit, dstSize, SkSurfaceProps(), 1,
324             GrMipmapped::kNo, srcView.proxy()->isProtected(), srcView.origin());
325     if (!dstSDC) {
326         return nullptr;
327     }
328 
329     GrPaint paint;
330     auto fp = GrTextureEffect::MakeSubset(std::move(srcView), srcAlphaType, SkMatrix::I(),
331                                           GrSamplerState::Filter::kLinear, srcBounds, srcBounds,
332                                           *rContext->priv().caps());
333     paint.setColorFragmentProcessor(std::move(fp));
334     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
335 
336     dstSDC->fillRectToRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
337                            SkRect::Make(dstSize), srcBounds);
338 
339     return dstSDC;
340 }
341 
two_pass_gaussian(GrRecordingContext * rContext,GrSurfaceProxyView srcView,GrColorType srcColorType,SkAlphaType srcAlphaType,sk_sp<SkColorSpace> colorSpace,SkIRect srcBounds,SkIRect dstBounds,float sigmaX,float sigmaY,int radiusX,int radiusY,SkTileMode mode,SkBackingFit fit)342 static std::unique_ptr<skgpu::v1::SurfaceDrawContext> two_pass_gaussian(
343         GrRecordingContext* rContext,
344         GrSurfaceProxyView srcView,
345         GrColorType srcColorType,
346         SkAlphaType srcAlphaType,
347         sk_sp<SkColorSpace> colorSpace,
348         SkIRect srcBounds,
349         SkIRect dstBounds,
350         float sigmaX,
351         float sigmaY,
352         int radiusX,
353         int radiusY,
354         SkTileMode mode,
355         SkBackingFit fit) {
356     SkASSERT(radiusX || radiusY);
357     std::unique_ptr<skgpu::v1::SurfaceDrawContext> dstSDC;
358     if (radiusX > 0) {
359         SkBackingFit xFit = radiusY > 0 ? SkBackingFit::kApprox : fit;
360         // Expand the dstBounds vertically to produce necessary content for the y-pass. Then we will
361         // clip these in a tile-mode dependent way to ensure the tile-mode gets implemented
362         // correctly. However, if we're not going to do a y-pass then we must use the original
363         // dstBounds without clipping to produce the correct output size.
364         SkIRect xPassDstBounds = dstBounds;
365         if (radiusY) {
366             xPassDstBounds.outset(0, radiusY);
367             if (mode == SkTileMode::kRepeat || mode == SkTileMode::kMirror) {
368                 int srcH = srcBounds.height();
369                 int srcTop = srcBounds.top();
370                 if (mode == SkTileMode::kMirror) {
371                     srcTop -= srcH;
372                     srcH *= 2;
373                 }
374 
375                 float floatH = srcH;
376                 // First row above the dst rect where we should restart the tile mode.
377                 int n = sk_float_floor2int_no_saturate((xPassDstBounds.top() - srcTop)/floatH);
378                 int topClip = srcTop + n*srcH;
379 
380                 // First row above below the dst rect where we should restart the tile mode.
381                 n = sk_float_ceil2int_no_saturate(
382                         (xPassDstBounds.bottom() - srcBounds.bottom())/floatH);
383                 int bottomClip = srcBounds.bottom() + n*srcH;
384 
385                 xPassDstBounds.fTop    = std::max(xPassDstBounds.top(),    topClip);
386                 xPassDstBounds.fBottom = std::min(xPassDstBounds.bottom(), bottomClip);
387             } else {
388                 if (xPassDstBounds.fBottom <= srcBounds.top()) {
389                     if (mode == SkTileMode::kDecal) {
390                         return nullptr;
391                     }
392                     xPassDstBounds.fTop = srcBounds.top();
393                     xPassDstBounds.fBottom = xPassDstBounds.fTop + 1;
394                 } else if (xPassDstBounds.fTop >= srcBounds.bottom()) {
395                     if (mode == SkTileMode::kDecal) {
396                         return nullptr;
397                     }
398                     xPassDstBounds.fBottom = srcBounds.bottom();
399                     xPassDstBounds.fTop = xPassDstBounds.fBottom - 1;
400                 } else {
401                     xPassDstBounds.fTop    = std::max(xPassDstBounds.fTop,    srcBounds.top());
402                     xPassDstBounds.fBottom = std::min(xPassDstBounds.fBottom, srcBounds.bottom());
403                 }
404                 int leftSrcEdge  = srcBounds.fLeft  - radiusX ;
405                 int rightSrcEdge = srcBounds.fRight + radiusX;
406                 if (mode == SkTileMode::kClamp) {
407                     // In clamp the column just outside the src bounds has the same value as the
408                     // column just inside, unlike decal.
409                     leftSrcEdge  += 1;
410                     rightSrcEdge -= 1;
411                 }
412                 if (xPassDstBounds.fRight <= leftSrcEdge) {
413                     if (mode == SkTileMode::kDecal) {
414                         return nullptr;
415                     }
416                     xPassDstBounds.fLeft = xPassDstBounds.fRight - 1;
417                 } else {
418                     xPassDstBounds.fLeft = std::max(xPassDstBounds.fLeft, leftSrcEdge);
419                 }
420                 if (xPassDstBounds.fLeft >= rightSrcEdge) {
421                     if (mode == SkTileMode::kDecal) {
422                         return nullptr;
423                     }
424                     xPassDstBounds.fRight = xPassDstBounds.fLeft + 1;
425                 } else {
426                     xPassDstBounds.fRight = std::min(xPassDstBounds.fRight, rightSrcEdge);
427                 }
428             }
429         }
430         dstSDC = convolve_gaussian(
431                 rContext, std::move(srcView), srcColorType, srcAlphaType, srcBounds, xPassDstBounds,
432                 Direction::kX, radiusX, sigmaX, mode, colorSpace, xFit);
433         if (!dstSDC) {
434             return nullptr;
435         }
436         srcView = dstSDC->readSurfaceView();
437         SkIVector newDstBoundsOffset = dstBounds.topLeft() - xPassDstBounds.topLeft();
438         dstBounds = SkIRect::MakeSize(dstBounds.size()).makeOffset(newDstBoundsOffset);
439         srcBounds = SkIRect::MakeSize(xPassDstBounds.size());
440     }
441 
442     if (!radiusY) {
443         return dstSDC;
444     }
445 
446     return convolve_gaussian(rContext, std::move(srcView), srcColorType, srcAlphaType, srcBounds,
447                              dstBounds, Direction::kY, radiusY, sigmaY, mode, colorSpace, fit);
448 }
449 #endif // SK_GPU_V1
450 
451 namespace SkGpuBlurUtils {
452 
453 #if SK_GPU_V1
GaussianBlur(GrRecordingContext * rContext,GrSurfaceProxyView srcView,GrColorType srcColorType,SkAlphaType srcAlphaType,sk_sp<SkColorSpace> colorSpace,SkIRect dstBounds,SkIRect srcBounds,float sigmaX,float sigmaY,SkTileMode mode,SkBackingFit fit)454 std::unique_ptr<skgpu::v1::SurfaceDrawContext> GaussianBlur(GrRecordingContext* rContext,
455                                                             GrSurfaceProxyView srcView,
456                                                             GrColorType srcColorType,
457                                                             SkAlphaType srcAlphaType,
458                                                             sk_sp<SkColorSpace> colorSpace,
459                                                             SkIRect dstBounds,
460                                                             SkIRect srcBounds,
461                                                             float sigmaX,
462                                                             float sigmaY,
463                                                             SkTileMode mode,
464                                                             SkBackingFit fit) {
465     SkASSERT(rContext);
466     TRACE_EVENT2("skia.gpu", "GaussianBlur", "sigmaX", sigmaX, "sigmaY", sigmaY);
467 
468     if (!srcView.asTextureProxy()) {
469         return nullptr;
470     }
471 
472     int maxRenderTargetSize = rContext->priv().caps()->maxRenderTargetSize();
473     if (dstBounds.width() > maxRenderTargetSize || dstBounds.height() > maxRenderTargetSize) {
474         return nullptr;
475     }
476 
477     int radiusX = SigmaRadius(sigmaX);
478     int radiusY = SigmaRadius(sigmaY);
479     // Attempt to reduce the srcBounds in order to detect that we can set the sigmas to zero or
480     // to reduce the amount of work to rescale the source if sigmas are large. TODO: Could consider
481     // how to minimize the required source bounds for repeat/mirror modes.
482     if (mode == SkTileMode::kClamp || mode == SkTileMode::kDecal) {
483         SkIRect reach = dstBounds.makeOutset(radiusX, radiusY);
484         SkIRect intersection;
485         if (!intersection.intersect(reach, srcBounds)) {
486             if (mode == SkTileMode::kDecal) {
487                 return nullptr;
488             } else {
489                 if (reach.fLeft >= srcBounds.fRight) {
490                     srcBounds.fLeft = srcBounds.fRight - 1;
491                 } else if (reach.fRight <= srcBounds.fLeft) {
492                     srcBounds.fRight = srcBounds.fLeft + 1;
493                 }
494                 if (reach.fTop >= srcBounds.fBottom) {
495                     srcBounds.fTop = srcBounds.fBottom - 1;
496                 } else if (reach.fBottom <= srcBounds.fTop) {
497                     srcBounds.fBottom = srcBounds.fTop + 1;
498                 }
499             }
500         } else {
501             srcBounds = intersection;
502         }
503     }
504 
505     if (mode != SkTileMode::kDecal) {
506         // All non-decal tile modes are equivalent for one pixel width/height src and amount to a
507         // single color value repeated at each column/row. Applying the normalized kernel to that
508         // column/row yields that same color. So no blurring is necessary.
509         if (srcBounds.width() == 1) {
510             sigmaX = 0.f;
511             radiusX = 0;
512         }
513         if (srcBounds.height() == 1) {
514             sigmaY = 0.f;
515             radiusY = 0;
516         }
517     }
518 
519     // If we determined that there is no blurring necessary in either direction then just do a
520     // a draw that applies the tile mode.
521     if (!radiusX && !radiusY) {
522         // Create the sdc with default SkSurfaceProps. Gaussian blurs will soon use a
523         // SurfaceFillContext, at which point the SkSurfaceProps won't exist anymore.
524         auto result = skgpu::v1::SurfaceDrawContext::Make(rContext,
525                                                           srcColorType,
526                                                           std::move(colorSpace),
527                                                           fit,
528                                                           dstBounds.size(),
529                                                           SkSurfaceProps(),
530                                                           1,
531                                                           GrMipmapped::kNo,
532                                                           srcView.proxy()->isProtected(),
533                                                           srcView.origin());
534         if (!result) {
535             return nullptr;
536         }
537         GrSamplerState sampler(SkTileModeToWrapMode(mode), GrSamplerState::Filter::kNearest);
538         auto fp = GrTextureEffect::MakeSubset(std::move(srcView),
539                                               srcAlphaType,
540                                               SkMatrix::I(),
541                                               sampler,
542                                               SkRect::Make(srcBounds),
543                                               SkRect::Make(dstBounds),
544                                               *rContext->priv().caps());
545         result->fillRectToRectWithFP(dstBounds, SkIRect::MakeSize(dstBounds.size()), std::move(fp));
546         return result;
547     }
548 
549     if (sigmaX <= kMaxSigma && sigmaY <= kMaxSigma) {
550         SkASSERT(radiusX <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius);
551         SkASSERT(radiusY <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius);
552         // For really small blurs (certainly no wider than 5x5 on desktop GPUs) it is faster to just
553         // launch a single non separable kernel vs two launches.
554         const int kernelSize = (2 * radiusX + 1) * (2 * radiusY + 1);
555         if (radiusX > 0 && radiusY > 0 &&
556             kernelSize <= GrMatrixConvolutionEffect::kMaxUniformSize &&
557             !rContext->priv().caps()->reducedShaderMode()) {
558             // Apply the proxy offset to src bounds and offset directly
559             return convolve_gaussian_2d(rContext, std::move(srcView), srcColorType, srcBounds,
560                                         dstBounds, radiusX, radiusY, sigmaX, sigmaY, mode,
561                                         std::move(colorSpace), fit);
562         }
563         // This will automatically degenerate into a single pass of X or Y if only one of the
564         // radii are non-zero.
565         return two_pass_gaussian(rContext, std::move(srcView), srcColorType, srcAlphaType,
566                                  std::move(colorSpace), srcBounds, dstBounds, sigmaX, sigmaY,
567                                  radiusX, radiusY, mode, fit);
568     }
569 
570     GrColorInfo colorInfo(srcColorType, srcAlphaType, colorSpace);
571     auto srcCtx = rContext->priv().makeSC(srcView, colorInfo);
572     SkASSERT(srcCtx);
573 
574     float scaleX = sigmaX > kMaxSigma ? kMaxSigma/sigmaX : 1.f;
575     float scaleY = sigmaY > kMaxSigma ? kMaxSigma/sigmaY : 1.f;
576     // We round down here so that when we recalculate sigmas we know they will be below
577     // kMaxSigma (but clamp to 1 do we don't have an empty texture).
578     SkISize rescaledSize = {std::max(sk_float_floor2int(srcBounds.width() *scaleX), 1),
579                             std::max(sk_float_floor2int(srcBounds.height()*scaleY), 1)};
580     // Compute the sigmas using the actual scale factors used once we integerized the
581     // rescaledSize.
582     scaleX = static_cast<float>(rescaledSize.width()) /srcBounds.width();
583     scaleY = static_cast<float>(rescaledSize.height())/srcBounds.height();
584     sigmaX *= scaleX;
585     sigmaY *= scaleY;
586 
587     // When we are in clamp mode any artifacts in the edge pixels due to downscaling may be
588     // exacerbated because of the tile mode. The particularly egregious case is when the original
589     // image has transparent black around the edges and the downscaling pulls in some non-zero
590     // values from the interior. Ultimately it'd be better for performance if the calling code could
591     // give us extra context around the blur to account for this. We don't currently have a good way
592     // to communicate this up stack. So we leave a 1 pixel border around the rescaled src bounds.
593     // We populate the top 1 pixel tall row of this border by rescaling the top row of the original
594     // source bounds into it. Because this is only rescaling in x (i.e. rescaling a 1 pixel high
595     // row into a shorter but still 1 pixel high row) we won't read any interior values. And similar
596     // for the other three borders. We'll adjust the source/dest bounds rescaled blur so that this
597     // border of extra pixels is used as the edge pixels for clamp mode but the dest bounds
598     // corresponds only to the pixels inside the border (the normally rescaled pixels inside this
599     // border).
600     // Moreover, if we clamped the rescaled size to 1 column or row then we still have a sigma
601     // that is greater than kMaxSigma. By using a pad and making the src 3 wide/tall instead of
602     // 1 we can recurse again and do another downscale. Since mirror and repeat modes are trivial
603     // for a single col/row we only add padding based on sigma exceeding kMaxSigma for decal.
604     int padX = mode == SkTileMode::kClamp ||
605                (mode == SkTileMode::kDecal && sigmaX > kMaxSigma) ? 1 : 0;
606     int padY = mode == SkTileMode::kClamp ||
607                (mode == SkTileMode::kDecal && sigmaY > kMaxSigma) ? 1 : 0;
608     // Create the sdc with default SkSurfaceProps. Gaussian blurs will soon use a
609     // SurfaceFillContext, at which point the SkSurfaceProps won't exist anymore.
610     auto rescaledSDC = skgpu::v1::SurfaceDrawContext::Make(
611             srcCtx->recordingContext(),
612             colorInfo.colorType(),
613             colorInfo.refColorSpace(),
614             SkBackingFit::kApprox,
615             {rescaledSize.width() + 2*padX, rescaledSize.height() + 2*padY},
616             SkSurfaceProps(),
617             1,
618             GrMipmapped::kNo,
619             srcCtx->asSurfaceProxy()->isProtected(),
620             srcCtx->origin());
621     if (!rescaledSDC) {
622         return nullptr;
623     }
624     if ((padX || padY) && mode == SkTileMode::kDecal) {
625         rescaledSDC->clear(SkPMColor4f{0, 0, 0, 0});
626     }
627     if (!srcCtx->rescaleInto(rescaledSDC.get(),
628                              SkIRect::MakeSize(rescaledSize).makeOffset(padX, padY),
629                              srcBounds,
630                              SkSurface::RescaleGamma::kSrc,
631                              SkSurface::RescaleMode::kRepeatedLinear)) {
632         return nullptr;
633     }
634     if (mode == SkTileMode::kClamp) {
635         SkASSERT(padX == 1 && padY == 1);
636         // Rather than run a potentially multi-pass rescaler on single rows/columns we just do a
637         // single bilerp draw. If we find this quality unacceptable we should think more about how
638         // to rescale these with better quality but without 4 separate multi-pass downscales.
639         auto cheapDownscale = [&](SkIRect dstRect, SkIRect srcRect) {
640             rescaledSDC->drawTexture(nullptr,
641                                      srcCtx->readSurfaceView(),
642                                      srcAlphaType,
643                                      GrSamplerState::Filter::kLinear,
644                                      GrSamplerState::MipmapMode::kNone,
645                                      SkBlendMode::kSrc,
646                                      SK_PMColor4fWHITE,
647                                      SkRect::Make(srcRect),
648                                      SkRect::Make(dstRect),
649                                      GrAA::kNo,
650                                      GrQuadAAFlags::kNone,
651                                      SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint,
652                                      SkMatrix::I(),
653                                      nullptr);
654         };
655         auto [dw, dh] = rescaledSize;
656         // The are the src rows and columns from the source that we will scale into the dst padding.
657         float sLCol = srcBounds.left();
658         float sTRow = srcBounds.top();
659         float sRCol = srcBounds.right() - 1;
660         float sBRow = srcBounds.bottom() - 1;
661 
662         int sx = srcBounds.left();
663         int sy = srcBounds.top();
664         int sw = srcBounds.width();
665         int sh = srcBounds.height();
666 
667         // Downscale the edges from the original source. These draws should batch together (and with
668         // the above interior rescaling when it is a single pass).
669         cheapDownscale(SkIRect::MakeXYWH(     0,      1,  1, dh),
670                        SkIRect::MakeXYWH( sLCol,     sy,  1, sh));
671         cheapDownscale(SkIRect::MakeXYWH(     1,      0, dw,  1),
672                        SkIRect::MakeXYWH(    sx,  sTRow, sw,  1));
673         cheapDownscale(SkIRect::MakeXYWH(dw + 1,      1,  1, dh),
674                        SkIRect::MakeXYWH( sRCol,     sy,  1, sh));
675         cheapDownscale(SkIRect::MakeXYWH(     1, dh + 1, dw,  1),
676                        SkIRect::MakeXYWH(    sx,  sBRow, sw,  1));
677 
678         // Copy the corners from the original source. These would batch with the edges except that
679         // at time of writing we recognize these can use kNearest and downgrade the filter. So they
680         // batch with each other but not the edge draws.
681         cheapDownscale(SkIRect::MakeXYWH(    0,     0,  1, 1),
682                        SkIRect::MakeXYWH(sLCol, sTRow,  1, 1));
683         cheapDownscale(SkIRect::MakeXYWH(dw + 1,     0, 1, 1),
684                        SkIRect::MakeXYWH(sRCol, sTRow,  1, 1));
685         cheapDownscale(SkIRect::MakeXYWH(dw + 1,dh + 1, 1, 1),
686                        SkIRect::MakeXYWH(sRCol, sBRow,  1, 1));
687         cheapDownscale(SkIRect::MakeXYWH(    0, dh + 1, 1, 1),
688                        SkIRect::MakeXYWH(sLCol, sBRow,  1, 1));
689     }
690     srcView = rescaledSDC->readSurfaceView();
691     // Drop the contexts so we don't hold the proxies longer than necessary.
692     rescaledSDC.reset();
693     srcCtx.reset();
694 
695     // Compute the dst bounds in the scaled down space. First move the origin to be at the top
696     // left since we trimmed off everything above and to the left of the original src bounds during
697     // the rescale.
698     SkRect scaledDstBounds = SkRect::Make(dstBounds.makeOffset(-srcBounds.topLeft()));
699     scaledDstBounds.fLeft   *= scaleX;
700     scaledDstBounds.fTop    *= scaleY;
701     scaledDstBounds.fRight  *= scaleX;
702     scaledDstBounds.fBottom *= scaleY;
703     // Account for padding in our rescaled src, if any.
704     scaledDstBounds.offset(padX, padY);
705     // Turn the scaled down dst bounds into an integer pixel rect.
706     auto scaledDstBoundsI = scaledDstBounds.roundOut();
707 
708     SkIRect scaledSrcBounds = SkIRect::MakeSize(srcView.dimensions());
709     auto sdc = GaussianBlur(rContext,
710                             std::move(srcView),
711                             srcColorType,
712                             srcAlphaType,
713                             colorSpace,
714                             scaledDstBoundsI,
715                             scaledSrcBounds,
716                             sigmaX,
717                             sigmaY,
718                             mode,
719                             fit);
720     if (!sdc) {
721         return nullptr;
722     }
723     // We rounded out the integer scaled dst bounds. Select the fractional dst bounds from the
724     // integer dimension blurred result when we scale back up.
725     scaledDstBounds.offset(-scaledDstBoundsI.left(), -scaledDstBoundsI.top());
726     return reexpand(rContext, std::move(sdc), scaledDstBounds, dstBounds.size(),
727                     std::move(colorSpace), fit);
728 }
729 #endif // SK_GPU_V1
730 
ComputeBlurredRRectParams(const SkRRect & srcRRect,const SkRRect & devRRect,SkScalar sigma,SkScalar xformedSigma,SkRRect * rrectToDraw,SkISize * widthHeight,SkScalar rectXs[kBlurRRectMaxDivisions],SkScalar rectYs[kBlurRRectMaxDivisions],SkScalar texXs[kBlurRRectMaxDivisions],SkScalar texYs[kBlurRRectMaxDivisions])731 bool ComputeBlurredRRectParams(const SkRRect& srcRRect, const SkRRect& devRRect,
732                                SkScalar sigma, SkScalar xformedSigma,
733                                SkRRect* rrectToDraw,
734                                SkISize* widthHeight,
735                                SkScalar rectXs[kBlurRRectMaxDivisions],
736                                SkScalar rectYs[kBlurRRectMaxDivisions],
737                                SkScalar texXs[kBlurRRectMaxDivisions],
738                                SkScalar texYs[kBlurRRectMaxDivisions]) {
739     unsigned int devBlurRadius = 3*SkScalarCeilToInt(xformedSigma-1/6.0f);
740     SkScalar srcBlurRadius = 3.0f * sigma;
741 
742     const SkRect& devOrig = devRRect.getBounds();
743     const SkVector& devRadiiUL = devRRect.radii(SkRRect::kUpperLeft_Corner);
744     const SkVector& devRadiiUR = devRRect.radii(SkRRect::kUpperRight_Corner);
745     const SkVector& devRadiiLR = devRRect.radii(SkRRect::kLowerRight_Corner);
746     const SkVector& devRadiiLL = devRRect.radii(SkRRect::kLowerLeft_Corner);
747 
748     const int devLeft  = SkScalarCeilToInt(std::max<SkScalar>(devRadiiUL.fX, devRadiiLL.fX));
749     const int devTop   = SkScalarCeilToInt(std::max<SkScalar>(devRadiiUL.fY, devRadiiUR.fY));
750     const int devRight = SkScalarCeilToInt(std::max<SkScalar>(devRadiiUR.fX, devRadiiLR.fX));
751     const int devBot   = SkScalarCeilToInt(std::max<SkScalar>(devRadiiLL.fY, devRadiiLR.fY));
752 
753     // This is a conservative check for nine-patchability
754     if (devOrig.fLeft + devLeft + devBlurRadius >= devOrig.fRight  - devRight - devBlurRadius ||
755         devOrig.fTop  + devTop  + devBlurRadius >= devOrig.fBottom - devBot   - devBlurRadius) {
756         return false;
757     }
758 
759     const SkVector& srcRadiiUL = srcRRect.radii(SkRRect::kUpperLeft_Corner);
760     const SkVector& srcRadiiUR = srcRRect.radii(SkRRect::kUpperRight_Corner);
761     const SkVector& srcRadiiLR = srcRRect.radii(SkRRect::kLowerRight_Corner);
762     const SkVector& srcRadiiLL = srcRRect.radii(SkRRect::kLowerLeft_Corner);
763 
764     const SkScalar srcLeft  = std::max<SkScalar>(srcRadiiUL.fX, srcRadiiLL.fX);
765     const SkScalar srcTop   = std::max<SkScalar>(srcRadiiUL.fY, srcRadiiUR.fY);
766     const SkScalar srcRight = std::max<SkScalar>(srcRadiiUR.fX, srcRadiiLR.fX);
767     const SkScalar srcBot   = std::max<SkScalar>(srcRadiiLL.fY, srcRadiiLR.fY);
768 
769     int newRRWidth = 2*devBlurRadius + devLeft + devRight + 1;
770     int newRRHeight = 2*devBlurRadius + devTop + devBot + 1;
771     widthHeight->fWidth = newRRWidth + 2 * devBlurRadius;
772     widthHeight->fHeight = newRRHeight + 2 * devBlurRadius;
773 
774     const SkRect srcProxyRect = srcRRect.getBounds().makeOutset(srcBlurRadius, srcBlurRadius);
775 
776     rectXs[0] = srcProxyRect.fLeft;
777     rectXs[1] = srcProxyRect.fLeft + 2*srcBlurRadius + srcLeft;
778     rectXs[2] = srcProxyRect.fRight - 2*srcBlurRadius - srcRight;
779     rectXs[3] = srcProxyRect.fRight;
780 
781     rectYs[0] = srcProxyRect.fTop;
782     rectYs[1] = srcProxyRect.fTop + 2*srcBlurRadius + srcTop;
783     rectYs[2] = srcProxyRect.fBottom - 2*srcBlurRadius - srcBot;
784     rectYs[3] = srcProxyRect.fBottom;
785 
786     texXs[0] = 0.0f;
787     texXs[1] = 2.0f*devBlurRadius + devLeft;
788     texXs[2] = 2.0f*devBlurRadius + devLeft + 1;
789     texXs[3] = SkIntToScalar(widthHeight->fWidth);
790 
791     texYs[0] = 0.0f;
792     texYs[1] = 2.0f*devBlurRadius + devTop;
793     texYs[2] = 2.0f*devBlurRadius + devTop + 1;
794     texYs[3] = SkIntToScalar(widthHeight->fHeight);
795 
796     const SkRect newRect = SkRect::MakeXYWH(SkIntToScalar(devBlurRadius),
797                                             SkIntToScalar(devBlurRadius),
798                                             SkIntToScalar(newRRWidth),
799                                             SkIntToScalar(newRRHeight));
800     SkVector newRadii[4];
801     newRadii[0] = { SkScalarCeilToScalar(devRadiiUL.fX), SkScalarCeilToScalar(devRadiiUL.fY) };
802     newRadii[1] = { SkScalarCeilToScalar(devRadiiUR.fX), SkScalarCeilToScalar(devRadiiUR.fY) };
803     newRadii[2] = { SkScalarCeilToScalar(devRadiiLR.fX), SkScalarCeilToScalar(devRadiiLR.fY) };
804     newRadii[3] = { SkScalarCeilToScalar(devRadiiLL.fX), SkScalarCeilToScalar(devRadiiLL.fY) };
805 
806     rrectToDraw->setRectRadii(newRect, newRadii);
807     return true;
808 }
809 
810 // TODO: it seems like there should be some synergy with SkBlurMask::ComputeBlurProfile
811 // TODO: maybe cache this on the cpu side?
CreateIntegralTable(float sixSigma,SkBitmap * table)812 int CreateIntegralTable(float sixSigma, SkBitmap* table) {
813     // The texture we're producing represents the integral of a normal distribution over a
814     // six-sigma range centered at zero. We want enough resolution so that the linear
815     // interpolation done in texture lookup doesn't introduce noticeable artifacts. We
816     // conservatively choose to have 2 texels for each dst pixel.
817     int minWidth = 2 * sk_float_ceil2int(sixSigma);
818     // Bin by powers of 2 with a minimum so we get good profile reuse.
819     int width = std::max(SkNextPow2(minWidth), 32);
820 
821     if (!table) {
822         return width;
823     }
824 
825     if (!table->tryAllocPixels(SkImageInfo::MakeA8(width, 1))) {
826         return 0;
827     }
828     *table->getAddr8(0, 0) = 255;
829     const float invWidth = 1.f / width;
830     for (int i = 1; i < width - 1; ++i) {
831         float x = (i + 0.5f) * invWidth;
832         x = (-6 * x + 3) * SK_ScalarRoot2Over2;
833         float integral = 0.5f * (std::erf(x) + 1.f);
834         *table->getAddr8(i, 0) = SkToU8(sk_float_round2int(255.f * integral));
835     }
836 
837     *table->getAddr8(width - 1, 0) = 0;
838     table->setImmutable();
839     return table->width();
840 }
841 
842 
Compute1DGaussianKernel(float * kernel,float sigma,int radius)843 void Compute1DGaussianKernel(float* kernel, float sigma, int radius) {
844     SkASSERT(radius == SigmaRadius(sigma));
845     if (SkGpuBlurUtils::IsEffectivelyZeroSigma(sigma)) {
846         // Calling SigmaRadius() produces 1, just computing ceil(sigma)*3 produces 3
847         SkASSERT(KernelWidth(radius) == 1);
848         std::fill_n(kernel, 1, 0.f);
849         kernel[0] = 1.f;
850         return;
851     }
852 
853     // If this fails, kEffectivelyZeroSigma isn't big enough to prevent precision issues
854     SkASSERT(!SkScalarNearlyZero(2.f * sigma * sigma));
855 
856     const float sigmaDenom = 1.0f / (2.f * sigma * sigma);
857     int size = KernelWidth(radius);
858     float sum = 0.0f;
859     for (int i = 0; i < size; ++i) {
860         float term = static_cast<float>(i - radius);
861         // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
862         // is dropped here, since we renormalize the kernel below.
863         kernel[i] = sk_float_exp(-term * term * sigmaDenom);
864         sum += kernel[i];
865     }
866     // Normalize the kernel
867     float scale = 1.0f / sum;
868     for (int i = 0; i < size; ++i) {
869         kernel[i] *= scale;
870     }
871 }
872 
Compute1DLinearGaussianKernel(float * kernel,float * offset,float sigma,int radius)873 void Compute1DLinearGaussianKernel(float* kernel, float* offset, float sigma, int radius) {
874     // Given 2 adjacent gaussian points, they are blended as: Wi * Ci + Wj * Cj.
875     // The GPU will mix Ci and Cj as Ci * (1 - x) + Cj * x during sampling.
876     // Compute W', x such that W' * (Ci * (1 - x) + Cj * x) = Wi * Ci + Wj * Cj.
877     // Solving W' * x = Wj, W' * (1 - x) = Wi:
878     // W' = Wi + Wj
879     // x = Wj / (Wi + Wj)
880     auto get_new_weight = [](float* new_w, float* offset, float wi, float wj) {
881         *new_w = wi + wj;
882         *offset = wj / (wi + wj);
883     };
884 
885     // Create a temporary standard kernel.
886     int size = KernelWidth(radius);
887     std::unique_ptr<float[]> temp_kernel(new float[size]);
888     Compute1DGaussianKernel(temp_kernel.get(), sigma, radius);
889 
890     // Note that halfsize isn't just size / 2, but radius + 1. This is the size of the output array.
891     int halfsize = LinearKernelWidth(radius);
892     int halfradius = halfsize / 2;
893     int low_index = halfradius - 1;
894 
895     // Compute1DGaussianKernel produces a full 2N + 1 kernel. Since the kernel can be mirrored,
896     // compute only the upper half and mirror to the lower half.
897 
898     int index = radius;
899     if (radius & 1) {
900         // If N is odd, then use two samples.
901         // The centre texel gets sampled twice, so halve its influence for each sample.
902         // We essentially sample like this:
903         // Texel edges
904         // v    v    v    v
905         // |    |    |    |
906         // \-----^---/ Lower sample
907         //      \---^-----/ Upper sample
908         get_new_weight(&kernel[halfradius], &offset[halfradius],
909                        temp_kernel[index] * 0.5f, temp_kernel[index + 1]);
910         kernel[low_index] = kernel[halfradius];
911         offset[low_index] = -offset[halfradius];
912         index++;
913         low_index--;
914     } else {
915         // If N is even, then there are an even number of texels on either side of the centre texel.
916         // Sample the centre texel directly.
917         kernel[halfradius] = temp_kernel[index];
918         offset[halfradius] = 0.0f;
919     }
920     index++;
921 
922     // Every other pair gets one sample.
923     for (int i = halfradius + 1; i < halfsize; index += 2, i++, low_index--) {
924         get_new_weight(&kernel[i], &offset[i], temp_kernel[index], temp_kernel[index + 1]);
925         offset[i] += static_cast<float>(index - radius);
926 
927         // Mirror to lower half.
928         kernel[low_index] = kernel[i];
929         offset[low_index] = -offset[i];
930     }
931 }
932 
933 }  // namespace SkGpuBlurUtils
934 
935 #endif
936