#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_vote : require
#extension GL_KHR_shader_subgroup_arithmetic : require
#extension GL_KHR_shader_subgroup_shuffle_relative : require
#extension GL_EXT_samplerless_texture_functions : require

#if STORAGE_MODE == 0
#extension GL_EXT_shader_8bit_storage : require
#extension GL_EXT_shader_16bit_storage : require
#endif

layout(local_size_x = 128) in;

layout(set = 0, binding = 0) writeonly uniform image2DArray uDequantImg;

layout(set = 0, binding = 1) readonly buffer PayloadOffsets
{
    uint data[];
} payload_offsets;

#if STORAGE_MODE == 0
layout(set = 0, binding = 2) readonly buffer Payloads
{
    uint data[];
} payload_data_u32;

layout(set = 0, binding = 2) readonly buffer Payloads16
{
    uint16_t data[];
} payload_data_u16;

layout(set = 0, binding = 2) readonly buffer Payloads8
{
    uint8_t data[];
} payload_data_u8;
#elif STORAGE_MODE == 1
layout(set = 0, binding = 2) uniform usamplerBuffer PayloadU32;
layout(set = 0, binding = 3) uniform mediump usamplerBuffer PayloadU16;
layout(set = 0, binding = 4) uniform mediump usamplerBuffer PayloadU8;
#else
layout(set = 0, binding = 2) uniform utexture2D PayloadU32;
layout(set = 0, binding = 3) uniform mediump utexture2D PayloadU16;
layout(set = 0, binding = 4) uniform mediump utexture2D PayloadU8;
#endif

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

layout(push_constant) uniform Registers
{
    ivec2 resolution;
    int output_layer;
    int block_offset_32x32;
    int block_stride_32x32;
} registers;

#if STORAGE_MODE == 1
uint read_payload_u8(int coord)
{
	return texelFetch(PayloadU8, coord).x;
}

uint read_payload_u16(int coord)
{
	return texelFetch(PayloadU16, coord).x;
}

uint read_payload_u32(int coord)
{
	return texelFetch(PayloadU32, coord).x;
}
#elif STORAGE_MODE == 2
uint read_payload_u8(uint coord)
{
	uint x = bitfieldExtract(coord, 0, 12);
	uint y = bitfieldExtract(coord, 12, 20);
	return texelFetch(PayloadU8, ivec2(x, y), 0).x;
}

uint read_payload_u16(uint coord)
{
	uint x = bitfieldExtract(coord, 0, 11);
	uint y = bitfieldExtract(coord, 11, 21);
	return texelFetch(PayloadU16, ivec2(x, y), 0).x;
}

uint read_payload_u32(uint coord)
{
	uint x = bitfieldExtract(coord, 0, 10);
	uint y = bitfieldExtract(coord, 10, 22);
	return texelFetch(PayloadU32, ivec2(x, y), 0).x;
}
#endif

mat2x4 decode_payload(uint code_word, uint q_bits, uint offset, uint block_index)
{
    bool empty_block = code_word == 0;
    if (empty_block)
        return mat2x4(vec4(0.0), vec4(0.0));

    int bit_offset = 2 * int(block_index);

    // First, we need to compute the offset that our 4x2 block starts on.
    uint lsbs = code_word & 0x5555u;
    uint msbs = code_word & 0xaaaau;
    uint msbs_shift = msbs >> 1;
    msbs |= msbs_shift;

    uint byte_offset =
        bitCount(bitfieldExtract(lsbs, 0, bit_offset)) +
        bitCount(bitfieldExtract(msbs, 0, bit_offset)) +
        q_bits * block_index + offset;

#if STORAGE_MODE == 0
    // Eagerly load the data to keep latency down.
    // Also forces the descriptor to be loaded early.
    uint payload = uint(payload_data_u8.data[byte_offset]);
#else
	uint payload = read_payload_u8(int(byte_offset));
#endif

    uint local_control_word = bitfieldExtract(code_word, bit_offset, 2);
    int decoded_abs[8] = int[8](0, 0, 0, 0, 0, 0, 0, 0);
    int plane_iterations = int(q_bits + local_control_word);

    for (int q = plane_iterations - 1; q >= 0; q--)
    {
        for (int b = 0; b < 8; b++)
        {
            int decoded = int(bitfieldExtract(payload, b, 1));
            decoded_abs[b] = bitfieldInsert(decoded_abs[b], decoded, q, 1);
        }
        byte_offset++;
#if STORAGE_MODE == 0
        payload = uint(payload_data_u8.data[byte_offset]);
#else
        payload = read_payload_u8(int(byte_offset));
#endif
    }

    mat2x4 m;

    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            float v = float(decoded_abs[i * 2 + j]);
            if (v != 0.0)
                v += 0.5;
            m[j][i] = v;
        }
    }

    return m;
}

shared uint shared_sign_offset;
shared uint shared_plane_byte_offsets[16];
shared uint shared_sign_scan[128 / 4];

const int MaxScaleExp = 4;

float decode_quant(uint quant_code)
{
    // Custom FP formulation for numbers in (0, 16) range.
    int e = MaxScaleExp - int(quant_code >> 3);
    int m = int(quant_code) & 0x7;
    float inv_quant = (1.0 / (8.0 * 1024.0 * 1024.0)) * float((8 + m) * (1 << (20 + e)));
    return inv_quant;
}

uint scan_subgroups(uint v)
{
    for (uint i = 1; i < gl_NumSubgroups; i *= 2)
    {
        uint up = subgroupShuffleUp(v, i);
        v += gl_SubgroupInvocationID >= i ? up : 0;
    }

    return v;
}

void scan_subgroups_fallback(uint local_index)
{
    barrier();

    // Slow LDS fallback for devices with wave size smaller than 16.
    bool active_lane = local_index < gl_NumSubgroups;

    uint v = 0;
    if (active_lane)
        v = shared_sign_scan[local_index];

    for (uint i = 1; i < gl_NumSubgroups; i *= 2)
    {
        uint up = 0;
        bool do_work = local_index >= i && active_lane;
        if (do_work)
            up = shared_sign_scan[local_index - i];

		// Resolve write-after-read hazard.
        barrier();

        if (do_work)
        {
            v += up;
            shared_sign_scan[local_index] = v;
        }

        barrier();
    }
}


void main()
{
    uint local_index = gl_SubgroupID * gl_SubgroupSize + gl_SubgroupInvocationID;

    int block_index_32x32 = int(registers.block_offset_32x32 +
        gl_WorkGroupID.y * registers.block_stride_32x32 +
        gl_WorkGroupID.x);

    uint block_local_index = bitfieldExtract(local_index, 0, 3);
    uint block_x = bitfieldExtract(local_index, 3, 2);
    uint block_y = bitfieldExtract(local_index, 5, 2);
    uint linear_block = block_y * 4 + block_x;

    // Each thread individually decodes 8 values.
    ivec2 local_coord = unswizzle8x8(block_local_index << 3);

    ivec2 coord = ivec2(gl_WorkGroupID.xy) * 32;
    coord += 8 * ivec2(block_x, block_y);
    coord += local_coord;

    uint offset_u32 = payload_offsets.data[block_index_32x32];

    if (offset_u32 == ~0u)
    {
        for (int j = 0; j < 2; j++)
            for (int i = 0; i < 4; i++)
                imageStore(uDequantImg, ivec3(coord + ivec2(i, j), registers.output_layer), vec4(0.0));
        return;
    }

#if STORAGE_MODE == 0
    uint ballot = payload_data_u32.data[offset_u32] & 0xffff;
    uint q_code = payload_data_u32.data[offset_u32 + 1] & 0xff;
#else
    uint ballot = read_payload_u16(2 * int(offset_u32));
    uint q_code = read_payload_u8(4 * int(offset_u32) + 4);
#endif

    if (local_index < 16)
    {
        uint control_word = 0;
        uint q_bits = 0;

        if (bitfieldExtract(ballot, int(local_index), 1) != 0)
        {
            uint local_code_offset = bitCount(bitfieldExtract(ballot, 0, int(local_index)));
#if STORAGE_MODE == 0
            control_word = uint(payload_data_u16.data[offset_u32 * 2 + 4 + local_code_offset]);
            q_bits = uint(payload_data_u8.data[offset_u32 * 4 + 8 + bitCount(ballot) * 2 + local_code_offset]) & 0xfu;
#else
            control_word = read_payload_u16(int(offset_u32 * 2 + 4 + local_code_offset));
            q_bits = read_payload_u8(int(offset_u32 * 4 + 8 + bitCount(ballot) * 2 + local_code_offset)) & 0xfu;
#endif
        }

        uint lsbs = control_word & 0x5555u;
        uint msbs = control_word & 0xaaaau;
        uint msbs_shift = msbs >> 1;
        msbs |= msbs_shift;
        uint byte_cost = bitCount(lsbs) + bitCount(msbs) + q_bits * 8;

        uint byte_scan = offset_u32 * 4 + 8 + 3 * bitCount(ballot) + subgroupInclusiveAdd(byte_cost);
        if (local_index == 15)
            shared_sign_offset = 8 * byte_scan;
        shared_plane_byte_offsets[local_index] = byte_scan - byte_cost;
    }

    barrier();

    mat2x4 v;
    int significant_count;

    if (bitfieldExtract(ballot, int(linear_block), 1) != 0)
    {
        uint local_code_offset = bitCount(bitfieldExtract(ballot, 0, int(linear_block)));

#if STORAGE_MODE == 0
        uint control_word = uint(payload_data_u16.data[offset_u32 * 2 + 4 + local_code_offset]);
        uint control_word2 = uint(payload_data_u8.data[offset_u32 * 4 + 8 + bitCount(ballot) * 2 + local_code_offset]);
#else
        uint control_word = read_payload_u16(int(offset_u32 * 2 + 4 + local_code_offset));
        uint control_word2 = read_payload_u8(int(offset_u32 * 4 + 8 + bitCount(ballot) * 2 + local_code_offset));
#endif

        v = decode_payload(control_word, control_word2 & 0xfu,
            shared_plane_byte_offsets[linear_block], block_local_index);

        significant_count = 0;
        for (int j = 0; j < 2; j++)
            for (int i = 0; i < 4; i++)
                significant_count += int(v[j][i] != 0.0);

        float q = decode_quant(q_code);
        float inv_scale = q * decode_quant_scale(bitfieldExtract(control_word2, QUANT_SCALE_OFFSET - 16, QUANT_SCALE_BITS));

        v *= inv_scale;
    }
    else
    {
        v = mat2x4(vec4(0.0), vec4(0.0));
        significant_count = 0;
    }

    // Figure out how many significant coefficients we have.
    int significant_scan = subgroupInclusiveAdd(significant_count);
    if (gl_SubgroupInvocationID == gl_SubgroupSize - 1)
        shared_sign_scan[gl_SubgroupID] = significant_scan;

    if (gl_NumSubgroups <= 8)
    {
        barrier();
		if (gl_SubgroupSize <= 32)
		{
			// Should be more robust since not all compilers properly understand the shuffle up pattern.
			// AMD is known to understand it well.
			if (local_index < gl_NumSubgroups)
				shared_sign_scan[local_index] = subgroupInclusiveAdd(shared_sign_scan[local_index]);
		}
		else
		{
			if (local_index < gl_NumSubgroups)
				shared_sign_scan[local_index] = scan_subgroups(shared_sign_scan[local_index]);
		}
        barrier();
    }
    else
    {
        scan_subgroups_fallback(local_index);
    }

    // Compute where we need to start reading sign bits from.
    uint sign_offset = shared_sign_offset + significant_scan - significant_count;
    if (gl_SubgroupID != 0)
        sign_offset += shared_sign_scan[gl_SubgroupID - 1];

    // Read out all sign bits we could possibly access per thread.
    // On AMD at least, this 64-bit load should be vectorizable.
#if STORAGE_MODE == 0
    uint sign_word = payload_data_u32.data[sign_offset / 32 + 0];
    uint sign_word_upper = payload_data_u32.data[sign_offset / 32 + 1];
#else
    uint sign_word = read_payload_u32(int(sign_offset / 32 + 0));
    uint sign_word_upper = read_payload_u32(int(sign_offset / 32 + 1));
#endif

    uint masked_sign_offset = sign_offset & 31u;
    if (masked_sign_offset != 0)
    {
        sign_word >>= masked_sign_offset;
        sign_word |= sign_word_upper << (32 - masked_sign_offset);
    }

    int sign_counter = 0;

    // Clock out the sign bits as needed.
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            if (v[j][i] != 0.0)
            {
                v[j][i] *= 1.0 - 2.0 * float(bitfieldExtract(sign_word, sign_counter, 1));
                sign_counter++;
            }
        }
    }

    // Write output.
    for (int j = 0; j < 2; j++)
        for (int i = 0; i < 4; i++)
            imageStore(uDequantImg, ivec3(coord + ivec2(i, j), registers.output_layer), vec4(v[j][i]));
}
