blob: 562b57260119eb1eb537dc525d386b93786e01ca [file] [log] [blame]
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/**************************************************************************************************
*** This file was autogenerated from GrRRectBlurEffect.fp; do not modify.
**************************************************************************************************/
#include "GrRRectBlurEffect.h"
#include "include/gpu/GrDirectContext.h"
#include "include/gpu/GrRecordingContext.h"
#include "src/core/SkAutoMalloc.h"
#include "src/core/SkGpuBlurUtils.h"
#include "src/core/SkRRectPriv.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrDirectContextPriv.h"
#include "src/gpu/GrPaint.h"
#include "src/gpu/GrProxyProvider.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrStyle.h"
#include "src/gpu/GrSurfaceDrawContext.h"
#include "src/gpu/GrThreadSafeCache.h"
#include "src/gpu/SkGr.h"
#include "src/gpu/effects/GrTextureEffect.h"
static constexpr auto kBlurredRRectMaskOrigin = kTopLeft_GrSurfaceOrigin;
static void make_blurred_rrect_key(GrUniqueKey* key,
const SkRRect& rrectToDraw,
float xformedSigma) {
SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey::Builder builder(key, kDomain, 9, "RoundRect Blur Mask");
builder[0] = SkScalarCeilToInt(xformedSigma - 1 / 6.0f);
int index = 1;
// TODO: this is overkill for _simple_ circular rrects
for (auto c : {SkRRect::kUpperLeft_Corner,
SkRRect::kUpperRight_Corner,
SkRRect::kLowerRight_Corner,
SkRRect::kLowerLeft_Corner}) {
SkASSERT(SkScalarIsInt(rrectToDraw.radii(c).fX) && SkScalarIsInt(rrectToDraw.radii(c).fY));
builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fX);
builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fY);
}
builder.finish();
}
static bool fillin_view_on_gpu(GrDirectContext* dContext,
const GrSurfaceProxyView& lazyView,
sk_sp<GrThreadSafeCache::Trampoline> trampoline,
const SkRRect& rrectToDraw,
const SkISize& dimensions,
float xformedSigma) {
SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
// We cache blur masks. Use default surface props here so we can use the same cached mask
// regardless of the final dst surface.
SkSurfaceProps defaultSurfaceProps;
std::unique_ptr<GrSurfaceDrawContext> rtc =
GrSurfaceDrawContext::MakeWithFallback(dContext,
GrColorType::kAlpha_8,
nullptr,
SkBackingFit::kExact,
dimensions,
defaultSurfaceProps,
1,
GrMipmapped::kNo,
GrProtected::kNo,
kBlurredRRectMaskOrigin);
if (!rtc) {
return false;
}
GrPaint paint;
rtc->clear(SK_PMColor4fTRANSPARENT);
rtc->drawRRect(nullptr,
std::move(paint),
GrAA::kYes,
SkMatrix::I(),
rrectToDraw,
GrStyle::SimpleFill());
GrSurfaceProxyView srcView = rtc->readSurfaceView();
SkASSERT(srcView.asTextureProxy());
auto rtc2 = SkGpuBlurUtils::GaussianBlur(dContext,
std::move(srcView),
rtc->colorInfo().colorType(),
rtc->colorInfo().alphaType(),
nullptr,
SkIRect::MakeSize(dimensions),
SkIRect::MakeSize(dimensions),
xformedSigma,
xformedSigma,
SkTileMode::kClamp,
SkBackingFit::kExact);
if (!rtc2 || !rtc2->readSurfaceView()) {
return false;
}
auto view = rtc2->readSurfaceView();
SkASSERT(view.swizzle() == lazyView.swizzle());
SkASSERT(view.origin() == lazyView.origin());
trampoline->fProxy = view.asTextureProxyRef();
return true;
}
// Evaluate the vertical blur at the specified 'y' value given the location of the top of the
// rrect.
static uint8_t eval_V(float top, int y, const uint8_t* integral, int integralSize, float sixSigma) {
if (top < 0) {
return 0; // an empty column
}
float fT = (top - y - 0.5f) * (integralSize / sixSigma);
if (fT < 0) {
return 255;
} else if (fT >= integralSize - 1) {
return 0;
}
int lower = (int)fT;
float frac = fT - lower;
SkASSERT(lower + 1 < integralSize);
return integral[lower] * (1.0f - frac) + integral[lower + 1] * frac;
}
// Apply a gaussian 'kernel' horizontally at the specified 'x', 'y' location.
static uint8_t eval_H(int x,
int y,
const std::vector<float>& topVec,
const float* kernel,
int kernelSize,
const uint8_t* integral,
int integralSize,
float sixSigma) {
SkASSERT(0 <= x && x < (int)topVec.size());
SkASSERT(kernelSize % 2);
float accum = 0.0f;
int xSampleLoc = x - (kernelSize / 2);
for (int i = 0; i < kernelSize; ++i, ++xSampleLoc) {
if (xSampleLoc < 0 || xSampleLoc >= (int)topVec.size()) {
continue;
}
accum += kernel[i] * eval_V(topVec[xSampleLoc], y, integral, integralSize, sixSigma);
}
return accum + 0.5f;
}
// Create a cpu-side blurred-rrect mask that is close to the version the gpu would've produced.
// The match needs to be close bc the cpu- and gpu-generated version must be interchangeable.
static GrSurfaceProxyView create_mask_on_cpu(GrRecordingContext* rContext,
const SkRRect& rrectToDraw,
const SkISize& dimensions,
float xformedSigma) {
SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
int radius = SkGpuBlurUtils::SigmaRadius(xformedSigma);
int kernelSize = 2 * radius + 1;
SkASSERT(kernelSize % 2);
SkASSERT(dimensions.width() % 2);
SkASSERT(dimensions.height() % 2);
SkVector radii = rrectToDraw.getSimpleRadii();
SkASSERT(SkScalarNearlyEqual(radii.fX, radii.fY));
const int halfWidthPlus1 = (dimensions.width() / 2) + 1;
const int halfHeightPlus1 = (dimensions.height() / 2) + 1;
std::unique_ptr<float[]> kernel(new float[kernelSize]);
SkGpuBlurUtils::Compute1DGaussianKernel(kernel.get(), xformedSigma, radius);
SkBitmap integral;
if (!SkGpuBlurUtils::CreateIntegralTable(6 * xformedSigma, &integral)) {
return {};
}
SkBitmap result;
if (!result.tryAllocPixels(SkImageInfo::MakeA8(dimensions.width(), dimensions.height()))) {
return {};
}
std::vector<float> topVec;
topVec.reserve(dimensions.width());
for (int x = 0; x < dimensions.width(); ++x) {
if (x < rrectToDraw.rect().fLeft || x > rrectToDraw.rect().fRight) {
topVec.push_back(-1);
} else {
if (x + 0.5f < rrectToDraw.rect().fLeft + radii.fX) { // in the circular section
float xDist = rrectToDraw.rect().fLeft + radii.fX - x - 0.5f;
float h = sqrtf(radii.fX * radii.fX - xDist * xDist);
SkASSERT(0 <= h && h < radii.fY);
topVec.push_back(rrectToDraw.rect().fTop + radii.fX - h + 3 * xformedSigma);
} else {
topVec.push_back(rrectToDraw.rect().fTop + 3 * xformedSigma);
}
}
}
for (int y = 0; y < halfHeightPlus1; ++y) {
uint8_t* scanline = result.getAddr8(0, y);
for (int x = 0; x < halfWidthPlus1; ++x) {
scanline[x] = eval_H(x,
y,
topVec,
kernel.get(),
kernelSize,
integral.getAddr8(0, 0),
integral.width(),
6 * xformedSigma);
scanline[dimensions.width() - x - 1] = scanline[x];
}
memcpy(result.getAddr8(0, dimensions.height() - y - 1), scanline, result.rowBytes());
}
result.setImmutable();
auto view = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, result));
if (!view) {
return {};
}
SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
return view;
}
static std::unique_ptr<GrFragmentProcessor> find_or_create_rrect_blur_mask_fp(
GrRecordingContext* rContext,
const SkRRect& rrectToDraw,
const SkISize& dimensions,
float xformedSigma) {
SkASSERT(!SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma));
GrUniqueKey key;
make_blurred_rrect_key(&key, rrectToDraw, xformedSigma);
auto threadSafeCache = rContext->priv().threadSafeCache();
// It seems like we could omit this matrix and modify the shader code to not normalize
// the coords used to sample the texture effect. However, the "proxyDims" value in the
// shader is not always the actual the proxy dimensions. This is because 'dimensions' here
// was computed using integer corner radii as determined in
// SkComputeBlurredRRectParams whereas the shader code uses the float radius to compute
// 'proxyDims'. Why it draws correctly with these unequal values is a mystery for the ages.
auto m = SkMatrix::Scale(dimensions.width(), dimensions.height());
GrSurfaceProxyView view;
if (GrDirectContext* dContext = rContext->asDirectContext()) {
// The gpu thread gets priority over the recording threads. If the gpu thread is first,
// it crams a lazy proxy into the cache and then fills it in later.
auto[lazyView, trampoline] = GrThreadSafeCache::CreateLazyView(dContext,
GrColorType::kAlpha_8,
dimensions,
kBlurredRRectMaskOrigin,
SkBackingFit::kExact);
if (!lazyView) {
return nullptr;
}
view = threadSafeCache->findOrAdd(key, lazyView);
if (view != lazyView) {
SkASSERT(view.asTextureProxy());
SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
}
if (!fillin_view_on_gpu(dContext,
lazyView,
std::move(trampoline),
rrectToDraw,
dimensions,
xformedSigma)) {
// In this case something has gone disastrously wrong so set up to drop the draw
// that needed this resource and reduce future pollution of the cache.
threadSafeCache->remove(key);
return nullptr;
}
} else {
view = threadSafeCache->find(key);
if (view) {
SkASSERT(view.asTextureProxy());
SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
}
view = create_mask_on_cpu(rContext, rrectToDraw, dimensions, xformedSigma);
if (!view) {
return nullptr;
}
view = threadSafeCache->add(key, view);
}
SkASSERT(view.asTextureProxy());
SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
}
std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::Make(
std::unique_ptr<GrFragmentProcessor> inputFP,
GrRecordingContext* context,
float sigma,
float xformedSigma,
const SkRRect& srcRRect,
const SkRRect& devRRect) {
// Should've been caught up-stream
#ifdef SK_DEBUG
SkASSERTF(!SkRRectPriv::IsCircle(devRRect),
"Unexpected circle. %d\n\t%s\n\t%s",
SkRRectPriv::IsCircle(srcRRect),
srcRRect.dumpToString(true).c_str(),
devRRect.dumpToString(true).c_str());
SkASSERTF(!devRRect.isRect(),
"Unexpected rect. %d\n\t%s\n\t%s",
srcRRect.isRect(),
srcRRect.dumpToString(true).c_str(),
devRRect.dumpToString(true).c_str());
#endif
// TODO: loosen this up
if (!SkRRectPriv::IsSimpleCircular(devRRect)) {
return nullptr;
}
if (SkGpuBlurUtils::IsEffectivelyZeroSigma(xformedSigma)) {
return inputFP;
}
// Make sure we can successfully ninepatch this rrect -- the blur sigma has to be
// sufficiently small relative to both the size of the corner radius and the
// width (and height) of the rrect.
SkRRect rrectToDraw;
SkISize dimensions;
SkScalar ignored[SkGpuBlurUtils::kBlurRRectMaxDivisions];
bool ninePatchable = SkGpuBlurUtils::ComputeBlurredRRectParams(srcRRect,
devRRect,
sigma,
xformedSigma,
&rrectToDraw,
&dimensions,
ignored,
ignored,
ignored,
ignored);
if (!ninePatchable) {
return nullptr;
}
std::unique_ptr<GrFragmentProcessor> maskFP =
find_or_create_rrect_blur_mask_fp(context, rrectToDraw, dimensions, xformedSigma);
if (!maskFP) {
return nullptr;
}
return std::unique_ptr<GrFragmentProcessor>(
new GrRRectBlurEffect(std::move(inputFP),
xformedSigma,
devRRect.getBounds(),
SkRRectPriv::GetSimpleRadii(devRRect).fX,
std::move(maskFP)));
}
#include "src/core/SkUtils.h"
#include "src/gpu/GrTexture.h"
#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
#include "src/sksl/SkSLCPP.h"
#include "src/sksl/SkSLUtil.h"
class GrGLSLRRectBlurEffect : public GrGLSLFragmentProcessor {
public:
GrGLSLRRectBlurEffect() {}
void emitCode(EmitArgs& args) override {
GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
const GrRRectBlurEffect& _outer = args.fFp.cast<GrRRectBlurEffect>();
(void)_outer;
auto sigma = _outer.sigma;
(void)sigma;
auto rect = _outer.rect;
(void)rect;
auto cornerRadius = _outer.cornerRadius;
(void)cornerRadius;
cornerRadiusVar = args.fUniformHandler->addUniform(
&_outer, kFragment_GrShaderFlag, kHalf_GrSLType, "cornerRadius");
proxyRectVar = args.fUniformHandler->addUniform(
&_outer, kFragment_GrShaderFlag, kFloat4_GrSLType, "proxyRect");
blurRadiusVar = args.fUniformHandler->addUniform(
&_outer, kFragment_GrShaderFlag, kHalf_GrSLType, "blurRadius");
fragBuilder->codeAppendf(
R"SkSL(float2 translatedFragPosFloat = sk_FragCoord.xy - %s.xy;
float2 proxyCenter = (%s.zw - %s.xy) * 0.5;
half edgeSize = (2.0 * %s + %s) + 0.5;
translatedFragPosFloat -= proxyCenter;
half2 fragDirection = half2(sign(translatedFragPosFloat));
translatedFragPosFloat = abs(translatedFragPosFloat);
half2 translatedFragPosHalf = half2(translatedFragPosFloat - (proxyCenter - float(edgeSize)));
translatedFragPosHalf = max(translatedFragPosHalf, 0.0);
translatedFragPosHalf *= fragDirection;
translatedFragPosHalf += half2(edgeSize);
half2 proxyDims = half2(2.0 * edgeSize);
half2 texCoord = translatedFragPosHalf / proxyDims;)SkSL",
args.fUniformHandler->getUniformCStr(proxyRectVar),
args.fUniformHandler->getUniformCStr(proxyRectVar),
args.fUniformHandler->getUniformCStr(proxyRectVar),
args.fUniformHandler->getUniformCStr(blurRadiusVar),
args.fUniformHandler->getUniformCStr(cornerRadiusVar));
SkString _sample0 = this->invokeChild(0, args);
SkString _coords1("float2(texCoord)");
SkString _sample1 = this->invokeChild(1, args, _coords1.c_str());
fragBuilder->codeAppendf(
R"SkSL(
return %s * %s.w;
)SkSL",
_sample0.c_str(),
_sample1.c_str());
}
private:
void onSetData(const GrGLSLProgramDataManager& pdman,
const GrFragmentProcessor& _proc) override {
const GrRRectBlurEffect& _outer = _proc.cast<GrRRectBlurEffect>();
{ pdman.set1f(cornerRadiusVar, _outer.cornerRadius); }
auto sigma = _outer.sigma;
(void)sigma;
auto rect = _outer.rect;
(void)rect;
UniformHandle& cornerRadius = cornerRadiusVar;
(void)cornerRadius;
UniformHandle& proxyRect = proxyRectVar;
(void)proxyRect;
UniformHandle& blurRadius = blurRadiusVar;
(void)blurRadius;
float blurRadiusValue = 3.f * SkScalarCeilToScalar(sigma - 1 / 6.0f);
pdman.set1f(blurRadius, blurRadiusValue);
SkRect outset = rect;
outset.outset(blurRadiusValue, blurRadiusValue);
pdman.set4f(proxyRect, outset.fLeft, outset.fTop, outset.fRight, outset.fBottom);
}
UniformHandle proxyRectVar;
UniformHandle blurRadiusVar;
UniformHandle cornerRadiusVar;
};
std::unique_ptr<GrGLSLFragmentProcessor> GrRRectBlurEffect::onMakeProgramImpl() const {
return std::make_unique<GrGLSLRRectBlurEffect>();
}
void GrRRectBlurEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
GrProcessorKeyBuilder* b) const {}
bool GrRRectBlurEffect::onIsEqual(const GrFragmentProcessor& other) const {
const GrRRectBlurEffect& that = other.cast<GrRRectBlurEffect>();
(void)that;
if (sigma != that.sigma) return false;
if (rect != that.rect) return false;
if (cornerRadius != that.cornerRadius) return false;
return true;
}
GrRRectBlurEffect::GrRRectBlurEffect(const GrRRectBlurEffect& src)
: INHERITED(kGrRRectBlurEffect_ClassID, src.optimizationFlags())
, sigma(src.sigma)
, rect(src.rect)
, cornerRadius(src.cornerRadius) {
this->cloneAndRegisterAllChildProcessors(src);
}
std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::clone() const {
return std::make_unique<GrRRectBlurEffect>(*this);
}
#if GR_TEST_UTILS
SkString GrRRectBlurEffect::onDumpInfo() const {
return SkStringPrintf("(sigma=%f, rect=float4(%f, %f, %f, %f), cornerRadius=%f)",
sigma,
rect.left(),
rect.top(),
rect.right(),
rect.bottom(),
cornerRadius);
}
#endif
GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRRectBlurEffect);
#if GR_TEST_UTILS
std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::TestCreate(GrProcessorTestData* d) {
SkScalar w = d->fRandom->nextRangeScalar(100.f, 1000.f);
SkScalar h = d->fRandom->nextRangeScalar(100.f, 1000.f);
SkScalar r = d->fRandom->nextRangeF(1.f, 9.f);
SkScalar sigma = d->fRandom->nextRangeF(1.f, 10.f);
SkRRect rrect;
rrect.setRectXY(SkRect::MakeWH(w, h), r, r);
return GrRRectBlurEffect::Make(d->inputFP(), d->context(), sigma, sigma, rrect, rrect);
}
#endif