Line data Source code
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 :
23 0 : static void scale_irect_roundout(SkIRect* rect, float xScale, float yScale) {
24 0 : rect->fLeft = SkScalarFloorToInt(rect->fLeft * xScale);
25 0 : rect->fTop = SkScalarFloorToInt(rect->fTop * yScale);
26 0 : rect->fRight = SkScalarCeilToInt(rect->fRight * xScale);
27 0 : rect->fBottom = SkScalarCeilToInt(rect->fBottom * yScale);
28 0 : }
29 :
30 0 : static void scale_irect(SkIRect* rect, int xScale, int yScale) {
31 0 : rect->fLeft *= xScale;
32 0 : rect->fTop *= yScale;
33 0 : rect->fRight *= xScale;
34 0 : rect->fBottom *= yScale;
35 0 : }
36 :
37 : #ifdef SK_DEBUG
38 0 : static inline int is_even(int x) { return !(x & 1); }
39 : #endif
40 :
41 0 : static void shrink_irect_by_2(SkIRect* rect, bool xAxis, bool yAxis) {
42 0 : if (xAxis) {
43 0 : SkASSERT(is_even(rect->fLeft) && is_even(rect->fRight));
44 0 : rect->fLeft /= 2;
45 0 : rect->fRight /= 2;
46 : }
47 0 : if (yAxis) {
48 0 : SkASSERT(is_even(rect->fTop) && is_even(rect->fBottom));
49 0 : rect->fTop /= 2;
50 0 : rect->fBottom /= 2;
51 : }
52 0 : }
53 :
54 0 : static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) {
55 0 : *scaleFactor = 1;
56 0 : while (sigma > MAX_BLUR_SIGMA) {
57 0 : *scaleFactor *= 2;
58 0 : sigma *= 0.5f;
59 0 : if (*scaleFactor > maxTextureSize) {
60 0 : *scaleFactor = maxTextureSize;
61 0 : sigma = MAX_BLUR_SIGMA;
62 : }
63 : }
64 0 : *radius = static_cast<int>(ceilf(sigma * 3.0f));
65 0 : SkASSERT(*radius <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius);
66 0 : return sigma;
67 : }
68 :
69 0 : 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 0 : GrPaint paint;
80 0 : paint.setGammaCorrect(renderTargetContext->isGammaCorrect());
81 :
82 0 : GrResourceProvider* resourceProvider = renderTargetContext->resourceProvider();
83 :
84 : sk_sp<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make(
85 0 : resourceProvider, std::move(proxy), direction, radius, sigma, useBounds, bounds));
86 0 : paint.addColorFragmentProcessor(std::move(conv));
87 0 : paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
88 0 : SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
89 0 : -SkIntToScalar(srcOffset.y()));
90 0 : renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
91 0 : SkRect::Make(dstRect), localMatrix);
92 0 : }
93 :
94 0 : 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 0 : SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
105 0 : -SkIntToScalar(srcOffset.y()));
106 0 : SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1);
107 0 : SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
108 0 : GrPaint paint;
109 0 : paint.setGammaCorrect(renderTargetContext->isGammaCorrect());
110 0 : SkIRect bounds = srcBounds ? *srcBounds : SkIRect::EmptyIRect();
111 :
112 0 : GrResourceProvider* resourceProvider = renderTargetContext->resourceProvider();
113 :
114 : sk_sp<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::MakeGaussian(
115 0 : resourceProvider, std::move(proxy), bounds, size, 1.0, 0.0, kernelOffset,
116 : srcBounds ? GrTextureDomain::kDecal_Mode : GrTextureDomain::kIgnore_Mode,
117 0 : true, sigmaX, sigmaY));
118 0 : paint.addColorFragmentProcessor(std::move(conv));
119 0 : paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
120 0 : renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
121 0 : SkRect::Make(dstRect), localMatrix);
122 0 : }
123 :
124 0 : 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 0 : int bounds[2] = { 0, 0 };
134 0 : SkIRect dstRect = SkIRect::MakeWH(srcRect.width(), srcRect.height());
135 0 : if (!srcBounds) {
136 0 : convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset,
137 0 : std::move(proxy), direction, radius, sigma, false, bounds);
138 0 : return;
139 : }
140 0 : SkIRect midRect = *srcBounds, leftRect, rightRect;
141 0 : midRect.offset(srcOffset);
142 : SkIRect topRect, bottomRect;
143 0 : if (direction == Gr1DKernelEffect::kX_Direction) {
144 0 : bounds[0] = srcBounds->left();
145 0 : bounds[1] = srcBounds->right();
146 0 : topRect = SkIRect::MakeLTRB(0, 0, dstRect.right(), midRect.top());
147 0 : bottomRect = SkIRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom());
148 0 : midRect.inset(radius, 0);
149 0 : leftRect = SkIRect::MakeLTRB(0, midRect.top(), midRect.left(), midRect.bottom());
150 : rightRect =
151 0 : SkIRect::MakeLTRB(midRect.right(), midRect.top(), dstRect.width(), midRect.bottom());
152 0 : dstRect.fTop = midRect.top();
153 0 : dstRect.fBottom = midRect.bottom();
154 : } else {
155 0 : bounds[0] = srcBounds->top();
156 0 : bounds[1] = srcBounds->bottom();
157 0 : topRect = SkIRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom());
158 0 : bottomRect = SkIRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom());
159 0 : midRect.inset(0, radius);
160 0 : leftRect = SkIRect::MakeLTRB(midRect.left(), 0, midRect.right(), midRect.top());
161 : rightRect =
162 0 : SkIRect::MakeLTRB(midRect.left(), midRect.bottom(), midRect.right(), dstRect.height());
163 0 : dstRect.fLeft = midRect.left();
164 0 : dstRect.fRight = midRect.right();
165 : }
166 0 : if (!topRect.isEmpty()) {
167 0 : renderTargetContext->clear(&topRect, 0, false);
168 : }
169 :
170 0 : if (!bottomRect.isEmpty()) {
171 0 : renderTargetContext->clear(&bottomRect, 0, false);
172 : }
173 0 : if (midRect.isEmpty()) {
174 : // Blur radius covers srcBounds; use bounds over entire draw
175 0 : convolve_gaussian_1d(renderTargetContext, clip, dstRect, srcOffset,
176 0 : std::move(proxy), direction, radius, sigma, true, bounds);
177 : } else {
178 : // Draw right and left margins with bounds; middle without.
179 0 : convolve_gaussian_1d(renderTargetContext, clip, leftRect, srcOffset,
180 0 : proxy, direction, radius, sigma, true, bounds);
181 0 : convolve_gaussian_1d(renderTargetContext, clip, rightRect, srcOffset,
182 0 : proxy, direction, radius, sigma, true, bounds);
183 0 : convolve_gaussian_1d(renderTargetContext, clip, midRect, srcOffset,
184 0 : std::move(proxy), direction, radius, sigma, false, bounds);
185 : }
186 : }
187 :
188 : namespace SkGpuBlurUtils {
189 :
190 0 : 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 0 : 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 0 : GrTexture* temp = srcProxy->instantiate(context->resourceProvider());
205 0 : if (!temp) {
206 0 : return nullptr;
207 : }
208 : }
209 :
210 : SkIRect clearRect;
211 : int scaleFactorX, radiusX;
212 : int scaleFactorY, radiusY;
213 0 : int maxTextureSize = context->caps()->maxTextureSize();
214 0 : sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
215 0 : sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
216 0 : SkASSERT(sigmaX || sigmaY);
217 :
218 0 : SkIPoint srcOffset = SkIPoint::Make(-dstBounds.x(), -dstBounds.y());
219 0 : SkIRect localDstBounds = SkIRect::MakeWH(dstBounds.width(), dstBounds.height());
220 : SkIRect localSrcBounds;
221 : SkIRect srcRect;
222 0 : if (srcBounds) {
223 0 : srcRect = localSrcBounds = *srcBounds;
224 0 : srcRect.offset(srcOffset);
225 0 : srcBounds = &localSrcBounds;
226 : } else {
227 0 : srcRect = localDstBounds;
228 : }
229 :
230 0 : scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
231 0 : scale_irect(&srcRect, scaleFactorX, scaleFactorY);
232 :
233 : // setup new clip
234 0 : GrFixedClip clip(localDstBounds);
235 :
236 0 : const GrPixelConfig config = srcProxy->config();
237 :
238 0 : 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 0 : const int width = dstBounds.width();
243 0 : const int height = dstBounds.height();
244 :
245 : sk_sp<GrRenderTargetContext> dstRenderTargetContext(context->makeDeferredRenderTargetContext(
246 0 : fit, width, height, config, colorSpace, 0, kBottomLeft_GrSurfaceOrigin));
247 0 : if (!dstRenderTargetContext) {
248 0 : 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 0 : if (sigmaX > 0.0f && sigmaY > 0.0f &&
254 0 : (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
255 : // We shouldn't be scaling because this is a small size blur
256 0 : SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY));
257 :
258 0 : convolve_gaussian_2d(dstRenderTargetContext.get(), clip, localDstBounds, srcOffset,
259 0 : std::move(srcProxy), radiusX, radiusY, sigmaX, sigmaY, srcBounds);
260 :
261 0 : return dstRenderTargetContext;
262 : }
263 :
264 : sk_sp<GrRenderTargetContext> tmpRenderTargetContext(context->makeDeferredRenderTargetContext(
265 0 : fit, width, height, config, colorSpace, 0, kBottomLeft_GrSurfaceOrigin));
266 0 : if (!tmpRenderTargetContext) {
267 0 : return nullptr;
268 : }
269 :
270 0 : sk_sp<GrRenderTargetContext> srcRenderTargetContext;
271 :
272 0 : SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY));
273 :
274 0 : for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
275 0 : GrPaint paint;
276 0 : paint.setGammaCorrect(dstRenderTargetContext->isGammaCorrect());
277 0 : SkIRect dstRect(srcRect);
278 0 : if (srcBounds && i == 1) {
279 0 : SkRect domain = SkRect::Make(*srcBounds);
280 0 : domain.inset((i < scaleFactorX) ? SK_ScalarHalf : 0.0f,
281 0 : (i < scaleFactorY) ? SK_ScalarHalf : 0.0f);
282 : sk_sp<GrFragmentProcessor> fp(GrTextureDomainEffect::Make(
283 : context->resourceProvider(),
284 0 : std::move(srcProxy),
285 : nullptr,
286 : SkMatrix::I(),
287 : domain,
288 : GrTextureDomain::kDecal_Mode,
289 0 : GrSamplerParams::kBilerp_FilterMode));
290 0 : paint.addColorFragmentProcessor(std::move(fp));
291 0 : srcRect.offset(-srcOffset);
292 0 : srcOffset.set(0, 0);
293 : } else {
294 0 : GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
295 0 : paint.addColorTextureProcessor(context->resourceProvider(), std::move(srcProxy),
296 0 : nullptr, SkMatrix::I(), params);
297 : }
298 0 : paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
299 0 : shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY);
300 :
301 0 : dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
302 0 : SkRect::Make(dstRect), SkRect::Make(srcRect));
303 :
304 0 : srcRenderTargetContext = dstRenderTargetContext;
305 0 : srcRect = dstRect;
306 0 : srcProxy = srcRenderTargetContext->asTextureProxyRef();
307 0 : if (!srcProxy) {
308 0 : return nullptr;
309 : }
310 0 : dstRenderTargetContext.swap(tmpRenderTargetContext);
311 0 : localSrcBounds = srcRect;
312 : }
313 :
314 0 : srcRect = localDstBounds;
315 0 : scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
316 0 : if (sigmaX > 0.0f) {
317 0 : if (scaleFactorX > 1) {
318 0 : 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 0 : radiusX, srcRect.height());
324 0 : srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
325 : }
326 :
327 0 : convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect,
328 0 : std::move(srcProxy), Gr1DKernelEffect::kX_Direction, radiusX, sigmaX,
329 0 : srcBounds, srcOffset);
330 0 : srcRenderTargetContext = dstRenderTargetContext;
331 0 : srcProxy = srcRenderTargetContext->asTextureProxyRef();
332 0 : if (!srcProxy) {
333 0 : return nullptr;
334 : }
335 0 : srcRect.offsetTo(0, 0);
336 0 : dstRenderTargetContext.swap(tmpRenderTargetContext);
337 0 : localSrcBounds = srcRect;
338 0 : srcOffset.set(0, 0);
339 : }
340 :
341 0 : if (sigmaY > 0.0f) {
342 0 : if (scaleFactorY > 1 || sigmaX > 0.0f) {
343 0 : 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 0 : srcRect.width(), radiusY);
349 0 : srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
350 : }
351 :
352 0 : convolve_gaussian(dstRenderTargetContext.get(), clip, srcRect,
353 0 : std::move(srcProxy), Gr1DKernelEffect::kY_Direction, radiusY, sigmaY,
354 0 : srcBounds, srcOffset);
355 :
356 0 : srcRenderTargetContext = dstRenderTargetContext;
357 0 : srcRect.offsetTo(0, 0);
358 0 : dstRenderTargetContext.swap(tmpRenderTargetContext);
359 : }
360 :
361 0 : SkASSERT(srcRenderTargetContext);
362 0 : srcProxy.reset(nullptr); // we don't use this from here on out
363 :
364 0 : 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 0 : clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, srcRect.width() + 1, 1);
369 0 : srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
370 0 : clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop, 1, srcRect.height());
371 0 : srcRenderTargetContext->priv().absClear(&clearRect, 0x0);
372 :
373 0 : GrPaint paint;
374 0 : paint.setGammaCorrect(dstRenderTargetContext->isGammaCorrect());
375 : // FIXME: this should be mitchell, not bilinear.
376 0 : GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
377 0 : sk_sp<GrTextureProxy> proxy(srcRenderTargetContext->asTextureProxyRef());
378 0 : if (!proxy) {
379 0 : return nullptr;
380 : }
381 :
382 0 : paint.addColorTextureProcessor(context->resourceProvider(), std::move(proxy),
383 0 : nullptr, SkMatrix::I(), params);
384 0 : paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
385 :
386 0 : SkIRect dstRect(srcRect);
387 0 : scale_irect(&dstRect, scaleFactorX, scaleFactorY);
388 :
389 0 : dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
390 0 : SkRect::Make(dstRect), SkRect::Make(srcRect));
391 :
392 0 : srcRenderTargetContext = dstRenderTargetContext;
393 0 : srcRect = dstRect;
394 0 : dstRenderTargetContext.swap(tmpRenderTargetContext);
395 : }
396 :
397 0 : return srcRenderTargetContext;
398 : }
399 :
400 9 : }
401 :
402 : #endif
403 :
|