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
18namespace SkGpuBlurUtils {
19
20#if SK_SUPPORT_GPU
21
22#define MAX_BLUR_SIGMA 4.0f
23
24static 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
31static 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
46static 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
64static 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
87static 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
144GrTexture* 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