Home | History | Annotate | Download | only in effects
      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