blob: 832e48a4e0247d98b614a2495d7338da668463d3 [file] [log] [blame]
robertphillips@google.com736dd032013-07-15 15:06:54 +00001/*
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 "effects/GrConvolutionEffect.h"
joshualitt5acfea72014-08-11 13:55:34 -070014#include "effects/GrMatrixConvolutionEffect.h"
robertphillips@google.com736dd032013-07-15 15:06:54 +000015#include "GrContext.h"
16#endif
17
18namespace SkGpuBlurUtils {
19
20#if SK_SUPPORT_GPU
21
22#define MAX_BLUR_SIGMA 4.0f
23
24static void scale_rect(SkRect* rect, float xScale, float yScale) {
commit-bot@chromium.org4b413c82013-11-25 19:44:07 +000025 rect->fLeft = SkScalarMul(rect->fLeft, xScale);
26 rect->fTop = SkScalarMul(rect->fTop, yScale);
27 rect->fRight = SkScalarMul(rect->fRight, xScale);
28 rect->fBottom = SkScalarMul(rect->fBottom, yScale);
robertphillips@google.com736dd032013-07-15 15:06:54 +000029}
30
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +000031static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) {
robertphillips@google.com736dd032013-07-15 15:06:54 +000032 *scaleFactor = 1;
33 while (sigma > MAX_BLUR_SIGMA) {
34 *scaleFactor *= 2;
35 sigma *= 0.5f;
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +000036 if (*scaleFactor > maxTextureSize) {
37 *scaleFactor = maxTextureSize;
38 sigma = MAX_BLUR_SIGMA;
39 }
robertphillips@google.com736dd032013-07-15 15:06:54 +000040 }
41 *radius = static_cast<int>(ceilf(sigma * 3.0f));
commit-bot@chromium.org96ae6882013-08-14 12:09:00 +000042 SkASSERT(*radius <= GrConvolutionEffect::kMaxKernelRadius);
robertphillips@google.com736dd032013-07-15 15:06:54 +000043 return sigma;
44}
45
joshualitt5acfea72014-08-11 13:55:34 -070046static void convolve_gaussian_1d(GrContext* context,
joshualitt25d9c152015-02-18 12:29:52 -080047 GrRenderTarget* rt,
joshualitt570d2f82015-02-25 13:19:48 -080048 const GrClip& clip,
joshualitt5acfea72014-08-11 13:55:34 -070049 const SkRect& srcRect,
50 const SkRect& dstRect,
51 GrTexture* texture,
52 Gr1DKernelEffect::Direction direction,
53 int radius,
54 float sigma,
55 bool useBounds,
56 float bounds[2]) {
robertphillips@google.com736dd032013-07-15 15:06:54 +000057 GrPaint paint;
joshualittb0a8a372014-09-23 09:50:21 -070058 SkAutoTUnref<GrFragmentProcessor> conv(GrConvolutionEffect::CreateGaussian(
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +000059 texture, direction, radius, sigma, useBounds, bounds));
joshualittb0a8a372014-09-23 09:50:21 -070060 paint.addColorProcessor(conv);
joshualitt570d2f82015-02-25 13:19:48 -080061 context->drawNonAARectToRect(rt, clip, paint, SkMatrix::I(), dstRect, srcRect);
robertphillips@google.com736dd032013-07-15 15:06:54 +000062}
63
joshualitt5acfea72014-08-11 13:55:34 -070064static void convolve_gaussian_2d(GrContext* context,
joshualitt25d9c152015-02-18 12:29:52 -080065 GrRenderTarget* rt,
joshualitt570d2f82015-02-25 13:19:48 -080066 const GrClip& clip,
joshualitt5acfea72014-08-11 13:55:34 -070067 const SkRect& srcRect,
68 const SkRect& dstRect,
69 GrTexture* texture,
70 int radiusX,
71 int radiusY,
72 SkScalar sigmaX,
73 SkScalar sigmaY,
74 bool useBounds,
75 SkIRect bounds) {
76 SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1);
77 SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
78 GrPaint paint;
joshualittb0a8a372014-09-23 09:50:21 -070079 SkAutoTUnref<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::CreateGaussian(
joshualitt5acfea72014-08-11 13:55:34 -070080 texture, bounds, size, 1.0, 0.0, kernelOffset,
81 useBounds ? GrTextureDomain::kClamp_Mode : GrTextureDomain::kIgnore_Mode,
82 true, sigmaX, sigmaY));
joshualittb0a8a372014-09-23 09:50:21 -070083 paint.addColorProcessor(conv);
joshualitt570d2f82015-02-25 13:19:48 -080084 context->drawNonAARectToRect(rt, clip, paint, SkMatrix::I(), dstRect, srcRect);
joshualitt5acfea72014-08-11 13:55:34 -070085}
86
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +000087static void convolve_gaussian(GrContext* context,
joshualitt25d9c152015-02-18 12:29:52 -080088 GrRenderTarget* rt,
joshualitt570d2f82015-02-25 13:19:48 -080089 const GrClip& clip,
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +000090 const SkRect& srcRect,
91 const SkRect& dstRect,
92 GrTexture* texture,
93 Gr1DKernelEffect::Direction direction,
94 int radius,
95 float sigma,
96 bool cropToSrcRect) {
97 float bounds[2] = { 0.0f, 1.0f };
98 if (!cropToSrcRect) {
joshualitt570d2f82015-02-25 13:19:48 -080099 convolve_gaussian_1d(context, rt, clip, srcRect, dstRect, texture,
joshualitt5acfea72014-08-11 13:55:34 -0700100 direction, radius, sigma, false, bounds);
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +0000101 return;
102 }
103 SkRect lowerSrcRect = srcRect, lowerDstRect = dstRect;
104 SkRect middleSrcRect = srcRect, middleDstRect = dstRect;
105 SkRect upperSrcRect = srcRect, upperDstRect = dstRect;
106 SkScalar size;
107 SkScalar rad = SkIntToScalar(radius);
108 if (direction == Gr1DKernelEffect::kX_Direction) {
109 bounds[0] = SkScalarToFloat(srcRect.left()) / texture->width();
110 bounds[1] = SkScalarToFloat(srcRect.right()) / texture->width();
111 size = srcRect.width();
112 lowerSrcRect.fRight = srcRect.left() + rad;
113 lowerDstRect.fRight = dstRect.left() + rad;
114 upperSrcRect.fLeft = srcRect.right() - rad;
115 upperDstRect.fLeft = dstRect.right() - rad;
116 middleSrcRect.inset(rad, 0);
117 middleDstRect.inset(rad, 0);
118 } else {
119 bounds[0] = SkScalarToFloat(srcRect.top()) / texture->height();
120 bounds[1] = SkScalarToFloat(srcRect.bottom()) / texture->height();
121 size = srcRect.height();
122 lowerSrcRect.fBottom = srcRect.top() + rad;
123 lowerDstRect.fBottom = dstRect.top() + rad;
124 upperSrcRect.fTop = srcRect.bottom() - rad;
125 upperDstRect.fTop = dstRect.bottom() - rad;
126 middleSrcRect.inset(0, rad);
127 middleDstRect.inset(0, rad);
128 }
129 if (radius >= size * SK_ScalarHalf) {
130 // Blur radius covers srcRect; use bounds over entire draw
joshualitt570d2f82015-02-25 13:19:48 -0800131 convolve_gaussian_1d(context, rt, clip, srcRect, dstRect, texture,
joshualitt5acfea72014-08-11 13:55:34 -0700132 direction, radius, sigma, true, bounds);
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +0000133 } else {
134 // Draw upper and lower margins with bounds; middle without.
joshualitt570d2f82015-02-25 13:19:48 -0800135 convolve_gaussian_1d(context, rt, clip, lowerSrcRect, lowerDstRect, texture,
joshualitt5acfea72014-08-11 13:55:34 -0700136 direction, radius, sigma, true, bounds);
joshualitt570d2f82015-02-25 13:19:48 -0800137 convolve_gaussian_1d(context, rt, clip, upperSrcRect, upperDstRect, texture,
joshualitt5acfea72014-08-11 13:55:34 -0700138 direction, radius, sigma, true, bounds);
joshualitt570d2f82015-02-25 13:19:48 -0800139 convolve_gaussian_1d(context, rt, clip, middleSrcRect, middleDstRect, texture,
joshualitt5acfea72014-08-11 13:55:34 -0700140 direction, radius, sigma, false, bounds);
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +0000141 }
142}
143
robertphillips@google.com736dd032013-07-15 15:06:54 +0000144GrTexture* GaussianBlur(GrContext* context,
145 GrTexture* srcTexture,
146 bool canClobberSrc,
147 const SkRect& rect,
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000148 bool cropToRect,
skia.committer@gmail.com977409a2013-07-16 07:00:56 +0000149 float sigmaX,
robertphillips@google.com736dd032013-07-15 15:06:54 +0000150 float sigmaY) {
bsalomon49f085d2014-09-05 13:34:00 -0700151 SkASSERT(context);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000152
robertphillips@google.com736dd032013-07-15 15:06:54 +0000153 SkIRect clearRect;
154 int scaleFactorX, radiusX;
155 int scaleFactorY, radiusY;
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +0000156 int maxTextureSize = context->getMaxTextureSize();
157 sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
158 sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000159
160 SkRect srcRect(rect);
161 scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
reedd02cf262014-11-18 18:06:45 -0800162 srcRect.roundOut(&srcRect);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000163 scale_rect(&srcRect, static_cast<float>(scaleFactorX),
164 static_cast<float>(scaleFactorY));
165
joshualitt570d2f82015-02-25 13:19:48 -0800166 // setup new clip
167 GrClip clip(SkRect::MakeWH(srcRect.width(), srcRect.height()));
robertphillips@google.com736dd032013-07-15 15:06:54 +0000168
commit-bot@chromium.org96ae6882013-08-14 12:09:00 +0000169 SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() ||
robertphillips@google.com736dd032013-07-15 15:06:54 +0000170 kRGBA_8888_GrPixelConfig == srcTexture->config() ||
171 kAlpha_8_GrPixelConfig == srcTexture->config());
172
bsalomonf2703d82014-10-28 14:33:06 -0700173 GrSurfaceDesc desc;
bsalomon6bc1b5f2015-02-23 09:06:38 -0800174 desc.fFlags = kRenderTarget_GrSurfaceFlag;
robertphillips@google.com736dd032013-07-15 15:06:54 +0000175 desc.fWidth = SkScalarFloorToInt(srcRect.width());
176 desc.fHeight = SkScalarFloorToInt(srcRect.height());
177 desc.fConfig = srcTexture->config();
178
bsalomone3059732014-10-14 11:47:22 -0700179 GrTexture* dstTexture;
180 GrTexture* tempTexture;
181 SkAutoTUnref<GrTexture> temp1, temp2;
182
183 temp1.reset(context->refScratchTexture(desc, GrContext::kApprox_ScratchTexMatch));
184 dstTexture = temp1.get();
185 if (canClobberSrc) {
186 tempTexture = srcTexture;
187 } else {
188 temp2.reset(context->refScratchTexture(desc, GrContext::kApprox_ScratchTexMatch));
189 tempTexture = temp2.get();
190 }
191
robertphillips@google.com736dd032013-07-15 15:06:54 +0000192 if (NULL == dstTexture || NULL == tempTexture) {
193 return NULL;
194 }
195
196 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
197 GrPaint paint;
198 SkMatrix matrix;
199 matrix.setIDiv(srcTexture->width(), srcTexture->height());
robertphillips@google.com736dd032013-07-15 15:06:54 +0000200 SkRect dstRect(srcRect);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000201 if (cropToRect && i == 1) {
202 dstRect.offset(-dstRect.fLeft, -dstRect.fTop);
203 SkRect domain;
204 matrix.mapRect(&domain, rect);
205 domain.inset(i < scaleFactorX ? SK_ScalarHalf / srcTexture->width() : 0.0f,
206 i < scaleFactorY ? SK_ScalarHalf / srcTexture->height() : 0.0f);
bsalomon9f876a32014-12-09 10:51:07 -0800207 SkAutoTUnref<GrFragmentProcessor> fp( GrTextureDomainEffect::Create(
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000208 srcTexture,
209 matrix,
210 domain,
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000211 GrTextureDomain::kDecal_Mode,
humper@google.comb86add12013-07-25 18:49:07 +0000212 GrTextureParams::kBilerp_FilterMode));
joshualittb0a8a372014-09-23 09:50:21 -0700213 paint.addColorProcessor(fp);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000214 } else {
humper@google.comb86add12013-07-25 18:49:07 +0000215 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
joshualittb0a8a372014-09-23 09:50:21 -0700216 paint.addColorTextureProcessor(srcTexture, matrix, params);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000217 }
robertphillips@google.com736dd032013-07-15 15:06:54 +0000218 scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
219 i < scaleFactorY ? 0.5f : 1.0f);
joshualitt570d2f82015-02-25 13:19:48 -0800220 context->drawNonAARectToRect(dstTexture->asRenderTarget(), clip, paint, SkMatrix::I(),
221 dstRect, srcRect);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000222 srcRect = dstRect;
223 srcTexture = dstTexture;
224 SkTSwap(dstTexture, tempTexture);
225 }
226
reedb07a94f2014-11-19 05:03:18 -0800227 const SkIRect srcIRect = srcRect.roundOut();
robertphillips@google.com736dd032013-07-15 15:06:54 +0000228
joshualitt5acfea72014-08-11 13:55:34 -0700229 // For really small blurs(Certainly no wider than 5x5 on desktop gpus) it is faster to just
230 // launch a single non separable kernel vs two launches
231 if (sigmaX > 0.0f && sigmaY > 0 &&
232 (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
233 // We shouldn't be scaling because this is a small size blur
234 SkASSERT((scaleFactorX == scaleFactorY) == 1);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000235 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
joshualitt570d2f82015-02-25 13:19:48 -0800236 convolve_gaussian_2d(context, dstTexture->asRenderTarget(), clip, srcRect, dstRect,
237 srcTexture, radiusX, radiusY, sigmaX, sigmaY, cropToRect, srcIRect);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000238 srcTexture = dstTexture;
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000239 srcRect = dstRect;
robertphillips@google.com736dd032013-07-15 15:06:54 +0000240 SkTSwap(dstTexture, tempTexture);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000241
joshualitt5acfea72014-08-11 13:55:34 -0700242 } else {
243 if (sigmaX > 0.0f) {
244 if (scaleFactorX > 1) {
245 // Clear out a radius to the right of the srcRect to prevent the
246 // X convolution from reading garbage.
247 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
248 radiusX, srcIRect.height());
joshualitt25d9c152015-02-18 12:29:52 -0800249 context->clear(&clearRect, 0x0, false, srcTexture->asRenderTarget());
joshualitt5acfea72014-08-11 13:55:34 -0700250 }
joshualitt5acfea72014-08-11 13:55:34 -0700251 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
joshualitt570d2f82015-02-25 13:19:48 -0800252 convolve_gaussian(context, dstTexture->asRenderTarget(), clip, srcRect, dstRect,
253 srcTexture, Gr1DKernelEffect::kX_Direction, radiusX, sigmaX,
254 cropToRect);
joshualitt5acfea72014-08-11 13:55:34 -0700255 srcTexture = dstTexture;
256 srcRect = dstRect;
257 SkTSwap(dstTexture, tempTexture);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000258 }
259
joshualitt5acfea72014-08-11 13:55:34 -0700260 if (sigmaY > 0.0f) {
261 if (scaleFactorY > 1 || sigmaX > 0.0f) {
262 // Clear out a radius below the srcRect to prevent the Y
263 // convolution from reading garbage.
264 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
265 srcIRect.width(), radiusY);
joshualitt25d9c152015-02-18 12:29:52 -0800266 context->clear(&clearRect, 0x0, false, srcTexture->asRenderTarget());
joshualitt5acfea72014-08-11 13:55:34 -0700267 }
268
joshualitt5acfea72014-08-11 13:55:34 -0700269 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
joshualitt570d2f82015-02-25 13:19:48 -0800270 convolve_gaussian(context, dstTexture->asRenderTarget(), clip, srcRect,
271 dstRect, srcTexture, Gr1DKernelEffect::kY_Direction, radiusY, sigmaY,
272 cropToRect);
joshualitt5acfea72014-08-11 13:55:34 -0700273 srcTexture = dstTexture;
274 srcRect = dstRect;
275 SkTSwap(dstTexture, tempTexture);
276 }
robertphillips@google.com736dd032013-07-15 15:06:54 +0000277 }
278
279 if (scaleFactorX > 1 || scaleFactorY > 1) {
280 // Clear one pixel to the right and below, to accommodate bilinear
281 // upsampling.
282 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
283 srcIRect.width() + 1, 1);
joshualitt25d9c152015-02-18 12:29:52 -0800284 context->clear(&clearRect, 0x0, false, srcTexture->asRenderTarget());
robertphillips@google.com736dd032013-07-15 15:06:54 +0000285 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
286 1, srcIRect.height());
joshualitt25d9c152015-02-18 12:29:52 -0800287 context->clear(&clearRect, 0x0, false, srcTexture->asRenderTarget());
robertphillips@google.com736dd032013-07-15 15:06:54 +0000288 SkMatrix matrix;
289 matrix.setIDiv(srcTexture->width(), srcTexture->height());
robertphillips@google.com736dd032013-07-15 15:06:54 +0000290
291 GrPaint paint;
292 // FIXME: this should be mitchell, not bilinear.
humper@google.comb86add12013-07-25 18:49:07 +0000293 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
joshualittb0a8a372014-09-23 09:50:21 -0700294 paint.addColorTextureProcessor(srcTexture, matrix, params);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000295
296 SkRect dstRect(srcRect);
297 scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY);
joshualitt570d2f82015-02-25 13:19:48 -0800298 context->drawNonAARectToRect(dstTexture->asRenderTarget(), clip, paint,
299 SkMatrix::I(), dstRect, srcRect);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000300 srcRect = dstRect;
301 srcTexture = dstTexture;
302 SkTSwap(dstTexture, tempTexture);
303 }
bsalomone3059732014-10-14 11:47:22 -0700304 return SkRef(srcTexture);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000305}
306#endif
307
robertphillips@google.comcce41022013-07-15 15:47:10 +0000308}