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 "effects/GrConvolutionEffect.h" 14#include "effects/GrMatrixConvolutionEffect.h" 15#include "GrContext.h" 16#include "GrCaps.h" 17#include "GrDrawContext.h" 18#endif 19 20namespace SkGpuBlurUtils { 21 22#if SK_SUPPORT_GPU 23 24#define MAX_BLUR_SIGMA 4.0f 25 26static void scale_rect(SkRect* rect, float xScale, float yScale) { 27 rect->fLeft = SkScalarMul(rect->fLeft, xScale); 28 rect->fTop = SkScalarMul(rect->fTop, yScale); 29 rect->fRight = SkScalarMul(rect->fRight, xScale); 30 rect->fBottom = SkScalarMul(rect->fBottom, yScale); 31} 32 33static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) { 34 *scaleFactor = 1; 35 while (sigma > MAX_BLUR_SIGMA) { 36 *scaleFactor *= 2; 37 sigma *= 0.5f; 38 if (*scaleFactor > maxTextureSize) { 39 *scaleFactor = maxTextureSize; 40 sigma = MAX_BLUR_SIGMA; 41 } 42 } 43 *radius = static_cast<int>(ceilf(sigma * 3.0f)); 44 SkASSERT(*radius <= GrConvolutionEffect::kMaxKernelRadius); 45 return sigma; 46} 47 48static void convolve_gaussian_1d(GrDrawContext* drawContext, 49 const GrClip& clip, 50 const SkRect& dstRect, 51 const SkPoint& srcOffset, 52 GrTexture* texture, 53 Gr1DKernelEffect::Direction direction, 54 int radius, 55 float sigma, 56 bool useBounds, 57 float bounds[2]) { 58 GrPaint paint; 59 SkAutoTUnref<GrFragmentProcessor> conv(GrConvolutionEffect::CreateGaussian( 60 texture, direction, radius, sigma, useBounds, bounds)); 61 paint.addColorFragmentProcessor(conv); 62 paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode); 63 SkMatrix localMatrix = SkMatrix::MakeTrans(-srcOffset.x(), -srcOffset.y()); 64 drawContext->fillRectWithLocalMatrix(clip, paint, SkMatrix::I(), dstRect, localMatrix); 65} 66 67static void convolve_gaussian_2d(GrDrawContext* drawContext, 68 const GrClip& clip, 69 const SkRect& dstRect, 70 const SkPoint& srcOffset, 71 GrTexture* texture, 72 int radiusX, 73 int radiusY, 74 SkScalar sigmaX, 75 SkScalar sigmaY, 76 const SkRect* srcBounds) { 77 SkMatrix localMatrix = SkMatrix::MakeTrans(-srcOffset.x(), -srcOffset.y()); 78 SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1); 79 SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY); 80 GrPaint paint; 81 SkIRect bounds; 82 if (srcBounds) { 83 srcBounds->roundOut(&bounds); 84 } else { 85 bounds.setEmpty(); 86 } 87 88 SkAutoTUnref<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::CreateGaussian( 89 texture, bounds, size, 1.0, 0.0, kernelOffset, 90 srcBounds ? GrTextureDomain::kDecal_Mode : GrTextureDomain::kIgnore_Mode, 91 true, sigmaX, sigmaY)); 92 paint.addColorFragmentProcessor(conv); 93 paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode); 94 drawContext->fillRectWithLocalMatrix(clip, paint, SkMatrix::I(), dstRect, localMatrix); 95} 96 97static void convolve_gaussian(GrDrawContext* drawContext, 98 const GrClip& clip, 99 const SkRect& srcRect, 100 GrTexture* texture, 101 Gr1DKernelEffect::Direction direction, 102 int radius, 103 float sigma, 104 const SkRect* srcBounds, 105 const SkPoint& srcOffset) { 106 float bounds[2] = { 0.0f, 1.0f }; 107 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height()); 108 if (!srcBounds) { 109 convolve_gaussian_1d(drawContext, clip, dstRect, srcOffset, texture, 110 direction, radius, sigma, false, bounds); 111 return; 112 } 113 SkRect midRect = *srcBounds, leftRect, rightRect; 114 midRect.offset(srcOffset); 115 SkIRect topRect, bottomRect; 116 SkScalar rad = SkIntToScalar(radius); 117 if (direction == Gr1DKernelEffect::kX_Direction) { 118 bounds[0] = SkScalarToFloat(srcBounds->left()) / texture->width(); 119 bounds[1] = SkScalarToFloat(srcBounds->right()) / texture->width(); 120 SkRect::MakeLTRB(0, 0, dstRect.right(), midRect.top()).roundOut(&topRect); 121 SkRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom()) 122 .roundOut(&bottomRect); 123 midRect.inset(rad, 0); 124 leftRect = SkRect::MakeLTRB(0, midRect.top(), midRect.left(), midRect.bottom()); 125 rightRect = 126 SkRect::MakeLTRB(midRect.right(), midRect.top(), dstRect.width(), midRect.bottom()); 127 dstRect.fTop = midRect.top(); 128 dstRect.fBottom = midRect.bottom(); 129 } else { 130 bounds[0] = SkScalarToFloat(srcBounds->top()) / texture->height(); 131 bounds[1] = SkScalarToFloat(srcBounds->bottom()) / texture->height(); 132 SkRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom()).roundOut(&topRect); 133 SkRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom()) 134 .roundOut(&bottomRect);; 135 midRect.inset(0, rad); 136 leftRect = SkRect::MakeLTRB(midRect.left(), 0, midRect.right(), midRect.top()); 137 rightRect = 138 SkRect::MakeLTRB(midRect.left(), midRect.bottom(), midRect.right(), dstRect.height()); 139 dstRect.fLeft = midRect.left(); 140 dstRect.fRight = midRect.right(); 141 } 142 if (!topRect.isEmpty()) { 143 drawContext->clear(&topRect, 0, false); 144 } 145 146 if (!bottomRect.isEmpty()) { 147 drawContext->clear(&bottomRect, 0, false); 148 } 149 if (midRect.isEmpty()) { 150 // Blur radius covers srcBounds; use bounds over entire draw 151 convolve_gaussian_1d(drawContext, clip, dstRect, srcOffset, texture, 152 direction, radius, sigma, true, bounds); 153 } else { 154 // Draw right and left margins with bounds; middle without. 155 convolve_gaussian_1d(drawContext, clip, leftRect, srcOffset, texture, 156 direction, radius, sigma, true, bounds); 157 convolve_gaussian_1d(drawContext, clip, rightRect, srcOffset, texture, 158 direction, radius, sigma, true, bounds); 159 convolve_gaussian_1d(drawContext, clip, midRect, srcOffset, texture, 160 direction, radius, sigma, false, bounds); 161 } 162} 163 164GrTexture* GaussianBlur(GrContext* context, 165 GrTexture* srcTexture, 166 bool canClobberSrc, 167 const SkRect& dstBounds, 168 const SkRect* srcBounds, 169 float sigmaX, 170 float sigmaY) { 171 SkASSERT(context); 172 SkIRect clearRect; 173 int scaleFactorX, radiusX; 174 int scaleFactorY, radiusY; 175 int maxTextureSize = context->caps()->maxTextureSize(); 176 sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX); 177 sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY); 178 179 SkPoint srcOffset = SkPoint::Make(-dstBounds.x(), -dstBounds.y()); 180 SkRect localDstBounds = SkRect::MakeWH(dstBounds.width(), dstBounds.height()); 181 SkRect localSrcBounds; 182 SkRect srcRect; 183 if (srcBounds) { 184 srcRect = localSrcBounds = *srcBounds; 185 srcRect.offset(srcOffset); 186 srcBounds = &localSrcBounds; 187 } else { 188 srcRect = localDstBounds; 189 } 190 191 scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); 192 srcRect.roundOut(&srcRect); 193 scale_rect(&srcRect, static_cast<float>(scaleFactorX), 194 static_cast<float>(scaleFactorY)); 195 196 // setup new clip 197 GrClip clip(localDstBounds); 198 199 SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() || 200 kRGBA_8888_GrPixelConfig == srcTexture->config() || 201 kAlpha_8_GrPixelConfig == srcTexture->config()); 202 203 GrSurfaceDesc desc; 204 desc.fFlags = kRenderTarget_GrSurfaceFlag; 205 desc.fWidth = SkScalarFloorToInt(dstBounds.width()); 206 desc.fHeight = SkScalarFloorToInt(dstBounds.height()); 207 desc.fConfig = srcTexture->config(); 208 209 GrTexture* dstTexture; 210 GrTexture* tempTexture; 211 SkAutoTUnref<GrTexture> temp1, temp2; 212 213 temp1.reset(context->textureProvider()->createApproxTexture(desc)); 214 dstTexture = temp1.get(); 215 if (canClobberSrc) { 216 tempTexture = srcTexture; 217 } else { 218 temp2.reset(context->textureProvider()->createApproxTexture(desc)); 219 tempTexture = temp2.get(); 220 } 221 222 if (nullptr == dstTexture || nullptr == tempTexture) { 223 return nullptr; 224 } 225 226 SkAutoTUnref<GrDrawContext> srcDrawContext; 227 228 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { 229 GrPaint paint; 230 SkMatrix matrix; 231 matrix.setIDiv(srcTexture->width(), srcTexture->height()); 232 SkRect dstRect(srcRect); 233 if (srcBounds && i == 1) { 234 SkRect domain; 235 matrix.mapRect(&domain, *srcBounds); 236 domain.inset((i < scaleFactorX) ? SK_ScalarHalf / srcTexture->width() : 0.0f, 237 (i < scaleFactorY) ? SK_ScalarHalf / srcTexture->height() : 0.0f); 238 SkAutoTUnref<const GrFragmentProcessor> fp(GrTextureDomainEffect::Create( 239 srcTexture, 240 matrix, 241 domain, 242 GrTextureDomain::kDecal_Mode, 243 GrTextureParams::kBilerp_FilterMode)); 244 paint.addColorFragmentProcessor(fp); 245 srcRect.offset(-srcOffset); 246 srcOffset.set(0, 0); 247 } else { 248 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode); 249 paint.addColorTextureProcessor(srcTexture, matrix, params); 250 } 251 paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode); 252 scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f, 253 i < scaleFactorY ? 0.5f : 1.0f); 254 255 SkAutoTUnref<GrDrawContext> dstDrawContext( 256 context->drawContext(dstTexture->asRenderTarget())); 257 if (!dstDrawContext) { 258 return nullptr; 259 } 260 dstDrawContext->fillRectToRect(clip, paint, SkMatrix::I(), dstRect, srcRect); 261 262 srcDrawContext.swap(dstDrawContext); 263 srcRect = dstRect; 264 srcTexture = dstTexture; 265 SkTSwap(dstTexture, tempTexture); 266 localSrcBounds = srcRect; 267 } 268 269 // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just 270 // launch a single non separable kernel vs two launches 271 srcRect = localDstBounds; 272 if (sigmaX > 0.0f && sigmaY > 0.0f && 273 (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) { 274 // We shouldn't be scaling because this is a small size blur 275 SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY)); 276 277 SkAutoTUnref<GrDrawContext> dstDrawContext( 278 context->drawContext(dstTexture->asRenderTarget())); 279 if (!dstDrawContext) { 280 return nullptr; 281 } 282 convolve_gaussian_2d(dstDrawContext, clip, srcRect, srcOffset, 283 srcTexture, radiusX, radiusY, sigmaX, sigmaY, srcBounds); 284 285 srcDrawContext.swap(dstDrawContext); 286 srcRect.offsetTo(0, 0); 287 srcTexture = dstTexture; 288 SkTSwap(dstTexture, tempTexture); 289 290 } else { 291 scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); 292 srcRect.roundOut(&srcRect); 293 const SkIRect srcIRect = srcRect.roundOut(); 294 if (sigmaX > 0.0f) { 295 if (scaleFactorX > 1) { 296 // TODO: if we pass in the source draw context we don't need this here 297 if (!srcDrawContext) { 298 srcDrawContext.reset(context->drawContext(srcTexture->asRenderTarget())); 299 if (!srcDrawContext) { 300 return nullptr; 301 } 302 } 303 304 // Clear out a radius to the right of the srcRect to prevent the 305 // X convolution from reading garbage. 306 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, 307 radiusX, srcIRect.height()); 308 srcDrawContext->clear(&clearRect, 0x0, false); 309 } 310 311 SkAutoTUnref<GrDrawContext> dstDrawContext( 312 context->drawContext(dstTexture->asRenderTarget())); 313 if (!dstDrawContext) { 314 return nullptr; 315 } 316 convolve_gaussian(dstDrawContext, clip, srcRect, 317 srcTexture, Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, 318 srcBounds, srcOffset); 319 srcDrawContext.swap(dstDrawContext); 320 srcTexture = dstTexture; 321 srcRect.offsetTo(0, 0); 322 SkTSwap(dstTexture, tempTexture); 323 localSrcBounds = srcRect; 324 srcOffset.set(0, 0); 325 } 326 327 if (sigmaY > 0.0f) { 328 if (scaleFactorY > 1 || sigmaX > 0.0f) { 329 // TODO: if we pass in the source draw context we don't need this here 330 if (!srcDrawContext) { 331 srcDrawContext.reset(context->drawContext(srcTexture->asRenderTarget())); 332 if (!srcDrawContext) { 333 return nullptr; 334 } 335 } 336 337 // Clear out a radius below the srcRect to prevent the Y 338 // convolution from reading garbage. 339 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, 340 srcIRect.width(), radiusY); 341 srcDrawContext->clear(&clearRect, 0x0, false); 342 } 343 344 SkAutoTUnref<GrDrawContext> dstDrawContext( 345 context->drawContext(dstTexture->asRenderTarget())); 346 if (!dstDrawContext) { 347 return nullptr; 348 } 349 convolve_gaussian(dstDrawContext, clip, srcRect, 350 srcTexture, Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, 351 srcBounds, srcOffset); 352 353 srcDrawContext.swap(dstDrawContext); 354 srcTexture = dstTexture; 355 srcRect.offsetTo(0, 0); 356 SkTSwap(dstTexture, tempTexture); 357 } 358 } 359 const SkIRect srcIRect = srcRect.roundOut(); 360 361 if (scaleFactorX > 1 || scaleFactorY > 1) { 362 SkASSERT(srcDrawContext); 363 364 // Clear one pixel to the right and below, to accommodate bilinear 365 // upsampling. 366 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, 367 srcIRect.width() + 1, 1); 368 srcDrawContext->clear(&clearRect, 0x0, false); 369 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, 370 1, srcIRect.height()); 371 srcDrawContext->clear(&clearRect, 0x0, false); 372 SkMatrix matrix; 373 matrix.setIDiv(srcTexture->width(), srcTexture->height()); 374 375 GrPaint paint; 376 // FIXME: this should be mitchell, not bilinear. 377 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode); 378 paint.addColorTextureProcessor(srcTexture, matrix, params); 379 paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode); 380 381 SkRect dstRect(srcRect); 382 scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY); 383 384 SkAutoTUnref<GrDrawContext> dstDrawContext( 385 context->drawContext(dstTexture->asRenderTarget())); 386 if (!dstDrawContext) { 387 return nullptr; 388 } 389 dstDrawContext->fillRectToRect(clip, paint, SkMatrix::I(), dstRect, srcRect); 390 391 srcDrawContext.swap(dstDrawContext); 392 srcRect = dstRect; 393 srcTexture = dstTexture; 394 SkTSwap(dstTexture, tempTexture); 395 } 396 397 return SkRef(srcTexture); 398} 399#endif 400 401} 402