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 
20 namespace SkGpuBlurUtils {
21 
22 #if SK_SUPPORT_GPU
23 
24 #define MAX_BLUR_SIGMA 4.0f
25 
scale_rect(SkRect * rect,float xScale,float yScale)26 static 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 
adjust_sigma(float sigma,int maxTextureSize,int * scaleFactor,int * radius)33 static 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 
convolve_gaussian_1d(GrDrawContext * drawContext,const GrClip & clip,const SkRect & dstRect,const SkPoint & srcOffset,GrTexture * texture,Gr1DKernelEffect::Direction direction,int radius,float sigma,bool useBounds,float bounds[2])48 static 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 
convolve_gaussian_2d(GrDrawContext * drawContext,const GrClip & clip,const SkRect & dstRect,const SkPoint & srcOffset,GrTexture * texture,int radiusX,int radiusY,SkScalar sigmaX,SkScalar sigmaY,const SkRect * srcBounds)67 static 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 
convolve_gaussian(GrDrawContext * drawContext,const GrClip & clip,const SkRect & srcRect,GrTexture * texture,Gr1DKernelEffect::Direction direction,int radius,float sigma,const SkRect * srcBounds,const SkPoint & srcOffset)97 static 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 
GaussianBlur(GrContext * context,GrTexture * srcTexture,bool canClobberSrc,const SkRect & dstBounds,const SkRect * srcBounds,float sigmaX,float sigmaY)164 GrTexture* 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