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
8in float sigma;
9layout(ctype=SkRect) in float4 rect;
10in uniform float cornerRadius;
11in uniform sampler2D ninePatchSampler;
12layout(ctype=SkRect) uniform float4 proxyRect;
13uniform half blurRadius;
14
15@header {
16    #include "GrClip.h"
17    #include "GrContext.h"
18    #include "GrContextPriv.h"
19    #include "GrPaint.h"
20    #include "GrProxyProvider.h"
21    #include "GrRenderTargetContext.h"
22    #include "GrStyle.h"
23    #include "SkBlurMaskFilter.h"
24    #include "SkBlurPriv.h"
25    #include "SkGpuBlurUtils.h"
26    #include "SkRRectPriv.h"
27}
28
29@class {
30    static sk_sp<GrTextureProxy> find_or_create_rrect_blur_mask(GrContext* context,
31                                                                const SkRRect& rrectToDraw,
32                                                                const SkISize& size,
33                                                                float xformedSigma) {
34        static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
35        GrUniqueKey key;
36        GrUniqueKey::Builder builder(&key, kDomain, 9, "RoundRect Blur Mask");
37        builder[0] = SkScalarCeilToInt(xformedSigma-1/6.0f);
38
39        int index = 1;
40        for (auto c : { SkRRect::kUpperLeft_Corner,  SkRRect::kUpperRight_Corner,
41                        SkRRect::kLowerRight_Corner, SkRRect::kLowerLeft_Corner }) {
42            SkASSERT(SkScalarIsInt(rrectToDraw.radii(c).fX) &&
43                     SkScalarIsInt(rrectToDraw.radii(c).fY));
44            builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fX);
45            builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fY);
46        }
47        builder.finish();
48
49        GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
50
51        sk_sp<GrTextureProxy> mask(proxyProvider->findOrCreateProxyByUniqueKey(
52                                                                 key, kBottomLeft_GrSurfaceOrigin));
53        if (!mask) {
54            GrBackendFormat format =
55                context->contextPriv().caps()->getBackendFormatFromColorType(kAlpha_8_SkColorType);
56            // TODO: this could be approx but the texture coords will need to be updated
57            sk_sp<GrRenderTargetContext> rtc(
58                    context->contextPriv().makeDeferredRenderTargetContextWithFallback(
59                                                format, SkBackingFit::kExact, size.fWidth,
60                                                size.fHeight, kAlpha_8_GrPixelConfig, nullptr));
61            if (!rtc) {
62                return nullptr;
63            }
64
65            GrPaint paint;
66
67            rtc->clear(nullptr, SK_PMColor4fTRANSPARENT,
68                       GrRenderTargetContext::CanClearFullscreen::kYes);
69            rtc->drawRRect(GrNoClip(), std::move(paint), GrAA::kYes, SkMatrix::I(), rrectToDraw,
70                           GrStyle::SimpleFill());
71
72            sk_sp<GrTextureProxy> srcProxy(rtc->asTextureProxyRef());
73            if (!srcProxy) {
74                return nullptr;
75            }
76            sk_sp<GrRenderTargetContext> rtc2(
77                      SkGpuBlurUtils::GaussianBlur(context,
78                                                   std::move(srcProxy),
79                                                   nullptr,
80                                                   SkIRect::MakeWH(size.fWidth, size.fHeight),
81                                                   SkIRect::EmptyIRect(),
82                                                   xformedSigma,
83                                                   xformedSigma,
84                                                   GrTextureDomain::kIgnore_Mode,
85                                                   kPremul_SkAlphaType,
86                                                   SkBackingFit::kExact));
87            if (!rtc2) {
88                return nullptr;
89            }
90
91            mask = rtc2->asTextureProxyRef();
92            if (!mask) {
93                return nullptr;
94            }
95            SkASSERT(mask->origin() == kBottomLeft_GrSurfaceOrigin);
96            proxyProvider->assignUniqueKeyToProxy(key, mask.get());
97        }
98
99        return mask;
100    }
101}
102
103@optimizationFlags {
104    kCompatibleWithCoverageAsAlpha_OptimizationFlag
105}
106
107@make {
108    static std::unique_ptr<GrFragmentProcessor> Make(GrContext* context, float sigma,
109                                                     float xformedSigma,
110                                                     const SkRRect& srcRRect,
111                                                     const SkRRect& devRRect);
112}
113
114@cpp {
115    std::unique_ptr<GrFragmentProcessor> GrRRectBlurEffect::Make(GrContext* context, float sigma,
116                                                                 float xformedSigma,
117                                                                 const SkRRect& srcRRect,
118                                                                 const SkRRect& devRRect) {
119        SkASSERT(!SkRRectPriv::IsCircle(devRRect) && !devRRect.isRect()); // Should've been caught up-stream
120
121        // TODO: loosen this up
122        if (!SkRRectPriv::IsSimpleCircular(devRRect)) {
123            return nullptr;
124        }
125
126        // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be
127        // sufficiently small relative to both the size of the corner radius and the
128        // width (and height) of the rrect.
129        SkRRect rrectToDraw;
130        SkISize size;
131        SkScalar ignored[kSkBlurRRectMaxDivisions];
132        int ignoredSize;
133        uint32_t ignored32;
134
135        bool ninePatchable = SkComputeBlurredRRectParams(srcRRect, devRRect,
136                                                         SkRect::MakeEmpty(),
137                                                         sigma, xformedSigma,
138                                                         &rrectToDraw, &size,
139                                                         ignored, ignored,
140                                                         ignored, ignored,
141                                                         &ignoredSize, &ignoredSize,
142                                                         &ignored32);
143        if (!ninePatchable) {
144            return nullptr;
145        }
146
147        sk_sp<GrTextureProxy> mask(find_or_create_rrect_blur_mask(context, rrectToDraw,
148                                                                  size, xformedSigma));
149        if (!mask) {
150            return nullptr;
151        }
152
153        return std::unique_ptr<GrFragmentProcessor>(
154                new GrRRectBlurEffect(xformedSigma, devRRect.getBounds(),
155                                      SkRRectPriv::GetSimpleRadii(devRRect).fX, std::move(mask)));
156    }
157}
158
159@test(d) {
160    SkScalar w = d->fRandom->nextRangeScalar(100.f, 1000.f);
161    SkScalar h = d->fRandom->nextRangeScalar(100.f, 1000.f);
162    SkScalar r = d->fRandom->nextRangeF(1.f, 9.f);
163    SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f);
164    SkRRect rrect;
165    rrect.setRectXY(SkRect::MakeWH(w, h), r, r);
166    return GrRRectBlurEffect::Make(d->context(), sigma, sigma, rrect, rrect);
167}
168
169void main() {
170    // warp the fragment position to the appropriate part of the 9patch blur texture
171
172    half2 rectCenter = (proxyRect.xy + proxyRect.zw) / 2.0;
173    half2 translatedFragPos = sk_FragCoord.xy - proxyRect.xy;
174    half threshold = cornerRadius + 2.0 * blurRadius;
175    half2 middle = proxyRect.zw - proxyRect.xy - 2.0 * threshold;
176
177    if (translatedFragPos.x >= threshold && translatedFragPos.x < (middle.x + threshold)) {
178            translatedFragPos.x = threshold;
179    } else if (translatedFragPos.x >= (middle.x + threshold)) {
180        translatedFragPos.x -= middle.x - 1.0;
181    }
182
183    if (translatedFragPos.y > threshold && translatedFragPos.y < (middle.y+threshold)) {
184        translatedFragPos.y = threshold;
185    } else if (translatedFragPos.y >= (middle.y + threshold)) {
186        translatedFragPos.y -= middle.y - 1.0;
187    }
188
189    half2 proxyDims = half2(2.0 * threshold + 1.0);
190    half2 texCoord = translatedFragPos / proxyDims;
191
192    sk_OutColor = sk_InColor * texture(ninePatchSampler, texCoord);
193}
194
195@setData(pdman) {
196    float blurRadiusValue = 3.f * SkScalarCeilToScalar(sigma - 1 / 6.0f);
197    pdman.set1f(blurRadius, blurRadiusValue);
198
199    SkRect outset = rect;
200    outset.outset(blurRadiusValue, blurRadiusValue);
201    pdman.set4f(proxyRect, outset.fLeft, outset.fTop, outset.fRight, outset.fBottom);
202}
203