/*
 * Mesa 3-D graphics library
 *
 * Copyright (C) 2011 VMware, 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 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.
 */

/**
 * Functions for mapping/unmapping texture images.
 */


#include "main/context.h"
#include "main/fbobject.h"
#include "main/teximage.h"
#include "swrast/swrast.h"
#include "swrast/s_context.h"


/**
 * Allocate a new swrast_texture_image (a subclass of gl_texture_image).
 * Called via ctx->Driver.NewTextureImage().
 */
struct gl_texture_image *
_swrast_new_texture_image( struct gl_context *ctx )
{
   (void) ctx;
   return (struct gl_texture_image *) CALLOC_STRUCT(swrast_texture_image);
}


/**
 * Free a swrast_texture_image (a subclass of gl_texture_image).
 * Called via ctx->Driver.DeleteTextureImage().
 */
void
_swrast_delete_texture_image(struct gl_context *ctx,
                             struct gl_texture_image *texImage)
{
   /* Nothing special for the subclass yet */
   _mesa_delete_texture_image(ctx, texImage);
}


/**
 * Called via ctx->Driver.AllocTextureImageBuffer()
 */
GLboolean
_swrast_alloc_texture_image_buffer(struct gl_context *ctx,
                                   struct gl_texture_image *texImage,
                                   gl_format format, GLsizei width,
                                   GLsizei height, GLsizei depth)
{
   struct swrast_texture_image *swImg = swrast_texture_image(texImage);
   GLuint bytes = _mesa_format_image_size(format, width, height, depth);
   GLuint i;

   /* This _should_ be true (revisit if these ever fail) */
   assert(texImage->Width == width);
   assert(texImage->Height == height);
   assert(texImage->Depth == depth);

   assert(!swImg->Buffer);
   swImg->Buffer = _mesa_align_malloc(bytes, 512);
   if (!swImg->Buffer)
      return GL_FALSE;

   /* RowStride and ImageOffsets[] describe how to address texels in 'Data' */
   swImg->RowStride = width;

   /* Allocate the ImageOffsets array and initialize to typical values.
    * We allocate the array for 1D/2D textures too in order to avoid special-
    * case code in the texstore routines.
    */
   swImg->ImageOffsets = (GLuint *) malloc(depth * sizeof(GLuint));
   if (!swImg->ImageOffsets)
      return GL_FALSE;

   for (i = 0; i < depth; i++) {
      swImg->ImageOffsets[i] = i * width * height;
   }

   if ((width == 1 || _mesa_is_pow_two(texImage->Width2)) &&
       (height == 1 || _mesa_is_pow_two(texImage->Height2)) &&
       (depth == 1 || _mesa_is_pow_two(texImage->Depth2)))
      swImg->_IsPowerOfTwo = GL_TRUE;
   else
      swImg->_IsPowerOfTwo = GL_FALSE;

   /* Compute Width/Height/DepthScale for mipmap lod computation */
   if (texImage->TexObject->Target == GL_TEXTURE_RECTANGLE_NV) {
      /* scale = 1.0 since texture coords directly map to texels */
      swImg->WidthScale = 1.0;
      swImg->HeightScale = 1.0;
      swImg->DepthScale = 1.0;
   }
   else {
      swImg->WidthScale = (GLfloat) texImage->Width;
      swImg->HeightScale = (GLfloat) texImage->Height;
      swImg->DepthScale = (GLfloat) texImage->Depth;
   }

   return GL_TRUE;
}


/**
 * Called via ctx->Driver.FreeTextureImageBuffer()
 */
void
_swrast_free_texture_image_buffer(struct gl_context *ctx,
                                  struct gl_texture_image *texImage)
{
   struct swrast_texture_image *swImage = swrast_texture_image(texImage);
   if (swImage->Buffer) {
      _mesa_align_free(swImage->Buffer);
      swImage->Buffer = NULL;
   }

   if (swImage->ImageOffsets) {
      free(swImage->ImageOffsets);
      swImage->ImageOffsets = NULL;
   }
}


/**
 * Error checking for debugging only.
 */
static void
_mesa_check_map_teximage(struct gl_texture_image *texImage,
                         GLuint slice, GLuint x, GLuint y, GLuint w, GLuint h)
{

   if (texImage->TexObject->Target == GL_TEXTURE_1D)
      assert(y == 0 && h == 1);

   assert(x < texImage->Width || texImage->Width == 0);
   assert(y < texImage->Height || texImage->Height == 0);
   assert(x + w <= texImage->Width);
   assert(y + h <= texImage->Height);
}

/**
 * Map a 2D slice of a texture image into user space.
 * (x,y,w,h) defines a region of interest (ROI).  Reading/writing texels
 * outside of the ROI is undefined.
 *
 * \param texImage  the texture image
 * \param slice  the 3D image slice or array texture slice
 * \param x, y, w, h  region of interest
 * \param mode  bitmask of GL_MAP_READ_BIT, GL_MAP_WRITE_BIT
 * \param mapOut  returns start of mapping of region of interest
 * \param rowStrideOut  returns row stride (in bytes)
 */
void
_swrast_map_teximage(struct gl_context *ctx,
                     struct gl_texture_image *texImage,
                     GLuint slice,
                     GLuint x, GLuint y, GLuint w, GLuint h,
                     GLbitfield mode,
                     GLubyte **mapOut,
                     GLint *rowStrideOut)
{
   struct swrast_texture_image *swImage = swrast_texture_image(texImage);
   GLubyte *map;
   GLint stride, texelSize;
   GLuint bw, bh;

   _mesa_check_map_teximage(texImage, slice, x, y, w, h);

   texelSize = _mesa_get_format_bytes(texImage->TexFormat);
   stride = _mesa_format_row_stride(texImage->TexFormat, texImage->Width);
   _mesa_get_format_block_size(texImage->TexFormat, &bw, &bh);

   assert(swImage->Buffer);

   map = swImage->Buffer;

   if (texImage->TexObject->Target == GL_TEXTURE_3D ||
       texImage->TexObject->Target == GL_TEXTURE_2D_ARRAY) {
      GLuint sliceSize = _mesa_format_image_size(texImage->TexFormat,
                                                 texImage->Width,
                                                 texImage->Height,
                                                 1);
      assert(slice < texImage->Depth);
      map += slice * sliceSize;
   } else if (texImage->TexObject->Target == GL_TEXTURE_1D_ARRAY) {
      GLuint sliceSize = _mesa_format_image_size(texImage->TexFormat,
                                                 texImage->Width,
                                                 1,
                                                 1);
      assert(slice < texImage->Height);
      map += slice * sliceSize;
   }

   /* apply x/y offset to map address */
   map += stride * (y / bh) + texelSize * (x / bw);

   *mapOut = map;
   *rowStrideOut = stride;
}

void
_swrast_unmap_teximage(struct gl_context *ctx,
                       struct gl_texture_image *texImage,
                       GLuint slice)
{
   /* nop */
}


void
_swrast_map_texture(struct gl_context *ctx, struct gl_texture_object *texObj)
{
   const GLuint faces = texObj->Target == GL_TEXTURE_CUBE_MAP ? 6 : 1;
   GLuint face, level;

   for (face = 0; face < faces; face++) {
      for (level = texObj->BaseLevel; level < MAX_TEXTURE_LEVELS; level++) {
         struct gl_texture_image *texImage = texObj->Image[face][level];
         if (texImage) {
            struct swrast_texture_image *swImage =
               swrast_texture_image(texImage);

            /* XXX we'll eventually call _swrast_map_teximage() here */
            swImage->Data = swImage->Buffer;
            assert(swImage->Buffer);
         }
      }
   }
}


void
_swrast_unmap_texture(struct gl_context *ctx, struct gl_texture_object *texObj)
{
   const GLuint faces = texObj->Target == GL_TEXTURE_CUBE_MAP ? 6 : 1;
   GLuint face, level;

   for (face = 0; face < faces; face++) {
      for (level = texObj->BaseLevel; level < MAX_TEXTURE_LEVELS; level++) {
         struct gl_texture_image *texImage = texObj->Image[face][level];
         if (texImage) {
            struct swrast_texture_image *swImage
               = swrast_texture_image(texImage);

            /* XXX we'll eventually call _swrast_unmap_teximage() here */
            swImage->Data = NULL;
         }
      }
   }
}


/**
 * Map all textures for reading prior to software rendering.
 */
void
_swrast_map_textures(struct gl_context *ctx)
{
   GLbitfield enabledUnits = ctx->Texture._EnabledUnits;

   /* loop over enabled texture units */
   while (enabledUnits) {
      GLuint unit = _mesa_ffs(enabledUnits) - 1;
      struct gl_texture_object *texObj = ctx->Texture.Unit[unit]._Current;
      
      _swrast_map_texture(ctx, texObj);

      enabledUnits &= ~(1 << unit);
   }
}


/**
 * Unmap all textures for reading prior to software rendering.
 */
void
_swrast_unmap_textures(struct gl_context *ctx)
{
   GLbitfield enabledUnits = ctx->Texture._EnabledUnits;

   /* loop over enabled texture units */
   while (enabledUnits) {
      GLuint unit = _mesa_ffs(enabledUnits) - 1;
      struct gl_texture_object *texObj = ctx->Texture.Unit[unit]._Current;
      
      _swrast_unmap_texture(ctx, texObj);

      enabledUnits &= ~(1 << unit);
   }
}


/**
 * Map or unmap any textures that we may be rendering to as renderbuffers.
 */
static void
map_unmap_renderbuffers(struct gl_context *ctx,
                        struct gl_framebuffer *fb,
                        GLboolean map)
{
   GLuint i;

   for (i = 0; i < Elements(fb->Attachment); i++) {
      struct gl_texture_object *texObj = fb->Attachment[i].Texture;
      if (texObj) {
         const GLuint level = fb->Attachment[i].TextureLevel;
         const GLuint face = fb->Attachment[i].CubeMapFace;
         struct gl_texture_image *texImage = texObj->Image[face][level];
         if (texImage) {
            struct swrast_texture_image *swImage
               = swrast_texture_image(texImage);

            if (map) {
               /* XXX we'll eventually call _swrast_map_teximage() here */
               swImage->Data = swImage->Buffer;
            }
            else {
               /* XXX we'll eventually call _swrast_unmap_teximage() here */
               swImage->Data = NULL;
            }
         }
      }
   }
}


void
_swrast_map_renderbuffers(struct gl_context *ctx)
{
   map_unmap_renderbuffers(ctx, ctx->DrawBuffer, GL_TRUE);
   if (ctx->ReadBuffer != ctx->DrawBuffer)
      map_unmap_renderbuffers(ctx, ctx->ReadBuffer, GL_TRUE);
}


void
_swrast_unmap_renderbuffers(struct gl_context *ctx)
{
   map_unmap_renderbuffers(ctx, ctx->DrawBuffer, GL_FALSE);
   if (ctx->ReadBuffer != ctx->DrawBuffer)
      map_unmap_renderbuffers(ctx, ctx->ReadBuffer, GL_FALSE);
}



/**
 * Called via ctx->Driver.AllocTextureStorage()
 * Just have to allocate memory for the texture images.
 */
GLboolean
_swrast_AllocTextureStorage(struct gl_context *ctx,
                            struct gl_texture_object *texObj,
                            GLsizei levels, GLsizei width,
                            GLsizei height, GLsizei depth)
{
   const GLint numFaces = (texObj->Target == GL_TEXTURE_CUBE_MAP) ? 6 : 1;
   GLint face, level;

   for (face = 0; face < numFaces; face++) {
      for (level = 0; level < levels; level++) {
         struct gl_texture_image *texImage = texObj->Image[face][level];
         if (!_swrast_alloc_texture_image_buffer(ctx, texImage,
                                                 texImage->TexFormat,
                                                 texImage->Width,
                                                 texImage->Height,
                                                 texImage->Depth)) {
            return GL_FALSE;
         }
      }
   }

   return GL_TRUE;
}

