/* * 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 "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 = ((abs(translatedPos.x - 0.5 * width) - 0.5 * wh.x)) / profileSize; half hlookup = texture(blurProfile, float2(hcoord, 0.5)).a; half vcoord = ((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 = sk_FragCoord.xy - rect.xy; half width = rect.z - rect.x; half height = rect.w - rect.y; half2 smallDims = half2(width - profileSize, height - profileSize); half center = 2 * floor(profileSize / 2 + 0.25) - 1; half2 wh = smallDims - float2(center, center); half hcoord = ((abs(translatedPos.x - 0.5 * width) - 0.5 * wh.x)) / profileSize; half hlookup = texture(blurProfile, float2(hcoord, 0.5)).a; half vcoord = ((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); }