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