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
20namespace SkGpuBlurUtils {
21
22#if SK_SUPPORT_GPU
23
24#define MAX_BLUR_SIGMA 4.0f
25
26static 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
33static 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
48static 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
67static 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
97static 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
164GrTexture* 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