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