blob: 975b7047cefc7daa819c9d950225d4ee2cbfb831 [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,
47 const SkRect& srcRect,
48 const SkRect& dstRect,
49 GrTexture* texture,
50 Gr1DKernelEffect::Direction direction,
51 int radius,
52 float sigma,
53 bool useBounds,
54 float bounds[2]) {
robertphillips@google.com736dd032013-07-15 15:06:54 +000055 GrPaint paint;
senorblanco@chromium.org194d7752013-07-24 22:19:24 +000056 paint.reset();
joshualittb0a8a372014-09-23 09:50:21 -070057 SkAutoTUnref<GrFragmentProcessor> conv(GrConvolutionEffect::CreateGaussian(
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +000058 texture, direction, radius, sigma, useBounds, bounds));
59 paint.reset();
joshualittb0a8a372014-09-23 09:50:21 -070060 paint.addColorProcessor(conv);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +000061 context->drawRectToRect(paint, 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,
65 const SkRect& srcRect,
66 const SkRect& dstRect,
67 GrTexture* texture,
68 int radiusX,
69 int radiusY,
70 SkScalar sigmaX,
71 SkScalar sigmaY,
72 bool useBounds,
73 SkIRect bounds) {
74 SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1);
75 SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
76 GrPaint paint;
77 paint.reset();
joshualittb0a8a372014-09-23 09:50:21 -070078 SkAutoTUnref<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::CreateGaussian(
joshualitt5acfea72014-08-11 13:55:34 -070079 texture, bounds, size, 1.0, 0.0, kernelOffset,
80 useBounds ? GrTextureDomain::kClamp_Mode : GrTextureDomain::kIgnore_Mode,
81 true, sigmaX, sigmaY));
82 paint.reset();
joshualittb0a8a372014-09-23 09:50:21 -070083 paint.addColorProcessor(conv);
joshualitt5acfea72014-08-11 13:55:34 -070084 context->drawRectToRect(paint, dstRect, srcRect);
85}
86
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +000087static void convolve_gaussian(GrContext* context,
88 const SkRect& srcRect,
89 const SkRect& dstRect,
90 GrTexture* texture,
91 Gr1DKernelEffect::Direction direction,
92 int radius,
93 float sigma,
94 bool cropToSrcRect) {
95 float bounds[2] = { 0.0f, 1.0f };
96 if (!cropToSrcRect) {
joshualitt5acfea72014-08-11 13:55:34 -070097 convolve_gaussian_1d(context, srcRect, dstRect, texture,
98 direction, radius, sigma, false, bounds);
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +000099 return;
100 }
101 SkRect lowerSrcRect = srcRect, lowerDstRect = dstRect;
102 SkRect middleSrcRect = srcRect, middleDstRect = dstRect;
103 SkRect upperSrcRect = srcRect, upperDstRect = dstRect;
104 SkScalar size;
105 SkScalar rad = SkIntToScalar(radius);
106 if (direction == Gr1DKernelEffect::kX_Direction) {
107 bounds[0] = SkScalarToFloat(srcRect.left()) / texture->width();
108 bounds[1] = SkScalarToFloat(srcRect.right()) / texture->width();
109 size = srcRect.width();
110 lowerSrcRect.fRight = srcRect.left() + rad;
111 lowerDstRect.fRight = dstRect.left() + rad;
112 upperSrcRect.fLeft = srcRect.right() - rad;
113 upperDstRect.fLeft = dstRect.right() - rad;
114 middleSrcRect.inset(rad, 0);
115 middleDstRect.inset(rad, 0);
116 } else {
117 bounds[0] = SkScalarToFloat(srcRect.top()) / texture->height();
118 bounds[1] = SkScalarToFloat(srcRect.bottom()) / texture->height();
119 size = srcRect.height();
120 lowerSrcRect.fBottom = srcRect.top() + rad;
121 lowerDstRect.fBottom = dstRect.top() + rad;
122 upperSrcRect.fTop = srcRect.bottom() - rad;
123 upperDstRect.fTop = dstRect.bottom() - rad;
124 middleSrcRect.inset(0, rad);
125 middleDstRect.inset(0, rad);
126 }
127 if (radius >= size * SK_ScalarHalf) {
128 // Blur radius covers srcRect; use bounds over entire draw
joshualitt5acfea72014-08-11 13:55:34 -0700129 convolve_gaussian_1d(context, srcRect, dstRect, texture,
130 direction, radius, sigma, true, bounds);
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +0000131 } else {
132 // Draw upper and lower margins with bounds; middle without.
joshualitt5acfea72014-08-11 13:55:34 -0700133 convolve_gaussian_1d(context, lowerSrcRect, lowerDstRect, texture,
134 direction, radius, sigma, true, bounds);
135 convolve_gaussian_1d(context, upperSrcRect, upperDstRect, texture,
136 direction, radius, sigma, true, bounds);
137 convolve_gaussian_1d(context, middleSrcRect, middleDstRect, texture,
138 direction, radius, sigma, false, bounds);
senorblanco@chromium.orgd71732a2013-07-30 21:11:05 +0000139 }
140}
141
robertphillips@google.com736dd032013-07-15 15:06:54 +0000142GrTexture* GaussianBlur(GrContext* context,
143 GrTexture* srcTexture,
144 bool canClobberSrc,
145 const SkRect& rect,
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000146 bool cropToRect,
skia.committer@gmail.com977409a2013-07-16 07:00:56 +0000147 float sigmaX,
robertphillips@google.com736dd032013-07-15 15:06:54 +0000148 float sigmaY) {
bsalomon49f085d2014-09-05 13:34:00 -0700149 SkASSERT(context);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000150
151 GrContext::AutoRenderTarget art(context);
152
153 GrContext::AutoMatrix am;
154 am.setIdentity(context);
155
156 SkIRect clearRect;
157 int scaleFactorX, radiusX;
158 int scaleFactorY, radiusY;
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +0000159 int maxTextureSize = context->getMaxTextureSize();
160 sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
161 sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000162
163 SkRect srcRect(rect);
164 scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
reedd02cf262014-11-18 18:06:45 -0800165 srcRect.roundOut(&srcRect);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000166 scale_rect(&srcRect, static_cast<float>(scaleFactorX),
167 static_cast<float>(scaleFactorY));
168
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000169 GrContext::AutoClip acs(context, SkRect::MakeWH(srcRect.width(), srcRect.height()));
robertphillips@google.com736dd032013-07-15 15:06:54 +0000170
commit-bot@chromium.org96ae6882013-08-14 12:09:00 +0000171 SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() ||
robertphillips@google.com736dd032013-07-15 15:06:54 +0000172 kRGBA_8888_GrPixelConfig == srcTexture->config() ||
173 kAlpha_8_GrPixelConfig == srcTexture->config());
174
bsalomonf2703d82014-10-28 14:33:06 -0700175 GrSurfaceDesc desc;
176 desc.fFlags = kRenderTarget_GrSurfaceFlag | kNoStencil_GrSurfaceFlag;
robertphillips@google.com736dd032013-07-15 15:06:54 +0000177 desc.fWidth = SkScalarFloorToInt(srcRect.width());
178 desc.fHeight = SkScalarFloorToInt(srcRect.height());
179 desc.fConfig = srcTexture->config();
180
bsalomone3059732014-10-14 11:47:22 -0700181 GrTexture* dstTexture;
182 GrTexture* tempTexture;
183 SkAutoTUnref<GrTexture> temp1, temp2;
184
185 temp1.reset(context->refScratchTexture(desc, GrContext::kApprox_ScratchTexMatch));
186 dstTexture = temp1.get();
187 if (canClobberSrc) {
188 tempTexture = srcTexture;
189 } else {
190 temp2.reset(context->refScratchTexture(desc, GrContext::kApprox_ScratchTexMatch));
191 tempTexture = temp2.get();
192 }
193
robertphillips@google.com736dd032013-07-15 15:06:54 +0000194 if (NULL == dstTexture || NULL == tempTexture) {
195 return NULL;
196 }
197
198 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
199 GrPaint paint;
200 SkMatrix matrix;
201 matrix.setIDiv(srcTexture->width(), srcTexture->height());
202 context->setRenderTarget(dstTexture->asRenderTarget());
203 SkRect dstRect(srcRect);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000204 if (cropToRect && i == 1) {
205 dstRect.offset(-dstRect.fLeft, -dstRect.fTop);
206 SkRect domain;
207 matrix.mapRect(&domain, rect);
208 domain.inset(i < scaleFactorX ? SK_ScalarHalf / srcTexture->width() : 0.0f,
209 i < scaleFactorY ? SK_ScalarHalf / srcTexture->height() : 0.0f);
joshualittb0a8a372014-09-23 09:50:21 -0700210 SkAutoTUnref<GrFragmentProcessor> fp(GrTextureDomainEffect::Create(
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000211 srcTexture,
212 matrix,
213 domain,
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000214 GrTextureDomain::kDecal_Mode,
humper@google.comb86add12013-07-25 18:49:07 +0000215 GrTextureParams::kBilerp_FilterMode));
joshualittb0a8a372014-09-23 09:50:21 -0700216 paint.addColorProcessor(fp);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000217 } else {
humper@google.comb86add12013-07-25 18:49:07 +0000218 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
joshualittb0a8a372014-09-23 09:50:21 -0700219 paint.addColorTextureProcessor(srcTexture, matrix, params);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000220 }
robertphillips@google.com736dd032013-07-15 15:06:54 +0000221 scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
222 i < scaleFactorY ? 0.5f : 1.0f);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000223 context->drawRectToRect(paint, dstRect, srcRect);
224 srcRect = dstRect;
225 srcTexture = dstTexture;
226 SkTSwap(dstTexture, tempTexture);
227 }
228
229 SkIRect srcIRect;
230 srcRect.roundOut(&srcIRect);
231
joshualitt5acfea72014-08-11 13:55:34 -0700232 // For really small blurs(Certainly no wider than 5x5 on desktop gpus) it is faster to just
233 // launch a single non separable kernel vs two launches
234 if (sigmaX > 0.0f && sigmaY > 0 &&
235 (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
236 // We shouldn't be scaling because this is a small size blur
237 SkASSERT((scaleFactorX == scaleFactorY) == 1);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000238 context->setRenderTarget(dstTexture->asRenderTarget());
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000239 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
joshualitt5acfea72014-08-11 13:55:34 -0700240 convolve_gaussian_2d(context, srcRect, dstRect, srcTexture,
241 radiusX, radiusY, sigmaX, sigmaY, cropToRect, srcIRect);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000242 srcTexture = dstTexture;
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000243 srcRect = dstRect;
robertphillips@google.com736dd032013-07-15 15:06:54 +0000244 SkTSwap(dstTexture, tempTexture);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000245
joshualitt5acfea72014-08-11 13:55:34 -0700246 } else {
247 if (sigmaX > 0.0f) {
248 if (scaleFactorX > 1) {
249 // Clear out a radius to the right of the srcRect to prevent the
250 // X convolution from reading garbage.
251 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
252 radiusX, srcIRect.height());
bsalomon89c62982014-11-03 12:08:42 -0800253 context->clear(&clearRect, 0x0, false, context->getRenderTarget());
joshualitt5acfea72014-08-11 13:55:34 -0700254 }
255 context->setRenderTarget(dstTexture->asRenderTarget());
256 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
257 convolve_gaussian(context, srcRect, dstRect, srcTexture,
258 Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect);
259 srcTexture = dstTexture;
260 srcRect = dstRect;
261 SkTSwap(dstTexture, tempTexture);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000262 }
263
joshualitt5acfea72014-08-11 13:55:34 -0700264 if (sigmaY > 0.0f) {
265 if (scaleFactorY > 1 || sigmaX > 0.0f) {
266 // Clear out a radius below the srcRect to prevent the Y
267 // convolution from reading garbage.
268 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
269 srcIRect.width(), radiusY);
bsalomon89c62982014-11-03 12:08:42 -0800270 context->clear(&clearRect, 0x0, false, context->getRenderTarget());
joshualitt5acfea72014-08-11 13:55:34 -0700271 }
272
273 context->setRenderTarget(dstTexture->asRenderTarget());
274 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
275 convolve_gaussian(context, srcRect, dstRect, srcTexture,
276 Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect);
277 srcTexture = dstTexture;
278 srcRect = dstRect;
279 SkTSwap(dstTexture, tempTexture);
280 }
robertphillips@google.com736dd032013-07-15 15:06:54 +0000281 }
282
283 if (scaleFactorX > 1 || scaleFactorY > 1) {
284 // Clear one pixel to the right and below, to accommodate bilinear
285 // upsampling.
286 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
287 srcIRect.width() + 1, 1);
bsalomon89c62982014-11-03 12:08:42 -0800288 context->clear(&clearRect, 0x0, false, context->getRenderTarget());
robertphillips@google.com736dd032013-07-15 15:06:54 +0000289 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
290 1, srcIRect.height());
bsalomon89c62982014-11-03 12:08:42 -0800291 context->clear(&clearRect, 0x0, false, context->getRenderTarget());
robertphillips@google.com736dd032013-07-15 15:06:54 +0000292 SkMatrix matrix;
293 matrix.setIDiv(srcTexture->width(), srcTexture->height());
294 context->setRenderTarget(dstTexture->asRenderTarget());
295
296 GrPaint paint;
297 // FIXME: this should be mitchell, not bilinear.
humper@google.comb86add12013-07-25 18:49:07 +0000298 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
joshualittb0a8a372014-09-23 09:50:21 -0700299 paint.addColorTextureProcessor(srcTexture, matrix, params);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000300
301 SkRect dstRect(srcRect);
302 scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY);
303 context->drawRectToRect(paint, dstRect, srcRect);
304 srcRect = dstRect;
305 srcTexture = dstTexture;
306 SkTSwap(dstTexture, tempTexture);
307 }
bsalomone3059732014-10-14 11:47:22 -0700308 return SkRef(srcTexture);
robertphillips@google.com736dd032013-07-15 15:06:54 +0000309}
310#endif
311
robertphillips@google.comcce41022013-07-15 15:47:10 +0000312}