• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
scale_rect(SkRect * rect,float xScale,float yScale)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 
adjust_sigma(float sigma,int maxTextureSize,int * scaleFactor,int * radius)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 
convolve_gaussian_1d(GrContext * context,const SkRect & srcRect,const SkRect & dstRect,GrTexture * texture,Gr1DKernelEffect::Direction direction,int radius,float sigma,bool useBounds,float bounds[2])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 
convolve_gaussian_2d(GrContext * context,const SkRect & srcRect,const SkRect & dstRect,GrTexture * texture,int radiusX,int radiusY,SkScalar sigmaX,SkScalar sigmaY,bool useBounds,SkIRect bounds)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 
convolve_gaussian(GrContext * context,const SkRect & srcRect,const SkRect & dstRect,GrTexture * texture,Gr1DKernelEffect::Direction direction,int radius,float sigma,bool cropToSrcRect)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 
GaussianBlur(GrContext * context,GrTexture * srcTexture,bool canClobberSrc,const SkRect & rect,bool cropToRect,float sigmaX,float sigmaY)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