/*
 * Mesa 3-D graphics library
 *
 * Copyright (C) 2014 LunarG, Inc.
 *
 * 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
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * 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 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 NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS 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.
 *
 * Authors:
 *    Chia-I Wu <olv@lunarg.com>
 */

#ifndef ILO_IMAGE_H
#define ILO_IMAGE_H

#include "genhw/genhw.h"

#include "ilo_core.h"
#include "ilo_dev.h"

/*
 * From the Ivy Bridge PRM, volume 4 part 1, page 75:
 *
 *     "(MIP Count / LOD) representing [1,15] MIP levels"
 */
#define ILO_IMAGE_MAX_LEVEL_COUNT 15

enum ilo_image_aux_type {
   ILO_IMAGE_AUX_NONE,
   ILO_IMAGE_AUX_HIZ,
   ILO_IMAGE_AUX_MCS,
};

enum ilo_image_walk_type {
   /*
    * LODs of each array layer are first packed together in MIPLAYOUT_BELOW.
    * Array layers are then stacked together vertically.
    *
    * This can be used for mipmapped 2D textures.
    */
   ILO_IMAGE_WALK_LAYER,

   /*
    * Array layers of each LOD are first stacked together vertically and
    * tightly.  LODs are then packed together in MIPLAYOUT_BELOW with each LOD
    * starting at page boundaries.
    *
    * This is usually used for non-mipmapped 2D textures, as multiple LODs are
    * not supported natively.
    */
   ILO_IMAGE_WALK_LOD,

   /*
    * 3D slices of each LOD are first packed together horizontally and tightly
    * with wrapping.  LODs are then stacked together vertically and tightly.
    *
    * This is used for 3D textures.
    */
   ILO_IMAGE_WALK_3D,
};

struct ilo_image_info {
   enum gen_surface_type type;

   enum gen_surface_format format;
   bool interleaved_stencil;
   bool is_integer;
   /* width, height and size of pixel blocks */
   bool compressed;
   unsigned block_width;
   unsigned block_height;
   unsigned block_size;

   /* image size */
   uint16_t width;
   uint16_t height;
   uint16_t depth;
   uint16_t array_size;
   uint8_t level_count;
   uint8_t sample_count;

   /* disable optional aux */
   bool aux_disable;

   /* tilings to consider, if any bit is set */
   uint8_t valid_tilings;

   /*
    * prefer GEN6_TILING_NONE when the (estimated) image size exceeds the
    * threshold; ignored when zero
    */
   uint32_t prefer_linear_threshold;

   /* force a stride when non-zero */
   uint32_t force_bo_stride;

   bool bind_surface_sampler;
   bool bind_surface_dp_render;
   bool bind_surface_dp_typed;
   bool bind_zs;
   bool bind_scanout;
   bool bind_cursor;
};

/*
 * When the walk type is ILO_IMAGE_WALK_LAYER, there is only a slice in each
 * LOD and this is used to describe LODs in the first array layer.  Otherwise,
 * there can be multiple slices in each LOD and this is used to describe the
 * first slice in each LOD.
 */
struct ilo_image_lod {
   /* physical position in pixels */
   unsigned x;
   unsigned y;

   /* physical size of a slice in pixels */
   unsigned slice_width;
   unsigned slice_height;
};

/**
 * Texture layout.
 */
struct ilo_image {
   enum gen_surface_type type;

   enum gen_surface_format format;
   bool interleaved_stencil;

   /* size, format, etc for programming hardware states */
   unsigned width0;
   unsigned height0;
   unsigned depth0;
   unsigned array_size;
   unsigned level_count;
   unsigned sample_count;

   /*
    * width, height, and size of pixel blocks for conversion between pixel
    * positions and memory offsets
    */
   unsigned block_width;
   unsigned block_height;
   unsigned block_size;

   enum ilo_image_walk_type walk;
   bool interleaved_samples;

   enum gen_surface_tiling tiling;

   /* physical LOD slice alignments */
   unsigned align_i;
   unsigned align_j;

   struct ilo_image_lod lods[ILO_IMAGE_MAX_LEVEL_COUNT];

   /* physical layer height for ILO_IMAGE_WALK_LAYER */
   unsigned walk_layer_height;

   /* distance in bytes between two pixel block rows */
   unsigned bo_stride;
   /* number of pixel block rows */
   unsigned bo_height;

   bool scanout;

   struct {
      enum ilo_image_aux_type type;

      /* bitmask of levels that can use aux */
      unsigned enables;

      /* LOD offsets for ILO_IMAGE_WALK_LOD */
      unsigned walk_lod_offsets[ILO_IMAGE_MAX_LEVEL_COUNT];

      unsigned walk_layer_height;
      unsigned bo_stride;
      unsigned bo_height;
   } aux;
};

bool
ilo_image_init(struct ilo_image *img,
               const struct ilo_dev *dev,
               const struct ilo_image_info *info);

static inline bool
ilo_image_can_enable_aux(const struct ilo_image *img, unsigned level)
{
   return (img->aux.enables & (1 << level));
}

/**
 * Convert from pixel position to 2D memory offset.
 */
static inline void
ilo_image_pos_to_mem(const struct ilo_image *img,
                     unsigned pos_x, unsigned pos_y,
                     unsigned *mem_x, unsigned *mem_y)
{
   assert(pos_x % img->block_width == 0);
   assert(pos_y % img->block_height == 0);

   *mem_x = pos_x / img->block_width * img->block_size;
   *mem_y = pos_y / img->block_height;
}

/**
 * Convert from 2D memory offset to linear offset.
 */
static inline unsigned
ilo_image_mem_to_linear(const struct ilo_image *img,
                        unsigned mem_x, unsigned mem_y)
{
   return mem_y * img->bo_stride + mem_x;
}

/**
 * Convert from 2D memory offset to raw offset.
 */
static inline unsigned
ilo_image_mem_to_raw(const struct ilo_image *img,
                     unsigned mem_x, unsigned mem_y)
{
   unsigned tile_w, tile_h;

   switch (img->tiling) {
   case GEN6_TILING_NONE:
      tile_w = 1;
      tile_h = 1;
      break;
   case GEN6_TILING_X:
      tile_w = 512;
      tile_h = 8;
      break;
   case GEN6_TILING_Y:
      tile_w = 128;
      tile_h = 32;
      break;
   case GEN8_TILING_W:
      tile_w = 64;
      tile_h = 64;
      break;
   default:
      assert(!"unknown tiling");
      tile_w = 1;
      tile_h = 1;
      break;
   }

   assert(mem_x % tile_w == 0);
   assert(mem_y % tile_h == 0);

   return mem_y * img->bo_stride + mem_x * tile_h;
}

/**
 * Return the stride, in bytes, between slices within a level.
 */
static inline unsigned
ilo_image_get_slice_stride(const struct ilo_image *img, unsigned level)
{
   unsigned h;

   switch (img->walk) {
   case ILO_IMAGE_WALK_LAYER:
      h = img->walk_layer_height;
      break;
   case ILO_IMAGE_WALK_LOD:
      h = img->lods[level].slice_height;
      break;
   case ILO_IMAGE_WALK_3D:
      if (level == 0) {
         h = img->lods[0].slice_height;
         break;
      }
      /* fall through */
   default:
      assert(!"no single stride to walk across slices");
      h = 0;
      break;
   }

   assert(h % img->block_height == 0);

   return (h / img->block_height) * img->bo_stride;
}

/**
 * Return the physical size, in bytes, of a slice in a level.
 */
static inline unsigned
ilo_image_get_slice_size(const struct ilo_image *img, unsigned level)
{
   const unsigned w = img->lods[level].slice_width;
   const unsigned h = img->lods[level].slice_height;

   assert(w % img->block_width == 0);
   assert(h % img->block_height == 0);

   return (w / img->block_width * img->block_size) *
      (h / img->block_height);
}

/**
 * Return the pixel position of a slice.
 */
static inline void
ilo_image_get_slice_pos(const struct ilo_image *img,
                        unsigned level, unsigned slice,
                        unsigned *x, unsigned *y)
{
   switch (img->walk) {
   case ILO_IMAGE_WALK_LAYER:
      *x = img->lods[level].x;
      *y = img->lods[level].y + img->walk_layer_height * slice;
      break;
   case ILO_IMAGE_WALK_LOD:
      *x = img->lods[level].x;
      *y = img->lods[level].y + img->lods[level].slice_height * slice;
      break;
   case ILO_IMAGE_WALK_3D:
      {
         /* slices are packed horizontally with wrapping */
         const unsigned sx = slice & ((1 << level) - 1);
         const unsigned sy = slice >> level;

         assert(slice < u_minify(img->depth0, level));

         *x = img->lods[level].x + img->lods[level].slice_width * sx;
         *y = img->lods[level].y + img->lods[level].slice_height * sy;
      }
      break;
   default:
      assert(!"unknown img walk type");
      *x = 0;
      *y = 0;
      break;
   }

   /* should not exceed the bo size */
   assert(*y + img->lods[level].slice_height <=
         img->bo_height * img->block_height);
}

#endif /* ILO_IMAGE_H */
