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