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,bool useBounds,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                                  bool useBounds,
78                                  int bounds[2]) {
79     GrPaint paint;
80     paint.setGammaCorrect(renderTargetContext->isGammaCorrect());
81 
82     GrResourceProvider* resourceProvider = renderTargetContext->resourceProvider();
83 
84     sk_sp<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make(
85             resourceProvider, std::move(proxy), direction, radius, sigma, useBounds, 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 
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)94 static 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     SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
105                                                -SkIntToScalar(srcOffset.y()));
106     SkISize size = SkISize::Make(2 * radiusX + 1,  2 * radiusY + 1);
107     SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
108     GrPaint paint;
109     paint.setGammaCorrect(renderTargetContext->isGammaCorrect());
110     SkIRect bounds = srcBounds ? *srcBounds : SkIRect::EmptyIRect();
111 
112     GrResourceProvider* resourceProvider = renderTargetContext->resourceProvider();
113 
114     sk_sp<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::MakeGaussian(
115             resourceProvider, std::move(proxy), bounds, size, 1.0, 0.0, kernelOffset,
116             srcBounds ? GrTextureDomain::kDecal_Mode : GrTextureDomain::kIgnore_Mode,
117             true, sigmaX, sigmaY));
118     paint.addColorFragmentProcessor(std::move(conv));
119     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
120     renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
121                                                  SkRect::Make(dstRect), localMatrix);
122 }
123 
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)124 static void convolve_gaussian(GrRenderTargetContext* renderTargetContext,
125                               const GrClip& clip,
126                               const SkIRect& srcRect,
127                               sk_sp<GrTextureProxy> proxy,
128                               Gr1DKernelEffect::Direction direction,
129                               int radius,
130                               float sigma,
131                               const SkIRect* srcBounds,
132                               const SkIPoint& srcOffset) {
133     int bounds[2] = { 0, 0 };
134     SkIRect dstRect = SkIRect::MakeWH(srcRect.width(), srcRect.height());
135     if (!srcBounds) {
136         convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset,
137                              std::move(proxy), direction, radius, sigma, false, bounds);
138         return;
139     }
140     SkIRect midRect = *srcBounds, leftRect, rightRect;
141     midRect.offset(srcOffset);
142     SkIRect topRect, bottomRect;
143     if (direction == Gr1DKernelEffect::kX_Direction) {
144         bounds[0] = srcBounds->left();
145         bounds[1] = srcBounds->right();
146         topRect = SkIRect::MakeLTRB(0, 0, dstRect.right(), midRect.top());
147         bottomRect = SkIRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom());
148         midRect.inset(radius, 0);
149         leftRect = SkIRect::MakeLTRB(0, midRect.top(), midRect.left(), midRect.bottom());
150         rightRect =
151             SkIRect::MakeLTRB(midRect.right(), midRect.top(), dstRect.width(), midRect.bottom());
152         dstRect.fTop = midRect.top();
153         dstRect.fBottom = midRect.bottom();
154     } else {
155         bounds[0] = srcBounds->top();
156         bounds[1] = srcBounds->bottom();
157         topRect = SkIRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom());
158         bottomRect = SkIRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom());
159         midRect.inset(0, radius);
160         leftRect = SkIRect::MakeLTRB(midRect.left(), 0, midRect.right(), midRect.top());
161         rightRect =
162             SkIRect::MakeLTRB(midRect.left(), midRect.bottom(), midRect.right(), dstRect.height());
163         dstRect.fLeft = midRect.left();
164         dstRect.fRight = midRect.right();
165     }
166     if (!topRect.isEmpty()) {
167         renderTargetContext->clear(&topRect, 0, false);
168     }
169 
170     if (!bottomRect.isEmpty()) {
171         renderTargetContext->clear(&bottomRect, 0, false);
172     }
173     if (midRect.isEmpty()) {
174         // Blur radius covers srcBounds; use bounds over entire draw
175         convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset,
176                              std::move(proxy), direction, radius, sigma, true, bounds);
177     } else {
178         // Draw right and left margins with bounds; middle without.
179         convolve_gaussian_1d(renderTargetContext, clip, leftRect, srcOffset,
180                              proxy, direction, radius, sigma, true, bounds);
181         convolve_gaussian_1d(renderTargetContext, clip, rightRect, srcOffset,
182                              proxy, direction, radius, sigma, true, bounds);
183         convolve_gaussian_1d(renderTargetContext, clip, midRect, srcOffset,
184                              std::move(proxy), direction, radius, sigma, false, bounds);
185     }
186 }
187 
188 namespace SkGpuBlurUtils {
189 
GaussianBlur(GrContext * context,sk_sp<GrTextureProxy> srcProxy,sk_sp<SkColorSpace> colorSpace,const SkIRect & dstBounds,const SkIRect * srcBounds,float sigmaX,float sigmaY,SkBackingFit fit)190 sk_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                                           SkBackingFit fit) {
198     SkASSERT(context);
199 
200     {
201         // Chrome is crashing with proxies when they need to be instantiated.
202         // Force an instantiation here (where, in olden days, we used to require a GrTexture)
203         // to see if the input is already un-instantiable.
204         GrTexture* temp = srcProxy->instantiate(context->resourceProvider());
205         if (!temp) {
206             return nullptr;
207         }
208     }
209 
210     SkIRect clearRect;
211     int scaleFactorX, radiusX;
212     int scaleFactorY, radiusY;
213     int maxTextureSize = context->caps()->maxTextureSize();
214     sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
215     sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
216     SkASSERT(sigmaX || sigmaY);
217 
218     SkIPoint srcOffset = SkIPoint::Make(-dstBounds.x(), -dstBounds.y());
219     SkIRect localDstBounds = SkIRect::MakeWH(dstBounds.width(), dstBounds.height());
220     SkIRect localSrcBounds;
221     SkIRect srcRect;
222     if (srcBounds) {
223         srcRect = localSrcBounds = *srcBounds;
224         srcRect.offset(srcOffset);
225         srcBounds = &localSrcBounds;
226     } else {
227         srcRect = localDstBounds;
228     }
229 
230     scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
231     scale_irect(&srcRect, scaleFactorX, scaleFactorY);
232 
233     // setup new clip
234     GrFixedClip clip(localDstBounds);
235 
236     const GrPixelConfig config = srcProxy->config();
237 
238     SkASSERT(kBGRA_8888_GrPixelConfig  == config || kRGBA_8888_GrPixelConfig  == config ||
239              kSRGBA_8888_GrPixelConfig == config || kSBGRA_8888_GrPixelConfig == config ||
240              kRGBA_half_GrPixelConfig  == config || kAlpha_8_GrPixelConfig    == config);
241 
242     const int width = dstBounds.width();
243     const int height = dstBounds.height();
244 
245     sk_sp<GrRenderTargetContext> dstRenderTargetContext(context->makeDeferredRenderTargetContext(
246         fit, width, height, config, colorSpace, 0, kBottomLeft_GrSurfaceOrigin));
247     if (!dstRenderTargetContext) {
248         return nullptr;
249     }
250 
251     // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just
252     // launch a single non separable kernel vs two launches
253     if (sigmaX > 0.0f && sigmaY > 0.0f &&
254             (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
255         // We shouldn't be scaling because this is a small size blur
256         SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY));
257 
258         convolve_gaussian_2d(dstRenderTargetContext.get(), clip, localDstBounds, srcOffset,
259                              std::move(srcProxy), radiusX, radiusY, sigmaX, sigmaY, srcBounds);
260 
261         return dstRenderTargetContext;
262     }
263 
264     sk_sp<GrRenderTargetContext> tmpRenderTargetContext(context->makeDeferredRenderTargetContext(
265         fit, width, height, config, colorSpace, 0, kBottomLeft_GrSurfaceOrigin));
266     if (!tmpRenderTargetContext) {
267         return nullptr;
268     }
269 
270     sk_sp<GrRenderTargetContext> srcRenderTargetContext;
271 
272     SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY));
273 
274     for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
275         GrPaint paint;
276         paint.setGammaCorrect(dstRenderTargetContext->isGammaCorrect());
277         SkIRect dstRect(srcRect);
278         if (srcBounds && i == 1) {
279             SkRect domain = SkRect::Make(*srcBounds);
280             domain.inset((i < scaleFactorX) ? SK_ScalarHalf : 0.0f,
281                          (i < scaleFactorY) ? SK_ScalarHalf : 0.0f);
282             sk_sp<GrFragmentProcessor> fp(GrTextureDomainEffect::Make(
283                                                         context->resourceProvider(),
284                                                         std::move(srcProxy),
285                                                         nullptr,
286                                                         SkMatrix::I(),
287                                                         domain,
288                                                         GrTextureDomain::kDecal_Mode,
289                                                         GrSamplerParams::kBilerp_FilterMode));
290             paint.addColorFragmentProcessor(std::move(fp));
291             srcRect.offset(-srcOffset);
292             srcOffset.set(0, 0);
293         } else {
294             GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
295             paint.addColorTextureProcessor(context->resourceProvider(), std::move(srcProxy),
296                                            nullptr, SkMatrix::I(), params);
297         }
298         paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
299         shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY);
300 
301         dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
302                                                SkRect::Make(dstRect), SkRect::Make(srcRect));
303 
304         srcRenderTargetContext = dstRenderTargetContext;
305         srcRect = dstRect;
306         srcProxy = srcRenderTargetContext->asTextureProxyRef();
307         if (!srcProxy) {
308             return nullptr;
309         }
310         dstRenderTargetContext.swap(tmpRenderTargetContext);
311         localSrcBounds = srcRect;
312     }
313 
314     srcRect = localDstBounds;
315     scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
316     if (sigmaX > 0.0f) {
317         if (scaleFactorX > 1) {
318             SkASSERT(srcRenderTargetContext);
319 
320             // Clear out a radius to the right of the srcRect to prevent the
321             // X convolution from reading garbage.
322             clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop,
323                                           radiusX, srcRect.height());
324             srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
325         }
326 
327         convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect,
328                           std::move(srcProxy), Gr1DKernelEffect::kX_Direction, radiusX, sigmaX,
329                           srcBounds, srcOffset);
330         srcRenderTargetContext = dstRenderTargetContext;
331         srcProxy = srcRenderTargetContext->asTextureProxyRef();
332         if (!srcProxy) {
333             return nullptr;
334         }
335         srcRect.offsetTo(0, 0);
336         dstRenderTargetContext.swap(tmpRenderTargetContext);
337         localSrcBounds = srcRect;
338         srcOffset.set(0, 0);
339     }
340 
341     if (sigmaY > 0.0f) {
342         if (scaleFactorY > 1 || sigmaX > 0.0f) {
343             SkASSERT(srcRenderTargetContext);
344 
345             // Clear out a radius below the srcRect to prevent the Y
346             // convolution from reading garbage.
347             clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom,
348                                           srcRect.width(), radiusY);
349             srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
350         }
351 
352         convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect,
353                           std::move(srcProxy), Gr1DKernelEffect::kY_Direction, radiusY, sigmaY,
354                           srcBounds, srcOffset);
355 
356         srcRenderTargetContext = dstRenderTargetContext;
357         srcRect.offsetTo(0, 0);
358         dstRenderTargetContext.swap(tmpRenderTargetContext);
359     }
360 
361     SkASSERT(srcRenderTargetContext);
362     srcProxy.reset(nullptr);  // we don't use this from here on out
363 
364     if (scaleFactorX > 1 || scaleFactorY > 1) {
365         // Clear one pixel to the right and below, to accommodate bilinear upsampling.
366         // TODO: it seems like we should actually be clamping here rather than darkening
367         // the bottom right edges.
368         clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, srcRect.width() + 1, 1);
369         srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
370         clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop, 1, srcRect.height());
371         srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
372 
373         GrPaint paint;
374         paint.setGammaCorrect(dstRenderTargetContext->isGammaCorrect());
375         // FIXME:  this should be mitchell, not bilinear.
376         GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
377         sk_sp<GrTextureProxy> proxy(srcRenderTargetContext->asTextureProxyRef());
378         if (!proxy) {
379             return nullptr;
380         }
381 
382         paint.addColorTextureProcessor(context->resourceProvider(), std::move(proxy),
383                                        nullptr, SkMatrix::I(), params);
384         paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
385 
386         SkIRect dstRect(srcRect);
387         scale_irect(&dstRect, scaleFactorX, scaleFactorY);
388 
389         dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
390                                                SkRect::Make(dstRect), SkRect::Make(srcRect));
391 
392         srcRenderTargetContext = dstRenderTargetContext;
393         srcRect = dstRect;
394         dstRenderTargetContext.swap(tmpRenderTargetContext);
395     }
396 
397     return srcRenderTargetContext;
398 }
399 
400 }
401 
402 #endif
403 
404