/*
 * Copyright 2023 Valve Corporation
 * SPDX-License-Identifier: MIT
 */

#include "util/format/u_format.h"
#include "nir_builder.h"

struct lower_state {
   nir_intrin_filter_cb filter;
   const void *data;
};

/*
 * If shader images are uncompressed, dedicated image atomics are unnecessary.
 * Instead, there may be a "load texel address" instruction that does all the
 * addressing math, and then regular global atomics may be used with the
 * calculated address. This pass lowers image atomics to image_texel_address +
 * global atomics.
 */

static bool
lower(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
   const struct lower_state *state = data;
   nir_intrinsic_op address_op;
   bool swap;

#define CASE(storage)                                                  \
   case nir_intrinsic_##storage##_atomic:                              \
   case nir_intrinsic_##storage##_atomic_swap:                         \
      address_op = nir_intrinsic_##storage##_texel_address;            \
      swap = intr->intrinsic == nir_intrinsic_##storage##_atomic_swap; \
      break;

   switch (intr->intrinsic) {
      CASE(image)
      CASE(bindless_image)
      CASE(image_deref)
      CASE(image_heap)
   default:
      return false;
   }
#undef CASE

   b->cursor = nir_before_instr(&intr->instr);
   nir_atomic_op atomic_op = nir_intrinsic_atomic_op(intr);
   enum pipe_format format = nir_intrinsic_format(intr);
   unsigned bit_size = intr->def.bit_size;

   if (state->filter && !state->filter(intr, state->data))
      return false;

   /* Even for "formatless" access, we know the size of the texel accessed,
    * since it's the size of the atomic. We can use that to synthesize a
    * compatible format, which is good enough for texel address computations.
    */
   if (format == PIPE_FORMAT_NONE) {
      nir_alu_type type_ = nir_atomic_op_type(atomic_op);
      enum util_format_type format_type;
      if (type_ == nir_type_float)
         format_type = UTIL_FORMAT_TYPE_FLOAT;
      else if (type_ == nir_type_int)
         format_type = UTIL_FORMAT_TYPE_SIGNED;
      else
         format_type = UTIL_FORMAT_TYPE_UNSIGNED;

      format = util_format_get_array(format_type, bit_size, 1, false,
                                     type_ != nir_type_float);
   }

   /* Get the relevant texel address */
   nir_def *address = nir_image_texel_address(
      b, 64, intr->src[0].ssa, intr->src[1].ssa, intr->src[2].ssa,
      .image_dim = nir_intrinsic_image_dim(intr),
      .image_array = nir_intrinsic_image_array(intr),
      .format = format,
      .access = nir_intrinsic_access(intr));

   nir_intrinsic_instr *address_intr = nir_def_as_intrinsic(address);

   address_intr->intrinsic = address_op;
   if (address_op == nir_intrinsic_image_texel_address) {
      nir_intrinsic_set_range_base(address_intr,
                                   nir_intrinsic_range_base(intr));
   }

   /* Build the global atomic */
   nir_def *global;
   if (swap) {
      global = nir_global_atomic_swap(b, bit_size, address, intr->src[3].ssa,
                                      intr->src[4].ssa, .atomic_op = atomic_op);
   } else {
      global = nir_global_atomic(b, bit_size, address, intr->src[3].ssa,
                                 .atomic_op = atomic_op);
   }

   b->shader->info.use_lowered_image_to_global = true;

   /* Replace the image atomic with the global atomic. Remove the image
    * explicitly because it has side effects so is not DCE'd.
    */
   nir_def_replace(&intr->def, global);
   return true;
}

bool
nir_lower_image_atomics_to_global(nir_shader *shader,
                                  nir_intrin_filter_cb filter,
                                  const void *data)
{
   struct lower_state state = {
      .filter = filter,
      .data = data,
   };
   return nir_shader_intrinsics_pass(shader, lower,
                                     nir_metadata_control_flow,
                                     (void *)&state);
}
