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