#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

layout(local_size_x_id = 0) in;

struct RDOperation
{
    int quant;
    uint block_offset_saving;
};

const int BLOCK_SPACE_SUBDIVISION = 16;

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

layout(set = 0, binding = 1) buffer QuantList
{
    int data[];
} quant_data;

layout(push_constant) uniform Registers
{
    uint target_payload_size;
    uint num_blocks_per_subdivision;
} registers;

void main()
{
    int required_savings_per_bucket = int(buckets.consumed_payload) - int(registers.target_payload_size);
    if (gl_WorkGroupID.x != 0)
    {
        int prev_bucket_total = buckets.total_savings_per_bucket[gl_WorkGroupID.x - 1];
        // This bucket is empty.
        if (buckets.total_savings_per_bucket[gl_WorkGroupID.x] == prev_bucket_total)
            return;

        required_savings_per_bucket -= prev_bucket_total;
    }
    else
    {
        // This bucket is empty.
        if (buckets.total_savings_per_bucket[gl_WorkGroupID.x] == 0)
            return;
    }

    // If all previous buckets can complete the job, skip.
    if (required_savings_per_bucket <= 0)
        return;

    uint total_saved = 0;

    for (uint i = 0; i < registers.num_blocks_per_subdivision && total_saved < required_savings_per_bucket; i += gl_SubgroupSize)
    {
        RDOperation op = RDOperation(0, 0);
        if (i + gl_SubgroupInvocationID < registers.num_blocks_per_subdivision)
            op = buckets.rdo_operations[gl_WorkGroupID.x * registers.num_blocks_per_subdivision + i + gl_SubgroupInvocationID];

        uint saving = bitfieldExtract(op.block_offset_saving, 16, 16);
        uint block_offset = bitfieldExtract(op.block_offset_saving, 0, 16);

        uint scan_saving = subgroupInclusiveAdd(saving);

        bool should_apply_quant = total_saved + scan_saving - saving < required_savings_per_bucket;
        if (should_apply_quant && saving != 0)
            atomicMax(quant_data.data[block_offset], op.quant);

        total_saved += subgroupShuffle(scan_saving, gl_SubgroupSize - 1);
    }
}

