#pragma only_renderers d3d11 vulkan metal glcore
RWTexture2D<float4> g_TextureInOut;
int g_TextureWidth;
int g_TextureHeight;

float4 g_MultiplicationFactor;
#pragma kernel Multiply
[numthreads(8,8,1)]
void Multiply(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
        return;
    g_TextureInOut[id.xy] = g_TextureInOut[id.xy] * g_MultiplicationFactor;
}

int g_ChannelIndex;
#pragma kernel BroadcastChannel
[numthreads(8,8,1)]
void BroadcastChannel(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
        return;
    if (g_ChannelIndex < 0 || g_ChannelIndex > 3)
        return;
    g_TextureInOut[id.xy] = g_TextureInOut[id.xy][g_ChannelIndex];
}

float g_ChannelValue;
#pragma kernel SetChannel
[numthreads(8,8,1)]
void SetChannel(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
        return;
    if (g_ChannelIndex < 0 || g_ChannelIndex > 3)
        return;
    switch (g_ChannelIndex)
    {
        case 0:
            g_TextureInOut[id.xy] = float4(g_ChannelValue, g_TextureInOut[id.xy].g, g_TextureInOut[id.xy].b, g_TextureInOut[id.xy].a);
            break;
        case 1:
            g_TextureInOut[id.xy] = float4(g_TextureInOut[id.xy].r, g_ChannelValue, g_TextureInOut[id.xy].b, g_TextureInOut[id.xy].a);
            break;
        case 2:
            g_TextureInOut[id.xy] = float4(g_TextureInOut[id.xy].r, g_TextureInOut[id.xy].g, g_ChannelValue, g_TextureInOut[id.xy].a);
            break;
        case 3:
            g_TextureInOut[id.xy] = float4(g_TextureInOut[id.xy].r, g_TextureInOut[id.xy].g, g_TextureInOut[id.xy].b, g_ChannelValue);
            break;
    }
}

Texture2D<float4> g_SourceTexture;
SamplerState g_LinearClampSampler;
RWTexture2D<float4> g_TextureOut;
int g_BoxFilterRadius;
#pragma kernel ReferenceBoxFilter
[numthreads(8,8,1)]
void ReferenceBoxFilter(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
        return;
    float4 sum = float4(0, 0, 0, 0);
    for (int y = -g_BoxFilterRadius; y <= g_BoxFilterRadius; ++y)
    {
        for (int x = -g_BoxFilterRadius; x <= g_BoxFilterRadius; ++x)
        {
            float2 uv = (float2(id.xy) + float2(x, y)) / float2(g_TextureWidth, g_TextureHeight); 
            sum += g_SourceTexture.SampleLevel(g_LinearClampSampler, uv, 0);
        }
    }
    g_TextureOut[id.xy] = sum / float((g_BoxFilterRadius * 2 + 1) * (g_BoxFilterRadius * 2 + 1));
}

#pragma kernel ReferenceBoxFilterBlueChannel
[numthreads(16,8,1)]
void ReferenceBoxFilterBlueChannel(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
        return;
    float4 color = g_SourceTexture[id.xy];
    float sum = 0;
    for (int y = -g_BoxFilterRadius; y <= g_BoxFilterRadius; ++y)
    {
        for (int x = -g_BoxFilterRadius; x <= g_BoxFilterRadius; ++x)
        {
            float2 uv = (float2(id.xy) + float2(x, y)) / float2(g_TextureWidth, g_TextureHeight); 
            sum += g_SourceTexture.SampleLevel(g_LinearClampSampler, uv, 0).b;
        }
    }
    float average = sum / float((g_BoxFilterRadius * 2 + 1) * (g_BoxFilterRadius * 2 + 1));
    g_TextureOut[id.xy] = float4(color.rg, average, color.a);
}

Texture2D<float4> g_SampleCountInW;
Texture2D<float4> g_VarianceInR;
#pragma kernel StandardError
[numthreads(8,8,1)]
void StandardError(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
        return;
    float variance = g_VarianceInR[id.xy].r;
    float error = sqrt(variance / g_SampleCountInW[id.xy].w);
    g_TextureOut[id.xy] = float4(error, error, error, 1.f);
}

Texture2D<float4> g_StandardErrorInR;
Texture2D<float4> g_MeanInR;
float g_StandardErrorThreshold;
#pragma kernel StandardErrorThreshold
[numthreads(8,8,1)]
void StandardErrorThreshold(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
        return;
    float mean = g_MeanInR[id.xy].r;
    float standardError = g_StandardErrorInR[id.xy].r;
    bool shouldStop = standardError < mean * g_StandardErrorThreshold || mean < 0.000001f;
    g_TextureOut[id.xy] = shouldStop ? float4(0.0f, 0.0f, 0.0f, 1.f) : float4(1.0f, 1.0f, 1.0f, 1.f);
}

RWStructuredBuffer<float> g_OutputBuffer;
int g_X;
int g_Y;
#pragma kernel GetValue
[numthreads(8,8,1)]
void GetValue(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
        return;
    if (id.x == uint(g_X) && id.y == uint(g_Y))
    {
        float4 color = g_SourceTexture[id.xy];
        g_OutputBuffer[0] = color.r;
        g_OutputBuffer[1] = color.g;
        g_OutputBuffer[2] = color.b;
        g_OutputBuffer[3] = color.a;
    }
}

#pragma kernel NormalizeByAlpha
[numthreads(8,8,1)]
void NormalizeByAlpha(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight))
        return;
    const float4 color = g_TextureInOut[id.xy];
    const float sampleCount = max(color.w, 1.0f);
    g_TextureInOut[id.xy] = float4(color.xyzw * (1.f / sampleCount));
}

StructuredBuffer<uint> g_InputBufferSelector; // 0 or 1
int g_InputBufferLength;
StructuredBuffer<uint> g_InputBuffer0;
StructuredBuffer<uint> g_InputBuffer1;
RWStructuredBuffer<uint> g_SelectionOutput;
#pragma kernel SelectGraphicsBuffer
[numthreads(16,1,1)]
void SelectGraphicsBuffer(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= uint(g_InputBufferLength) || id.y >= 1u || id.z >= 1u)
        return;
    g_SelectionOutput[id.x] = g_InputBufferSelector[0] == 0u ? g_InputBuffer0[id.x] : g_InputBuffer1[id.x];
}

RWTexture2D<float4> g_DestinationTexture;
int g_SourceX;
int g_SourceY;
int g_SourceWidth;
int g_SourceHeight;
int g_DestinationX;
int g_DestinationY;
#pragma kernel CopyTextureAdditive
[numthreads(8,8,1)]
void CopyTextureAdditive(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= uint(g_SourceWidth) || id.y >= uint(g_SourceHeight))
        return;
    uint2 src = id.xy + uint2(uint(g_SourceX), uint(g_SourceY));
    uint2 dst = id.xy + uint2(uint(g_DestinationX), uint(g_DestinationY));
    g_DestinationTexture[dst] = g_DestinationTexture[dst] + g_SourceTexture[src];
}