/* -*- mesa-c++  -*-
 *
 * Copyright (c) 2018 Collabora LTD
 *
 * Author: Gert Wollny <gert.wollny@collabora.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * on the rights to use, copy, modify, merge, publish, distribute, sub
 * license, and/or sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "sfn_emittexinstruction.h"
#include "sfn_shader_base.h"
#include "sfn_instruction_fetch.h"

namespace r600 {

EmitTexInstruction::EmitTexInstruction(ShaderFromNirProcessor &processor):
   EmitInstruction (processor)
{
}

bool EmitTexInstruction::do_emit(nir_instr* instr)
{
   nir_tex_instr* ir = nir_instr_as_tex(instr);

   TexInputs src;
   if (!get_inputs(*ir, src))
      return false;

   if (ir->sampler_dim == GLSL_SAMPLER_DIM_CUBE) {
      switch (ir->op) {
      case nir_texop_tex:
         return emit_cube_tex(ir, src);
      case nir_texop_txf:
         return emit_cube_txf(ir, src);
      case nir_texop_txb:
         return emit_cube_txb(ir, src);
      case nir_texop_txl:
         return emit_cube_txl(ir, src);
      case nir_texop_txs:
         return emit_tex_txs(ir, src, {0,1,2,3});
      case nir_texop_txd:
         return emit_cube_txd(ir, src);
      case nir_texop_lod:
         return emit_cube_lod(ir, src);
      case nir_texop_tg4:
         return emit_cube_tg4(ir, src);
      case nir_texop_query_levels:
         return emit_tex_txs(ir, src, {3,7,7,7});
      default:
         return false;
      }
   } else if (ir->sampler_dim == GLSL_SAMPLER_DIM_BUF) {
      switch (ir->op) {
      case nir_texop_txf:
         return emit_buf_txf(ir, src);
      case nir_texop_txs:
         return emit_tex_txs(ir, src, {0,1,2,3});
      default:
         return false;
      }
   } else {
      switch (ir->op) {
      case nir_texop_tex:
         return emit_tex_tex(ir, src);
      case nir_texop_txf:
         return emit_tex_txf(ir, src);
      case nir_texop_txb:
         return emit_tex_txb(ir, src);
      case nir_texop_txl:
         return emit_tex_txl(ir, src);
      case nir_texop_txd:
         return emit_tex_txd(ir, src);
      case nir_texop_txs:
         return emit_tex_txs(ir, src, {0,1,2,3});
      case nir_texop_lod:
         return emit_tex_lod(ir, src);
      case nir_texop_tg4:
         return emit_tex_tg4(ir, src);
      case nir_texop_txf_ms:
         return emit_tex_txf_ms(ir, src);
      case nir_texop_query_levels:
         return emit_tex_txs(ir, src, {3,7,7,7});
      case nir_texop_texture_samples:
         return emit_tex_texture_samples(ir, src, {3,7,7,7});
      default:

         return false;
      }
   }
}

bool EmitTexInstruction::emit_cube_txf(UNUSED nir_tex_instr* instr, UNUSED TexInputs &src)
{
   return false;
}

bool EmitTexInstruction::emit_cube_txd(nir_tex_instr* instr, TexInputs& tex_src)
{

   assert(instr->src[0].src.is_ssa);

   r600::sfn_log << SfnLog::instr << "emit '"
                 << *reinterpret_cast<nir_instr*>(instr)
                 << "' (" << __func__ << ")\n";

   auto tex_op = TexInstruction::sample_g;

   std::array<PValue, 4> v;
   for (int i = 0; i < 4; ++i)
      v[i] = from_nir(instr->dest, i);

   GPRVector cubed(v);
   emit_cube_prep(tex_src.coord, cubed, instr->is_array);

   std::array<PValue,4> dst_elms;
   std::array<PValue,4> src_elms;

   const uint16_t lookup[4] = {1, 0, 3, 2};
   for (uint16_t i = 0; i < 4; ++i) {
      dst_elms[i] = v[i];
      src_elms[i] = cubed.reg_i(lookup[i]);
   }

   GPRVector empty_dst(0, {7,7,7,7});

   if (instr->is_shadow)  {
      emit_instruction(new AluInstruction(op1_mov, src_elms[3], tex_src.comperator,
                       {alu_last_instr, alu_write}));
      tex_op = TexInstruction::sample_c_g;
   }


   PValue half(new LiteralValue(0.5f));
   for (int i = 0; i < 3; ++i) {
      emit_instruction(new AluInstruction(op2_mul_ieee, tex_src.ddx.reg_i(i), {tex_src.ddx.reg_i(i), half},
      {alu_last_instr, alu_write}));
   }
   for (int i = 0; i < 3; ++i) {
      emit_instruction(new AluInstruction(op2_mul_ieee, tex_src.ddy.reg_i(i), {tex_src.ddy.reg_i(i), half},
      {alu_last_instr, alu_write}));
   }

   auto sampler = get_samplerr_id(instr->sampler_index, tex_src.sampler_deref);
   assert(!sampler.indirect);

   TexInstruction *irgh = new TexInstruction(TexInstruction::set_gradient_h, empty_dst, tex_src.ddx,
                                             sampler.id, sampler.id + R600_MAX_CONST_BUFFERS, tex_src.sampler_offset);
   irgh->set_dest_swizzle({7,7,7,7});

   TexInstruction *irgv = new TexInstruction(TexInstruction::set_gradient_v, empty_dst, tex_src.ddy,
                           sampler.id, sampler.id + R600_MAX_CONST_BUFFERS, tex_src.sampler_offset);
   irgv->set_dest_swizzle({7,7,7,7});

   GPRVector dst(dst_elms);
   GPRVector src(src_elms);
   TexInstruction *ir = new TexInstruction(tex_op, dst, src, instr->sampler_index,
                                 sampler.id + R600_MAX_CONST_BUFFERS, tex_src.sampler_offset);

   set_rect_coordinate_flags(instr, ir);
   //set_offsets(ir, tex_src.offset);

   emit_instruction(irgh);
   emit_instruction(irgv);
   emit_instruction(ir);
   return true;
}


bool EmitTexInstruction::emit_cube_txl(nir_tex_instr* instr, TexInputs& tex_src)
{
   assert(instr->src[0].src.is_ssa);

   if (instr->is_shadow)
      return false;

   r600::sfn_log << SfnLog::instr << "emit '"
                 << *reinterpret_cast<nir_instr*>(instr)
                 << "' (" << __func__ << ")\n";

   std::array<PValue, 4> v;
   for (int i = 0; i < 4; ++i)
      v[i] = from_nir(instr->dest, i);

   GPRVector cubed(v);
   emit_cube_prep(tex_src.coord, cubed, instr->is_array);

   std::array<PValue,4> dst_elms;
   std::array<PValue,4> src_elms;

   const uint16_t lookup[4] = {1, 0, 3, 2};
   for (uint16_t i = 0; i < 4; ++i) {
      dst_elms[i] = v[i];
      src_elms[i] = cubed.reg_i(lookup[i]);
   }

   auto *ir = new AluInstruction(op1_mov, src_elms[3], tex_src.lod,
                                 {alu_last_instr, alu_write});
   emit_instruction(ir);

   GPRVector src(src_elms);
   GPRVector dst(dst_elms);

   auto sampler = get_samplerr_id(instr->sampler_index, tex_src.sampler_deref);
   assert(!sampler.indirect);

   auto tir = new TexInstruction(TexInstruction::sample_l, dst, src,
                                 sampler.id,sampler.id + R600_MAX_CONST_BUFFERS,
                                 tex_src.sampler_offset);

   if (instr->is_array)
      tir->set_flag(TexInstruction::z_unnormalized);

   emit_instruction(tir);
   return true;
}

bool EmitTexInstruction::emit_cube_lod(nir_tex_instr* instr, TexInputs& src)
{
   auto tex_op = TexInstruction::get_tex_lod;

   std::array<PValue, 4> v;
   for (int i = 0; i < 4; ++i)
      v[i] = from_nir(instr->dest, i);

   GPRVector cubed(v);
   emit_cube_prep(src.coord, cubed, instr->is_array);

   auto sampler = get_samplerr_id(instr->sampler_index, src.sampler_deref);
   assert(!sampler.indirect);

   auto dst = make_dest(*instr);
   auto irt = new TexInstruction(tex_op, dst, cubed, sampler.id,
                                 sampler.id + R600_MAX_CONST_BUFFERS,
                                 src.sampler_offset);

   emit_instruction(irt);
   return true;

}


bool EmitTexInstruction::emit_cube_txb(nir_tex_instr* instr, TexInputs& tex_src)
{
   assert(instr->src[0].src.is_ssa);

   r600::sfn_log << SfnLog::instr << "emit '"
                 << *reinterpret_cast<nir_instr*>(instr)
                 << "' (" << __func__ << ")\n";

   std::array<PValue, 4> v;
   for (int i = 0; i < 4; ++i)
      v[i] = from_nir(instr->dest, i);

   GPRVector cubed(v);
   emit_cube_prep(tex_src.coord, cubed, instr->is_array);

   std::array<PValue,4> dst_elms;
   std::array<PValue,4> src_elms;

   const uint16_t lookup[4] = {1, 0, 3, 2};
   for (uint16_t i = 0; i < 4; ++i) {
      dst_elms[i] = v[i];
      src_elms[i] = v[lookup[i]];
   }

   GPRVector src(src_elms);
   GPRVector dst(dst_elms);

   auto tex_op = TexInstruction::sample_lb;
   if (!instr->is_shadow)  {
      emit_instruction(new AluInstruction(op1_mov, src_elms[3], tex_src.bias,
                                          {alu_last_instr, alu_write}));
   } else {
      emit_instruction(new AluInstruction(op1_mov, src_elms[3], tex_src.comperator,
                       {alu_last_instr, alu_write}));
      tex_op = TexInstruction::sample_c_lb;
   }

   auto sampler = get_samplerr_id(instr->sampler_index, tex_src.sampler_deref);
   assert(!sampler.indirect && "Indirect sampler selection not yet supported");

   auto tir = new TexInstruction(tex_op, dst, src,
                                 sampler.id,
                                 sampler.id + R600_MAX_CONST_BUFFERS, tex_src.sampler_offset);
   emit_instruction(tir);
   return true;

}

bool EmitTexInstruction::emit_cube_tex(nir_tex_instr* instr, TexInputs& tex_src)
{
   std::array<PValue, 4> v;
   for (int i = 0; i < 4; ++i)
      v[i] = from_nir(instr->dest, i);

   auto tex_op = TexInstruction::sample;
   GPRVector cubed(v);
   emit_cube_prep(tex_src.coord, cubed, instr->is_array);

   std::array<PValue,4> dst_elms;
   std::array<PValue,4> src_elms;

   const uint16_t lookup[4] = {1, 0, 3, 2};
   for (uint16_t i = 0; i < 4; ++i) {
      dst_elms[i] = v[i];
      src_elms[i] = v[lookup[i]];
   }

   if (instr->is_shadow)  {
      emit_instruction(new AluInstruction(op1_mov, src_elms[3], tex_src.comperator,
                       {alu_last_instr, alu_write}));
      tex_op = TexInstruction::sample_c;
   }

   GPRVector dst(dst_elms);
   GPRVector src(src_elms);

   auto sampler = get_samplerr_id(instr->sampler_index, tex_src.sampler_deref);
   assert(!sampler.indirect && "Indirect sampler selection not yet supported");

   auto tir = new TexInstruction(tex_op, dst, src,
                                 sampler.id,
                                 sampler.id + R600_MAX_CONST_BUFFERS, tex_src.sampler_offset);
   if (instr->is_array)
      tir->set_flag(TexInstruction::z_unnormalized);

   emit_instruction(tir);
   return true;

}

bool EmitTexInstruction::emit_cube_prep(const GPRVector& coord, GPRVector& cubed, bool is_array)
{
   AluInstruction *ir = nullptr;
   const uint16_t src0_chan[4] = {2, 2, 0, 1};
   const uint16_t src1_chan[4] = {1, 0, 2, 2};

   for (int i = 0; i < 4; ++i)  {
      ir = new AluInstruction(op2_cube, cubed.reg_i(i), coord.reg_i(src0_chan[i]),
                              coord.reg_i(src1_chan[i]), {alu_write});

      emit_instruction(ir);
   }
   ir->set_flag(alu_last_instr);

   ir = new AluInstruction(op1_recip_ieee, cubed.reg_i(2), cubed.reg_i(2), {alu_write, alu_last_instr});
   ir->set_flag(alu_src0_abs);
   emit_instruction(ir);

   PValue one_p_5(new LiteralValue(1.5f));
   for (int i = 0; i < 2; ++i)  {
      ir = new AluInstruction(op3_muladd, cubed.reg_i(i), cubed.reg_i(i), cubed.reg_i(2),
                              one_p_5, {alu_write});
      emit_instruction(ir);
   }
   ir->set_flag(alu_last_instr);

   if (is_array) {
      auto face = cubed.reg_i(3);
      PValue array_index = get_temp_register();

      ir = new AluInstruction(op1_rndne, array_index, coord.reg_i(3), {alu_write, alu_last_instr});
      emit_instruction(ir);

      ir = new AluInstruction(op2_max, array_index, {array_index, Value::zero}, {alu_write, alu_last_instr});
      emit_instruction(ir);

      ir = new AluInstruction(op3_muladd, face, {array_index, PValue (new LiteralValue(8.0f)), face},
                              {alu_write, alu_last_instr});
      emit_instruction(ir);
   }

   return true;
}

bool EmitTexInstruction::emit_buf_txf(nir_tex_instr* instr, TexInputs &src)
{
   auto dst = make_dest(*instr);

   auto ir = new FetchInstruction(vc_fetch, no_index_offset, dst, src.coord.reg_i(0), 0,
                                  instr->texture_index +  R600_MAX_CONST_BUFFERS,
                                  src.texture_offset, bim_none);
   ir->set_flag(vtx_use_const_field);
   emit_instruction(ir);
   return true;
}

bool EmitTexInstruction::emit_tex_tex(nir_tex_instr* instr, TexInputs& src)
{

   r600::sfn_log << SfnLog::instr << "emit '"
                 << *reinterpret_cast<nir_instr*>(instr)
                 << "' (" << __func__ << ")\n";

   auto tex_op = TexInstruction::sample;

   auto sampler = get_samplerr_id(instr->sampler_index, src.sampler_deref);
   assert(!sampler.indirect);

   if (instr->is_shadow)  {
      emit_instruction(new AluInstruction(op1_mov, src.coord.reg_i(3), src.comperator,
                       {alu_last_instr, alu_write}));
      tex_op = TexInstruction::sample_c;
   }

   auto dst = make_dest(*instr);
   auto irt = new TexInstruction(tex_op, dst, src.coord, sampler.id,
                                 sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);
   if (instr->is_array)
      handle_array_index(*instr, src.coord, irt);

   set_rect_coordinate_flags(instr, irt);
   set_offsets(irt, src.offset);

   emit_instruction(irt);
   return true;
}

bool EmitTexInstruction::emit_tex_txd(nir_tex_instr* instr, TexInputs& src)
{
   r600::sfn_log << SfnLog::instr << "emit '"
                 << *reinterpret_cast<nir_instr*>(instr)
                 << "' (" << __func__ << ")\n";

   auto tex_op = TexInstruction::sample_g;
   auto dst = make_dest(*instr);

   GPRVector empty_dst(0,{7,7,7,7});

   if (instr->is_shadow)  {
      emit_instruction(new AluInstruction(op1_mov, src.coord.reg_i(3), src.comperator,
                       {alu_last_instr, alu_write}));
      tex_op = TexInstruction::sample_c_g;
   }

   auto sampler = get_samplerr_id(instr->sampler_index, src.sampler_deref);
   assert(!sampler.indirect && "Indirect sampler selection not yet supported");

   TexInstruction *irgh = new TexInstruction(TexInstruction::set_gradient_h, empty_dst, src.ddx,
                                             sampler.id,
                                             sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);
   irgh->set_dest_swizzle({7,7,7,7});

   TexInstruction *irgv = new TexInstruction(TexInstruction::set_gradient_v, empty_dst, src.ddy,
                           sampler.id, sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);
   irgv->set_dest_swizzle({7,7,7,7});

   TexInstruction *ir = new TexInstruction(tex_op, dst, src.coord, sampler.id,
                                           sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);
   if (instr->is_array)
      handle_array_index(*instr, src.coord, ir);

   set_rect_coordinate_flags(instr, ir);
   set_offsets(ir, src.offset);

   emit_instruction(irgh);
   emit_instruction(irgv);
   emit_instruction(ir);
   return true;
}

bool EmitTexInstruction::emit_tex_txf(nir_tex_instr* instr, TexInputs& src)
{
   r600::sfn_log << SfnLog::instr << "emit '"
                 << *reinterpret_cast<nir_instr*>(instr)
                 << "' (" << __func__ << ")\n";

   auto dst = make_dest(*instr);

   if (*src.coord.reg_i(3) != *src.lod)
      emit_instruction(new AluInstruction(op1_mov, src.coord.reg_i(3), src.lod, {alu_write, alu_last_instr}));

   auto sampler = get_samplerr_id(instr->sampler_index, src.sampler_deref);
   assert(!sampler.indirect);

   /* txf doesn't need rounding for the array index, but 1D has the array index
    * in the z component */
   if (instr->is_array && instr->sampler_dim == GLSL_SAMPLER_DIM_1D)
      src.coord.set_reg_i(2, src.coord.reg_i(1));

   auto tex_ir = new TexInstruction(TexInstruction::ld, dst, src.coord,
                                    sampler.id,
                                    sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);


   if (src.offset) {
      assert(src.offset->is_ssa);
      AluInstruction *ir = nullptr;
      for (unsigned i = 0; i < src.offset->ssa->num_components; ++i) {
         ir = new AluInstruction(op2_add_int, src.coord.reg_i(i),
                  {src.coord.reg_i(i), from_nir(*src.offset, i, i)}, {alu_write});
         emit_instruction(ir);
      }
      if (ir)
         ir->set_flag(alu_last_instr);
   }

   emit_instruction(tex_ir);
   return true;
}

bool EmitTexInstruction::emit_tex_lod(nir_tex_instr* instr, TexInputs& src)
{
   auto tex_op = TexInstruction::get_tex_lod;

   auto sampler = get_samplerr_id(instr->sampler_index, src.sampler_deref);
   assert(!sampler.indirect && "Indirect sampler selection not yet supported");

   auto dst = make_dest(*instr);
   auto irt = new TexInstruction(tex_op, dst, src.coord, sampler.id,
                                 sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);
   irt->set_dest_swizzle({1,0,7,7});
   emit_instruction(irt);
   return true;

}

bool EmitTexInstruction::emit_tex_txl(nir_tex_instr* instr, TexInputs& src)
{
   r600::sfn_log << SfnLog::instr << "emit '"
                 << *reinterpret_cast<nir_instr*>(instr)
                 << "' (" << __func__ << ")\n";

   auto tex_op = TexInstruction::sample_l;
   emit_instruction(new AluInstruction(op1_mov, src.coord.reg_i(3), src.lod,
                                       {alu_last_instr, alu_write}));

   if (instr->is_shadow)  {
      emit_instruction(new AluInstruction(op1_mov, src.coord.reg_i(2), src.comperator,
                       {alu_last_instr, alu_write}));
      tex_op = TexInstruction::sample_c_l;
   }

   auto sampler = get_samplerr_id(instr->sampler_index, src.sampler_deref);
   assert(!sampler.indirect && "Indirect sampler selection not yet supported");

   auto dst = make_dest(*instr);
   auto irt = new TexInstruction(tex_op, dst, src.coord, sampler.id,
                                 sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);

   if (instr->is_array)
      handle_array_index(*instr, src.coord, irt);

   set_rect_coordinate_flags(instr, irt);
   set_offsets(irt, src.offset);

   emit_instruction(irt);
   return true;
}

bool EmitTexInstruction::emit_tex_txb(nir_tex_instr* instr, TexInputs& src)
{
   auto tex_op = TexInstruction::sample_lb;

   std::array<uint8_t, 4> in_swizzle = {0,1,2,3};

   emit_instruction(new AluInstruction(op1_mov, src.coord.reg_i(3), src.bias,
                                       {alu_last_instr, alu_write}));

   if (instr->is_shadow) {
      emit_instruction(new AluInstruction(op1_mov, src.coord.reg_i(2), src.comperator,
                                          {alu_last_instr, alu_write}));
      tex_op = TexInstruction::sample_c_lb;
   }

   GPRVector tex_src(src.coord, in_swizzle);

   auto sampler = get_samplerr_id(instr->sampler_index, src.sampler_deref);
   assert(!sampler.indirect && "Indirect sampler selection not yet supported");

   auto dst = make_dest(*instr);
   auto irt = new TexInstruction(tex_op, dst, tex_src, sampler.id,
                                 sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);
   if (instr->is_array)
      handle_array_index(*instr, tex_src, irt);

   set_rect_coordinate_flags(instr, irt);
   set_offsets(irt, src.offset);

   emit_instruction(irt);
   return true;
}

bool EmitTexInstruction::emit_tex_txs(nir_tex_instr* instr, TexInputs& tex_src,
                                      const std::array<int,4>& dest_swz)
{
   std::array<PValue,4> dst_elms;
   std::array<PValue,4> src_elms;

   for (uint16_t i = 0; i < 4; ++i) {
      dst_elms[i] = from_nir(instr->dest, (i < instr->dest.ssa.num_components) ? i : 7);
   }

   GPRVector dst(dst_elms);

   if (instr->sampler_dim == GLSL_SAMPLER_DIM_BUF) {
      emit_instruction(new FetchInstruction(dst, PValue(new GPRValue(0, 7)),
                       instr->sampler_index + R600_MAX_CONST_BUFFERS,
                       bim_none));
   } else {
      for (uint16_t i = 0; i < 4; ++i)
         src_elms[i] =  tex_src.lod;
      GPRVector src(src_elms);

      auto sampler = get_samplerr_id(instr->sampler_index, tex_src.sampler_deref);
      assert(!sampler.indirect && "Indirect sampler selection not yet supported");

      auto ir = new TexInstruction(TexInstruction::get_resinfo, dst, src,
                                   sampler.id,
                                   sampler.id + R600_MAX_CONST_BUFFERS, tex_src.sampler_offset);
      ir->set_dest_swizzle(dest_swz);
      emit_instruction(ir);
   }

   return true;

}

bool EmitTexInstruction::emit_tex_texture_samples(nir_tex_instr* instr, TexInputs& src,
                                                  const std::array<int, 4> &dest_swz)
{
   GPRVector dest = vec_from_nir(instr->dest, nir_dest_num_components(instr->dest));
   GPRVector help{0,{4,4,4,4}};

   auto dyn_offset = PValue();
   int res_id = R600_MAX_CONST_BUFFERS + instr->sampler_index;

   auto ir = new TexInstruction(TexInstruction::get_nsampled, dest, help,
                                0, res_id, src.sampler_offset);
   ir->set_dest_swizzle(dest_swz);
   emit_instruction(ir);
   return true;
}

bool EmitTexInstruction::emit_tex_tg4(nir_tex_instr* instr, TexInputs& src)
{
   r600::sfn_log << SfnLog::instr << "emit '"
                 << *reinterpret_cast<nir_instr*>(instr)
                 << "' (" << __func__ << ")\n";

   TexInstruction *set_ofs = nullptr;

   auto tex_op = TexInstruction::gather4;

   if (instr->is_shadow)  {
      emit_instruction(new AluInstruction(op1_mov, src.coord.reg_i(3), src.comperator,
                       {alu_last_instr, alu_write}));
      tex_op = TexInstruction::gather4_c;
   }

   auto sampler = get_samplerr_id(instr->sampler_index, src.sampler_deref);
   assert(!sampler.indirect && "Indirect sampler selection not yet supported");

   bool literal_offset = false;
   if (src.offset) {
      literal_offset =  src.offset->is_ssa && get_literal_register(*src.offset);
      r600::sfn_log << SfnLog::tex << " really have offsets and they are " <<
                       (literal_offset ? "literal" : "varying") <<
                       "\n";

      if (!literal_offset) {
         GPRVector::Swizzle swizzle = {4,4,4,4};
         for (unsigned i = 0; i < instr->coord_components; ++i)
            swizzle[i] = i;

         int noffsets = instr->coord_components;
         if (instr->is_array)
            --noffsets;

         auto ofs = vec_from_nir_with_fetch_constant(*src.offset,
                                                     ( 1 << noffsets) - 1,
                                                     swizzle);
         GPRVector dummy(0, {7,7,7,7});
         tex_op = (tex_op == TexInstruction::gather4_c) ?
                     TexInstruction::gather4_c_o : TexInstruction::gather4_o;

         set_ofs = new TexInstruction(TexInstruction::set_offsets, dummy,
                                           ofs, sampler.id,
                                      sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);
         set_ofs->set_dest_swizzle({7,7,7,7});
      }
   }


   /* pre CAYMAN needs swizzle */
   auto dst = make_dest(*instr);
   auto irt = new TexInstruction(tex_op, dst, src.coord, sampler.id,
                                 sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);

   irt->set_dest_swizzle({1,2,0,3});
   irt->set_gather_comp(instr->component);

   if (instr->is_array)
      handle_array_index(*instr, src.coord, irt);

   if (literal_offset) {
      r600::sfn_log << SfnLog::tex << "emit literal offsets\n";
      set_offsets(irt, src.offset);
   }

   set_rect_coordinate_flags(instr, irt);

   if (set_ofs)
      emit_instruction(set_ofs);

   emit_instruction(irt);
   return true;
}

bool EmitTexInstruction::emit_cube_tg4(nir_tex_instr* instr, TexInputs& tex_src)
{
   std::array<PValue, 4> v;
   for (int i = 0; i < 4; ++i)
      v[i] = from_nir(instr->dest, i);

   auto tex_op = TexInstruction::gather4;
   GPRVector cubed(v);
   emit_cube_prep(tex_src.coord, cubed, instr->is_array);

   std::array<PValue,4> dst_elms;
   std::array<PValue,4> src_elms;

   const uint16_t lookup[4] = {1, 0, 3, 2};
   for (uint16_t i = 0; i < 4; ++i) {
      dst_elms[i] = v[i];
      src_elms[i] = v[lookup[i]];
   }

   if (instr->is_shadow)  {
      emit_instruction(new AluInstruction(op1_mov, src_elms[3], tex_src.comperator,
                       {alu_last_instr, alu_write}));
      tex_op = TexInstruction::gather4_c;
   }

   GPRVector dst(dst_elms);
   GPRVector src(src_elms);

   auto sampler = get_samplerr_id(instr->sampler_index, tex_src.sampler_deref);
   assert(!sampler.indirect && "Indirect sampler selection not yet supported");

   auto tir = new TexInstruction(tex_op, dst, src, sampler.id,
                                 sampler.id + R600_MAX_CONST_BUFFERS, tex_src.sampler_offset);

   tir->set_gather_comp(instr->component);

   tir->set_dest_swizzle({1, 2, 0, 3});

   if (instr->is_array)
      tir->set_flag(TexInstruction::z_unnormalized);

   emit_instruction(tir);
   return true;
}

bool EmitTexInstruction::emit_tex_txf_ms(nir_tex_instr* instr, TexInputs& src)
{
   assert(instr->src[0].src.is_ssa);

   r600::sfn_log << SfnLog::instr << "emit '"
                 << *reinterpret_cast<nir_instr*>(instr)
                 << "' (" << __func__ << ")\n";

   auto sampler = get_samplerr_id(instr->sampler_index, src.sampler_deref);
   assert(!sampler.indirect && "Indirect sampler selection not yet supported");

   int sample_id = allocate_temp_register();

   GPRVector sample_id_dest(sample_id, {0,7,7,7});
   PValue help(new GPRValue(sample_id, 1));

   /* FIXME: Texture destination registers must be handled differently,
    * because the swizzle identfies which source componnet has to be written
    * at a certain position, and the target register is actually different.
    * At this point we just add a helper register, but for later work (scheduling
    * and optimization on the r600 IR level, this needs to be implemented
    * differently */


   emit_instruction(new AluInstruction(op1_mov, src.coord.reg_i(3),
                                       src.ms_index,
                                       {alu_write, alu_last_instr}));

   auto tex_sample_id_ir = new TexInstruction(TexInstruction::ld, sample_id_dest, src.coord,
                                              sampler.id,
                                              sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);
   tex_sample_id_ir->set_flag(TexInstruction::x_unnormalized);
   tex_sample_id_ir->set_flag(TexInstruction::y_unnormalized);
   tex_sample_id_ir->set_flag(TexInstruction::z_unnormalized);
   tex_sample_id_ir->set_flag(TexInstruction::w_unnormalized);
   tex_sample_id_ir->set_inst_mode(1);

   emit_instruction(tex_sample_id_ir);

   emit_instruction(new AluInstruction(op2_mullo_int, help,
                                       {src.ms_index, PValue(new LiteralValue(4))},
                                       {alu_write, alu_last_instr}));

   emit_instruction(new AluInstruction(op2_lshr_int, src.coord.reg_i(3),
                                       {sample_id_dest.reg_i(0), help},
                                       {alu_write, alu_last_instr}));

   emit_instruction(new AluInstruction(op2_and_int, src.coord.reg_i(3),
                                       {src.coord.reg_i(3), PValue(new LiteralValue(15))},
                                       {alu_write, alu_last_instr}));

   auto dst = make_dest(*instr);

   /* txf doesn't need rounding for the array index, but 1D has the array index
    * in the z component */
   if (instr->is_array && instr->sampler_dim == GLSL_SAMPLER_DIM_1D)
      src.coord.set_reg_i(2, src.coord.reg_i(1));

   auto tex_ir = new TexInstruction(TexInstruction::ld, dst, src.coord,
                                    sampler.id,
                                    sampler.id + R600_MAX_CONST_BUFFERS, src.sampler_offset);


   if (src.offset) {
      assert(src.offset->is_ssa);
      AluInstruction *ir = nullptr;
      for (unsigned i = 0; i < src.offset->ssa->num_components; ++i) {
         ir = new AluInstruction(op2_add_int, src.coord.reg_i(i),
                  {src.coord.reg_i(i), from_nir(*src.offset, i, i)}, {alu_write});
         emit_instruction(ir);
      }
      if (ir)
         ir->set_flag(alu_last_instr);
   }

   emit_instruction(tex_ir);
   return true;
}

bool EmitTexInstruction::get_inputs(const nir_tex_instr& instr, TexInputs &src)
{
   sfn_log << SfnLog::tex << "Get Inputs with " << instr.coord_components << " components\n";

   unsigned grad_components = instr.coord_components;
   if (instr.is_array)
      --grad_components;


   src.offset = nullptr;
   bool retval = true;
   for (unsigned i = 0; i < instr.num_srcs; ++i) {
      switch (instr.src[i].src_type) {
      case nir_tex_src_bias:
         src.bias = from_nir(instr.src[i], 0);
         break;

      case nir_tex_src_coord: {
         src.coord = vec_from_nir_with_fetch_constant(instr.src[i].src,
                                                      (1 << instr.coord_components) - 1,
         {0,1,2,3});
      } break;
      case nir_tex_src_comparator:
         src.comperator = from_nir(instr.src[i], 0);
         break;
      case nir_tex_src_ddx: {
         sfn_log << SfnLog::tex << "Get DDX ";
         src.ddx = vec_from_nir_with_fetch_constant(instr.src[i].src,
                                                    (1 << grad_components) - 1,
                                                    swizzle_from_comps(grad_components));
         sfn_log << SfnLog::tex << src.ddx << "\n";
      } break;
      case nir_tex_src_ddy:{
         sfn_log << SfnLog::tex << "Get DDY ";
         src.ddy = vec_from_nir_with_fetch_constant(instr.src[i].src,
                                                    (1 << grad_components) - 1,
                                                    swizzle_from_comps(grad_components));
         sfn_log << SfnLog::tex << src.ddy << "\n";
      }  break;
      case nir_tex_src_lod:
         src.lod = from_nir_with_fetch_constant(instr.src[i].src, 0);
         break;
      case nir_tex_src_offset:
         sfn_log << SfnLog::tex << "  -- Find offset\n";
         src.offset = &instr.src[i].src;
         break;
      case nir_tex_src_sampler_deref:
         src.sampler_deref = get_deref_location(instr.src[i].src);
         break;
      case nir_tex_src_texture_deref:
         src.texture_deref = get_deref_location(instr.src[i].src);
         break;
      case nir_tex_src_ms_index:
         src.ms_index = from_nir(instr.src[i], 0);
         break;
      case nir_tex_src_texture_offset:
         src.texture_offset = from_nir(instr.src[i], 0);
         break;
      case nir_tex_src_sampler_offset:
         src.sampler_offset = from_nir(instr.src[i], 0);
         break;
      case nir_tex_src_plane:
      case nir_tex_src_projector:
      case nir_tex_src_min_lod:
      case nir_tex_src_ms_mcs:
      default:
         sfn_log << SfnLog::tex << "Texture source type " <<  instr.src[i].src_type << " not supported\n";
         retval = false;
      }
   }
   return retval;
}

GPRVector EmitTexInstruction::make_dest(nir_tex_instr& instr)
{
   int num_dest_components = instr.dest.is_ssa ? instr.dest.ssa.num_components :
                                                 instr.dest.reg.reg->num_components;
   std::array<PValue,4> dst_elms;
   for (uint16_t i = 0; i < 4; ++i)
      dst_elms[i] = from_nir(instr.dest, (i < num_dest_components) ? i : 7);
   return GPRVector(dst_elms);
}


GPRVector EmitTexInstruction::make_dest(nir_tex_instr& instr,
                                        const std::array<int, 4>& swizzle)
{
   int num_dest_components = instr.dest.is_ssa ? instr.dest.ssa.num_components :
                                                 instr.dest.reg.reg->num_components;
   std::array<PValue,4> dst_elms;
   for (uint16_t i = 0; i < 4; ++i) {
      int k = swizzle[i];
      dst_elms[i] = from_nir(instr.dest, (k < num_dest_components) ? k : 7);
   }
   return GPRVector(dst_elms);
}

void EmitTexInstruction::set_rect_coordinate_flags(nir_tex_instr* instr,
                                                   TexInstruction* ir) const
{
   if (instr->sampler_dim == GLSL_SAMPLER_DIM_RECT) {
      ir->set_flag(TexInstruction::x_unnormalized);
      ir->set_flag(TexInstruction::y_unnormalized);
   }
}

void EmitTexInstruction::set_offsets(TexInstruction* ir, nir_src *offset)
{
   if (!offset)
      return;

   assert(offset->is_ssa);
   auto literal = get_literal_register(*offset);
   assert(literal);

   for (int i = 0; i < offset->ssa->num_components; ++i) {
      ir->set_offset(i, literal->value[i].i32);
   }
}

void EmitTexInstruction::handle_array_index(const nir_tex_instr& instr, const GPRVector& src, TexInstruction *ir)
{
   int src_idx = instr.sampler_dim == GLSL_SAMPLER_DIM_1D ? 1 : 2;
   emit_instruction(new AluInstruction(op1_rndne, src.reg_i(2), src.reg_i(src_idx),
                                       {alu_last_instr, alu_write}));
   ir->set_flag(TexInstruction::z_unnormalized);
}

EmitTexInstruction::SamplerId
EmitTexInstruction::get_samplerr_id(int sampler_id, const nir_variable *deref)
{
   EmitTexInstruction::SamplerId result = {sampler_id, false};

   if (deref) {
      assert(glsl_type_is_sampler(deref->type));
      result.id = deref->data.binding;
   }
   return result;
}

EmitTexInstruction::TexInputs::TexInputs():
   sampler_deref(nullptr),
   texture_deref(nullptr),
   offset(nullptr)
{
}

}
