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 "../effects/SkBlurMask.h"
11}
12
13in uniform float4 rect;
14in float sigma;
15in uniform sampler2D blurProfile;
16
17@constructorParams {
18    GrSamplerState samplerParams
19}
20
21@samplerParams(blurProfile) {
22    samplerParams
23}
24
25// in OpenGL ES, mediump floats have a minimum range of 2^14. If we have coordinates bigger than
26// that, the shader math will end up with infinities and result in the blur effect not working
27// correctly. To avoid this, we switch into highp when the coordinates are too big. As 2^14 is the
28// minimum range but the actual range can be bigger, we might end up switching to highp sooner than
29// strictly necessary, but most devices that have a bigger range for mediump also have mediump being
30// exactly the same as highp (e.g. all non-OpenGL ES devices), and thus incur no additional penalty
31// for the switch.
32layout(key) bool highPrecision = abs(rect.x) > 16000 || abs(rect.y) > 16000 ||
33                                 abs(rect.z) > 16000 || abs(rect.w) > 16000 ||
34                                 abs(rect.z - rect.x) > 16000 || abs(rect.w - rect.y) > 16000;
35
36layout(when=!highPrecision) uniform half4 proxyRectHalf;
37layout(when=highPrecision) uniform float4 proxyRectFloat;
38uniform half profileSize;
39
40
41@class {
42    static sk_sp<GrTextureProxy> CreateBlurProfileTexture(GrProxyProvider* proxyProvider,
43                                                          float sigma) {
44        unsigned int profileSize = SkScalarCeilToInt(6 * sigma);
45
46        static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
47        GrUniqueKey key;
48        GrUniqueKey::Builder builder(&key, kDomain, 1);
49        builder[0] = profileSize;
50        builder.finish();
51
52        sk_sp<GrTextureProxy> blurProfile(proxyProvider->findOrCreateProxyByUniqueKey(
53                                                                    key, kTopLeft_GrSurfaceOrigin));
54        if (!blurProfile) {
55            GrSurfaceDesc texDesc;
56            texDesc.fOrigin = kTopLeft_GrSurfaceOrigin;
57            texDesc.fWidth = profileSize;
58            texDesc.fHeight = 1;
59            texDesc.fConfig = kAlpha_8_GrPixelConfig;
60
61            std::unique_ptr<uint8_t[]> profile(SkBlurMask::ComputeBlurProfile(sigma));
62
63            blurProfile = proxyProvider->createTextureProxy(texDesc, SkBudgeted::kYes,
64                                                            profile.get(), 0);
65            if (!blurProfile) {
66                return nullptr;
67            }
68
69            SkASSERT(blurProfile->origin() == kTopLeft_GrSurfaceOrigin);
70            proxyProvider->assignUniqueKeyToProxy(key, blurProfile.get());
71        }
72
73        return blurProfile;
74    }
75}
76
77@make {
78     static std::unique_ptr<GrFragmentProcessor> Make(GrProxyProvider* proxyProvider,
79                                                      const SkRect& rect, float sigma) {
80         int doubleProfileSize = SkScalarCeilToInt(12*sigma);
81
82         if (doubleProfileSize >= rect.width() || doubleProfileSize >= rect.height()) {
83             // if the blur sigma is too large so the gaussian overlaps the whole
84             // rect in either direction, fall back to CPU path for now.
85             return nullptr;
86         }
87
88         sk_sp<GrTextureProxy> blurProfile(CreateBlurProfileTexture(proxyProvider, sigma));
89         if (!blurProfile) {
90            return nullptr;
91         }
92
93         return std::unique_ptr<GrFragmentProcessor>(new GrRectBlurEffect(
94            rect, sigma, std::move(blurProfile),
95            GrSamplerState(GrSamplerState::WrapMode::kClamp, GrSamplerState::Filter::kBilerp)));
96     }
97}
98
99void main() {
100    @if (highPrecision) {
101        float2 translatedPos = sk_FragCoord.xy - rect.xy;
102        float width = rect.z - rect.x;
103        float height = rect.w - rect.y;
104        float2 smallDims = float2(width - profileSize, height - profileSize);
105        float center = 2 * floor(profileSize / 2 + 0.25) - 1;
106        float2 wh = smallDims - float2(center, center);
107        half hcoord = ((abs(translatedPos.x - 0.5 * width) - 0.5 * wh.x)) / profileSize;
108        half hlookup = texture(blurProfile, float2(hcoord, 0.5)).a;
109        half vcoord = ((abs(translatedPos.y - 0.5 * height) - 0.5 * wh.y)) / profileSize;
110        half vlookup = texture(blurProfile, float2(vcoord, 0.5)).a;
111        sk_OutColor = sk_InColor * hlookup * vlookup;
112    } else {
113        half2 translatedPos = sk_FragCoord.xy - rect.xy;
114        half width = rect.z - rect.x;
115        half height = rect.w - rect.y;
116        half2 smallDims = half2(width - profileSize, height - profileSize);
117        half center = 2 * floor(profileSize / 2 + 0.25) - 1;
118        half2 wh = smallDims - float2(center, center);
119        half hcoord = ((abs(translatedPos.x - 0.5 * width) - 0.5 * wh.x)) / profileSize;
120        half hlookup = texture(blurProfile, float2(hcoord, 0.5)).a;
121        half vcoord = ((abs(translatedPos.y - 0.5 * height) - 0.5 * wh.y)) / profileSize;
122        half vlookup = texture(blurProfile, float2(vcoord, 0.5)).a;
123        sk_OutColor = sk_InColor * hlookup * vlookup;
124    }
125}
126
127@setData(pdman) {
128    pdman.set1f(profileSize, SkScalarCeilToScalar(6 * sigma));
129}
130
131@optimizationFlags { kCompatibleWithCoverageAsAlpha_OptimizationFlag }
132
133@test(data) {
134    float sigma = data->fRandom->nextRangeF(3,8);
135    float width = data->fRandom->nextRangeF(200,300);
136    float height = data->fRandom->nextRangeF(200,300);
137    return GrRectBlurEffect::Make(data->proxyProvider(), SkRect::MakeWH(width, height), sigma);
138}
139