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