• 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 "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 
to_texture_domain_mode(SkTileMode tileMode)74 static GrTextureDomain::Mode to_texture_domain_mode(SkTileMode tileMode) {
75     switch (tileMode) {
76         case SkTileMode::kClamp:
77             return GrTextureDomain::kClamp_Mode;
78         case SkTileMode::kDecal:
79             return GrTextureDomain::kDecal_Mode;
80         case SkTileMode::kMirror:
81             // TODO (michaelludwig) - Support mirror mode, treat as repeat for now
82         case SkTileMode::kRepeat:
83             return GrTextureDomain::kRepeat_Mode;
84         default:
85             SK_ABORT("Unsupported tile mode.");
86     }
87 }
88 
89 /**
90  * Draws 'rtcRect' into 'renderTargetContext' evaluating a 1D Gaussian over 'srcView'. The src rect
91  * is 'rtcRect' offset by 'rtcToSrcOffset'. 'mode' and 'bounds' are applied to the src coords.
92  */
convolve_gaussian_1d(GrRenderTargetContext * renderTargetContext,GrSurfaceProxyView srcView,SkIVector rtcToSrcOffset,const SkIRect & rtcRect,SkAlphaType srcAlphaType,Direction direction,int radius,float sigma,SkTileMode mode,int bounds[2])93 static void convolve_gaussian_1d(GrRenderTargetContext* renderTargetContext,
94                                  GrSurfaceProxyView srcView,
95                                  SkIVector rtcToSrcOffset,
96                                  const SkIRect& rtcRect,
97                                  SkAlphaType srcAlphaType,
98                                  Direction direction,
99                                  int radius,
100                                  float sigma,
101                                  SkTileMode mode,
102                                  int bounds[2]) {
103     GrPaint paint;
104     auto domainMode = to_texture_domain_mode(mode);
105     int realBounds[2];
106     if (bounds) {
107         realBounds[0] = bounds[0]; realBounds[1] = bounds[1];
108     } else {
109         auto proxy = srcView.proxy();
110         realBounds[0] = 0;
111         realBounds[1] = direction == Direction::kX ? proxy->width() : proxy->height();
112     }
113     std::unique_ptr<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make(
114             std::move(srcView), srcAlphaType, direction, radius, sigma, domainMode, realBounds));
115     paint.addColorFragmentProcessor(std::move(conv));
116     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
117     auto srcRect = SkRect::Make(rtcRect.makeOffset(rtcToSrcOffset));
118     renderTargetContext->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(),
119                                         SkRect::Make(rtcRect), srcRect);
120 }
121 
convolve_gaussian_2d(GrRecordingContext * context,GrSurfaceProxyView srcView,GrColorType srcColorType,const SkIRect & srcBounds,const SkIRect & dstBounds,int radiusX,int radiusY,SkScalar sigmaX,SkScalar sigmaY,SkTileMode mode,sk_sp<SkColorSpace> finalCS,SkBackingFit dstFit)122 static std::unique_ptr<GrRenderTargetContext> convolve_gaussian_2d(GrRecordingContext* context,
123                                                                    GrSurfaceProxyView srcView,
124                                                                    GrColorType srcColorType,
125                                                                    const SkIRect& srcBounds,
126                                                                    const SkIRect& dstBounds,
127                                                                    int radiusX,
128                                                                    int radiusY,
129                                                                    SkScalar sigmaX,
130                                                                    SkScalar sigmaY,
131                                                                    SkTileMode mode,
132                                                                    sk_sp<SkColorSpace> finalCS,
133                                                                    SkBackingFit dstFit) {
134     auto renderTargetContext = GrRenderTargetContext::Make(
135             context, srcColorType, std::move(finalCS), dstFit, dstBounds.size(), 1,
136             GrMipMapped::kNo, srcView.proxy()->isProtected(), srcView.origin());
137     if (!renderTargetContext) {
138         return nullptr;
139     }
140 
141     SkISize size = SkISize::Make(2 * radiusX + 1,  2 * radiusY + 1);
142     SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
143     GrPaint paint;
144     auto domainMode = to_texture_domain_mode(mode);
145     auto conv = GrMatrixConvolutionEffect::MakeGaussian(std::move(srcView), srcBounds, size,
146                                                         1.0, 0.0, kernelOffset, domainMode, true,
147                                                         sigmaX, sigmaY);
148     paint.addColorFragmentProcessor(std::move(conv));
149     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
150 
151     // 'dstBounds' is actually in 'srcView' proxy space. It represents the blurred area from src
152     // space that we want to capture in the new RTC at {0, 0}. Hence, we use its size as the rect to
153     // draw and it directly as the local rect.
154     renderTargetContext->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(),
155                                         SkRect::Make(dstBounds.size()), SkRect::Make(dstBounds));
156 
157     return renderTargetContext;
158 }
159 
convolve_gaussian(GrRecordingContext * context,GrSurfaceProxyView srcView,GrColorType srcColorType,SkAlphaType srcAlphaType,SkIRect * contentRect,SkIRect dstBounds,Direction direction,int radius,float sigma,SkTileMode mode,sk_sp<SkColorSpace> finalCS,SkBackingFit fit)160 static std::unique_ptr<GrRenderTargetContext> convolve_gaussian(GrRecordingContext* context,
161                                                                 GrSurfaceProxyView srcView,
162                                                                 GrColorType srcColorType,
163                                                                 SkAlphaType srcAlphaType,
164                                                                 SkIRect* contentRect,
165                                                                 SkIRect dstBounds,
166                                                                 Direction direction,
167                                                                 int radius,
168                                                                 float sigma,
169                                                                 SkTileMode mode,
170                                                                 sk_sp<SkColorSpace> finalCS,
171                                                                 SkBackingFit fit) {
172     // Logically we're creating an infinite blur of 'contentRect' of 'srcView' with 'mode' tiling
173     // and then capturing the 'dstBounds' portion in a new RTC where the top left of 'dstBounds' is
174     // at {0, 0} in the new RTC.
175     auto dstRenderTargetContext = GrRenderTargetContext::Make(
176             context, srcColorType, std::move(finalCS), fit, dstBounds.size(), 1, GrMipMapped::kNo,
177             srcView.proxy()->isProtected(), srcView.origin());
178     if (!dstRenderTargetContext) {
179         return nullptr;
180     }
181 
182     // This represents the translation from 'dstRenderTargetContext' coords to 'srcView' coords.
183     auto rtcToSrcOffset = dstBounds.topLeft();
184 
185     if (SkTileMode::kClamp == mode &&
186         contentRect->contains(SkIRect::MakeSize(srcView.proxy()->backingStoreDimensions()))) {
187         auto dstRect = SkIRect::MakeSize(dstBounds.size());
188         convolve_gaussian_1d(dstRenderTargetContext.get(), std::move(srcView), rtcToSrcOffset,
189                              dstRect, srcAlphaType, direction, radius, sigma, SkTileMode::kClamp,
190                              nullptr);
191         *contentRect = dstRect;
192         return dstRenderTargetContext;
193     }
194 
195     // 'left' and 'right' are the sub rects of 'contentTect' where 'mode' must be enforced.
196     // 'mid' is the area where we can ignore the mode because the kernel does not reach to the
197     // edge of 'contentRect'. The names are derived from the Direction::kX case.
198     // TODO: When mode is kMirror or kRepeat it makes more sense to think of 'contentRect'
199     // as a tile and figure out the collection of mid/left/right rects that cover 'dstBounds'.
200     // Also if 'mid' is small and 'left' or 'right' is non-empty we should probably issue one
201     // draw that implements the mode in the shader rather than break it up in this fashion.
202     SkIRect mid, left, right;
203     // 'top' and 'bottom' are areas of 'dstBounds' that are entirely above/below
204     // 'contentRect'. These are areas that we can simply clear in the dst. If 'contentRect'
205     // straddles the top edge of 'dstBounds' then 'top' will be inverted and we will skip
206     // the clear. Similar for 'bottom'. The positional/directional labels above refer to the
207     // Direction::kX case and one should think of these as 'left' and 'right' for Direction::kY.
208     SkIRect top, bottom;
209     int bounds[2];
210     if (Direction::kX == direction) {
211         bounds[0] = contentRect->left();
212         bounds[1] = contentRect->right();
213 
214         top    = {dstBounds.left(), dstBounds.top()      , dstBounds.right(), contentRect->top()};
215         bottom = {dstBounds.left(), contentRect->bottom(), dstBounds.right(), dstBounds.bottom()};
216 
217         // Inset for sub-rect of 'contentRect' where the x-dir kernel doesn't reach the edges.
218         // TODO: Consider clipping mid/left/right to dstBounds to increase likelihood of doing
219         // fewer draws below.
220         mid = contentRect->makeInset(radius, 0);
221 
222         left  = {dstBounds.left(), mid.top(), mid.left()       , mid.bottom()};
223         right = {mid.right(),      mid.top(), dstBounds.right(), mid.bottom()};
224 
225         // The new 'contentRect' when we're done will be the area between the clears.
226         *contentRect = {dstBounds.left(), top.bottom(), dstBounds.right(), bottom.top()};
227     } else {
228         // This is the same as the x direction code if you turn your head 90 degrees CCW. Swap x and
229         // y and swap top/bottom with left/right.
230         bounds[0] = contentRect->top();
231         bounds[1] = contentRect->bottom();
232 
233         top    = {dstBounds.left(),     dstBounds.top() , contentRect->left(), dstBounds.bottom()};
234         bottom = {contentRect->right(), dstBounds.top() , dstBounds.right()  , dstBounds.bottom()};
235 
236         mid = contentRect->makeInset(0, radius);
237 
238         left  = {mid.left(), dstBounds.top(), mid.right(), mid.top()         };
239         right = {mid.left(), mid.bottom()   , mid.right(), dstBounds.bottom()};
240 
241         *contentRect = {top.right(), dstBounds.top(), bottom.left(), dstBounds.bottom()};
242     }
243     // Move all the rects from 'srcView' coord system to 'dstRenderTargetContext' coord system.
244     mid   .offset(-rtcToSrcOffset);
245     top   .offset(-rtcToSrcOffset);
246     bottom.offset(-rtcToSrcOffset);
247     left  .offset(-rtcToSrcOffset);
248     right .offset(-rtcToSrcOffset);
249 
250     contentRect->offset(-rtcToSrcOffset);
251 
252     if (!top.isEmpty()) {
253         dstRenderTargetContext->clear(&top, SK_PMColor4fTRANSPARENT,
254                                       GrRenderTargetContext::CanClearFullscreen::kYes);
255     }
256 
257     if (!bottom.isEmpty()) {
258         dstRenderTargetContext->clear(&bottom, SK_PMColor4fTRANSPARENT,
259                                       GrRenderTargetContext::CanClearFullscreen::kYes);
260     }
261 
262     if (mid.isEmpty()) {
263         convolve_gaussian_1d(dstRenderTargetContext.get(), std::move(srcView), rtcToSrcOffset,
264                              *contentRect, srcAlphaType, direction, radius, sigma, mode, bounds);
265     } else {
266         // Draw right and left margins with bounds; middle without.
267         convolve_gaussian_1d(dstRenderTargetContext.get(), srcView, rtcToSrcOffset, left,
268                              srcAlphaType, direction, radius, sigma, mode, bounds);
269         convolve_gaussian_1d(dstRenderTargetContext.get(), srcView, rtcToSrcOffset, right,
270                              srcAlphaType, direction, radius, sigma, mode, bounds);
271         convolve_gaussian_1d(dstRenderTargetContext.get(), std::move(srcView), rtcToSrcOffset, mid,
272                              srcAlphaType, direction, radius, sigma, SkTileMode::kClamp, nullptr);
273     }
274 
275     return dstRenderTargetContext;
276 }
277 
278 // Returns a high quality scaled-down version of src. This is used to create an intermediate,
279 // shrunken version of the source image in the event that the requested blur sigma exceeds
280 // MAX_BLUR_SIGMA.
decimate(GrRecordingContext * context,GrSurfaceProxyView srcView,GrColorType srcColorType,SkAlphaType srcAlphaType,SkIPoint srcOffset,SkIRect * contentRect,int scaleFactorX,int scaleFactorY,SkTileMode mode,sk_sp<SkColorSpace> finalCS)281 static GrSurfaceProxyView decimate(GrRecordingContext* context,
282                                    GrSurfaceProxyView srcView,
283                                    GrColorType srcColorType,
284                                    SkAlphaType srcAlphaType,
285                                    SkIPoint srcOffset,
286                                    SkIRect* contentRect,
287                                    int scaleFactorX,
288                                    int scaleFactorY,
289                                    SkTileMode mode,
290                                    sk_sp<SkColorSpace> finalCS) {
291     SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY));
292     SkASSERT(scaleFactorX > 1 || scaleFactorY > 1);
293 
294     SkIRect srcRect = contentRect->makeOffset(srcOffset);
295 
296     scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
297     scale_irect(&srcRect, scaleFactorX, scaleFactorY);
298 
299     SkIRect dstRect(srcRect);
300 
301     std::unique_ptr<GrRenderTargetContext> dstRenderTargetContext;
302 
303     for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
304         shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY);
305 
306         dstRenderTargetContext = GrRenderTargetContext::Make(
307                 context, srcColorType, finalCS, SkBackingFit::kApprox,
308                 {dstRect.fRight, dstRect.fBottom}, 1, GrMipMapped::kNo,
309                 srcView.proxy()->isProtected(), srcView.origin());
310         if (!dstRenderTargetContext) {
311             return {};
312         }
313 
314         GrPaint paint;
315         std::unique_ptr<GrFragmentProcessor> fp;
316         if (i == 1) {
317             GrSamplerState::WrapMode wrapMode;
318             if (mode == SkTileMode::kClamp) {
319                 wrapMode = GrSamplerState::WrapMode::kClamp;
320             } else {
321                 // GrTextureEffect does not support WrapMode::k[Mirror]Repeat with
322                 // GrSamplerState::Filter::kBilerp. So we use kClampToBorder.
323                 wrapMode = GrSamplerState::WrapMode::kClampToBorder;
324             }
325             const auto& caps = *context->priv().caps();
326             GrSamplerState sampler(wrapMode, GrSamplerState::Filter::kBilerp);
327             fp = GrTextureEffect::MakeSubset(std::move(srcView), srcAlphaType, SkMatrix::I(),
328                                              sampler, SkRect::Make(*contentRect), caps);
329             srcRect.offset(-srcOffset);
330         } else {
331             fp = GrTextureEffect::Make(std::move(srcView), srcAlphaType, SkMatrix::I(),
332                                        GrSamplerState::Filter::kBilerp);
333         }
334         paint.addColorFragmentProcessor(std::move(fp));
335         paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
336 
337         dstRenderTargetContext->fillRectToRect(GrFixedClip::Disabled(), std::move(paint), GrAA::kNo,
338                                                SkMatrix::I(), SkRect::Make(dstRect),
339                                                SkRect::Make(srcRect));
340 
341         srcView = dstRenderTargetContext->readSurfaceView();
342         if (!srcView.asTextureProxy()) {
343             return {};
344         }
345         srcRect = dstRect;
346     }
347 
348     *contentRect = dstRect;
349 
350     SkASSERT(dstRenderTargetContext);
351     SkASSERT(srcView == dstRenderTargetContext->readSurfaceView());
352 
353     return srcView;
354 }
355 
356 // Expand the contents of 'srcRenderTargetContext' to fit in 'dstII'. At this point, we are
357 // expanding an intermediate image, so there's no need to account for a proxy offset from the
358 // original input.
reexpand(GrRecordingContext * context,std::unique_ptr<GrRenderTargetContext> src,const SkIRect & srcBounds,int scaleFactorX,int scaleFactorY,SkISize dstSize,sk_sp<SkColorSpace> colorSpace,SkBackingFit fit)359 static std::unique_ptr<GrRenderTargetContext> reexpand(GrRecordingContext* context,
360                                                        std::unique_ptr<GrRenderTargetContext> src,
361                                                        const SkIRect& srcBounds,
362                                                        int scaleFactorX,
363                                                        int scaleFactorY,
364                                                        SkISize dstSize,
365                                                        sk_sp<SkColorSpace> colorSpace,
366                                                        SkBackingFit fit) {
367     const SkIRect srcRect = SkIRect::MakeWH(src->width(), src->height());
368 
369     GrSurfaceProxyView srcView = src->readSurfaceView();
370     if (!srcView.asTextureProxy()) {
371         return nullptr;
372     }
373 
374     GrColorType srcColorType = src->colorInfo().colorType();
375     SkAlphaType srcAlphaType = src->colorInfo().alphaType();
376 
377     src.reset(); // no longer needed
378 
379     auto dstRenderTargetContext = GrRenderTargetContext::Make(
380             context, srcColorType, std::move(colorSpace), fit, dstSize, 1, GrMipMapped::kNo,
381             srcView.proxy()->isProtected(), srcView.origin());
382     if (!dstRenderTargetContext) {
383         return nullptr;
384     }
385 
386     GrPaint paint;
387     const auto& caps = *context->priv().caps();
388     auto fp = GrTextureEffect::MakeSubset(std::move(srcView), srcAlphaType, SkMatrix::I(),
389                                           GrSamplerState::Filter::kBilerp, SkRect::Make(srcBounds),
390                                           caps);
391     paint.addColorFragmentProcessor(std::move(fp));
392     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
393     GrFixedClip clip(SkIRect::MakeSize(dstSize));
394 
395     // TODO: using dstII as dstRect results in some image diffs - why?
396     SkIRect dstRect(srcRect);
397     scale_irect(&dstRect, scaleFactorX, scaleFactorY);
398 
399     dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
400                                            SkRect::Make(dstRect), SkRect::Make(srcRect));
401 
402     return dstRenderTargetContext;
403 }
404 
two_pass_gaussian(GrRecordingContext * context,GrSurfaceProxyView srcView,GrColorType srcColorType,SkAlphaType srcAlphaType,sk_sp<SkColorSpace> colorSpace,SkIRect * srcBounds,SkIRect dstBounds,float sigmaX,float sigmaY,int radiusX,int radiusY,SkTileMode mode,SkBackingFit fit)405 static std::unique_ptr<GrRenderTargetContext> two_pass_gaussian(GrRecordingContext* context,
406                                                                 GrSurfaceProxyView srcView,
407                                                                 GrColorType srcColorType,
408                                                                 SkAlphaType srcAlphaType,
409                                                                 sk_sp<SkColorSpace> colorSpace,
410                                                                 SkIRect* srcBounds,
411                                                                 SkIRect dstBounds,
412                                                                 float sigmaX,
413                                                                 float sigmaY,
414                                                                 int radiusX,
415                                                                 int radiusY,
416                                                                 SkTileMode mode,
417                                                                 SkBackingFit fit) {
418     std::unique_ptr<GrRenderTargetContext> dstRenderTargetContext;
419     if (sigmaX > 0.0f) {
420         SkBackingFit xFit = sigmaY > 0 ? SkBackingFit::kApprox : fit;
421         dstRenderTargetContext = convolve_gaussian(
422                 context, std::move(srcView), srcColorType, srcAlphaType, srcBounds, dstBounds,
423                 Direction::kX, radiusX, sigmaX, mode, colorSpace, xFit);
424         if (!dstRenderTargetContext) {
425             return nullptr;
426         }
427         srcView = dstRenderTargetContext->readSurfaceView();
428         dstBounds = SkIRect::MakeSize(dstBounds.size());
429     }
430 
431     if (sigmaY == 0.0f) {
432         return dstRenderTargetContext;
433     }
434 
435     return convolve_gaussian(context, std::move(srcView), srcColorType, srcAlphaType, srcBounds,
436                              dstBounds, Direction::kY, radiusY, sigmaY, mode, colorSpace, fit);
437 }
438 
439 namespace SkGpuBlurUtils {
440 
GaussianBlur(GrRecordingContext * context,GrSurfaceProxyView srcView,GrColorType srcColorType,SkAlphaType srcAlphaType,sk_sp<SkColorSpace> colorSpace,const SkIRect & dstBounds,const SkIRect & srcBounds,float sigmaX,float sigmaY,SkTileMode mode,SkBackingFit fit)441 std::unique_ptr<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context,
442                                                     GrSurfaceProxyView srcView,
443                                                     GrColorType srcColorType,
444                                                     SkAlphaType srcAlphaType,
445                                                     sk_sp<SkColorSpace> colorSpace,
446                                                     const SkIRect& dstBounds,
447                                                     const SkIRect& srcBounds,
448                                                     float sigmaX,
449                                                     float sigmaY,
450                                                     SkTileMode mode,
451                                                     SkBackingFit fit) {
452     SkASSERT(context);
453     TRACE_EVENT2("skia.gpu", "GaussianBlur", "sigmaX", sigmaX, "sigmaY", sigmaY);
454 
455     if (!srcView.asTextureProxy()) {
456         return nullptr;
457     }
458 
459     int scaleFactorX, radiusX;
460     int scaleFactorY, radiusY;
461     int maxTextureSize = context->priv().caps()->maxTextureSize();
462     sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
463     sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
464     SkASSERT(sigmaX || sigmaY);
465 
466     auto localSrcBounds = srcBounds;
467 
468     if (scaleFactorX == 1 && scaleFactorY == 1) {
469         // For really small blurs (certainly no wider than 5x5 on desktop GPUs) it is faster to just
470         // launch a single non separable kernel vs two launches.
471         if (sigmaX > 0 && sigmaY > 0 && (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
472             // Apply the proxy offset to src bounds and offset directly
473             return convolve_gaussian_2d(context, std::move(srcView), srcColorType, srcBounds,
474                                         dstBounds, radiusX, radiusY, sigmaX, sigmaY, mode,
475                                         colorSpace, fit);
476         }
477         return two_pass_gaussian(context, std::move(srcView), srcColorType, srcAlphaType,
478                                  std::move(colorSpace), &localSrcBounds, dstBounds, sigmaX, sigmaY,
479                                  radiusX, radiusY, mode, fit);
480     }
481 
482     auto srcOffset = -dstBounds.topLeft();
483     srcView = decimate(context, std::move(srcView), srcColorType, srcAlphaType, srcOffset,
484                        &localSrcBounds, scaleFactorX, scaleFactorY, mode, colorSpace);
485     if (!srcView.proxy()) {
486         return nullptr;
487     }
488     SkASSERT(srcView.asTextureProxy());
489     auto scaledDstBounds = SkIRect::MakeWH(sk_float_ceil(dstBounds.width()  / (float)scaleFactorX),
490                                            sk_float_ceil(dstBounds.height() / (float)scaleFactorY));
491     auto rtc = two_pass_gaussian(context, std::move(srcView), srcColorType, srcAlphaType,
492                                  colorSpace, &localSrcBounds, scaledDstBounds, sigmaX, sigmaY,
493                                  radiusX, radiusY, mode, SkBackingFit::kApprox);
494     if (!rtc) {
495         return nullptr;
496     }
497     return reexpand(context, std::move(rtc), localSrcBounds, scaleFactorX, scaleFactorY,
498                     dstBounds.size(), std::move(colorSpace), fit);
499 }
500 
501 }
502 
503 #endif
504