skia2/gm/gpu_blur_utils.cpp
Brian Salomon ed3c75b0a5 adjust values in very_large_sigma_gpu_blur gms
Only the largest sigma (320) would hit the downscale limit with the
old sigmas and image sizes. However, that sigma was far too large
to produce reference values in a reasonable amount of time.

Reduce the image sizes so that the downscale limit can be hit with a
smaller sigma.

Bug: skia:11735
Change-Id: Ia7f4385c6114f7071a14336ca39f81cf9ac68861
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/386058
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
2021-03-23 15:23:18 +00:00

426 lines
19 KiB
C++

/*
* Copyright 2020 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "gm/gm.h"
#include "include/effects/SkGradientShader.h"
#include "include/gpu/GrRecordingContext.h"
#include "src/core/SkGpuBlurUtils.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrStyle.h"
#include "src/gpu/SkGr.h"
#include "src/gpu/effects/GrBlendFragmentProcessor.h"
#include "src/image/SkImage_Base.h"
static GrSurfaceProxyView blur(GrRecordingContext* ctx,
GrSurfaceProxyView src,
SkIRect dstB,
SkIRect srcB,
float sigmaX,
float sigmaY,
SkTileMode mode) {
auto resultSDC = SkGpuBlurUtils::GaussianBlur(ctx,
src,
GrColorType::kRGBA_8888,
kPremul_SkAlphaType,
nullptr,
dstB,
srcB,
sigmaX,
sigmaY,
mode);
if (!resultSDC) {
return {};
}
return resultSDC->readSurfaceView();
};
// Performs tiling first of the src into dst bounds with a surrounding skirt so the blur can use
// clamp. Does repeated blurs rather than invoking downsampling.
static GrSurfaceProxyView slow_blur(GrRecordingContext* ctx,
GrSurfaceProxyView src,
SkIRect dstB,
SkIRect srcB,
float sigmaX,
float sigmaY,
SkTileMode mode) {
auto tileInto = [ctx](GrSurfaceProxyView src,
SkIRect srcTileRect,
SkISize resultSize,
SkIPoint offset,
SkTileMode mode) {
GrImageInfo info(GrColorType::kRGBA_8888, kPremul_SkAlphaType, nullptr, resultSize);
auto fc = GrSurfaceFillContext::Make(ctx, info);
if (!fc) {
return GrSurfaceProxyView{};
}
GrSamplerState sampler(SkTileModeToWrapMode(mode), SkFilterMode::kNearest);
auto fp = GrTextureEffect::MakeSubset(src,
kPremul_SkAlphaType,
SkMatrix::Translate(-offset.x(), -offset.y()),
sampler,
SkRect::Make(srcTileRect),
*ctx->priv().caps());
fc->fillWithFP(std::move(fp));
return fc->readSurfaceView();
};
SkIPoint outset = {SkGpuBlurUtils::SigmaRadius(sigmaX), SkGpuBlurUtils::SigmaRadius(sigmaY)};
SkISize size = {dstB.width() + 2*outset.x(), dstB.height() + 2*outset.y()};
src = tileInto(std::move(src), srcB, size, outset - dstB.topLeft(), mode);
if (!src) {
return {};
}
dstB = SkIRect::MakePtSize(outset, dstB.size());
while (sigmaX || sigmaY) {
float stepX = sigmaX;
if (stepX > SkGpuBlurUtils::kMaxSigma) {
stepX = SkGpuBlurUtils::kMaxSigma;
// A blur of sigma1 followed by a blur of sigma2 is equiv. to a single blur of
// sqrt(sigma1^2 + sigma2^2).
sigmaX = sqrt(sigmaX*sigmaX - SkGpuBlurUtils::kMaxSigma*SkGpuBlurUtils::kMaxSigma);
} else {
sigmaX = 0.f;
}
float stepY = sigmaY;
if (stepY > SkGpuBlurUtils::kMaxSigma) {
stepY = SkGpuBlurUtils::kMaxSigma;
sigmaY = sqrt(sigmaY*sigmaY- SkGpuBlurUtils::kMaxSigma*SkGpuBlurUtils::kMaxSigma);
} else {
sigmaY = 0.f;
}
auto bounds = SkIRect::MakeSize(src.dimensions());
auto sdc = SkGpuBlurUtils::GaussianBlur(ctx,
std::move(src),
GrColorType::kRGBA_8888,
kPremul_SkAlphaType,
nullptr,
bounds,
bounds,
stepX,
stepY,
SkTileMode::kClamp);
if (!sdc) {
return {};
}
src = sdc->readSurfaceView();
}
// We have o use the original mode here because we may have only blurred in X or Y and then
// the other dimension was not expanded.
auto srcRect = SkIRect::MakeSize(src.dimensions());
return tileInto(std::move(src), srcRect, dstB.size(), -outset, SkTileMode::kClamp);
};
// Makes a src texture for as a source for blurs. If 'contentArea' then the content will
// be in that rect, the 1-pixel surrounding border will be transparent black, and red outside of
// that. Otherwise, the content fills the dimensions.
GrSurfaceProxyView make_src_image(GrRecordingContext* rContext,
SkISize dimensions,
const SkIRect* contentArea = nullptr) {
auto srcII = SkImageInfo::Make(dimensions, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
auto surf = SkSurface::MakeRenderTarget(rContext, SkBudgeted::kYes, srcII);
if (!surf) {
return {};
}
float w, h;
if (contentArea) {
surf->getCanvas()->clear(SK_ColorRED);
surf->getCanvas()->clipIRect(contentArea->makeOutset(1, 1));
surf->getCanvas()->clear(SK_ColorTRANSPARENT);
surf->getCanvas()->clipIRect(*contentArea);
surf->getCanvas()->translate(contentArea->top(), contentArea->left());
w = contentArea->width();
h = contentArea->height();
} else {
w = dimensions.width();
h = dimensions.height();
}
surf->getCanvas()->drawColor(SK_ColorDKGRAY);
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
// Draw four horizontal lines at 1/8, 1/4, 3/4, 7/8.
paint.setStrokeWidth(h/12.f);
paint.setColor(SK_ColorRED);
surf->getCanvas()->drawLine({0.f, 1.f*h/8.f}, {w, 1.f*h/8.f}, paint);
paint.setColor(/* sea foam */ 0xFF71EEB8);
surf->getCanvas()->drawLine({0.f, 1.f*h/4.f}, {w, 1.f*h/4.f}, paint);
paint.setColor(SK_ColorYELLOW);
surf->getCanvas()->drawLine({0.f, 3.f*h/4.f}, {w, 3.f*h/4.f}, paint);
paint.setColor(SK_ColorCYAN);
surf->getCanvas()->drawLine({0.f, 7.f*h/8.f}, {w, 7.f*h/8.f}, paint);
// Draw four vertical lines at 1/8, 1/4, 3/4, 7/8.
paint.setStrokeWidth(w/12.f);
paint.setColor(/* orange */ 0xFFFFA500);
surf->getCanvas()->drawLine({1.f*w/8.f, 0.f}, {1.f*h/8.f, h}, paint);
paint.setColor(SK_ColorBLUE);
surf->getCanvas()->drawLine({1.f*w/4.f, 0.f}, {1.f*h/4.f, h}, paint);
paint.setColor(SK_ColorMAGENTA);
surf->getCanvas()->drawLine({3.f*w/4.f, 0.f}, {3.f*h/4.f, h}, paint);
paint.setColor(SK_ColorGREEN);
surf->getCanvas()->drawLine({7.f*w/8.f, 0.f}, {7.f*h/8.f, h}, paint);
auto img = surf->makeImageSnapshot();
auto [src, ct] = as_IB(img)->asView(rContext, GrMipmapped::kNo);
return src;
}
static void run(GrRecordingContext* rContext, GrSurfaceDrawContext* sdc, bool subsetSrc, bool ref) {
GrSurfaceProxyView src = make_src_image(rContext, {60, 60});
if (!src) {
return;
}
SkIRect srcRect = SkIRect::MakeSize(src.dimensions());
if (subsetSrc) {
srcRect = SkIRect::MakeXYWH(2.f*srcRect.width() /8.f,
1.f*srcRect.height()/8.f,
5.f*srcRect.width() /8.f,
6.f*srcRect.height()/8.f);
}
int srcW = srcRect.width();
int srcH = srcRect.height();
// Each set of rects is drawn in one test area so they probably should not abut or overlap
// to visualize the blurs separately.
const std::vector<SkIRect> dstRectSets[] = {
// encloses source bounds.
{
srcRect.makeOutset(srcW/5, srcH/5)
},
// partial overlap from above/below.
{
SkIRect::MakeXYWH(srcRect.x(), srcRect.y() + 3*srcH/4, srcW, srcH),
SkIRect::MakeXYWH(srcRect.x(), srcRect.y() - 3*srcH/4, srcW, srcH)
},
// adjacent to each side of src bounds.
{
srcRect.makeOffset( 0, srcH),
srcRect.makeOffset( srcW, 0),
srcRect.makeOffset( 0, -srcH),
srcRect.makeOffset(-srcW, 0),
},
// fully outside src bounds in one direction.
{
SkIRect::MakeXYWH(-6.f*srcW/8.f, -7.f*srcH/8.f, 4.f*srcW/8.f, 20.f*srcH/8.f)
.makeOffset(srcRect.topLeft()),
SkIRect::MakeXYWH(-1.f*srcW/8.f, -7.f*srcH/8.f, 16.f*srcW/8.f, 2.f*srcH/8.f)
.makeOffset(srcRect.topLeft()),
SkIRect::MakeXYWH(10.f*srcW/8.f, -3.f*srcH/8.f, 4.f*srcW/8.f, 16.f*srcH/8.f)
.makeOffset(srcRect.topLeft()),
SkIRect::MakeXYWH(-7.f*srcW/8.f, 14.f*srcH/8.f, 18.f*srcW/8.f, 1.f*srcH/8.f)
.makeOffset(srcRect.topLeft()),
},
// outside of src bounds in both directions.
{
SkIRect::MakeXYWH(-5.f*srcW/8.f, -5.f*srcH/8.f, 2.f*srcW/8.f, 2.f*srcH/8.f)
.makeOffset(srcRect.topLeft()),
SkIRect::MakeXYWH(-5.f*srcW/8.f, 12.f*srcH/8.f, 2.f*srcW/8.f, 2.f*srcH/8.f)
.makeOffset(srcRect.topLeft()),
SkIRect::MakeXYWH(12.f*srcW/8.f, -5.f*srcH/8.f, 2.f*srcW/8.f, 2.f*srcH/8.f)
.makeOffset(srcRect.topLeft()),
SkIRect::MakeXYWH(12.f*srcW/8.f, 12.f*srcH/8.f, 2.f*srcW/8.f, 2.f*srcH/8.f)
.makeOffset(srcRect.topLeft()),
},
};
const auto& caps = *rContext->priv().caps();
static constexpr SkScalar kPad = 10;
SkVector trans = {kPad, kPad};
sdc->clear(SK_PMColor4fWHITE);
SkIRect testArea = srcRect;
testArea.outset(testArea.width(), testArea.height());
for (const auto& dstRectSet : dstRectSets) {
for (int t = 0; t < kSkTileModeCount; ++t) {
auto mode = static_cast<SkTileMode>(t);
GrSamplerState sampler(SkTileModeToWrapMode(mode), GrSamplerState::Filter::kNearest);
SkMatrix m = SkMatrix::Translate(trans.x() - testArea.x(), trans.y() - testArea.y());
// Draw the src subset in the tile mode faded as a reference before drawing the blur
// on top.
{
static constexpr float kAlpha = 0.2f;
auto fp = GrTextureEffect::MakeSubset(src, kPremul_SkAlphaType, SkMatrix::I(),
sampler, SkRect::Make(srcRect), caps);
fp = GrFragmentProcessor::ModulateRGBA(std::move(fp),
{kAlpha, kAlpha, kAlpha, kAlpha});
GrPaint paint;
paint.setColorFragmentProcessor(std::move(fp));
sdc->drawRect(nullptr, std::move(paint), GrAA::kNo, m, SkRect::Make(testArea));
}
// Do a blur for each dstRect in the set over our testArea-sized background.
for (const auto& dstRect : dstRectSet) {
const SkScalar sigmaX = src.width() / 10.f;
const SkScalar sigmaY = src.height() / 10.f;
auto blurFn = ref ? slow_blur : blur;
// Blur using the rect and draw on top.
if (auto blurView = blurFn(rContext,
src,
dstRect,
srcRect,
sigmaX,
sigmaY,
mode)) {
auto fp = GrTextureEffect::Make(blurView,
kPremul_SkAlphaType,
SkMatrix::I(),
sampler,
caps);
// Compose against white (default paint color)
fp = GrBlendFragmentProcessor::Make(std::move(fp),
/*dst=*/nullptr,
SkBlendMode::kSrcOver);
GrPaint paint;
// Compose against white (default paint color) and then replace the dst
// (SkBlendMode::kSrc).
fp = GrBlendFragmentProcessor::Make(std::move(fp), /*dst=*/nullptr,
SkBlendMode::kSrcOver);
paint.setColorFragmentProcessor(std::move(fp));
paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
sdc->fillRectToRect(nullptr,
std::move(paint),
GrAA::kNo,
m,
SkRect::Make(dstRect),
SkRect::Make(blurView.dimensions()));
}
// Show the outline of the dst rect. Mostly for kDecal but also allows visual
// confirmation that the resulting blur is the right size and in the right place.
{
GrPaint paint;
static constexpr float kAlpha = 0.6f;
paint.setColor4f({0, kAlpha, 0, kAlpha});
SkPaint stroke;
stroke.setStyle(SkPaint::kStroke_Style);
stroke.setStrokeWidth(1.f);
GrStyle style(stroke);
auto dstR = SkRect::Make(dstRect).makeOutset(0.5f, 0.5f);
sdc->drawRect(nullptr, std::move(paint), GrAA::kNo, m, dstR, &style);
}
}
// Show the rect that's being blurred.
{
GrPaint paint;
static constexpr float kAlpha = 0.3f;
paint.setColor4f({0, 0, 0, kAlpha});
SkPaint stroke;
stroke.setStyle(SkPaint::kStroke_Style);
stroke.setStrokeWidth(1.f);
GrStyle style(stroke);
auto srcR = SkRect::Make(srcRect).makeOutset(0.5f, 0.5f);
sdc->drawRect(nullptr, std::move(paint), GrAA::kNo, m, srcR, &style);
}
trans.fX += testArea.width() + kPad;
}
trans.fX = kPad;
trans.fY += testArea.height() + kPad;
}
}
DEF_SIMPLE_GPU_GM(gpu_blur_utils, ctx, sdc, canvas, 765, 955) { run(ctx, sdc, false, false); }
DEF_SIMPLE_GPU_GM(gpu_blur_utils_ref, ctx, sdc, canvas, 765, 955) { run(ctx, sdc, false, true); }
DEF_SIMPLE_GPU_GM(gpu_blur_utils_subset_rect, ctx, sdc, canvas, 485, 730) {
run(ctx, sdc, true, false);
}
DEF_SIMPLE_GPU_GM(gpu_blur_utils_subset_ref, ctx, sdc, canvas, 485, 730) {
run(ctx, sdc, true, true);
}
// Because of the way blur sigmas concat (sigTotal = sqrt(sig1^2 + sig2^2) generating these images
// for very large sigmas is incredibly slow. This can be enabled while working on the blur code to
// check results.
static bool constexpr kShowSlowRefImages = false;
static void do_very_large_blur_gm(GrSurfaceDrawContext* sdc,
GrRecordingContext* rContext,
GrSurfaceProxyView src,
SkIRect srcB) {
// Clear to a color other than gray to contrast with test image.
sdc->clear(SkColor4f{0.3f, 0.4f, 0.2f, 1});
int x = 10;
int y = 10;
for (auto blurDirs : {0b01, 0b10, 0b11}) {
for (int t = 0; t <= static_cast<int>(SkTileMode::kLastTileMode); ++t) {
auto tm = static_cast<SkTileMode>(t);
auto dstB = srcB.makeOutset(30, 30);
for (float sigma : {0.f, 5.f, 25.f, 80.f}) {
std::vector<decltype(blur)*> blurs;
blurs.push_back(blur);
if (kShowSlowRefImages) {
blurs.push_back(slow_blur);
}
for (auto b : blurs) {
float sigX = sigma*((blurDirs & 0b01) >> 0);
float sigY = sigma*((blurDirs & 0b10) >> 1);
GrSurfaceProxyView result = b(rContext, src, dstB, srcB, sigX, sigY, tm);
auto dstRect = SkIRect::MakeSize(dstB.size()).makeOffset(x, y);
// Draw a rect to show where the result should be so it's obvious if it's
// missing.
GrPaint paint;
paint.setColor4f(b == blur ? SkPMColor4f{0, 0, 1, 1} : SkPMColor4f{1, 0, 0, 1});
sdc->drawRect(nullptr,
std::move(paint),
GrAA::kNo,
SkMatrix::I(),
SkRect::Make(dstRect).makeOutset(0.5, 0.5),
&GrStyle::SimpleHairline());
if (result) {
std::unique_ptr<GrFragmentProcessor> fp =
GrTextureEffect::Make(std::move(result), kPremul_SkAlphaType);
fp = GrBlendFragmentProcessor::Make(std::move(fp),
/*dst=*/nullptr,
SkBlendMode::kSrcOver);
sdc->fillRectToRectWithFP(SkIRect::MakeSize(dstB.size()),
dstRect,
std::move(fp));
}
x += dstB.width() + 10;
}
}
x = 10;
y += dstB.height() + 10;
}
}
}
DEF_SIMPLE_GPU_GM(very_large_sigma_gpu_blur, ctx, sdc, canvas, 350, 1030) {
auto src = make_src_image(ctx, {15, 15});
auto srcB = SkIRect::MakeSize(src.dimensions());
do_very_large_blur_gm(sdc, ctx, std::move(src), srcB);
}
DEF_SIMPLE_GPU_GM(very_large_sigma_gpu_blur_subset, ctx, sdc, canvas, 350, 1030) {
auto srcB = SkIRect::MakeXYWH(2, 2, 15, 15);
SkISize imageSize = SkISize{srcB.width() + 4, srcB.height() + 4};
auto src = make_src_image(ctx, imageSize, &srcB);
do_very_large_blur_gm(sdc, ctx, std::move(src), srcB);
}
DEF_SIMPLE_GPU_GM(very_large_sigma_gpu_blur_subset_transparent_border,
ctx,
sdc,
canvas,
355, 1055) {
auto srcB = SkIRect::MakeXYWH(3, 3, 15, 15);
SkISize imageSize = SkISize{srcB.width() + 4, srcB.height() + 4};
auto src = make_src_image(ctx, imageSize, &srcB);
do_very_large_blur_gm(sdc, ctx, std::move(src), srcB.makeOutset(1, 1));
}