| 1 | /* |
| 2 | * Copyright 2013 Google Inc. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
| 6 | */ |
| 7 | |
| 8 | #include "src/core/SkGpuBlurUtils.h" |
| 9 | |
| 10 | #include "include/core/SkRect.h" |
| 11 | |
| 12 | #if SK_SUPPORT_GPU |
| 13 | #include "include/private/GrRecordingContext.h" |
| 14 | #include "src/gpu/GrCaps.h" |
| 15 | #include "src/gpu/GrFixedClip.h" |
| 16 | #include "src/gpu/GrRecordingContextPriv.h" |
| 17 | #include "src/gpu/GrRenderTargetContext.h" |
| 18 | #include "src/gpu/GrRenderTargetContextPriv.h" |
| 19 | #include "src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h" |
| 20 | #include "src/gpu/effects/GrMatrixConvolutionEffect.h" |
| 21 | |
| 22 | #include "src/gpu/SkGr.h" |
| 23 | |
| 24 | #define MAX_BLUR_SIGMA 4.0f |
| 25 | |
| 26 | using Direction = GrGaussianConvolutionFragmentProcessor::Direction; |
| 27 | |
| 28 | static void scale_irect_roundout(SkIRect* rect, float xScale, float yScale) { |
| 29 | rect->fLeft = SkScalarFloorToInt(rect->fLeft * xScale); |
| 30 | rect->fTop = SkScalarFloorToInt(rect->fTop * yScale); |
| 31 | rect->fRight = SkScalarCeilToInt(rect->fRight * xScale); |
| 32 | rect->fBottom = SkScalarCeilToInt(rect->fBottom * yScale); |
| 33 | } |
| 34 | |
| 35 | static void scale_irect(SkIRect* rect, int xScale, int yScale) { |
| 36 | rect->fLeft *= xScale; |
| 37 | rect->fTop *= yScale; |
| 38 | rect->fRight *= xScale; |
| 39 | rect->fBottom *= yScale; |
| 40 | } |
| 41 | |
| 42 | #ifdef SK_DEBUG |
| 43 | static inline int is_even(int x) { return !(x & 1); } |
| 44 | #endif |
| 45 | |
| 46 | static void shrink_irect_by_2(SkIRect* rect, bool xAxis, bool yAxis) { |
| 47 | if (xAxis) { |
| 48 | SkASSERT(is_even(rect->fLeft) && is_even(rect->fRight)); |
| 49 | rect->fLeft /= 2; |
| 50 | rect->fRight /= 2; |
| 51 | } |
| 52 | if (yAxis) { |
| 53 | SkASSERT(is_even(rect->fTop) && is_even(rect->fBottom)); |
| 54 | rect->fTop /= 2; |
| 55 | rect->fBottom /= 2; |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) { |
| 60 | *scaleFactor = 1; |
| 61 | while (sigma > MAX_BLUR_SIGMA) { |
| 62 | *scaleFactor *= 2; |
| 63 | sigma *= 0.5f; |
| 64 | if (*scaleFactor > maxTextureSize) { |
| 65 | *scaleFactor = maxTextureSize; |
| 66 | sigma = MAX_BLUR_SIGMA; |
| 67 | } |
| 68 | } |
| 69 | *radius = static_cast<int>(ceilf(sigma * 3.0f)); |
| 70 | SkASSERT(*radius <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius); |
| 71 | return sigma; |
| 72 | } |
| 73 | |
| 74 | static GrTextureDomain::Mode to_texture_domain_mode(SkTileMode tileMode) { |
| 75 | switch (tileMode) { |
| 76 | case SkTileMode::kClamp: |
| 77 | return GrTextureDomain::kClamp_Mode; |
| 78 | case SkTileMode::kDecal: |
| 79 | return GrTextureDomain::kDecal_Mode; |
| 80 | case SkTileMode::kMirror: |
| 81 | // TODO (michaelludwig) - Support mirror mode, treat as repeat for now |
| 82 | case SkTileMode::kRepeat: |
| 83 | return GrTextureDomain::kRepeat_Mode; |
| 84 | default: |
| 85 | SK_ABORT("Unsupported tile mode." ); |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * Draws 'rtcRect' into 'renderTargetContext' evaluating a 1D Gaussian over 'srcView'. The src rect |
| 91 | * is 'rtcRect' offset by 'rtcToSrcOffset'. 'mode' and 'bounds' are applied to the src coords. |
| 92 | */ |
| 93 | static void convolve_gaussian_1d(GrRenderTargetContext* renderTargetContext, |
| 94 | GrSurfaceProxyView srcView, |
| 95 | SkIVector rtcToSrcOffset, |
| 96 | const SkIRect& rtcRect, |
| 97 | SkAlphaType srcAlphaType, |
| 98 | Direction direction, |
| 99 | int radius, |
| 100 | float sigma, |
| 101 | SkTileMode mode, |
| 102 | int bounds[2]) { |
| 103 | GrPaint paint; |
| 104 | auto domainMode = to_texture_domain_mode(mode); |
| 105 | int realBounds[2]; |
| 106 | if (bounds) { |
| 107 | realBounds[0] = bounds[0]; realBounds[1] = bounds[1]; |
| 108 | } else { |
| 109 | auto proxy = srcView.proxy(); |
| 110 | realBounds[0] = 0; |
| 111 | realBounds[1] = direction == Direction::kX ? proxy->width() : proxy->height(); |
| 112 | } |
| 113 | std::unique_ptr<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make( |
| 114 | std::move(srcView), srcAlphaType, direction, radius, sigma, domainMode, realBounds)); |
| 115 | paint.addColorFragmentProcessor(std::move(conv)); |
| 116 | paint.setPorterDuffXPFactory(SkBlendMode::kSrc); |
| 117 | auto srcRect = SkRect::Make(rtcRect.makeOffset(rtcToSrcOffset)); |
| 118 | renderTargetContext->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), |
| 119 | SkRect::Make(rtcRect), srcRect); |
| 120 | } |
| 121 | |
| 122 | static std::unique_ptr<GrRenderTargetContext> convolve_gaussian_2d(GrRecordingContext* context, |
| 123 | GrSurfaceProxyView srcView, |
| 124 | GrColorType srcColorType, |
| 125 | const SkIRect& srcBounds, |
| 126 | const SkIRect& dstBounds, |
| 127 | int radiusX, |
| 128 | int radiusY, |
| 129 | SkScalar sigmaX, |
| 130 | SkScalar sigmaY, |
| 131 | SkTileMode mode, |
| 132 | sk_sp<SkColorSpace> finalCS, |
| 133 | SkBackingFit dstFit) { |
| 134 | auto renderTargetContext = GrRenderTargetContext::Make( |
| 135 | context, srcColorType, std::move(finalCS), dstFit, dstBounds.size(), 1, |
| 136 | GrMipMapped::kNo, srcView.proxy()->isProtected(), srcView.origin()); |
| 137 | if (!renderTargetContext) { |
| 138 | return nullptr; |
| 139 | } |
| 140 | |
| 141 | SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1); |
| 142 | SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY); |
| 143 | GrPaint paint; |
| 144 | auto domainMode = to_texture_domain_mode(mode); |
| 145 | auto conv = GrMatrixConvolutionEffect::MakeGaussian(std::move(srcView), srcBounds, size, |
| 146 | 1.0, 0.0, kernelOffset, domainMode, true, |
| 147 | sigmaX, sigmaY); |
| 148 | paint.addColorFragmentProcessor(std::move(conv)); |
| 149 | paint.setPorterDuffXPFactory(SkBlendMode::kSrc); |
| 150 | |
| 151 | // 'dstBounds' is actually in 'srcView' proxy space. It represents the blurred area from src |
| 152 | // space that we want to capture in the new RTC at {0, 0}. Hence, we use its size as the rect to |
| 153 | // draw and it directly as the local rect. |
| 154 | renderTargetContext->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), |
| 155 | SkRect::Make(dstBounds.size()), SkRect::Make(dstBounds)); |
| 156 | |
| 157 | return renderTargetContext; |
| 158 | } |
| 159 | |
| 160 | static std::unique_ptr<GrRenderTargetContext> convolve_gaussian(GrRecordingContext* context, |
| 161 | GrSurfaceProxyView srcView, |
| 162 | GrColorType srcColorType, |
| 163 | SkAlphaType srcAlphaType, |
| 164 | SkIRect* contentRect, |
| 165 | SkIRect dstBounds, |
| 166 | Direction direction, |
| 167 | int radius, |
| 168 | float sigma, |
| 169 | SkTileMode mode, |
| 170 | sk_sp<SkColorSpace> finalCS, |
| 171 | SkBackingFit fit) { |
| 172 | // Logically we're creating an infinite blur of 'contentRect' of 'srcView' with 'mode' tiling |
| 173 | // and then capturing the 'dstBounds' portion in a new RTC where the top left of 'dstBounds' is |
| 174 | // at {0, 0} in the new RTC. |
| 175 | auto dstRenderTargetContext = GrRenderTargetContext::Make( |
| 176 | context, srcColorType, std::move(finalCS), fit, dstBounds.size(), 1, GrMipMapped::kNo, |
| 177 | srcView.proxy()->isProtected(), srcView.origin()); |
| 178 | if (!dstRenderTargetContext) { |
| 179 | return nullptr; |
| 180 | } |
| 181 | |
| 182 | // This represents the translation from 'dstRenderTargetContext' coords to 'srcView' coords. |
| 183 | auto rtcToSrcOffset = dstBounds.topLeft(); |
| 184 | |
| 185 | if (SkTileMode::kClamp == mode && |
| 186 | contentRect->contains(SkIRect::MakeSize(srcView.proxy()->backingStoreDimensions()))) { |
| 187 | auto dstRect = SkIRect::MakeSize(dstBounds.size()); |
| 188 | convolve_gaussian_1d(dstRenderTargetContext.get(), std::move(srcView), rtcToSrcOffset, |
| 189 | dstRect, srcAlphaType, direction, radius, sigma, SkTileMode::kClamp, |
| 190 | nullptr); |
| 191 | *contentRect = dstRect; |
| 192 | return dstRenderTargetContext; |
| 193 | } |
| 194 | |
| 195 | // 'left' and 'right' are the sub rects of 'contentTect' where 'mode' must be enforced. |
| 196 | // 'mid' is the area where we can ignore the mode because the kernel does not reach to the |
| 197 | // edge of 'contentRect'. The names are derived from the Direction::kX case. |
| 198 | // TODO: When mode is kMirror or kRepeat it makes more sense to think of 'contentRect' |
| 199 | // as a tile and figure out the collection of mid/left/right rects that cover 'dstBounds'. |
| 200 | // Also if 'mid' is small and 'left' or 'right' is non-empty we should probably issue one |
| 201 | // draw that implements the mode in the shader rather than break it up in this fashion. |
| 202 | SkIRect mid, left, right; |
| 203 | // 'top' and 'bottom' are areas of 'dstBounds' that are entirely above/below |
| 204 | // 'contentRect'. These are areas that we can simply clear in the dst. If 'contentRect' |
| 205 | // straddles the top edge of 'dstBounds' then 'top' will be inverted and we will skip |
| 206 | // the clear. Similar for 'bottom'. The positional/directional labels above refer to the |
| 207 | // Direction::kX case and one should think of these as 'left' and 'right' for Direction::kY. |
| 208 | SkIRect top, bottom; |
| 209 | int bounds[2]; |
| 210 | if (Direction::kX == direction) { |
| 211 | bounds[0] = contentRect->left(); |
| 212 | bounds[1] = contentRect->right(); |
| 213 | |
| 214 | top = {dstBounds.left(), dstBounds.top() , dstBounds.right(), contentRect->top()}; |
| 215 | bottom = {dstBounds.left(), contentRect->bottom(), dstBounds.right(), dstBounds.bottom()}; |
| 216 | |
| 217 | // Inset for sub-rect of 'contentRect' where the x-dir kernel doesn't reach the edges. |
| 218 | // TODO: Consider clipping mid/left/right to dstBounds to increase likelihood of doing |
| 219 | // fewer draws below. |
| 220 | mid = contentRect->makeInset(radius, 0); |
| 221 | |
| 222 | left = {dstBounds.left(), mid.top(), mid.left() , mid.bottom()}; |
| 223 | right = {mid.right(), mid.top(), dstBounds.right(), mid.bottom()}; |
| 224 | |
| 225 | // The new 'contentRect' when we're done will be the area between the clears. |
| 226 | *contentRect = {dstBounds.left(), top.bottom(), dstBounds.right(), bottom.top()}; |
| 227 | } else { |
| 228 | // This is the same as the x direction code if you turn your head 90 degrees CCW. Swap x and |
| 229 | // y and swap top/bottom with left/right. |
| 230 | bounds[0] = contentRect->top(); |
| 231 | bounds[1] = contentRect->bottom(); |
| 232 | |
| 233 | top = {dstBounds.left(), dstBounds.top() , contentRect->left(), dstBounds.bottom()}; |
| 234 | bottom = {contentRect->right(), dstBounds.top() , dstBounds.right() , dstBounds.bottom()}; |
| 235 | |
| 236 | mid = contentRect->makeInset(0, radius); |
| 237 | |
| 238 | left = {mid.left(), dstBounds.top(), mid.right(), mid.top() }; |
| 239 | right = {mid.left(), mid.bottom() , mid.right(), dstBounds.bottom()}; |
| 240 | |
| 241 | *contentRect = {top.right(), dstBounds.top(), bottom.left(), dstBounds.bottom()}; |
| 242 | } |
| 243 | // Move all the rects from 'srcView' coord system to 'dstRenderTargetContext' coord system. |
| 244 | mid .offset(-rtcToSrcOffset); |
| 245 | top .offset(-rtcToSrcOffset); |
| 246 | bottom.offset(-rtcToSrcOffset); |
| 247 | left .offset(-rtcToSrcOffset); |
| 248 | right .offset(-rtcToSrcOffset); |
| 249 | |
| 250 | contentRect->offset(-rtcToSrcOffset); |
| 251 | |
| 252 | if (!top.isEmpty()) { |
| 253 | dstRenderTargetContext->clear(&top, SK_PMColor4fTRANSPARENT, |
| 254 | GrRenderTargetContext::CanClearFullscreen::kYes); |
| 255 | } |
| 256 | |
| 257 | if (!bottom.isEmpty()) { |
| 258 | dstRenderTargetContext->clear(&bottom, SK_PMColor4fTRANSPARENT, |
| 259 | GrRenderTargetContext::CanClearFullscreen::kYes); |
| 260 | } |
| 261 | |
| 262 | if (mid.isEmpty()) { |
| 263 | convolve_gaussian_1d(dstRenderTargetContext.get(), std::move(srcView), rtcToSrcOffset, |
| 264 | *contentRect, srcAlphaType, direction, radius, sigma, mode, bounds); |
| 265 | } else { |
| 266 | // Draw right and left margins with bounds; middle without. |
| 267 | convolve_gaussian_1d(dstRenderTargetContext.get(), srcView, rtcToSrcOffset, left, |
| 268 | srcAlphaType, direction, radius, sigma, mode, bounds); |
| 269 | convolve_gaussian_1d(dstRenderTargetContext.get(), srcView, rtcToSrcOffset, right, |
| 270 | srcAlphaType, direction, radius, sigma, mode, bounds); |
| 271 | convolve_gaussian_1d(dstRenderTargetContext.get(), std::move(srcView), rtcToSrcOffset, mid, |
| 272 | srcAlphaType, direction, radius, sigma, SkTileMode::kClamp, nullptr); |
| 273 | } |
| 274 | |
| 275 | return dstRenderTargetContext; |
| 276 | } |
| 277 | |
| 278 | // Returns a high quality scaled-down version of src. This is used to create an intermediate, |
| 279 | // shrunken version of the source image in the event that the requested blur sigma exceeds |
| 280 | // MAX_BLUR_SIGMA. |
| 281 | static GrSurfaceProxyView decimate(GrRecordingContext* context, |
| 282 | GrSurfaceProxyView srcView, |
| 283 | GrColorType srcColorType, |
| 284 | SkAlphaType srcAlphaType, |
| 285 | SkIPoint srcOffset, |
| 286 | SkIRect* contentRect, |
| 287 | int scaleFactorX, |
| 288 | int scaleFactorY, |
| 289 | SkTileMode mode, |
| 290 | sk_sp<SkColorSpace> finalCS) { |
| 291 | SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY)); |
| 292 | SkASSERT(scaleFactorX > 1 || scaleFactorY > 1); |
| 293 | |
| 294 | SkIRect srcRect = contentRect->makeOffset(srcOffset); |
| 295 | |
| 296 | scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); |
| 297 | scale_irect(&srcRect, scaleFactorX, scaleFactorY); |
| 298 | |
| 299 | SkIRect dstRect(srcRect); |
| 300 | |
| 301 | std::unique_ptr<GrRenderTargetContext> dstRenderTargetContext; |
| 302 | |
| 303 | for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { |
| 304 | shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY); |
| 305 | |
| 306 | dstRenderTargetContext = GrRenderTargetContext::Make( |
| 307 | context, srcColorType, finalCS, SkBackingFit::kApprox, |
| 308 | {dstRect.fRight, dstRect.fBottom}, 1, GrMipMapped::kNo, |
| 309 | srcView.proxy()->isProtected(), srcView.origin()); |
| 310 | if (!dstRenderTargetContext) { |
| 311 | return {}; |
| 312 | } |
| 313 | |
| 314 | GrPaint paint; |
| 315 | std::unique_ptr<GrFragmentProcessor> fp; |
| 316 | if (i == 1) { |
| 317 | GrSamplerState::WrapMode wrapMode; |
| 318 | if (mode == SkTileMode::kClamp) { |
| 319 | wrapMode = GrSamplerState::WrapMode::kClamp; |
| 320 | } else { |
| 321 | // GrTextureEffect does not support WrapMode::k[Mirror]Repeat with |
| 322 | // GrSamplerState::Filter::kBilerp. So we use kClampToBorder. |
| 323 | wrapMode = GrSamplerState::WrapMode::kClampToBorder; |
| 324 | } |
| 325 | const auto& caps = *context->priv().caps(); |
| 326 | GrSamplerState sampler(wrapMode, GrSamplerState::Filter::kBilerp); |
| 327 | fp = GrTextureEffect::MakeSubset(std::move(srcView), srcAlphaType, SkMatrix::I(), |
| 328 | sampler, SkRect::Make(*contentRect), caps); |
| 329 | srcRect.offset(-srcOffset); |
| 330 | } else { |
| 331 | fp = GrTextureEffect::Make(std::move(srcView), srcAlphaType, SkMatrix::I(), |
| 332 | GrSamplerState::Filter::kBilerp); |
| 333 | } |
| 334 | paint.addColorFragmentProcessor(std::move(fp)); |
| 335 | paint.setPorterDuffXPFactory(SkBlendMode::kSrc); |
| 336 | |
| 337 | dstRenderTargetContext->fillRectToRect(GrFixedClip::Disabled(), std::move(paint), GrAA::kNo, |
| 338 | SkMatrix::I(), SkRect::Make(dstRect), |
| 339 | SkRect::Make(srcRect)); |
| 340 | |
| 341 | srcView = dstRenderTargetContext->readSurfaceView(); |
| 342 | if (!srcView.asTextureProxy()) { |
| 343 | return {}; |
| 344 | } |
| 345 | srcRect = dstRect; |
| 346 | } |
| 347 | |
| 348 | *contentRect = dstRect; |
| 349 | |
| 350 | SkASSERT(dstRenderTargetContext); |
| 351 | SkASSERT(srcView == dstRenderTargetContext->readSurfaceView()); |
| 352 | |
| 353 | return srcView; |
| 354 | } |
| 355 | |
| 356 | // Expand the contents of 'srcRenderTargetContext' to fit in 'dstII'. At this point, we are |
| 357 | // expanding an intermediate image, so there's no need to account for a proxy offset from the |
| 358 | // original input. |
| 359 | static std::unique_ptr<GrRenderTargetContext> reexpand(GrRecordingContext* context, |
| 360 | std::unique_ptr<GrRenderTargetContext> src, |
| 361 | const SkIRect& srcBounds, |
| 362 | int scaleFactorX, |
| 363 | int scaleFactorY, |
| 364 | SkISize dstSize, |
| 365 | sk_sp<SkColorSpace> colorSpace, |
| 366 | SkBackingFit fit) { |
| 367 | const SkIRect srcRect = SkIRect::MakeWH(src->width(), src->height()); |
| 368 | |
| 369 | GrSurfaceProxyView srcView = src->readSurfaceView(); |
| 370 | if (!srcView.asTextureProxy()) { |
| 371 | return nullptr; |
| 372 | } |
| 373 | |
| 374 | GrColorType srcColorType = src->colorInfo().colorType(); |
| 375 | SkAlphaType srcAlphaType = src->colorInfo().alphaType(); |
| 376 | |
| 377 | src.reset(); // no longer needed |
| 378 | |
| 379 | auto dstRenderTargetContext = GrRenderTargetContext::Make( |
| 380 | context, srcColorType, std::move(colorSpace), fit, dstSize, 1, GrMipMapped::kNo, |
| 381 | srcView.proxy()->isProtected(), srcView.origin()); |
| 382 | if (!dstRenderTargetContext) { |
| 383 | return nullptr; |
| 384 | } |
| 385 | |
| 386 | GrPaint paint; |
| 387 | const auto& caps = *context->priv().caps(); |
| 388 | auto fp = GrTextureEffect::MakeSubset(std::move(srcView), srcAlphaType, SkMatrix::I(), |
| 389 | GrSamplerState::Filter::kBilerp, SkRect::Make(srcBounds), |
| 390 | caps); |
| 391 | paint.addColorFragmentProcessor(std::move(fp)); |
| 392 | paint.setPorterDuffXPFactory(SkBlendMode::kSrc); |
| 393 | GrFixedClip clip(SkIRect::MakeSize(dstSize)); |
| 394 | |
| 395 | // TODO: using dstII as dstRect results in some image diffs - why? |
| 396 | SkIRect dstRect(srcRect); |
| 397 | scale_irect(&dstRect, scaleFactorX, scaleFactorY); |
| 398 | |
| 399 | dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), |
| 400 | SkRect::Make(dstRect), SkRect::Make(srcRect)); |
| 401 | |
| 402 | return dstRenderTargetContext; |
| 403 | } |
| 404 | |
| 405 | static std::unique_ptr<GrRenderTargetContext> two_pass_gaussian(GrRecordingContext* context, |
| 406 | GrSurfaceProxyView srcView, |
| 407 | GrColorType srcColorType, |
| 408 | SkAlphaType srcAlphaType, |
| 409 | sk_sp<SkColorSpace> colorSpace, |
| 410 | SkIRect* srcBounds, |
| 411 | SkIRect dstBounds, |
| 412 | float sigmaX, |
| 413 | float sigmaY, |
| 414 | int radiusX, |
| 415 | int radiusY, |
| 416 | SkTileMode mode, |
| 417 | SkBackingFit fit) { |
| 418 | std::unique_ptr<GrRenderTargetContext> dstRenderTargetContext; |
| 419 | if (sigmaX > 0.0f) { |
| 420 | SkBackingFit xFit = sigmaY > 0 ? SkBackingFit::kApprox : fit; |
| 421 | dstRenderTargetContext = convolve_gaussian( |
| 422 | context, std::move(srcView), srcColorType, srcAlphaType, srcBounds, dstBounds, |
| 423 | Direction::kX, radiusX, sigmaX, mode, colorSpace, xFit); |
| 424 | if (!dstRenderTargetContext) { |
| 425 | return nullptr; |
| 426 | } |
| 427 | srcView = dstRenderTargetContext->readSurfaceView(); |
| 428 | dstBounds = SkIRect::MakeSize(dstBounds.size()); |
| 429 | } |
| 430 | |
| 431 | if (sigmaY == 0.0f) { |
| 432 | return dstRenderTargetContext; |
| 433 | } |
| 434 | |
| 435 | return convolve_gaussian(context, std::move(srcView), srcColorType, srcAlphaType, srcBounds, |
| 436 | dstBounds, Direction::kY, radiusY, sigmaY, mode, colorSpace, fit); |
| 437 | } |
| 438 | |
| 439 | namespace SkGpuBlurUtils { |
| 440 | |
| 441 | std::unique_ptr<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context, |
| 442 | GrSurfaceProxyView srcView, |
| 443 | GrColorType srcColorType, |
| 444 | SkAlphaType srcAlphaType, |
| 445 | sk_sp<SkColorSpace> colorSpace, |
| 446 | const SkIRect& dstBounds, |
| 447 | const SkIRect& srcBounds, |
| 448 | float sigmaX, |
| 449 | float sigmaY, |
| 450 | SkTileMode mode, |
| 451 | SkBackingFit fit) { |
| 452 | SkASSERT(context); |
| 453 | TRACE_EVENT2("skia.gpu" , "GaussianBlur" , "sigmaX" , sigmaX, "sigmaY" , sigmaY); |
| 454 | |
| 455 | if (!srcView.asTextureProxy()) { |
| 456 | return nullptr; |
| 457 | } |
| 458 | |
| 459 | int scaleFactorX, radiusX; |
| 460 | int scaleFactorY, radiusY; |
| 461 | int maxTextureSize = context->priv().caps()->maxTextureSize(); |
| 462 | sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX); |
| 463 | sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY); |
| 464 | SkASSERT(sigmaX || sigmaY); |
| 465 | |
| 466 | auto localSrcBounds = srcBounds; |
| 467 | |
| 468 | if (scaleFactorX == 1 && scaleFactorY == 1) { |
| 469 | // For really small blurs (certainly no wider than 5x5 on desktop GPUs) it is faster to just |
| 470 | // launch a single non separable kernel vs two launches. |
| 471 | if (sigmaX > 0 && sigmaY > 0 && (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) { |
| 472 | // Apply the proxy offset to src bounds and offset directly |
| 473 | return convolve_gaussian_2d(context, std::move(srcView), srcColorType, srcBounds, |
| 474 | dstBounds, radiusX, radiusY, sigmaX, sigmaY, mode, |
| 475 | colorSpace, fit); |
| 476 | } |
| 477 | return two_pass_gaussian(context, std::move(srcView), srcColorType, srcAlphaType, |
| 478 | std::move(colorSpace), &localSrcBounds, dstBounds, sigmaX, sigmaY, |
| 479 | radiusX, radiusY, mode, fit); |
| 480 | } |
| 481 | |
| 482 | auto srcOffset = -dstBounds.topLeft(); |
| 483 | srcView = decimate(context, std::move(srcView), srcColorType, srcAlphaType, srcOffset, |
| 484 | &localSrcBounds, scaleFactorX, scaleFactorY, mode, colorSpace); |
| 485 | if (!srcView.proxy()) { |
| 486 | return nullptr; |
| 487 | } |
| 488 | SkASSERT(srcView.asTextureProxy()); |
| 489 | auto scaledDstBounds = SkIRect::MakeWH(sk_float_ceil(dstBounds.width() / (float)scaleFactorX), |
| 490 | sk_float_ceil(dstBounds.height() / (float)scaleFactorY)); |
| 491 | auto rtc = two_pass_gaussian(context, std::move(srcView), srcColorType, srcAlphaType, |
| 492 | colorSpace, &localSrcBounds, scaledDstBounds, sigmaX, sigmaY, |
| 493 | radiusX, radiusY, mode, SkBackingFit::kApprox); |
| 494 | if (!rtc) { |
| 495 | return nullptr; |
| 496 | } |
| 497 | return reexpand(context, std::move(rtc), localSrcBounds, scaleFactorX, scaleFactorY, |
| 498 | dstBounds.size(), std::move(colorSpace), fit); |
| 499 | } |
| 500 | |
| 501 | } |
| 502 | |
| 503 | #endif |
| 504 | |