1/*
2 * Copyright 2017 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 "src/gpu/GrShaderCaps.h"
10}
11
12in fragmentProcessor inputFP;
13layout(key) in GrClipEdgeType edgeType;
14in float2 center;
15in float2 radii;
16
17float2 prevCenter;
18float2 prevRadii = float2(-1);
19// The ellipse uniform is (center.x, center.y, 1 / rx^2, 1 / ry^2)
20// The last two terms can underflow when float != fp32, so we also provide a workaround.
21uniform float4 ellipse;
22
23layout(when=!sk_Caps.floatIs32Bits) uniform float2 scale;
24
25@make {
26    static GrFPResult Make(std::unique_ptr<GrFragmentProcessor> inputFP, GrClipEdgeType edgeType,
27                           SkPoint center, SkPoint radii, const GrShaderCaps& caps) {
28        // Small radii produce bad results on devices without full float.
29        if (!caps.floatIs32Bits() && (radii.fX < 0.5f || radii.fY < 0.5f)) {
30            return GrFPFailure(std::move(inputFP));
31        }
32        // Very narrow ellipses produce bad results on devices without full float
33        if (!caps.floatIs32Bits() && (radii.fX > 255*radii.fY || radii.fY > 255*radii.fX)) {
34            return GrFPFailure(std::move(inputFP));
35        }
36        // Very large ellipses produce bad results on devices without full float
37        if (!caps.floatIs32Bits() && (radii.fX > 16384 || radii.fY > 16384)) {
38            return GrFPFailure(std::move(inputFP));
39        }
40        return GrFPSuccess(std::unique_ptr<GrFragmentProcessor>(
41                    new GrEllipseEffect(std::move(inputFP), edgeType, center, radii)));
42    }
43}
44
45@optimizationFlags {
46    ProcessorOptimizationFlags(inputFP.get()) & kCompatibleWithCoverageAsAlpha_OptimizationFlag
47}
48
49@setData(pdman) {
50    if (radii != prevRadii || center != prevCenter) {
51        float invRXSqd;
52        float invRYSqd;
53        // If we're using a scale factor to work around precision issues, choose the larger
54        // radius as the scale factor. The inv radii need to be pre-adjusted by the scale
55        // factor.
56        if (scale.isValid()) {
57            if (radii.fX > radii.fY) {
58                invRXSqd = 1.f;
59                invRYSqd = (radii.fX * radii.fX) / (radii.fY * radii.fY);
60                pdman.set2f(scale, radii.fX, 1.f / radii.fX);
61            } else {
62                invRXSqd = (radii.fY * radii.fY) / (radii.fX * radii.fX);
63                invRYSqd = 1.f;
64                pdman.set2f(scale, radii.fY, 1.f / radii.fY);
65            }
66        } else {
67            invRXSqd = 1.f / (radii.fX * radii.fX);
68            invRYSqd = 1.f / (radii.fY * radii.fY);
69        }
70        pdman.set4f(ellipse, center.fX, center.fY, invRXSqd, invRYSqd);
71        prevCenter = center;
72        prevRadii = radii;
73    }
74}
75
76half4 main() {
77    // d is the offset to the ellipse center
78    float2 d = sk_FragCoord.xy - ellipse.xy;
79    // If we're on a device with a "real" mediump then we'll do the distance computation in a space
80    // that is normalized by the larger radius or 128, whichever is smaller. The scale uniform will
81    // be scale, 1/scale. The inverse squared radii uniform values are already in this normalized space.
82    // The center is not.
83    const bool medPrecision = !sk_Caps.floatIs32Bits;
84    @if (medPrecision) {
85        d *= scale.y;
86    }
87    float2 Z = d * ellipse.zw;
88    // implicit is the evaluation of (x/rx)^2 + (y/ry)^2 - 1.
89    float implicit = dot(Z, d) - 1;
90    // grad_dot is the squared length of the gradient of the implicit.
91    float grad_dot = 4 * dot(Z, Z);
92    // Avoid calling inversesqrt on zero.
93    @if (medPrecision) {
94        grad_dot = max(grad_dot, 6.1036e-5);
95    } else {
96        grad_dot = max(grad_dot, 1.1755e-38);
97    }
98    float approx_dist = implicit * inversesqrt(grad_dot);
99    @if (medPrecision) {
100        approx_dist *= scale.x;
101    }
102
103    half alpha;
104    @switch (edgeType) {
105        case GrClipEdgeType::kFillBW:
106            alpha = approx_dist > 0.0 ? 0.0 : 1.0;
107            break;
108        case GrClipEdgeType::kFillAA:
109            alpha = saturate(0.5 - half(approx_dist));
110            break;
111        case GrClipEdgeType::kInverseFillBW:
112            alpha = approx_dist > 0.0 ? 1.0 : 0.0;
113            break;
114        case GrClipEdgeType::kInverseFillAA:
115            alpha = saturate(0.5 + half(approx_dist));
116            break;
117        default:
118            // hairline not supported
119            discard;
120    }
121    return sample(inputFP) * alpha;
122}
123
124@test(testData) {
125    SkPoint center;
126    center.fX = testData->fRandom->nextRangeScalar(0.f, 1000.f);
127    center.fY = testData->fRandom->nextRangeScalar(0.f, 1000.f);
128    SkScalar rx = testData->fRandom->nextRangeF(0.f, 1000.f);
129    SkScalar ry = testData->fRandom->nextRangeF(0.f, 1000.f);
130    bool success;
131    std::unique_ptr<GrFragmentProcessor> fp = testData->inputFP();
132    do {
133        GrClipEdgeType et = (GrClipEdgeType)testData->fRandom->nextULessThan(kGrClipEdgeTypeCnt);
134        std::tie(success, fp) = GrEllipseEffect::Make(std::move(fp), et, center,
135                                                      SkPoint::Make(rx, ry),
136                                                      *testData->caps()->shaderCaps());
137    } while (!success);
138    return fp;
139}
140