#version 450
#extension GL_KHR_shader_subgroup_basic : require
#extension GL_KHR_shader_subgroup_ballot : require
#extension GL_KHR_shader_subgroup_arithmetic : require
#extension GL_KHR_shader_subgroup_vote : require
#extension GL_KHR_shader_subgroup_shuffle_relative : require
#extension GL_KHR_shader_subgroup_shuffle : require
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
#extension GL_EXT_shader_explicit_arithmetic_types_int16 : require

#include "dwt_quant_scale.h"
#include "constants.h"

layout(local_size_x = 64) in;

struct BlockMeta
{
    uint code_word;
    uint offset;
};

struct RDOperation
{
    int quant;
    uint block_offset_saving;
};

const int BLOCK_SPACE_SUBDIVISION = 16;

layout(set = 0, binding = 0) buffer Buckets
{
    uint count;
    uint consumed_payload;
    layout(offset = 64) uint total_savings_per_bucket[128 * BLOCK_SPACE_SUBDIVISION];
    RDOperation rdo_operations[];
} buckets;

struct QuantStats
{
    float16_t square_error;
    uint16_t payload_cost;
};

struct BlockStats
{
    uint num_planes;
    QuantStats errors[15];
};

layout(set = 0, binding = 1) readonly buffer SSBOBlockStats
{
    BlockStats stats[];
} block_stats;

layout(push_constant) uniform Registers
{
    ivec2 resolution;
    ivec2 resolution_8x8_blocks;
    int block_offset_8x8;
    int block_stride_8x8;
    int block_offset_32x32;
    int block_stride_32x32;
    uint total_wg_count;
    uint num_blocks_aligned;
    uint block_index_shamt;
} registers;

shared uint shared_rate_cost[16];
shared float shared_distortion[16];
shared uint shared_tmp[4];

// Perform operations that cause lower distortion first.
uint distortion_to_bucket_index(float d, float cost, float d_base, float cost_base)
{
    if (cost == cost_base)
        return 0;

    // Compress a large range into 64 possible buckets.
    // Every band is ~1.5 dB.
    // Greedily chase least added (weighted) distortion per byte removed from code stream.
    float index = 60.0 + 2.0 * log2(max(d - d_base, 0.0) / (cost_base - cost));
    return uint(max(index + 0.5, 0.0));
}

uint inclusive_max_clustered16(uint v)
{
    // Ensures that we never end up with a value > 127.
    v = min(v, 128 - 16 + gl_SubgroupInvocationID);

    for (uint i = 1; i < 16; i *= 2)
    {
        // Ensure monotonic progression for buckets.
        // Separate every quant level out by at least one bucket.
        uint up = subgroupShuffleUp(v, i) + i;
        v = max(v, gl_SubgroupInvocationID >= i ? up : 0);
    }

    return v;
}

void emit_rdo_operations()
{
    float distortion;
    float cost;

    if (gl_SubgroupInvocationID < 16)
    {
        cost = float(shared_rate_cost[gl_SubgroupInvocationID]);
        distortion = shared_distortion[gl_SubgroupInvocationID];
    }
    else
    {
        // Dummy values.
        cost = float(shared_rate_cost[gl_SubgroupInvocationID]);
        distortion = 1e30;
    }

    uint bucket_index = distortion_to_bucket_index(distortion, cost, shared_distortion[0], float(shared_rate_cost[0]));
    if (gl_SubgroupInvocationID == 0)
        bucket_index = 0;

    // Constraints:
    // bucket_index for Q1 must be less than bucket_index for Q2 if Q1 < Q2.
    // If a high quant target sees very favorable RD, lower bucket indices for lower Q values.
    uint inclusive_bucket_index = inclusive_max_clustered16(bucket_index);

    if (gl_SubgroupInvocationID == 0)
    {
        uint unquantized_cost = shared_rate_cost[0];
        atomicAdd(buckets.consumed_payload, unquantized_cost);
    }
    else if (gl_SubgroupInvocationID < 16)
    {
        uint saving = shared_rate_cost[gl_SubgroupInvocationID - 1] - shared_rate_cost[gl_SubgroupInvocationID];
        // PUNKTFUNK PATCH 0002: RDOperation packs saving into 16 bits; clamp the bucket
        // accounting to match, else resolve over-credits applied ops and the produced
        // bitstream can overshoot the hard rate target (a buffer-overrun class).
        saving = min(saving, 65535u);

        if (saving != 0)
        {
            ivec2 block32x32_index = ivec2(gl_WorkGroupID.xy);
            int block_index = registers.block_offset_32x32 +
                block32x32_index.y * registers.block_stride_32x32 + block32x32_index.x;
            uint subdivision = block_index >> registers.block_index_shamt;
            atomicAdd(buckets.total_savings_per_bucket[inclusive_bucket_index * BLOCK_SPACE_SUBDIVISION + subdivision], saving);
            buckets.rdo_operations[block_index + inclusive_bucket_index * registers.num_blocks_aligned] =
                RDOperation(int(gl_SubgroupInvocationID), block_index | (saving << 16));
        }
    }
}

void main()
{
    // Each workgroup processes a 64x64 block and computes all possible rate wins for every potential quant rate.
    uint index = gl_SubgroupInvocationID + gl_SubgroupSize * gl_SubgroupID;
    ivec2 block32x32_index = ivec2(gl_WorkGroupID.xy);
    ivec2 local_block_index = ivec2(bitfieldExtract(index, 0, 2), bitfieldExtract(index, 2, 2));
    ivec2 block8x8_index = 4 * block32x32_index + local_block_index;

    uint num_active_planes;

    bool block8x8_in_range = all(lessThan(block8x8_index, registers.resolution_8x8_blocks));
    int block_index_8x8 = registers.block_offset_8x8 +
        registers.block_stride_8x8 * block8x8_index.y +
        block8x8_index.x;

    if (block8x8_in_range)
        num_active_planes = block_stats.stats[block_index_8x8].num_planes;

    uint bit_index = index >> 4;

    for (uint i = bit_index; i < 16; i += 4)
    {
        float dist = 0.0;
        uint cost = 0;

        if (block8x8_in_range)
        {
            QuantStats stats = block_stats.stats[block_index_8x8].errors[min(i, num_active_planes)];
            dist = float(stats.square_error);
            cost = uint(stats.payload_cost);
        }

        // 16 bits to encode the control codes, 8 bits to encode Q bits + quant scale.
        // Cost is encoded in terms of bits. 8x8 blocks are decoded in isolation.
        if (cost != 0)
            cost += 24;

        if (gl_SubgroupSize == 16)
        {
            cost = subgroupAdd(cost);
            dist = subgroupAdd(dist);
        }
        else
        {
            cost += subgroupShuffleXor(cost, 1);
            cost += subgroupShuffleXor(cost, 2);
            cost += subgroupShuffleXor(cost, 4);
            cost += subgroupShuffleXor(cost, 8);

            dist += subgroupShuffleXor(dist, 1);
            dist += subgroupShuffleXor(dist, 2);
            dist += subgroupShuffleXor(dist, 4);
            dist += subgroupShuffleXor(dist, 8);
        }

        if ((index & 15u) == 0u)
        {
            // Need to encode a header.
            // We can eliminate 32x32 blocks if everything decodes to 0.
            if (cost != 0)
                cost += 64;

            // Each packet is aligned to 4 bytes for practical reasons.
            shared_rate_cost[i] = (cost + 31) >> 5;
            shared_distortion[i] = dist;
        }
    }

    barrier();

    if (gl_SubgroupID == 0)
        emit_rdo_operations();
}