xref: /aosp_15_r20/external/skia/gm/gpu_blur_utils.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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