/*
    Copyright 2016-2025 melonDS team

    This file is part of melonDS.

    melonDS is free software: you can redistribute it and/or modify it under
    the terms of the GNU General Public License as published by the Free
    Software Foundation, either version 3 of the License, or (at your option)
    any later version.

    melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with melonDS. If not, see http://www.gnu.org/licenses/.
*/

#ifndef GPU3D_COMPUTE_SHADERS
#define GPU3D_COMPUTE_SHADERS

#include <string>

namespace melonDS
{

namespace ComputeRendererShaders
{

// defines:
// InterpSpans
// BinCombined
// Rasterise
// DepthBlend
// ClearCoarseBinMask
// ClearIndirectWorkCount
// CalculateWorkOffsets
// SortWork
// FinalPass

// AntiAliasing
// EdgeMarking
// Fog

// ZBuffer
// WBuffer

// for Rasterise
// NoTexture
// UseTexture
// Decal
// Modulate
// Toon
// Highlight
// ShadowMask


/*
    Some notes on signed division:

    we want to avoid it, so we can avoid higher precision numbers
    in a few places.
    
    Fortunately all divisions *should* assuming I'm not mistaken
    have the same sign on the divisor and the dividend.

    Thus we apply:

    assuming n < 0 <=> d < 0
    n/d = abs(n)/abs(d)

*/

const std::string XSpanSetupBuffer{R"(

const uint XSpanSetup_Linear = 1U << 0;
const uint XSpanSetup_FillInside = 1U << 1;
const uint XSpanSetup_FillLeft = 1U << 2;
const uint XSpanSetup_FillRight = 1U << 3;

struct XSpanSetup
{
    int X0, X1;

    int InsideStart, InsideEnd, EdgeCovL, EdgeCovR;

    int XRecip;

    uint Flags;

    int Z0, Z1, W0, W1;
    int ColorR0, ColorG0, ColorB0;
    int ColorR1, ColorG1, ColorB1;
    int TexcoordU0, TexcoordV0;
    int TexcoordU1, TexcoordV1;

    int CovLInitial, CovRInitial;
};

#if defined(Rasterise)
int CalcYFactorX(XSpanSetup span, int x)
{
    x -= span.X0;

    if (span.X0 != span.X1)
    {
        uint numLo = uint(x) * uint(span.W0);
        uint numHi = 0U;
        numHi |= numLo >> (32U-YFactorShift);
        numLo <<= YFactorShift;

        uint den = uint(x) * uint(span.W0) + uint(span.X1 - span.X0 - x) * uint(span.W1);

        if (den == 0)
            return 0;
        else
            return int(Div64_32_32(numHi, numLo, den));
    }
    else
    {
        return 0;
    }
}
#endif

layout (std430, binding = 1) buffer XSpanSetupsBuffer
{
    XSpanSetup XSpanSetups[];
};

)"};

const std::string YSpanSetupBuffer{R"(

struct YSpanSetup
{
    // Attributes
    int Z0, Z1, W0, W1;
    int ColorR0, ColorG0, ColorB0;
    int ColorR1, ColorG1, ColorB1;
    int TexcoordU0, TexcoordV0;
    int TexcoordU1, TexcoordV1;

    // Interpolator
    int I0, I1;
    bool Linear;
    int IRecip;
    int W0n, W0d, W1d;

    // Slope
    int Increment;

    int X0, X1, Y0, Y1;
    int XMin, XMax;
    int DxInitial;

    int XCovIncr;

    bool IsDummy;
};

#if defined(InterpSpans)
int CalcYFactorY(YSpanSetup span, int i)
{
    /*
        maybe it would be better to do use a 32x32=64 multiplication?
    */
    uint numLo = uint(abs(i)) * uint(span.W0n);
    uint numHi = 0U;
    numHi |= numLo >> (32U-YFactorShift);
    numLo <<= YFactorShift;

    uint den = uint(abs(i)) * uint(span.W0d) + uint(abs(span.I1 - span.I0 - i)) * span.W1d;

    if (den == 0)
    {
        return 0;
    }
    else
    {
        return int(Div64_32_32(numHi, numLo, den));
    }
}

int CalculateDx(int y, YSpanSetup span)
{
    return span.DxInitial + (y - span.Y0) * span.Increment;
}

int CalculateX(int dx, YSpanSetup span)
{
    int x = span.X0;
    if (span.X1 < span.X0)
        x -= dx >> 18;
    else
        x += dx >> 18;
    return clamp(x, span.XMin, span.XMax);
}

void EdgeParams_XMajor(bool side, int dx, YSpanSetup span, out int edgelen, out int edgecov)
{
    bool negative = span.X1 < span.X0;
    int len;
    if (side != negative)
        len = (dx >> 18) - ((dx-span.Increment) >> 18);
    else
        len = ((dx+span.Increment) >> 18) - (dx >> 18);
    edgelen = len;

    int xlen = span.XMax + 1 - span.XMin;
    int startx = dx >> 18;
    if (negative) startx = xlen - startx;
    if (side) startx = startx - len + 1;

    uint r;
    int startcov = int(Div(uint(((startx << 10) + 0x1FF) * (span.Y1 - span.Y0)), uint(xlen), r));
    edgecov = (1<<31) | ((startcov & 0x3FF) << 12) | (span.XCovIncr & 0x3FF);
}

void EdgeParams_YMajor(bool side, int dx, YSpanSetup span, out int edgelen, out int edgecov)
{
    bool negative = span.X1 < span.X0;
    edgelen = 1;
    
    if (span.Increment == 0)
    {
        edgecov = 31;
    }
    else
    {
        int cov = ((dx >> 9) + (span.Increment >> 10)) >> 4;
        if ((cov >> 5) != (dx >> 18)) cov = 31;
        cov &= 0x1F;
        if (side == negative) cov = 0x1F - cov;

        edgecov = cov;
    }
}
#endif

layout (std430, binding = 2) buffer YSpanSetupsBuffer
{
    YSpanSetup YSpanSetups[];
};

)"};

const std::string PolygonBuffer{R"(
struct Polygon
{
    int FirstXSpan;
    int YTop, YBot;

    int XMin, XMax;
    int XMinY, XMaxY;

    int Variant;

    uint Attr;

    float TextureLayer;
};

layout (std430, binding = 0) readonly buffer PolygonBuffer
{
    Polygon Polygons[];
};
)"};

const std::string BinningBuffer{R"(

layout (std430, binding = 6) buffer BinResultBuffer
{
    uvec4 VariantWorkCount[MaxVariants];
    uint SortedWorkOffset[MaxVariants];

    uvec4 SortWorkWorkCount;

    uint BinningMaskAndOffset[];
    //uint BinnedMaskCoarse[TilesPerLine*TileLines*CoarseBinStride];
    //uint BinnedMask[TilesPerLine*TileLines*BinStride];
    //uint WorkOffsets[TilesPerLine*TileLines*BinStride];
};

const int BinningCoarseMaskStart = 0;
const int BinningMaskStart = BinningCoarseMaskStart+TilesPerLine*TileLines*CoarseBinStride;
const int BinningWorkOffsetsStart = BinningMaskStart+TilesPerLine*TileLines*BinStride;

)"};

/*
    structure of each WorkDesc item:
        x:
            bits 0-10: polygon idx
            bits 11-31: tile idx (before sorting within variant after sorting within all tiles)
        y:
            bits 0-15: X position on screen
            bits 15-31: Y position on screen
*/
const std::string WorkDescBuffer{R"(
layout (std430, binding = 7) buffer WorkDescBuffer
{
    //uvec2 UnsortedWorkDescs[MaxWorkTiles];
    //uvec2 SortedWorkDescs[MaxWorkTiles];
    uvec2 WorkDescs[];
};

const uint WorkDescsUnsortedStart = 0;
const uint WorkDescsSortedStart = WorkDescsUnsortedStart+MaxWorkTiles;

)"};

const std::string Tilebuffers{R"(
layout (std430, binding = 2) buffer ColorTileBuffer
{
    uint ColorTiles[];
};
layout (std430, binding = 3) buffer DepthTileBuffer
{
    uint DepthTiles[];
};
layout (std430, binding = 4) buffer AttrTileBuffer
{
    uint AttrTiles[];
};

)"};

const std::string ResultBuffer{R"(
layout (std430, binding = 5) buffer ResultBuffer
{
    uint ResultValue[];
};

const uint ResultColorStart = 0;
const uint ResultDepthStart = ResultColorStart+ScreenWidth*ScreenHeight*2;
const uint ResultAttrStart = ResultDepthStart+ScreenWidth*ScreenHeight*2;
)"};

const char* Common = R"(

const int CoarseTileCountX = 8;
const int CoarseTileW = (CoarseTileCountX * TileSize);
const int CoarseTileH = (CoarseTileCountY * TileSize);

const int FramebufferStride = ScreenWidth*ScreenHeight;
const int TilesPerLine = ScreenWidth/TileSize;
const int TileLines = ScreenHeight/TileSize;

const int BinStride = 2048/32;
const int CoarseBinStride = BinStride/32;

const int MaxVariants = 256;

layout (std140, binding = 0) uniform MetaUniform
{
    uint NumPolygons;
    uint NumVariants;

    int AlphaRef;

    uint DispCnt;

    // r = Toon
    // g = Fog Density
    // b = Edge Color
    uvec4 ToonTable[34];

    uint ClearColor, ClearDepth, ClearAttr;

    uint FogOffset, FogShift, FogColor;
};

#ifdef InterpSpans
const int YFactorShift = 9;
#else
const int YFactorShift = 8;
#endif

#if defined(InterpSpans) || defined(Rasterise)
uint Umulh(uint a, uint b)
{
    uint lo, hi;
    umulExtended(a, b, hi, lo);
    return hi;
}

const uint startTable[256] = uint[256](
    254, 252, 250, 248, 246, 244, 242, 240, 238, 236, 234, 233, 231, 229, 227, 225, 224, 222, 220, 218, 217, 215, 213, 212, 210, 208, 207, 205, 203, 202, 200, 199, 197, 195, 194, 192, 191, 189, 188, 186, 185, 183, 182, 180, 179, 178, 176, 175, 173, 172, 170, 169, 168, 166, 165, 164, 162, 161, 160, 158, 
157, 156, 154, 153, 152, 151, 149, 148, 147, 146, 144, 143, 142, 141, 139, 138, 137, 136, 135, 134, 132, 131, 130, 129, 128, 127, 126, 125, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 88, 87, 86, 85, 84, 83, 82, 81, 80, 80, 79, 78, 77, 76, 75, 74, 74, 73, 72, 71, 70, 70, 69, 68, 67, 66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 59, 58, 57, 56, 56, 55, 54, 53, 53, 52, 51, 50, 50, 49, 48, 48, 47, 46, 46, 45, 44, 43, 43, 42, 41, 41, 40, 39, 39, 38, 37, 37, 36, 35, 35, 34, 33, 33, 32, 32, 31, 30, 30, 29, 28, 28, 27, 27, 26, 25, 25, 24, 24, 23, 22, 22, 21, 21, 20, 19, 19, 18, 18, 17, 17, 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0
);

uint Div(uint x, uint y, out uint r)
{
    // https://www.microsoft.com/en-us/research/publication/software-integer-division/
    uint k = 31 - findMSB(y);
    uint ty = (y << k) >> (32 - 9);
    uint t = startTable[ty - 256] + 256;
    uint z = (t << (32 - 9)) >> (32 - k - 1);
    uint my = 0 - y;

    z += Umulh(z, my * z);
    z += Umulh(z, my * z);

    uint q = Umulh(x, z);
    r = x - y * q;
    if(r >= y)
    {
        r = r - y;
        q = q + 1;
        if(r >= y)
        {
            r = r - y;
            q = q + 1;
        }
    }

    return q;
}

uint Div64_32_32(uint numHi, uint numLo, uint den)
{
    // based on https://github.com/ridiculousfish/libdivide/blob/3bd34388573681ce563348cdf04fe15d24770d04/libdivide.h#L469
    // modified to work with half the size 64/32=32 instead of 128/64=64
    // for further details see https://ridiculousfish.com/blog/posts/labor-of-division-episode-iv.html

    // We work in base 2**16.
    // A uint32 holds a single digit (in the lower 16 bit). A uint32 holds two digits.
    // Our numerator is conceptually [num3, num2, num1, num0].
    // Our denominator is [den1, den0].
    const uint b = (1U << 16);

    // Determine the normalization factor. We multiply den by this, so that its leading digit is at
    // least half b. In binary this means just shifting left by the number of leading zeros, so that
    // there's a 1 in the MSB.
    // We also shift numer by the same amount. This cannot overflow because numHi < den.
    // The expression (-shift & 63) is the same as (64 - shift), except it avoids the UB of shifting
    // by 64. (it's also UB in GLSL!!!!)
    uint shift = 31 - findMSB(den);
    den <<= shift;
    numHi <<= shift;
    numHi |= (numLo >> (-shift & 31U)) & uint(-int(shift) >> 31);
    numLo <<= shift;

    // Extract the low digits of the numerator and both digits of the denominator.
    uint num1 = (numLo >> 16);
    uint num0 = (numLo & 0xFFFFU);
    uint den1 = (den >> 16);
    uint den0 = (den & 0xFFFFU);

    // We wish to compute q1 = [n3 n2 n1] / [d1 d0].
    // Estimate q1 as [n3 n2] / [d1], and then correct it.
    // Note while qhat may be 2 digits, q1 is always 1 digit.

    uint rhat;
    uint qhat = Div(numHi, den1, rhat);
    uint c1 = qhat * den0;
    uint c2 = rhat * b + num1;
    if (c1 > c2) qhat -= (c1 - c2 > den) ? 2 : 1;
    uint q1 = qhat & 0xFFFFU;

    // Compute the true (partial) remainder.
    uint rem = numHi * b + num1 - q1 * den;

    // We wish to compute q0 = [rem1 rem0 n0] / [d1 d0].
    // Estimate q0 as [rem1 rem0] / [d1] and correct it.
    qhat = Div(rem, den1, rhat);
    c1 = qhat * den0;
    c2 = rhat * b + num0;
    if (c1 > c2) qhat -= (c1 - c2 > den) ? 2 : 1;

    return bitfieldInsert(qhat, q1, 16, 16);
}

int InterpolateAttrPersp(int y0, int y1, int ifactor)
{
    if (y0 == y1)
        return y0;

    if (y0 < y1)
        return y0 + (((y1-y0) * ifactor) >> YFactorShift);
    else
        return y1 + (((y0-y1) * ((1<<YFactorShift)-ifactor)) >> YFactorShift);
}

int InterpolateAttrLinear(int y0, int y1, int i, int irecip, int idiff)
{
    if (y0 == y1)
        return y0;

#ifndef Rasterise
    irecip = abs(irecip);
#endif

    uint mulLo, mulHi, carry;
    if (y0 < y1)
    {
#ifndef Rasterise
        uint offset = uint(abs(i));
#else
        uint offset = uint(i);
#endif
        umulExtended(uint(y1-y0)*offset, uint(irecip), mulHi, mulLo);
        mulLo = uaddCarry(mulLo, 3U<<24, carry);
        mulHi += carry;
        return y0 + int((mulLo >> 30) | (mulHi << (32 - 30)));
        //return y0 + int(((int64_t(y1-y0) * int64_t(offset) * int64_t(irecip)) + int64_t(3<<24)) >> 30);
    }
    else
    {
#ifndef Rasterise
        uint offset = uint(abs(idiff-i));
#else
        uint offset = uint(idiff-i);
#endif
        umulExtended(uint(y0-y1)*offset, uint(irecip), mulHi, mulLo);
        mulLo = uaddCarry(mulLo, 3<<24, carry);
        mulHi += carry;
        return y1 + int((mulLo >> 30) | (mulHi << (32 - 30)));
        //return y1 + int(((int64_t(y0-y1) * int64_t(offset) * int64_t(irecip)) + int64_t(3<<24)) >> 30);
    }
}

uint InterpolateZZBuffer(int z0, int z1, int i, int irecip, int idiff)
{
    if (z0 == z1)
        return z0;

    uint base, disp, factor;
    if (z0 < z1)
    {
        base = uint(z0);
        disp = uint(z1 - z0);
        factor = uint(abs(i));
    }
    else
    {
        base = uint(z1);
        disp = uint(z0 - z1),
        factor = uint(abs(idiff - i));
    }

#ifdef InterpSpans
    int shiftl = 0;
    const int shiftr = 22;
    if (disp > 0x3FF)
    {
        shiftl = findMSB(disp) - 9;
        disp >>= shiftl;
    }
#else
    disp >>= 9;
    const int shiftl = 0;
    const int shiftr = 13;
#endif
    uint mulLo, mulHi;

    umulExtended(disp * factor, abs(irecip) >> 8, mulHi, mulLo);

    return base + (((mulLo >> shiftr) | (mulHi << (32 - shiftr))) << shiftl);
/*
    int base, disp, factor;
    if (z0 < z1)
    {
        base = z0;
        disp = z1 - z0;
        factor = i;
    }
    else
    {
        base = z1;
        disp = z0 - z1,
        factor = idiff - i;
    }

#ifdef InterpSpans
    {
        int shift = 0;
        while (disp > 0x3FF)
        {
            disp >>= 1;
            shift++;
        }

        return base + int(((int64_t(disp) * int64_t(factor) * (int64_t(irecip) >> 8)) >> 22) << shift);
    }
#else
    {
        disp >>= 9;
        return base + int((int64_t(disp) * int64_t(factor) * (int64_t(irecip) >> 8)) >> 13);
    }
#endif*/
}

uint InterpolateZWBuffer(int z0, int z1, int ifactor)
{
    if (z0 == z1)
        return z0;

#ifdef Rasterise
    // since the precision along x spans is only 8 bit the result will always fit in 32-bit
    if (z0 < z1)
    {
        return uint(z0) + (((z1-z0) * ifactor) >> YFactorShift);
    }
    else
    {
        return uint(z1) + (((z0-z1) * ((1<<YFactorShift)-ifactor)) >> YFactorShift);
    }
#else
    uint mulLo, mulHi;
    if (z0 < z1)
    {
        umulExtended(z1-z0, ifactor, mulHi, mulLo);
        // 64-bit shift
        return uint(z0) + ((mulLo >> YFactorShift) | (mulHi << (32-YFactorShift)));
    }
    else
    {
        umulExtended(z0-z1, (1<<YFactorShift)-ifactor, mulHi, mulLo);
        return uint(z1) + ((mulLo >> YFactorShift) | (mulHi << (32-YFactorShift)));
    }
#endif
    /*if (z0 < z1)
    {
        return uint(z0) + uint((int64_t(z1-z0) * int64_t(ifactor)) >> YFactorShift);
    }
    else
    {
        return uint(z1) + uint((int64_t(z0-z1) * int64_t((1<<YFactorShift)-ifactor)) >> YFactorShift);
    }*/
}
#endif

)";

const std::string InterpSpans = 
    PolygonBuffer +
    XSpanSetupBuffer +
    YSpanSetupBuffer + R"(
layout (local_size_x = 32) in;

layout (binding = 0, rgba16ui) uniform readonly uimageBuffer SetupIndices;

void main()
{
    uvec4 setup = imageLoad(SetupIndices, int(gl_GlobalInvocationID.x));

    YSpanSetup spanL = YSpanSetups[setup.y];
    YSpanSetup spanR = YSpanSetups[setup.z];
    
    XSpanSetup xspan;
    xspan.Flags = 0U;

    int y = int(setup.w);

    int dxl = CalculateDx(y, spanL);
    int dxr = CalculateDx(y, spanR);

    int xl = CalculateX(dxl, spanL);
    int xr = CalculateX(dxr, spanR);

    Polygon polygon = Polygons[setup.x];

    int edgeLenL, edgeLenR;

    if (xl > xr)
    {
        YSpanSetup tmpSpan = spanL;
        spanL = spanR;
        spanR = tmpSpan;

        int tmp = xl;
        xl = xr;
        xr = tmp;
    
        EdgeParams_YMajor(false, dxr, spanL, edgeLenL, xspan.EdgeCovL);
        EdgeParams_YMajor(true, dxl, spanR, edgeLenR, xspan.EdgeCovR);
    }
    else
    {
        // edges are the right way
        if (spanL.Increment > 0x40000)
            EdgeParams_XMajor(false, dxl, spanL, edgeLenL, xspan.EdgeCovL);
        else
            EdgeParams_YMajor(false, dxl, spanL, edgeLenL, xspan.EdgeCovL);
        if (spanR.Increment > 0x40000)
            EdgeParams_XMajor(true, dxr, spanR, edgeLenR, xspan.EdgeCovR);
        else
            EdgeParams_YMajor(true, dxr, spanR, edgeLenR, xspan.EdgeCovR);
    }

    xspan.CovLInitial = (xspan.EdgeCovL >> 12) & 0x3FF;
    if (xspan.CovLInitial == 0x3FF)
        xspan.CovLInitial = 0;
    xspan.CovRInitial = (xspan.EdgeCovR >> 12) & 0x3FF;
    if (xspan.CovRInitial == 0x3FF)
        xspan.CovRInitial = 0;

    xspan.X0 = xl;
    xspan.X1 = xr + 1;

    uint polyalpha = ((polygon.Attr >> 16) & 0x1FU);
    bool isWireframe = polyalpha == 0U;

    if (!isWireframe || (y == polygon.YTop || y == polygon.YBot - 1))
        xspan.Flags |= XSpanSetup_FillInside;

    xspan.InsideStart = xspan.X0 + edgeLenL;
    if (xspan.InsideStart > xspan.X1)
        xspan.InsideStart = xspan.X1;
    xspan.InsideEnd = xspan.X1 - edgeLenR;
    if (xspan.InsideEnd > xspan.X1)
        xspan.InsideEnd = xspan.X1;

    bool isShadowMask = ((polygon.Attr & 0x3F000030U) == 0x00000030U);
    bool fillAllEdges = polyalpha < 31 || (DispCnt & (3U<<4)) != 0U;

    if (fillAllEdges || spanL.X1 < spanL.X0 || spanL.Increment <= 0x40000)
        xspan.Flags |= XSpanSetup_FillLeft;
    if (fillAllEdges || (spanR.X1 >= spanR.X0 && spanR.Increment > 0x40000) || spanR.Increment == 0)
        xspan.Flags |= XSpanSetup_FillRight;

    if (spanL.I0 == spanL.I1)
    {
        xspan.TexcoordU0 = spanL.TexcoordU0;
        xspan.TexcoordV0 = spanL.TexcoordV0;
        xspan.ColorR0 = spanL.ColorR0;
        xspan.ColorG0 = spanL.ColorG0;
        xspan.ColorB0 = spanL.ColorB0;
        xspan.Z0 = spanL.Z0;
        xspan.W0 = spanL.W0;
    }
    else
    {
        int i = (spanL.Increment > 0x40000 ? xl : y) - spanL.I0;
        int ifactor = CalcYFactorY(spanL, i);
        int idiff = spanL.I1 - spanL.I0;

#ifdef ZBuffer
        xspan.Z0 = int(InterpolateZZBuffer(spanL.Z0, spanL.Z1, i, spanL.IRecip, idiff));
#endif
#ifdef WBuffer
        xspan.Z0 = int(InterpolateZWBuffer(spanL.Z0, spanL.Z1, ifactor));
#endif

        if (!spanL.Linear)
        {
            xspan.TexcoordU0 = InterpolateAttrPersp(spanL.TexcoordU0, spanL.TexcoordU1, ifactor);
            xspan.TexcoordV0 = InterpolateAttrPersp(spanL.TexcoordV0, spanL.TexcoordV1, ifactor);

            xspan.ColorR0 = InterpolateAttrPersp(spanL.ColorR0, spanL.ColorR1, ifactor);
            xspan.ColorG0 = InterpolateAttrPersp(spanL.ColorG0, spanL.ColorG1, ifactor);
            xspan.ColorB0 = InterpolateAttrPersp(spanL.ColorB0, spanL.ColorB1, ifactor);

            xspan.W0 = InterpolateAttrPersp(spanL.W0, spanL.W1, ifactor);
        }
        else
        {
            xspan.TexcoordU0 = InterpolateAttrLinear(spanL.TexcoordU0, spanL.TexcoordU1, i, spanL.IRecip, idiff);
            xspan.TexcoordV0 = InterpolateAttrLinear(spanL.TexcoordV0, spanL.TexcoordV1, i, spanL.IRecip, idiff);

            xspan.ColorR0 = InterpolateAttrLinear(spanL.ColorR0, spanL.ColorR1, i, spanL.IRecip, idiff);
            xspan.ColorG0 = InterpolateAttrLinear(spanL.ColorG0, spanL.ColorG1, i, spanL.IRecip, idiff);
            xspan.ColorB0 = InterpolateAttrLinear(spanL.ColorB0, spanL.ColorB1, i, spanL.IRecip, idiff);

            xspan.W0 = spanL.W0; // linear mode is only taken if W0 == W1
        }
    }

    if (spanR.I0 == spanR.I1)
    {
        xspan.TexcoordU1 = spanR.TexcoordU0;
        xspan.TexcoordV1 = spanR.TexcoordV0;
        xspan.ColorR1 = spanR.ColorR0;
        xspan.ColorG1 = spanR.ColorG0;
        xspan.ColorB1 = spanR.ColorB0;
        xspan.Z1 = spanR.Z0;
        xspan.W1 = spanR.W0;
    }
    else
    {
        int i = (spanR.Increment > 0x40000 ? xr : y) - spanR.I0;
        int ifactor = CalcYFactorY(spanR, i);
        int idiff = spanR.I1 - spanR.I0;

    #ifdef ZBuffer
            xspan.Z1 = int(InterpolateZZBuffer(spanR.Z0, spanR.Z1, i, spanR.IRecip, idiff));
    #endif
    #ifdef WBuffer
            xspan.Z1 = int(InterpolateZWBuffer(spanR.Z0, spanR.Z1, ifactor));
    #endif

        if (!spanR.Linear)
        {
            xspan.TexcoordU1 = InterpolateAttrPersp(spanR.TexcoordU0, spanR.TexcoordU1, ifactor);
            xspan.TexcoordV1 = InterpolateAttrPersp(spanR.TexcoordV0, spanR.TexcoordV1, ifactor);

            xspan.ColorR1 = InterpolateAttrPersp(spanR.ColorR0, spanR.ColorR1, ifactor);
            xspan.ColorG1 = InterpolateAttrPersp(spanR.ColorG0, spanR.ColorG1, ifactor);
            xspan.ColorB1 = InterpolateAttrPersp(spanR.ColorB0, spanR.ColorB1, ifactor);

            xspan.W1 = int(InterpolateAttrPersp(spanR.W0, spanR.W1, ifactor));
        }
        else
        {
            xspan.TexcoordU1 = InterpolateAttrLinear(spanR.TexcoordU0, spanR.TexcoordU1, i, spanR.IRecip, idiff);
            xspan.TexcoordV1 = InterpolateAttrLinear(spanR.TexcoordV0, spanR.TexcoordV1, i, spanR.IRecip, idiff);

            xspan.ColorR1 = InterpolateAttrLinear(spanR.ColorR0, spanR.ColorR1, i, spanR.IRecip, idiff);
            xspan.ColorG1 = InterpolateAttrLinear(spanR.ColorG0, spanR.ColorG1, i, spanR.IRecip, idiff);
            xspan.ColorB1 = InterpolateAttrLinear(spanR.ColorB0, spanR.ColorB1, i, spanR.IRecip, idiff);

            xspan.W1 = spanR.W0;
        }
    }

    if (xspan.W0 == xspan.W1 && ((xspan.W0 | xspan.W1) & 0x7F) == 0)
    {
        xspan.Flags |= XSpanSetup_Linear;
// a bit hacky, but when wbuffering we only need to calculate xrecip for linear spans
#ifdef ZBuffer
    }
    {
#endif
        uint r;
        xspan.XRecip = int(Div(1U<<30, uint(xspan.X1 - xspan.X0), r));
    }

    XSpanSetups[gl_GlobalInvocationID.x] = xspan;
}

)";

const std::string ClearIndirectWorkCount =
    BinningBuffer + R"(

layout (local_size_x = 32) in;

void main()
{
    VariantWorkCount[gl_GlobalInvocationID.x] = uvec4(1, 1, 0, 0);
}

)";

const std::string ClearCoarseBinMask =
    BinningBuffer + R"(
layout (local_size_x = ClearCoarseBinMaskLocalSize) in;

void main()
{
    BinningMaskAndOffset[BinningCoarseMaskStart + gl_GlobalInvocationID.x*CoarseBinStride+0] = 0;
    BinningMaskAndOffset[BinningCoarseMaskStart + gl_GlobalInvocationID.x*CoarseBinStride+1] = 0;
}

)";

const std::string BinCombined =
    PolygonBuffer +
    BinningBuffer +
    XSpanSetupBuffer +
    WorkDescBuffer + R"(

layout (local_size_x = CoarseTileArea) in;

bool BinPolygon(Polygon polygon, ivec2 topLeft, ivec2 botRight)
{
    if (polygon.YTop > botRight.y || polygon.YBot <= topLeft.y)
        return false;

    int polygonHeight = polygon.YBot - polygon.YTop;

    /*
        All (good) polygons are convex. So the following holds true:

        Starting from the top most point where both edges originate
        the X coordinate of the left edge will stay the same or falls until
        the minimum X-axis coordinate is reached. Then it stays the same or
        rises until the point it meets with the right edge.

        The same applies to the right edge, except that it first may rise or stay equal and
        after the maximum point may only fall or stay the same.

        This means that for every tile which doesn't contain the point where the direction changes
        we can just get the maximum point by sampling the top most and bottom most coordinate
        within the tile.

        For a tile which is that the height of the direction change

        As a sidenote another consequence of this design decision is
        that malformed polygons aren't binned properly.

        As a note bottom Y is exclusive!
    */
    int polyInnerTopY = clamp(topLeft.y - polygon.YTop, 0, max(polygonHeight-1, 0));
    int polyInnerBotY = clamp(botRight.y - polygon.YTop, 0, max(polygonHeight-1, 0));

    XSpanSetup xspanTop = XSpanSetups[polygon.FirstXSpan + polyInnerTopY];
    XSpanSetup xspanBot = XSpanSetups[polygon.FirstXSpan + polyInnerBotY];

    int minXL;
    if (polygon.XMinY >= topLeft.y && polygon.XMinY <= botRight.y)
        minXL = polygon.XMin;
    else
        minXL = min(xspanTop.X0, xspanBot.X0);

    if (minXL > botRight.x)
        return false;

    int maxXR;
    if (polygon.XMaxY >= topLeft.y && polygon.XMaxY <= botRight.y)
        maxXR = polygon.XMax;
    else
        maxXR = max(xspanTop.X1, xspanBot.X1) - 1;

    if (maxXR < topLeft.x)
        return false;

    return true;
}

shared uint mergedMaskShared;

void main()
{
    int groupIdx = int(gl_WorkGroupID.x);
    ivec2 coarseTile = ivec2(gl_WorkGroupID.yz);

#if 0
    int localIdx = int(gl_SubGroupInvocationARB);
#else
    int localIdx = int(gl_LocalInvocationIndex);

    if (localIdx == 0)
        mergedMaskShared = 0U;
    barrier();
#endif

    int polygonIdx = groupIdx * 32 + localIdx;

    ivec2 coarseTopLeft = coarseTile * ivec2(CoarseTileW, CoarseTileH);
    ivec2 coarseBotRight = coarseTopLeft + ivec2(CoarseTileW-1, CoarseTileH-1);

    bool binned = false;
    if (polygonIdx < NumPolygons)
    {
        binned = BinPolygon(Polygons[polygonIdx], coarseTopLeft, coarseBotRight);
    }

#if 0
    uint mergedMask = unpackUint2x32(ballotARB(binned)).x;
#else
    if (binned)
        atomicOr(mergedMaskShared, 1U << localIdx);
    barrier();
    uint mergedMask = mergedMaskShared;
#endif

    ivec2 fineTile = ivec2(localIdx & 0x7, localIdx >> 3);

    ivec2 fineTileTopLeft = coarseTopLeft + fineTile * ivec2(TileSize, TileSize);
    ivec2 fineTileBotRight = fineTileTopLeft + ivec2(TileSize-1, TileSize-1);

    uint binnedMask = 0U;
    while (mergedMask != 0U)
    {
        int bit = findLSB(mergedMask);
        mergedMask &= ~(1U << bit);

        int polygonIdx = groupIdx * 32 + bit;

        if (BinPolygon(Polygons[polygonIdx], fineTileTopLeft, fineTileBotRight))
            binnedMask |= 1U << bit;
    }

    int linearTile = fineTile.x + fineTile.y * TilesPerLine + coarseTile.x * CoarseTileCountX + coarseTile.y * TilesPerLine * CoarseTileCountY;

    BinningMaskAndOffset[BinningMaskStart + linearTile * BinStride + groupIdx] = binnedMask;
    int coarseMaskIdx = linearTile * CoarseBinStride + (groupIdx >> 5);
    if (binnedMask != 0U)
        atomicOr(BinningMaskAndOffset[BinningCoarseMaskStart + coarseMaskIdx], 1U << (groupIdx & 0x1F));

    if (binnedMask != 0U)
    {
        uint workOffset = atomicAdd(VariantWorkCount[0].w, uint(bitCount(binnedMask)));
        BinningMaskAndOffset[BinningWorkOffsetsStart + linearTile * BinStride + groupIdx] = workOffset;

        uint tilePositionCombined = bitfieldInsert(fineTileTopLeft.x, fineTileTopLeft.y, 16, 16);

        int idx = 0;
        while (binnedMask != 0U)
        {
            int bit = findLSB(binnedMask);
            binnedMask &= ~(1U << bit);

            int polygonIdx = groupIdx * 32 + bit;
            int variantIdx = Polygons[polygonIdx].Variant;

            int inVariantOffset = int(atomicAdd(VariantWorkCount[variantIdx].z, 1));
            WorkDescs[WorkDescsUnsortedStart + workOffset + idx] = uvec2(tilePositionCombined, bitfieldInsert(polygonIdx, inVariantOffset, 11, 21));

            idx++;
        }
    }
}

)";

const std::string CalcOffsets = 
    BinningBuffer + R"(

layout (local_size_x = 32) in;

void main()
{
    if (gl_GlobalInvocationID.x < NumVariants)
    {
        if (gl_GlobalInvocationID.x == 0)
        {
            // a bit of a cheat putting this here, but this shader won't run that often
            SortWorkWorkCount = uvec4((VariantWorkCount[0].w + 31) / 32, 1, 1, 0);
        }
        SortedWorkOffset[gl_GlobalInvocationID.x] = atomicAdd(VariantWorkCount[1].w, VariantWorkCount[gl_GlobalInvocationID.x].z);
    }
}


)";

const std::string SortWork =
    PolygonBuffer +
    BinningBuffer +
    WorkDescBuffer + R"(

layout (local_size_x = 32) in;

void main()
{
    if (gl_GlobalInvocationID.x < VariantWorkCount[0].w)
    {
        uvec2 workDesc = WorkDescs[WorkDescsUnsortedStart + gl_GlobalInvocationID.x];
        int inVariantOffset = int(bitfieldExtract(workDesc.y, 11, 21));
        int polygonIdx = int(bitfieldExtract(workDesc.y, 0, 11));
        int variantIdx = Polygons[polygonIdx].Variant;

        int sortedIndex = int(SortedWorkOffset[variantIdx]) + inVariantOffset;
        WorkDescs[WorkDescsSortedStart + sortedIndex] = uvec2(workDesc.x, bitfieldInsert(workDesc.y, gl_GlobalInvocationID.x, 11, 21));
    }
}

)";

const std::string Rasterise =
    PolygonBuffer +
    WorkDescBuffer +
    XSpanSetupBuffer +
    BinningBuffer +
    Tilebuffers + R"(

layout (local_size_x = TileSize, local_size_y = TileSize) in;

layout (binding = 0) uniform usampler2DArray CurrentTexture;

layout (location = 0) uniform uint CurVariant;
layout (location = 1) uniform vec2 InvTextureSize;

void main()
{
    uvec2 workDesc = WorkDescs[WorkDescsSortedStart + SortedWorkOffset[CurVariant] + gl_WorkGroupID.z];
    Polygon polygon = Polygons[bitfieldExtract(workDesc.y, 0, 11)];
    ivec2 position = ivec2(bitfieldExtract(workDesc.x, 0, 16), bitfieldExtract(workDesc.x, 16, 16)) + ivec2(gl_LocalInvocationID.xy);
    int tileOffset = int(bitfieldExtract(workDesc.y, 11, 21)) * TileSize * TileSize + TileSize * int(gl_LocalInvocationID.y) + int(gl_LocalInvocationID.x);

    uint color = 0U;
    if (position.y >= polygon.YTop && position.y < polygon.YBot)
    {
        XSpanSetup xspan = XSpanSetups[polygon.FirstXSpan + (position.y - polygon.YTop)];

        bool insideLeftEdge = position.x < xspan.InsideStart;
        bool insideRightEdge = position.x >= xspan.InsideEnd;
        bool insidePolygonInside = !insideLeftEdge && !insideRightEdge;

        if (position.x >= xspan.X0 && position.x < xspan.X1
            && ((insideLeftEdge && (xspan.Flags & XSpanSetup_FillLeft) != 0U)
                || (insideRightEdge && (xspan.Flags & XSpanSetup_FillRight) != 0U)
                || (insidePolygonInside && (xspan.Flags & XSpanSetup_FillInside) != 0U)))
        {
            uint attr = 0;
            if (position.y == polygon.YTop)
                attr |= 0x4U;
            else if (position.y == polygon.YBot - 1)
                attr |= 0x8U;

            if (insideLeftEdge)
            {
                attr |= 0x1U;

                int cov = xspan.EdgeCovL;
                if (cov < 0)
                {
                    int xcov = xspan.CovLInitial + (xspan.EdgeCovL & 0x3FF) * (position.x - xspan.X0);
                    cov = min(xcov >> 5, 31);
                }

                attr |= uint(cov) << 8;
            }
            else if (insideRightEdge)
            {
                attr |= 0x2U;

                int cov = xspan.EdgeCovR;
                if (cov < 0)
                {
                    int xcov = xspan.CovRInitial + (xspan.EdgeCovR & 0x3FF) * (position.x - xspan.InsideEnd);
                    cov = max(0x1F - (xcov >> 5), 0);
                }

                attr |= uint(cov) << 8;
            }

            uint z;
            int u, v, vr, vg, vb;

            if (xspan.X0 == xspan.X1)
            {
                z = xspan.Z0;
                u = xspan.TexcoordU0;
                v = xspan.TexcoordV0;
                vr = xspan.ColorR0;
                vg = xspan.ColorG0;
                vb = xspan.ColorB0;
            }
            else
            {
                int ifactor = CalcYFactorX(xspan, position.x);
                int idiff = xspan.X1 - xspan.X0;
                int i = position.x - xspan.X0;

#ifdef ZBuffer
                z = InterpolateZZBuffer(xspan.Z0, xspan.Z1, i, xspan.XRecip, idiff);
#endif
#ifdef WBuffer
                z = InterpolateZWBuffer(xspan.Z0, xspan.Z1, ifactor);
#endif
                if ((xspan.Flags & XSpanSetup_Linear) == 0U)
                {
                    u = InterpolateAttrPersp(xspan.TexcoordU0, xspan.TexcoordU1, ifactor);
                    v = InterpolateAttrPersp(xspan.TexcoordV0, xspan.TexcoordV1, ifactor);

                    vr = InterpolateAttrPersp(xspan.ColorR0, xspan.ColorR1, ifactor);
                    vg = InterpolateAttrPersp(xspan.ColorG0, xspan.ColorG1, ifactor);
                    vb = InterpolateAttrPersp(xspan.ColorB0, xspan.ColorB1, ifactor);
                }
                else
                {
                    u = InterpolateAttrLinear(xspan.TexcoordU0, xspan.TexcoordU1, i, xspan.XRecip, idiff);
                    v = InterpolateAttrLinear(xspan.TexcoordV0, xspan.TexcoordV1, i, xspan.XRecip, idiff);

                    vr = InterpolateAttrLinear(xspan.ColorR0, xspan.ColorR1, i, xspan.XRecip, idiff);
                    vg = InterpolateAttrLinear(xspan.ColorG0, xspan.ColorG1, i, xspan.XRecip, idiff);
                    vb = InterpolateAttrLinear(xspan.ColorB0, xspan.ColorB1, i, xspan.XRecip, idiff);
                }
            }

#ifndef ShadowMask
            vr >>= 3;
            vg >>= 3;
            vb >>= 3;

            uint r, g, b, a;
            uint polyalpha = bitfieldExtract(polygon.Attr, 16, 5);

#ifdef Toon
            uint tooncolor = ToonTable[vr >> 1].r;
            vr = int(bitfieldExtract(tooncolor, 0, 8));
            vg = int(bitfieldExtract(tooncolor, 8, 8));
            vb = int(bitfieldExtract(tooncolor, 16, 8));
#endif
#ifdef Highlight
            vg = vr;
            vb = vr;
#endif

#ifdef NoTexture
            a = int(polyalpha);
#endif
            r = vr;
            g = vg;
            b = vb;

#ifdef UseTexture
            vec2 uvf = vec2(ivec2(u, v)) * vec2(1.0 / 16.0) * InvTextureSize;

            uvec4 texcolor = texture(CurrentTexture, vec3(uvf, polygon.TextureLayer));
#ifdef Decal
            if (texcolor.a == 31)
            {
                r = int(texcolor.r);
                g = int(texcolor.g);
                b = int(texcolor.b);
            }
            else if (texcolor.a > 0)
            {
                r = int((texcolor.r * texcolor.a) + (vr * (31-texcolor.a))) >> 5;
                g = int((texcolor.g * texcolor.a) + (vg * (31-texcolor.a))) >> 5;
                b = int((texcolor.b * texcolor.a) + (vb * (31-texcolor.a))) >> 5;
            }
            a = int(polyalpha);
#endif
#if defined(Modulate) || defined(Toon) || defined(Highlight)
            r = int((texcolor.r+1) * (vr+1) - 1) >> 6;
            g = int((texcolor.g+1) * (vg+1) - 1) >> 6;
            b = int((texcolor.b+1) * (vb+1) - 1) >> 6;
            a = int((texcolor.a+1) * (polyalpha+1) - 1) >> 5;
#endif
#endif

#ifdef Highlight
            uint tooncolor = ToonTable[vr >> 1].r;

            r = min(r + int(bitfieldExtract(tooncolor, 0, 8)), 63);
            g = min(g + int(bitfieldExtract(tooncolor, 8, 8)), 63);
            b = min(b + int(bitfieldExtract(tooncolor, 16, 8)), 63);
#endif

            if (polyalpha == 0)
                a = 31;

            if (a > AlphaRef)
            {
                color = r | (g << 8) | (b << 16) | (a << 24);

                DepthTiles[tileOffset] = z;
                AttrTiles[tileOffset] = attr;
            }
#else
            color = 0xFFFFFFFF; // doesn't really matter as long as it's not 0
            DepthTiles[tileOffset] = z;
#endif
        }
    }

    ColorTiles[tileOffset] = color;
}

)";

const std::string DepthBlend =
    PolygonBuffer +
    Tilebuffers +
    ResultBuffer +
    BinningBuffer + R"(

layout (local_size_x = TileSize, local_size_y = TileSize) in;

void PlotTranslucent(inout uint color, inout uint depth, inout uint attr, bool isShadow, uint tileColor, uint srcA, uint tileDepth, uint srcAttr, bool writeDepth)
{
    uint blendAttr = (srcAttr & 0xE0F0U) | ((srcAttr >> 8) & 0xFF0000U) | (1U<<22) | (attr & 0xFF001F0FU);

    if ((!isShadow || (attr & (1U<<22)) != 0U)
        ? (attr & 0x007F0000U) != (blendAttr & 0x007F0000U)
        : (attr & 0x3F000000U) != (srcAttr & 0x3F000000U))
    {
        // le blend
        if (writeDepth)
            depth = tileDepth;

        if ((attr & (1U<<15)) == 0)
            blendAttr &= ~(1U<<15);
        attr = blendAttr;

        uint srcRB = tileColor & 0x3F003FU;
        uint srcG = tileColor & 0x003F00U;
        uint dstRB = color & 0x3F003FU;
        uint dstG = color & 0x003F00U;
        uint dstA = color & 0x1F000000U;

        uint alpha = (srcA >> 24) + 1;
        if (dstA != 0)
        {
            srcRB = ((srcRB * alpha) + (dstRB * (32-alpha))) >> 5;
            srcG = ((srcG * alpha) + (dstG * (32-alpha))) >> 5;
        }

        color = (srcRB & 0x3F003FU) | (srcG & 0x003F00U) | max(dstA, srcA);
    }
}

void ProcessCoarseMask(int linearTile, uint coarseMask, uint coarseOffset,
    inout uvec2 color, inout uvec2 depth, inout uvec2 attr, inout uint stencil,
    inout bool prevIsShadowMask)
{
    int tileInnerOffset = int(gl_LocalInvocationID.x) + int(gl_LocalInvocationID.y) * TileSize;

    while (coarseMask != 0U)
    {
        uint coarseBit = findLSB(coarseMask);
        coarseMask &= ~(1U << coarseBit);

        uint tileOffset = linearTile * BinStride + coarseBit + coarseOffset;

        uint fineMask = BinningMaskAndOffset[BinningMaskStart + tileOffset];
        uint workIdx = BinningMaskAndOffset[BinningWorkOffsetsStart + tileOffset];

        while (fineMask != 0U)
        {
            uint fineIdx = findLSB(fineMask);
            fineMask &= ~(1U << fineIdx);

            uint pixelindex = tileInnerOffset + workIdx * TileSize * TileSize;
            uint tileColor = ColorTiles[pixelindex];
            workIdx++;

            uint polygonIdx = fineIdx + (coarseBit + coarseOffset) * 32;

            if (tileColor != 0U)
            {
                uint polygonAttr = Polygons[polygonIdx].Attr;

                bool isShadowMask = ((polygonAttr & 0x3F000030U) == 0x00000030U);
                bool prevIsShadowMaskOld = prevIsShadowMask;
                prevIsShadowMask = isShadowMask;

                bool equalDepthTest = (polygonAttr & (1U << 14)) != 0U;

                uint tileDepth = DepthTiles[pixelindex];
                uint tileAttr = AttrTiles[pixelindex];

                uint dstattr = attr.x;

                if (!isShadowMask)
                {
                    bool isShadow = (polygonAttr & 0x30U) == 0x30U;

                    bool writeSecondLayer = false;

                    if (isShadow)
                    {
                        if (stencil == 0U)
                            continue;
                        if ((stencil & 1U) == 0U)
                            writeSecondLayer = true;
                        if ((stencil & 2U) == 0U)
                            dstattr &= ~0x3U;
                    }

                    uint dstDepth = writeSecondLayer ? depth.y : depth.x;
                    if (!(equalDepthTest
#ifdef WBuffer
                        ? dstDepth - tileDepth + 0xFFU <= 0x1FE
#endif
#ifdef ZBuffer
                        ? dstDepth - tileDepth + 0x200 <= 0x400
#endif
                        : tileDepth < dstDepth))
                    {
                        if ((dstattr & 0x3U) == 0U || writeSecondLayer)
                            continue;

                        writeSecondLayer = true;
                        dstattr = attr.y;
                        if (!(equalDepthTest
#ifdef WBuffer
                            ? depth.y - tileDepth + 0xFFU <= 0x1FE
#endif
#ifdef ZBuffer
                            ? depth.y - tileDepth + 0x200 <= 0x400
#endif
                            : tileDepth < depth.y))
                            continue;
                    }

                    uint srcAttr = (polygonAttr & 0x3F008000U);

                    uint srcA = tileColor & 0x1F000000U;
                    if (srcA == 0x1F000000U)
                    {
                        srcAttr |= tileAttr;

                        if (!writeSecondLayer)
                        {
                            if ((srcAttr & 0x3U) != 0U)
                            {
                                color.y = color.x;
                                depth.y = depth.x;
                                attr.y = attr.x;
                            }

                            color.x = tileColor;
                            depth.x = tileDepth;
                            attr.x = srcAttr;
                        }
                        else
                        {
                            color.y = tileColor;
                            depth.y = tileDepth;
                            attr.y = srcAttr;
                        }
                    }
                    else
                    {
                        bool writeDepth = (polygonAttr & (1U<<11)) != 0;

                        if (!writeSecondLayer)
                        {
                            // blend into both layers
                            PlotTranslucent(color.x, depth.x, attr.x, isShadow, tileColor, srcA, tileDepth, srcAttr, writeDepth);
                        }
                        if (writeSecondLayer || (dstattr & 0x3U) != 0U)
                        {
                            PlotTranslucent(color.y, depth.y, attr.y, isShadow, tileColor, srcA, tileDepth, srcAttr, writeDepth);
                        }
                    }
                }
                else
                {
                    if (!prevIsShadowMaskOld)
                        stencil = 0;

                    if (!(equalDepthTest
#ifdef WBuffer
                        ? depth.x - tileDepth + 0xFFU <= 0x1FE
#endif
#ifdef ZBuffer
                        ? depth.x - tileDepth + 0x200 <= 0x400
#endif
                        : tileDepth < depth.x))
                        stencil = 0x1U;

                    if ((dstattr & 0x3U) != 0U)
                    {
                        if (!(equalDepthTest
#ifdef WBuffer
                            ? depth.y - tileDepth + 0xFFU <= 0x1FE
#endif
#ifdef ZBuffer
                            ? depth.y - tileDepth + 0x200 <= 0x400
#endif
                            : tileDepth < depth.y))
                            stencil |= 0x2U;
                    }
                }
            }
        }
    }
}

void main()
{
    int linearTile = int(gl_WorkGroupID.x + (gl_WorkGroupID.y * TilesPerLine));

    uint coarseMaskLo = BinningMaskAndOffset[BinningCoarseMaskStart + linearTile*CoarseBinStride + 0];
    uint coarseMaskHi = BinningMaskAndOffset[BinningCoarseMaskStart + linearTile*CoarseBinStride + 1];

    uvec2 color = uvec2(ClearColor, 0U);
    uvec2 depth = uvec2(ClearDepth, 0U);
    uvec2 attr = uvec2(ClearAttr, 0U);
    uint stencil = 0U;
    bool prevIsShadowMask = false;

    ProcessCoarseMask(linearTile, coarseMaskLo, 0, color, depth, attr, stencil, prevIsShadowMask);
    ProcessCoarseMask(linearTile, coarseMaskHi, BinStride/2, color, depth, attr, stencil, prevIsShadowMask);

    int resultOffset = int(gl_GlobalInvocationID.x) + int(gl_GlobalInvocationID.y) * ScreenWidth;
    ResultValue[ResultColorStart+resultOffset] = color.x;
    ResultValue[ResultColorStart+resultOffset+FramebufferStride] = color.y;
    ResultValue[ResultDepthStart+resultOffset] = depth.x;
    ResultValue[ResultDepthStart+resultOffset+FramebufferStride] = depth.y;
    ResultValue[ResultAttrStart+resultOffset] = attr.x;
    ResultValue[ResultAttrStart+resultOffset+FramebufferStride] = attr.y;
}

)";

const std::string FinalPass =
    ResultBuffer + R"(

layout (local_size_x = 32) in;

layout (binding = 0, rgba8) writeonly uniform image2D FinalFB; 
layout (binding = 1, rgba8ui) writeonly uniform uimage2D LowResFB; 

uint BlendFog(uint color, uint depth)
{
    uint densityid = 0, densityfrac = 0;

    if (depth >= FogOffset)
    {
        depth -= FogOffset;
        depth = (depth >> 2) << FogShift;

        densityid = depth >> 17;
        if (densityid >= 32)
        {
            densityid = 32;
            densityfrac = 0;
        }
        else
        {
            densityfrac = depth & 0x1FFFFU;
        }
    }

    uint density =
        ((ToonTable[densityid].g * (0x20000U-densityfrac)) +
         (ToonTable[densityid+1].g * densityfrac)) >> 17;
    density = min(density, 128U);

    uint colorRB = color & 0x3F003FU;
    uint colorGA = (color >> 8) & 0x3F003FU;

    uint fogRB = FogColor & 0x3F003FU;
    uint fogGA = (FogColor >> 8) & 0x1F003FU;

    uint finalColorRB = ((fogRB * density) + (colorRB * (128-density))) >> 7;
    uint finalColorGA = ((fogGA * density) + (colorGA * (128-density))) >> 7;

    finalColorRB &= 0x3F003FU;
    finalColorGA &= 0x1F003FU;

    return (DispCnt & (1U<<6)) != 0
        ? (bitfieldInsert(color, finalColorGA >> 16, 24, 8))
        : (finalColorRB | (finalColorGA << 8));
}

void main()
{
    int srcX = int(gl_GlobalInvocationID.x);
    int resultOffset = int(srcX) + int(gl_GlobalInvocationID.y) * ScreenWidth;

    uvec2 color = uvec2(ResultValue[resultOffset+ResultColorStart], ResultValue[resultOffset+FramebufferStride+ResultColorStart]);
    uvec2 depth = uvec2(ResultValue[resultOffset+ResultDepthStart], ResultValue[resultOffset+FramebufferStride+ResultDepthStart]);
    uvec2 attr = uvec2(ResultValue[resultOffset+ResultAttrStart], ResultValue[resultOffset+FramebufferStride+ResultAttrStart]);

#ifdef EdgeMarking
    if ((attr.x & 0xFU) != 0U)
    {
        uvec4 otherAttr = uvec4(ClearAttr);
        uvec4 otherDepth = uvec4(ClearDepth);

        if (srcX > 0U)
        {
            otherAttr.x = ResultValue[resultOffset-1+ResultAttrStart];
            otherDepth.x = ResultValue[resultOffset-1+ResultDepthStart];
        }
        if (srcX < ScreenWidth-1)
        {
            otherAttr.y = ResultValue[resultOffset+1+ResultAttrStart];
            otherDepth.y = ResultValue[resultOffset+1+ResultDepthStart];
        }
        if (gl_GlobalInvocationID.y > 0U)
        {
            otherAttr.z = ResultValue[resultOffset-ScreenWidth+ResultAttrStart];
            otherDepth.z = ResultValue[resultOffset-ScreenWidth+ResultDepthStart];
        }
        if (gl_GlobalInvocationID.y < ScreenHeight-1)
        {
            otherAttr.w = ResultValue[resultOffset+ScreenWidth+ResultAttrStart];
            otherDepth.w = ResultValue[resultOffset+ScreenWidth+ResultDepthStart];
        }

        uint polyId = bitfieldExtract(attr.x, 24, 6);
        uvec4 otherPolyId = bitfieldExtract(otherAttr, 24, 6);

        bvec4 polyIdMismatch = notEqual(uvec4(polyId), otherPolyId);
        bvec4 nearer = lessThan(uvec4(depth.x), otherDepth);

        if ((polyIdMismatch.x && nearer.x)
            || (polyIdMismatch.y && nearer.y)
            || (polyIdMismatch.z && nearer.z)
            || (polyIdMismatch.w && nearer.w))
        {
            color.x = ToonTable[polyId >> 3].b | (color.x & 0xFF000000U);
            attr.x = (attr.x & 0xFFFFE0FFU) | 0x00001000U;
        }
    }
#endif

#ifdef Fog
    if ((attr.x & (1U<<15)) != 0U)
    {
        color.x = BlendFog(color.x, depth.x);
    }

    if ((attr.x & 0xFU) != 0 && (attr.y & (1U<<15)) != 0U)
    {
        color.y = BlendFog(color.y, depth.y);
    }
#endif

#ifdef AntiAliasing
    // resolve anti-aliasing
    if ((attr.x & 0x3U) != 0)
    {
        uint coverage = (attr.x >> 8) & 0x1FU;

        if (coverage != 0)
        {
            uint topRB = color.x & 0x3F003FU;
            uint topG = color.x & 0x003F00U;
            uint topA = bitfieldExtract(color.x, 24, 5);

            uint botRB = color.y & 0x3F003FU;
            uint botG = color.y & 0x003F00U;
            uint botA = bitfieldExtract(color.y, 24, 5);

            coverage++;

            if (botA > 0)
            {
                topRB = ((topRB * coverage) + (botRB * (32-coverage))) >> 5;
                topG = ((topG * coverage) + (botG * (32-coverage))) >> 5;

                topRB &= 0x3F003FU;
                topG &= 0x003F00U;
            }

            topA = ((topA * coverage) + (botA * (32-coverage))) >> 5;

            color.x = topRB | topG | (topA << 24);
        }
        else
        {
            color.x = color.y;
        }
    }
#endif

//    if (bitfieldExtract(color.x, 24, 8) != 0U)
//        color.x |= 0x40000000U;
//    else
//        color.x = 0U;

    //if ((gl_GlobalInvocationID.y % 8) == 7 || (gl_GlobalInvocationID.y % 8) == 7)
    //    color.x = 0x1F00001FU | 0x40000000U;

    vec4 result = vec4(bitfieldExtract(color.x, 16, 8), bitfieldExtract(color.x, 8, 8), color.x & 0x3FU, bitfieldExtract(color.x, 24, 8));
    result /= vec4(63.0, 63.0, 63.0, 31.0);
    imageStore(FinalFB, ivec2(gl_GlobalInvocationID.xy), result);

    // It's a division by constant, so using the builtin division is fine
    const int scale = ScreenWidth/256;
    ivec2 lowresCoordinate = ivec2(gl_GlobalInvocationID.xy) / scale;
    ivec2 lowresCoordinateRest = ivec2(gl_GlobalInvocationID.xy) % scale;
    if (lowresCoordinateRest == ivec2(0, 0))
    {
        uvec4 color8;
        color8.x = bitfieldExtract(color.x, 0, 8);
        color8.y = bitfieldExtract(color.x, 8, 8);
        color8.z = bitfieldExtract(color.x, 16, 8);
        color8.w = bitfieldExtract(color.x, 24, 8);
        imageStore(LowResFB, lowresCoordinate, color8);
    }
}

)";

}

}

#endif