/*
 * Mesa 3-D graphics library
 * Version:  6.5
 *
 * Copyright (C) 1999-2006  Brian Paul   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, 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
 * BRIAN PAUL 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 "main/glheader.h"
#include "main/condrender.h"
#include "main/image.h"
#include "main/macros.h"
#include "main/format_unpack.h"
#include "main/format_pack.h"
#include "s_context.h"


#define ABS(X)   ((X) < 0 ? -(X) : (X))


/**
 * Generate a row resampler function for GL_NEAREST mode.
 */
#define RESAMPLE(NAME, PIXELTYPE, SIZE)			\
static void						\
NAME(GLint srcWidth, GLint dstWidth,			\
     const GLvoid *srcBuffer, GLvoid *dstBuffer,	\
     GLboolean flip)					\
{							\
   const PIXELTYPE *src = (const PIXELTYPE *) srcBuffer;\
   PIXELTYPE *dst = (PIXELTYPE *) dstBuffer;		\
   GLint dstCol;					\
							\
   if (flip) {						\
      for (dstCol = 0; dstCol < dstWidth; dstCol++) {	\
         GLint srcCol = (dstCol * srcWidth) / dstWidth;	\
         ASSERT(srcCol >= 0);				\
         ASSERT(srcCol < srcWidth);			\
         srcCol = srcWidth - 1 - srcCol; /* flip */	\
         if (SIZE == 1) {				\
            dst[dstCol] = src[srcCol];			\
         }						\
         else if (SIZE == 2) {				\
            dst[dstCol*2+0] = src[srcCol*2+0];		\
            dst[dstCol*2+1] = src[srcCol*2+1];		\
         }						\
         else if (SIZE == 4) {				\
            dst[dstCol*4+0] = src[srcCol*4+0];		\
            dst[dstCol*4+1] = src[srcCol*4+1];		\
            dst[dstCol*4+2] = src[srcCol*4+2];		\
            dst[dstCol*4+3] = src[srcCol*4+3];		\
         }						\
      }							\
   }							\
   else {						\
      for (dstCol = 0; dstCol < dstWidth; dstCol++) {	\
         GLint srcCol = (dstCol * srcWidth) / dstWidth;	\
         ASSERT(srcCol >= 0);				\
         ASSERT(srcCol < srcWidth);			\
         if (SIZE == 1) {				\
            dst[dstCol] = src[srcCol];			\
         }						\
         else if (SIZE == 2) {				\
            dst[dstCol*2+0] = src[srcCol*2+0];		\
            dst[dstCol*2+1] = src[srcCol*2+1];		\
         }						\
         else if (SIZE == 4) {				\
            dst[dstCol*4+0] = src[srcCol*4+0];		\
            dst[dstCol*4+1] = src[srcCol*4+1];		\
            dst[dstCol*4+2] = src[srcCol*4+2];		\
            dst[dstCol*4+3] = src[srcCol*4+3];		\
         }						\
      }							\
   }							\
}

/**
 * Resamplers for 1, 2, 4, 8 and 16-byte pixels.
 */
RESAMPLE(resample_row_1, GLubyte, 1)
RESAMPLE(resample_row_2, GLushort, 1)
RESAMPLE(resample_row_4, GLuint, 1)
RESAMPLE(resample_row_8, GLuint, 2)
RESAMPLE(resample_row_16, GLuint, 4)


/**
 * Blit color, depth or stencil with GL_NEAREST filtering.
 */
static void
blit_nearest(struct gl_context *ctx,
             GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
             GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
             GLbitfield buffer)
{
   struct gl_renderbuffer *readRb, *drawRb;
   struct gl_renderbuffer_attachment *readAtt, *drawAtt;
   struct gl_framebuffer *readFb = ctx->ReadBuffer;
   struct gl_framebuffer *drawFb = ctx->DrawBuffer;
   GLuint numDrawBuffers = 0;
   GLuint i;

   const GLint srcWidth = ABS(srcX1 - srcX0);
   const GLint dstWidth = ABS(dstX1 - dstX0);
   const GLint srcHeight = ABS(srcY1 - srcY0);
   const GLint dstHeight = ABS(dstY1 - dstY0);

   const GLint srcXpos = MIN2(srcX0, srcX1);
   const GLint srcYpos = MIN2(srcY0, srcY1);
   const GLint dstXpos = MIN2(dstX0, dstX1);
   const GLint dstYpos = MIN2(dstY0, dstY1);

   const GLboolean invertX = (srcX1 < srcX0) ^ (dstX1 < dstX0);
   const GLboolean invertY = (srcY1 < srcY0) ^ (dstY1 < dstY0);
   enum mode {
      DIRECT,
      UNPACK_RGBA_FLOAT,
      UNPACK_Z_FLOAT,
      UNPACK_Z_INT,
      UNPACK_S,
   } mode;
   GLubyte *srcMap, *dstMap;
   GLint srcRowStride, dstRowStride;
   GLint dstRow;

   GLint pixelSize;
   GLvoid *srcBuffer, *dstBuffer;
   GLint prevY = -1;

   typedef void (*resample_func)(GLint srcWidth, GLint dstWidth,
                                 const GLvoid *srcBuffer, GLvoid *dstBuffer,
                                 GLboolean flip);
   resample_func resampleRow;

   switch (buffer) {
   case GL_COLOR_BUFFER_BIT:
      readAtt = &readFb->Attachment[readFb->_ColorReadBufferIndex];
      readRb = readFb->_ColorReadBuffer;
      numDrawBuffers = drawFb->_NumColorDrawBuffers;
      break;
   case GL_DEPTH_BUFFER_BIT:
      readAtt = &readFb->Attachment[BUFFER_DEPTH];
      drawAtt = &drawFb->Attachment[BUFFER_DEPTH];
      readRb = readAtt->Renderbuffer;
      drawRb = drawAtt->Renderbuffer;
      numDrawBuffers = 1;

      /* Note that for depth/stencil, the formats of src/dst must match.  By
       * using the core helpers for pack/unpack, we avoid needing to handle
       * masking for things like DEPTH copies of Z24S8.
       */
      if (readRb->Format == MESA_FORMAT_Z32_FLOAT ||
	  readRb->Format == MESA_FORMAT_Z32_FLOAT_X24S8) {
	 mode = UNPACK_Z_FLOAT;
      } else {
	 mode = UNPACK_Z_INT;
      }
      pixelSize = 4;
      break;
   case GL_STENCIL_BUFFER_BIT:
      readAtt = &readFb->Attachment[BUFFER_STENCIL];
      drawAtt = &drawFb->Attachment[BUFFER_STENCIL];
      readRb = readAtt->Renderbuffer;
      drawRb = drawAtt->Renderbuffer;
      numDrawBuffers = 1;
      mode = UNPACK_S;
      pixelSize = 1;
      break;
   default:
      _mesa_problem(ctx, "unexpected buffer in blit_nearest()");
      return;
   }

   /* Blit to all the draw buffers */
   for (i = 0; i < numDrawBuffers; i++) {
      if (buffer == GL_COLOR_BUFFER_BIT) {
         int idx = drawFb->_ColorDrawBufferIndexes[i];
         if (idx == -1)
            continue;
         drawAtt = &drawFb->Attachment[idx];
         drawRb = drawAtt->Renderbuffer;

         if (!drawRb)
            continue;

         if (readRb->Format == drawRb->Format) {
            mode = DIRECT;
            pixelSize = _mesa_get_format_bytes(readRb->Format);
         } else {
            mode = UNPACK_RGBA_FLOAT;
            pixelSize = 16;
         }
      }

      /* choose row resampler */
      switch (pixelSize) {
      case 1:
         resampleRow = resample_row_1;
         break;
      case 2:
         resampleRow = resample_row_2;
         break;
      case 4:
         resampleRow = resample_row_4;
         break;
      case 8:
         resampleRow = resample_row_8;
         break;
      case 16:
         resampleRow = resample_row_16;
         break;
      default:
         _mesa_problem(ctx, "unexpected pixel size (%d) in blit_nearest",
                       pixelSize);
         return;
      }

      if ((readRb == drawRb) ||
          (readAtt->Texture && drawAtt->Texture &&
           (readAtt->Texture == drawAtt->Texture))) {
         /* map whole buffer for read/write */
         /* XXX we could be clever and just map the union region of the
          * source and dest rects.
          */
         GLubyte *map;
         GLint rowStride;
         GLint formatSize = _mesa_get_format_bytes(readRb->Format);

         ctx->Driver.MapRenderbuffer(ctx, readRb, 0, 0,
                                     readRb->Width, readRb->Height,
                                     GL_MAP_READ_BIT | GL_MAP_WRITE_BIT,
                                     &map, &rowStride);
         if (!map) {
            _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBlitFramebuffer");
            return;
         }

         srcMap = map + srcYpos * rowStride + srcXpos * formatSize;
         dstMap = map + dstYpos * rowStride + dstXpos * formatSize;

         /* this handles overlapping copies */
         if (srcY0 < dstY0) {
            /* copy in reverse (top->down) order */
            srcMap += rowStride * (readRb->Height - 1);
            dstMap += rowStride * (readRb->Height - 1);
            srcRowStride = -rowStride;
            dstRowStride = -rowStride;
         }
         else {
            /* copy in normal (bottom->up) order */
            srcRowStride = rowStride;
            dstRowStride = rowStride;
         }
      }
      else {
         /* different src/dst buffers */
         ctx->Driver.MapRenderbuffer(ctx, readRb,
                                     srcXpos, srcYpos,
                                     srcWidth, srcHeight,
                                     GL_MAP_READ_BIT, &srcMap, &srcRowStride);
         if (!srcMap) {
            _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBlitFramebuffer");
            return;
         }
         ctx->Driver.MapRenderbuffer(ctx, drawRb,
                                     dstXpos, dstYpos,
                                     dstWidth, dstHeight,
                                     GL_MAP_WRITE_BIT, &dstMap, &dstRowStride);
         if (!dstMap) {
            ctx->Driver.UnmapRenderbuffer(ctx, readRb);
            _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBlitFramebuffer");
            return;
         }
      }

      /* allocate the src/dst row buffers */
      srcBuffer = malloc(pixelSize * srcWidth);
      if (!srcBuffer) {
         _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBlitFrameBufferEXT");
         return;
      }
      dstBuffer = malloc(pixelSize * dstWidth);
      if (!dstBuffer) {
         free(srcBuffer);
         _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBlitFrameBufferEXT");
         return;
      }

      for (dstRow = 0; dstRow < dstHeight; dstRow++) {
         GLfloat srcRowF = (dstRow + 0.5F) / dstHeight * srcHeight - 0.5F;
         GLint srcRow = IROUND(srcRowF);
         GLubyte *dstRowStart = dstMap + dstRowStride * dstRow;

         ASSERT(srcRow >= 0);
         ASSERT(srcRow < srcHeight);

         if (invertY) {
            srcRow = srcHeight - 1 - srcRow;
         }

         /* get pixel row from source and resample to match dest width */
         if (prevY != srcRow) {
            GLubyte *srcRowStart = srcMap + srcRowStride * srcRow;

            switch (mode) {
            case DIRECT:
               memcpy(srcBuffer, srcRowStart, pixelSize * srcWidth);
               break;
            case UNPACK_RGBA_FLOAT:
               _mesa_unpack_rgba_row(readRb->Format, srcWidth, srcRowStart,
                                     srcBuffer);
               break;
            case UNPACK_Z_FLOAT:
               _mesa_unpack_float_z_row(readRb->Format, srcWidth, srcRowStart,
                                        srcBuffer);
               break;
            case UNPACK_Z_INT:
               _mesa_unpack_uint_z_row(readRb->Format, srcWidth, srcRowStart,
                                       srcBuffer);
               break;
            case UNPACK_S:
               _mesa_unpack_ubyte_stencil_row(readRb->Format, srcWidth,
                                              srcRowStart, srcBuffer);
               break;
            }

            (*resampleRow)(srcWidth, dstWidth, srcBuffer, dstBuffer, invertX);
            prevY = srcRow;
         }

         /* store pixel row in destination */
         switch (mode) {
         case DIRECT:
            memcpy(dstRowStart, dstBuffer, pixelSize * dstWidth);
            break;
         case UNPACK_RGBA_FLOAT:
            _mesa_pack_float_rgba_row(drawRb->Format, dstWidth, dstBuffer,
                                      dstRowStart);
            break;
         case UNPACK_Z_FLOAT:
            _mesa_pack_float_z_row(drawRb->Format, dstWidth, dstBuffer,
                                   dstRowStart);
            break;
         case UNPACK_Z_INT:
            _mesa_pack_uint_z_row(drawRb->Format, dstWidth, dstBuffer,
                                  dstRowStart);
            break;
         case UNPACK_S:
            _mesa_pack_ubyte_stencil_row(drawRb->Format, dstWidth, dstBuffer,
                                         dstRowStart);
            break;
         }
      }

      free(srcBuffer);
      free(dstBuffer);

      ctx->Driver.UnmapRenderbuffer(ctx, readRb);
      if (drawRb != readRb) {
         ctx->Driver.UnmapRenderbuffer(ctx, drawRb);
      }
   }
}



#define LERP(T, A, B)  ( (A) + (T) * ((B) - (A)) )

static inline GLfloat
lerp_2d(GLfloat a, GLfloat b,
        GLfloat v00, GLfloat v10, GLfloat v01, GLfloat v11)
{
   const GLfloat temp0 = LERP(a, v00, v10);
   const GLfloat temp1 = LERP(a, v01, v11);
   return LERP(b, temp0, temp1);
}


/**
 * Bilinear interpolation of two source rows.
 * GLubyte pixels.
 */
static void
resample_linear_row_ub(GLint srcWidth, GLint dstWidth,
                       const GLvoid *srcBuffer0, const GLvoid *srcBuffer1,
                       GLvoid *dstBuffer, GLboolean flip, GLfloat rowWeight)
{
   const GLubyte (*srcColor0)[4] = (const GLubyte (*)[4]) srcBuffer0;
   const GLubyte (*srcColor1)[4] = (const GLubyte (*)[4]) srcBuffer1;
   GLubyte (*dstColor)[4] = (GLubyte (*)[4]) dstBuffer;
   GLint dstCol;

   for (dstCol = 0; dstCol < dstWidth; dstCol++) {
      const GLfloat srcCol = (dstCol + 0.5F) / dstWidth * srcWidth - 0.5F;
      GLint srcCol0 = MAX2(0, IFLOOR(srcCol));
      GLint srcCol1 = srcCol0 + 1;
      GLfloat colWeight = srcCol - srcCol0; /* fractional part of srcCol */
      GLfloat red, green, blue, alpha;

      ASSERT(srcCol0 < srcWidth);
      ASSERT(srcCol1 <= srcWidth);

      if (srcCol1 == srcWidth) {
         /* last column fudge */
         srcCol1--;
         colWeight = 0.0;
      }

      if (flip) {
         srcCol0 = srcWidth - 1 - srcCol0;
         srcCol1 = srcWidth - 1 - srcCol1;
      }

      red = lerp_2d(colWeight, rowWeight,
                    srcColor0[srcCol0][RCOMP], srcColor0[srcCol1][RCOMP],
                    srcColor1[srcCol0][RCOMP], srcColor1[srcCol1][RCOMP]);
      green = lerp_2d(colWeight, rowWeight,
                    srcColor0[srcCol0][GCOMP], srcColor0[srcCol1][GCOMP],
                    srcColor1[srcCol0][GCOMP], srcColor1[srcCol1][GCOMP]);
      blue = lerp_2d(colWeight, rowWeight,
                    srcColor0[srcCol0][BCOMP], srcColor0[srcCol1][BCOMP],
                    srcColor1[srcCol0][BCOMP], srcColor1[srcCol1][BCOMP]);
      alpha = lerp_2d(colWeight, rowWeight,
                    srcColor0[srcCol0][ACOMP], srcColor0[srcCol1][ACOMP],
                    srcColor1[srcCol0][ACOMP], srcColor1[srcCol1][ACOMP]);
      
      dstColor[dstCol][RCOMP] = IFLOOR(red);
      dstColor[dstCol][GCOMP] = IFLOOR(green);
      dstColor[dstCol][BCOMP] = IFLOOR(blue);
      dstColor[dstCol][ACOMP] = IFLOOR(alpha);
   }
}


/**
 * Bilinear interpolation of two source rows.  floating point pixels.
 */
static void
resample_linear_row_float(GLint srcWidth, GLint dstWidth,
                          const GLvoid *srcBuffer0, const GLvoid *srcBuffer1,
                          GLvoid *dstBuffer, GLboolean flip, GLfloat rowWeight)
{
   const GLfloat (*srcColor0)[4] = (const GLfloat (*)[4]) srcBuffer0;
   const GLfloat (*srcColor1)[4] = (const GLfloat (*)[4]) srcBuffer1;
   GLfloat (*dstColor)[4] = (GLfloat (*)[4]) dstBuffer;
   GLint dstCol;

   for (dstCol = 0; dstCol < dstWidth; dstCol++) {
      const GLfloat srcCol = (dstCol + 0.5F) / dstWidth * srcWidth - 0.5F;
      GLint srcCol0 = MAX2(0, IFLOOR(srcCol));
      GLint srcCol1 = srcCol0 + 1;
      GLfloat colWeight = srcCol - srcCol0; /* fractional part of srcCol */
      GLfloat red, green, blue, alpha;

      ASSERT(srcCol0 < srcWidth);
      ASSERT(srcCol1 <= srcWidth);

      if (srcCol1 == srcWidth) {
         /* last column fudge */
         srcCol1--;
         colWeight = 0.0;
      }

      if (flip) {
         srcCol0 = srcWidth - 1 - srcCol0;
         srcCol1 = srcWidth - 1 - srcCol1;
      }

      red = lerp_2d(colWeight, rowWeight,
                    srcColor0[srcCol0][RCOMP], srcColor0[srcCol1][RCOMP],
                    srcColor1[srcCol0][RCOMP], srcColor1[srcCol1][RCOMP]);
      green = lerp_2d(colWeight, rowWeight,
                    srcColor0[srcCol0][GCOMP], srcColor0[srcCol1][GCOMP],
                    srcColor1[srcCol0][GCOMP], srcColor1[srcCol1][GCOMP]);
      blue = lerp_2d(colWeight, rowWeight,
                    srcColor0[srcCol0][BCOMP], srcColor0[srcCol1][BCOMP],
                    srcColor1[srcCol0][BCOMP], srcColor1[srcCol1][BCOMP]);
      alpha = lerp_2d(colWeight, rowWeight,
                    srcColor0[srcCol0][ACOMP], srcColor0[srcCol1][ACOMP],
                    srcColor1[srcCol0][ACOMP], srcColor1[srcCol1][ACOMP]);
      
      dstColor[dstCol][RCOMP] = red;
      dstColor[dstCol][GCOMP] = green;
      dstColor[dstCol][BCOMP] = blue;
      dstColor[dstCol][ACOMP] = alpha;
   }
}



/**
 * Bilinear filtered blit (color only, non-integer values).
 */
static void
blit_linear(struct gl_context *ctx,
            GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
            GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1)
{
   struct gl_framebuffer *drawFb = ctx->DrawBuffer;
   struct gl_framebuffer *readFb = ctx->ReadBuffer;
   struct gl_renderbuffer *readRb = readFb->_ColorReadBuffer;
   struct gl_renderbuffer_attachment *readAtt =
      &readFb->Attachment[readFb->_ColorReadBufferIndex];

   const GLint srcWidth = ABS(srcX1 - srcX0);
   const GLint dstWidth = ABS(dstX1 - dstX0);
   const GLint srcHeight = ABS(srcY1 - srcY0);
   const GLint dstHeight = ABS(dstY1 - dstY0);

   const GLint srcXpos = MIN2(srcX0, srcX1);
   const GLint srcYpos = MIN2(srcY0, srcY1);
   const GLint dstXpos = MIN2(dstX0, dstX1);
   const GLint dstYpos = MIN2(dstY0, dstY1);

   const GLboolean invertX = (srcX1 < srcX0) ^ (dstX1 < dstX0);
   const GLboolean invertY = (srcY1 < srcY0) ^ (dstY1 < dstY0);

   GLint dstRow;

   GLint pixelSize;
   GLvoid *srcBuffer0, *srcBuffer1;
   GLint srcBufferY0 = -1, srcBufferY1 = -1;
   GLvoid *dstBuffer;

   gl_format readFormat = _mesa_get_srgb_format_linear(readRb->Format);
   GLuint bpp = _mesa_get_format_bytes(readFormat);

   GLenum pixelType;

   GLubyte *srcMap, *dstMap;
   GLint srcRowStride, dstRowStride;
   GLuint i;


   /* Determine datatype for resampling */
   if (_mesa_get_format_max_bits(readFormat) == 8 &&
       _mesa_get_format_datatype(readFormat) == GL_UNSIGNED_NORMALIZED) {
      pixelType = GL_UNSIGNED_BYTE;
      pixelSize = 4 * sizeof(GLubyte);
   }
   else {
      pixelType = GL_FLOAT;
      pixelSize = 4 * sizeof(GLfloat);
   }

   /* Allocate the src/dst row buffers.
    * Keep two adjacent src rows around for bilinear sampling.
    */
   srcBuffer0 = malloc(pixelSize * srcWidth);
   if (!srcBuffer0) {
      _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBlitFrameBufferEXT");
      return;
   }
   srcBuffer1 = malloc(pixelSize * srcWidth);
   if (!srcBuffer1) {
      free(srcBuffer0);
      _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBlitFrameBufferEXT");
      return;
   }
   dstBuffer = malloc(pixelSize * dstWidth);
   if (!dstBuffer) {
      free(srcBuffer0);
      free(srcBuffer1);
      _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBlitFrameBufferEXT");
      return;
   }

   for (i = 0; i < drawFb->_NumColorDrawBuffers; i++) {
      GLint idx = drawFb->_ColorDrawBufferIndexes[i];
      struct gl_renderbuffer_attachment *drawAtt;
      struct gl_renderbuffer *drawRb;
      gl_format drawFormat;

      if (idx == -1)
         continue;

      drawAtt = &drawFb->Attachment[idx];
      drawRb = drawAtt->Renderbuffer;
      if (!drawRb)
         continue;

      drawFormat = _mesa_get_srgb_format_linear(drawRb->Format);

      /*
       * Map src / dst renderbuffers
       */
      if ((readRb == drawRb) ||
          (readAtt->Texture && drawAtt->Texture &&
           (readAtt->Texture == drawAtt->Texture))) {
         /* map whole buffer for read/write */
         ctx->Driver.MapRenderbuffer(ctx, readRb,
                                     0, 0, readRb->Width, readRb->Height,
                                     GL_MAP_READ_BIT | GL_MAP_WRITE_BIT,
                                     &srcMap, &srcRowStride);
         if (!srcMap) {
            free(srcBuffer0);
            free(srcBuffer1);
            free(dstBuffer);
            _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBlitFramebuffer");
            return;
         }

         dstMap = srcMap;
         dstRowStride = srcRowStride;
      }
      else {
         /* different src/dst buffers */
         /* XXX with a bit of work we could just map the regions to be
          * read/written instead of the whole buffers.
          */
         ctx->Driver.MapRenderbuffer(ctx, readRb,
                                     0, 0, readRb->Width, readRb->Height,
                                     GL_MAP_READ_BIT, &srcMap, &srcRowStride);
         if (!srcMap) {
            free(srcBuffer0);
            free(srcBuffer1);
            free(dstBuffer);
            _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBlitFramebuffer");
            return;
         }
         ctx->Driver.MapRenderbuffer(ctx, drawRb,
                                     0, 0, drawRb->Width, drawRb->Height,
                                     GL_MAP_WRITE_BIT, &dstMap, &dstRowStride);
         if (!dstMap) {
            ctx->Driver.UnmapRenderbuffer(ctx, readRb);
            free(srcBuffer0);
            free(srcBuffer1);
            free(dstBuffer);
            _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBlitFramebuffer");
            return;
         }
      }

      for (dstRow = 0; dstRow < dstHeight; dstRow++) {
         const GLint dstY = dstYpos + dstRow;
         GLfloat srcRow = (dstRow + 0.5F) / dstHeight * srcHeight - 0.5F;
         GLint srcRow0 = MAX2(0, IFLOOR(srcRow));
         GLint srcRow1 = srcRow0 + 1;
         GLfloat rowWeight = srcRow - srcRow0; /* fractional part of srcRow */

         if (srcRow1 == srcHeight) {
            /* last row fudge */
            srcRow1 = srcRow0;
            rowWeight = 0.0;
         }

         if (invertY) {
            srcRow0 = srcHeight - 1 - srcRow0;
            srcRow1 = srcHeight - 1 - srcRow1;
         }

         srcY0 = srcYpos + srcRow0;
         srcY1 = srcYpos + srcRow1;

         /* get the two source rows */
         if (srcY0 == srcBufferY0 && srcY1 == srcBufferY1) {
            /* use same source row buffers again */
         }
         else if (srcY0 == srcBufferY1) {
            /* move buffer1 into buffer0 by swapping pointers */
            GLvoid *tmp = srcBuffer0;
            srcBuffer0 = srcBuffer1;
            srcBuffer1 = tmp;
            /* get y1 row */
            {
               GLubyte *src = srcMap + srcY1 * srcRowStride + srcXpos * bpp;
               if (pixelType == GL_UNSIGNED_BYTE) {
                  _mesa_unpack_ubyte_rgba_row(readFormat, srcWidth,
                                              src, srcBuffer1);
               }
               else {
                  _mesa_unpack_rgba_row(readFormat, srcWidth,
                                        src, srcBuffer1);
               }
            }
            srcBufferY0 = srcY0;
            srcBufferY1 = srcY1;
         }
         else {
            /* get both new rows */
            {
               GLubyte *src0 = srcMap + srcY0 * srcRowStride + srcXpos * bpp;
               GLubyte *src1 = srcMap + srcY1 * srcRowStride + srcXpos * bpp;
               if (pixelType == GL_UNSIGNED_BYTE) {
                  _mesa_unpack_ubyte_rgba_row(readFormat, srcWidth,
                                              src0, srcBuffer0);
                  _mesa_unpack_ubyte_rgba_row(readFormat, srcWidth,
                                              src1, srcBuffer1);
               }
               else {
                  _mesa_unpack_rgba_row(readFormat, srcWidth, src0, srcBuffer0);
                  _mesa_unpack_rgba_row(readFormat, srcWidth, src1, srcBuffer1);
               }
            }
            srcBufferY0 = srcY0;
            srcBufferY1 = srcY1;
         }

         if (pixelType == GL_UNSIGNED_BYTE) {
            resample_linear_row_ub(srcWidth, dstWidth, srcBuffer0, srcBuffer1,
                                   dstBuffer, invertX, rowWeight);
         }
         else {
            resample_linear_row_float(srcWidth, dstWidth, srcBuffer0, srcBuffer1,
                                      dstBuffer, invertX, rowWeight);
         }

         /* store pixel row in destination */
         {
            GLubyte *dst = dstMap + dstY * dstRowStride + dstXpos * bpp;
            if (pixelType == GL_UNSIGNED_BYTE) {
               _mesa_pack_ubyte_rgba_row(drawFormat, dstWidth, dstBuffer, dst);
            }
            else {
               _mesa_pack_float_rgba_row(drawFormat, dstWidth, dstBuffer, dst);
            }
         }
      }

      free(srcBuffer0);
      free(srcBuffer1);
      free(dstBuffer);

      ctx->Driver.UnmapRenderbuffer(ctx, readRb);
      if (drawRb != readRb) {
         ctx->Driver.UnmapRenderbuffer(ctx, drawRb);
      }
   }
}



/**
 * Software fallback for glBlitFramebufferEXT().
 */
void
_swrast_BlitFramebuffer(struct gl_context *ctx,
                        GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
                        GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                        GLbitfield mask, GLenum filter)
{
   static const GLbitfield buffers[3] = {
      GL_COLOR_BUFFER_BIT,
      GL_DEPTH_BUFFER_BIT,
      GL_STENCIL_BUFFER_BIT
   };
   static const GLenum buffer_enums[3] = {
      GL_COLOR,
      GL_DEPTH,
      GL_STENCIL,
   };
   GLint i;

   if (!_mesa_clip_blit(ctx, &srcX0, &srcY0, &srcX1, &srcY1,
                        &dstX0, &dstY0, &dstX1, &dstY1)) {
      return;
   }

   if (SWRAST_CONTEXT(ctx)->NewState)
      _swrast_validate_derived(ctx);

   /* First, try covering whatever buffers possible using the fast 1:1 copy
    * path.
    */
   if (srcX1 - srcX0 == dstX1 - dstX0 &&
       srcY1 - srcY0 == dstY1 - dstY0 &&
       srcX0 < srcX1 &&
       srcY0 < srcY1 &&
       dstX0 < dstX1 &&
       dstY0 < dstY1) {
      for (i = 0; i < 3; i++) {
         if (mask & buffers[i]) {
	    if (swrast_fast_copy_pixels(ctx,
					srcX0, srcY0,
					srcX1 - srcX0, srcY1 - srcY0,
					dstX0, dstY0,
					buffer_enums[i])) {
	       mask &= ~buffers[i];
	    }
	 }
      }

      if (!mask)
	 return;
   }

   if (filter == GL_NEAREST) {
      for (i = 0; i < 3; i++) {
	 if (mask & buffers[i]) {
	    blit_nearest(ctx,  srcX0, srcY0, srcX1, srcY1,
			 dstX0, dstY0, dstX1, dstY1, buffers[i]);
	 }
      }
   }
   else {
      ASSERT(filter == GL_LINEAR);
      if (mask & GL_COLOR_BUFFER_BIT) {  /* depth/stencil not allowed */
	 blit_linear(ctx,  srcX0, srcY0, srcX1, srcY1,
		     dstX0, dstY0, dstX1, dstY1);
      }
   }

}
