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