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 "effects/GrConvolutionEffect.h"
14 #include "effects/GrMatrixConvolutionEffect.h"
15 #include "GrContext.h"
16 #endif
17
18 namespace SkGpuBlurUtils {
19
20 #if SK_SUPPORT_GPU
21
22 #define MAX_BLUR_SIGMA 4.0f
23
scale_rect(SkRect * rect,float xScale,float yScale)24 static void scale_rect(SkRect* rect, float xScale, float yScale) {
25 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);
29 }
30
adjust_sigma(float sigma,int maxTextureSize,int * scaleFactor,int * radius)31 static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) {
32 *scaleFactor = 1;
33 while (sigma > MAX_BLUR_SIGMA) {
34 *scaleFactor *= 2;
35 sigma *= 0.5f;
36 if (*scaleFactor > maxTextureSize) {
37 *scaleFactor = maxTextureSize;
38 sigma = MAX_BLUR_SIGMA;
39 }
40 }
41 *radius = static_cast<int>(ceilf(sigma * 3.0f));
42 SkASSERT(*radius <= GrConvolutionEffect::kMaxKernelRadius);
43 return sigma;
44 }
45
convolve_gaussian_1d(GrContext * context,const SkRect & srcRect,const SkRect & dstRect,GrTexture * texture,Gr1DKernelEffect::Direction direction,int radius,float sigma,bool useBounds,float bounds[2])46 static 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]) {
55 GrPaint paint;
56 paint.reset();
57 SkAutoTUnref<GrFragmentProcessor> conv(GrConvolutionEffect::CreateGaussian(
58 texture, direction, radius, sigma, useBounds, bounds));
59 paint.reset();
60 paint.addColorProcessor(conv);
61 context->drawRectToRect(paint, dstRect, srcRect);
62 }
63
convolve_gaussian_2d(GrContext * context,const SkRect & srcRect,const SkRect & dstRect,GrTexture * texture,int radiusX,int radiusY,SkScalar sigmaX,SkScalar sigmaY,bool useBounds,SkIRect bounds)64 static 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();
78 SkAutoTUnref<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::CreateGaussian(
79 texture, bounds, size, 1.0, 0.0, kernelOffset,
80 useBounds ? GrTextureDomain::kClamp_Mode : GrTextureDomain::kIgnore_Mode,
81 true, sigmaX, sigmaY));
82 paint.reset();
83 paint.addColorProcessor(conv);
84 context->drawRectToRect(paint, dstRect, srcRect);
85 }
86
convolve_gaussian(GrContext * context,const SkRect & srcRect,const SkRect & dstRect,GrTexture * texture,Gr1DKernelEffect::Direction direction,int radius,float sigma,bool cropToSrcRect)87 static 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) {
97 convolve_gaussian_1d(context, srcRect, dstRect, texture,
98 direction, radius, sigma, false, bounds);
99 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
129 convolve_gaussian_1d(context, srcRect, dstRect, texture,
130 direction, radius, sigma, true, bounds);
131 } else {
132 // Draw upper and lower margins with bounds; middle without.
133 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);
139 }
140 }
141
GaussianBlur(GrContext * context,GrTexture * srcTexture,bool canClobberSrc,const SkRect & rect,bool cropToRect,float sigmaX,float sigmaY)142 GrTexture* GaussianBlur(GrContext* context,
143 GrTexture* srcTexture,
144 bool canClobberSrc,
145 const SkRect& rect,
146 bool cropToRect,
147 float sigmaX,
148 float sigmaY) {
149 SkASSERT(context);
150
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;
159 int maxTextureSize = context->getMaxTextureSize();
160 sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
161 sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
162
163 SkRect srcRect(rect);
164 scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
165 srcRect.roundOut();
166 scale_rect(&srcRect, static_cast<float>(scaleFactorX),
167 static_cast<float>(scaleFactorY));
168
169 GrContext::AutoClip acs(context, SkRect::MakeWH(srcRect.width(), srcRect.height()));
170
171 SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() ||
172 kRGBA_8888_GrPixelConfig == srcTexture->config() ||
173 kAlpha_8_GrPixelConfig == srcTexture->config());
174
175 GrTextureDesc desc;
176 desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
177 desc.fWidth = SkScalarFloorToInt(srcRect.width());
178 desc.fHeight = SkScalarFloorToInt(srcRect.height());
179 desc.fConfig = srcTexture->config();
180
181 GrAutoScratchTexture temp1, temp2;
182 GrTexture* dstTexture = temp1.set(context, desc);
183 GrTexture* tempTexture = canClobberSrc ? srcTexture : temp2.set(context, desc);
184 if (NULL == dstTexture || NULL == tempTexture) {
185 return NULL;
186 }
187
188 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
189 GrPaint paint;
190 SkMatrix matrix;
191 matrix.setIDiv(srcTexture->width(), srcTexture->height());
192 context->setRenderTarget(dstTexture->asRenderTarget());
193 SkRect dstRect(srcRect);
194 if (cropToRect && i == 1) {
195 dstRect.offset(-dstRect.fLeft, -dstRect.fTop);
196 SkRect domain;
197 matrix.mapRect(&domain, rect);
198 domain.inset(i < scaleFactorX ? SK_ScalarHalf / srcTexture->width() : 0.0f,
199 i < scaleFactorY ? SK_ScalarHalf / srcTexture->height() : 0.0f);
200 SkAutoTUnref<GrFragmentProcessor> fp(GrTextureDomainEffect::Create(
201 srcTexture,
202 matrix,
203 domain,
204 GrTextureDomain::kDecal_Mode,
205 GrTextureParams::kBilerp_FilterMode));
206 paint.addColorProcessor(fp);
207 } else {
208 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
209 paint.addColorTextureProcessor(srcTexture, matrix, params);
210 }
211 scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
212 i < scaleFactorY ? 0.5f : 1.0f);
213 context->drawRectToRect(paint, dstRect, srcRect);
214 srcRect = dstRect;
215 srcTexture = dstTexture;
216 SkTSwap(dstTexture, tempTexture);
217 }
218
219 SkIRect srcIRect;
220 srcRect.roundOut(&srcIRect);
221
222 // For really small blurs(Certainly no wider than 5x5 on desktop gpus) it is faster to just
223 // launch a single non separable kernel vs two launches
224 if (sigmaX > 0.0f && sigmaY > 0 &&
225 (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
226 // We shouldn't be scaling because this is a small size blur
227 SkASSERT((scaleFactorX == scaleFactorY) == 1);
228 context->setRenderTarget(dstTexture->asRenderTarget());
229 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
230 convolve_gaussian_2d(context, srcRect, dstRect, srcTexture,
231 radiusX, radiusY, sigmaX, sigmaY, cropToRect, srcIRect);
232 srcTexture = dstTexture;
233 srcRect = dstRect;
234 SkTSwap(dstTexture, tempTexture);
235
236 } else {
237 if (sigmaX > 0.0f) {
238 if (scaleFactorX > 1) {
239 // Clear out a radius to the right of the srcRect to prevent the
240 // X convolution from reading garbage.
241 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
242 radiusX, srcIRect.height());
243 context->clear(&clearRect, 0x0, false);
244 }
245 context->setRenderTarget(dstTexture->asRenderTarget());
246 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
247 convolve_gaussian(context, srcRect, dstRect, srcTexture,
248 Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect);
249 srcTexture = dstTexture;
250 srcRect = dstRect;
251 SkTSwap(dstTexture, tempTexture);
252 }
253
254 if (sigmaY > 0.0f) {
255 if (scaleFactorY > 1 || sigmaX > 0.0f) {
256 // Clear out a radius below the srcRect to prevent the Y
257 // convolution from reading garbage.
258 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
259 srcIRect.width(), radiusY);
260 context->clear(&clearRect, 0x0, false);
261 }
262
263 context->setRenderTarget(dstTexture->asRenderTarget());
264 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
265 convolve_gaussian(context, srcRect, dstRect, srcTexture,
266 Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect);
267 srcTexture = dstTexture;
268 srcRect = dstRect;
269 SkTSwap(dstTexture, tempTexture);
270 }
271 }
272
273 if (scaleFactorX > 1 || scaleFactorY > 1) {
274 // Clear one pixel to the right and below, to accommodate bilinear
275 // upsampling.
276 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
277 srcIRect.width() + 1, 1);
278 context->clear(&clearRect, 0x0, false);
279 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
280 1, srcIRect.height());
281 context->clear(&clearRect, 0x0, false);
282 SkMatrix matrix;
283 matrix.setIDiv(srcTexture->width(), srcTexture->height());
284 context->setRenderTarget(dstTexture->asRenderTarget());
285
286 GrPaint paint;
287 // FIXME: this should be mitchell, not bilinear.
288 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
289 paint.addColorTextureProcessor(srcTexture, matrix, params);
290
291 SkRect dstRect(srcRect);
292 scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY);
293 context->drawRectToRect(paint, dstRect, srcRect);
294 srcRect = dstRect;
295 srcTexture = dstTexture;
296 SkTSwap(dstTexture, tempTexture);
297 }
298 if (srcTexture == temp1.texture()) {
299 return temp1.detach();
300 } else if (srcTexture == temp2.texture()) {
301 return temp2.detach();
302 } else {
303 srcTexture->ref();
304 return srcTexture;
305 }
306 }
307 #endif
308
309 }
310