1 /*
2 * Copyright 2020 Google LLC.
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 "gm/gm.h"
9
10 #include "include/core/SkColorSpace.h"
11 #include "include/effects/SkGradientShader.h"
12 #include "include/gpu/ganesh/GrRecordingContext.h"
13 #include "include/gpu/ganesh/SkSurfaceGanesh.h"
14 #include "src/core/SkCanvasPriv.h"
15 #include "src/gpu/BlurUtils.h"
16 #include "src/gpu/ganesh/GrBlurUtils.h"
17 #include "src/gpu/ganesh/GrCanvas.h"
18 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
19 #include "src/gpu/ganesh/GrStyle.h"
20 #include "src/gpu/ganesh/SkGr.h"
21 #include "src/gpu/ganesh/SurfaceDrawContext.h"
22 #include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h"
23 #include "src/gpu/ganesh/effects/GrTextureEffect.h"
24 #include "src/gpu/ganesh/image/GrImageUtils.h"
25 #include "src/image/SkImage_Base.h"
26
27 namespace {
28
blur(GrRecordingContext * ctx,GrSurfaceProxyView src,SkIRect dstB,SkIRect srcB,float sigmaX,float sigmaY,SkTileMode mode)29 static GrSurfaceProxyView blur(GrRecordingContext* ctx,
30 GrSurfaceProxyView src,
31 SkIRect dstB,
32 SkIRect srcB,
33 float sigmaX,
34 float sigmaY,
35 SkTileMode mode) {
36 auto resultSDC = GrBlurUtils::GaussianBlur(ctx,
37 src,
38 GrColorType::kRGBA_8888,
39 kPremul_SkAlphaType,
40 nullptr,
41 dstB,
42 srcB,
43 sigmaX,
44 sigmaY,
45 mode);
46 if (!resultSDC) {
47 return {};
48 }
49 return resultSDC->readSurfaceView();
50 };
51
52 // Performs tiling first of the src into dst bounds with a surrounding skirt so the blur can use
53 // clamp. Does repeated blurs rather than invoking downsampling.
slow_blur(GrRecordingContext * rContext,GrSurfaceProxyView src,SkIRect dstB,SkIRect srcB,float sigmaX,float sigmaY,SkTileMode mode)54 static GrSurfaceProxyView slow_blur(GrRecordingContext* rContext,
55 GrSurfaceProxyView src,
56 SkIRect dstB,
57 SkIRect srcB,
58 float sigmaX,
59 float sigmaY,
60 SkTileMode mode) {
61 auto tileInto = [rContext](GrSurfaceProxyView src,
62 SkIRect srcTileRect,
63 SkISize resultSize,
64 SkIPoint offset,
65 SkTileMode mode) {
66 GrImageInfo info(GrColorType::kRGBA_8888, kPremul_SkAlphaType, nullptr, resultSize);
67 auto sfc = rContext->priv().makeSFC(info, /*label=*/{});
68 if (!sfc) {
69 return GrSurfaceProxyView{};
70 }
71 GrSamplerState sampler(SkTileModeToWrapMode(mode), SkFilterMode::kNearest);
72 auto fp = GrTextureEffect::MakeSubset(src,
73 kPremul_SkAlphaType,
74 SkMatrix::Translate(-offset.x(), -offset.y()),
75 sampler,
76 SkRect::Make(srcTileRect),
77 *rContext->priv().caps());
78 sfc->fillWithFP(std::move(fp));
79 return sfc->readSurfaceView();
80 };
81
82 SkIPoint outset = {skgpu::BlurSigmaRadius(sigmaX), skgpu::BlurSigmaRadius(sigmaY)};
83 SkISize size = {dstB.width() + 2*outset.x(), dstB.height() + 2*outset.y()};
84 src = tileInto(std::move(src), srcB, size, outset - dstB.topLeft(), mode);
85 if (!src) {
86 return {};
87 }
88 dstB = SkIRect::MakePtSize(outset, dstB.size());
89
90 while (sigmaX || sigmaY) {
91 float stepX = sigmaX;
92 if (stepX > skgpu::kMaxLinearBlurSigma) {
93 stepX = skgpu::kMaxLinearBlurSigma;
94 // A blur of sigma1 followed by a blur of sigma2 is equiv. to a single blur of
95 // sqrt(sigma1^2 + sigma2^2).
96 sigmaX = sqrt(sigmaX*sigmaX - skgpu::kMaxLinearBlurSigma*skgpu::kMaxLinearBlurSigma);
97 } else {
98 sigmaX = 0.f;
99 }
100 float stepY = sigmaY;
101 if (stepY > skgpu::kMaxLinearBlurSigma) {
102 stepY = skgpu::kMaxLinearBlurSigma;
103 sigmaY = sqrt(sigmaY*sigmaY- skgpu::kMaxLinearBlurSigma*skgpu::kMaxLinearBlurSigma);
104 } else {
105 sigmaY = 0.f;
106 }
107 auto bounds = SkIRect::MakeSize(src.dimensions());
108 auto sdc = GrBlurUtils::GaussianBlur(rContext,
109 std::move(src),
110 GrColorType::kRGBA_8888,
111 kPremul_SkAlphaType,
112 nullptr,
113 bounds,
114 bounds,
115 stepX,
116 stepY,
117 SkTileMode::kClamp);
118 if (!sdc) {
119 return {};
120 }
121 src = sdc->readSurfaceView();
122 }
123 // We have o use the original mode here because we may have only blurred in X or Y and then
124 // the other dimension was not expanded.
125 auto srcRect = SkIRect::MakeSize(src.dimensions());
126 return tileInto(std::move(src), srcRect, dstB.size(), -outset, SkTileMode::kClamp);
127 };
128
129 // Makes a src texture for as a source for blurs. If 'contentArea' then the content will
130 // be in that rect, the 1-pixel surrounding border will be transparent black, and red outside of
131 // that. Otherwise, the content fills the dimensions.
make_src_image(GrRecordingContext * rContext,SkISize dimensions,const SkIRect * contentArea=nullptr)132 GrSurfaceProxyView make_src_image(GrRecordingContext* rContext,
133 SkISize dimensions,
134 const SkIRect* contentArea = nullptr) {
135 auto srcII = SkImageInfo::Make(dimensions, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
136 auto surf = SkSurfaces::RenderTarget(rContext, skgpu::Budgeted::kYes, srcII);
137 if (!surf) {
138 return {};
139 }
140
141 float w, h;
142 if (contentArea) {
143 surf->getCanvas()->clear(SK_ColorRED);
144 surf->getCanvas()->clipIRect(contentArea->makeOutset(1, 1));
145 surf->getCanvas()->clear(SK_ColorTRANSPARENT);
146 surf->getCanvas()->clipIRect(*contentArea);
147 surf->getCanvas()->translate(contentArea->top(), contentArea->left());
148 w = contentArea->width();
149 h = contentArea->height();
150 } else {
151 w = dimensions.width();
152 h = dimensions.height();
153 }
154
155 surf->getCanvas()->drawColor(SK_ColorDKGRAY);
156 SkPaint paint;
157 paint.setAntiAlias(true);
158 paint.setStyle(SkPaint::kStroke_Style);
159 // Draw four horizontal lines at 1/8, 1/4, 3/4, 7/8.
160 paint.setStrokeWidth(h/12.f);
161 paint.setColor(SK_ColorRED);
162 surf->getCanvas()->drawLine({0.f, 1.f*h/8.f}, {w, 1.f*h/8.f}, paint);
163 paint.setColor(/* sea foam */ 0xFF71EEB8);
164 surf->getCanvas()->drawLine({0.f, 1.f*h/4.f}, {w, 1.f*h/4.f}, paint);
165 paint.setColor(SK_ColorYELLOW);
166 surf->getCanvas()->drawLine({0.f, 3.f*h/4.f}, {w, 3.f*h/4.f}, paint);
167 paint.setColor(SK_ColorCYAN);
168 surf->getCanvas()->drawLine({0.f, 7.f*h/8.f}, {w, 7.f*h/8.f}, paint);
169
170 // Draw four vertical lines at 1/8, 1/4, 3/4, 7/8.
171 paint.setStrokeWidth(w/12.f);
172 paint.setColor(/* orange */ 0xFFFFA500);
173 surf->getCanvas()->drawLine({1.f*w/8.f, 0.f}, {1.f*h/8.f, h}, paint);
174 paint.setColor(SK_ColorBLUE);
175 surf->getCanvas()->drawLine({1.f*w/4.f, 0.f}, {1.f*h/4.f, h}, paint);
176 paint.setColor(SK_ColorMAGENTA);
177 surf->getCanvas()->drawLine({3.f*w/4.f, 0.f}, {3.f*h/4.f, h}, paint);
178 paint.setColor(SK_ColorGREEN);
179 surf->getCanvas()->drawLine({7.f*w/8.f, 0.f}, {7.f*h/8.f, h}, paint);
180
181 auto img = surf->makeImageSnapshot();
182 auto [src, ct] = skgpu::ganesh::AsView(rContext, img, skgpu::Mipmapped::kNo);
183 return src;
184 }
185
186 } // namespace
187
188 namespace skiagm {
189
run(GrRecordingContext * rContext,SkCanvas * canvas,SkString * errorMsg,bool subsetSrc,bool ref)190 static GM::DrawResult run(GrRecordingContext* rContext, SkCanvas* canvas, SkString* errorMsg,
191 bool subsetSrc, bool ref) {
192 GrSurfaceProxyView src = make_src_image(rContext, {60, 60});
193 if (!src) {
194 *errorMsg = "Failed to create source image";
195 return DrawResult::kSkip;
196 }
197
198 auto sdc = skgpu::ganesh::TopDeviceSurfaceDrawContext(canvas);
199 if (!sdc) {
200 *errorMsg = GM::kErrorMsg_DrawSkippedGpuOnly;
201 return DrawResult::kSkip;
202 }
203
204 SkIRect srcRect = SkIRect::MakeSize(src.dimensions());
205 if (subsetSrc) {
206 srcRect = SkIRect::MakeXYWH(2.f*srcRect.width() /8.f,
207 1.f*srcRect.height()/8.f,
208 5.f*srcRect.width() /8.f,
209 6.f*srcRect.height()/8.f);
210 }
211 int srcW = srcRect.width();
212 int srcH = srcRect.height();
213 // Each set of rects is drawn in one test area so they probably should not abut or overlap
214 // to visualize the blurs separately.
215 const std::vector<SkIRect> dstRectSets[] = {
216 // encloses source bounds.
217 {
218 srcRect.makeOutset(srcW/5, srcH/5)
219 },
220
221 // partial overlap from above/below.
222 {
223 SkIRect::MakeXYWH(srcRect.x(), srcRect.y() + 3*srcH/4, srcW, srcH),
224 SkIRect::MakeXYWH(srcRect.x(), srcRect.y() - 3*srcH/4, srcW, srcH)
225 },
226
227 // adjacent to each side of src bounds.
228 {
229 srcRect.makeOffset( 0, srcH),
230 srcRect.makeOffset( srcW, 0),
231 srcRect.makeOffset( 0, -srcH),
232 srcRect.makeOffset(-srcW, 0),
233 },
234
235 // fully outside src bounds in one direction.
236 {
237 SkIRect::MakeXYWH(-6.f*srcW/8.f, -7.f*srcH/8.f, 4.f*srcW/8.f, 20.f*srcH/8.f)
238 .makeOffset(srcRect.topLeft()),
239 SkIRect::MakeXYWH(-1.f*srcW/8.f, -7.f*srcH/8.f, 16.f*srcW/8.f, 2.f*srcH/8.f)
240 .makeOffset(srcRect.topLeft()),
241 SkIRect::MakeXYWH(10.f*srcW/8.f, -3.f*srcH/8.f, 4.f*srcW/8.f, 16.f*srcH/8.f)
242 .makeOffset(srcRect.topLeft()),
243 SkIRect::MakeXYWH(-7.f*srcW/8.f, 14.f*srcH/8.f, 18.f*srcW/8.f, 1.f*srcH/8.f)
244 .makeOffset(srcRect.topLeft()),
245 },
246
247 // outside of src bounds in both directions.
248 {
249 SkIRect::MakeXYWH(-5.f*srcW/8.f, -5.f*srcH/8.f, 2.f*srcW/8.f, 2.f*srcH/8.f)
250 .makeOffset(srcRect.topLeft()),
251 SkIRect::MakeXYWH(-5.f*srcW/8.f, 12.f*srcH/8.f, 2.f*srcW/8.f, 2.f*srcH/8.f)
252 .makeOffset(srcRect.topLeft()),
253 SkIRect::MakeXYWH(12.f*srcW/8.f, -5.f*srcH/8.f, 2.f*srcW/8.f, 2.f*srcH/8.f)
254 .makeOffset(srcRect.topLeft()),
255 SkIRect::MakeXYWH(12.f*srcW/8.f, 12.f*srcH/8.f, 2.f*srcW/8.f, 2.f*srcH/8.f)
256 .makeOffset(srcRect.topLeft()),
257 },
258 };
259
260 const auto& caps = *rContext->priv().caps();
261
262 static constexpr SkScalar kPad = 10;
263 SkVector trans = {kPad, kPad};
264
265 sdc->clear(SK_PMColor4fWHITE);
266
267 SkIRect testArea = srcRect;
268 testArea.outset(testArea.width(), testArea.height());
269 for (const auto& dstRectSet : dstRectSets) {
270 for (int t = 0; t < kSkTileModeCount; ++t) {
271 auto mode = static_cast<SkTileMode>(t);
272 GrSamplerState sampler(SkTileModeToWrapMode(mode), GrSamplerState::Filter::kNearest);
273 SkMatrix m = SkMatrix::Translate(trans.x() - testArea.x(), trans.y() - testArea.y());
274 // Draw the src subset in the tile mode faded as a reference before drawing the blur
275 // on top.
276 {
277 static constexpr float kAlpha = 0.2f;
278 auto fp = GrTextureEffect::MakeSubset(src, kPremul_SkAlphaType, SkMatrix::I(),
279 sampler, SkRect::Make(srcRect), caps);
280 fp = GrFragmentProcessor::ModulateRGBA(std::move(fp),
281 {kAlpha, kAlpha, kAlpha, kAlpha});
282 GrPaint paint;
283 paint.setColorFragmentProcessor(std::move(fp));
284 sdc->drawRect(nullptr, std::move(paint), GrAA::kNo, m, SkRect::Make(testArea));
285 }
286 // Do a blur for each dstRect in the set over our testArea-sized background.
287 for (const auto& dstRect : dstRectSet) {
288 const SkScalar sigmaX = src.width() / 10.f;
289 const SkScalar sigmaY = src.height() / 10.f;
290 auto blurFn = ref ? slow_blur : blur;
291 // Blur using the rect and draw on top.
292 if (auto blurView = blurFn(rContext,
293 src,
294 dstRect,
295 srcRect,
296 sigmaX,
297 sigmaY,
298 mode)) {
299 auto fp = GrTextureEffect::Make(blurView,
300 kPremul_SkAlphaType,
301 SkMatrix::I(),
302 sampler,
303 caps);
304 // Compose against white (default paint color)
305 fp = GrBlendFragmentProcessor::Make<SkBlendMode::kSrcOver>(std::move(fp),
306 /*dst=*/nullptr);
307 GrPaint paint;
308 // Compose against white (default paint color) and then replace the dst
309 // (SkBlendMode::kSrc).
310 fp = GrBlendFragmentProcessor::Make<SkBlendMode::kSrcOver>(std::move(fp),
311 /*dst=*/nullptr);
312 paint.setColorFragmentProcessor(std::move(fp));
313 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
314 sdc->fillRectToRect(nullptr,
315 std::move(paint),
316 GrAA::kNo,
317 m,
318 SkRect::Make(dstRect),
319 SkRect::Make(blurView.dimensions()));
320 }
321 // Show the outline of the dst rect. Mostly for kDecal but also allows visual
322 // confirmation that the resulting blur is the right size and in the right place.
323 {
324 GrPaint paint;
325 static constexpr float kAlpha = 0.6f;
326 paint.setColor4f({0, kAlpha, 0, kAlpha});
327 SkPaint stroke;
328 stroke.setStyle(SkPaint::kStroke_Style);
329 stroke.setStrokeWidth(1.f);
330 GrStyle style(stroke);
331 auto dstR = SkRect::Make(dstRect).makeOutset(0.5f, 0.5f);
332 sdc->drawRect(nullptr, std::move(paint), GrAA::kNo, m, dstR, &style);
333 }
334 }
335 // Show the rect that's being blurred.
336 {
337 GrPaint paint;
338 static constexpr float kAlpha = 0.3f;
339 paint.setColor4f({0, 0, 0, kAlpha});
340 SkPaint stroke;
341 stroke.setStyle(SkPaint::kStroke_Style);
342 stroke.setStrokeWidth(1.f);
343 GrStyle style(stroke);
344 auto srcR = SkRect::Make(srcRect).makeOutset(0.5f, 0.5f);
345 sdc->drawRect(nullptr, std::move(paint), GrAA::kNo, m, srcR, &style);
346 }
347 trans.fX += testArea.width() + kPad;
348 }
349 trans.fX = kPad;
350 trans.fY += testArea.height() + kPad;
351 }
352
353 return DrawResult::kOk;
354 }
355
356 DEF_SIMPLE_GPU_GM_CAN_FAIL(gpu_blur_utils, rContext, canvas, errorMsg, 765, 955) {
357 return run(rContext, canvas, errorMsg, false, false);
358 }
359
360 DEF_SIMPLE_GPU_GM_CAN_FAIL(gpu_blur_utils_ref, rContext, canvas, errorMsg, 765, 955) {
361 return run(rContext, canvas, errorMsg, false, true);
362 }
363
364 DEF_SIMPLE_GPU_GM_CAN_FAIL(gpu_blur_utils_subset_rect, rContext, canvas, errorMsg, 485, 730) {
365 return run(rContext, canvas, errorMsg, true, false);
366 }
367
368 DEF_SIMPLE_GPU_GM_CAN_FAIL(gpu_blur_utils_subset_ref, rContext, canvas, errorMsg, 485, 730) {
369 return run(rContext, canvas, errorMsg, true, true);
370 }
371
372 // Because of the way blur sigmas concat (sigTotal = sqrt(sig1^2 + sig2^2) generating these images
373 // for very large sigmas is incredibly slow. This can be enabled while working on the blur code to
374 // check results.
375 static bool constexpr kShowSlowRefImages = false;
376
do_very_large_blur_gm(GrRecordingContext * rContext,SkCanvas * canvas,SkString * errorMsg,GrSurfaceProxyView src,SkIRect srcB)377 static DrawResult do_very_large_blur_gm(GrRecordingContext* rContext,
378 SkCanvas* canvas,
379 SkString* errorMsg,
380 GrSurfaceProxyView src,
381 SkIRect srcB) {
382 auto sdc = skgpu::ganesh::TopDeviceSurfaceDrawContext(canvas);
383 if (!sdc) {
384 *errorMsg = GM::kErrorMsg_DrawSkippedGpuOnly;
385 return DrawResult::kSkip;
386 }
387
388 // Clear to a color other than gray to contrast with test image.
389 sdc->clear(SkColor4f{0.3f, 0.4f, 0.2f, 1});
390
391 int x = 10;
392 int y = 10;
393 for (auto blurDirs : {0b01, 0b10, 0b11}) {
394 for (int t = 0; t < kSkTileModeCount; ++t) {
395 auto tm = static_cast<SkTileMode>(t);
396 auto dstB = srcB.makeOutset(30, 30);
397 for (float sigma : {0.f, 5.f, 25.f, 80.f}) {
398 std::vector<decltype(blur)*> blurs;
399 blurs.push_back(blur);
400 if (kShowSlowRefImages) {
401 blurs.push_back(slow_blur);
402 }
403 for (auto b : blurs) {
404 float sigX = sigma*((blurDirs & 0b01) >> 0);
405 float sigY = sigma*((blurDirs & 0b10) >> 1);
406 GrSurfaceProxyView result = b(rContext, src, dstB, srcB, sigX, sigY, tm);
407 auto dstRect = SkIRect::MakeSize(dstB.size()).makeOffset(x, y);
408 // Draw a rect to show where the result should be so it's obvious if it's
409 // missing.
410 GrPaint paint;
411 paint.setColor4f(b == blur ? SkPMColor4f{0, 0, 1, 1} : SkPMColor4f{1, 0, 0, 1});
412 sdc->drawRect(nullptr,
413 std::move(paint),
414 GrAA::kNo,
415 SkMatrix::I(),
416 SkRect::Make(dstRect).makeOutset(0.5, 0.5),
417 &GrStyle::SimpleHairline());
418 if (result) {
419 std::unique_ptr<GrFragmentProcessor> fp =
420 GrTextureEffect::Make(std::move(result), kPremul_SkAlphaType);
421 fp = GrBlendFragmentProcessor::Make<SkBlendMode::kSrcOver>(std::move(fp),
422 /*dst=*/nullptr);
423 sdc->fillRectToRectWithFP(SkIRect::MakeSize(dstB.size()),
424 dstRect,
425 std::move(fp));
426 }
427 x += dstB.width() + 10;
428 }
429 }
430 x = 10;
431 y += dstB.height() + 10;
432 }
433 }
434
435 return DrawResult::kOk;
436 }
437
438 DEF_SIMPLE_GPU_GM_CAN_FAIL(very_large_sigma_gpu_blur, rContext, canvas, errorMsg, 350, 1030) {
439 auto src = make_src_image(rContext, {15, 15});
440 auto srcB = SkIRect::MakeSize(src.dimensions());
441 return do_very_large_blur_gm(rContext, canvas, errorMsg, std::move(src), srcB);
442 }
443
444 DEF_SIMPLE_GPU_GM_CAN_FAIL(very_large_sigma_gpu_blur_subset,
445 rContext,
446 canvas,
447 errorMsg,
448 350, 1030) {
449 auto srcB = SkIRect::MakeXYWH(2, 2, 15, 15);
450 SkISize imageSize = SkISize{srcB.width() + 4, srcB.height() + 4};
451 auto src = make_src_image(rContext, imageSize, &srcB);
452 return do_very_large_blur_gm(rContext, canvas, errorMsg, std::move(src), srcB);
453 }
454
455 DEF_SIMPLE_GPU_GM_CAN_FAIL(very_large_sigma_gpu_blur_subset_transparent_border,
456 rContext,
457 canvas,
458 errorMsg,
459 355, 1055) {
460 auto srcB = SkIRect::MakeXYWH(3, 3, 15, 15);
461 SkISize imageSize = SkISize{srcB.width() + 4, srcB.height() + 4};
462 auto src = make_src_image(rContext, imageSize, &srcB);
463 return do_very_large_blur_gm(rContext, canvas, errorMsg, std::move(src), srcB.makeOutset(1, 1));
464 }
465
466 } // namespace skiagm
467