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