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 23 using Direction = GrGaussianConvolutionFragmentProcessor::Direction; 24 25 static 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 32 static 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 40 static inline int is_even(int x) { return !(x & 1); } 41 #endif 42 43 static 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 56 static 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 71 static 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 94 static 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 120 static 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 188 namespace SkGpuBlurUtils { 189 190 sk_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 kRGBA_1010102_GrPixelConfig == config); 240 241 const int width = dstBounds.width(); 242 const int height = dstBounds.height(); 243 244 sk_sp<GrRenderTargetContext> dstRenderTargetContext; 245 246 // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just 247 // launch a single non separable kernel vs two launches 248 if (sigmaX > 0.0f && sigmaY > 0.0f && 249 (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) { 250 // We shouldn't be scaling because this is a small size blur 251 SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY)); 252 253 dstRenderTargetContext = context->makeDeferredRenderTargetContext(fit, width, height, 254 config, colorSpace); 255 if (!dstRenderTargetContext) { 256 return nullptr; 257 } 258 259 convolve_gaussian_2d(dstRenderTargetContext.get(), 260 clip, localDstBounds, srcOffset, std::move(srcProxy), 261 radiusX, radiusY, sigmaX, sigmaY, srcBounds, mode); 262 263 return dstRenderTargetContext; 264 } 265 266 SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY)); 267 268 // GrTextureDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter. 269 GrTextureDomain::Mode modeForScaling = 270 GrTextureDomain::kRepeat_Mode == mode ? GrTextureDomain::kDecal_Mode : mode; 271 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { 272 SkIRect dstRect(srcRect); 273 shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY); 274 275 dstRenderTargetContext = context->makeDeferredRenderTargetContext( 276 fit, 277 SkTMin(dstRect.fRight, width), 278 SkTMin(dstRect.fBottom, height), 279 config, colorSpace); 280 if (!dstRenderTargetContext) { 281 return nullptr; 282 } 283 284 GrPaint paint; 285 paint.setGammaCorrect(dstRenderTargetContext->colorSpaceInfo().isGammaCorrect()); 286 287 if (GrTextureDomain::kIgnore_Mode != mode && i == 1) { 288 SkRect domain = SkRect::Make(localSrcBounds); 289 domain.inset((i < scaleFactorX) ? SK_ScalarHalf : 0.0f, 290 (i < scaleFactorY) ? SK_ScalarHalf : 0.0f); 291 auto fp = GrTextureDomainEffect::Make(std::move(srcProxy), 292 SkMatrix::I(), 293 domain, 294 modeForScaling, 295 GrSamplerState::Filter::kBilerp); 296 paint.addColorFragmentProcessor(std::move(fp)); 297 srcRect.offset(-srcOffset); 298 srcOffset.set(0, 0); 299 } else { 300 paint.addColorTextureProcessor(std::move(srcProxy), SkMatrix::I(), 301 GrSamplerState::ClampBilerp()); 302 } 303 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 304 305 dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), 306 SkRect::Make(dstRect), SkRect::Make(srcRect)); 307 308 srcProxy = dstRenderTargetContext->asTextureProxyRef(); 309 if (!srcProxy) { 310 return nullptr; 311 } 312 srcRect = dstRect; 313 localSrcBounds = srcRect; 314 } 315 316 srcRect = localDstBounds; 317 scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); 318 if (sigmaX > 0.0f) { 319 if (scaleFactorX > 1) { 320 SkASSERT(dstRenderTargetContext); 321 322 // Clear out a radius to the right of the srcRect to prevent the 323 // X convolution from reading garbage. 324 clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop, 325 radiusX, srcRect.height()); 326 dstRenderTargetContext->priv().absClear(&clearRect, 0x0); 327 } 328 329 SkASSERT(srcRect.width() <= width && srcRect.height() <= height); 330 dstRenderTargetContext = context->makeDeferredRenderTargetContext(fit, srcRect.width(), 331 srcRect.height(), 332 config, colorSpace); 333 if (!dstRenderTargetContext) { 334 return nullptr; 335 } 336 337 convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect, std::move(srcProxy), 338 Direction::kX, radiusX, sigmaX, localSrcBounds, srcOffset, mode); 339 340 srcProxy = dstRenderTargetContext->asTextureProxyRef(); 341 if (!srcProxy) { 342 return nullptr; 343 } 344 srcRect.offsetTo(0, 0); 345 localSrcBounds = srcRect; 346 if (GrTextureDomain::kClamp_Mode == mode) { 347 // We need to adjust bounds because we only fill part of the srcRect in x-pass. 348 localSrcBounds.inset(0, radiusY); 349 } 350 srcOffset.set(0, 0); 351 } 352 353 if (sigmaY > 0.0f) { 354 if (scaleFactorY > 1 || sigmaX > 0.0f) { 355 SkASSERT(dstRenderTargetContext); 356 357 // Clear out a radius below the srcRect to prevent the Y 358 // convolution from reading garbage. 359 clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, 360 srcRect.width(), radiusY); 361 dstRenderTargetContext->priv().absClear(&clearRect, 0x0); 362 } 363 364 SkASSERT(srcRect.width() <= width && srcRect.height() <= height); 365 dstRenderTargetContext = context->makeDeferredRenderTargetContext(fit, srcRect.width(), 366 srcRect.height(), 367 config, colorSpace); 368 if (!dstRenderTargetContext) { 369 return nullptr; 370 } 371 372 convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect, std::move(srcProxy), 373 Direction::kY, radiusY, sigmaY, localSrcBounds, srcOffset, mode); 374 375 srcProxy = dstRenderTargetContext->asTextureProxyRef(); 376 if (!srcProxy) { 377 return nullptr; 378 } 379 srcRect.offsetTo(0, 0); 380 } 381 382 SkASSERT(dstRenderTargetContext); 383 SkASSERT(srcProxy.get() == dstRenderTargetContext->asTextureProxy()); 384 385 if (scaleFactorX > 1 || scaleFactorY > 1) { 386 // Clear one pixel to the right and below, to accommodate bilinear upsampling. 387 // TODO: it seems like we should actually be clamping here rather than darkening 388 // the bottom right edges. 389 clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, srcRect.width() + 1, 1); 390 dstRenderTargetContext->priv().absClear(&clearRect, 0x0); 391 clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop, 1, srcRect.height()); 392 dstRenderTargetContext->priv().absClear(&clearRect, 0x0); 393 394 SkIRect dstRect(srcRect); 395 scale_irect(&dstRect, scaleFactorX, scaleFactorY); 396 397 dstRenderTargetContext = context->makeDeferredRenderTargetContext( 398 fit, SkTMin(dstRect.width(), width), 399 SkTMin(dstRect.height(), height), 400 config, colorSpace); 401 if (!dstRenderTargetContext) { 402 return nullptr; 403 } 404 405 GrPaint paint; 406 paint.setGammaCorrect(dstRenderTargetContext->colorSpaceInfo().isGammaCorrect()); 407 408 if (GrTextureDomain::kIgnore_Mode != mode) { 409 SkRect domain = SkRect::Make(localSrcBounds); 410 auto fp = GrTextureDomainEffect::Make(std::move(srcProxy), 411 SkMatrix::I(), 412 domain, 413 modeForScaling, 414 GrSamplerState::Filter::kBilerp); 415 paint.addColorFragmentProcessor(std::move(fp)); 416 } else { 417 // FIXME: this should be mitchell, not bilinear. 418 paint.addColorTextureProcessor(std::move(srcProxy), SkMatrix::I(), 419 GrSamplerState::ClampBilerp()); 420 } 421 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 422 423 dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), 424 SkRect::Make(dstRect), SkRect::Make(srcRect)); 425 426 srcProxy = dstRenderTargetContext->asTextureProxyRef(); 427 if (!srcProxy) { 428 return nullptr; 429 } 430 srcRect = dstRect; 431 } 432 433 return dstRenderTargetContext; 434 } 435 436 } 437 438 #endif 439 440