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 #include "SkGr.h"
22 
23 #define MAX_BLUR_SIGMA 4.0f
24 
25 using Direction = GrGaussianConvolutionFragmentProcessor::Direction;
26 
scale_irect_roundout(SkIRect * rect,float xScale,float yScale)27 static void scale_irect_roundout(SkIRect* rect, float xScale, float yScale) {
28     rect->fLeft   = SkScalarFloorToInt(rect->fLeft  * xScale);
29     rect->fTop    = SkScalarFloorToInt(rect->fTop   * yScale);
30     rect->fRight  = SkScalarCeilToInt(rect->fRight  * xScale);
31     rect->fBottom = SkScalarCeilToInt(rect->fBottom * yScale);
32 }
33 
scale_irect(SkIRect * rect,int xScale,int yScale)34 static void scale_irect(SkIRect* rect, int xScale, int yScale) {
35     rect->fLeft   *= xScale;
36     rect->fTop    *= yScale;
37     rect->fRight  *= xScale;
38     rect->fBottom *= yScale;
39 }
40 
41 #ifdef SK_DEBUG
is_even(int x)42 static inline int is_even(int x) { return !(x & 1); }
43 #endif
44 
shrink_irect_by_2(SkIRect * rect,bool xAxis,bool yAxis)45 static void shrink_irect_by_2(SkIRect* rect, bool xAxis, bool yAxis) {
46     if (xAxis) {
47         SkASSERT(is_even(rect->fLeft) && is_even(rect->fRight));
48         rect->fLeft /= 2;
49         rect->fRight /= 2;
50     }
51     if (yAxis) {
52         SkASSERT(is_even(rect->fTop) && is_even(rect->fBottom));
53         rect->fTop /= 2;
54         rect->fBottom /= 2;
55     }
56 }
57 
adjust_sigma(float sigma,int maxTextureSize,int * scaleFactor,int * radius)58 static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) {
59     *scaleFactor = 1;
60     while (sigma > MAX_BLUR_SIGMA) {
61         *scaleFactor *= 2;
62         sigma *= 0.5f;
63         if (*scaleFactor > maxTextureSize) {
64             *scaleFactor = maxTextureSize;
65             sigma = MAX_BLUR_SIGMA;
66         }
67     }
68     *radius = static_cast<int>(ceilf(sigma * 3.0f));
69     SkASSERT(*radius <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius);
70     return sigma;
71 }
72 
convolve_gaussian_1d(GrRenderTargetContext * renderTargetContext,const GrClip & clip,const SkIRect & dstRect,const SkIPoint & srcOffset,sk_sp<GrTextureProxy> proxy,Direction direction,int radius,float sigma,GrTextureDomain::Mode mode,int bounds[2])73 static void convolve_gaussian_1d(GrRenderTargetContext* renderTargetContext,
74                                  const GrClip& clip,
75                                  const SkIRect& dstRect,
76                                  const SkIPoint& srcOffset,
77                                  sk_sp<GrTextureProxy> proxy,
78                                  Direction direction,
79                                  int radius,
80                                  float sigma,
81                                  GrTextureDomain::Mode mode,
82                                  int bounds[2]) {
83     GrPaint paint;
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 
get_blur_config(GrTextureProxy * proxy)94 static GrPixelConfig get_blur_config(GrTextureProxy* proxy) {
95     GrPixelConfig config = proxy->config();
96 
97     SkASSERT(kBGRA_8888_GrPixelConfig == config || kRGBA_8888_GrPixelConfig == config ||
98              kRGB_888_GrPixelConfig == config || kRGBA_4444_GrPixelConfig == config ||
99              kRGB_565_GrPixelConfig == config || kSRGBA_8888_GrPixelConfig == config ||
100              kSBGRA_8888_GrPixelConfig == config || kRGBA_half_GrPixelConfig == config ||
101              kAlpha_8_GrPixelConfig == config || kRGBA_1010102_GrPixelConfig == config);
102 
103     return config;
104 }
105 
convolve_gaussian_2d(GrContext * context,sk_sp<GrTextureProxy> proxy,const SkIRect & srcBounds,const SkIPoint & srcOffset,int radiusX,int radiusY,SkScalar sigmaX,SkScalar sigmaY,GrTextureDomain::Mode mode,const SkImageInfo & dstII,SkBackingFit dstFit)106 static sk_sp<GrRenderTargetContext> convolve_gaussian_2d(GrContext* context,
107                                                          sk_sp<GrTextureProxy> proxy,
108                                                          const SkIRect& srcBounds,
109                                                          const SkIPoint& srcOffset,
110                                                          int radiusX,
111                                                          int radiusY,
112                                                          SkScalar sigmaX,
113                                                          SkScalar sigmaY,
114                                                          GrTextureDomain::Mode mode,
115                                                          const SkImageInfo& dstII,
116                                                          SkBackingFit dstFit) {
117 
118     GrPixelConfig config = get_blur_config(proxy.get());
119 
120     GrBackendFormat format = proxy->backendFormat().makeTexture2D();
121     if (!format.isValid()) {
122         return nullptr;
123     }
124 
125     sk_sp<GrRenderTargetContext> renderTargetContext;
126     renderTargetContext = context->contextPriv().makeDeferredRenderTargetContext(
127                                                          format,
128                                                          dstFit, dstII.width(), dstII.height(),
129                                                          config, dstII.refColorSpace(),
130                                                          1, GrMipMapped::kNo,
131                                                          proxy->origin());
132     if (!renderTargetContext) {
133         return nullptr;
134     }
135 
136     SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
137                                                -SkIntToScalar(srcOffset.y()));
138     SkISize size = SkISize::Make(2 * radiusX + 1,  2 * radiusY + 1);
139     SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
140     GrPaint paint;
141     auto conv = GrMatrixConvolutionEffect::MakeGaussian(std::move(proxy), srcBounds, size, 1.0, 0.0,
142                                                         kernelOffset, mode, true, sigmaX, sigmaY);
143     paint.addColorFragmentProcessor(std::move(conv));
144     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
145     GrFixedClip clip(dstII.bounds());
146 
147     renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
148                                                  SkRect::Make(dstII.bounds()), localMatrix);
149 
150     return renderTargetContext;
151 }
152 
convolve_gaussian(GrContext * context,sk_sp<GrTextureProxy> proxy,const SkIRect & srcRect,const SkIPoint & srcOffset,Direction direction,int radius,float sigma,SkIRect * contentRect,GrTextureDomain::Mode mode,const SkImageInfo & dstII,SkBackingFit fit)153 static sk_sp<GrRenderTargetContext> convolve_gaussian(GrContext* context,
154                                                       sk_sp<GrTextureProxy> proxy,
155                                                       const SkIRect& srcRect,
156                                                       const SkIPoint& srcOffset,
157                                                       Direction direction,
158                                                       int radius,
159                                                       float sigma,
160                                                       SkIRect* contentRect,
161                                                       GrTextureDomain::Mode mode,
162                                                       const SkImageInfo& dstII,
163                                                       SkBackingFit fit) {
164     SkASSERT(srcRect.width() <= dstII.width() && srcRect.height() <= dstII.height());
165 
166     GrPixelConfig config = get_blur_config(proxy.get());
167 
168     GrBackendFormat format = proxy->backendFormat().makeTexture2D();
169     if (!format.isValid()) {
170         return nullptr;
171     }
172 
173     sk_sp<GrRenderTargetContext> dstRenderTargetContext;
174     dstRenderTargetContext = context->contextPriv().makeDeferredRenderTargetContext(
175                                                                 format,
176                                                                 fit, srcRect.width(),
177                                                                 srcRect.height(),
178                                                                 config,
179                                                                 dstII.refColorSpace(),
180                                                                 1, GrMipMapped::kNo,
181                                                                 proxy->origin());
182     if (!dstRenderTargetContext) {
183         return nullptr;
184     }
185 
186     GrFixedClip clip(dstII.bounds());
187 
188     int bounds[2] = { 0, 0 };
189     SkIRect dstRect = SkIRect::MakeWH(srcRect.width(), srcRect.height());
190     if (GrTextureDomain::kIgnore_Mode == mode) {
191         *contentRect = dstRect;
192         convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, srcOffset,
193                              std::move(proxy), direction, radius, sigma,
194                              GrTextureDomain::kIgnore_Mode, bounds);
195         return dstRenderTargetContext;
196     }
197 
198     SkIRect midRect = *contentRect, leftRect, rightRect;
199     midRect.offset(srcOffset);
200     SkIRect topRect, bottomRect;
201     if (Direction::kX == direction) {
202         bounds[0] = contentRect->left();
203         bounds[1] = contentRect->right();
204         topRect = SkIRect::MakeLTRB(0, 0, dstRect.right(), midRect.top());
205         bottomRect = SkIRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom());
206         midRect.inset(radius, 0);
207         leftRect = SkIRect::MakeLTRB(0, midRect.top(), midRect.left(), midRect.bottom());
208         rightRect =
209             SkIRect::MakeLTRB(midRect.right(), midRect.top(), dstRect.width(), midRect.bottom());
210         dstRect.fTop = midRect.top();
211         dstRect.fBottom = midRect.bottom();
212 
213         contentRect->fLeft = dstRect.fLeft;
214         contentRect->fTop = midRect.fTop;
215         contentRect->fRight = dstRect.fRight;
216         contentRect->fBottom = midRect.fBottom;
217     } else {
218         bounds[0] = contentRect->top();
219         bounds[1] = contentRect->bottom();
220         topRect = SkIRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom());
221         bottomRect = SkIRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom());
222         midRect.inset(0, radius);
223         leftRect = SkIRect::MakeLTRB(midRect.left(), 0, midRect.right(), midRect.top());
224         rightRect =
225             SkIRect::MakeLTRB(midRect.left(), midRect.bottom(), midRect.right(), dstRect.height());
226         dstRect.fLeft = midRect.left();
227         dstRect.fRight = midRect.right();
228 
229         contentRect->fLeft = midRect.fLeft;
230         contentRect->fTop = dstRect.fTop;
231         contentRect->fRight = midRect.fRight;
232         contentRect->fBottom = dstRect.fBottom;
233     }
234     if (!topRect.isEmpty()) {
235         dstRenderTargetContext->clear(&topRect, SK_PMColor4fTRANSPARENT,
236                                       GrRenderTargetContext::CanClearFullscreen::kNo);
237     }
238 
239     if (!bottomRect.isEmpty()) {
240         dstRenderTargetContext->clear(&bottomRect, SK_PMColor4fTRANSPARENT,
241                                       GrRenderTargetContext::CanClearFullscreen::kNo);
242     }
243 
244     if (midRect.isEmpty()) {
245         // Blur radius covers srcBounds; use bounds over entire draw
246         convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, srcOffset,
247                              std::move(proxy), direction, radius, sigma, mode, bounds);
248     } else {
249         // Draw right and left margins with bounds; middle without.
250         convolve_gaussian_1d(dstRenderTargetContext.get(), clip, leftRect, srcOffset,
251                              proxy, direction, radius, sigma, mode, bounds);
252         convolve_gaussian_1d(dstRenderTargetContext.get(), clip, rightRect, srcOffset,
253                              proxy, direction, radius, sigma, mode, bounds);
254         convolve_gaussian_1d(dstRenderTargetContext.get(), clip, midRect, srcOffset,
255                              std::move(proxy), direction, radius, sigma,
256                              GrTextureDomain::kIgnore_Mode, bounds);
257     }
258 
259     return dstRenderTargetContext;
260 }
261 
decimate(GrContext * context,sk_sp<GrTextureProxy> src,SkIPoint * srcOffset,SkIRect * contentRect,int scaleFactorX,int scaleFactorY,bool willBeXFiltering,bool willBeYFiltering,int radiusX,int radiusY,GrTextureDomain::Mode mode,const SkImageInfo & dstII)262 static sk_sp<GrTextureProxy> decimate(GrContext* context,
263                                       sk_sp<GrTextureProxy> src,
264                                       SkIPoint* srcOffset,
265                                       SkIRect* contentRect,
266                                       int scaleFactorX, int scaleFactorY,
267                                       bool willBeXFiltering, bool willBeYFiltering,
268                                       int radiusX, int radiusY,
269                                       GrTextureDomain::Mode mode,
270                                       const SkImageInfo& dstII) {
271     SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY));
272     SkASSERT(scaleFactorX > 1 || scaleFactorY > 1);
273 
274     GrPixelConfig config = get_blur_config(src.get());
275 
276     SkIRect srcRect;
277     if (GrTextureDomain::kIgnore_Mode == mode) {
278         srcRect = dstII.bounds();
279     } else {
280         srcRect = *contentRect;
281         srcRect.offset(*srcOffset);
282     }
283 
284     scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
285     scale_irect(&srcRect, scaleFactorX, scaleFactorY);
286 
287     SkIRect dstRect(srcRect);
288 
289     sk_sp<GrRenderTargetContext> dstRenderTargetContext;
290 
291     GrBackendFormat format = src->backendFormat().makeTexture2D();
292     if (!format.isValid()) {
293         return nullptr;
294     }
295 
296     for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
297         shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY);
298 
299         // We know this will not be the final draw so we are free to make it an approx match.
300         dstRenderTargetContext = context->contextPriv().makeDeferredRenderTargetContext(
301                                                     format,
302                                                     SkBackingFit::kApprox,
303                                                     dstRect.fRight,
304                                                     dstRect.fBottom,
305                                                     config, dstII.refColorSpace(),
306                                                     1, GrMipMapped::kNo,
307                                                     src->origin());
308         if (!dstRenderTargetContext) {
309             return nullptr;
310         }
311 
312         GrPaint paint;
313         if (GrTextureDomain::kIgnore_Mode != mode && i == 1) {
314             // GrTextureDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter.
315             GrTextureDomain::Mode modeForScaling = GrTextureDomain::kRepeat_Mode == mode
316                                                                 ? GrTextureDomain::kDecal_Mode
317                                                                 : mode;
318 
319             SkRect domain = SkRect::Make(*contentRect);
320             domain.inset((i < scaleFactorX) ? SK_ScalarHalf : 0.0f,
321                          (i < scaleFactorY) ? SK_ScalarHalf : 0.0f);
322             auto fp = GrTextureDomainEffect::Make(std::move(src),
323                                                   SkMatrix::I(),
324                                                   domain,
325                                                   modeForScaling,
326                                                   GrSamplerState::Filter::kBilerp);
327             paint.addColorFragmentProcessor(std::move(fp));
328             srcRect.offset(-(*srcOffset));
329             // TODO: consume the srcOffset in both first draws and always set it to zero
330             // back in GaussianBlur
331             srcOffset->set(0, 0);
332         } else {
333             paint.addColorTextureProcessor(std::move(src), SkMatrix::I(),
334                                            GrSamplerState::ClampBilerp());
335         }
336         paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
337 
338         GrFixedClip clip(dstRect);
339         dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo,
340                                                SkMatrix::I(), SkRect::Make(dstRect),
341                                                SkRect::Make(srcRect));
342 
343         src = dstRenderTargetContext->asTextureProxyRef();
344         if (!src) {
345             return nullptr;
346         }
347         srcRect = dstRect;
348     }
349 
350     *contentRect = dstRect;
351 
352     SkASSERT(dstRenderTargetContext);
353 
354     if (willBeXFiltering) {
355         if (scaleFactorX > 1) {
356             // Clear out a radius to the right of the contentRect to prevent the
357             // X convolution from reading garbage.
358             SkIRect clearRect = SkIRect::MakeXYWH(contentRect->fRight, contentRect->fTop,
359                                                   radiusX, contentRect->height());
360             dstRenderTargetContext->priv().absClear(&clearRect, SK_PMColor4fTRANSPARENT);
361         }
362     } else {
363         if (scaleFactorY > 1) {
364             // Clear out a radius below the contentRect to prevent the Y
365             // convolution from reading garbage.
366             SkIRect clearRect = SkIRect::MakeXYWH(contentRect->fLeft, contentRect->fBottom,
367                                                   contentRect->width(), radiusY);
368             dstRenderTargetContext->priv().absClear(&clearRect, SK_PMColor4fTRANSPARENT);
369         }
370     }
371 
372     return dstRenderTargetContext->asTextureProxyRef();
373 }
374 
375 // Expand the contents of 'srcRenderTargetContext' to fit in 'dstII'.
reexpand(GrContext * context,sk_sp<GrRenderTargetContext> srcRenderTargetContext,const SkIRect & localSrcBounds,int scaleFactorX,int scaleFactorY,GrTextureDomain::Mode mode,const SkImageInfo & dstII,SkBackingFit fit)376 static sk_sp<GrRenderTargetContext> reexpand(GrContext* context,
377                                              sk_sp<GrRenderTargetContext> srcRenderTargetContext,
378                                              const SkIRect& localSrcBounds,
379                                              int scaleFactorX, int scaleFactorY,
380                                              GrTextureDomain::Mode mode,
381                                              const SkImageInfo& dstII,
382                                              SkBackingFit fit) {
383     const SkIRect srcRect = SkIRect::MakeWH(srcRenderTargetContext->width(),
384                                             srcRenderTargetContext->height());
385 
386     // Clear one pixel to the right and below, to accommodate bilinear upsampling.
387     // TODO: it seems like we should actually be clamping here rather than darkening
388     // the bottom right edges.
389     SkIRect clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, srcRect.width() + 1, 1);
390     srcRenderTargetContext->priv().absClear(&clearRect, SK_PMColor4fTRANSPARENT);
391     clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop, 1, srcRect.height());
392     srcRenderTargetContext->priv().absClear(&clearRect, SK_PMColor4fTRANSPARENT);
393 
394     sk_sp<GrTextureProxy> srcProxy = srcRenderTargetContext->asTextureProxyRef();
395     if (!srcProxy) {
396         return nullptr;
397     }
398 
399     srcRenderTargetContext = nullptr; // no longer needed
400 
401     GrPixelConfig config = get_blur_config(srcProxy.get());
402 
403     GrBackendFormat format = srcProxy->backendFormat().makeTexture2D();
404     if (!format.isValid()) {
405         return nullptr;
406     }
407 
408     sk_sp<GrRenderTargetContext> dstRenderTargetContext =
409         context->contextPriv().makeDeferredRenderTargetContext(format,
410                                                                fit, dstII.width(), dstII.height(),
411                                                                config, dstII.refColorSpace(),
412                                                                1, GrMipMapped::kNo,
413                                                                srcProxy->origin());
414     if (!dstRenderTargetContext) {
415         return nullptr;
416     }
417 
418     GrPaint paint;
419     if (GrTextureDomain::kIgnore_Mode != mode) {
420         // GrTextureDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter.
421         GrTextureDomain::Mode modeForScaling = GrTextureDomain::kRepeat_Mode == mode
422                                                             ? GrTextureDomain::kDecal_Mode
423                                                             : mode;
424 
425         SkRect domain = SkRect::Make(localSrcBounds);
426         auto fp = GrTextureDomainEffect::Make(std::move(srcProxy),
427                                                 SkMatrix::I(),
428                                                 domain,
429                                                 modeForScaling,
430                                                 GrSamplerState::Filter::kBilerp);
431         paint.addColorFragmentProcessor(std::move(fp));
432     } else {
433         // FIXME:  this should be mitchell, not bilinear.
434         paint.addColorTextureProcessor(std::move(srcProxy), SkMatrix::I(),
435                                        GrSamplerState::ClampBilerp());
436     }
437     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
438     GrFixedClip clip(dstII.bounds());
439 
440     // TODO: using dstII as dstRect results in some image diffs - why?
441     SkIRect dstRect(srcRect);
442     scale_irect(&dstRect, scaleFactorX, scaleFactorY);
443 
444     dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
445                                            SkRect::Make(dstRect), SkRect::Make(srcRect));
446 
447     return dstRenderTargetContext;
448 }
449 
450 namespace SkGpuBlurUtils {
451 
GaussianBlur(GrContext * context,sk_sp<GrTextureProxy> srcProxy,sk_sp<SkColorSpace> colorSpace,const SkIRect & dstBounds,const SkIRect & srcBounds,float sigmaX,float sigmaY,GrTextureDomain::Mode mode,SkAlphaType at,SkBackingFit fit)452 sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context,
453                                           sk_sp<GrTextureProxy> srcProxy,
454                                           sk_sp<SkColorSpace> colorSpace,
455                                           const SkIRect& dstBounds,
456                                           const SkIRect& srcBounds,
457                                           float sigmaX,
458                                           float sigmaY,
459                                           GrTextureDomain::Mode mode,
460                                           SkAlphaType at,
461                                           SkBackingFit fit) {
462     SkASSERT(context);
463 
464     const GrPixelConfig config = get_blur_config(srcProxy.get());
465     SkColorType ct;
466     if (!GrPixelConfigToColorType(config, &ct)) {
467         return nullptr;
468     }
469 
470     const SkImageInfo finalDestII = SkImageInfo::Make(dstBounds.width(), dstBounds.height(),
471                                                       ct, at, std::move(colorSpace));
472 
473     int scaleFactorX, radiusX;
474     int scaleFactorY, radiusY;
475     int maxTextureSize = context->contextPriv().caps()->maxTextureSize();
476     sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
477     sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
478     SkASSERT(sigmaX || sigmaY);
479 
480     SkIPoint srcOffset = SkIPoint::Make(-dstBounds.x(), -dstBounds.y());
481 
482     // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just
483     // launch a single non separable kernel vs two launches
484     if (sigmaX > 0.0f && sigmaY > 0.0f &&
485             (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
486         // We shouldn't be scaling because this is a small size blur
487         SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY));
488 
489         return convolve_gaussian_2d(context, std::move(srcProxy), srcBounds, srcOffset,
490                                     radiusX, radiusY, sigmaX, sigmaY,
491                                     mode, finalDestII, fit);
492     }
493 
494     // Only the last rendered renderTargetContext needs to match the supplied 'fit'
495     SkBackingFit xFit = fit, yFit = fit;
496     if (scaleFactorX > 1 || scaleFactorY > 1) {
497         xFit = yFit = SkBackingFit::kApprox;  // reexpand will be last
498     } else if (sigmaY > 0.0f) {
499         xFit = SkBackingFit::kApprox;         // the y-pass will be last
500     }
501 
502     SkIRect localSrcBounds = srcBounds;
503 
504     if (scaleFactorX > 1 || scaleFactorY > 1) {
505         srcProxy = decimate(context, std::move(srcProxy), &srcOffset, &localSrcBounds,
506                             scaleFactorX, scaleFactorY, sigmaX > 0.0f, sigmaY > 0.0f,
507                             radiusX, radiusY, mode, finalDestII);
508         if (!srcProxy) {
509             return nullptr;
510         }
511     }
512 
513     sk_sp<GrRenderTargetContext> dstRenderTargetContext;
514 
515     SkIRect srcRect = finalDestII.bounds();
516     scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
517     if (sigmaX > 0.0f) {
518         dstRenderTargetContext = convolve_gaussian(context, std::move(srcProxy), srcRect, srcOffset,
519                                                    Direction::kX, radiusX, sigmaX, &localSrcBounds,
520                                                    mode, finalDestII, xFit);
521         if (!dstRenderTargetContext) {
522             return nullptr;
523         }
524 
525         if (sigmaY > 0.0f) {
526             // Clear out a radius below the srcRect to prevent the Y
527             // convolution from reading garbage.
528             SkIRect clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom,
529                                                   srcRect.width(), radiusY);
530             dstRenderTargetContext->priv().absClear(&clearRect, SK_PMColor4fTRANSPARENT);
531         }
532 
533         srcProxy = dstRenderTargetContext->asTextureProxyRef();
534         if (!srcProxy) {
535             return nullptr;
536         }
537 
538         srcRect.offsetTo(0, 0);
539         srcOffset.set(0, 0);
540     }
541 
542     if (sigmaY > 0.0f) {
543         dstRenderTargetContext = convolve_gaussian(context, std::move(srcProxy), srcRect, srcOffset,
544                                                    Direction::kY, radiusY, sigmaY, &localSrcBounds,
545                                                    mode, finalDestII, yFit);
546         if (!dstRenderTargetContext) {
547             return nullptr;
548         }
549 
550         srcProxy = dstRenderTargetContext->asTextureProxyRef();
551         if (!srcProxy) {
552             return nullptr;
553         }
554 
555         srcRect.offsetTo(0, 0);
556         srcOffset.set(0, 0);
557     }
558 
559     SkASSERT(dstRenderTargetContext);
560     SkASSERT(srcProxy.get() == dstRenderTargetContext->asTextureProxy());
561 
562     if (scaleFactorX > 1 || scaleFactorY > 1) {
563         dstRenderTargetContext = reexpand(context, std::move(dstRenderTargetContext),
564                                           localSrcBounds, scaleFactorX, scaleFactorY,
565                                           mode, finalDestII, fit);
566     }
567 
568     SkASSERT(!dstRenderTargetContext || dstRenderTargetContext->origin() == srcProxy->origin());
569     return dstRenderTargetContext;
570 }
571 
572 }
573 
574 #endif
575