1/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8@header {
9    #include "GrProxyProvider.h"
10    #include "SkBlurMask.h"
11    #include "SkScalar.h"
12}
13
14in uniform float4 rect;
15in float sigma;
16in uniform sampler2D blurProfile;
17
18@constructorParams {
19    GrSamplerState samplerParams
20}
21
22@samplerParams(blurProfile) {
23    samplerParams
24}
25
26// in OpenGL ES, mediump floats have a minimum range of 2^14. If we have coordinates bigger than
27// that, the shader math will end up with infinities and result in the blur effect not working
28// correctly. To avoid this, we switch into highp when the coordinates are too big. As 2^14 is the
29// minimum range but the actual range can be bigger, we might end up switching to highp sooner than
30// strictly necessary, but most devices that have a bigger range for mediump also have mediump being
31// exactly the same as highp (e.g. all non-OpenGL ES devices), and thus incur no additional penalty
32// for the switch.
33layout(key) bool highPrecision = abs(rect.x) > 16000 || abs(rect.y) > 16000 ||
34                                 abs(rect.z) > 16000 || abs(rect.w) > 16000 ||
35                                 abs(rect.z - rect.x) > 16000 || abs(rect.w - rect.y) > 16000;
36
37layout(when=!highPrecision) uniform half4 proxyRectHalf;
38layout(when=highPrecision) uniform float4 proxyRectFloat;
39uniform half profileSize;
40
41
42@class {
43    static sk_sp<GrTextureProxy> CreateBlurProfileTexture(GrProxyProvider* proxyProvider,
44                                                          float sigma) {
45        unsigned int profileSize = SkScalarCeilToInt(6 * sigma);
46
47        static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
48        GrUniqueKey key;
49        GrUniqueKey::Builder builder(&key, kDomain, 1, "Rect Blur Mask");
50        builder[0] = profileSize;
51        builder.finish();
52
53        sk_sp<GrTextureProxy> blurProfile(proxyProvider->findOrCreateProxyByUniqueKey(
54                                                                    key, kTopLeft_GrSurfaceOrigin));
55        if (!blurProfile) {
56            SkImageInfo ii = SkImageInfo::MakeA8(profileSize, 1);
57
58            SkBitmap bitmap;
59            if (!bitmap.tryAllocPixels(ii)) {
60                return nullptr;
61            }
62
63            SkBlurMask::ComputeBlurProfile(bitmap.getAddr8(0, 0), profileSize, sigma);
64            bitmap.setImmutable();
65
66            sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
67            if (!image) {
68                return nullptr;
69            }
70
71            blurProfile = proxyProvider->createTextureProxy(std::move(image), kNone_GrSurfaceFlags,
72                                                            1, SkBudgeted::kYes,
73                                                            SkBackingFit::kExact);
74            if (!blurProfile) {
75                return nullptr;
76            }
77
78            SkASSERT(blurProfile->origin() == kTopLeft_GrSurfaceOrigin);
79            proxyProvider->assignUniqueKeyToProxy(key, blurProfile.get());
80        }
81
82        return blurProfile;
83    }
84}
85
86@make {
87     static std::unique_ptr<GrFragmentProcessor> Make(GrProxyProvider* proxyProvider,
88                                                      const GrShaderCaps& caps,
89                                                      const SkRect& rect, float sigma) {
90         if (!caps.floatIs32Bits()) {
91             // We promote the rect uniform from half to float when it has large values for
92             // precision. If we don't have full float then fail.
93             if (SkScalarAbs(rect.fLeft) > 16000.f || SkScalarAbs(rect.fTop) > 16000.f ||
94                 SkScalarAbs(rect.fRight) > 16000.f || SkScalarAbs(rect.fBottom) > 16000.f ||
95                 SkScalarAbs(rect.width()) > 16000.f || SkScalarAbs(rect.height()) > 16000.f) {
96                 return nullptr;
97             }
98         }
99         int doubleProfileSize = SkScalarCeilToInt(12*sigma);
100
101         if (doubleProfileSize >= rect.width() || doubleProfileSize >= rect.height()) {
102             // if the blur sigma is too large so the gaussian overlaps the whole
103             // rect in either direction, fall back to CPU path for now.
104             return nullptr;
105         }
106
107         sk_sp<GrTextureProxy> blurProfile(CreateBlurProfileTexture(proxyProvider, sigma));
108         if (!blurProfile) {
109            return nullptr;
110         }
111
112         return std::unique_ptr<GrFragmentProcessor>(new GrRectBlurEffect(
113            rect, sigma, std::move(blurProfile),
114            GrSamplerState(GrSamplerState::WrapMode::kClamp, GrSamplerState::Filter::kBilerp)));
115     }
116}
117
118void main() {
119    @if (highPrecision) {
120        float2 translatedPos = sk_FragCoord.xy - rect.xy;
121        float width = rect.z - rect.x;
122        float height = rect.w - rect.y;
123        float2 smallDims = float2(width - profileSize, height - profileSize);
124        float center = 2 * floor(profileSize / 2 + 0.25) - 1;
125        float2 wh = smallDims - float2(center, center);
126        half hcoord = ((abs(translatedPos.x - 0.5 * width) - 0.5 * wh.x)) / profileSize;
127        half hlookup = texture(blurProfile, float2(hcoord, 0.5)).a;
128        half vcoord = ((abs(translatedPos.y - 0.5 * height) - 0.5 * wh.y)) / profileSize;
129        half vlookup = texture(blurProfile, float2(vcoord, 0.5)).a;
130        sk_OutColor = sk_InColor * hlookup * vlookup;
131    } else {
132        half2 translatedPos = sk_FragCoord.xy - rect.xy;
133        half width = rect.z - rect.x;
134        half height = rect.w - rect.y;
135        half2 smallDims = half2(width - profileSize, height - profileSize);
136        half center = 2 * floor(profileSize / 2 + 0.25) - 1;
137        half2 wh = smallDims - float2(center, center);
138        half hcoord = ((abs(translatedPos.x - 0.5 * width) - 0.5 * wh.x)) / profileSize;
139        half hlookup = texture(blurProfile, float2(hcoord, 0.5)).a;
140        half vcoord = ((abs(translatedPos.y - 0.5 * height) - 0.5 * wh.y)) / profileSize;
141        half vlookup = texture(blurProfile, float2(vcoord, 0.5)).a;
142        sk_OutColor = sk_InColor * hlookup * vlookup;
143    }
144}
145
146@setData(pdman) {
147    pdman.set1f(profileSize, SkScalarCeilToScalar(6 * sigma));
148}
149
150@optimizationFlags { kCompatibleWithCoverageAsAlpha_OptimizationFlag }
151
152@test(data) {
153    float sigma = data->fRandom->nextRangeF(3,8);
154    float width = data->fRandom->nextRangeF(200,300);
155    float height = data->fRandom->nextRangeF(200,300);
156    return GrRectBlurEffect::Make(data->proxyProvider(), *data->caps()->shaderCaps(),
157                                  SkRect::MakeWH(width, height), sigma);
158}
159