/*
 * Copyright 2023 Rive
 */

// Common functions shared by draw shaders.

// Feathered coverage values get shifted by "FEATHER_COVERAGE_BIAS" in order
// to classify the coverage as belonging to a feather.
#define FEATHER_COVERAGE_BIAS -2.

// Fragment shaders test if a coverage value is less than
// "FEATHER_COVERAGE_THRESHOLD" to see if the coverage belongs to a feather.
#define FEATHER_COVERAGE_THRESHOLD -1.5

// Since the sign of x dictates the sign of coverage, and since x may be 0 when
// feathering, bias x slightly upward so we don't lose the sign when it's 0.
#define FEATHER_X_COORD_BIAS .25

// Magnitude of cotan(theta) at which we decide an angle is flat when processing
// feathers.
#define HORIZONTAL_COTANGENT_THRESHOLD 1e3

// Value to assign cotTheta to ensure it gets treated as flat.
#define HORIZONTAL_COTANGENT_VALUE                                             \
    (HORIZONTAL_COTANGENT_THRESHOLD * HORIZONTAL_COTANGENT_THRESHOLD)

#ifdef @VERTEX
VERTEX_TEXTURE_BLOCK_BEGIN
TEXTURE_TESSDATA4(PER_FLUSH_BINDINGS_SET,
                  TESS_VERTEX_TEXTURE_IDX,
                  @tessVertexTexture);
#ifdef @ENABLE_FEATHER
TEXTURE_R16F_1D_ARRAY(PER_FLUSH_BINDINGS_SET,
                      FEATHER_TEXTURE_IDX,
                      @featherTexture);
#endif
VERTEX_TEXTURE_BLOCK_END

VERTEX_STORAGE_BUFFER_BLOCK_BEGIN
STORAGE_BUFFER_U32x4(PATH_BUFFER_IDX, PathBuffer, @pathBuffer);
STORAGE_BUFFER_U32x2(PAINT_BUFFER_IDX, PaintBuffer, @paintBuffer);
STORAGE_BUFFER_F32x4(PAINT_AUX_BUFFER_IDX, PaintAuxBuffer, @paintAuxBuffer);
STORAGE_BUFFER_U32x4(CONTOUR_BUFFER_IDX, ContourBuffer, @contourBuffer);
VERTEX_STORAGE_BUFFER_BLOCK_END
#endif // @VERTEX

#if defined(@ENABLE_FEATHER) || defined(@ATLAS_BLIT)
SAMPLER_LINEAR(FEATHER_TEXTURE_IDX, featherSampler)
#endif

#ifdef @FRAGMENT
FRAG_TEXTURE_BLOCK_BEGIN
TEXTURE_RGBA8(PER_FLUSH_BINDINGS_SET, GRAD_TEXTURE_IDX, @gradTexture);
#if defined(@ENABLE_FEATHER) || defined(@ATLAS_BLIT)
TEXTURE_R16F_1D_ARRAY(PER_FLUSH_BINDINGS_SET,
                      FEATHER_TEXTURE_IDX,
                      @featherTexture);
#endif
#ifdef @ATLAS_BLIT
#ifdef @ATLAS_TEXTURE_R32UI_FLOAT_BITS
TEXTURE_R32UI(PER_FLUSH_BINDINGS_SET, ATLAS_TEXTURE_IDX, @atlasTexture);
#elif defined(@ATLAS_TEXTURE_R32I_FIXED_POINT)
TEXTURE_R32I(PER_FLUSH_BINDINGS_SET, ATLAS_TEXTURE_IDX, @atlasTexture);
#elif defined(@ATLAS_TEXTURE_RGBA8_UNORM)
TEXTURE_RGBA8(PER_FLUSH_BINDINGS_SET, ATLAS_TEXTURE_IDX, @atlasTexture);
#else
TEXTURE_R16F(PER_FLUSH_BINDINGS_SET, ATLAS_TEXTURE_IDX, @atlasTexture);
#endif
#endif
TEXTURE_RGBA8(PER_DRAW_BINDINGS_SET, IMAGE_TEXTURE_IDX, @imageTexture);
// The Qualcomm compiler can't handle line breaks in #ifs.
// clang-format off
#if defined(@RENDER_MODE_MSAA) && defined(@ENABLE_ADVANCED_BLEND) && !defined(@FIXED_FUNCTION_COLOR_OUTPUT)
// clang-format on
DST_COLOR_TEXTURE(@dstColorTexture);
#endif
FRAG_TEXTURE_BLOCK_END

SAMPLER_LINEAR(GRAD_TEXTURE_IDX, gradSampler)
// Metal defines @VERTEX and @FRAGMENT at the same time, so yield to the vertex
// definition of featherSampler in this case.
#ifdef @ATLAS_BLIT
SAMPLER_LINEAR(ATLAS_TEXTURE_IDX, atlasSampler)
#endif
DYNAMIC_SAMPLER_BLOCK_BEGIN
SAMPLER_DYNAMIC(PER_DRAW_BINDINGS_SET, IMAGE_SAMPLER_IDX, imageSampler)
DYNAMIC_SAMPLER_BLOCK_END
#endif // @FRAGMENT

// We distinguish between strokes and fills by the sign of coverages.y,
// regardless of whether feathering is enabled (coverages are float4), or
// disabled (coverages are half2),
#ifdef @FRAGMENT
INLINE bool is_stroke(float4 coverages) { return coverages.y >= .0; }
INLINE bool is_stroke(half2 coverages) { return coverages.y >= .0; }
#endif // FRAGMENT

#if defined(@FRAGMENT) && defined(@ENABLE_FEATHER)
// We can also classify a fragments as feathered/not-feathered strokes/fills by
// looking at coverages.
INLINE bool is_feathered_stroke(float4 coverages)
{
    return coverages.x < FEATHER_COVERAGE_THRESHOLD;
}

INLINE bool is_feathered_fill(float4 coverages)
{
    return coverages.y < FEATHER_COVERAGE_THRESHOLD;
}
#endif // @FRAGMENT && @ENABLE_FEATHER

#ifdef @VERTEX
// Packs all the info to evaluate a feathered fill into 4 varying floats.
float4 pack_feathered_fill_coverages(float cornerTheta,
                                     float2 spokeNorm,
                                     float outset)
{
    // Find the corner's local coordinate within the feather convolution, where
    // the convolution matrix is centered on [.5, .5] and spans 0..1, and the
    // first edge of the corner runs horizontal and to the left.
    float2 cornerLocalCoord = (1. - spokeNorm * abs(outset)) * .5;

    // Calculate cotTheta and y0 for the fragment shader.
    // (See eval_feathered_fill() for details.)
    float cotTheta, y0;
    if (abs(cornerTheta - PI_OVER_2) < 1. / HORIZONTAL_COTANGENT_THRESHOLD)
    {
        cotTheta = .0;
        y0 = .0;
    }
    else
    {
        float tanTheta = tan(cornerTheta);
        cotTheta = sign(PI_OVER_2 - cornerTheta) /
                   max(abs(tanTheta), 1. / HORIZONTAL_COTANGENT_VALUE);
        y0 = cotTheta >= .0
                 ? cornerLocalCoord.y - (1. - cornerLocalCoord.x) * tanTheta
                 : cornerLocalCoord.y + cornerLocalCoord.x * tanTheta;
    }

    // Bias x & y for additional information:
    //
    //  * x will be negated later on if the triangle is back-facing. This tells
    //    the fragment shader what sign to give feathered coverage. Ensure it's
    //    greater than zero.
    //
    //  * y < FEATHER_COVERAGE_BIAS tells the fragment shader this is a feather.
    //
    float4 coverages;
    coverages.x = max(cornerLocalCoord.x, .0) + FEATHER_X_COORD_BIAS;
    coverages.y = -cornerLocalCoord.y + FEATHER_COVERAGE_BIAS;
    coverages.z = cotTheta;
    coverages.w = y0;
    return coverages;
}
#endif // @VERTEX

#ifdef @ENABLE_FEATHER
INLINE half eval_feathered_fill(float4 coverages TEXTURE_CONTEXT_DECL)
{
    // x and y are the relative coordinates of the corner vertex within the
    // feather convolution. They are oriented above center=[.5, .5], with the
    // first edge running horizontal and to the left.
    //
    // The second edge exits x,y at an angle of theta. "y0" is the location
    // where it intersects with either the left or right edge of the
    // convolution (left if cotTheta < 0, right if cotTheta > 0).
    //
    // y0 and cotTheta are both 0 when the corner angle is pi/2.
    half cotTheta = coverages.z;
    half y0 = max(coverages.w, .0); // Clamp y0 at the top of the convolution.

    // First compute the upper area of the convolution that is fully contained
    // within both edges. (i.e., the area above y0.)
    //
    // NOTE: If we aren't a corner, this will be the entire feather.
    half featherCoverage = cotTheta >= .0 ? FEATHER(y0) : .0;

    // If we're a (not flat) corner, the bottom area of the convolution needs to
    // account for both edges.
    //
    // Integrate the area contained within both edges, taking advantage of the
    // separability property of our convolution:
    //           _
    //    t=y   / \.
    //          |
    //          |  FEATHER(t * m + b) * FEATHER'(t) * dt
    //          |
    //   t=y0 \_/
    //
    // NOTE: The derivative FEATHER'(t) is the normal distribution with:
    //
    //   mu = 1/2
    //   sigma = 1 / (2 * FEATHER_TEXTURE_STDDEVS)
    //
    // We can evaluate this directly without a lookup table.
    //
    // For performance constraints, we only take 4 samples on the integral.
    if (abs(cotTheta) < HORIZONTAL_COTANGENT_THRESHOLD)
    {
        // Unpack x & y from the varying coverages.
        half x = abs(coverages.x) - FEATHER_X_COORD_BIAS;
        half y = -coverages.y + FEATHER_COVERAGE_BIAS;

        // Find the height of each sample, and pre-scale by 1/(sigma*sqrt(2pi))
        // from the normal distribution (to save on multiplies later).
        //
        //   dt = (y - y0) / 4 / (sigma * sqrt(2pi))
        //
        half dt = (y - y0) * 0.5984134206;

        // Subdivide into 4 bars of even height, and sample at the centers.
        // (Reuse dt so we don't have to recompute "y - y1" again.)
        //
        //   t = lerp(y0, y1, [1/8, 3/8, 5/8, 7/8])
        //
        half4 t = y0 + dt * make_half4(0.20888568955,
                                       0.62665706865,
                                       1.04442844776,
                                       1.46219982687);

        // Feather horizontally where each sample intersects the second edge.
        half4 u = t * -cotTheta + (y * cotTheta + x);
        half4 feathers = make_half4(FEATHER(u[0]),
                                    FEATHER(u[1]),
                                    FEATHER(u[2]),
                                    FEATHER(u[3]));

        // Evaluate the normal distribution at each vertical sample.
        // (Scale t_ by sqrt(log2(e)) to change the base of the function from
        // e^x to 2^x.)
        //
        //   t_ = 1/sqrt(2) * (x - mu) / sigma * sqrt(log2(e))
        //   normalDistro = 2^(-t_ * t_)
        //
        half4 t_ = t * 5.09593080173 + -2.54796540086;
        half4 ddtFeather = exp2(-t_ * t_);

        // Take the sum of "FEATHER(u) * FEATHER'(t) * dt" at all 4 samples.
        featherCoverage += dot(feathers, ddtFeather) * dt;
    }

    // Clockwise triangles add to the featherCoverage, counterclockwise
    // triangles subtract from it.
    return featherCoverage * sign(coverages.x);
}

INLINE half eval_feathered_stroke(float4 coverages TEXTURE_CONTEXT_DECL)
{
    // Feathered stroke is:
    // 1 - feather(1 - leftCoverage) - feather(1 - rightCoverage)
    float featherCoverage = 1.;

    // The portion OUTSIDE the featherCoverage is "1 - featherCoverage".
    // (coverages.x is biased in order to classify this featherCoverage as a
    // feather, so also remove the bias.)
    float leftOutsideCoverage = (1. - FEATHER_COVERAGE_BIAS) + coverages.x;
    featherCoverage -= FEATHER(leftOutsideCoverage);

    float rightOutsideCoverage = 1. - coverages.y;
    featherCoverage -= FEATHER(rightOutsideCoverage);

    return featherCoverage;
}
#endif // @ENABLE_FEATHER

#if defined(@FRAGMENT) && defined(@ATLAS_BLIT)
// Upscales a pre-rendered feather from the atlas, converting from gaussian
// space to linear before doing a bilerp.
INLINE half
filter_feather_atlas(float2 atlasCoord,
                     float2 atlasTextureInverseSize TEXTURE_CONTEXT_DECL)
{
    // Gather the quad of pixels we need to filter.
    // Gather from the exact center of the quad to make sure there are no
    // rounding differences between us and the texture unit.
    float2 atlasQuadCenter = round(atlasCoord);
    half4 coverages;
#ifdef @ATLAS_TEXTURE_R32UI_FLOAT_BITS
    coverages = uintBitsToFloat(uint4(TEXTURE_GATHER(@atlasTexture,
                                                     atlasSampler,
                                                     atlasQuadCenter,
                                                     atlasTextureInverseSize)));
#elif defined(@ATLAS_TEXTURE_R32I_FIXED_POINT)
    int4 coverages_i32 = int4(TEXTURE_GATHER(@atlasTexture,
                                             atlasSampler,
                                             atlasQuadCenter,
                                             atlasTextureInverseSize));
    coverages = float4(coverages_i32) * (1. / ATLAS_R32I_FIXED_POINT_FACTOR);
#elif defined(@ATLAS_TEXTURE_RGBA8_UNORM)
    int2 coord = int2(atlasQuadCenter);
    half4x4 coverages_u8x4 =
        make_half4x4(TEXTURE_GATHER_MATRIX(@atlasTexture, coord, .rgba));
    // Apply the following weights to the RGBA of each u8x4 coverage value:
    //   - R counts fractional, positive coverage.
    //   - G counts fractional, negative coverage.
    //   - B counts integer, positive coverage.
    //   - A counts integer, negative coverage.
    coverages = make_half4(ATLAS_UNORM8_COVERAGE_SCALE_FACTOR,
                           -ATLAS_UNORM8_COVERAGE_SCALE_FACTOR,
                           255.,
                           -255.) *
                coverages_u8x4;
#else
    coverages = make_half4(TEXTURE_GATHER(@atlasTexture,
                                          atlasSampler,
                                          atlasQuadCenter,
                                          atlasTextureInverseSize));
#endif
    // Convert each pixel from gaussian space back to linear.
    coverages = make_half4(INVERSE_FEATHER(coverages.x),
                           INVERSE_FEATHER(coverages.y),
                           INVERSE_FEATHER(coverages.z),
                           INVERSE_FEATHER(coverages.w));
    // Bilerp in linear space.
    coverages.xw = mix(coverages.xw,
                       coverages.yz,
                       make_half(atlasCoord.x + .5 - atlasQuadCenter.x));
    coverages.x = mix(coverages.w,
                      coverages.x,
                      make_half(atlasCoord.y + .5 - atlasQuadCenter.y));
    // Go back to gaussian now that the bilerp is finished.
    return FEATHER(coverages.x);
}
#endif // @FRAGMENT && @ATLAS_BLIT

#if defined(@VERTEX) && defined(@DRAW_PATH)
INLINE int2 tess_texel_coord(int texelIndex)
{
    return int2(texelIndex & ((1 << TESS_TEXTURE_WIDTH_LOG2) - 1),
                texelIndex >> TESS_TEXTURE_WIDTH_LOG2);
}

INLINE float manhattan_pixel_width(float2x2 M, float2 normalized)
{

    float2 v = MUL(M, normalized);
    return (abs(v.x) + abs(v.y)) * (1. / dot(v, v));
}

INLINE bool unpack_tessellated_path_vertex(float4 patchVertexData,
                                           float4 mirroredVertexData,
                                           int _instanceID,
                                           OUT(uint) outPathID,
                                           OUT(float2) outVertexPosition
#ifndef @RENDER_MODE_MSAA
                                           ,
                                           OUT(float4) outCoverages
#else
                                           ,
                                           OUT(ushort) outPathZIndex
#endif
                                               VERTEX_CONTEXT_DECL)
{
    // Unpack patchVertexData.
    int localVertexID = int(patchVertexData.x);
    float outset = patchVertexData.y;
    float fillCoverage = patchVertexData.z;
    int patchSegmentSpan = floatBitsToInt(patchVertexData.w) >> 2;
    int vertexType = floatBitsToInt(patchVertexData.w) & 3;

    // Fetch a vertex that definitely belongs to the contour we're drawing.
    int vertexIDOnContour = min(localVertexID, patchSegmentSpan - 1);
    int tessVertexIdx = _instanceID * patchSegmentSpan + vertexIDOnContour;
    TESSDATA4 tessVertexData =
        TEXEL_FETCH(@tessVertexTexture, tess_texel_coord(tessVertexIdx));
    uint contourIDWithFlags = TESSDATA_AS_UINT(tessVertexData.w);

    // Fetch and unpack the contour referenced by the tessellation vertex.
    // NOTE: The contourID is guaranteed to be >= 1 at this point, but clamp it
    // anyway because in the event of a bug, a buffer load at index "0u - 1" can
    // be very serious and hard to catch.
    uint contourID = max(contourIDWithFlags & CONTOUR_ID_MASK, 1u);
    uint4 contourData = STORAGE_BUFFER_LOAD4(@contourBuffer, contourID - 1u);
    float2 midpoint = uintBitsToFloat(contourData.xy);
    outPathID = contourData.z & 0xffffu;
    uint vertexIndex0 = contourData.w;

    // Fetch and unpack the path.
    float2x2 M = make_float2x2(
        uintBitsToFloat(STORAGE_BUFFER_LOAD4(@pathBuffer, outPathID * 4u)));
    uint4 pathData = STORAGE_BUFFER_LOAD4(@pathBuffer, outPathID * 4u + 1u);
    float2 translate = uintBitsToFloat(pathData.xy);
    float strokeRadius = uintBitsToFloat(pathData.z);
    float featherRadius = uintBitsToFloat(pathData.w);

    // Fix the tessellation vertex if we fetched the wrong one in order to
    // guarantee we got the correct contour ID and flags, or if we belong to a
    // mirrored contour and this vertex has an alternate position when mirrored.
    uint mirroredContourFlag =
        contourIDWithFlags & MIRRORED_CONTOUR_CONTOUR_FLAG;
    if (mirroredContourFlag != 0u)
    {
        localVertexID = int(mirroredVertexData.x);
        outset = mirroredVertexData.y;
        fillCoverage = mirroredVertexData.z;
    }
    if (localVertexID != vertexIDOnContour)
    {
        // This can peek one vertex before or after the contour, but the
        // tessellator guarantees there is always at least one padding vertex at
        // the beginning and end of the data.
        int replacementTessVertexIdx =
            tessVertexIdx + localVertexID - vertexIDOnContour;
        TESSDATA4 replacementTessVertexData =
            TEXEL_FETCH(@tessVertexTexture,
                        tess_texel_coord(replacementTessVertexIdx));
        if ((TESSDATA_AS_UINT(replacementTessVertexData.w) &
             (MIRRORED_CONTOUR_CONTOUR_FLAG | 0xffffu)) !=
            (contourIDWithFlags & (MIRRORED_CONTOUR_CONTOUR_FLAG | 0xffffu)))
        {
            // We crossed over into a new contour. Either wrap to the first
            // vertex in the contour or leave it clamped at the final vertex of
            // the contour.
            bool isClosed = strokeRadius == .0 || // filled
                            midpoint.x != .0;     // explicity closed stroke
            if (isClosed)
            {
                tessVertexIdx = int(vertexIndex0);
                tessVertexData = TEXEL_FETCH(@tessVertexTexture,
                                             tess_texel_coord(tessVertexIdx));
            }
        }
        else
        {
            tessVertexIdx = replacementTessVertexIdx;
            tessVertexData = replacementTessVertexData;
        }
        // MIRRORED_CONTOUR_CONTOUR_FLAG is not preserved at vertexIndex0.
        // Preserve it here. By not preserving this flag, the normal and
        // mirrored contour can both share the same contour record.
        contourIDWithFlags = (TESSDATA_AS_UINT(tessVertexData.w) &
                              ~MIRRORED_CONTOUR_CONTOUR_FLAG) |
                             mirroredContourFlag;
    }

    // Find the tangent angle of the curve at our vertex.
    float theta;
#ifdef @ENABLE_FEATHER
    float featherJoinEdge0Theta;
    float featherJoinCornerTheta;
    if ((contourIDWithFlags & JOIN_TYPE_MASK) == FEATHER_JOIN_CONTOUR_FLAG &&
        vertexType == STROKE_VERTEX)
    {
        // Feather joins work out their stepping here in the vertex shader.
        // Instead of emitting just the tangent angle, the tessellation shader
        // gave us the original tessellation parameters.
        uint joinDataPacked = TESSDATA_AS_UINT(tessVertexData.z);
        float joinVertexID = float(joinDataPacked & 0xffffu);
        float joinSegmentCount = float(joinDataPacked >> 16);

        // Find the tessellation vertices immediately before and after the
        // feather join in order to work out the corner angles.
        int2 edgeVertexOffsets =
            int2(-joinVertexID - 1., joinSegmentCount - joinVertexID + 1.);
        if ((contourIDWithFlags & MIRRORED_CONTOUR_CONTOUR_FLAG) != 0u)
            edgeVertexOffsets = -edgeVertexOffsets;
        TESSDATA4 tessDataBeforeJoin =
            TEXEL_FETCH(@tessVertexTexture,
                        tess_texel_coord(tessVertexIdx + edgeVertexOffsets.x));
        TESSDATA4 tessDataAfterJoin =
            TEXEL_FETCH(@tessVertexTexture,
                        tess_texel_coord(tessVertexIdx + edgeVertexOffsets.y));
        if ((TESSDATA_AS_UINT(tessDataAfterJoin.w) &
             (MIRRORED_CONTOUR_CONTOUR_FLAG | 0xffffu)) !=
            (TESSDATA_AS_UINT(tessDataBeforeJoin.w) &
             (MIRRORED_CONTOUR_CONTOUR_FLAG | 0xffffu)))
        {
            // We reached over into a new contour. The edge immediately after
            // this feather join is actually the first vertex in the countour.
            tessDataAfterJoin =
                TEXEL_FETCH(@tessVertexTexture,
                            tess_texel_coord(int(vertexIndex0)));
        }

        featherJoinEdge0Theta = TESSDATA_AS_FLOAT(tessDataBeforeJoin.z);
        float featherJoinEdge1Theta = TESSDATA_AS_FLOAT(tessDataAfterJoin.z);
        featherJoinCornerTheta = featherJoinEdge1Theta - featherJoinEdge0Theta;
        if (abs(featherJoinCornerTheta) > PI)
            featherJoinCornerTheta -= _2PI * sign(featherJoinCornerTheta);

        // Feather joins draw backwards segments across the angle outside the
        // join, in order to erase some of the coverage that got written. Divide
        // the forward and backward segments proportionally to their respective
        // angles.
        float nonHelperSegmentCount =
            joinSegmentCount + 1. - float(FEATHER_JOIN_HELPER_VERTEX_COUNT);
        float forwardSegmentCount = clamp(
            round(abs(featherJoinCornerTheta) / PI * nonHelperSegmentCount),
            1.,
            nonHelperSegmentCount - 1.);
        float backwardSegmentCount =
            nonHelperSegmentCount - forwardSegmentCount;
        if (joinVertexID <= backwardSegmentCount)
        {
            // We're a backwards segment of the feather join.
            featherJoinCornerTheta =
                -(PI * sign(featherJoinCornerTheta) - featherJoinCornerTheta);
            joinSegmentCount = backwardSegmentCount;
            // On the final backward vertex, negate outset (later we will use
            // theta=featherJoinEdge1Theta instead of
            // featherJoinEdge1Theta - PI). This creates a crack-free
            // tessellation with the edge we're joining.
            if (joinVertexID == backwardSegmentCount)
                outset = -outset;
        }
        else if (joinVertexID == backwardSegmentCount + 1.)
        {
            // There's a discontinuous jump between the backward and forward
            // segments. This is a throwaway vertex to disconnect them.
            joinVertexID = .0;
            joinSegmentCount = .0;
            outset = .0;
        }
        else
        {
            // We're a forward segment of the feather join.
            joinVertexID -= backwardSegmentCount + 2.;
            joinSegmentCount = forwardSegmentCount;
        }

        if (joinVertexID == joinSegmentCount)
        {
            // Emit "featherJoinEdge1Theta" precisely (instead of the
            // approximate lerp below) to create crack-free tessellation with
            // the edges we're joining.
            theta = featherJoinEdge1Theta;
        }
        else
        {
            theta = featherJoinEdge0Theta +
                    featherJoinCornerTheta * (joinVertexID / joinSegmentCount);
        }
    }
    else
#endif // @ENABLE_FEATHER
    {
        theta = TESSDATA_AS_FLOAT(tessVertexData.z);
    }
    float2 norm = float2(sin(theta), -cos(theta));
    float2 origin = TESSDATA_AS_FLOAT(tessVertexData.xy);
    float2 postTransformVertexOffset = float2(0, 0);

    if (featherRadius != .0)
    {
        // Never use a feather harder than 1.5 standard deviations across a
        // radius of 1/2px. This is the point where feathering just looks like
        // antialiasing, and any harder looks aliased.
        featherRadius =
            max(featherRadius,
                (FEATHER_TEXTURE_STDDEVS / 3.) / length(MUL(M, norm)));
    }

    if (strokeRadius != .0) // Is this a stroke?
    {
        // Ensure strokes always emit clockwise triangles.
        outset *= sign(determinant(M));

        // Joins only emanate from the outer side of the stroke.
        if ((contourIDWithFlags & LEFT_JOIN_CONTOUR_FLAG) != 0u)
            outset = min(outset, .0);
        if ((contourIDWithFlags & RIGHT_JOIN_CONTOUR_FLAG) != 0u)
            outset = max(outset, .0);

        float aaRadius = featherRadius != .0
                             ? featherRadius
                             : manhattan_pixel_width(M, norm) * AA_RADIUS;
        half globalCoverage = 1.;
        if (aaRadius > strokeRadius && featherRadius == .0)
        {
            // The stroke is narrower than the AA ramp. Instead of emitting
            // subpixel geometry, make the stroke as wide as the AA ramp and
            // apply a global coverage multiplier.
            globalCoverage =
                cast_float_to_half(strokeRadius) / cast_float_to_half(aaRadius);
            strokeRadius = aaRadius;
        }

        // Extend the vertex by half the width of the AA ramp.
        float2 vertexOffset =
            norm * (strokeRadius + aaRadius); // Bloat stroke width for AA.

#ifndef @RENDER_MODE_MSAA
        // Calculate the AA distance to both the outset and inset edges of the
        // stroke. The fragment shader will use whichever is lesser.
        float x = outset * (strokeRadius + aaRadius);
        outCoverages.xy =
            (1. / (aaRadius * 2.)) * (float2(x, -x) + strokeRadius) + .5;
        outCoverages.zw = make_float2(.0);
#endif

        uint joinType = contourIDWithFlags & JOIN_TYPE_MASK;
        if (joinType > ROUND_JOIN_CONTOUR_FLAG)
        {
            // This vertex belongs to a miter or bevel join. Begin by finding
            // the bisector, which is the same as the miter line. The first two
            // vertices in the join peek forward to figure out the bisector, and
            // the final two peek backward.
            int peekDir = 2;
            if ((contourIDWithFlags & JOIN_TANGENT_0_CONTOUR_FLAG) == 0u)
                peekDir = -peekDir;
            if ((contourIDWithFlags & MIRRORED_CONTOUR_CONTOUR_FLAG) != 0u)
                peekDir = -peekDir;
            int2 otherJoinTexelCoord =
                tess_texel_coord(tessVertexIdx + peekDir);
            TESSDATA4 otherJoinData =
                TEXEL_FETCH(@tessVertexTexture, otherJoinTexelCoord);
            float otherJoinTheta = TESSDATA_AS_FLOAT(otherJoinData.z);
            float joinAngle = abs(otherJoinTheta - theta);
            if (joinAngle > PI)
                joinAngle = _2PI - joinAngle;
            bool isTan0 =
                (contourIDWithFlags & JOIN_TANGENT_0_CONTOUR_FLAG) != 0u;
            bool isLeftJoin =
                (contourIDWithFlags & LEFT_JOIN_CONTOUR_FLAG) != 0u;
            float bisectTheta =
                joinAngle * (isTan0 == isLeftJoin ? -.5 : .5) + theta;
            float2 bisector = float2(sin(bisectTheta), -cos(bisectTheta));
            float bisectPixelWidth = manhattan_pixel_width(M, bisector);

            // Generalize everything to a "miter-clip", which is proposed in the
            // SVG-2 draft. Bevel joins are converted to miter-clip joins with a
            // miter limit of 1/2 pixel. They technically bleed out 1/2 pixel
            // when drawn this way, but they seem to look fine and there is not
            // an obvious solution to antialias them without an ink bleed.
            float miterRatio = cos(joinAngle * .5);
            float clipRadius;
            if ((joinType == MITER_CLIP_JOIN_CONTOUR_FLAG) ||
                (joinType == MITER_REVERT_JOIN_CONTOUR_FLAG &&
                 miterRatio >= .25))
            {
                // Miter! (Or square cap.)
                // We currently use hard coded miter limits:
                //   * 1 for square caps being emulated as miter-clip joins.
                //   * 4, which is the SVG default, for all other miter joins.
                float miterInverseLimit =
                    (contourIDWithFlags & EMULATED_STROKE_CAP_CONTOUR_FLAG) !=
                            0u
                        ? 1.
                        : .25;
                clipRadius =
                    strokeRadius * (1. / max(miterRatio, miterInverseLimit));
            }
            else
            {
                // Bevel! (Or butt cap.)
                clipRadius = strokeRadius * miterRatio +
                             /* 1/2px bleed! */ bisectPixelWidth * .5;
            }
            float clipAARadius = clipRadius + bisectPixelWidth * AA_RADIUS;
            if ((contourIDWithFlags & JOIN_TANGENT_INNER_CONTOUR_FLAG) != 0u)
            {
                // Reposition the inner join vertices at the miter-clip
                // positions. Leave the outer join vertices as duplicates on the
                // surrounding curve endpoints. We emit duplicate vertex
                // positions because we need a hard stop on the clip distance
                // (see below).
                //
                // Use aaRadius here because we're tracking AA on the mitered
                // edge, NOT the outer clip edge.
                float strokeAARaidus = strokeRadius + aaRadius;
                // clipAARadius must be 1/16 of an AA ramp (~1/16 pixel) longer
                // than the miter length before we start clipping, to ensure we
                // are solving for a numerically stable intersection.
                float slop = aaRadius * .125;
                if (strokeAARaidus <= clipAARadius * miterRatio + slop)
                {
                    // The miter point is before the clip line. Extend out to
                    // the miter point.
                    float miterAARadius = strokeAARaidus * (1. / miterRatio);
                    vertexOffset = bisector * miterAARadius;
                }
                else
                {
                    // The clip line is before the miter point. Find where the
                    // clip line and the mitered edge intersect.
                    float2 bisectAAOffset = bisector * clipAARadius;
                    float2 k = float2(dot(vertexOffset, vertexOffset),
                                      dot(bisectAAOffset, bisectAAOffset));
                    vertexOffset =
                        MUL(k, inverse(float2x2(vertexOffset, bisectAAOffset)));
                }
            }
            // The clip distance tells us how to antialias the outer clipped
            // edge. Since joins only emanate from the outset side of the
            // stroke, we can repurpose the inset distance as the clip distance.
            float2 pt = abs(outset) * vertexOffset;
            float clipDistance = (clipAARadius - dot(pt, bisector)) /
                                 (bisectPixelWidth * (AA_RADIUS * 2.));
#ifndef @RENDER_MODE_MSAA
            if ((contourIDWithFlags & LEFT_JOIN_CONTOUR_FLAG) != 0u)
                outCoverages.y = clipDistance;
            else
                outCoverages.x = clipDistance;
#endif
        }

#ifndef @RENDER_MODE_MSAA
        outCoverages.xy *= globalCoverage;

        // Bias outCoverages.y slightly upwards in order to guarantee
        // outCoverages.y is >= 0 at every pixel. "outCoverages.y < 0" is
        // used to differentiate between strokes and fills.
        outCoverages.y = max(outCoverages.y, 1e-4);

        if (featherRadius != .0)
        {
            // Bias x to tell the fragment shader that this is a feathered
            // stroke.
            outCoverages.x = FEATHER_COVERAGE_BIAS - outCoverages.x;
        }
#endif

        postTransformVertexOffset = MUL(M, outset * vertexOffset);

        // Throw away the fan triangles since we're a stroke.
        if (vertexType != STROKE_VERTEX)
            return false;
    }
    else // This is a fill.
    {
#ifndef @RENDER_MODE_MSAA
        // "outCoverages.y < 0" indicates to the fragment shader that this is
        // a fill, as opposed to a stroke.
        outCoverages = float4(fillCoverage, -1., .0, .0);

#ifdef @ENABLE_FEATHER
        if (featherRadius != .0)
        {
            // Bias y to tell the fragment shader that this is a feathered edge.
            outCoverages.y = FEATHER_COVERAGE_BIAS;

            // "outCoverages.z = HORIZONTAL_COTANGENT_VALUE" initializes us
            // in a default state of feathering a flat edge (as opposed to a
            // corner).
            outCoverages.z = HORIZONTAL_COTANGENT_VALUE;

            // eval_feathered_fill() just feathers outCoverages.w=y0 when
            // we're a flat edge, so initialize it with fillCoverage.
            outCoverages.w = fillCoverage;

            if ((contourIDWithFlags & JOIN_TYPE_MASK) ==
                    FEATHER_JOIN_CONTOUR_FLAG &&
                vertexType == STROKE_VERTEX)
            {
                // Feathered corners are symmetric; swap the first and second
                // edge if needed so the corner angle is always positive.
                if (featherJoinCornerTheta < .0)
                {
                    featherJoinEdge0Theta += featherJoinCornerTheta;
                    featherJoinCornerTheta = -featherJoinCornerTheta;
                }

                // Find the angle and local outset direction of our specific
                // spoke in the feather join, relative to the first edge. Take
                // advantage of the fact that feathered corners are symmetric
                // again, and limit spokeTheta to the first half of the join
                // angle.
                float spokeTheta = theta - featherJoinEdge0Theta;
                spokeTheta = mod(spokeTheta + PI_OVER_2, _2PI) - PI_OVER_2;
                spokeTheta = clamp(spokeTheta, .0, featherJoinCornerTheta);
                if (spokeTheta > featherJoinCornerTheta * .5)
                {
                    spokeTheta = featherJoinCornerTheta - spokeTheta;
                }
                float2 spokeNorm = float2(sin(spokeTheta), cos(spokeTheta));

                // TODO: This contraction logic generates cracks in geometry. It
                // needs more investigation.
#if 0
                // When coners have stong curvature, their feather diminishes
                // faster than it does for flat edges. In this scenario we can
                // contract the tessellation a little to save on performance
                // without losing visual fidelity.
                //
                // This code attempts to be somewhat methodical, but it's just
                // hackery. The idea is to measure actual feather coverage at an
                // outset of N standard deviations, compare that to what
                // coverage would have been for a flat edge, and contract
                // accordingly. By observation, a logarithmic function of
                // featherJoinCornerTheta gives values for N with a good balance
                // of perf and quality.
                float N =
                    1. + .33 * log2(PI_OVER_2 /
                            (PI - min(featherJoinCornerTheta, PI - PI / 16.)));
                float4 coveragesAtNStddevOutset =
                    pack_feathered_fill_coverages(featherJoinCornerTheta,
                            spokeNorm,
                            .5 * (N / 3.));
                float featherAtNStddevOutset = eval_feathered_fill(
                        coveragesAtNStddevOutset TEXTURE_CONTEXT_FORWARD);
                float inverseFeather =
                    INVERSE_FEATHER(featherAtNStddevOutset);
                float stddevsAwayFromCenter =
                    (.5 - inverseFeather) * (FEATHER_TEXTURE_STDDEVS * 2.);
                float contraction = N / max(stddevsAwayFromCenter, N);
                outset *= contraction;
#endif

                // Emit coverage values for the fragment shader.
                outCoverages =
                    pack_feathered_fill_coverages(featherJoinCornerTheta,
                                                  spokeNorm,
                                                  outset);
            }
            // Offset the vertex for feathering.
            postTransformVertexOffset = MUL(M, (outset * featherRadius) * norm);
        }
        else
#endif // @ENABLE_FEATHER
        {
            // Offset the vertex for Manhattan AA.
            postTransformVertexOffset =
                sign(MUL(outset * norm, inverse(M))) * AA_RADIUS;
        }

        if (bool(contourIDWithFlags & MIRRORED_CONTOUR_CONTOUR_FLAG) !=
            bool(contourIDWithFlags & NEGATE_PATH_FILL_COVERAGE_FLAG))
        {
            // Effectively: outCoverages.x = -outCoverages.x
            //
            // ... But don't write that because it hits a bug in the Mali T720
            // compiler that also negates Y.
            outCoverages *= float4(-1., +1., +1., +1.);
        }
#endif // !RENDER_MODE_MSAA

        // Place the fan point.
        if (vertexType == FAN_MIDPOINT_VERTEX)
            origin = midpoint;

        // If we're actually just drawing a triangle, throw away the entire
        // patch except a single fan triangle.
        if ((contourIDWithFlags & RETROFITTED_TRIANGLE_CONTOUR_FLAG) != 0u &&
            vertexType != FAN_VERTEX)
        {
            return false;
        }
    }

    outVertexPosition = MUL(M, origin) + postTransformVertexOffset + translate;

#ifdef @RENDER_MODE_MSAA
    uint4 pathData2 = STORAGE_BUFFER_LOAD4(@pathBuffer, outPathID * 4u + 2u);
    outPathZIndex = cast_uint_to_ushort(pathData2.r);
#else
    // Force coverage to solid when wireframe is enabled so we can see the
    // triangles.
    outCoverages.xy = mix(outCoverages.xy,
                          float2(1., -1.),
                          make_bool2(uniforms.wireframeEnabled != 0u));
#endif

    return true;
}
#endif // @VERTEX && @DRAW_PATH

#if defined(@VERTEX) && defined(@DRAW_INTERIOR_TRIANGLES)
INLINE float2 unpack_interior_triangle_vertex(float3 triangleVertex,
                                              OUT(uint) outPathID
#ifdef @RENDER_MODE_MSAA
                                              ,
                                              OUT(ushort) outPathZIndex
#else
                                              ,
                                              OUT(half) outWindingWeight
#endif
                                                  VERTEX_CONTEXT_DECL)
{
    outPathID = floatBitsToUint(triangleVertex.z) & 0xffffu;
#ifdef @RENDER_MODE_MSAA
    uint4 pathData2 = STORAGE_BUFFER_LOAD4(@pathBuffer, outPathID * 4u + 2u);
    outPathZIndex = cast_uint_to_ushort(pathData2.x);
#else
    outWindingWeight = cast_int_to_half(floatBitsToInt(triangleVertex.z) >> 16);
#endif
    float2 vertexPos = triangleVertex.xy;
    // ATLAS_BLIT draws vertices in screen space.
    float2x2 M = make_float2x2(
        uintBitsToFloat(STORAGE_BUFFER_LOAD4(@pathBuffer, outPathID * 4u)));
    uint4 pathData = STORAGE_BUFFER_LOAD4(@pathBuffer, outPathID * 4u + 1u);
    float2 translate = uintBitsToFloat(pathData.xy);
    vertexPos = MUL(M, vertexPos) + translate;
    return vertexPos;
}
#endif // @VERTEX && @DRAW_INTERIOR_TRIANGLES

#if defined(@VERTEX) && defined(@ATLAS_BLIT)
INLINE float2
unpack_atlas_coverage_vertex(float3 triangleVertex,
                             OUT(uint) outPathID,
#ifdef @RENDER_MODE_MSAA
                             OUT(ushort) outPathZIndex,
#endif
                             OUT(float2) outAtlasCoord VERTEX_CONTEXT_DECL)
{
    outPathID = floatBitsToUint(triangleVertex.z) & 0xffffu;
    uint4 pathData2 = STORAGE_BUFFER_LOAD4(@pathBuffer, outPathID * 4u + 2u);
#ifdef @RENDER_MODE_MSAA
    outPathZIndex = cast_uint_to_ushort(pathData2.x);
#endif
    float2 vertexPos = triangleVertex.xy;
    // outAtlasCoord tells the fragment shader where to fetch coverage from the
    // atlas, when using atlas coverage.
    float3 atlasTransform = uintBitsToFloat(pathData2.yzw);
    outAtlasCoord = vertexPos * atlasTransform.x + atlasTransform.yz;
    return vertexPos;
}
#endif // @VERTEX && @ATLAS_BLIT
