#version 450

#extension GL_EXT_control_flow_attributes : require
#ifndef FWHT_SHMEM
#extension GL_KHR_shader_subgroup_basic : enable
#extension GL_KHR_shader_subgroup_shuffle : enable
#endif

layout(constant_id = 0) const uint BLOCK_SIZE = 32;
layout(constant_id = 1) const uint N = 128;

layout(local_size_x_id = 0, local_size_y = 4, local_size_z = 1) in;

layout(push_constant) uniform parameter
{
    uint n_rows;
    uint src_offset;
    uint dst_offset;
    float scale;
};

layout(binding = 0, std430) readonly buffer A { float data_a[]; };
layout(binding = 1, std430) writeonly buffer D { float data_d[]; };

const uint EL_W = N / BLOCK_SIZE;

#ifdef FWHT_SHMEM
shared float shmem[4 * N];
#endif

void main() {
#ifdef FWHT_SHMEM
    const uint tid = gl_LocalInvocationID.x;
    const uint shmem_base = gl_LocalInvocationID.y * N;
    const uint row_id = gl_LocalInvocationID.y;
#else
    const uint tid = gl_SubgroupInvocationID;
    const uint row_id = gl_SubgroupID;
#endif

    for (uint base_row = gl_WorkGroupID.x * gl_WorkGroupSize.y;
            base_row < n_rows;
            base_row += gl_NumWorkGroups.x * gl_WorkGroupSize.y) {
        const uint row = base_row + row_id;
        const uint row_offset = row * N;

#ifndef FWHT_SHMEM
        if (row >= n_rows) {
            continue;
        }
#endif

        float reg[EL_W];

        [[unroll]]
        for (uint i = 0; i < EL_W; ++i) {
            reg[i] = row < n_rows ? data_a[src_offset + row_offset + i * BLOCK_SIZE + tid] * scale : 0.0;
        }

#ifdef FWHT_SHMEM
        [[unroll]]
        for (uint h = 1; h < BLOCK_SIZE; h <<= 1) {
            [[unroll]]
            for (uint i = 0; i < EL_W; ++i) {
                shmem[shmem_base + i * BLOCK_SIZE + tid] = reg[i];
            }
            barrier();
            [[unroll]]
            for (uint j = 0; j < EL_W; ++j) {
                const float val = reg[j];
                const float other = shmem[shmem_base + j * BLOCK_SIZE + (tid ^ h)];
                reg[j] = (tid & h) == 0 ? val + other : other - val;
            }
            barrier();
        }
#else
        [[unroll]]
        for (uint h = 1; h < BLOCK_SIZE; h <<= 1) {
            [[unroll]]
            for (uint j = 0; j < EL_W; ++j) {
                const float val = reg[j];
                const float val2 = subgroupShuffleXor(val, h);
                reg[j] = (tid & h) == 0 ? val + val2 : val2 - val;
            }
        }
#endif

        [[unroll]]
        for (uint h = BLOCK_SIZE; h < N; h <<= 1) {
            const uint step = h / BLOCK_SIZE;
            [[unroll]]
            for (uint j = 0; j < EL_W; j += 2 * step) {
                [[unroll]]
                for (uint k = 0; k < step; ++k) {
                    const float x = reg[j + k];
                    const float y = reg[j + k + step];
                    reg[j + k] = x + y;
                    reg[j + k + step] = x - y;
                }
            }
        }

#ifdef FWHT_SHMEM
        if (row < n_rows) {
#endif
            [[unroll]]
            for (uint i = 0; i < EL_W; ++i) {
                data_d[dst_offset + row_offset + i * BLOCK_SIZE + tid] = reg[i];
            }
#ifdef FWHT_SHMEM
        }
        barrier();
#endif
    }
}
