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,GrRenderTarget * rt,const GrClip & clip,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 GrRenderTarget* rt,
48 const GrClip& clip,
49 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]) {
57 GrPaint paint;
58 SkAutoTUnref<GrFragmentProcessor> conv(GrConvolutionEffect::CreateGaussian(
59 texture, direction, radius, sigma, useBounds, bounds));
60 paint.addColorProcessor(conv);
61 context->drawNonAARectToRect(rt, clip, paint, SkMatrix::I(), dstRect, srcRect);
62 }
63
convolve_gaussian_2d(GrContext * context,GrRenderTarget * rt,const GrClip & clip,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 GrRenderTarget* rt,
66 const GrClip& clip,
67 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;
79 SkAutoTUnref<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::CreateGaussian(
80 texture, bounds, size, 1.0, 0.0, kernelOffset,
81 useBounds ? GrTextureDomain::kClamp_Mode : GrTextureDomain::kIgnore_Mode,
82 true, sigmaX, sigmaY));
83 paint.addColorProcessor(conv);
84 context->drawNonAARectToRect(rt, clip, paint, SkMatrix::I(), dstRect, srcRect);
85 }
86
convolve_gaussian(GrContext * context,GrRenderTarget * rt,const GrClip & clip,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 GrRenderTarget* rt,
89 const GrClip& clip,
90 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) {
99 convolve_gaussian_1d(context, rt, clip, srcRect, dstRect, texture,
100 direction, radius, sigma, false, bounds);
101 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
131 convolve_gaussian_1d(context, rt, clip, srcRect, dstRect, texture,
132 direction, radius, sigma, true, bounds);
133 } else {
134 // Draw upper and lower margins with bounds; middle without.
135 convolve_gaussian_1d(context, rt, clip, lowerSrcRect, lowerDstRect, texture,
136 direction, radius, sigma, true, bounds);
137 convolve_gaussian_1d(context, rt, clip, upperSrcRect, upperDstRect, texture,
138 direction, radius, sigma, true, bounds);
139 convolve_gaussian_1d(context, rt, clip, middleSrcRect, middleDstRect, texture,
140 direction, radius, sigma, false, bounds);
141 }
142 }
143
GaussianBlur(GrContext * context,GrTexture * srcTexture,bool canClobberSrc,const SkRect & rect,bool cropToRect,float sigmaX,float sigmaY)144 GrTexture* GaussianBlur(GrContext* context,
145 GrTexture* srcTexture,
146 bool canClobberSrc,
147 const SkRect& rect,
148 bool cropToRect,
149 float sigmaX,
150 float sigmaY) {
151 SkASSERT(context);
152
153 SkIRect clearRect;
154 int scaleFactorX, radiusX;
155 int scaleFactorY, radiusY;
156 int maxTextureSize = context->getMaxTextureSize();
157 sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
158 sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
159
160 SkRect srcRect(rect);
161 scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
162 srcRect.roundOut(&srcRect);
163 scale_rect(&srcRect, static_cast<float>(scaleFactorX),
164 static_cast<float>(scaleFactorY));
165
166 // setup new clip
167 GrClip clip(SkRect::MakeWH(srcRect.width(), srcRect.height()));
168
169 SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() ||
170 kRGBA_8888_GrPixelConfig == srcTexture->config() ||
171 kAlpha_8_GrPixelConfig == srcTexture->config());
172
173 GrSurfaceDesc desc;
174 desc.fFlags = kRenderTarget_GrSurfaceFlag;
175 desc.fWidth = SkScalarFloorToInt(srcRect.width());
176 desc.fHeight = SkScalarFloorToInt(srcRect.height());
177 desc.fConfig = srcTexture->config();
178
179 GrTexture* dstTexture;
180 GrTexture* tempTexture;
181 SkAutoTUnref<GrTexture> temp1, temp2;
182
183 temp1.reset(context->textureProvider()->refScratchTexture(
184 desc, GrTextureProvider::kApprox_ScratchTexMatch));
185 dstTexture = temp1.get();
186 if (canClobberSrc) {
187 tempTexture = srcTexture;
188 } else {
189 temp2.reset(context->textureProvider()->refScratchTexture(
190 desc, GrTextureProvider::kApprox_ScratchTexMatch));
191 tempTexture = temp2.get();
192 }
193
194 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 SkRect dstRect(srcRect);
203 if (cropToRect && i == 1) {
204 dstRect.offset(-dstRect.fLeft, -dstRect.fTop);
205 SkRect domain;
206 matrix.mapRect(&domain, rect);
207 domain.inset(i < scaleFactorX ? SK_ScalarHalf / srcTexture->width() : 0.0f,
208 i < scaleFactorY ? SK_ScalarHalf / srcTexture->height() : 0.0f);
209 SkAutoTUnref<GrFragmentProcessor> fp( GrTextureDomainEffect::Create(
210 srcTexture,
211 matrix,
212 domain,
213 GrTextureDomain::kDecal_Mode,
214 GrTextureParams::kBilerp_FilterMode));
215 paint.addColorProcessor(fp);
216 } else {
217 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
218 paint.addColorTextureProcessor(srcTexture, matrix, params);
219 }
220 scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
221 i < scaleFactorY ? 0.5f : 1.0f);
222 context->drawNonAARectToRect(dstTexture->asRenderTarget(), clip, paint, SkMatrix::I(),
223 dstRect, srcRect);
224 srcRect = dstRect;
225 srcTexture = dstTexture;
226 SkTSwap(dstTexture, tempTexture);
227 }
228
229 const SkIRect srcIRect = srcRect.roundOut();
230
231 // For really small blurs(Certainly no wider than 5x5 on desktop gpus) it is faster to just
232 // launch a single non separable kernel vs two launches
233 if (sigmaX > 0.0f && sigmaY > 0 &&
234 (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
235 // We shouldn't be scaling because this is a small size blur
236 SkASSERT((scaleFactorX == scaleFactorY) == 1);
237 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
238 convolve_gaussian_2d(context, dstTexture->asRenderTarget(), clip, srcRect, dstRect,
239 srcTexture, radiusX, radiusY, sigmaX, sigmaY, cropToRect, srcIRect);
240 srcTexture = dstTexture;
241 srcRect = dstRect;
242 SkTSwap(dstTexture, tempTexture);
243
244 } else {
245 if (sigmaX > 0.0f) {
246 if (scaleFactorX > 1) {
247 // Clear out a radius to the right of the srcRect to prevent the
248 // X convolution from reading garbage.
249 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
250 radiusX, srcIRect.height());
251 context->clear(&clearRect, 0x0, false, srcTexture->asRenderTarget());
252 }
253 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
254 convolve_gaussian(context, dstTexture->asRenderTarget(), clip, srcRect, dstRect,
255 srcTexture, Gr1DKernelEffect::kX_Direction, radiusX, sigmaX,
256 cropToRect);
257 srcTexture = dstTexture;
258 srcRect = dstRect;
259 SkTSwap(dstTexture, tempTexture);
260 }
261
262 if (sigmaY > 0.0f) {
263 if (scaleFactorY > 1 || sigmaX > 0.0f) {
264 // Clear out a radius below the srcRect to prevent the Y
265 // convolution from reading garbage.
266 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
267 srcIRect.width(), radiusY);
268 context->clear(&clearRect, 0x0, false, srcTexture->asRenderTarget());
269 }
270
271 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
272 convolve_gaussian(context, dstTexture->asRenderTarget(), clip, srcRect,
273 dstRect, srcTexture, Gr1DKernelEffect::kY_Direction, radiusY, sigmaY,
274 cropToRect);
275 srcTexture = dstTexture;
276 srcRect = dstRect;
277 SkTSwap(dstTexture, tempTexture);
278 }
279 }
280
281 if (scaleFactorX > 1 || scaleFactorY > 1) {
282 // Clear one pixel to the right and below, to accommodate bilinear
283 // upsampling.
284 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
285 srcIRect.width() + 1, 1);
286 context->clear(&clearRect, 0x0, false, srcTexture->asRenderTarget());
287 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
288 1, srcIRect.height());
289 context->clear(&clearRect, 0x0, false, srcTexture->asRenderTarget());
290 SkMatrix matrix;
291 matrix.setIDiv(srcTexture->width(), srcTexture->height());
292
293 GrPaint paint;
294 // FIXME: this should be mitchell, not bilinear.
295 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
296 paint.addColorTextureProcessor(srcTexture, matrix, params);
297
298 SkRect dstRect(srcRect);
299 scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY);
300 context->drawNonAARectToRect(dstTexture->asRenderTarget(), clip, paint,
301 SkMatrix::I(), dstRect, srcRect);
302 srcRect = dstRect;
303 srcTexture = dstTexture;
304 SkTSwap(dstTexture, tempTexture);
305 }
306 return SkRef(srcTexture);
307 }
308 #endif
309
310 }
311