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 "GrCaps.h"
14#include "GrContext.h"
15#include "GrFixedClip.h"
16#include "GrRenderTargetContext.h"
17#include "GrRenderTargetContextPriv.h"
18#include "effects/GrGaussianConvolutionFragmentProcessor.h"
19#include "effects/GrMatrixConvolutionEffect.h"
20
21#define MAX_BLUR_SIGMA 4.0f
22
23using Direction = GrGaussianConvolutionFragmentProcessor::Direction;
24
25static void scale_irect_roundout(SkIRect* rect, float xScale, float yScale) {
26    rect->fLeft   = SkScalarFloorToInt(rect->fLeft  * xScale);
27    rect->fTop    = SkScalarFloorToInt(rect->fTop   * yScale);
28    rect->fRight  = SkScalarCeilToInt(rect->fRight  * xScale);
29    rect->fBottom = SkScalarCeilToInt(rect->fBottom * yScale);
30}
31
32static void scale_irect(SkIRect* rect, int xScale, int yScale) {
33    rect->fLeft   *= xScale;
34    rect->fTop    *= yScale;
35    rect->fRight  *= xScale;
36    rect->fBottom *= yScale;
37}
38
39#ifdef SK_DEBUG
40static inline int is_even(int x) { return !(x & 1); }
41#endif
42
43static void shrink_irect_by_2(SkIRect* rect, bool xAxis, bool yAxis) {
44    if (xAxis) {
45        SkASSERT(is_even(rect->fLeft) && is_even(rect->fRight));
46        rect->fLeft /= 2;
47        rect->fRight /= 2;
48    }
49    if (yAxis) {
50        SkASSERT(is_even(rect->fTop) && is_even(rect->fBottom));
51        rect->fTop /= 2;
52        rect->fBottom /= 2;
53    }
54}
55
56static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) {
57    *scaleFactor = 1;
58    while (sigma > MAX_BLUR_SIGMA) {
59        *scaleFactor *= 2;
60        sigma *= 0.5f;
61        if (*scaleFactor > maxTextureSize) {
62            *scaleFactor = maxTextureSize;
63            sigma = MAX_BLUR_SIGMA;
64        }
65    }
66    *radius = static_cast<int>(ceilf(sigma * 3.0f));
67    SkASSERT(*radius <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius);
68    return sigma;
69}
70
71static void convolve_gaussian_1d(GrRenderTargetContext* renderTargetContext,
72                                 const GrClip& clip,
73                                 const SkIRect& dstRect,
74                                 const SkIPoint& srcOffset,
75                                 sk_sp<GrTextureProxy> proxy,
76                                 Direction direction,
77                                 int radius,
78                                 float sigma,
79                                 GrTextureDomain::Mode mode,
80                                 int bounds[2]) {
81    GrPaint paint;
82    paint.setGammaCorrect(renderTargetContext->colorSpaceInfo().isGammaCorrect());
83
84    std::unique_ptr<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make(
85            std::move(proxy), direction, radius, sigma, mode, bounds));
86    paint.addColorFragmentProcessor(std::move(conv));
87    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
88    SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
89                                               -SkIntToScalar(srcOffset.y()));
90    renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
91                                                 SkRect::Make(dstRect), localMatrix);
92}
93
94static void convolve_gaussian_2d(GrRenderTargetContext* renderTargetContext,
95                                 const GrClip& clip,
96                                 const SkIRect& dstRect,
97                                 const SkIPoint& srcOffset,
98                                 sk_sp<GrTextureProxy> proxy,
99                                 int radiusX,
100                                 int radiusY,
101                                 SkScalar sigmaX,
102                                 SkScalar sigmaY,
103                                 const SkIRect& srcBounds,
104                                 GrTextureDomain::Mode mode) {
105    SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
106                                               -SkIntToScalar(srcOffset.y()));
107    SkISize size = SkISize::Make(2 * radiusX + 1,  2 * radiusY + 1);
108    SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
109    GrPaint paint;
110    paint.setGammaCorrect(renderTargetContext->colorSpaceInfo().isGammaCorrect());
111
112    auto conv = GrMatrixConvolutionEffect::MakeGaussian(std::move(proxy), srcBounds, size, 1.0, 0.0,
113                                                        kernelOffset, mode, true, sigmaX, sigmaY);
114    paint.addColorFragmentProcessor(std::move(conv));
115    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
116    renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
117                                                 SkRect::Make(dstRect), localMatrix);
118}
119
120static void convolve_gaussian(GrRenderTargetContext* renderTargetContext,
121                              const GrClip& clip,
122                              const SkIRect& srcRect,
123                              sk_sp<GrTextureProxy> proxy,
124                              Direction direction,
125                              int radius,
126                              float sigma,
127                              const SkIRect& srcBounds,
128                              const SkIPoint& srcOffset,
129                              GrTextureDomain::Mode mode) {
130    int bounds[2] = { 0, 0 };
131    SkIRect dstRect = SkIRect::MakeWH(srcRect.width(), srcRect.height());
132    if (GrTextureDomain::kIgnore_Mode == mode) {
133        convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset,
134                             std::move(proxy), direction, radius, sigma,
135                             GrTextureDomain::kIgnore_Mode, bounds);
136        return;
137    }
138    SkIRect midRect = srcBounds, leftRect, rightRect;
139    midRect.offset(srcOffset);
140    SkIRect topRect, bottomRect;
141    if (Direction::kX == direction) {
142        bounds[0] = srcBounds.left();
143        bounds[1] = srcBounds.right();
144        topRect = SkIRect::MakeLTRB(0, 0, dstRect.right(), midRect.top());
145        bottomRect = SkIRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom());
146        midRect.inset(radius, 0);
147        leftRect = SkIRect::MakeLTRB(0, midRect.top(), midRect.left(), midRect.bottom());
148        rightRect =
149            SkIRect::MakeLTRB(midRect.right(), midRect.top(), dstRect.width(), midRect.bottom());
150        dstRect.fTop = midRect.top();
151        dstRect.fBottom = midRect.bottom();
152    } else {
153        bounds[0] = srcBounds.top();
154        bounds[1] = srcBounds.bottom();
155        topRect = SkIRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom());
156        bottomRect = SkIRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom());
157        midRect.inset(0, radius);
158        leftRect = SkIRect::MakeLTRB(midRect.left(), 0, midRect.right(), midRect.top());
159        rightRect =
160            SkIRect::MakeLTRB(midRect.left(), midRect.bottom(), midRect.right(), dstRect.height());
161        dstRect.fLeft = midRect.left();
162        dstRect.fRight = midRect.right();
163    }
164    if (!topRect.isEmpty()) {
165        renderTargetContext->clear(&topRect, 0, GrRenderTargetContext::CanClearFullscreen::kNo);
166    }
167
168    if (!bottomRect.isEmpty()) {
169        renderTargetContext->clear(&bottomRect, 0, GrRenderTargetContext::CanClearFullscreen::kNo);
170    }
171
172    if (midRect.isEmpty()) {
173        // Blur radius covers srcBounds; use bounds over entire draw
174        convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset,
175                             std::move(proxy), direction, radius, sigma, mode, bounds);
176    } else {
177        // Draw right and left margins with bounds; middle without.
178        convolve_gaussian_1d(renderTargetContext, clip, leftRect, srcOffset,
179                             proxy, direction, radius, sigma, mode, bounds);
180        convolve_gaussian_1d(renderTargetContext, clip, rightRect, srcOffset,
181                             proxy, direction, radius, sigma, mode, bounds);
182        convolve_gaussian_1d(renderTargetContext, clip, midRect, srcOffset,
183                             std::move(proxy), direction, radius, sigma,
184                             GrTextureDomain::kIgnore_Mode, bounds);
185    }
186}
187
188namespace SkGpuBlurUtils {
189
190sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context,
191                                          sk_sp<GrTextureProxy> srcProxy,
192                                          sk_sp<SkColorSpace> colorSpace,
193                                          const SkIRect& dstBounds,
194                                          const SkIRect& srcBounds,
195                                          float sigmaX,
196                                          float sigmaY,
197                                          GrTextureDomain::Mode mode,
198                                          SkBackingFit fit) {
199    SkASSERT(context);
200    SkIRect clearRect;
201    int scaleFactorX, radiusX;
202    int scaleFactorY, radiusY;
203    int maxTextureSize = context->caps()->maxTextureSize();
204    sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
205    sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
206    SkASSERT(sigmaX || sigmaY);
207
208    SkIPoint srcOffset = SkIPoint::Make(-dstBounds.x(), -dstBounds.y());
209    SkIRect localDstBounds = SkIRect::MakeWH(dstBounds.width(), dstBounds.height());
210    SkIRect localSrcBounds;
211    SkIRect srcRect;
212    if (GrTextureDomain::kIgnore_Mode == mode) {
213        srcRect = localDstBounds;
214    } else {
215        srcRect = localSrcBounds = srcBounds;
216        srcRect.offset(srcOffset);
217    }
218
219    scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
220    scale_irect(&srcRect, scaleFactorX, scaleFactorY);
221
222    // setup new clip
223    GrFixedClip clip(localDstBounds);
224
225    GrPixelConfig config = srcProxy->config();
226
227    if (GrPixelConfigIsSRGB(config) && !colorSpace) {
228        // If the context doesn't have sRGB write control, and we make an sRGB RTC, we won't be
229        // able to suppress the linear -> sRGB conversion out of the shader. Not all GL drivers
230        // have that feature, and Vulkan is missing it entirely. To keep things simple, switch to
231        // a non-sRGB destination, to ensure correct blurring behavior.
232        config = kRGBA_8888_GrPixelConfig;
233    }
234
235    SkASSERT(kBGRA_8888_GrPixelConfig == config || kRGBA_8888_GrPixelConfig == config ||
236             kRGBA_4444_GrPixelConfig == config || kRGB_565_GrPixelConfig == config ||
237             kSRGBA_8888_GrPixelConfig == config || kSBGRA_8888_GrPixelConfig == config ||
238             kRGBA_half_GrPixelConfig == config || kAlpha_8_GrPixelConfig == config);
239
240    const int width = dstBounds.width();
241    const int height = dstBounds.height();
242
243    sk_sp<GrRenderTargetContext> dstRenderTargetContext;
244
245    // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just
246    // launch a single non separable kernel vs two launches
247    if (sigmaX > 0.0f && sigmaY > 0.0f &&
248            (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
249        // We shouldn't be scaling because this is a small size blur
250        SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY));
251
252        dstRenderTargetContext = context->makeDeferredRenderTargetContext(fit, width, height,
253                                                                          config, colorSpace);
254        if (!dstRenderTargetContext) {
255            return nullptr;
256        }
257
258        convolve_gaussian_2d(dstRenderTargetContext.get(),
259                             clip, localDstBounds, srcOffset, std::move(srcProxy),
260                             radiusX, radiusY, sigmaX, sigmaY, srcBounds, mode);
261
262        return dstRenderTargetContext;
263    }
264
265    SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY));
266
267    // GrTextureDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter.
268    GrTextureDomain::Mode modeForScaling =
269            GrTextureDomain::kRepeat_Mode == mode ? GrTextureDomain::kDecal_Mode : mode;
270    for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
271        SkIRect dstRect(srcRect);
272        shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY);
273
274        dstRenderTargetContext = context->makeDeferredRenderTargetContext(
275                                                                fit,
276                                                                SkTMin(dstRect.fRight, width),
277                                                                SkTMin(dstRect.fBottom, height),
278                                                                config, colorSpace);
279        if (!dstRenderTargetContext) {
280            return nullptr;
281        }
282
283        GrPaint paint;
284        paint.setGammaCorrect(dstRenderTargetContext->colorSpaceInfo().isGammaCorrect());
285
286        if (GrTextureDomain::kIgnore_Mode != mode && i == 1) {
287            SkRect domain = SkRect::Make(localSrcBounds);
288            domain.inset((i < scaleFactorX) ? SK_ScalarHalf : 0.0f,
289                         (i < scaleFactorY) ? SK_ScalarHalf : 0.0f);
290            auto fp = GrTextureDomainEffect::Make(std::move(srcProxy),
291                                                  SkMatrix::I(),
292                                                  domain,
293                                                  modeForScaling,
294                                                  GrSamplerState::Filter::kBilerp);
295            paint.addColorFragmentProcessor(std::move(fp));
296            srcRect.offset(-srcOffset);
297            srcOffset.set(0, 0);
298        } else {
299            paint.addColorTextureProcessor(std::move(srcProxy), SkMatrix::I(),
300                                           GrSamplerState::ClampBilerp());
301        }
302        paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
303
304        dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
305                                               SkRect::Make(dstRect), SkRect::Make(srcRect));
306
307        srcProxy = dstRenderTargetContext->asTextureProxyRef();
308        if (!srcProxy) {
309            return nullptr;
310        }
311        srcRect = dstRect;
312        localSrcBounds = srcRect;
313    }
314
315    srcRect = localDstBounds;
316    scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
317    if (sigmaX > 0.0f) {
318        if (scaleFactorX > 1) {
319            SkASSERT(dstRenderTargetContext);
320
321            // Clear out a radius to the right of the srcRect to prevent the
322            // X convolution from reading garbage.
323            clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop,
324                                          radiusX, srcRect.height());
325            dstRenderTargetContext->priv().absClear(&clearRect, 0x0);
326        }
327
328        SkASSERT(srcRect.width() <= width && srcRect.height() <= height);
329        dstRenderTargetContext = context->makeDeferredRenderTargetContext(fit, srcRect.width(),
330                                                                          srcRect.height(),
331                                                                          config, colorSpace);
332        if (!dstRenderTargetContext) {
333            return nullptr;
334        }
335
336        convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect, std::move(srcProxy),
337                          Direction::kX, radiusX, sigmaX, localSrcBounds, srcOffset, mode);
338
339        srcProxy = dstRenderTargetContext->asTextureProxyRef();
340        if (!srcProxy) {
341            return nullptr;
342        }
343        srcRect.offsetTo(0, 0);
344        localSrcBounds = srcRect;
345        if (GrTextureDomain::kClamp_Mode == mode) {
346            // We need to adjust bounds because we only fill part of the srcRect in x-pass.
347            localSrcBounds.inset(0, radiusY);
348        }
349        srcOffset.set(0, 0);
350    }
351
352    if (sigmaY > 0.0f) {
353        if (scaleFactorY > 1 || sigmaX > 0.0f) {
354            SkASSERT(dstRenderTargetContext);
355
356            // Clear out a radius below the srcRect to prevent the Y
357            // convolution from reading garbage.
358            clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom,
359                                          srcRect.width(), radiusY);
360            dstRenderTargetContext->priv().absClear(&clearRect, 0x0);
361        }
362
363        SkASSERT(srcRect.width() <= width && srcRect.height() <= height);
364        dstRenderTargetContext = context->makeDeferredRenderTargetContext(fit, srcRect.width(),
365                                                                          srcRect.height(),
366                                                                          config, colorSpace);
367        if (!dstRenderTargetContext) {
368            return nullptr;
369        }
370
371        convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect, std::move(srcProxy),
372                          Direction::kY, radiusY, sigmaY, localSrcBounds, srcOffset, mode);
373
374        srcProxy = dstRenderTargetContext->asTextureProxyRef();
375        if (!srcProxy) {
376            return nullptr;
377        }
378        srcRect.offsetTo(0, 0);
379    }
380
381    SkASSERT(dstRenderTargetContext);
382    SkASSERT(srcProxy.get() == dstRenderTargetContext->asTextureProxy());
383
384    if (scaleFactorX > 1 || scaleFactorY > 1) {
385        // Clear one pixel to the right and below, to accommodate bilinear upsampling.
386        // TODO: it seems like we should actually be clamping here rather than darkening
387        // the bottom right edges.
388        clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, srcRect.width() + 1, 1);
389        dstRenderTargetContext->priv().absClear(&clearRect, 0x0);
390        clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop, 1, srcRect.height());
391        dstRenderTargetContext->priv().absClear(&clearRect, 0x0);
392
393        SkIRect dstRect(srcRect);
394        scale_irect(&dstRect, scaleFactorX, scaleFactorY);
395
396        dstRenderTargetContext = context->makeDeferredRenderTargetContext(
397                                                                fit, SkTMin(dstRect.width(), width),
398                                                                SkTMin(dstRect.height(), height),
399                                                                config, colorSpace);
400        if (!dstRenderTargetContext) {
401            return nullptr;
402        }
403
404        GrPaint paint;
405        paint.setGammaCorrect(dstRenderTargetContext->colorSpaceInfo().isGammaCorrect());
406
407        if (GrTextureDomain::kIgnore_Mode != mode) {
408            SkRect domain = SkRect::Make(localSrcBounds);
409            auto fp = GrTextureDomainEffect::Make(std::move(srcProxy),
410                                                  SkMatrix::I(),
411                                                  domain,
412                                                  modeForScaling,
413                                                  GrSamplerState::Filter::kBilerp);
414            paint.addColorFragmentProcessor(std::move(fp));
415        } else {
416            // FIXME:  this should be mitchell, not bilinear.
417            paint.addColorTextureProcessor(std::move(srcProxy), SkMatrix::I(),
418                                           GrSamplerState::ClampBilerp());
419        }
420        paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
421
422        dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
423                                               SkRect::Make(dstRect), SkRect::Make(srcRect));
424
425        srcProxy = dstRenderTargetContext->asTextureProxyRef();
426        if (!srcProxy) {
427            return nullptr;
428        }
429        srcRect = dstRect;
430    }
431
432    return dstRenderTargetContext;
433}
434
435}
436
437#endif
438
439