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