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 #endif 17 18 namespace SkGpuBlurUtils { 19 20 #if SK_SUPPORT_GPU 21 22 #define MAX_BLUR_SIGMA 4.0f 23 24 static void scale_rect(SkRect* rect, float xScale, float yScale) { 25 rect->fLeft = SkScalarMul(rect->fLeft, xScale); 26 rect->fTop = SkScalarMul(rect->fTop, yScale); 27 rect->fRight = SkScalarMul(rect->fRight, xScale); 28 rect->fBottom = SkScalarMul(rect->fBottom, yScale); 29 } 30 31 static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) { 32 *scaleFactor = 1; 33 while (sigma > MAX_BLUR_SIGMA) { 34 *scaleFactor *= 2; 35 sigma *= 0.5f; 36 if (*scaleFactor > maxTextureSize) { 37 *scaleFactor = maxTextureSize; 38 sigma = MAX_BLUR_SIGMA; 39 } 40 } 41 *radius = static_cast<int>(ceilf(sigma * 3.0f)); 42 SkASSERT(*radius <= GrConvolutionEffect::kMaxKernelRadius); 43 return sigma; 44 } 45 46 static void convolve_gaussian_1d(GrContext* context, 47 const SkRect& srcRect, 48 const SkRect& dstRect, 49 GrTexture* texture, 50 Gr1DKernelEffect::Direction direction, 51 int radius, 52 float sigma, 53 bool useBounds, 54 float bounds[2]) { 55 GrPaint paint; 56 paint.reset(); 57 SkAutoTUnref<GrFragmentProcessor> conv(GrConvolutionEffect::CreateGaussian( 58 texture, direction, radius, sigma, useBounds, bounds)); 59 paint.reset(); 60 paint.addColorProcessor(conv); 61 context->drawRectToRect(paint, dstRect, srcRect); 62 } 63 64 static void convolve_gaussian_2d(GrContext* context, 65 const SkRect& srcRect, 66 const SkRect& dstRect, 67 GrTexture* texture, 68 int radiusX, 69 int radiusY, 70 SkScalar sigmaX, 71 SkScalar sigmaY, 72 bool useBounds, 73 SkIRect bounds) { 74 SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1); 75 SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY); 76 GrPaint paint; 77 paint.reset(); 78 SkAutoTUnref<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::CreateGaussian( 79 texture, bounds, size, 1.0, 0.0, kernelOffset, 80 useBounds ? GrTextureDomain::kClamp_Mode : GrTextureDomain::kIgnore_Mode, 81 true, sigmaX, sigmaY)); 82 paint.reset(); 83 paint.addColorProcessor(conv); 84 context->drawRectToRect(paint, dstRect, srcRect); 85 } 86 87 static void convolve_gaussian(GrContext* context, 88 const SkRect& srcRect, 89 const SkRect& dstRect, 90 GrTexture* texture, 91 Gr1DKernelEffect::Direction direction, 92 int radius, 93 float sigma, 94 bool cropToSrcRect) { 95 float bounds[2] = { 0.0f, 1.0f }; 96 if (!cropToSrcRect) { 97 convolve_gaussian_1d(context, srcRect, dstRect, texture, 98 direction, radius, sigma, false, bounds); 99 return; 100 } 101 SkRect lowerSrcRect = srcRect, lowerDstRect = dstRect; 102 SkRect middleSrcRect = srcRect, middleDstRect = dstRect; 103 SkRect upperSrcRect = srcRect, upperDstRect = dstRect; 104 SkScalar size; 105 SkScalar rad = SkIntToScalar(radius); 106 if (direction == Gr1DKernelEffect::kX_Direction) { 107 bounds[0] = SkScalarToFloat(srcRect.left()) / texture->width(); 108 bounds[1] = SkScalarToFloat(srcRect.right()) / texture->width(); 109 size = srcRect.width(); 110 lowerSrcRect.fRight = srcRect.left() + rad; 111 lowerDstRect.fRight = dstRect.left() + rad; 112 upperSrcRect.fLeft = srcRect.right() - rad; 113 upperDstRect.fLeft = dstRect.right() - rad; 114 middleSrcRect.inset(rad, 0); 115 middleDstRect.inset(rad, 0); 116 } else { 117 bounds[0] = SkScalarToFloat(srcRect.top()) / texture->height(); 118 bounds[1] = SkScalarToFloat(srcRect.bottom()) / texture->height(); 119 size = srcRect.height(); 120 lowerSrcRect.fBottom = srcRect.top() + rad; 121 lowerDstRect.fBottom = dstRect.top() + rad; 122 upperSrcRect.fTop = srcRect.bottom() - rad; 123 upperDstRect.fTop = dstRect.bottom() - rad; 124 middleSrcRect.inset(0, rad); 125 middleDstRect.inset(0, rad); 126 } 127 if (radius >= size * SK_ScalarHalf) { 128 // Blur radius covers srcRect; use bounds over entire draw 129 convolve_gaussian_1d(context, srcRect, dstRect, texture, 130 direction, radius, sigma, true, bounds); 131 } else { 132 // Draw upper and lower margins with bounds; middle without. 133 convolve_gaussian_1d(context, lowerSrcRect, lowerDstRect, texture, 134 direction, radius, sigma, true, bounds); 135 convolve_gaussian_1d(context, upperSrcRect, upperDstRect, texture, 136 direction, radius, sigma, true, bounds); 137 convolve_gaussian_1d(context, middleSrcRect, middleDstRect, texture, 138 direction, radius, sigma, false, bounds); 139 } 140 } 141 142 GrTexture* GaussianBlur(GrContext* context, 143 GrTexture* srcTexture, 144 bool canClobberSrc, 145 const SkRect& rect, 146 bool cropToRect, 147 float sigmaX, 148 float sigmaY) { 149 SkASSERT(context); 150 151 GrContext::AutoRenderTarget art(context); 152 153 GrContext::AutoMatrix am; 154 am.setIdentity(context); 155 156 SkIRect clearRect; 157 int scaleFactorX, radiusX; 158 int scaleFactorY, radiusY; 159 int maxTextureSize = context->getMaxTextureSize(); 160 sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX); 161 sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY); 162 163 SkRect srcRect(rect); 164 scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); 165 srcRect.roundOut(); 166 scale_rect(&srcRect, static_cast<float>(scaleFactorX), 167 static_cast<float>(scaleFactorY)); 168 169 GrContext::AutoClip acs(context, SkRect::MakeWH(srcRect.width(), srcRect.height())); 170 171 SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() || 172 kRGBA_8888_GrPixelConfig == srcTexture->config() || 173 kAlpha_8_GrPixelConfig == srcTexture->config()); 174 175 GrTextureDesc desc; 176 desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit; 177 desc.fWidth = SkScalarFloorToInt(srcRect.width()); 178 desc.fHeight = SkScalarFloorToInt(srcRect.height()); 179 desc.fConfig = srcTexture->config(); 180 181 GrAutoScratchTexture temp1, temp2; 182 GrTexture* dstTexture = temp1.set(context, desc); 183 GrTexture* tempTexture = canClobberSrc ? srcTexture : temp2.set(context, desc); 184 if (NULL == dstTexture || NULL == tempTexture) { 185 return NULL; 186 } 187 188 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { 189 GrPaint paint; 190 SkMatrix matrix; 191 matrix.setIDiv(srcTexture->width(), srcTexture->height()); 192 context->setRenderTarget(dstTexture->asRenderTarget()); 193 SkRect dstRect(srcRect); 194 if (cropToRect && i == 1) { 195 dstRect.offset(-dstRect.fLeft, -dstRect.fTop); 196 SkRect domain; 197 matrix.mapRect(&domain, rect); 198 domain.inset(i < scaleFactorX ? SK_ScalarHalf / srcTexture->width() : 0.0f, 199 i < scaleFactorY ? SK_ScalarHalf / srcTexture->height() : 0.0f); 200 SkAutoTUnref<GrFragmentProcessor> fp(GrTextureDomainEffect::Create( 201 srcTexture, 202 matrix, 203 domain, 204 GrTextureDomain::kDecal_Mode, 205 GrTextureParams::kBilerp_FilterMode)); 206 paint.addColorProcessor(fp); 207 } else { 208 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode); 209 paint.addColorTextureProcessor(srcTexture, matrix, params); 210 } 211 scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f, 212 i < scaleFactorY ? 0.5f : 1.0f); 213 context->drawRectToRect(paint, dstRect, srcRect); 214 srcRect = dstRect; 215 srcTexture = dstTexture; 216 SkTSwap(dstTexture, tempTexture); 217 } 218 219 SkIRect srcIRect; 220 srcRect.roundOut(&srcIRect); 221 222 // For really small blurs(Certainly no wider than 5x5 on desktop gpus) it is faster to just 223 // launch a single non separable kernel vs two launches 224 if (sigmaX > 0.0f && sigmaY > 0 && 225 (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) { 226 // We shouldn't be scaling because this is a small size blur 227 SkASSERT((scaleFactorX == scaleFactorY) == 1); 228 context->setRenderTarget(dstTexture->asRenderTarget()); 229 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height()); 230 convolve_gaussian_2d(context, srcRect, dstRect, srcTexture, 231 radiusX, radiusY, sigmaX, sigmaY, cropToRect, srcIRect); 232 srcTexture = dstTexture; 233 srcRect = dstRect; 234 SkTSwap(dstTexture, tempTexture); 235 236 } else { 237 if (sigmaX > 0.0f) { 238 if (scaleFactorX > 1) { 239 // Clear out a radius to the right of the srcRect to prevent the 240 // X convolution from reading garbage. 241 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, 242 radiusX, srcIRect.height()); 243 context->clear(&clearRect, 0x0, false); 244 } 245 context->setRenderTarget(dstTexture->asRenderTarget()); 246 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height()); 247 convolve_gaussian(context, srcRect, dstRect, srcTexture, 248 Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect); 249 srcTexture = dstTexture; 250 srcRect = dstRect; 251 SkTSwap(dstTexture, tempTexture); 252 } 253 254 if (sigmaY > 0.0f) { 255 if (scaleFactorY > 1 || sigmaX > 0.0f) { 256 // Clear out a radius below the srcRect to prevent the Y 257 // convolution from reading garbage. 258 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, 259 srcIRect.width(), radiusY); 260 context->clear(&clearRect, 0x0, false); 261 } 262 263 context->setRenderTarget(dstTexture->asRenderTarget()); 264 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height()); 265 convolve_gaussian(context, srcRect, dstRect, srcTexture, 266 Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect); 267 srcTexture = dstTexture; 268 srcRect = dstRect; 269 SkTSwap(dstTexture, tempTexture); 270 } 271 } 272 273 if (scaleFactorX > 1 || scaleFactorY > 1) { 274 // Clear one pixel to the right and below, to accommodate bilinear 275 // upsampling. 276 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, 277 srcIRect.width() + 1, 1); 278 context->clear(&clearRect, 0x0, false); 279 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, 280 1, srcIRect.height()); 281 context->clear(&clearRect, 0x0, false); 282 SkMatrix matrix; 283 matrix.setIDiv(srcTexture->width(), srcTexture->height()); 284 context->setRenderTarget(dstTexture->asRenderTarget()); 285 286 GrPaint paint; 287 // FIXME: this should be mitchell, not bilinear. 288 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode); 289 paint.addColorTextureProcessor(srcTexture, matrix, params); 290 291 SkRect dstRect(srcRect); 292 scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY); 293 context->drawRectToRect(paint, dstRect, srcRect); 294 srcRect = dstRect; 295 srcTexture = dstTexture; 296 SkTSwap(dstTexture, tempTexture); 297 } 298 if (srcTexture == temp1.texture()) { 299 return temp1.detach(); 300 } else if (srcTexture == temp2.texture()) { 301 return temp2.detach(); 302 } else { 303 srcTexture->ref(); 304 return srcTexture; 305 } 306 } 307 #endif 308 309 } 310