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 "SkGpuBlurUtils.h" 9 10#include "SkRect.h" 11 12#if SK_SUPPORT_GPU 13#include "GrCaps.h" 14#include "GrContext.h" 15#include "GrFixedClip.h" 16#include "GrRenderTargetContext.h" 17#include "GrRenderTargetContextPriv.h" 18#include "effects/GrGaussianConvolutionFragmentProcessor.h" 19#include "effects/GrMatrixConvolutionEffect.h" 20 21#define MAX_BLUR_SIGMA 4.0f 22 23using Direction = GrGaussianConvolutionFragmentProcessor::Direction; 24 25static void scale_irect_roundout(SkIRect* rect, float xScale, float yScale) { 26 rect->fLeft = SkScalarFloorToInt(rect->fLeft * xScale); 27 rect->fTop = SkScalarFloorToInt(rect->fTop * yScale); 28 rect->fRight = SkScalarCeilToInt(rect->fRight * xScale); 29 rect->fBottom = SkScalarCeilToInt(rect->fBottom * yScale); 30} 31 32static void scale_irect(SkIRect* rect, int xScale, int yScale) { 33 rect->fLeft *= xScale; 34 rect->fTop *= yScale; 35 rect->fRight *= xScale; 36 rect->fBottom *= yScale; 37} 38 39#ifdef SK_DEBUG 40static inline int is_even(int x) { return !(x & 1); } 41#endif 42 43static void shrink_irect_by_2(SkIRect* rect, bool xAxis, bool yAxis) { 44 if (xAxis) { 45 SkASSERT(is_even(rect->fLeft) && is_even(rect->fRight)); 46 rect->fLeft /= 2; 47 rect->fRight /= 2; 48 } 49 if (yAxis) { 50 SkASSERT(is_even(rect->fTop) && is_even(rect->fBottom)); 51 rect->fTop /= 2; 52 rect->fBottom /= 2; 53 } 54} 55 56static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) { 57 *scaleFactor = 1; 58 while (sigma > MAX_BLUR_SIGMA) { 59 *scaleFactor *= 2; 60 sigma *= 0.5f; 61 if (*scaleFactor > maxTextureSize) { 62 *scaleFactor = maxTextureSize; 63 sigma = MAX_BLUR_SIGMA; 64 } 65 } 66 *radius = static_cast<int>(ceilf(sigma * 3.0f)); 67 SkASSERT(*radius <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius); 68 return sigma; 69} 70 71static void convolve_gaussian_1d(GrRenderTargetContext* renderTargetContext, 72 const GrClip& clip, 73 const SkIRect& dstRect, 74 const SkIPoint& srcOffset, 75 sk_sp<GrTextureProxy> proxy, 76 Direction direction, 77 int radius, 78 float sigma, 79 GrTextureDomain::Mode mode, 80 int bounds[2]) { 81 GrPaint paint; 82 paint.setGammaCorrect(renderTargetContext->colorSpaceInfo().isGammaCorrect()); 83 84 std::unique_ptr<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make( 85 std::move(proxy), direction, radius, sigma, mode, bounds)); 86 paint.addColorFragmentProcessor(std::move(conv)); 87 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 88 SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()), 89 -SkIntToScalar(srcOffset.y())); 90 renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), 91 SkRect::Make(dstRect), localMatrix); 92} 93 94static void convolve_gaussian_2d(GrRenderTargetContext* renderTargetContext, 95 const GrClip& clip, 96 const SkIRect& dstRect, 97 const SkIPoint& srcOffset, 98 sk_sp<GrTextureProxy> proxy, 99 int radiusX, 100 int radiusY, 101 SkScalar sigmaX, 102 SkScalar sigmaY, 103 const SkIRect& srcBounds, 104 GrTextureDomain::Mode mode) { 105 SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()), 106 -SkIntToScalar(srcOffset.y())); 107 SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1); 108 SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY); 109 GrPaint paint; 110 paint.setGammaCorrect(renderTargetContext->colorSpaceInfo().isGammaCorrect()); 111 112 auto conv = GrMatrixConvolutionEffect::MakeGaussian(std::move(proxy), srcBounds, size, 1.0, 0.0, 113 kernelOffset, mode, true, sigmaX, sigmaY); 114 paint.addColorFragmentProcessor(std::move(conv)); 115 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 116 renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), 117 SkRect::Make(dstRect), localMatrix); 118} 119 120static void convolve_gaussian(GrRenderTargetContext* renderTargetContext, 121 const GrClip& clip, 122 const SkIRect& srcRect, 123 sk_sp<GrTextureProxy> proxy, 124 Direction direction, 125 int radius, 126 float sigma, 127 const SkIRect& srcBounds, 128 const SkIPoint& srcOffset, 129 GrTextureDomain::Mode mode) { 130 int bounds[2] = { 0, 0 }; 131 SkIRect dstRect = SkIRect::MakeWH(srcRect.width(), srcRect.height()); 132 if (GrTextureDomain::kIgnore_Mode == mode) { 133 convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset, 134 std::move(proxy), direction, radius, sigma, 135 GrTextureDomain::kIgnore_Mode, bounds); 136 return; 137 } 138 SkIRect midRect = srcBounds, leftRect, rightRect; 139 midRect.offset(srcOffset); 140 SkIRect topRect, bottomRect; 141 if (Direction::kX == direction) { 142 bounds[0] = srcBounds.left(); 143 bounds[1] = srcBounds.right(); 144 topRect = SkIRect::MakeLTRB(0, 0, dstRect.right(), midRect.top()); 145 bottomRect = SkIRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom()); 146 midRect.inset(radius, 0); 147 leftRect = SkIRect::MakeLTRB(0, midRect.top(), midRect.left(), midRect.bottom()); 148 rightRect = 149 SkIRect::MakeLTRB(midRect.right(), midRect.top(), dstRect.width(), midRect.bottom()); 150 dstRect.fTop = midRect.top(); 151 dstRect.fBottom = midRect.bottom(); 152 } else { 153 bounds[0] = srcBounds.top(); 154 bounds[1] = srcBounds.bottom(); 155 topRect = SkIRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom()); 156 bottomRect = SkIRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom()); 157 midRect.inset(0, radius); 158 leftRect = SkIRect::MakeLTRB(midRect.left(), 0, midRect.right(), midRect.top()); 159 rightRect = 160 SkIRect::MakeLTRB(midRect.left(), midRect.bottom(), midRect.right(), dstRect.height()); 161 dstRect.fLeft = midRect.left(); 162 dstRect.fRight = midRect.right(); 163 } 164 if (!topRect.isEmpty()) { 165 renderTargetContext->clear(&topRect, 0, GrRenderTargetContext::CanClearFullscreen::kNo); 166 } 167 168 if (!bottomRect.isEmpty()) { 169 renderTargetContext->clear(&bottomRect, 0, GrRenderTargetContext::CanClearFullscreen::kNo); 170 } 171 172 if (midRect.isEmpty()) { 173 // Blur radius covers srcBounds; use bounds over entire draw 174 convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset, 175 std::move(proxy), direction, radius, sigma, mode, bounds); 176 } else { 177 // Draw right and left margins with bounds; middle without. 178 convolve_gaussian_1d(renderTargetContext, clip, leftRect, srcOffset, 179 proxy, direction, radius, sigma, mode, bounds); 180 convolve_gaussian_1d(renderTargetContext, clip, rightRect, srcOffset, 181 proxy, direction, radius, sigma, mode, bounds); 182 convolve_gaussian_1d(renderTargetContext, clip, midRect, srcOffset, 183 std::move(proxy), direction, radius, sigma, 184 GrTextureDomain::kIgnore_Mode, bounds); 185 } 186} 187 188namespace SkGpuBlurUtils { 189 190sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, 191 sk_sp<GrTextureProxy> srcProxy, 192 sk_sp<SkColorSpace> colorSpace, 193 const SkIRect& dstBounds, 194 const SkIRect& srcBounds, 195 float sigmaX, 196 float sigmaY, 197 GrTextureDomain::Mode mode, 198 SkBackingFit fit) { 199 SkASSERT(context); 200 SkIRect clearRect; 201 int scaleFactorX, radiusX; 202 int scaleFactorY, radiusY; 203 int maxTextureSize = context->caps()->maxTextureSize(); 204 sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX); 205 sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY); 206 SkASSERT(sigmaX || sigmaY); 207 208 SkIPoint srcOffset = SkIPoint::Make(-dstBounds.x(), -dstBounds.y()); 209 SkIRect localDstBounds = SkIRect::MakeWH(dstBounds.width(), dstBounds.height()); 210 SkIRect localSrcBounds; 211 SkIRect srcRect; 212 if (GrTextureDomain::kIgnore_Mode == mode) { 213 srcRect = localDstBounds; 214 } else { 215 srcRect = localSrcBounds = srcBounds; 216 srcRect.offset(srcOffset); 217 } 218 219 scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); 220 scale_irect(&srcRect, scaleFactorX, scaleFactorY); 221 222 // setup new clip 223 GrFixedClip clip(localDstBounds); 224 225 GrPixelConfig config = srcProxy->config(); 226 227 if (GrPixelConfigIsSRGB(config) && !colorSpace) { 228 // If the context doesn't have sRGB write control, and we make an sRGB RTC, we won't be 229 // able to suppress the linear -> sRGB conversion out of the shader. Not all GL drivers 230 // have that feature, and Vulkan is missing it entirely. To keep things simple, switch to 231 // a non-sRGB destination, to ensure correct blurring behavior. 232 config = kRGBA_8888_GrPixelConfig; 233 } 234 235 SkASSERT(kBGRA_8888_GrPixelConfig == config || kRGBA_8888_GrPixelConfig == config || 236 kRGBA_4444_GrPixelConfig == config || kRGB_565_GrPixelConfig == config || 237 kSRGBA_8888_GrPixelConfig == config || kSBGRA_8888_GrPixelConfig == config || 238 kRGBA_half_GrPixelConfig == config || kAlpha_8_GrPixelConfig == config); 239 240 const int width = dstBounds.width(); 241 const int height = dstBounds.height(); 242 243 sk_sp<GrRenderTargetContext> dstRenderTargetContext; 244 245 // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just 246 // launch a single non separable kernel vs two launches 247 if (sigmaX > 0.0f && sigmaY > 0.0f && 248 (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) { 249 // We shouldn't be scaling because this is a small size blur 250 SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY)); 251 252 dstRenderTargetContext = context->makeDeferredRenderTargetContext(fit, width, height, 253 config, colorSpace); 254 if (!dstRenderTargetContext) { 255 return nullptr; 256 } 257 258 convolve_gaussian_2d(dstRenderTargetContext.get(), 259 clip, localDstBounds, srcOffset, std::move(srcProxy), 260 radiusX, radiusY, sigmaX, sigmaY, srcBounds, mode); 261 262 return dstRenderTargetContext; 263 } 264 265 SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY)); 266 267 // GrTextureDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter. 268 GrTextureDomain::Mode modeForScaling = 269 GrTextureDomain::kRepeat_Mode == mode ? GrTextureDomain::kDecal_Mode : mode; 270 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { 271 SkIRect dstRect(srcRect); 272 shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY); 273 274 dstRenderTargetContext = context->makeDeferredRenderTargetContext( 275 fit, 276 SkTMin(dstRect.fRight, width), 277 SkTMin(dstRect.fBottom, height), 278 config, colorSpace); 279 if (!dstRenderTargetContext) { 280 return nullptr; 281 } 282 283 GrPaint paint; 284 paint.setGammaCorrect(dstRenderTargetContext->colorSpaceInfo().isGammaCorrect()); 285 286 if (GrTextureDomain::kIgnore_Mode != mode && i == 1) { 287 SkRect domain = SkRect::Make(localSrcBounds); 288 domain.inset((i < scaleFactorX) ? SK_ScalarHalf : 0.0f, 289 (i < scaleFactorY) ? SK_ScalarHalf : 0.0f); 290 auto fp = GrTextureDomainEffect::Make(std::move(srcProxy), 291 SkMatrix::I(), 292 domain, 293 modeForScaling, 294 GrSamplerState::Filter::kBilerp); 295 paint.addColorFragmentProcessor(std::move(fp)); 296 srcRect.offset(-srcOffset); 297 srcOffset.set(0, 0); 298 } else { 299 paint.addColorTextureProcessor(std::move(srcProxy), SkMatrix::I(), 300 GrSamplerState::ClampBilerp()); 301 } 302 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 303 304 dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), 305 SkRect::Make(dstRect), SkRect::Make(srcRect)); 306 307 srcProxy = dstRenderTargetContext->asTextureProxyRef(); 308 if (!srcProxy) { 309 return nullptr; 310 } 311 srcRect = dstRect; 312 localSrcBounds = srcRect; 313 } 314 315 srcRect = localDstBounds; 316 scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); 317 if (sigmaX > 0.0f) { 318 if (scaleFactorX > 1) { 319 SkASSERT(dstRenderTargetContext); 320 321 // Clear out a radius to the right of the srcRect to prevent the 322 // X convolution from reading garbage. 323 clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop, 324 radiusX, srcRect.height()); 325 dstRenderTargetContext->priv().absClear(&clearRect, 0x0); 326 } 327 328 SkASSERT(srcRect.width() <= width && srcRect.height() <= height); 329 dstRenderTargetContext = context->makeDeferredRenderTargetContext(fit, srcRect.width(), 330 srcRect.height(), 331 config, colorSpace); 332 if (!dstRenderTargetContext) { 333 return nullptr; 334 } 335 336 convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect, std::move(srcProxy), 337 Direction::kX, radiusX, sigmaX, localSrcBounds, srcOffset, mode); 338 339 srcProxy = dstRenderTargetContext->asTextureProxyRef(); 340 if (!srcProxy) { 341 return nullptr; 342 } 343 srcRect.offsetTo(0, 0); 344 localSrcBounds = srcRect; 345 if (GrTextureDomain::kClamp_Mode == mode) { 346 // We need to adjust bounds because we only fill part of the srcRect in x-pass. 347 localSrcBounds.inset(0, radiusY); 348 } 349 srcOffset.set(0, 0); 350 } 351 352 if (sigmaY > 0.0f) { 353 if (scaleFactorY > 1 || sigmaX > 0.0f) { 354 SkASSERT(dstRenderTargetContext); 355 356 // Clear out a radius below the srcRect to prevent the Y 357 // convolution from reading garbage. 358 clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, 359 srcRect.width(), radiusY); 360 dstRenderTargetContext->priv().absClear(&clearRect, 0x0); 361 } 362 363 SkASSERT(srcRect.width() <= width && srcRect.height() <= height); 364 dstRenderTargetContext = context->makeDeferredRenderTargetContext(fit, srcRect.width(), 365 srcRect.height(), 366 config, colorSpace); 367 if (!dstRenderTargetContext) { 368 return nullptr; 369 } 370 371 convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect, std::move(srcProxy), 372 Direction::kY, radiusY, sigmaY, localSrcBounds, srcOffset, mode); 373 374 srcProxy = dstRenderTargetContext->asTextureProxyRef(); 375 if (!srcProxy) { 376 return nullptr; 377 } 378 srcRect.offsetTo(0, 0); 379 } 380 381 SkASSERT(dstRenderTargetContext); 382 SkASSERT(srcProxy.get() == dstRenderTargetContext->asTextureProxy()); 383 384 if (scaleFactorX > 1 || scaleFactorY > 1) { 385 // Clear one pixel to the right and below, to accommodate bilinear upsampling. 386 // TODO: it seems like we should actually be clamping here rather than darkening 387 // the bottom right edges. 388 clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, srcRect.width() + 1, 1); 389 dstRenderTargetContext->priv().absClear(&clearRect, 0x0); 390 clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop, 1, srcRect.height()); 391 dstRenderTargetContext->priv().absClear(&clearRect, 0x0); 392 393 SkIRect dstRect(srcRect); 394 scale_irect(&dstRect, scaleFactorX, scaleFactorY); 395 396 dstRenderTargetContext = context->makeDeferredRenderTargetContext( 397 fit, SkTMin(dstRect.width(), width), 398 SkTMin(dstRect.height(), height), 399 config, colorSpace); 400 if (!dstRenderTargetContext) { 401 return nullptr; 402 } 403 404 GrPaint paint; 405 paint.setGammaCorrect(dstRenderTargetContext->colorSpaceInfo().isGammaCorrect()); 406 407 if (GrTextureDomain::kIgnore_Mode != mode) { 408 SkRect domain = SkRect::Make(localSrcBounds); 409 auto fp = GrTextureDomainEffect::Make(std::move(srcProxy), 410 SkMatrix::I(), 411 domain, 412 modeForScaling, 413 GrSamplerState::Filter::kBilerp); 414 paint.addColorFragmentProcessor(std::move(fp)); 415 } else { 416 // FIXME: this should be mitchell, not bilinear. 417 paint.addColorTextureProcessor(std::move(srcProxy), SkMatrix::I(), 418 GrSamplerState::ClampBilerp()); 419 } 420 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 421 422 dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), 423 SkRect::Make(dstRect), SkRect::Make(srcRect)); 424 425 srcProxy = dstRenderTargetContext->asTextureProxyRef(); 426 if (!srcProxy) { 427 return nullptr; 428 } 429 srcRect = dstRect; 430 } 431 432 return dstRenderTargetContext; 433} 434 435} 436 437#endif 438 439