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 "src/core/SkGpuBlurUtils.h"
9 
10 #include "include/core/SkRect.h"
11 
12 #if SK_SUPPORT_GPU
13 #include "include/private/GrRecordingContext.h"
14 #include "src/gpu/GrCaps.h"
15 #include "src/gpu/GrFixedClip.h"
16 #include "src/gpu/GrRecordingContextPriv.h"
17 #include "src/gpu/GrRenderTargetContext.h"
18 #include "src/gpu/GrRenderTargetContextPriv.h"
19 #include "src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h"
20 #include "src/gpu/effects/GrMatrixConvolutionEffect.h"
21 
22 #include "src/gpu/SkGr.h"
23 
24 #define MAX_BLUR_SIGMA 4.0f
25 
26 using Direction = GrGaussianConvolutionFragmentProcessor::Direction;
27 
scale_irect_roundout(SkIRect * rect,float xScale,float yScale)28 static void scale_irect_roundout(SkIRect* rect, float xScale, float yScale) {
29     rect->fLeft   = SkScalarFloorToInt(rect->fLeft  * xScale);
30     rect->fTop    = SkScalarFloorToInt(rect->fTop   * yScale);
31     rect->fRight  = SkScalarCeilToInt(rect->fRight  * xScale);
32     rect->fBottom = SkScalarCeilToInt(rect->fBottom * yScale);
33 }
34 
scale_irect(SkIRect * rect,int xScale,int yScale)35 static void scale_irect(SkIRect* rect, int xScale, int yScale) {
36     rect->fLeft   *= xScale;
37     rect->fTop    *= yScale;
38     rect->fRight  *= xScale;
39     rect->fBottom *= yScale;
40 }
41 
42 #ifdef SK_DEBUG
is_even(int x)43 static inline int is_even(int x) { return !(x & 1); }
44 #endif
45 
shrink_irect_by_2(SkIRect * rect,bool xAxis,bool yAxis)46 static void shrink_irect_by_2(SkIRect* rect, bool xAxis, bool yAxis) {
47     if (xAxis) {
48         SkASSERT(is_even(rect->fLeft) && is_even(rect->fRight));
49         rect->fLeft /= 2;
50         rect->fRight /= 2;
51     }
52     if (yAxis) {
53         SkASSERT(is_even(rect->fTop) && is_even(rect->fBottom));
54         rect->fTop /= 2;
55         rect->fBottom /= 2;
56     }
57 }
58 
adjust_sigma(float sigma,int maxTextureSize,int * scaleFactor,int * radius)59 static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) {
60     *scaleFactor = 1;
61     while (sigma > MAX_BLUR_SIGMA) {
62         *scaleFactor *= 2;
63         sigma *= 0.5f;
64         if (*scaleFactor > maxTextureSize) {
65             *scaleFactor = maxTextureSize;
66             sigma = MAX_BLUR_SIGMA;
67         }
68     }
69     *radius = static_cast<int>(ceilf(sigma * 3.0f));
70     SkASSERT(*radius <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius);
71     return sigma;
72 }
73 
convolve_gaussian_1d(GrRenderTargetContext * renderTargetContext,const GrClip & clip,const SkIRect & dstRect,const SkIPoint & srcOffset,sk_sp<GrTextureProxy> proxy,GrColorType srcColorType,Direction direction,int radius,float sigma,GrTextureDomain::Mode mode,int bounds[2])74 static void convolve_gaussian_1d(GrRenderTargetContext* renderTargetContext,
75                                  const GrClip& clip,
76                                  const SkIRect& dstRect,
77                                  const SkIPoint& srcOffset,
78                                  sk_sp<GrTextureProxy> proxy,
79                                  GrColorType srcColorType,
80                                  Direction direction,
81                                  int radius,
82                                  float sigma,
83                                  GrTextureDomain::Mode mode,
84                                  int bounds[2]) {
85     GrPaint paint;
86     std::unique_ptr<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make(
87             std::move(proxy), srcColorType, direction, radius, sigma, mode, bounds));
88     paint.addColorFragmentProcessor(std::move(conv));
89     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
90     SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
91                                                -SkIntToScalar(srcOffset.y()));
92     renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
93                                                  SkRect::Make(dstRect), localMatrix);
94 }
95 
convolve_gaussian_2d(GrRecordingContext * context,sk_sp<GrTextureProxy> srcProxy,GrColorType srcColorType,const SkIRect & srcBounds,const SkIPoint & srcOffset,int radiusX,int radiusY,SkScalar sigmaX,SkScalar sigmaY,GrTextureDomain::Mode mode,int finalW,int finalH,sk_sp<SkColorSpace> finalCS,SkBackingFit dstFit)96 static std::unique_ptr<GrRenderTargetContext> convolve_gaussian_2d(GrRecordingContext* context,
97                                                                    sk_sp<GrTextureProxy> srcProxy,
98                                                                    GrColorType srcColorType,
99                                                                    const SkIRect& srcBounds,
100                                                                    const SkIPoint& srcOffset,
101                                                                    int radiusX,
102                                                                    int radiusY,
103                                                                    SkScalar sigmaX,
104                                                                    SkScalar sigmaY,
105                                                                    GrTextureDomain::Mode mode,
106                                                                    int finalW,
107                                                                    int finalH,
108                                                                    sk_sp<SkColorSpace> finalCS,
109                                                                    SkBackingFit dstFit) {
110 
111     auto renderTargetContext = context->priv().makeDeferredRenderTargetContext(
112             dstFit,
113             finalW,
114             finalH,
115             srcColorType,
116             std::move(finalCS),
117             1,
118             GrMipMapped::kNo,
119             srcProxy->origin(),
120             nullptr,
121             SkBudgeted::kYes,
122             srcProxy->isProtected() ? GrProtected::kYes : GrProtected::kNo);
123     if (!renderTargetContext) {
124         return nullptr;
125     }
126 
127     SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
128                                                -SkIntToScalar(srcOffset.y()));
129     SkISize size = SkISize::Make(2 * radiusX + 1,  2 * radiusY + 1);
130     SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
131     GrPaint paint;
132     auto conv = GrMatrixConvolutionEffect::MakeGaussian(std::move(srcProxy), srcBounds, size,
133                                                         1.0, 0.0, kernelOffset, mode, true,
134                                                         sigmaX, sigmaY);
135     paint.addColorFragmentProcessor(std::move(conv));
136     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
137     GrFixedClip clip(SkIRect::MakeWH(finalW, finalH));
138 
139     renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
140                                                  SkRect::MakeWH(finalW, finalH), localMatrix);
141 
142     return renderTargetContext;
143 }
144 
145 // NOTE: Both convolve_gaussian or decimate accept a proxyOffset. This is separate from the
146 // srcBounds and srcOffset, which are relative to the content rect of the image, whereas proxyOffset
147 // maps from the content rect to the proxy's coordinate space. Due to how the destination bounds are
148 // calculated, it is more convenient to have the proxy offset kept separate from the logical bounds
149 // (which do impact destination decisions). Both functions incorporate the proxy offset into the
150 // geometry they submit or before calling convolve_gaussian_1d.
151 
convolve_gaussian(GrRecordingContext * context,sk_sp<GrTextureProxy> srcProxy,GrColorType srcColorType,const SkIPoint & proxyOffset,const SkIRect & srcRect,const SkIPoint & srcOffset,Direction direction,int radius,float sigma,SkIRect * contentRect,GrTextureDomain::Mode mode,int finalW,int finalH,sk_sp<SkColorSpace> finalCS,SkBackingFit fit)152 static std::unique_ptr<GrRenderTargetContext> convolve_gaussian(GrRecordingContext* context,
153                                                                 sk_sp<GrTextureProxy> srcProxy,
154                                                                 GrColorType srcColorType,
155                                                                 const SkIPoint& proxyOffset,
156                                                                 const SkIRect& srcRect,
157                                                                 const SkIPoint& srcOffset,
158                                                                 Direction direction,
159                                                                 int radius,
160                                                                 float sigma,
161                                                                 SkIRect* contentRect,
162                                                                 GrTextureDomain::Mode mode,
163                                                                 int finalW,
164                                                                 int finalH,
165                                                                 sk_sp<SkColorSpace> finalCS,
166                                                                 SkBackingFit fit) {
167     SkASSERT(srcRect.width() <= finalW && srcRect.height() <= finalH);
168 
169     auto dstRenderTargetContext = context->priv().makeDeferredRenderTargetContext(
170             fit,
171             srcRect.width(),
172             srcRect.height(),
173             srcColorType,
174             std::move(finalCS),
175             1,
176             GrMipMapped::kNo,
177             srcProxy->origin(),
178             nullptr,
179             SkBudgeted::kYes,
180             srcProxy->isProtected() ? GrProtected::kYes : GrProtected::kNo);
181     if (!dstRenderTargetContext) {
182         return nullptr;
183     }
184 
185     GrFixedClip clip(SkIRect::MakeWH(finalW, finalH));
186 
187     int bounds[2] = { 0, 0 };
188     SkIRect dstRect = SkIRect::MakeWH(srcRect.width(), srcRect.height());
189     SkIPoint netOffset = srcOffset - proxyOffset;
190     if (GrTextureDomain::kIgnore_Mode == mode) {
191         *contentRect = dstRect;
192         convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, netOffset,
193                              std::move(srcProxy), srcColorType, direction, radius, sigma,
194                              GrTextureDomain::kIgnore_Mode, bounds);
195         return dstRenderTargetContext;
196     }
197     // These destination rects need to be adjusted by srcOffset, but should *not* be adjusted by
198     // the proxyOffset, which is why keeping them separate is convenient.
199     SkIRect midRect = *contentRect, leftRect, rightRect;
200     midRect.offset(srcOffset);
201     SkIRect topRect, bottomRect;
202     if (Direction::kX == direction) {
203         bounds[0] = contentRect->left() + proxyOffset.x();
204         bounds[1] = contentRect->right() + proxyOffset.x();
205         topRect = SkIRect::MakeLTRB(0, 0, dstRect.right(), midRect.top());
206         bottomRect = SkIRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom());
207         midRect.inset(radius, 0);
208         leftRect = SkIRect::MakeLTRB(0, midRect.top(), midRect.left(), midRect.bottom());
209         rightRect =
210             SkIRect::MakeLTRB(midRect.right(), midRect.top(), dstRect.width(), midRect.bottom());
211         dstRect.fTop = midRect.top();
212         dstRect.fBottom = midRect.bottom();
213 
214         contentRect->fLeft = dstRect.fLeft;
215         contentRect->fTop = midRect.fTop;
216         contentRect->fRight = dstRect.fRight;
217         contentRect->fBottom = midRect.fBottom;
218     } else {
219         bounds[0] = contentRect->top() + proxyOffset.y();
220         bounds[1] = contentRect->bottom() + proxyOffset.y();
221         topRect = SkIRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom());
222         bottomRect = SkIRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom());
223         midRect.inset(0, radius);
224         leftRect = SkIRect::MakeLTRB(midRect.left(), 0, midRect.right(), midRect.top());
225         rightRect =
226             SkIRect::MakeLTRB(midRect.left(), midRect.bottom(), midRect.right(), dstRect.height());
227         dstRect.fLeft = midRect.left();
228         dstRect.fRight = midRect.right();
229 
230         contentRect->fLeft = midRect.fLeft;
231         contentRect->fTop = dstRect.fTop;
232         contentRect->fRight = midRect.fRight;
233         contentRect->fBottom = dstRect.fBottom;
234     }
235     if (!topRect.isEmpty()) {
236         dstRenderTargetContext->clear(&topRect, SK_PMColor4fTRANSPARENT,
237                                       GrRenderTargetContext::CanClearFullscreen::kYes);
238     }
239 
240     if (!bottomRect.isEmpty()) {
241         dstRenderTargetContext->clear(&bottomRect, SK_PMColor4fTRANSPARENT,
242                                       GrRenderTargetContext::CanClearFullscreen::kYes);
243     }
244 
245     if (midRect.isEmpty()) {
246         // Blur radius covers srcBounds; use bounds over entire draw
247         convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, netOffset,
248                              std::move(srcProxy), srcColorType, direction, radius, sigma, mode,
249                              bounds);
250     } else {
251         // Draw right and left margins with bounds; middle without.
252         convolve_gaussian_1d(dstRenderTargetContext.get(), clip, leftRect, netOffset,
253                              srcProxy, srcColorType, direction, radius, sigma, mode, bounds);
254         convolve_gaussian_1d(dstRenderTargetContext.get(), clip, rightRect, netOffset,
255                              srcProxy, srcColorType, direction, radius, sigma, mode, bounds);
256         convolve_gaussian_1d(dstRenderTargetContext.get(), clip, midRect, netOffset,
257                              std::move(srcProxy), srcColorType, direction, radius, sigma,
258                              GrTextureDomain::kIgnore_Mode, bounds);
259     }
260 
261     return dstRenderTargetContext;
262 }
263 
264 // Returns a high quality scaled-down version of src. This is used to create an intermediate,
265 // shrunken version of the source image in the event that the requested blur sigma exceeds
266 // MAX_BLUR_SIGMA.
decimate(GrRecordingContext * context,sk_sp<GrTextureProxy> srcProxy,GrColorType srcColorType,const SkIPoint & proxyOffset,SkIPoint * srcOffset,SkIRect * contentRect,int scaleFactorX,int scaleFactorY,int radiusX,int radiusY,GrTextureDomain::Mode mode,int finalW,int finalH,sk_sp<SkColorSpace> finalCS)267 static sk_sp<GrTextureProxy> decimate(GrRecordingContext* context,
268                                       sk_sp<GrTextureProxy> srcProxy,
269                                       GrColorType srcColorType,
270                                       const SkIPoint& proxyOffset,
271                                       SkIPoint* srcOffset,
272                                       SkIRect* contentRect,
273                                       int scaleFactorX, int scaleFactorY,
274                                       int radiusX, int radiusY,
275                                       GrTextureDomain::Mode mode,
276                                       int finalW,
277                                       int finalH,
278                                       sk_sp<SkColorSpace> finalCS) {
279     SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY));
280     SkASSERT(scaleFactorX > 1 || scaleFactorY > 1);
281 
282     SkIRect srcRect;
283     if (GrTextureDomain::kIgnore_Mode == mode) {
284         srcRect = SkIRect::MakeWH(finalW, finalH);
285     } else {
286         srcRect = *contentRect;
287         srcRect.offset(*srcOffset);
288     }
289 
290     scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
291     scale_irect(&srcRect, scaleFactorX, scaleFactorY);
292 
293     SkIRect dstRect(srcRect);
294 
295     // Map the src rect into proxy space, this only has to happen once since subsequent loops
296     // to decimate will have created a new proxy that has its origin at (0, 0).
297     srcRect.offset(proxyOffset.x(), proxyOffset.y());
298     std::unique_ptr<GrRenderTargetContext> dstRenderTargetContext;
299 
300     for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
301         shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY);
302 
303         // We know this will not be the final draw so we are free to make it an approx match.
304         dstRenderTargetContext = context->priv().makeDeferredRenderTargetContext(
305                 SkBackingFit::kApprox,
306                 dstRect.fRight,
307                 dstRect.fBottom,
308                 srcColorType,
309                 finalCS,
310                 1,
311                 GrMipMapped::kNo,
312                 srcProxy->origin(),
313                 nullptr,
314                 SkBudgeted::kYes,
315                 srcProxy->isProtected() ? GrProtected::kYes : GrProtected::kNo);
316         if (!dstRenderTargetContext) {
317             return nullptr;
318         }
319 
320         GrPaint paint;
321         if (GrTextureDomain::kIgnore_Mode != mode && i == 1) {
322             // GrTextureDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter.
323             GrTextureDomain::Mode modeForScaling = GrTextureDomain::kRepeat_Mode == mode
324                                                                 ? GrTextureDomain::kDecal_Mode
325                                                                 : mode;
326 
327             SkRect domain = SkRect::Make(*contentRect);
328             domain.inset((i < scaleFactorX) ? SK_ScalarHalf + SK_ScalarNearlyZero : 0.0f,
329                          (i < scaleFactorY) ? SK_ScalarHalf + SK_ScalarNearlyZero : 0.0f);
330             // Ensure that the insetting doesn't invert the domain rectangle.
331             if (domain.fRight < domain.fLeft) {
332                 domain.fLeft = domain.fRight = SkScalarAve(domain.fLeft, domain.fRight);
333             }
334             if (domain.fBottom < domain.fTop) {
335                 domain.fTop = domain.fBottom = SkScalarAve(domain.fTop, domain.fBottom);
336             }
337             domain.offset(proxyOffset.x(), proxyOffset.y());
338             auto fp = GrTextureDomainEffect::Make(std::move(srcProxy),
339                                                   srcColorType,
340                                                   SkMatrix::I(),
341                                                   domain,
342                                                   modeForScaling,
343                                                   GrSamplerState::Filter::kBilerp);
344             paint.addColorFragmentProcessor(std::move(fp));
345             srcRect.offset(-(*srcOffset));
346             // TODO: consume the srcOffset in both first draws and always set it to zero
347             // back in GaussianBlur
348             srcOffset->set(0, 0);
349         } else {
350             paint.addColorTextureProcessor(std::move(srcProxy), srcColorType, SkMatrix::I(),
351                                            GrSamplerState::ClampBilerp());
352         }
353         paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
354 
355         dstRenderTargetContext->fillRectToRect(GrFixedClip::Disabled(), std::move(paint), GrAA::kNo,
356                                                SkMatrix::I(), SkRect::Make(dstRect),
357                                                SkRect::Make(srcRect));
358 
359         srcProxy = dstRenderTargetContext->asTextureProxyRef();
360         if (!srcProxy) {
361             return nullptr;
362         }
363         srcRect = dstRect;
364     }
365 
366     *contentRect = dstRect;
367 
368     SkASSERT(dstRenderTargetContext);
369 
370     return dstRenderTargetContext->asTextureProxyRef();
371 }
372 
373 // Expand the contents of 'srcRenderTargetContext' to fit in 'dstII'. At this point, we are
374 // expanding an intermediate image, so there's no need to account for a proxy offset from the
375 // original input.
reexpand(GrRecordingContext * context,std::unique_ptr<GrRenderTargetContext> srcRenderTargetContext,const SkIRect & localSrcBounds,int scaleFactorX,int scaleFactorY,int finalW,int finalH,sk_sp<SkColorSpace> finalCS,SkBackingFit fit)376 static std::unique_ptr<GrRenderTargetContext> reexpand(
377         GrRecordingContext* context,
378         std::unique_ptr<GrRenderTargetContext> srcRenderTargetContext,
379         const SkIRect& localSrcBounds,
380         int scaleFactorX, int scaleFactorY,
381         int finalW,
382         int finalH,
383         sk_sp<SkColorSpace> finalCS,
384         SkBackingFit fit) {
385     const SkIRect srcRect = SkIRect::MakeWH(srcRenderTargetContext->width(),
386                                             srcRenderTargetContext->height());
387 
388     sk_sp<GrTextureProxy> srcProxy = srcRenderTargetContext->asTextureProxyRef();
389     if (!srcProxy) {
390         return nullptr;
391     }
392 
393     GrColorType srcColorType = srcRenderTargetContext->colorInfo().colorType();
394 
395     srcRenderTargetContext = nullptr; // no longer needed
396 
397     auto dstRenderTargetContext = context->priv().makeDeferredRenderTargetContext(
398             fit, finalW, finalH, srcColorType, std::move(finalCS), 1, GrMipMapped::kNo,
399             srcProxy->origin());
400     if (!dstRenderTargetContext) {
401         return nullptr;
402     }
403 
404     GrPaint paint;
405     SkRect domain = GrTextureDomain::MakeTexelDomain(localSrcBounds, GrTextureDomain::kClamp_Mode,
406                                                      GrTextureDomain::kClamp_Mode);
407     auto fp = GrTextureDomainEffect::Make(std::move(srcProxy), srcColorType, SkMatrix::I(), domain,
408                                           GrTextureDomain::kClamp_Mode,
409                                           GrSamplerState::Filter::kBilerp);
410     paint.addColorFragmentProcessor(std::move(fp));
411     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
412     GrFixedClip clip(SkIRect::MakeWH(finalW, finalH));
413 
414     // TODO: using dstII as dstRect results in some image diffs - why?
415     SkIRect dstRect(srcRect);
416     scale_irect(&dstRect, scaleFactorX, scaleFactorY);
417 
418     dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
419                                            SkRect::Make(dstRect), SkRect::Make(srcRect));
420 
421     return dstRenderTargetContext;
422 }
423 
424 namespace SkGpuBlurUtils {
425 
GaussianBlur(GrRecordingContext * context,sk_sp<GrTextureProxy> srcProxy,GrColorType srcColorType,SkAlphaType srcAT,const SkIPoint & proxyOffset,sk_sp<SkColorSpace> colorSpace,const SkIRect & dstBounds,const SkIRect & srcBounds,float sigmaX,float sigmaY,GrTextureDomain::Mode mode,SkBackingFit fit)426 std::unique_ptr<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context,
427                                                     sk_sp<GrTextureProxy> srcProxy,
428                                                     GrColorType srcColorType,
429                                                     SkAlphaType srcAT,
430                                                     const SkIPoint& proxyOffset,
431                                                     sk_sp<SkColorSpace> colorSpace,
432                                                     const SkIRect& dstBounds,
433                                                     const SkIRect& srcBounds,
434                                                     float sigmaX,
435                                                     float sigmaY,
436                                                     GrTextureDomain::Mode mode,
437                                                     SkBackingFit fit) {
438     SkASSERT(context);
439 
440     TRACE_EVENT2("skia.gpu", "GaussianBlur", "sigmaX", sigmaX, "sigmaY", sigmaY);
441 
442     int finalW = dstBounds.width();
443     int finalH = dstBounds.height();
444 
445     int scaleFactorX, radiusX;
446     int scaleFactorY, radiusY;
447     int maxTextureSize = context->priv().caps()->maxTextureSize();
448     sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
449     sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
450     SkASSERT(sigmaX || sigmaY);
451 
452     SkIPoint srcOffset = SkIPoint::Make(-dstBounds.x(), -dstBounds.y());
453     SkIRect localSrcBounds = srcBounds;
454     SkIPoint localProxyOffset = proxyOffset;
455 
456     // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just
457     // launch a single non separable kernel vs two launches
458     if (sigmaX > 0.0f && sigmaY > 0.0f &&
459             (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
460         // We shouldn't be scaling because this is a small size blur
461         SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY));
462         // Apply the proxy offset to src bounds and offset directly
463         srcOffset -= proxyOffset;
464         localSrcBounds.offset(proxyOffset);
465         return convolve_gaussian_2d(context, std::move(srcProxy), srcColorType, localSrcBounds,
466                                     srcOffset, radiusX, radiusY, sigmaX, sigmaY, mode,
467                                     finalW, finalH, colorSpace, fit);
468     }
469 
470     // Only the last rendered renderTargetContext needs to match the supplied 'fit'
471     SkBackingFit xFit = fit, yFit = fit;
472     if (scaleFactorX > 1 || scaleFactorY > 1) {
473         xFit = yFit = SkBackingFit::kApprox;  // reexpand will be last
474     } else if (sigmaY > 0.0f) {
475         xFit = SkBackingFit::kApprox;         // the y-pass will be last
476     }
477 
478     GrTextureDomain::Mode currDomainMode = mode;
479     if (scaleFactorX > 1 || scaleFactorY > 1) {
480         srcProxy = decimate(context, std::move(srcProxy), srcColorType, localProxyOffset,
481                             &srcOffset, &localSrcBounds, scaleFactorX, scaleFactorY, radiusX,
482                             radiusY, currDomainMode, finalW, finalH, colorSpace);
483         if (!srcProxy) {
484             return nullptr;
485         }
486         localProxyOffset.set(0, 0);
487         if (GrTextureDomain::kIgnore_Mode == currDomainMode) {
488             // decimate() always returns an approx texture, possibly with garbage after the image.
489             // We can't ignore the domain anymore.
490             currDomainMode = GrTextureDomain::kClamp_Mode;
491         }
492     }
493 
494     std::unique_ptr<GrRenderTargetContext> dstRenderTargetContext;
495 
496     auto srcRect = SkIRect::MakeWH(finalW, finalH);
497     scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
498     if (sigmaX > 0.0f) {
499         dstRenderTargetContext = convolve_gaussian(
500                 context, std::move(srcProxy), srcColorType, localProxyOffset, srcRect, srcOffset,
501                 Direction::kX, radiusX, sigmaX, &localSrcBounds, currDomainMode, finalW, finalH,
502                 colorSpace, xFit);
503         if (!dstRenderTargetContext) {
504             return nullptr;
505         }
506 
507         srcProxy = dstRenderTargetContext->asTextureProxyRef();
508         if (!srcProxy) {
509             return nullptr;
510         }
511 
512         srcRect.offsetTo(0, 0);
513         srcOffset.set(0, 0);
514         localProxyOffset.set(0, 0);
515         if (SkBackingFit::kApprox == xFit && GrTextureDomain::kIgnore_Mode == currDomainMode) {
516             // srcProxy is now an approx texture, possibly with garbage after the image. We can't
517             // ignore the domain anymore.
518             currDomainMode = GrTextureDomain::kClamp_Mode;
519         }
520     }
521 
522     if (sigmaY > 0.0f) {
523         dstRenderTargetContext = convolve_gaussian(
524                 context, std::move(srcProxy), srcColorType, localProxyOffset, srcRect, srcOffset,
525                 Direction::kY, radiusY, sigmaY, &localSrcBounds, currDomainMode, finalW, finalH,
526                 colorSpace, yFit);
527         if (!dstRenderTargetContext) {
528             return nullptr;
529         }
530 
531         srcProxy = dstRenderTargetContext->asTextureProxyRef();
532         if (!srcProxy) {
533             return nullptr;
534         }
535 
536         srcRect.offsetTo(0, 0);
537         srcOffset.set(0, 0);
538         localProxyOffset.set(0, 0);
539     }
540 
541     SkASSERT(dstRenderTargetContext);
542     SkASSERT(srcProxy.get() == dstRenderTargetContext->asTextureProxy());
543     SkASSERT(localProxyOffset.x() == 0 && localProxyOffset.y() == 0);
544 
545     if (scaleFactorX > 1 || scaleFactorY > 1) {
546         dstRenderTargetContext =
547                 reexpand(context, std::move(dstRenderTargetContext), localSrcBounds, scaleFactorX,
548                          scaleFactorY, finalW, finalH, colorSpace, fit);
549     }
550 
551     SkASSERT(!dstRenderTargetContext || dstRenderTargetContext->origin() == srcProxy->origin());
552     return dstRenderTargetContext;
553 }
554 }
555 
556 #endif
557