blob: 50f833c59d431cd72ac7b22e61dfb67d6cd74e18 [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.
*/
@header {
#include "GrProxyProvider.h"
#include "GrShaderCaps.h"
#include "SkBlurMask.h"
#include "SkScalar.h"
}
in uniform float4 rect;
in float sigma;
in uniform sampler2D blurProfile;
@constructorParams {
GrSamplerState samplerParams
}
@samplerParams(blurProfile) {
samplerParams
}
// in OpenGL ES, mediump floats have a minimum range of 2^14. If we have coordinates bigger than
// that, the shader math will end up with infinities and result in the blur effect not working
// correctly. To avoid this, we switch into highp when the coordinates are too big. As 2^14 is the
// minimum range but the actual range can be bigger, we might end up switching to highp sooner than
// strictly necessary, but most devices that have a bigger range for mediump also have mediump being
// exactly the same as highp (e.g. all non-OpenGL ES devices), and thus incur no additional penalty
// for the switch.
layout(key) bool highPrecision = abs(rect.x) > 16000 || abs(rect.y) > 16000 ||
abs(rect.z) > 16000 || abs(rect.w) > 16000 ||
abs(rect.z - rect.x) > 16000 || abs(rect.w - rect.y) > 16000;
layout(when=!highPrecision) uniform half4 proxyRectHalf;
layout(when=highPrecision) uniform float4 proxyRectFloat;
uniform half profileSize;
@class {
static sk_sp<GrTextureProxy> CreateBlurProfileTexture(GrProxyProvider* proxyProvider,
float sigma) {
unsigned int profileSize = SkScalarCeilToInt(6 * sigma);
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey key;
GrUniqueKey::Builder builder(&key, kDomain, 1, "Rect Blur Mask");
builder[0] = profileSize;
builder.finish();
sk_sp<GrTextureProxy> blurProfile(proxyProvider->findOrCreateProxyByUniqueKey(
key, kTopLeft_GrSurfaceOrigin));
if (!blurProfile) {
SkImageInfo ii = SkImageInfo::MakeA8(profileSize, 1);
SkBitmap bitmap;
if (!bitmap.tryAllocPixels(ii)) {
return nullptr;
}
SkBlurMask::ComputeBlurProfile(bitmap.getAddr8(0, 0), profileSize, sigma);
bitmap.setImmutable();
sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
if (!image) {
return nullptr;
}
blurProfile = proxyProvider->createTextureProxy(std::move(image), kNone_GrSurfaceFlags,
1, SkBudgeted::kYes,
SkBackingFit::kExact);
if (!blurProfile) {
return nullptr;
}
SkASSERT(blurProfile->origin() == kTopLeft_GrSurfaceOrigin);
proxyProvider->assignUniqueKeyToProxy(key, blurProfile.get());
}
return blurProfile;
}
}
@make {
static std::unique_ptr<GrFragmentProcessor> Make(GrProxyProvider* proxyProvider,
const GrShaderCaps& caps,
const SkRect& rect, float sigma) {
if (!caps.floatIs32Bits()) {
// We promote the rect uniform from half to float when it has large values for
// precision. If we don't have full float then fail.
if (SkScalarAbs(rect.fLeft) > 16000.f || SkScalarAbs(rect.fTop) > 16000.f ||
SkScalarAbs(rect.fRight) > 16000.f || SkScalarAbs(rect.fBottom) > 16000.f ||
SkScalarAbs(rect.width()) > 16000.f || SkScalarAbs(rect.height()) > 16000.f) {
return nullptr;
}
}
int doubleProfileSize = SkScalarCeilToInt(12*sigma);
if (doubleProfileSize >= rect.width() || doubleProfileSize >= rect.height()) {
// if the blur sigma is too large so the gaussian overlaps the whole
// rect in either direction, fall back to CPU path for now.
return nullptr;
}
sk_sp<GrTextureProxy> blurProfile(CreateBlurProfileTexture(proxyProvider, sigma));
if (!blurProfile) {
return nullptr;
}
return std::unique_ptr<GrFragmentProcessor>(new GrRectBlurEffect(
rect, sigma, std::move(blurProfile),
GrSamplerState(GrSamplerState::WrapMode::kClamp, GrSamplerState::Filter::kBilerp)));
}
}
void main() {
@if (highPrecision) {
float2 translatedPos = sk_FragCoord.xy - rect.xy;
float width = rect.z - rect.x;
float height = rect.w - rect.y;
float2 smallDims = float2(width - profileSize, height - profileSize);
float center = 2 * floor(profileSize / 2 + 0.25) - 1;
float2 wh = smallDims - float2(center, center);
half hcoord = half(((abs(translatedPos.x - 0.5 * width) - 0.5 * wh.x)) / profileSize);
half hlookup = texture(blurProfile, float2(hcoord, 0.5)).a;
half vcoord = half(((abs(translatedPos.y - 0.5 * height) - 0.5 * wh.y)) / profileSize);
half vlookup = texture(blurProfile, float2(vcoord, 0.5)).a;
sk_OutColor = sk_InColor * hlookup * vlookup;
} else {
half2 translatedPos = half2(sk_FragCoord.xy - rect.xy);
half width = half(rect.z - rect.x);
half height = half(rect.w - rect.y);
half2 smallDims = half2(width - profileSize, height - profileSize);
half center = 2 * floor(profileSize / 2 + 0.25) - 1;
half2 wh = smallDims - half2(center, center);
half hcoord = ((half(abs(translatedPos.x - 0.5 * width)) - 0.5 * wh.x)) / profileSize;
half hlookup = texture(blurProfile, float2(hcoord, 0.5)).a;
half vcoord = ((half(abs(translatedPos.y - 0.5 * height)) - 0.5 * wh.y)) / profileSize;
half vlookup = texture(blurProfile, float2(vcoord, 0.5)).a;
sk_OutColor = sk_InColor * hlookup * vlookup;
}
}
@setData(pdman) {
pdman.set1f(profileSize, SkScalarCeilToScalar(6 * sigma));
}
@optimizationFlags { kCompatibleWithCoverageAsAlpha_OptimizationFlag }
@test(data) {
float sigma = data->fRandom->nextRangeF(3,8);
float width = data->fRandom->nextRangeF(200,300);
float height = data->fRandom->nextRangeF(200,300);
return GrRectBlurEffect::Make(data->proxyProvider(), *data->caps()->shaderCaps(),
SkRect::MakeWH(width, height), sigma);
}