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 #include "GrCaps.h"
17 #include "GrDrawContext.h"
18 #endif
19
20 namespace SkGpuBlurUtils {
21
22 #if SK_SUPPORT_GPU
23
24 #define MAX_BLUR_SIGMA 4.0f
25
scale_rect(SkRect * rect,float xScale,float yScale)26 static void scale_rect(SkRect* rect, float xScale, float yScale) {
27 rect->fLeft = SkScalarMul(rect->fLeft, xScale);
28 rect->fTop = SkScalarMul(rect->fTop, yScale);
29 rect->fRight = SkScalarMul(rect->fRight, xScale);
30 rect->fBottom = SkScalarMul(rect->fBottom, yScale);
31 }
32
adjust_sigma(float sigma,int maxTextureSize,int * scaleFactor,int * radius)33 static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) {
34 *scaleFactor = 1;
35 while (sigma > MAX_BLUR_SIGMA) {
36 *scaleFactor *= 2;
37 sigma *= 0.5f;
38 if (*scaleFactor > maxTextureSize) {
39 *scaleFactor = maxTextureSize;
40 sigma = MAX_BLUR_SIGMA;
41 }
42 }
43 *radius = static_cast<int>(ceilf(sigma * 3.0f));
44 SkASSERT(*radius <= GrConvolutionEffect::kMaxKernelRadius);
45 return sigma;
46 }
47
convolve_gaussian_1d(GrDrawContext * drawContext,const GrClip & clip,const SkRect & dstRect,const SkPoint & srcOffset,GrTexture * texture,Gr1DKernelEffect::Direction direction,int radius,float sigma,bool useBounds,float bounds[2])48 static void convolve_gaussian_1d(GrDrawContext* drawContext,
49 const GrClip& clip,
50 const SkRect& dstRect,
51 const SkPoint& srcOffset,
52 GrTexture* texture,
53 Gr1DKernelEffect::Direction direction,
54 int radius,
55 float sigma,
56 bool useBounds,
57 float bounds[2]) {
58 GrPaint paint;
59 SkAutoTUnref<GrFragmentProcessor> conv(GrConvolutionEffect::CreateGaussian(
60 texture, direction, radius, sigma, useBounds, bounds));
61 paint.addColorFragmentProcessor(conv);
62 paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode);
63 SkMatrix localMatrix = SkMatrix::MakeTrans(-srcOffset.x(), -srcOffset.y());
64 drawContext->fillRectWithLocalMatrix(clip, paint, SkMatrix::I(), dstRect, localMatrix);
65 }
66
convolve_gaussian_2d(GrDrawContext * drawContext,const GrClip & clip,const SkRect & dstRect,const SkPoint & srcOffset,GrTexture * texture,int radiusX,int radiusY,SkScalar sigmaX,SkScalar sigmaY,const SkRect * srcBounds)67 static void convolve_gaussian_2d(GrDrawContext* drawContext,
68 const GrClip& clip,
69 const SkRect& dstRect,
70 const SkPoint& srcOffset,
71 GrTexture* texture,
72 int radiusX,
73 int radiusY,
74 SkScalar sigmaX,
75 SkScalar sigmaY,
76 const SkRect* srcBounds) {
77 SkMatrix localMatrix = SkMatrix::MakeTrans(-srcOffset.x(), -srcOffset.y());
78 SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1);
79 SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
80 GrPaint paint;
81 SkIRect bounds;
82 if (srcBounds) {
83 srcBounds->roundOut(&bounds);
84 } else {
85 bounds.setEmpty();
86 }
87
88 SkAutoTUnref<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::CreateGaussian(
89 texture, bounds, size, 1.0, 0.0, kernelOffset,
90 srcBounds ? GrTextureDomain::kDecal_Mode : GrTextureDomain::kIgnore_Mode,
91 true, sigmaX, sigmaY));
92 paint.addColorFragmentProcessor(conv);
93 paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode);
94 drawContext->fillRectWithLocalMatrix(clip, paint, SkMatrix::I(), dstRect, localMatrix);
95 }
96
convolve_gaussian(GrDrawContext * drawContext,const GrClip & clip,const SkRect & srcRect,GrTexture * texture,Gr1DKernelEffect::Direction direction,int radius,float sigma,const SkRect * srcBounds,const SkPoint & srcOffset)97 static void convolve_gaussian(GrDrawContext* drawContext,
98 const GrClip& clip,
99 const SkRect& srcRect,
100 GrTexture* texture,
101 Gr1DKernelEffect::Direction direction,
102 int radius,
103 float sigma,
104 const SkRect* srcBounds,
105 const SkPoint& srcOffset) {
106 float bounds[2] = { 0.0f, 1.0f };
107 SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
108 if (!srcBounds) {
109 convolve_gaussian_1d(drawContext, clip, dstRect, srcOffset, texture,
110 direction, radius, sigma, false, bounds);
111 return;
112 }
113 SkRect midRect = *srcBounds, leftRect, rightRect;
114 midRect.offset(srcOffset);
115 SkIRect topRect, bottomRect;
116 SkScalar rad = SkIntToScalar(radius);
117 if (direction == Gr1DKernelEffect::kX_Direction) {
118 bounds[0] = SkScalarToFloat(srcBounds->left()) / texture->width();
119 bounds[1] = SkScalarToFloat(srcBounds->right()) / texture->width();
120 SkRect::MakeLTRB(0, 0, dstRect.right(), midRect.top()).roundOut(&topRect);
121 SkRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom())
122 .roundOut(&bottomRect);
123 midRect.inset(rad, 0);
124 leftRect = SkRect::MakeLTRB(0, midRect.top(), midRect.left(), midRect.bottom());
125 rightRect =
126 SkRect::MakeLTRB(midRect.right(), midRect.top(), dstRect.width(), midRect.bottom());
127 dstRect.fTop = midRect.top();
128 dstRect.fBottom = midRect.bottom();
129 } else {
130 bounds[0] = SkScalarToFloat(srcBounds->top()) / texture->height();
131 bounds[1] = SkScalarToFloat(srcBounds->bottom()) / texture->height();
132 SkRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom()).roundOut(&topRect);
133 SkRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom())
134 .roundOut(&bottomRect);;
135 midRect.inset(0, rad);
136 leftRect = SkRect::MakeLTRB(midRect.left(), 0, midRect.right(), midRect.top());
137 rightRect =
138 SkRect::MakeLTRB(midRect.left(), midRect.bottom(), midRect.right(), dstRect.height());
139 dstRect.fLeft = midRect.left();
140 dstRect.fRight = midRect.right();
141 }
142 if (!topRect.isEmpty()) {
143 drawContext->clear(&topRect, 0, false);
144 }
145
146 if (!bottomRect.isEmpty()) {
147 drawContext->clear(&bottomRect, 0, false);
148 }
149 if (midRect.isEmpty()) {
150 // Blur radius covers srcBounds; use bounds over entire draw
151 convolve_gaussian_1d(drawContext, clip, dstRect, srcOffset, texture,
152 direction, radius, sigma, true, bounds);
153 } else {
154 // Draw right and left margins with bounds; middle without.
155 convolve_gaussian_1d(drawContext, clip, leftRect, srcOffset, texture,
156 direction, radius, sigma, true, bounds);
157 convolve_gaussian_1d(drawContext, clip, rightRect, srcOffset, texture,
158 direction, radius, sigma, true, bounds);
159 convolve_gaussian_1d(drawContext, clip, midRect, srcOffset, texture,
160 direction, radius, sigma, false, bounds);
161 }
162 }
163
GaussianBlur(GrContext * context,GrTexture * srcTexture,bool canClobberSrc,const SkRect & dstBounds,const SkRect * srcBounds,float sigmaX,float sigmaY)164 GrTexture* GaussianBlur(GrContext* context,
165 GrTexture* srcTexture,
166 bool canClobberSrc,
167 const SkRect& dstBounds,
168 const SkRect* srcBounds,
169 float sigmaX,
170 float sigmaY) {
171 SkASSERT(context);
172 SkIRect clearRect;
173 int scaleFactorX, radiusX;
174 int scaleFactorY, radiusY;
175 int maxTextureSize = context->caps()->maxTextureSize();
176 sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
177 sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
178
179 SkPoint srcOffset = SkPoint::Make(-dstBounds.x(), -dstBounds.y());
180 SkRect localDstBounds = SkRect::MakeWH(dstBounds.width(), dstBounds.height());
181 SkRect localSrcBounds;
182 SkRect srcRect;
183 if (srcBounds) {
184 srcRect = localSrcBounds = *srcBounds;
185 srcRect.offset(srcOffset);
186 srcBounds = &localSrcBounds;
187 } else {
188 srcRect = localDstBounds;
189 }
190
191 scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
192 srcRect.roundOut(&srcRect);
193 scale_rect(&srcRect, static_cast<float>(scaleFactorX),
194 static_cast<float>(scaleFactorY));
195
196 // setup new clip
197 GrClip clip(localDstBounds);
198
199 SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() ||
200 kRGBA_8888_GrPixelConfig == srcTexture->config() ||
201 kAlpha_8_GrPixelConfig == srcTexture->config());
202
203 GrSurfaceDesc desc;
204 desc.fFlags = kRenderTarget_GrSurfaceFlag;
205 desc.fWidth = SkScalarFloorToInt(dstBounds.width());
206 desc.fHeight = SkScalarFloorToInt(dstBounds.height());
207 desc.fConfig = srcTexture->config();
208
209 GrTexture* dstTexture;
210 GrTexture* tempTexture;
211 SkAutoTUnref<GrTexture> temp1, temp2;
212
213 temp1.reset(context->textureProvider()->createApproxTexture(desc));
214 dstTexture = temp1.get();
215 if (canClobberSrc) {
216 tempTexture = srcTexture;
217 } else {
218 temp2.reset(context->textureProvider()->createApproxTexture(desc));
219 tempTexture = temp2.get();
220 }
221
222 if (nullptr == dstTexture || nullptr == tempTexture) {
223 return nullptr;
224 }
225
226 SkAutoTUnref<GrDrawContext> srcDrawContext;
227
228 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
229 GrPaint paint;
230 SkMatrix matrix;
231 matrix.setIDiv(srcTexture->width(), srcTexture->height());
232 SkRect dstRect(srcRect);
233 if (srcBounds && i == 1) {
234 SkRect domain;
235 matrix.mapRect(&domain, *srcBounds);
236 domain.inset((i < scaleFactorX) ? SK_ScalarHalf / srcTexture->width() : 0.0f,
237 (i < scaleFactorY) ? SK_ScalarHalf / srcTexture->height() : 0.0f);
238 SkAutoTUnref<const GrFragmentProcessor> fp(GrTextureDomainEffect::Create(
239 srcTexture,
240 matrix,
241 domain,
242 GrTextureDomain::kDecal_Mode,
243 GrTextureParams::kBilerp_FilterMode));
244 paint.addColorFragmentProcessor(fp);
245 srcRect.offset(-srcOffset);
246 srcOffset.set(0, 0);
247 } else {
248 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
249 paint.addColorTextureProcessor(srcTexture, matrix, params);
250 }
251 paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode);
252 scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
253 i < scaleFactorY ? 0.5f : 1.0f);
254
255 SkAutoTUnref<GrDrawContext> dstDrawContext(
256 context->drawContext(dstTexture->asRenderTarget()));
257 if (!dstDrawContext) {
258 return nullptr;
259 }
260 dstDrawContext->fillRectToRect(clip, paint, SkMatrix::I(), dstRect, srcRect);
261
262 srcDrawContext.swap(dstDrawContext);
263 srcRect = dstRect;
264 srcTexture = dstTexture;
265 SkTSwap(dstTexture, tempTexture);
266 localSrcBounds = srcRect;
267 }
268
269 // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just
270 // launch a single non separable kernel vs two launches
271 srcRect = localDstBounds;
272 if (sigmaX > 0.0f && sigmaY > 0.0f &&
273 (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
274 // We shouldn't be scaling because this is a small size blur
275 SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY));
276
277 SkAutoTUnref<GrDrawContext> dstDrawContext(
278 context->drawContext(dstTexture->asRenderTarget()));
279 if (!dstDrawContext) {
280 return nullptr;
281 }
282 convolve_gaussian_2d(dstDrawContext, clip, srcRect, srcOffset,
283 srcTexture, radiusX, radiusY, sigmaX, sigmaY, srcBounds);
284
285 srcDrawContext.swap(dstDrawContext);
286 srcRect.offsetTo(0, 0);
287 srcTexture = dstTexture;
288 SkTSwap(dstTexture, tempTexture);
289
290 } else {
291 scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
292 srcRect.roundOut(&srcRect);
293 const SkIRect srcIRect = srcRect.roundOut();
294 if (sigmaX > 0.0f) {
295 if (scaleFactorX > 1) {
296 // TODO: if we pass in the source draw context we don't need this here
297 if (!srcDrawContext) {
298 srcDrawContext.reset(context->drawContext(srcTexture->asRenderTarget()));
299 if (!srcDrawContext) {
300 return nullptr;
301 }
302 }
303
304 // Clear out a radius to the right of the srcRect to prevent the
305 // X convolution from reading garbage.
306 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
307 radiusX, srcIRect.height());
308 srcDrawContext->clear(&clearRect, 0x0, false);
309 }
310
311 SkAutoTUnref<GrDrawContext> dstDrawContext(
312 context->drawContext(dstTexture->asRenderTarget()));
313 if (!dstDrawContext) {
314 return nullptr;
315 }
316 convolve_gaussian(dstDrawContext, clip, srcRect,
317 srcTexture, Gr1DKernelEffect::kX_Direction, radiusX, sigmaX,
318 srcBounds, srcOffset);
319 srcDrawContext.swap(dstDrawContext);
320 srcTexture = dstTexture;
321 srcRect.offsetTo(0, 0);
322 SkTSwap(dstTexture, tempTexture);
323 localSrcBounds = srcRect;
324 srcOffset.set(0, 0);
325 }
326
327 if (sigmaY > 0.0f) {
328 if (scaleFactorY > 1 || sigmaX > 0.0f) {
329 // TODO: if we pass in the source draw context we don't need this here
330 if (!srcDrawContext) {
331 srcDrawContext.reset(context->drawContext(srcTexture->asRenderTarget()));
332 if (!srcDrawContext) {
333 return nullptr;
334 }
335 }
336
337 // Clear out a radius below the srcRect to prevent the Y
338 // convolution from reading garbage.
339 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
340 srcIRect.width(), radiusY);
341 srcDrawContext->clear(&clearRect, 0x0, false);
342 }
343
344 SkAutoTUnref<GrDrawContext> dstDrawContext(
345 context->drawContext(dstTexture->asRenderTarget()));
346 if (!dstDrawContext) {
347 return nullptr;
348 }
349 convolve_gaussian(dstDrawContext, clip, srcRect,
350 srcTexture, Gr1DKernelEffect::kY_Direction, radiusY, sigmaY,
351 srcBounds, srcOffset);
352
353 srcDrawContext.swap(dstDrawContext);
354 srcTexture = dstTexture;
355 srcRect.offsetTo(0, 0);
356 SkTSwap(dstTexture, tempTexture);
357 }
358 }
359 const SkIRect srcIRect = srcRect.roundOut();
360
361 if (scaleFactorX > 1 || scaleFactorY > 1) {
362 SkASSERT(srcDrawContext);
363
364 // Clear one pixel to the right and below, to accommodate bilinear
365 // upsampling.
366 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
367 srcIRect.width() + 1, 1);
368 srcDrawContext->clear(&clearRect, 0x0, false);
369 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
370 1, srcIRect.height());
371 srcDrawContext->clear(&clearRect, 0x0, false);
372 SkMatrix matrix;
373 matrix.setIDiv(srcTexture->width(), srcTexture->height());
374
375 GrPaint paint;
376 // FIXME: this should be mitchell, not bilinear.
377 GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
378 paint.addColorTextureProcessor(srcTexture, matrix, params);
379 paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode);
380
381 SkRect dstRect(srcRect);
382 scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY);
383
384 SkAutoTUnref<GrDrawContext> dstDrawContext(
385 context->drawContext(dstTexture->asRenderTarget()));
386 if (!dstDrawContext) {
387 return nullptr;
388 }
389 dstDrawContext->fillRectToRect(clip, paint, SkMatrix::I(), dstRect, srcRect);
390
391 srcDrawContext.swap(dstDrawContext);
392 srcRect = dstRect;
393 srcTexture = dstTexture;
394 SkTSwap(dstTexture, tempTexture);
395 }
396
397 return SkRef(srcTexture);
398 }
399 #endif
400
401 }
402