/*
 * 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 "include/core/SkScalar.h"
    #include "src/core/SkBlurMask.h"
    #include "src/gpu/GrProxyProvider.h"
    #include "src/gpu/GrShaderCaps.h"
}

in float4 rect;

layout(key) bool highp = abs(rect.x) > 16000 || abs(rect.y) > 16000 ||
                         abs(rect.z) > 16000 || abs(rect.w) > 16000;

layout(when= highp) uniform float4 rectF;
layout(when=!highp) uniform half4  rectH;

in uniform half sigma;

@make {
     static std::unique_ptr<GrFragmentProcessor> Make(GrProxyProvider* proxyProvider,
                                                      const GrShaderCaps& caps,
                                                      const SkRect& rect, float sigma) {
         float doubleProfileSize = (12 * sigma);
         if (!caps.floatIs32Bits()) {
             // We promote the math that gets us into the Gaussian space to full float when the rect
             // coords are large. If we don't have full float then fail. We could probably clip the
             // rect to an outset device bounds instead.
             if (SkScalarAbs(rect.fLeft)  > 16000.f || SkScalarAbs(rect.fTop)    > 16000.f ||
                 SkScalarAbs(rect.fRight) > 16000.f || SkScalarAbs(rect.fBottom) > 16000.f) {
                    return nullptr;
             }
         }
         // Sigma is always a half.
         SkASSERT(sigma > 0);
         if (sigma > 16000.f) {
             return nullptr;
         }

         if (doubleProfileSize >= (float) rect.width() ||
             doubleProfileSize >= (float) 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;
         }

         return std::unique_ptr<GrFragmentProcessor>(new GrRectBlurEffect(rect, sigma));
     }
}

void main() {
    half invr = 1.0 / (2.0 * sigma);

    // Get the smaller of the signed distance from the frag coord to the left and right edges.
    half x;
    @if (highp) {
        float lDiff = rectF.x - sk_FragCoord.x;
        float rDiff = sk_FragCoord.x - rectF.z;
        x = half(max(lDiff, rDiff) * invr);
    } else {
        half lDiff = half(rectH.x - sk_FragCoord.x);
        half rDiff = half(sk_FragCoord.x - rectH.z);
        x = max(lDiff, rDiff) * invr;
    }
    // This is lifted from the implementation of SkBlurMask::ComputeBlurProfile. It approximates
    // a Gaussian as three box filters, and then computes the integral of this approximation from
    // -inf to x.
    // TODO: Make this a function when supported in .fp files as we duplicate it for y below.
    half xCoverage;
    if (x > 1.5) {
        xCoverage = 0.0;
    } else if (x < -1.5) {
        xCoverage = 1.0;
    } else {
        half x2 = x * x;
        half x3 = x2 * x;

        if (x > 0.5) {
            xCoverage = 0.5625 - (x3 / 6.0 - 3.0 * x2 * 0.25 + 1.125 * x);
        } else if (x > -0.5) {
            xCoverage = 0.5 - (0.75 * x - x3 / 3.0);
        } else {
            xCoverage = 0.4375 + (-x3 / 6.0 - 3.0 * x2 * 0.25 - 1.125 * x);
        }
    }

    // Repeat of above for y.
    half y;
    @if (highp) {
        float tDiff = rectF.y - sk_FragCoord.y;
        float bDiff = sk_FragCoord.y - rectF.w;
        y = half(max(tDiff, bDiff) * invr);
    } else {
        half tDiff = half(rectH.y - sk_FragCoord.y);
        half bDiff = half(sk_FragCoord.y - rectH.w);
        y = max(tDiff, bDiff) * invr;
    }
    half yCoverage;
    if (y > 1.5) {
        yCoverage = 0.0;
    } else if (y < -1.5) {
        yCoverage = 1.0;
    } else {
        half y2 = y * y;
        half y3 = y2 * y;

        if (y > 0.5) {
            yCoverage = 0.5625 - (y3 / 6.0 - 3.0 * y2 * 0.25 + 1.125 * y);
        } else if (y > -0.5) {
            yCoverage = 0.5 - (0.75 * y - y3 / 3.0);
        } else {
            yCoverage = 0.4375 + (-y3 / 6.0 - 3.0 * y2 * 0.25 - 1.125 * y);
        }
    }

    sk_OutColor = sk_InColor * xCoverage * yCoverage;
}

@setData(pdman) {
    float r[] {rect.fLeft, rect.fTop, rect.fRight, rect.fBottom};
    pdman.set4fv(highp ? rectF : rectH, 1, r);
}

@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);
}