/*
 * Copyright 2022 Rive
 */

// Common definitions and functions shared by multiple shaders.

#define PI 3.14159265359
#define _2PI 6.28318530718
#define PI_OVER_2 1.57079632679
#define ONE_OVER_SQRT_2 0.70710678118 // 1/sqrt(2)

#ifndef @RENDER_MODE_MSAA
#define AA_RADIUS float(.5)
#else
#define AA_RADIUS float(.0)
#endif

// Defined as a macro because 'uniforms' isn't always available at global scope.
#define RENDER_TARGET_COORD_TO_CLIP_COORD(COORD)                               \
    pixel_coord_to_clip_coord(COORD,                                           \
                              uniforms.renderTargetInverseViewportX,           \
                              uniforms.renderTargetInverseViewportY)

#ifdef @TESS_TEXTURE_FLOATING_POINT
#define TEXTURE_TESSDATA4(SET, IDX, NAME) TEXTURE_RGBA32F(SET, IDX, NAME)
#define TESSDATA4 float4
#define FLOAT_AS_TESSDATA(X) X
#define TESSDATA_AS_FLOAT(X) X
#define UINT_AS_TESSDATA(X) uintBitsToFloat(X)
#define TESSDATA_AS_UINT(X) floatBitsToUint(X)
#else
#define TEXTURE_TESSDATA4(SET, IDX, NAME) TEXTURE_RGBA32UI(SET, IDX, NAME)
#define TESSDATA4 uint4
#define FLOAT_AS_TESSDATA(X) floatBitsToUint(X)
#define TESSDATA_AS_FLOAT(X) uintBitsToFloat(X)
#define UINT_AS_TESSDATA(X) X
#define TESSDATA_AS_UINT(X) X
#endif

// Gathers a 4xN matrix of texels, in the same order as the textureGather() API.
// clang-format off
#define TEXTURE_GATHER_MATRIX(NAME, COORD, COMPONENTS)                         \
    TEXEL_FETCH(NAME, int2(COORD) + int2(-1, 0))COMPONENTS,                    \
        TEXEL_FETCH(NAME, int2(COORD) + int2(0, 0))COMPONENTS,                 \
        TEXEL_FETCH(NAME, int2(COORD) + int2(0, -1))COMPONENTS,                \
        TEXEL_FETCH(NAME, int2(COORD) + int2(-1, -1))COMPONENTS
// clang-format on

// This is a macro because we can't (at least for now) forward texture refs to a
// function in a way that works in all the languages we support.
// This is a macro because we can't (at least for now) forward texture refs to a
// function in a way that works in all the languages we support.
#define FEATHER(X)                                                             \
    TEXTURE_SAMPLE_LOD_1D_ARRAY(@featherTexture,                               \
                                featherSampler,                                \
                                X,                                             \
                                FEATHER_FUNCTION_ARRAY_INDEX,                  \
                                float(FEATHER_FUNCTION_ARRAY_INDEX),           \
                                .0)                                            \
        .r
#define INVERSE_FEATHER(X)                                                     \
    TEXTURE_SAMPLE_LOD_1D_ARRAY(@featherTexture,                               \
                                featherSampler,                                \
                                X,                                             \
                                FEATHER_INVERSE_FUNCTION_ARRAY_INDEX,          \
                                float(FEATHER_INVERSE_FUNCTION_ARRAY_INDEX),   \
                                .0)                                            \
        .r

#ifdef GLSL
// GLSL has different semantics around precision. Normalize type conversions
// across languages with "cast_*_to_*()" methods.
INLINE half cast_float_to_half(float x) { return x; }
INLINE half cast_uint_to_half(uint x) { return float(x); }
INLINE half cast_ushort_to_half(ushort x) { return float(x); }
INLINE half cast_int_to_half(int x) { return float(x); }
INLINE half4 cast_float4_to_half4(float4 xyzw) { return xyzw; }
INLINE half2 cast_float2_to_half2(float2 xy) { return xy; }
INLINE half4 cast_uint4_to_half4(uint4 xyzw) { return vec4(xyzw); }
INLINE ushort cast_half_to_ushort(half x) { return uint(x); }
INLINE ushort cast_uint_to_ushort(uint x) { return x; }
#else
INLINE half cast_float_to_half(float x) { return (half)x; }
INLINE half cast_uint_to_half(uint x) { return (half)x; }
INLINE half cast_ushort_to_half(ushort x) { return (half)x; }
INLINE half cast_int_to_half(int x) { return (half)x; }
INLINE half4 cast_float4_to_half4(float4 xyzw) { return (half4)xyzw; }
INLINE half2 cast_float2_to_half2(float2 xy) { return (half2)xy; }
INLINE half4 cast_uint4_to_half4(uint4 xyzw) { return (half4)xyzw; }
INLINE ushort cast_half_to_ushort(half x) { return (ushort)x; }
INLINE ushort cast_uint_to_ushort(uint x) { return (ushort)x; }
#endif

INLINE half make_half(half x) { return x; }

INLINE half2 make_half2(half2 xy) { return xy; }

INLINE half2 make_half2(half x, half y)
{
    half2 ret;
    ret.x = x, ret.y = y;
    return ret;
}

INLINE half2 make_half2(half x)
{
    half2 ret;
    ret.x = x, ret.y = x;
    return ret;
}

INLINE float2 make_float2(float x) { return float2(x, x); }

INLINE half3 make_half3(half x, half y, half z)
{
    half3 ret;
    ret.x = x, ret.y = y, ret.z = z;
    return ret;
}

INLINE half3 make_half3(half x)
{
    half3 ret;
    ret.x = x, ret.y = x, ret.z = x;
    return ret;
}

INLINE half4 make_half4(half x, half y, half z, half w)
{
    half4 ret;
    ret.x = x, ret.y = y, ret.z = z, ret.w = w;
    return ret;
}

INLINE half4 make_half4(half3 xyz, half w)
{
    half4 ret;
    ret.xyz = xyz;
    ret.w = w;
    return ret;
}

INLINE half4 make_half4(half x)
{
    half4 ret;
    ret.x = x, ret.y = x, ret.z = x, ret.w = x;
    return ret;
}

INLINE half4 make_half4(half4 x) { return x; }

INLINE bool2 make_bool2(bool b) { return bool2(b, b); }

INLINE half3x3 make_half3x3(half3 a, half3 b, half3 c)
{
    half3x3 ret;
    ret[0] = a;
    ret[1] = b;
    ret[2] = c;
    return ret;
}

INLINE half2x3 make_half2x3(half3 a, half3 b)
{
    half2x3 ret;
    ret[0] = a;
    ret[1] = b;
    return ret;
}

INLINE half4x4 make_half4x4(half4 a, half4 b, half4 c, half4 d)
{
    half4x4 ret;
    ret[0] = a;
    ret[1] = b;
    ret[2] = c;
    ret[3] = d;
    return ret;
}

INLINE float2x2 make_float2x2(float4 x) { return float2x2(x.xy, x.zw); }

INLINE uint make_uint(ushort x) { return x; }

INLINE float2 unchecked_mix(float2 a, float2 b, float t)
{
    return (b - a) * t + a;
}

INLINE half id_bits_to_f16(uint idBits, uint pathIDGranularity)
{
    return idBits == 0u
               ? .0
               : unpackHalf2x16((idBits + MAX_DENORM_F16) * pathIDGranularity)
                     .r;
}

INLINE float atan2(float2 v)
{
    v = normalize(v);
    float theta = acos(clamp(v.x, -1., 1.));
    return v.y >= .0 ? theta : -theta;
}

INLINE half4 premultiply(half4 color)
{
    return make_half4(color.rgb * color.a, color.a);
}

INLINE half3 unmultiply_rgb(half4 premul)
{
    // We *could* return preciesly 1 when premul.rgb == premul.a, but we can
    // also be approximate here. The blend modes that depend on this exact level
    // of precision (colordodge and colorburn) account for it with dstPremul.
    return premul.rgb * (premul.a != .0 ? 1. / premul.a : .0);
}

INLINE half min_value(half4 min4)
{
    half2 min2 = min(min4.xy, min4.zw);
    half min1 = min(min2.x, min2.y);
    return min1;
}

INLINE float manhattan_width(float2 x) { return abs(x.x) + abs(x.y); }

// ARM Mali has experienced multiple errors for us when calling clamp(), in both
// GL and Vulkan.
INLINE half safe_clamp_for_mali(half x, half lo, half hi)
{
#if defined(@GL_RENDERER_MALI) || defined(@VULKAN_VENDOR_ID)
#ifdef @VULKAN_VENDOR_ID
    if (@VULKAN_VENDOR_ID == VULKAN_VENDOR_ARM)
#endif
    {
        if (x < hi)
            if (x > lo)
                return x;
            else
                return lo;
        else
            return hi;
    }
#endif // @GL_RENDERER_MALI || @VULKAN_VENDOR_ID
    return clamp(x, lo, hi);
}

#ifndef $UNIFORM_DEFINITIONS_AUTO_GENERATED
UNIFORM_BLOCK_BEGIN(FLUSH_UNIFORM_BUFFER_IDX, @FlushUniforms)
float gradInverseViewportY;
float tessInverseViewportY;
float renderTargetInverseViewportX;
float renderTargetInverseViewportY;
uint renderTargetWidth;
uint renderTargetHeight;
uint colorClearValue;           // Only used if clears are implemented as draws.
uint coverageClearValue;        // Only used if clears are implemented as draws.
int4 renderTargetUpdateBounds;  // drawBounds, or renderTargetBounds if there is
                                // a clear. (LTRB.)
float2 atlasTextureInverseSize; // 1 / [atlasWidth, atlasHeight]
float2 atlasContentInverseViewport; // 2 / atlasContentBounds
uint coverageBufferPrefix;
uint pathIDGranularity; // Spacing between adjacent path IDs (1 if IEEE
                        // compliant).
float vertexDiscardValue;
float mipMapLODBias;
// Debugging.
uint wireframeEnabled;
UNIFORM_BLOCK_END(uniforms)
#endif

#ifdef @VERTEX

INLINE float4 pixel_coord_to_clip_coord(float2 pixelCoord,
                                        float inverseViewportX,
                                        float inverseViewportY)
{
    return float4(pixelCoord.x * inverseViewportX - 1.,
                  pixelCoord.y * inverseViewportY - sign(inverseViewportY),
                  0.,
                  1.);
}

#ifndef @RENDER_MODE_MSAA
// Calculates the Manhattan distance in pixels from the given pixelPosition, to
// the point at each edge of the clipRect where coverage = 0.
//
// clipRectInverseMatrix transforms from pixel coordinates to a space where the
// clipRect is the normalized rectangle: [-1, -1, 1, 1].
INLINE float4 find_clip_rect_coverage_distances(float2x2 clipRectInverseMatrix,
                                                float2 clipRectInverseTranslate,
                                                float2 pixelPosition)
{
    float2 clipRectAAWidth =
        abs(clipRectInverseMatrix[0]) + abs(clipRectInverseMatrix[1]);
    if (clipRectAAWidth.x != .0 && clipRectAAWidth.y != .0)
    {
        float2 r = 1. / clipRectAAWidth;
        float2 clipRectCoord = MUL(clipRectInverseMatrix, pixelPosition) +
                               clipRectInverseTranslate;
        // When the center of a pixel falls exactly on an edge, coverage should
        // be .5.
        const float coverageWhenDistanceIsZero = .5;
        return float4(clipRectCoord, -clipRectCoord) * r.xyxy + r.xyxy +
               coverageWhenDistanceIsZero;
    }
    else
    {
        // The caller gave us a singular clipRectInverseMatrix. This is a
        // special case where we are expected to use tx and ty as uniform
        // coverage.
        return clipRectInverseTranslate.xyxy;
    }
}

#else // !@RENDER_MODE_MSAA => @RENDER_MODE_MSAA

INLINE float normalize_z_index(uint zIndex)
{
    return 1. - float(zIndex) * (2. / 32768.);
}

#ifdef @ENABLE_CLIP_RECT
INLINE void set_clip_rect_plane_distances(float2x2 clipRectInverseMatrix,
                                          float2 clipRectInverseTranslate,
                                          float2 pixelPosition
                                              CLIP_CONTEXT_FORWARD)
{
// MSAA uses gl_ClipDistance when ENABLE_CLIP_RECT is set, but since SPIRV uses
// specialization constants (as opposed to compile-time flags), it means that
// the usage of them is in the compiled shader even if that codepath is not
// going to be taken, which ends up as a validation failure on systems that do
// not support that extension. In those cases, we compile separate SPIRV
// binaries with gl_ClipDistance explicitly disabled.
#ifndef @DISABLE_CLIP_DISTANCE_FOR_UBERSHADERS
    if (any(notEqual(float4(clipRectInverseMatrix), float4(.0, .0, .0, .0))))
    {
        float2 clipRectCoord = MUL(clipRectInverseMatrix, pixelPosition) +
                               clipRectInverseTranslate.xy;
        gl_ClipDistance[0] = clipRectCoord.x + 1.;
        gl_ClipDistance[1] = clipRectCoord.y + 1.;
        gl_ClipDistance[2] = 1. - clipRectCoord.x;
        gl_ClipDistance[3] = 1. - clipRectCoord.y;
    }
    else
    {
        // "clipRectInverseMatrix == 0" is a special case:
        //     "clipRectInverseTranslate.x == 1" => all in.
        //     "clipRectInverseTranslate.x == 0" => all out.
        gl_ClipDistance[0] = gl_ClipDistance[1] = gl_ClipDistance[2] =
            gl_ClipDistance[3] = clipRectInverseTranslate.x - .5;
    }
#endif // !@DISABLE_CLIP_DISTANCE_FOR_UBERSHADERS
}
#endif // ENABLE_CLIP_RECT

#endif // @RENDER_MODE_MSAA
#endif // VERTEX

#ifdef @FRAGMENT
#ifdef @NEEDS_GAMMA_CORRECTION
INLINE half gamma_to_linear(half color)
{
    return (color <= 0.04045) ? color / 12.92
                              : pow(abs((color + 0.055) / 1.055), 2.4);
}

INLINE half3 gamma_to_linear(half3 color)
{
    return make_half3(gamma_to_linear(color.r),
                      gamma_to_linear(color.g),
                      gamma_to_linear(color.b));
}

INLINE half4 gamma_to_linear(half4 color)
{
    return make_half4(gamma_to_linear(color.rgb), color.a);
}
#endif // NEEDS_GAMMA_CORRECTION
#endif // FRAGMENT

#ifdef @DRAW_IMAGE
#ifndef $UNIFORM_DEFINITIONS_AUTO_GENERATED
UNIFORM_BLOCK_BEGIN(IMAGE_DRAW_UNIFORM_BUFFER_IDX, @ImageDrawUniforms)
float4 viewMatrix;
float2 translate;
float opacity;
float padding;
// clipRectInverseMatrix transforms from pixel coordinates to a space where the
// clipRect is the normalized rectangle: [-1, -1, 1, 1].
float4 clipRectInverseMatrix;
float2 clipRectInverseTranslate;
uint clipID;
uint blendMode;
uint zIndex;
UNIFORM_BLOCK_END(imageDrawUniforms)
#endif
#endif

// The Qualcomm compiler can't handle line breaks in #ifs.
// clang-format off
#if defined(@FRAGMENT) && defined(@RENDER_MODE_MSAA) && !defined(@FIXED_FUNCTION_COLOR_OUTPUT)
// clang-format on
INLINE half4 dst_color_fetch(half4x4 dstSamples, int sampleMask)
{
    if (sampleMask == 0xf)
    {
        // Average together all samples for this fragment.
        return (dstSamples[0] + dstSamples[1] + dstSamples[2] + dstSamples[3]) *
               .25;
    }
    else
    {
        // Average together only the samples that are inside the sample mask.
        half4 mask = float4(notEqual(sampleMask & int4(1, 2, 4, 8), int4(0)));
        half4 ret = MUL(dstSamples, mask);
        // Since the sample mask can only have 4 bits, counting them is faster
        // this way on Galaxy S24 than calling bitCount().
        int numSamples = (sampleMask & 5) + ((sampleMask >> 1) & 5);
        numSamples = (numSamples & 3) + (numSamples >> 2);
        ret *= 1. / float(numSamples);
        return ret;
    }
}
#endif // @FRAGMENT && @RENDER_MODE_MSAA && !@FIXED_FUNCTION_COLOR_OUTPUT
