/**************************************************************************
 * 
 * Copyright 2006 Tungsten Graphics, Inc., Cedar Park, Texas.
 * All Rights Reserved.
 * 
 * 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, 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 TUNGSTEN GRAPHICS AND/OR ITS 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.
 * 
 **************************************************************************/
 /*
  * Authors:
  *   Keith Whitwell <keith@tungstengraphics.com>
  *   Michel Dänzer <michel@tungstengraphics.com>
  */

#include "pipe/p_state.h"
#include "pipe/p_context.h"
#include "pipe/p_defines.h"
#include "util/u_inlines.h"
#include "util/u_format.h"
#include "util/u_math.h"
#include "util/u_memory.h"
#include "util/u_rect.h"

#include "i915_context.h"
#include "i915_resource.h"
#include "i915_screen.h"
#include "i915_winsys.h"
#include "i915_debug.h"


#define DEBUG_TEXTURES 0

/*
 * Helper function and arrays
 */


/**
 * Initial offset for Cube map.
 */
static const int initial_offsets[6][2] = {
   [PIPE_TEX_FACE_POS_X] = {0, 0},
   [PIPE_TEX_FACE_POS_Y] = {1, 0},
   [PIPE_TEX_FACE_POS_Z] = {1, 1},
   [PIPE_TEX_FACE_NEG_X] = {0, 2},
   [PIPE_TEX_FACE_NEG_Y] = {1, 2},
   [PIPE_TEX_FACE_NEG_Z] = {1, 3},
};

/**
 * Step offsets for Cube map.
 */
static const int step_offsets[6][2] = {
   [PIPE_TEX_FACE_POS_X] = { 0, 2},
   [PIPE_TEX_FACE_POS_Y] = {-1, 2},
   [PIPE_TEX_FACE_POS_Z] = {-1, 1},
   [PIPE_TEX_FACE_NEG_X] = { 0, 2},
   [PIPE_TEX_FACE_NEG_Y] = {-1, 2},
   [PIPE_TEX_FACE_NEG_Z] = {-1, 1},
};

/**
 * For compressed level 2
 */
static const int bottom_offsets[6] = {
   [PIPE_TEX_FACE_POS_X] = 16 + 0 * 8,
   [PIPE_TEX_FACE_POS_Y] = 16 + 1 * 8,
   [PIPE_TEX_FACE_POS_Z] = 16 + 2 * 8,
   [PIPE_TEX_FACE_NEG_X] = 16 + 3 * 8,
   [PIPE_TEX_FACE_NEG_Y] = 16 + 4 * 8,
   [PIPE_TEX_FACE_NEG_Z] = 16 + 5 * 8,
};

static INLINE unsigned
align_nblocksx(enum pipe_format format, unsigned width, unsigned align_to)
{
   return align(util_format_get_nblocksx(format, width), align_to);
}

static INLINE unsigned
align_nblocksy(enum pipe_format format, unsigned width, unsigned align_to)
{
   return align(util_format_get_nblocksy(format, width), align_to);
}

static INLINE unsigned
get_pot_stride(enum pipe_format format, unsigned width)
{
   return util_next_power_of_two(util_format_get_stride(format, width));
}

static INLINE const char*
get_tiling_string(enum i915_winsys_buffer_tile tile)
{
   switch(tile) {
   case I915_TILE_NONE:
      return "none";
   case I915_TILE_X:
      return "x";
   case I915_TILE_Y:
      return "y";
   default:
      assert(FALSE);
      return "?";
   }
}


/*
 * More advanced helper funcs
 */


static void
i915_texture_set_level_info(struct i915_texture *tex,
                            unsigned level, unsigned nr_images)
{
   assert(level < Elements(tex->nr_images));
   assert(nr_images);
   assert(!tex->image_offset[level]);

   tex->nr_images[level] = nr_images;
   tex->image_offset[level] = MALLOC(nr_images * sizeof(struct offset_pair));
   tex->image_offset[level][0].nblocksx = 0;
   tex->image_offset[level][0].nblocksy = 0;
}

INLINE unsigned i915_texture_offset(struct i915_texture *tex,
                                    unsigned level, unsigned layer)
{
   unsigned x, y;
   x = tex->image_offset[level][layer].nblocksx
      * util_format_get_blocksize(tex->b.b.format);
   y = tex->image_offset[level][layer].nblocksy;

   return y * tex->stride + x;
}

static void
i915_texture_set_image_offset(struct i915_texture *tex,
                              unsigned level, unsigned img,
                              unsigned nblocksx, unsigned nblocksy)
{
   /* for the first image and level make sure offset is zero */
   assert(!(img == 0 && level == 0) || (nblocksx == 0 && nblocksy == 0));
   assert(img < tex->nr_images[level]);

   tex->image_offset[level][img].nblocksx = nblocksx;
   tex->image_offset[level][img].nblocksy = nblocksy;

#if DEBUG_TEXTURES
   debug_printf("%s: %p level %u, img %u (%u, %u)\n", __FUNCTION__,
                tex, level, img, x, y);
#endif
}

static enum i915_winsys_buffer_tile
i915_texture_tiling(struct i915_screen *is, struct i915_texture *tex)
{
   if (!is->debug.tiling)
      return I915_TILE_NONE;

   if (tex->b.b.target == PIPE_TEXTURE_1D)
      return I915_TILE_NONE;

   if (util_format_is_s3tc(tex->b.b.format))
      return I915_TILE_X;

   if (is->debug.use_blitter)
      return I915_TILE_X;
   else
      return I915_TILE_Y;
}


/*
 * Shared layout functions
 */


/**
 * Special case to deal with scanout textures.
 */
static boolean
i9x5_scanout_layout(struct i915_texture *tex)
{
   struct pipe_resource *pt = &tex->b.b;

   if (pt->last_level > 0 || util_format_get_blocksize(pt->format) != 4)
      return FALSE;

   i915_texture_set_level_info(tex, 0, 1);
   i915_texture_set_image_offset(tex, 0, 0, 0, 0);

   if (pt->width0 >= 240) {
      tex->stride = align(util_format_get_stride(pt->format, pt->width0), 64);
      tex->total_nblocksy = align_nblocksy(pt->format, pt->height0, 8);
      tex->tiling = I915_TILE_X;
   /* special case for cursors */
   } else if (pt->width0 == 64 && pt->height0 == 64) {
      tex->stride = get_pot_stride(pt->format, pt->width0);
      tex->total_nblocksy = align_nblocksy(pt->format, pt->height0, 8);
   } else {
      return FALSE;
   }

#if DEBUG_TEXTURE
   debug_printf("%s size: %d,%d,%d offset %d,%d (0x%x)\n", __FUNCTION__,
      pt->width0, pt->height0, util_format_get_blocksize(pt->format),
      tex->stride, tex->total_nblocksy, tex->stride * tex->total_nblocksy);
#endif

   return TRUE;
}

/**
 * Special case to deal with shared textures.
 */
static boolean
i9x5_display_target_layout(struct i915_texture *tex)
{
   struct pipe_resource *pt = &tex->b.b;

   if (pt->last_level > 0 || util_format_get_blocksize(pt->format) != 4)
      return FALSE;

   /* fallback to normal textures for small textures */
   if (pt->width0 < 240)
      return FALSE;

   i915_texture_set_level_info(tex, 0, 1);
   i915_texture_set_image_offset(tex, 0, 0, 0, 0);

   tex->stride = align(util_format_get_stride(pt->format, pt->width0), 64);
   tex->total_nblocksy = align_nblocksy(pt->format, pt->height0, 8);
   tex->tiling = I915_TILE_X;

#if DEBUG_TEXTURE
   debug_printf("%s size: %d,%d,%d offset %d,%d (0x%x)\n", __FUNCTION__,
      pt->width0, pt->height0, util_format_get_blocksize(pt->format),
      tex->stride, tex->total_nblocksy, tex->stride * tex->total_nblocksy);
#endif

   return TRUE;
}

/**
 * Helper function for special layouts
 */
static boolean
i9x5_special_layout(struct i915_texture *tex)
{
   struct pipe_resource *pt = &tex->b.b;

   /* Scanouts needs special care */
   if (pt->bind & PIPE_BIND_SCANOUT)
      if (i9x5_scanout_layout(tex))
         return TRUE;

   /* Shared buffers needs to be compatible with X servers
    *
    * XXX: need a better name than shared for this if it is to be part
    * of core gallium, and probably move the flag to resource.flags,
    * rather than bindings.
    */
   if (pt->bind & (PIPE_BIND_SHARED | PIPE_BIND_DISPLAY_TARGET))
      if (i9x5_display_target_layout(tex))
         return TRUE;

   return FALSE;
}

/**
 * Cube layout used on i915 and for non-compressed textures on i945.
 */
static void
i9x5_texture_layout_cube(struct i915_texture *tex)
{
   struct pipe_resource *pt = &tex->b.b;
   const unsigned nblocks = util_format_get_nblocksx(pt->format, pt->width0);
   unsigned level;
   unsigned face;

   assert(pt->width0 == pt->height0); /* cubemap images are square */

   /* double pitch for cube layouts */
   tex->stride = align(nblocks * util_format_get_blocksize(pt->format) * 2, 4);
   tex->total_nblocksy = nblocks * 4;

   for (level = 0; level <= pt->last_level; level++)
      i915_texture_set_level_info(tex, level, 6);

   for (face = 0; face < 6; face++) {
      unsigned x = initial_offsets[face][0] * nblocks;
      unsigned y = initial_offsets[face][1] * nblocks;
      unsigned d = nblocks;

      for (level = 0; level <= pt->last_level; level++) {
         i915_texture_set_image_offset(tex, level, face, x, y);
         d >>= 1;
         x += step_offsets[face][0] * d;
         y += step_offsets[face][1] * d;
      }
   }
}


/*
 * i915 layout functions
 */


static void
i915_texture_layout_2d(struct i915_texture *tex)
{
   struct pipe_resource *pt = &tex->b.b;
   unsigned level;
   unsigned width = pt->width0;
   unsigned height = pt->height0;
   unsigned nblocksy = util_format_get_nblocksy(pt->format, pt->width0);
   unsigned align_y = 2;

   if (util_format_is_s3tc(pt->format))
      align_y = 1;

   tex->stride = align(util_format_get_stride(pt->format, pt->width0), 4);
   tex->total_nblocksy = 0;

   for (level = 0; level <= pt->last_level; level++) {
      i915_texture_set_level_info(tex, level, 1);
      i915_texture_set_image_offset(tex, level, 0, 0, tex->total_nblocksy);

      tex->total_nblocksy += nblocksy;

      width = u_minify(width, 1);
      height = u_minify(height, 1);
      nblocksy = align_nblocksy(pt->format, height, align_y);
   }
}

static void
i915_texture_layout_3d(struct i915_texture *tex)
{
   struct pipe_resource *pt = &tex->b.b;
   unsigned level;

   unsigned width = pt->width0;
   unsigned height = pt->height0;
   unsigned depth = pt->depth0;
   unsigned nblocksy = util_format_get_nblocksy(pt->format, pt->height0);
   unsigned stack_nblocksy = 0;

   /* Calculate the size of a single slice. 
    */
   tex->stride = align(util_format_get_stride(pt->format, pt->width0), 4);

   /* XXX: hardware expects/requires 9 levels at minimum.
    */
   for (level = 0; level <= MAX2(8, pt->last_level); level++) {
      i915_texture_set_level_info(tex, level, depth);

      stack_nblocksy += MAX2(2, nblocksy);

      width = u_minify(width, 1);
      height = u_minify(height, 1);
      nblocksy = util_format_get_nblocksy(pt->format, height);
   }

   /* Fixup depth image_offsets: 
    */
   for (level = 0; level <= pt->last_level; level++) {
      unsigned i;
      for (i = 0; i < depth; i++) 
         i915_texture_set_image_offset(tex, level, i, 0, i * stack_nblocksy);

      depth = u_minify(depth, 1);
   }

   /* Multiply slice size by texture depth for total size.  It's
    * remarkable how wasteful of memory the i915 texture layouts
    * are.  They are largely fixed in the i945.
    */
   tex->total_nblocksy = stack_nblocksy * pt->depth0;
}

static boolean
i915_texture_layout(struct i915_texture * tex)
{
   switch (tex->b.b.target) {
   case PIPE_TEXTURE_1D:
   case PIPE_TEXTURE_2D:
   case PIPE_TEXTURE_RECT:
      if (!i9x5_special_layout(tex))
         i915_texture_layout_2d(tex);
      break;
   case PIPE_TEXTURE_3D:
      i915_texture_layout_3d(tex);
      break;
   case PIPE_TEXTURE_CUBE:
      i9x5_texture_layout_cube(tex);
      break;
   default:
      assert(0);
      return FALSE;
   }

   return TRUE;
}


/*
 * i945 layout functions
 */


static void
i945_texture_layout_2d(struct i915_texture *tex)
{
   struct pipe_resource *pt = &tex->b.b;
   int align_x = 4, align_y = 2;
   unsigned level;
   unsigned x = 0;
   unsigned y = 0;
   unsigned width = pt->width0;
   unsigned height = pt->height0;
   unsigned nblocksx = util_format_get_nblocksx(pt->format, pt->width0);
   unsigned nblocksy = util_format_get_nblocksy(pt->format, pt->height0);

   if (util_format_is_s3tc(pt->format)) {
      align_x = 1;
      align_y = 1;
   }

   tex->stride = align(util_format_get_stride(pt->format, pt->width0), 4);

   /* May need to adjust pitch to accomodate the placement of
    * the 2nd mipmap level.  This occurs when the alignment
    * constraints of mipmap placement push the right edge of the
    * 2nd mipmap level out past the width of its parent.
    */
   if (pt->last_level > 0) {
      unsigned mip1_nblocksx =
         align_nblocksx(pt->format, u_minify(pt->width0, 1), align_x) +
         util_format_get_nblocksx(pt->format, u_minify(pt->width0, 2));

      if (mip1_nblocksx > nblocksx)
         tex->stride = mip1_nblocksx * util_format_get_blocksize(pt->format);
   }

   /* Pitch must be a whole number of dwords
    */
   tex->stride = align(tex->stride, 64);
   tex->total_nblocksy = 0;

   for (level = 0; level <= pt->last_level; level++) {
      i915_texture_set_level_info(tex, level, 1);
      i915_texture_set_image_offset(tex, level, 0, x, y);

      /* Because the images are packed better, the final offset
       * might not be the maximal one:
       */
      tex->total_nblocksy = MAX2(tex->total_nblocksy, y + nblocksy);

      /* Layout_below: step right after second mipmap level.
       */
      if (level == 1) {
         x += nblocksx;
      } else {
         y += nblocksy;
      }

      width  = u_minify(width, 1);
      height = u_minify(height, 1);
      nblocksx = align_nblocksx(pt->format, width, align_x);
      nblocksy = align_nblocksy(pt->format, height, align_y);
   }
}

static void
i945_texture_layout_3d(struct i915_texture *tex)
{
   struct pipe_resource *pt = &tex->b.b;
   unsigned width = pt->width0;
   unsigned height = pt->height0;
   unsigned depth = pt->depth0;
   unsigned nblocksy = util_format_get_nblocksy(pt->format, pt->width0);
   unsigned pack_x_pitch, pack_x_nr;
   unsigned pack_y_pitch;
   unsigned level;

   tex->stride = align(util_format_get_stride(pt->format, pt->width0), 4);
   tex->total_nblocksy = 0;

   pack_y_pitch = MAX2(nblocksy, 2);
   pack_x_pitch = tex->stride / util_format_get_blocksize(pt->format);
   pack_x_nr = 1;

   for (level = 0; level <= pt->last_level; level++) {
      int x = 0;
      int y = 0;
      unsigned q, j;

      i915_texture_set_level_info(tex, level, depth);

      for (q = 0; q < depth;) {
         for (j = 0; j < pack_x_nr && q < depth; j++, q++) {
            i915_texture_set_image_offset(tex, level, q, x, y + tex->total_nblocksy);
            x += pack_x_pitch;
         }

         x = 0;
         y += pack_y_pitch;
      }

      tex->total_nblocksy += y;

      if (pack_x_pitch > 4) {
         pack_x_pitch >>= 1;
         pack_x_nr <<= 1;
         assert(pack_x_pitch * pack_x_nr * util_format_get_blocksize(pt->format) <= tex->stride);
      }

      if (pack_y_pitch > 2) {
         pack_y_pitch >>= 1;
      }

      width = u_minify(width, 1);
      height = u_minify(height, 1);
      depth = u_minify(depth, 1);
      nblocksy = util_format_get_nblocksy(pt->format, height);
   }
}

static void
i945_texture_layout_cube(struct i915_texture *tex)
{
   struct pipe_resource *pt = &tex->b.b;
   const unsigned nblocks = util_format_get_nblocksx(pt->format, pt->width0);
   const unsigned dim = pt->width0;
   unsigned level;
   unsigned face;

   assert(pt->width0 == pt->height0); /* cubemap images are square */
   assert(util_next_power_of_two(pt->width0) == pt->width0); /* npot only */
   assert(util_format_is_s3tc(pt->format)); /* compressed only */

   /*
    * Depending on the size of the largest images, pitch can be
    * determined either by the old-style packing of cubemap faces,
    * or the final row of 4x4, 2x2 and 1x1 faces below this.
    *
    * 64  * 2 / 4 = 32
    * 14 * 2 = 28
    */
   if (pt->width0 >= 64)
      tex->stride = nblocks * 2 * util_format_get_blocksize(pt->format);
   else
      tex->stride = 14 * 2 * util_format_get_blocksize(pt->format);

   /*
    * Something similary apply for height as well.
    */
   if (pt->width0 >= 4)
      tex->total_nblocksy = nblocks * 4 + 1;
   else
      tex->total_nblocksy = 1;

   /* Set all the levels to effectively occupy the whole rectangular region */
   for (level = 0; level <= pt->last_level; level++)
      i915_texture_set_level_info(tex, level, 6);

   for (face = 0; face < 6; face++) {
      /* all calculations in pixels */
      unsigned total_height = tex->total_nblocksy * 4;
      unsigned x = initial_offsets[face][0] * dim;
      unsigned y = initial_offsets[face][1] * dim;
      unsigned d = dim;

      if (dim == 4 && face >= 4) {
         x = (face - 4) * 8;
         y = tex->total_nblocksy * 4 - 4; /* 4 = 1 block */
      } else if (dim < 4 && (face > 0)) {
         x = face * 8;
         y = total_height - 4;
      }

      for (level = 0; level <= pt->last_level; level++) {
         i915_texture_set_image_offset(tex, level, face,
                                       util_format_get_nblocksx(pt->format, x),
                                       util_format_get_nblocksy(pt->format, y));

         d >>= 1;

         switch (d) {
         case 4:
            switch (face) {
            case PIPE_TEX_FACE_POS_X:
            case PIPE_TEX_FACE_NEG_X:
               x += step_offsets[face][0] * d;
               y += step_offsets[face][1] * d;
               break;
            case PIPE_TEX_FACE_POS_Y:
            case PIPE_TEX_FACE_NEG_Y:
               y += 12;
               x -= 8;
               break;
            case PIPE_TEX_FACE_POS_Z:
            case PIPE_TEX_FACE_NEG_Z:
               y = total_height - 4;
               x = (face - 4) * 8;
               break;
            }
            break;
         case 2:
            y = total_height - 4;
            x = bottom_offsets[face];
            break;
         case 1:
            x += 48;
            break;
         default:
            x += step_offsets[face][0] * d;
            y += step_offsets[face][1] * d;
            break;
         }
      }
   }
}

static boolean
i945_texture_layout(struct i915_texture * tex)
{
   switch (tex->b.b.target) {
   case PIPE_TEXTURE_1D:
   case PIPE_TEXTURE_2D:
   case PIPE_TEXTURE_RECT:
      if (!i9x5_special_layout(tex))
         i945_texture_layout_2d(tex);
      break;
   case PIPE_TEXTURE_3D:
      i945_texture_layout_3d(tex);
      break;
   case PIPE_TEXTURE_CUBE:
      if (!util_format_is_s3tc(tex->b.b.format))
         i9x5_texture_layout_cube(tex);
      else
         i945_texture_layout_cube(tex);
      break;
   default:
      assert(0);
      return FALSE;
   }

   return TRUE;
}



/*
 * Screen texture functions
 */



static boolean
i915_texture_get_handle(struct pipe_screen * screen,
                        struct pipe_resource *texture,
                        struct winsys_handle *whandle)
{
   struct i915_screen *is = i915_screen(screen);
   struct i915_texture *tex = i915_texture(texture);
   struct i915_winsys *iws = is->iws;

   return iws->buffer_get_handle(iws, tex->buffer, whandle, tex->stride);
}


static void
i915_texture_destroy(struct pipe_screen *screen,
                     struct pipe_resource *pt)
{
   struct i915_texture *tex = i915_texture(pt);
   struct i915_winsys *iws = i915_screen(screen)->iws;
   uint i;

   if (tex->buffer)
      iws->buffer_destroy(iws, tex->buffer);

   for (i = 0; i < Elements(tex->image_offset); i++)
      if (tex->image_offset[i])
         FREE(tex->image_offset[i]);

   FREE(tex);
}

static struct pipe_transfer *
i915_texture_get_transfer(struct pipe_context *pipe,
                          struct pipe_resource *resource,
                          unsigned level,
                          unsigned usage,
                          const struct pipe_box *box)
{
   struct i915_context *i915 = i915_context(pipe);
   struct i915_texture *tex = i915_texture(resource);
   struct i915_transfer *transfer = util_slab_alloc(&i915->texture_transfer_pool);
   boolean use_staging_texture = FALSE;

   if (transfer == NULL)
      return NULL;

   transfer->b.resource = resource;
   transfer->b.level = level;
   transfer->b.usage = usage;
   transfer->b.box = *box;
   transfer->b.stride = tex->stride;
   transfer->staging_texture = NULL;
   /* XXX: handle depth textures everyhwere*/
   transfer->b.layer_stride = 0;
   transfer->b.data = NULL;

   /* only support textures we can render to, because we need that for u_blitter */
   if (i915->blitter &&
       i915_is_format_supported(NULL, /* screen */
                                transfer->b.resource->format,
                                0, /* target */
                                1, /* sample count */
                                PIPE_BIND_RENDER_TARGET) &&
       (usage & PIPE_TRANSFER_WRITE) &&
       !(usage & (PIPE_TRANSFER_READ | PIPE_TRANSFER_DONTBLOCK | PIPE_TRANSFER_UNSYNCHRONIZED)))
      use_staging_texture = TRUE;

   use_staging_texture = FALSE;

   if (use_staging_texture) {
      /* 
       * Allocate the untiled staging texture.
       * If the alloc fails, transfer->staging_texture is NULL and we fallback to a map() 
       */
      transfer->staging_texture = i915_texture_create(pipe->screen, resource, TRUE);
   }

   return (struct pipe_transfer*)transfer;
}

static void
i915_transfer_destroy(struct pipe_context *pipe,
                      struct pipe_transfer *transfer)
{
   struct i915_context *i915 = i915_context(pipe);
   struct i915_transfer *itransfer = (struct i915_transfer*)transfer;

   if ((itransfer->staging_texture) &&
       (transfer->usage & PIPE_TRANSFER_WRITE)) {
      struct pipe_box sbox;

      u_box_origin_2d(itransfer->b.box.width, itransfer->b.box.height, &sbox);
      pipe->resource_copy_region(pipe, itransfer->b.resource, itransfer->b.level,
                                   itransfer->b.box.x, itransfer->b.box.y, itransfer->b.box.z,
                                   itransfer->staging_texture,
                                   0, &sbox);
      pipe->flush(pipe, NULL);
      pipe_resource_reference(&itransfer->staging_texture, NULL);
   }

   util_slab_free(&i915->texture_transfer_pool, itransfer);
}

static void *
i915_texture_transfer_map(struct pipe_context *pipe,
                          struct pipe_transfer *transfer)
{
   struct i915_transfer *itransfer = (struct i915_transfer*)transfer;
   struct pipe_resource *resource = itransfer->b.resource;
   struct i915_texture *tex = NULL;
   struct i915_winsys *iws = i915_screen(pipe->screen)->iws;
   struct pipe_box *box = &itransfer->b.box;
   enum pipe_format format = resource->format;
   unsigned offset;
   char *map;

   if (resource->target != PIPE_TEXTURE_3D &&
       resource->target != PIPE_TEXTURE_CUBE)
      assert(box->z == 0);

   if (itransfer->staging_texture) {
      tex = i915_texture(itransfer->staging_texture);
   } else {
      /* TODO this is a sledgehammer */
      tex = i915_texture(resource);
      pipe->flush(pipe, NULL);
   }

   offset = i915_texture_offset(tex, itransfer->b.level, box->z);

   map = iws->buffer_map(iws, tex->buffer,
                         (itransfer->b.usage & PIPE_TRANSFER_WRITE) ? TRUE : FALSE);
   if (map == NULL) {
      return NULL;
   }

   return map + offset +
      box->y / util_format_get_blockheight(format) * itransfer->b.stride +
      box->x / util_format_get_blockwidth(format) * util_format_get_blocksize(format);
}

static void
i915_texture_transfer_unmap(struct pipe_context *pipe,
			    struct pipe_transfer *transfer)
{
   struct i915_transfer *itransfer = (struct i915_transfer*)transfer;
   struct i915_texture *tex = i915_texture(itransfer->b.resource);
   struct i915_winsys *iws = i915_screen(tex->b.b.screen)->iws;

   if (itransfer->staging_texture)
      tex = i915_texture(itransfer->staging_texture);

   iws->buffer_unmap(iws, tex->buffer);
}

static void i915_transfer_inline_write( struct pipe_context *pipe,
                                 struct pipe_resource *resource,
                                 unsigned level,
                                 unsigned usage,
                                 const struct pipe_box *box,
                                 const void *data,
                                 unsigned stride,
                                 unsigned layer_stride)
{
   struct pipe_transfer *transfer = NULL;
   struct i915_transfer *itransfer = NULL;
   const uint8_t *src_data = data;
   unsigned i;

   transfer = pipe->get_transfer(pipe,
                                 resource,
                                 level,
                                 usage,
                                 box );
   if (transfer == NULL)
      goto out;

   itransfer = (struct i915_transfer*)transfer;

   if (itransfer->staging_texture) {
      struct i915_texture *tex = i915_texture(itransfer->staging_texture);
      enum pipe_format format = tex->b.b.format;
      struct i915_winsys *iws = i915_screen(tex->b.b.screen)->iws;
      size_t offset;
      size_t size;

      offset = i915_texture_offset(tex, transfer->level, transfer->box.z);

      for (i = 0; i < box->depth; i++) {
         if (!tex->b.b.last_level &&
                     tex->b.b.width0 == transfer->box.width) {
             unsigned nby = util_format_get_nblocksy(format, transfer->box.y);
             assert(!offset);
             assert(!transfer->box.x);
             assert(tex->stride == transfer->stride);

             offset += tex->stride * nby;
             size = util_format_get_2d_size(format, transfer->stride,
                             transfer->box.height);
             iws->buffer_write(iws, tex->buffer, offset, size, transfer->data);

         } else {
             unsigned nby = util_format_get_nblocksy(format, transfer->box.y);
             int i;
             offset += util_format_get_stride(format, transfer->box.x);
             size = transfer->stride;

             for (i = 0; i < nby; i++) {
                     iws->buffer_write(iws, tex->buffer, offset, size, transfer->data);
                     offset += tex->stride;
             }
         }
         offset += layer_stride;
      }
   } else {
      uint8_t *map = pipe_transfer_map(pipe, &itransfer->b);
      if (map == NULL)
         goto nomap;

      for (i = 0; i < box->depth; i++) {
         util_copy_rect(map,
                        resource->format,
                        itransfer->b.stride, /* bytes */
                        0, 0,
                        box->width,
                        box->height,
                        src_data,
                        stride,       /* bytes */
                        0, 0);
         map += itransfer->b.layer_stride;
         src_data += layer_stride;
      }
nomap:
      if (map)
         pipe_transfer_unmap(pipe, &itransfer->b);
   }

out:
   if (itransfer)
      pipe_transfer_destroy(pipe, &itransfer->b);
}



struct u_resource_vtbl i915_texture_vtbl =
{
   i915_texture_get_handle,	      /* get_handle */
   i915_texture_destroy,	      /* resource_destroy */
   i915_texture_get_transfer,	      /* get_transfer */
   i915_transfer_destroy,	      /* transfer_destroy */
   i915_texture_transfer_map,	      /* transfer_map */
   u_default_transfer_flush_region,   /* transfer_flush_region */
   i915_texture_transfer_unmap,	      /* transfer_unmap */
   i915_transfer_inline_write         /* transfer_inline_write */
};




struct pipe_resource *
i915_texture_create(struct pipe_screen *screen,
                    const struct pipe_resource *template,
                    boolean force_untiled)
{
   struct i915_screen *is = i915_screen(screen);
   struct i915_winsys *iws = is->iws;
   struct i915_texture *tex = CALLOC_STRUCT(i915_texture);
   unsigned buf_usage = 0;

   if (!tex)
      return NULL;

   tex->b.b = *template;
   tex->b.vtbl = &i915_texture_vtbl;
   pipe_reference_init(&tex->b.b.reference, 1);
   tex->b.b.screen = screen;

   if (force_untiled)
      tex->tiling = I915_TILE_NONE;
   else
      tex->tiling = i915_texture_tiling(is, tex);

   if (is->is_i945) {
      if (!i945_texture_layout(tex))
         goto fail;
   } else {
      if (!i915_texture_layout(tex))
         goto fail;
   }

   /* for scanouts and cursors, cursors arn't scanouts */

   /* XXX: use a custom flag for cursors, don't rely on magically
    * guessing that this is Xorg asking for a cursor
    */
   if ((template->bind & PIPE_BIND_SCANOUT) && template->width0 != 64)
      buf_usage = I915_NEW_SCANOUT;
   else
      buf_usage = I915_NEW_TEXTURE;

   if (tex->tiling == I915_TILE_NONE)
      tex->buffer = iws->buffer_create(iws, tex->total_nblocksy * tex->stride,
                                       buf_usage);
   else
      tex->buffer = iws->buffer_create_tiled(iws, &tex->stride, tex->total_nblocksy,
                                             &tex->tiling, buf_usage);
   if (!tex->buffer)
      goto fail;

   I915_DBG(DBG_TEXTURE, "%s: %p stride %u, blocks (%u, %u) tiling %s\n", __func__,
            tex, tex->stride,
            tex->stride / util_format_get_blocksize(tex->b.b.format),
            tex->total_nblocksy, get_tiling_string(tex->tiling));

   return &tex->b.b;

fail:
   FREE(tex);
   return NULL;
}

struct pipe_resource *
i915_texture_from_handle(struct pipe_screen * screen,
			  const struct pipe_resource *template,
			  struct winsys_handle *whandle)
{
   struct i915_screen *is = i915_screen(screen);
   struct i915_texture *tex;
   struct i915_winsys *iws = is->iws;
   struct i915_winsys_buffer *buffer;
   unsigned stride;
   enum i915_winsys_buffer_tile tiling;

   assert(screen);

   buffer = iws->buffer_from_handle(iws, whandle, &tiling, &stride);

   /* Only supports one type */
   if ((template->target != PIPE_TEXTURE_2D &&
       template->target != PIPE_TEXTURE_RECT) ||
       template->last_level != 0 ||
       template->depth0 != 1) {
      return NULL;
   }

   tex = CALLOC_STRUCT(i915_texture);
   if (!tex)
      return NULL;

   tex->b.b = *template;
   tex->b.vtbl = &i915_texture_vtbl;
   pipe_reference_init(&tex->b.b.reference, 1);
   tex->b.b.screen = screen;

   tex->stride = stride;
   tex->tiling = tiling;
   tex->total_nblocksy = align_nblocksy(tex->b.b.format, tex->b.b.height0, 8);

   i915_texture_set_level_info(tex, 0, 1);
   i915_texture_set_image_offset(tex, 0, 0, 0, 0);

   tex->buffer = buffer;

   I915_DBG(DBG_TEXTURE, "%s: %p stride %u, blocks (%u, %u) tiling %s\n", __func__,
            tex, tex->stride,
            tex->stride / util_format_get_blocksize(tex->b.b.format),
            tex->total_nblocksy, get_tiling_string(tex->tiling));

   return &tex->b.b;
}

