#version 450
// Copyright (c) 2025 Hans-Kristian Arntzen
// SPDX-License-Identifier: MIT
#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_shuffle_relative : require
#extension GL_KHR_shader_subgroup_shuffle : require
#extension GL_KHR_shader_subgroup_clustered : require
#extension GL_KHR_shader_subgroup_vote : require
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
#extension GL_EXT_shader_explicit_arithmetic_types_int16 : require
#extension GL_EXT_shader_8bit_storage : require

#include "constants.h"

layout(local_size_x = 64) in;

struct BlockMeta
{
    uint code_word;
    uint offset;
};

struct BitstreamPacket
{
    uint offset;
    uint num_words;
};

layout(set = 0, binding = 0) writeonly buffer BitstreamPayload
{
    uint data[];
} bitstream_data;

layout(set = 0, binding = 0) writeonly buffer BitstreamPayload16Bit
{
    uint16_t data[];
} bitstream_data_16b;

layout(set = 0, binding = 0) writeonly buffer BitstreamPayload8Bit
{
    uint8_t data[];
} bitstream_data_8b;

layout(set = 0, binding = 1) writeonly buffer BitstreamMeta
{
    BitstreamPacket packets[];
} bitstream_meta;

layout(set = 0, binding = 2) readonly buffer SSBOMeta
{
    BlockMeta meta[];
} block_meta;

layout(set = 0, binding = 3) buffer Payloads
{
    layout(offset = 4) uint bitstream_payload_counter;
    layout(offset = 8) uint8_t data[];
} payload_data;

struct QuantStats
{
    float16_t square_error;
    uint16_t payload_cost;
};

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

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

layout(set = 0, binding = 5) readonly buffer RateControlQuant
{
    int data[];
} quant_data;

layout(push_constant) uniform Registers
{
    ivec2 resolution;
    ivec2 resolution_32x32_blocks;
    ivec2 resolution_8x8_blocks;
    uint quant_resolution_code;
    uint sequence_code;
    int block_offset_32x32;
    int block_stride_32x32;
    int block_offset_8x8;
    int block_stride_8x8;
} registers;

uint compute_required_8x8_size(uint control_word)
{
    int q_bits = int(bitfieldExtract(control_word, Q_PLANES_OFFSET, Q_PLANES_BITS));
    uint lsbs = control_word & 0x5555u;
    uint msbs = control_word & 0xaaaau;
    uint msbs_shift = msbs >> 1;
    msbs |= msbs_shift;
    return bitCount(lsbs) + bitCount(msbs) + q_bits * 8;
}

uint quantize_code_word(uint control_word, int quant)
{
    if (quant != 0 && control_word != 0)
    {
        int q_bits = int(bitfieldExtract(control_word, Q_PLANES_OFFSET, Q_PLANES_BITS));
        int sub_quant = min(q_bits, quant);
        q_bits -= sub_quant;
        quant -= sub_quant;

        if (quant != 0)
        {
            quant = min(quant, 3);

            uint plane0 = control_word & 0x5555u;
            uint plane1 = (control_word & 0xaaaau) >> 1;
            uint plane2 = plane0 & plane1;

            do
            {
                plane0 = plane1;
                plane1 = plane2;
                plane2 = 0;
                quant--;
            } while (quant != 0);

            plane0 &= ~plane1;

            uint new_control_word = plane0 | (plane1 << 1);
            control_word = bitfieldInsert(control_word, new_control_word, 0, 16);
        }

        control_word = bitfieldInsert(control_word, uint(q_bits), Q_PLANES_OFFSET, Q_PLANES_BITS);
    }

    return control_word;
}

uint copy_bytes(inout uint output_offset, uint input_offset, uint count)
{
    uint significant_mask = 0;

    do
    {
        uint in_data = uint(payload_data.data[input_offset]);
        // If we observe any 1 in the non-sign planes, it's not deadzone quantized.
        significant_mask |= in_data;
        bitstream_data_8b.data[output_offset++] = uint8_t(in_data);
        count--;
        input_offset++;
    } while (count > 0);

    return significant_mask;
}

uint modify_quant_code(uint code, int quant)
{
    int e = int(bitfieldExtract(code, 3, 5));
    e = max(e - quant, 0);
    code = bitfieldInsert(code, e, 3, 5);
    return code;
}

uint inclusive_add_clustered16(uint v)
{
    for (uint i = 1; i < 16; i *= 2)
    {
        uint up = subgroupShuffleUp(v, i);
        v += (gl_SubgroupInvocationID & 15) >= i ? up : 0;
    }

    return v;
}

shared uint shared_sign_bank[4][1024 / 32];
uint pending_sign_write = 0;
uint pending_sign_mask = 0;

void append_sign_plane(uint bank, inout uint local_sign_offset, uint sign_mask, uint significant_mask)
{
    // Clock out one bit a time. This seems kinda slow.
    while (significant_mask != 0)
    {
        int bit = findLSB(significant_mask);
        significant_mask &= significant_mask - 1;
        int out_bit = int(local_sign_offset & 31u);
        pending_sign_write = bitfieldInsert(pending_sign_write, bitfieldExtract(sign_mask, bit, 1), out_bit, 1);
        pending_sign_mask = bitfieldInsert(pending_sign_mask, 1, out_bit, 1);

        if (out_bit == 31)
        {
            if (pending_sign_mask == ~0u)
            {
                shared_sign_bank[bank][local_sign_offset / 32] = pending_sign_write;
            }
            else
            {
                atomicAnd(shared_sign_bank[bank][local_sign_offset / 32], ~pending_sign_mask);
                atomicOr(shared_sign_bank[bank][local_sign_offset / 32], pending_sign_write & pending_sign_mask);
            }

            pending_sign_mask = 0;
        }

        local_sign_offset++;
    }
}

void flush_sign_plane(uint bank, uint local_sign_offset)
{
    if (pending_sign_mask != 0)
    {
        atomicAnd(shared_sign_bank[bank][local_sign_offset / 32], ~pending_sign_mask);
        atomicOr(shared_sign_bank[bank][local_sign_offset / 32], pending_sign_write & pending_sign_mask);
        pending_sign_mask = 0;
    }
}

void main()
{
    uint index = gl_SubgroupInvocationID + gl_SubgroupSize * gl_SubgroupID;
    uint linear_block_32x32_index = index >> 4;
    ivec2 block32x32_index = 2 * ivec2(gl_WorkGroupID.xy);
    block32x32_index.x += int(bitfieldExtract(index, 4, 1));
    block32x32_index.y += int(bitfieldExtract(index, 5, 1));
    ivec2 local_block_index = ivec2(bitfieldExtract(index, 0, 2), bitfieldExtract(index, 2, 2));
    ivec2 block8x8_index = 4 * block32x32_index + local_block_index;

    BlockMeta meta;
    int quant;

    bool in_range_8x8 = all(lessThan(block8x8_index, registers.resolution_8x8_blocks));
    bool in_range_32x32 = all(lessThan(block32x32_index, registers.resolution_32x32_blocks));
    uint num_bits_for_q = 0;

    if (in_range_32x32)
    {
        int block_index = registers.block_offset_32x32 +
            registers.block_stride_32x32 * block32x32_index.y +
            block32x32_index.x;
        quant = quant_data.data[block_index];
    }
    else
    {
        quant = 0;
    }

    if (in_range_8x8)
    {
        int block_index = registers.block_offset_8x8 +
            registers.block_stride_8x8 * block8x8_index.y +
            block8x8_index.x;
        meta = block_meta.meta[block_index];
        uint num_planes = block_stats.stats[block_index].num_planes;
        num_bits_for_q = uint(block_stats.stats[block_index].errors[min(num_planes, quant)].payload_cost);
    }
    else
    {
        meta = BlockMeta(0, 0);
    }

    uint code_word = quantize_code_word(meta.code_word, quant);
    bool active_code_word = (code_word & 0xffffu) != 0;

    uvec4 code_word_ballot = subgroupBallot(active_code_word);
    uint local_ballot = gl_SubgroupSize >= 64 && linear_block_32x32_index >= 2 ? code_word_ballot.y : code_word_ballot.x;
    local_ballot = bitfieldExtract(local_ballot, int(16u * (linear_block_32x32_index & 1u)), 16);

    uint required_plane_bytes = compute_required_8x8_size(code_word);
    uint required_sign_bits = num_bits_for_q - required_plane_bytes * 8;

    uint required_bits_with_meta = num_bits_for_q;
    if (required_bits_with_meta != 0)
        required_bits_with_meta += 24;

    const uint HeaderSize = 2;
    bool writes_header =
        all(lessThan(block32x32_index, registers.resolution_32x32_blocks)) && (index & 15u) == 15u;

    uint payload_total_bits = subgroupClusteredAdd(required_bits_with_meta, 16);
    uint payload_total_words = (payload_total_bits + 31) / 32;
    if (payload_total_words != 0)
        payload_total_words += HeaderSize;

    uint global_payload_offset = 0;
    if (writes_header && payload_total_words != 0)
        global_payload_offset = atomicAdd(payload_data.bitstream_payload_counter, payload_total_words);
    global_payload_offset = subgroupShuffle(global_payload_offset, gl_SubgroupInvocationID | 15u);

    if (writes_header)
    {
        uint block_index = registers.block_offset_32x32 +
            block32x32_index.y * registers.block_stride_32x32 + block32x32_index.x;

        if (payload_total_words != 0)
        {
            bitstream_data.data[global_payload_offset + 0] =
                local_ballot | (payload_total_words << 16) | (registers.sequence_code << 28);
            bitstream_data.data[global_payload_offset + 1] =
                modify_quant_code(registers.quant_resolution_code, quant) | (block_index << 8);
        }

        bitstream_meta.packets[block_index] = BitstreamPacket(global_payload_offset, payload_total_words);
    }

    uint total_subblocks = bitCount(local_ballot);

    uint total_sign_bits = inclusive_add_clustered16(required_sign_bits);
    uint local_planes_offset = inclusive_add_clustered16(required_plane_bytes) - required_plane_bytes;
    uint local_sign_offset = total_sign_bits - required_sign_bits;
    uint global_planes_offset = 4 * global_payload_offset + 3 * total_subblocks + 4 * HeaderSize;
    uint global_sign_offset = global_planes_offset + subgroupClusteredAdd(required_plane_bytes, 16);
    global_planes_offset += local_planes_offset;

    uint total_sign_bytes = (subgroupShuffle(total_sign_bits, gl_SubgroupInvocationID | 15u) + 7) / 8;

    // Followed by N code words which map to the local ballot of active 16x16 regions.
    if (active_code_word)
    {
        uint block_header_offset = bitCount(bitfieldExtract(
            local_ballot, 0, local_block_index.y * 4 + local_block_index.x));

        uint in_q_bits = bitfieldExtract(meta.code_word, Q_PLANES_OFFSET, Q_PLANES_BITS);
        uint out_q_bits = bitfieldExtract(code_word, Q_PLANES_OFFSET, Q_PLANES_BITS);
        uint input_offset = meta.offset;
        uint output_offset = global_planes_offset;

        for (int bit_offset = 0; bit_offset < 16; bit_offset += 2)
        {
            uint out_planes = bitfieldExtract(code_word, bit_offset, 2) + out_q_bits;
            uint in_planes = bitfieldExtract(meta.code_word, bit_offset, 2) + in_q_bits;
            if (in_planes != 0)
                in_planes++;

            uint sign_plane = uint(payload_data.data[input_offset]);

            if (out_planes != 0)
            {
                uint significant_mask = copy_bytes(output_offset, input_offset + 1, out_planes);
                append_sign_plane(linear_block_32x32_index, local_sign_offset, sign_plane, significant_mask);
            }

            input_offset += in_planes;
        }

        flush_sign_plane(linear_block_32x32_index, local_sign_offset);

        bitstream_data_16b.data[2 * global_payload_offset + block_header_offset + 2 * HeaderSize] =
            uint16_t(code_word);
        bitstream_data_8b.data[4 * global_payload_offset + 2 * total_subblocks + block_header_offset + 4 * HeaderSize] =
            uint8_t(code_word >> 16);
    }

    subgroupBarrier();

    // Copy out all sign planes for any given group.
    for (uint i = index & 15u; i < total_sign_bytes / 4; i += 16)
    {
        uint sign_word = shared_sign_bank[linear_block_32x32_index][i];
        uint offset_8b = global_sign_offset + 4 * i;
        bitstream_data_8b.data[offset_8b + 0] = uint8_t(sign_word >> 0);
        bitstream_data_8b.data[offset_8b + 1] = uint8_t(sign_word >> 8);
        bitstream_data_8b.data[offset_8b + 2] = uint8_t(sign_word >> 16);
        bitstream_data_8b.data[offset_8b + 3] = uint8_t(sign_word >> 24);
    }

    // Copy out any stragglers.
    for (uint i = (total_sign_bytes & ~3u) + (index & 15u); i < total_sign_bytes; i += 16)
    {
        uint sign_word = shared_sign_bank[linear_block_32x32_index][i / 4];
        uint offset_8b = global_sign_offset + i;
        bitstream_data_8b.data[offset_8b] = uint8_t(sign_word >> (8 * (i & 3u)));
    }
}
