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,bool useBounds,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 bool useBounds,
78 int bounds[2]) {
79 GrPaint paint;
80 paint.setGammaCorrect(renderTargetContext->isGammaCorrect());
81
82 GrResourceProvider* resourceProvider = renderTargetContext->resourceProvider();
83
84 sk_sp<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make(
85 resourceProvider, std::move(proxy), direction, radius, sigma, useBounds, bounds));
86 paint.addColorFragmentProcessor(std::move(conv));
87 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
88 SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
89 -SkIntToScalar(srcOffset.y()));
90 renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
91 SkRect::Make(dstRect), localMatrix);
92 }
93
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)94 static void convolve_gaussian_2d(GrRenderTargetContext* renderTargetContext,
95 const GrClip& clip,
96 const SkIRect& dstRect,
97 const SkIPoint& srcOffset,
98 sk_sp<GrTextureProxy> proxy,
99 int radiusX,
100 int radiusY,
101 SkScalar sigmaX,
102 SkScalar sigmaY,
103 const SkIRect* srcBounds) {
104 SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
105 -SkIntToScalar(srcOffset.y()));
106 SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1);
107 SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
108 GrPaint paint;
109 paint.setGammaCorrect(renderTargetContext->isGammaCorrect());
110 SkIRect bounds = srcBounds ? *srcBounds : SkIRect::EmptyIRect();
111
112 GrResourceProvider* resourceProvider = renderTargetContext->resourceProvider();
113
114 sk_sp<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::MakeGaussian(
115 resourceProvider, std::move(proxy), bounds, size, 1.0, 0.0, kernelOffset,
116 srcBounds ? GrTextureDomain::kDecal_Mode : GrTextureDomain::kIgnore_Mode,
117 true, sigmaX, sigmaY));
118 paint.addColorFragmentProcessor(std::move(conv));
119 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
120 renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
121 SkRect::Make(dstRect), localMatrix);
122 }
123
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)124 static void convolve_gaussian(GrRenderTargetContext* renderTargetContext,
125 const GrClip& clip,
126 const SkIRect& srcRect,
127 sk_sp<GrTextureProxy> proxy,
128 Gr1DKernelEffect::Direction direction,
129 int radius,
130 float sigma,
131 const SkIRect* srcBounds,
132 const SkIPoint& srcOffset) {
133 int bounds[2] = { 0, 0 };
134 SkIRect dstRect = SkIRect::MakeWH(srcRect.width(), srcRect.height());
135 if (!srcBounds) {
136 convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset,
137 std::move(proxy), direction, radius, sigma, false, bounds);
138 return;
139 }
140 SkIRect midRect = *srcBounds, leftRect, rightRect;
141 midRect.offset(srcOffset);
142 SkIRect topRect, bottomRect;
143 if (direction == Gr1DKernelEffect::kX_Direction) {
144 bounds[0] = srcBounds->left();
145 bounds[1] = srcBounds->right();
146 topRect = SkIRect::MakeLTRB(0, 0, dstRect.right(), midRect.top());
147 bottomRect = SkIRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom());
148 midRect.inset(radius, 0);
149 leftRect = SkIRect::MakeLTRB(0, midRect.top(), midRect.left(), midRect.bottom());
150 rightRect =
151 SkIRect::MakeLTRB(midRect.right(), midRect.top(), dstRect.width(), midRect.bottom());
152 dstRect.fTop = midRect.top();
153 dstRect.fBottom = midRect.bottom();
154 } else {
155 bounds[0] = srcBounds->top();
156 bounds[1] = srcBounds->bottom();
157 topRect = SkIRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom());
158 bottomRect = SkIRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom());
159 midRect.inset(0, radius);
160 leftRect = SkIRect::MakeLTRB(midRect.left(), 0, midRect.right(), midRect.top());
161 rightRect =
162 SkIRect::MakeLTRB(midRect.left(), midRect.bottom(), midRect.right(), dstRect.height());
163 dstRect.fLeft = midRect.left();
164 dstRect.fRight = midRect.right();
165 }
166 if (!topRect.isEmpty()) {
167 renderTargetContext->clear(&topRect, 0, false);
168 }
169
170 if (!bottomRect.isEmpty()) {
171 renderTargetContext->clear(&bottomRect, 0, false);
172 }
173 if (midRect.isEmpty()) {
174 // Blur radius covers srcBounds; use bounds over entire draw
175 convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset,
176 std::move(proxy), direction, radius, sigma, true, bounds);
177 } else {
178 // Draw right and left margins with bounds; middle without.
179 convolve_gaussian_1d(renderTargetContext, clip, leftRect, srcOffset,
180 proxy, direction, radius, sigma, true, bounds);
181 convolve_gaussian_1d(renderTargetContext, clip, rightRect, srcOffset,
182 proxy, direction, radius, sigma, true, bounds);
183 convolve_gaussian_1d(renderTargetContext, clip, midRect, srcOffset,
184 std::move(proxy), direction, radius, sigma, false, bounds);
185 }
186 }
187
188 namespace SkGpuBlurUtils {
189
GaussianBlur(GrContext * context,sk_sp<GrTextureProxy> srcProxy,sk_sp<SkColorSpace> colorSpace,const SkIRect & dstBounds,const SkIRect * srcBounds,float sigmaX,float sigmaY,SkBackingFit fit)190 sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context,
191 sk_sp<GrTextureProxy> srcProxy,
192 sk_sp<SkColorSpace> colorSpace,
193 const SkIRect& dstBounds,
194 const SkIRect* srcBounds,
195 float sigmaX,
196 float sigmaY,
197 SkBackingFit fit) {
198 SkASSERT(context);
199
200 {
201 // Chrome is crashing with proxies when they need to be instantiated.
202 // Force an instantiation here (where, in olden days, we used to require a GrTexture)
203 // to see if the input is already un-instantiable.
204 GrTexture* temp = srcProxy->instantiate(context->resourceProvider());
205 if (!temp) {
206 return nullptr;
207 }
208 }
209
210 SkIRect clearRect;
211 int scaleFactorX, radiusX;
212 int scaleFactorY, radiusY;
213 int maxTextureSize = context->caps()->maxTextureSize();
214 sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
215 sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
216 SkASSERT(sigmaX || sigmaY);
217
218 SkIPoint srcOffset = SkIPoint::Make(-dstBounds.x(), -dstBounds.y());
219 SkIRect localDstBounds = SkIRect::MakeWH(dstBounds.width(), dstBounds.height());
220 SkIRect localSrcBounds;
221 SkIRect srcRect;
222 if (srcBounds) {
223 srcRect = localSrcBounds = *srcBounds;
224 srcRect.offset(srcOffset);
225 srcBounds = &localSrcBounds;
226 } else {
227 srcRect = localDstBounds;
228 }
229
230 scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
231 scale_irect(&srcRect, scaleFactorX, scaleFactorY);
232
233 // setup new clip
234 GrFixedClip clip(localDstBounds);
235
236 const GrPixelConfig config = srcProxy->config();
237
238 SkASSERT(kBGRA_8888_GrPixelConfig == config || kRGBA_8888_GrPixelConfig == config ||
239 kSRGBA_8888_GrPixelConfig == config || kSBGRA_8888_GrPixelConfig == config ||
240 kRGBA_half_GrPixelConfig == config || kAlpha_8_GrPixelConfig == config);
241
242 const int width = dstBounds.width();
243 const int height = dstBounds.height();
244
245 sk_sp<GrRenderTargetContext> dstRenderTargetContext(context->makeDeferredRenderTargetContext(
246 fit, width, height, config, colorSpace, 0, kBottomLeft_GrSurfaceOrigin));
247 if (!dstRenderTargetContext) {
248 return nullptr;
249 }
250
251 // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just
252 // launch a single non separable kernel vs two launches
253 if (sigmaX > 0.0f && sigmaY > 0.0f &&
254 (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
255 // We shouldn't be scaling because this is a small size blur
256 SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY));
257
258 convolve_gaussian_2d(dstRenderTargetContext.get(), clip, localDstBounds, srcOffset,
259 std::move(srcProxy), radiusX, radiusY, sigmaX, sigmaY, srcBounds);
260
261 return dstRenderTargetContext;
262 }
263
264 sk_sp<GrRenderTargetContext> tmpRenderTargetContext(context->makeDeferredRenderTargetContext(
265 fit, width, height, config, colorSpace, 0, kBottomLeft_GrSurfaceOrigin));
266 if (!tmpRenderTargetContext) {
267 return nullptr;
268 }
269
270 sk_sp<GrRenderTargetContext> srcRenderTargetContext;
271
272 SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY));
273
274 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
275 GrPaint paint;
276 paint.setGammaCorrect(dstRenderTargetContext->isGammaCorrect());
277 SkIRect dstRect(srcRect);
278 if (srcBounds && i == 1) {
279 SkRect domain = SkRect::Make(*srcBounds);
280 domain.inset((i < scaleFactorX) ? SK_ScalarHalf : 0.0f,
281 (i < scaleFactorY) ? SK_ScalarHalf : 0.0f);
282 sk_sp<GrFragmentProcessor> fp(GrTextureDomainEffect::Make(
283 context->resourceProvider(),
284 std::move(srcProxy),
285 nullptr,
286 SkMatrix::I(),
287 domain,
288 GrTextureDomain::kDecal_Mode,
289 GrSamplerParams::kBilerp_FilterMode));
290 paint.addColorFragmentProcessor(std::move(fp));
291 srcRect.offset(-srcOffset);
292 srcOffset.set(0, 0);
293 } else {
294 GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
295 paint.addColorTextureProcessor(context->resourceProvider(), std::move(srcProxy),
296 nullptr, SkMatrix::I(), params);
297 }
298 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
299 shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY);
300
301 dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
302 SkRect::Make(dstRect), SkRect::Make(srcRect));
303
304 srcRenderTargetContext = dstRenderTargetContext;
305 srcRect = dstRect;
306 srcProxy = srcRenderTargetContext->asTextureProxyRef();
307 if (!srcProxy) {
308 return nullptr;
309 }
310 dstRenderTargetContext.swap(tmpRenderTargetContext);
311 localSrcBounds = srcRect;
312 }
313
314 srcRect = localDstBounds;
315 scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
316 if (sigmaX > 0.0f) {
317 if (scaleFactorX > 1) {
318 SkASSERT(srcRenderTargetContext);
319
320 // Clear out a radius to the right of the srcRect to prevent the
321 // X convolution from reading garbage.
322 clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop,
323 radiusX, srcRect.height());
324 srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
325 }
326
327 convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect,
328 std::move(srcProxy), Gr1DKernelEffect::kX_Direction, radiusX, sigmaX,
329 srcBounds, srcOffset);
330 srcRenderTargetContext = dstRenderTargetContext;
331 srcProxy = srcRenderTargetContext->asTextureProxyRef();
332 if (!srcProxy) {
333 return nullptr;
334 }
335 srcRect.offsetTo(0, 0);
336 dstRenderTargetContext.swap(tmpRenderTargetContext);
337 localSrcBounds = srcRect;
338 srcOffset.set(0, 0);
339 }
340
341 if (sigmaY > 0.0f) {
342 if (scaleFactorY > 1 || sigmaX > 0.0f) {
343 SkASSERT(srcRenderTargetContext);
344
345 // Clear out a radius below the srcRect to prevent the Y
346 // convolution from reading garbage.
347 clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom,
348 srcRect.width(), radiusY);
349 srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
350 }
351
352 convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect,
353 std::move(srcProxy), Gr1DKernelEffect::kY_Direction, radiusY, sigmaY,
354 srcBounds, srcOffset);
355
356 srcRenderTargetContext = dstRenderTargetContext;
357 srcRect.offsetTo(0, 0);
358 dstRenderTargetContext.swap(tmpRenderTargetContext);
359 }
360
361 SkASSERT(srcRenderTargetContext);
362 srcProxy.reset(nullptr); // we don't use this from here on out
363
364 if (scaleFactorX > 1 || scaleFactorY > 1) {
365 // Clear one pixel to the right and below, to accommodate bilinear upsampling.
366 // TODO: it seems like we should actually be clamping here rather than darkening
367 // the bottom right edges.
368 clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, srcRect.width() + 1, 1);
369 srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
370 clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop, 1, srcRect.height());
371 srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
372
373 GrPaint paint;
374 paint.setGammaCorrect(dstRenderTargetContext->isGammaCorrect());
375 // FIXME: this should be mitchell, not bilinear.
376 GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
377 sk_sp<GrTextureProxy> proxy(srcRenderTargetContext->asTextureProxyRef());
378 if (!proxy) {
379 return nullptr;
380 }
381
382 paint.addColorTextureProcessor(context->resourceProvider(), std::move(proxy),
383 nullptr, SkMatrix::I(), params);
384 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
385
386 SkIRect dstRect(srcRect);
387 scale_irect(&dstRect, scaleFactorX, scaleFactorY);
388
389 dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
390 SkRect::Make(dstRect), SkRect::Make(srcRect));
391
392 srcRenderTargetContext = dstRenderTargetContext;
393 srcRect = dstRect;
394 dstRenderTargetContext.swap(tmpRenderTargetContext);
395 }
396
397 return srcRenderTargetContext;
398 }
399
400 }
401
402 #endif
403
404