blob: e8cb928439df0847f846c2db0b7578e1834e17f1 [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.
*/
in fragmentProcessor? inputFP;
in float sigma;
layout(ctype=SkRect) in float4 rect;
in uniform half cornerRadius;
in fragmentProcessor ninePatchFP;
layout(ctype=SkRect) uniform float4 proxyRect;
uniform half blurRadius;
@header {
#include "include/gpu/GrContext.h"
#include "include/private/GrRecordingContext.h"
#include "src/core/SkBlurPriv.h"
#include "src/core/SkGpuBlurUtils.h"
#include "src/core/SkRRectPriv.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrPaint.h"
#include "src/gpu/GrProxyProvider.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrRenderTargetContext.h"
#include "src/gpu/GrStyle.h"
#include "src/gpu/effects/GrTextureEffect.h"
}
@class {
static std::unique_ptr<GrFragmentProcessor> find_or_create_rrect_blur_mask_fp(
GrRecordingContext* context,
const SkRRect& rrectToDraw,
const SkISize& dimensions,
float xformedSigma) {
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey key;
GrUniqueKey::Builder builder(&key, kDomain, 9, "RoundRect Blur Mask");
builder[0] = SkScalarCeilToInt(xformedSigma-1/6.0f);
int index = 1;
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();
// 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());
static constexpr auto kMaskOrigin = kBottomLeft_GrSurfaceOrigin;
GrProxyProvider* proxyProvider = context->priv().proxyProvider();
if (auto view = proxyProvider->findCachedProxyWithColorTypeFallback(
key, kMaskOrigin, GrColorType::kAlpha_8, 1)) {
return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
}
auto rtc = GrRenderTargetContext::MakeWithFallback(
context, GrColorType::kAlpha_8, nullptr, SkBackingFit::kExact, dimensions, 1,
GrMipMapped::kNo, GrProtected::kNo, kMaskOrigin);
if (!rtc) {
return nullptr;
}
GrPaint paint;
rtc->clear(SK_PMColor4fTRANSPARENT);
rtc->drawRRect(nullptr, std::move(paint), GrAA::kYes, SkMatrix::I(), rrectToDraw,
GrStyle::SimpleFill());
GrSurfaceProxyView srcView = rtc->readSurfaceView();
if (!srcView) {
return nullptr;
}
SkASSERT(srcView.asTextureProxy());
auto rtc2 = SkGpuBlurUtils::GaussianBlur(context,
std::move(srcView),
rtc->colorInfo().colorType(),
rtc->colorInfo().alphaType(),
nullptr,
SkIRect::MakeSize(dimensions),
SkIRect::MakeSize(dimensions),
xformedSigma,
xformedSigma,
SkTileMode::kClamp,
SkBackingFit::kExact);
if (!rtc2) {
return nullptr;
}
GrSurfaceProxyView mask = rtc2->readSurfaceView();
if (!mask) {
return nullptr;
}
SkASSERT(mask.asTextureProxy());
SkASSERT(mask.origin() == kMaskOrigin);
proxyProvider->assignUniqueKeyToProxy(key, mask.asTextureProxy());
return GrTextureEffect::Make(std::move(mask), kPremul_SkAlphaType, m);
}
}
@optimizationFlags {
(inputFP ? ProcessorOptimizationFlags(inputFP.get()) : kAll_OptimizationFlags) &
kCompatibleWithCoverageAsAlpha_OptimizationFlag
}
@make {
static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> inputFP,
GrRecordingContext* context,
float sigma,
float xformedSigma,
const SkRRect& srcRRect,
const SkRRect& devRRect);
}
@cpp {
std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::Make(
std::unique_ptr<GrFragmentProcessor> inputFP,
GrRecordingContext* context,
float sigma,
float xformedSigma,
const SkRRect& srcRRect,
const SkRRect& devRRect) {
SkASSERT(!SkRRectPriv::IsCircle(devRRect) && !devRRect.isRect()); // Should've been caught up-stream
// TODO: loosen this up
if (!SkRRectPriv::IsSimpleCircular(devRRect)) {
return nullptr;
}
// 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[kSkBlurRRectMaxDivisions];
int ignoredSize;
uint32_t ignored32;
bool ninePatchable = SkComputeBlurredRRectParams(srcRRect, devRRect,
SkRect::MakeEmpty(),
sigma, xformedSigma,
&rrectToDraw, &dimensions,
ignored, ignored,
ignored, ignored,
&ignoredSize, &ignoredSize,
&ignored32);
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)));
}
}
@test(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(/*inputFP=*/nullptr, d->context(), sigma, sigma, rrect, rrect);
}
void main() {
// Warp the fragment position to the appropriate part of the 9-patch blur texture by snipping
// out the middle section of the proxy rect.
half2 translatedFragPos = half2(sk_FragCoord.xy - proxyRect.LT);
half2 proxyCenter = half2((proxyRect.RB - proxyRect.LT) * 0.5);
half edgeSize = 2.0 * blurRadius + cornerRadius + 0.5;
// Position the fragment so that (0, 0) marks the center of the proxy rectangle.
// Negative coordinates are on the left/top side and positive numbers are on the right/bottom.
translatedFragPos -= proxyCenter;
// Temporarily strip off the fragment's sign. x/y are now strictly increasing as we move away
// from the center.
half2 fragDirection = sign(translatedFragPos);
translatedFragPos = abs(translatedFragPos);
// Our goal is to snip out the "middle section" of the proxy rect (everything but the edge).
// We've repositioned our fragment position so that (0, 0) is the centerpoint and x/y are always
// positive, so we can subtract here and interpret negative results as being within the middle
// section.
translatedFragPos -= proxyCenter - edgeSize;
// Remove the middle section by clamping to zero.
translatedFragPos = max(translatedFragPos, 0);
// Reapply the fragment's sign, so that negative coordinates once again mean left/top side and
// positive means bottom/right side.
translatedFragPos *= fragDirection;
// Offset the fragment so that (0, 0) marks the upper-left again, instead of the center point.
translatedFragPos += half2(edgeSize);
half2 proxyDims = half2(2.0 * edgeSize);
half2 texCoord = translatedFragPos / proxyDims;
half4 inputColor = sample(inputFP, sk_InColor);
sk_OutColor = inputColor * sample(ninePatchFP, texCoord);
}
@setData(pdman) {
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);
}