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 22namespace { 23 24static 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. 49static 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. 127GrSurfaceProxyView 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 183namespace skiagm { 184 185static 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 353DEF_SIMPLE_GPU_GM_CAN_FAIL(gpu_blur_utils, rContext, canvas, errorMsg, 765, 955) { 354 return run(rContext, canvas, errorMsg, false, false); 355} 356 357DEF_SIMPLE_GPU_GM_CAN_FAIL(gpu_blur_utils_ref, rContext, canvas, errorMsg, 765, 955) { 358 return run(rContext, canvas, errorMsg, false, true); 359} 360 361DEF_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 365DEF_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. 372static bool constexpr kShowSlowRefImages = false; 373 374static 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 436DEF_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 442DEF_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 453DEF_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